1 import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
8 } from '@circlon/angular-tree-component';
9 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
10 import _ from 'lodash';
12 import { forkJoin, Subscription, timer as observableTimer } from 'rxjs';
13 import { RgwRealmService } from '~/app/shared/api/rgw-realm.service';
14 import { RgwZoneService } from '~/app/shared/api/rgw-zone.service';
15 import { RgwZonegroupService } from '~/app/shared/api/rgw-zonegroup.service';
16 import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
17 import { ActionLabelsI18n, TimerServiceInterval } from '~/app/shared/constants/app.constants';
18 import { Icons } from '~/app/shared/enum/icons.enum';
19 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
20 import { CdTableAction } from '~/app/shared/models/cd-table-action';
21 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
22 import { Permission } from '~/app/shared/models/permissions';
23 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
24 import { ModalService } from '~/app/shared/services/modal.service';
25 import { NotificationService } from '~/app/shared/services/notification.service';
26 import { TimerService } from '~/app/shared/services/timer.service';
27 import { RgwRealm, RgwZone, RgwZonegroup } from '../models/rgw-multisite';
28 import { RgwMultisiteMigrateComponent } from '../rgw-multisite-migrate/rgw-multisite-migrate.component';
29 import { RgwMultisiteZoneDeletionFormComponent } from '../models/rgw-multisite-zone-deletion-form/rgw-multisite-zone-deletion-form.component';
30 import { RgwMultisiteZonegroupDeletionFormComponent } from '../models/rgw-multisite-zonegroup-deletion-form/rgw-multisite-zonegroup-deletion-form.component';
31 import { RgwMultisiteExportComponent } from '../rgw-multisite-export/rgw-multisite-export.component';
32 import { RgwMultisiteImportComponent } from '../rgw-multisite-import/rgw-multisite-import.component';
33 import { RgwMultisiteRealmFormComponent } from '../rgw-multisite-realm-form/rgw-multisite-realm-form.component';
34 import { RgwMultisiteZoneFormComponent } from '../rgw-multisite-zone-form/rgw-multisite-zone-form.component';
35 import { RgwMultisiteZonegroupFormComponent } from '../rgw-multisite-zonegroup-form/rgw-multisite-zonegroup-form.component';
36 import { RgwDaemonService } from '~/app/shared/api/rgw-daemon.service';
37 import { MgrModuleService } from '~/app/shared/api/mgr-module.service';
38 import { BlockUI, NgBlockUI } from 'ng-block-ui';
39 import { Router } from '@angular/router';
42 selector: 'cd-rgw-multisite-details',
43 templateUrl: './rgw-multisite-details.component.html',
44 styleUrls: ['./rgw-multisite-details.component.scss']
46 export class RgwMultisiteDetailsComponent implements OnDestroy, OnInit {
47 private sub = new Subscription();
49 @ViewChild('tree') tree: TreeComponent;
52 noDefaultRealm: $localize`Please create a default realm first to enable this feature`,
53 noMasterZone: $localize`Please create a master zone for each zone group to enable this feature`,
54 noRealmExists: $localize`No realm exists`,
55 disableExport: $localize`Please create master zone group and master zone for each of the realms`
62 permission: Permission;
63 selection = new CdTableSelection();
64 createTableActions: CdTableAction[];
65 migrateTableAction: CdTableAction[];
66 importAction: CdTableAction[];
67 exportAction: CdTableAction[];
68 loadingIndicator = true;
70 treeOptions: ITreeOptions = {
71 useVirtualScroll: true,
76 click: this.onNodeSelected.bind(this)
80 modalRef: NgbModalRef;
82 realms: RgwRealm[] = [];
83 zonegroups: RgwZonegroup[] = [];
84 zones: RgwZone[] = [];
86 metadataTitle: string;
87 bsModalRef: NgbModalRef;
88 realmIds: string[] = [];
89 zoneIds: string[] = [];
91 defaultZonegroupId = '';
93 multisiteInfo: object[] = [];
94 defaultsInfo: string[] = [];
95 showMigrateAction: boolean = false;
96 editTitle: string = 'Edit';
97 deleteTitle: string = 'Delete';
99 rgwModuleStatus: boolean;
100 restartGatewayMessage = false;
101 rgwModuleData: string | any[] = [];
104 private modalService: ModalService,
105 private timerService: TimerService,
106 private authStorageService: AuthStorageService,
107 public actionLabels: ActionLabelsI18n,
108 public timerServiceVariable: TimerServiceInterval,
109 public router: Router,
110 public rgwRealmService: RgwRealmService,
111 public rgwZonegroupService: RgwZonegroupService,
112 public rgwZoneService: RgwZoneService,
113 public rgwDaemonService: RgwDaemonService,
114 public mgrModuleService: MgrModuleService,
115 private notificationService: NotificationService
117 this.permission = this.authStorageService.getPermissions().rgw;
120 openModal(entity: any, edit = false) {
121 const entityName = edit ? entity.data.type : entity;
122 const action = edit ? 'edit' : 'create';
123 const initialState = {
124 resource: entityName,
127 defaultsInfo: this.defaultsInfo,
128 multisiteInfo: this.multisiteInfo
130 if (entityName === 'realm') {
131 this.bsModalRef = this.modalService.show(RgwMultisiteRealmFormComponent, initialState, {
134 } else if (entityName === 'zonegroup') {
135 this.bsModalRef = this.modalService.show(RgwMultisiteZonegroupFormComponent, initialState, {
139 this.bsModalRef = this.modalService.show(RgwMultisiteZoneFormComponent, initialState, {
146 const initialState = {
147 multisiteInfo: this.multisiteInfo
149 this.bsModalRef = this.modalService.show(RgwMultisiteMigrateComponent, initialState, {
155 const initialState = {
156 multisiteInfo: this.multisiteInfo
158 this.bsModalRef = this.modalService.show(RgwMultisiteImportComponent, initialState, {
164 const initialState = {
165 defaultsInfo: this.defaultsInfo,
166 multisiteInfo: this.multisiteInfo
168 this.bsModalRef = this.modalService.show(RgwMultisiteExportComponent, initialState, {
174 this.realms.forEach((realm: any) => {
175 this.zonegroups.forEach((zonegroup) => {
176 if (realm.id === zonegroup.realm_id) {
177 if (zonegroup.is_master && zonegroup.master_zone !== '') {
178 this.disableExport = false;
183 if (!this.rgwModuleStatus) {
186 if (this.realms.length < 1) {
187 return this.messages.noRealmExists;
188 } else if (this.disableExport) {
189 return this.messages.disableExport;
196 if (!this.rgwModuleStatus) {
204 const createRealmAction: CdTableAction = {
205 permission: 'create',
207 name: this.actionLabels.CREATE + ' Realm',
208 click: () => this.openModal('realm')
210 const createZonegroupAction: CdTableAction = {
211 permission: 'create',
213 name: this.actionLabels.CREATE + ' Zone Group',
214 click: () => this.openModal('zonegroup'),
215 disable: () => this.getDisable()
217 const createZoneAction: CdTableAction = {
218 permission: 'create',
220 name: this.actionLabels.CREATE + ' Zone',
221 click: () => this.openModal('zone')
223 const migrateMultsiteAction: CdTableAction = {
225 icon: Icons.exchange,
226 name: this.actionLabels.MIGRATE,
227 click: () => this.openMigrateModal()
229 const importMultsiteAction: CdTableAction = {
231 icon: Icons.download,
232 name: this.actionLabels.IMPORT,
233 click: () => this.openImportModal(),
234 disable: () => this.getDisableImport()
236 const exportMultsiteAction: CdTableAction = {
239 name: this.actionLabels.EXPORT,
240 click: () => this.openExportModal(),
241 disable: () => this.getDisableExport()
243 this.createTableActions = [createRealmAction, createZonegroupAction, createZoneAction];
244 this.migrateTableAction = [migrateMultsiteAction];
245 this.importAction = [importMultsiteAction];
246 this.exportAction = [exportMultsiteAction];
248 const observables = [
249 this.rgwRealmService.getAllRealmsInfo(),
250 this.rgwZonegroupService.getAllZonegroupsInfo(),
251 this.rgwZoneService.getAllZonesInfo()
253 this.sub = this.timerService
254 .get(() => forkJoin(observables), this.timerServiceVariable.TIMER_SERVICE_PERIOD * 2)
256 (multisiteInfo: [object, object, object]) => {
257 this.multisiteInfo = multisiteInfo;
258 this.loadingIndicator = false;
259 this.nodes = this.abstractTreeData(multisiteInfo);
263 this.mgrModuleService.list().subscribe((moduleData: any) => {
264 this.rgwModuleData = moduleData.filter((module: object) => module['name'] === 'rgw');
265 if (this.rgwModuleData.length > 0) {
266 this.rgwModuleStatus = this.rgwModuleData[0].enabled;
270 /* setConfigValues() {
271 this.rgwDaemonService
273 this.defaultsInfo['defaultRealmName'],
274 this.defaultsInfo['defaultZonegroupName'],
275 this.defaultsInfo['defaultZoneName']
277 .subscribe(() => {});
281 this.sub.unsubscribe();
284 private abstractTreeData(multisiteInfo: [object, object, object]): any[] {
285 let allNodes: object[] = [];
287 let firstChildNodes = {};
288 let allFirstChildNodes = [];
289 let secondChildNodes = {};
290 let allSecondChildNodes: {}[] = [];
291 this.realms = multisiteInfo[0]['realms'];
292 this.zonegroups = multisiteInfo[1]['zonegroups'];
293 this.zones = multisiteInfo[2]['zones'];
294 this.defaultRealmId = multisiteInfo[0]['default_realm'];
295 this.defaultZonegroupId = multisiteInfo[1]['default_zonegroup'];
296 this.defaultZoneId = multisiteInfo[2]['default_zone'];
297 this.defaultsInfo = this.getDefaultsEntities(
299 this.defaultZonegroupId,
302 if (this.realms.length > 0) {
303 // get tree for realm -> zonegroup -> zone
304 for (const realm of this.realms) {
305 const result = this.rgwRealmService.getRealmTree(realm, this.defaultRealmId);
306 rootNodes = result['nodes'];
307 this.realmIds = this.realmIds.concat(result['realmIds']);
308 for (const zonegroup of this.zonegroups) {
309 if (zonegroup.realm_id === realm.id) {
310 firstChildNodes = this.rgwZonegroupService.getZonegroupTree(
312 this.defaultZonegroupId,
315 for (const zone of zonegroup.zones) {
316 const zoneResult = this.rgwZoneService.getZoneTree(
323 secondChildNodes = zoneResult['nodes'];
324 this.zoneIds = this.zoneIds.concat(zoneResult['zoneIds']);
325 allSecondChildNodes.push(secondChildNodes);
326 secondChildNodes = {};
328 firstChildNodes['children'] = allSecondChildNodes;
329 allSecondChildNodes = [];
330 allFirstChildNodes.push(firstChildNodes);
331 firstChildNodes = {};
334 rootNodes['children'] = allFirstChildNodes;
335 allNodes.push(rootNodes);
336 firstChildNodes = {};
337 secondChildNodes = {};
339 allFirstChildNodes = [];
340 allSecondChildNodes = [];
343 if (this.zonegroups.length > 0) {
344 // get tree for zonegroup -> zone (standalone zonegroups that don't match a realm eg(initial default))
345 for (const zonegroup of this.zonegroups) {
346 if (!this.realmIds.includes(zonegroup.realm_id)) {
347 rootNodes = this.rgwZonegroupService.getZonegroupTree(zonegroup, this.defaultZonegroupId);
348 for (const zone of zonegroup.zones) {
349 const zoneResult = this.rgwZoneService.getZoneTree(
355 firstChildNodes = zoneResult['nodes'];
356 this.zoneIds = this.zoneIds.concat(zoneResult['zoneIds']);
357 allFirstChildNodes.push(firstChildNodes);
358 firstChildNodes = {};
360 rootNodes['children'] = allFirstChildNodes;
361 allNodes.push(rootNodes);
362 firstChildNodes = {};
364 allFirstChildNodes = [];
368 if (this.zones.length > 0) {
369 // get tree for standalone zones(zones that do not belong to a zonegroup)
370 for (const zone of this.zones) {
371 if (this.zoneIds.length > 0 && !this.zoneIds.includes(zone.id)) {
372 const zoneResult = this.rgwZoneService.getZoneTree(zone, this.defaultZoneId, this.zones);
373 rootNodes = zoneResult['nodes'];
374 allNodes.push(rootNodes);
379 if (this.realms.length < 1 && this.zonegroups.length < 1 && this.zones.length < 1) {
388 this.getDisableMigrate();
389 this.rgwDaemonService.list().subscribe((data: any) => {
390 const realmName = data.map((item: { [x: string]: any }) => item['realm_name']);
392 this.defaultRealmId != '' &&
393 this.defaultZonegroupId != '' &&
394 this.defaultZoneId != '' &&
395 realmName.includes('')
397 this.restartGatewayMessage = true;
404 defaultRealmId: string,
405 defaultZonegroupId: string,
406 defaultZoneId: string
408 const defaultRealm = this.realms.find((x: { id: string }) => x.id === defaultRealmId);
409 const defaultZonegroup = this.zonegroups.find(
410 (x: { id: string }) => x.id === defaultZonegroupId
412 const defaultZone = this.zones.find((x: { id: string }) => x.id === defaultZoneId);
413 const defaultRealmName = defaultRealm !== undefined ? defaultRealm.name : null;
414 const defaultZonegroupName = defaultZonegroup !== undefined ? defaultZonegroup.name : null;
415 const defaultZoneName = defaultZone !== undefined ? defaultZone.name : null;
417 defaultRealmName: defaultRealmName,
418 defaultZonegroupName: defaultZonegroupName,
419 defaultZoneName: defaultZoneName
423 onNodeSelected(tree: TreeModel, node: TreeNode) {
424 TREE_ACTIONS.ACTIVATE(tree, node, true);
425 this.metadataTitle = node.data.name;
426 this.metadata = node.data.info;
427 node.data.show = true;
431 this.tree.treeModel.expandAll();
435 let isMasterZone = true;
436 if (this.defaultRealmId === '') {
437 return this.messages.noDefaultRealm;
439 this.zonegroups.forEach((zgp: any) => {
440 if (_.isEmpty(zgp.master_zone)) {
441 isMasterZone = false;
446 'Please create a master zone for each existing zonegroup to enable this feature';
447 return this.messages.noMasterZone;
449 this.editTitle = 'Edit';
455 getDisableMigrate() {
457 this.realms.length === 0 &&
458 this.zonegroups.length === 1 &&
459 this.zonegroups[0].name === 'default' &&
460 this.zones.length === 1 &&
461 this.zones[0].name === 'default'
463 this.showMigrateAction = true;
465 this.showMigrateAction = false;
467 return this.showMigrateAction;
470 isDeleteDisabled(node: TreeNode): boolean {
471 let disable: boolean = false;
472 let masterZonegroupCount: number = 0;
473 if (node.data.type === 'realm' && node.data.is_default && this.realms.length < 2) {
477 if (node.data.type === 'zonegroup') {
478 if (this.zonegroups.length < 2) {
479 this.deleteTitle = 'You can not delete the only zonegroup available';
481 } else if (node.data.is_default) {
482 this.deleteTitle = 'You can not delete the default zonegroup';
484 } else if (node.data.is_master) {
485 for (let zonegroup of this.zonegroups) {
486 if (zonegroup.is_master === true) {
487 masterZonegroupCount++;
488 if (masterZonegroupCount > 1) break;
491 if (masterZonegroupCount < 2) {
492 this.deleteTitle = 'You can not delete the only master zonegroup available';
498 if (node.data.type === 'zone') {
499 if (this.zones.length < 2) {
500 this.deleteTitle = 'You can not delete the only zone available';
502 } else if (node.data.is_default) {
503 this.deleteTitle = 'You can not delete the default zone';
505 } else if (node.data.is_master && node.data.zone_zonegroup.zones.length < 2) {
507 'You can not delete the master zone as there are no more zones in this zonegroup';
513 this.deleteTitle = 'Delete';
519 delete(node: TreeNode) {
520 if (node.data.type === 'realm') {
521 this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
522 itemDescription: $localize`${node.data.type} ${node.data.name}`,
523 itemNames: [`${node.data.name}`],
524 submitAction: () => {
525 this.rgwRealmService.delete(node.data.name).subscribe(
527 this.modalRef.close();
528 this.notificationService.show(
529 NotificationType.success,
530 $localize`Realm: '${node.data.name}' deleted successfully`
534 this.modalRef.componentInstance.stopLoadingSpinner();
539 } else if (node.data.type === 'zonegroup') {
540 this.modalRef = this.modalService.show(RgwMultisiteZonegroupDeletionFormComponent, {
543 } else if (node.data.type === 'zone') {
544 this.modalRef = this.modalService.show(RgwMultisiteZoneDeletionFormComponent, {
552 const fnWaitUntilReconnected = () => {
553 observableTimer(2000).subscribe(() => {
554 // Trigger an API request to check if the connection is
556 this.mgrModuleService.list().subscribe(
558 // Resume showing the notification toasties.
559 this.notificationService.suspendToasties(false);
560 // Unblock the whole UI.
562 // Reload the data table content.
563 this.notificationService.show(NotificationType.success, $localize`Enabled RGW Module`);
564 this.router.navigateByUrl('/', { skipLocationChange: true }).then(() => {
565 this.router.navigate(['/rgw/multisite']);
567 // Reload the data table content.
570 fnWaitUntilReconnected();
576 if (!this.rgwModuleStatus) {
577 $obs = this.mgrModuleService.enable('rgw');
582 // Suspend showing the notification toasties.
583 this.notificationService.suspendToasties(true);
584 // Block the whole UI to prevent user interactions until
585 // the connection to the backend is reestablished
586 this.blockUI.start($localize`Reconnecting, please wait ...`);
587 fnWaitUntilReconnected();
592 onNavChange(event: any) {
593 if (event.nextId == 'configuration') {
594 this.metadata = null;
596 It is a known issue with angular2-tree package when tree is hidden (for example inside tab or modal),
597 it is not rendered when it becomes visible. Solution is to call this.tree.sizeChanged() which recalculates
598 the rendered nodes according to the actual viewport size. (https://angular2-tree.readme.io/docs/common-issues)
601 this.tree.sizeChanged();