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[] = [];
105 private modalService: ModalService,
106 private timerService: TimerService,
107 private authStorageService: AuthStorageService,
108 public actionLabels: ActionLabelsI18n,
109 public timerServiceVariable: TimerServiceInterval,
110 public router: Router,
111 public rgwRealmService: RgwRealmService,
112 public rgwZonegroupService: RgwZonegroupService,
113 public rgwZoneService: RgwZoneService,
114 public rgwDaemonService: RgwDaemonService,
115 public mgrModuleService: MgrModuleService,
116 private notificationService: NotificationService
118 this.permission = this.authStorageService.getPermissions().rgw;
119 const activeId = this.router.getCurrentNavigation()?.extras?.state?.activeId;
121 this.activeId = activeId;
125 openModal(entity: any, edit = false) {
126 const entityName = edit ? entity.data.type : entity;
127 const action = edit ? 'edit' : 'create';
128 const initialState = {
129 resource: entityName,
132 defaultsInfo: this.defaultsInfo,
133 multisiteInfo: this.multisiteInfo
135 if (entityName === 'realm') {
136 this.bsModalRef = this.modalService.show(RgwMultisiteRealmFormComponent, initialState, {
139 } else if (entityName === 'zonegroup') {
140 this.bsModalRef = this.modalService.show(RgwMultisiteZonegroupFormComponent, initialState, {
144 this.bsModalRef = this.modalService.show(RgwMultisiteZoneFormComponent, initialState, {
151 const initialState = {
152 multisiteInfo: this.multisiteInfo
154 this.bsModalRef = this.modalService.show(RgwMultisiteMigrateComponent, initialState, {
160 const initialState = {
161 multisiteInfo: this.multisiteInfo
163 this.bsModalRef = this.modalService.show(RgwMultisiteImportComponent, initialState, {
169 const initialState = {
170 defaultsInfo: this.defaultsInfo,
171 multisiteInfo: this.multisiteInfo
173 this.bsModalRef = this.modalService.show(RgwMultisiteExportComponent, initialState, {
179 this.realms.forEach((realm: any) => {
180 this.zonegroups.forEach((zonegroup) => {
181 if (realm.id === zonegroup.realm_id) {
182 if (zonegroup.is_master && zonegroup.master_zone !== '') {
183 this.disableExport = false;
188 if (!this.rgwModuleStatus) {
191 if (this.realms.length < 1) {
192 return this.messages.noRealmExists;
193 } else if (this.disableExport) {
194 return this.messages.disableExport;
201 if (!this.rgwModuleStatus) {
209 const createRealmAction: CdTableAction = {
210 permission: 'create',
212 name: this.actionLabels.CREATE + ' Realm',
213 click: () => this.openModal('realm')
215 const createZonegroupAction: CdTableAction = {
216 permission: 'create',
218 name: this.actionLabels.CREATE + ' Zone Group',
219 click: () => this.openModal('zonegroup'),
220 disable: () => this.getDisable()
222 const createZoneAction: CdTableAction = {
223 permission: 'create',
225 name: this.actionLabels.CREATE + ' Zone',
226 click: () => this.openModal('zone')
228 const migrateMultsiteAction: CdTableAction = {
230 icon: Icons.exchange,
231 name: this.actionLabels.MIGRATE,
232 click: () => this.openMigrateModal()
234 const importMultsiteAction: CdTableAction = {
236 icon: Icons.download,
237 name: this.actionLabels.IMPORT,
238 click: () => this.openImportModal(),
239 disable: () => this.getDisableImport()
241 const exportMultsiteAction: CdTableAction = {
244 name: this.actionLabels.EXPORT,
245 click: () => this.openExportModal(),
246 disable: () => this.getDisableExport()
248 this.createTableActions = [createRealmAction, createZonegroupAction, createZoneAction];
249 this.migrateTableAction = [migrateMultsiteAction];
250 this.importAction = [importMultsiteAction];
251 this.exportAction = [exportMultsiteAction];
253 const observables = [
254 this.rgwRealmService.getAllRealmsInfo(),
255 this.rgwZonegroupService.getAllZonegroupsInfo(),
256 this.rgwZoneService.getAllZonesInfo()
258 this.sub = this.timerService
259 .get(() => forkJoin(observables), this.timerServiceVariable.TIMER_SERVICE_PERIOD * 2)
261 (multisiteInfo: [object, object, object]) => {
262 this.multisiteInfo = multisiteInfo;
263 this.loadingIndicator = false;
264 this.nodes = this.abstractTreeData(multisiteInfo);
268 this.mgrModuleService.list().subscribe((moduleData: any) => {
269 this.rgwModuleData = moduleData.filter((module: object) => module['name'] === 'rgw');
270 if (this.rgwModuleData.length > 0) {
271 this.rgwModuleStatus = this.rgwModuleData[0].enabled;
275 /* setConfigValues() {
276 this.rgwDaemonService
278 this.defaultsInfo['defaultRealmName'],
279 this.defaultsInfo['defaultZonegroupName'],
280 this.defaultsInfo['defaultZoneName']
282 .subscribe(() => {});
286 this.sub.unsubscribe();
289 private abstractTreeData(multisiteInfo: [object, object, object]): any[] {
290 let allNodes: object[] = [];
292 let firstChildNodes = {};
293 let allFirstChildNodes = [];
294 let secondChildNodes = {};
295 let allSecondChildNodes: {}[] = [];
296 this.realms = multisiteInfo[0]['realms'];
297 this.zonegroups = multisiteInfo[1]['zonegroups'];
298 this.zones = multisiteInfo[2]['zones'];
299 this.defaultRealmId = multisiteInfo[0]['default_realm'];
300 this.defaultZonegroupId = multisiteInfo[1]['default_zonegroup'];
301 this.defaultZoneId = multisiteInfo[2]['default_zone'];
302 this.defaultsInfo = this.getDefaultsEntities(
304 this.defaultZonegroupId,
307 if (this.realms.length > 0) {
308 // get tree for realm -> zonegroup -> zone
309 for (const realm of this.realms) {
310 const result = this.rgwRealmService.getRealmTree(realm, this.defaultRealmId);
311 rootNodes = result['nodes'];
312 this.realmIds = this.realmIds.concat(result['realmIds']);
313 for (const zonegroup of this.zonegroups) {
314 if (zonegroup.realm_id === realm.id) {
315 firstChildNodes = this.rgwZonegroupService.getZonegroupTree(
317 this.defaultZonegroupId,
320 for (const zone of zonegroup.zones) {
321 const zoneResult = this.rgwZoneService.getZoneTree(
328 secondChildNodes = zoneResult['nodes'];
329 this.zoneIds = this.zoneIds.concat(zoneResult['zoneIds']);
330 allSecondChildNodes.push(secondChildNodes);
331 secondChildNodes = {};
333 firstChildNodes['children'] = allSecondChildNodes;
334 allSecondChildNodes = [];
335 allFirstChildNodes.push(firstChildNodes);
336 firstChildNodes = {};
339 rootNodes['children'] = allFirstChildNodes;
340 allNodes.push(rootNodes);
341 firstChildNodes = {};
342 secondChildNodes = {};
344 allFirstChildNodes = [];
345 allSecondChildNodes = [];
348 if (this.zonegroups.length > 0) {
349 // get tree for zonegroup -> zone (standalone zonegroups that don't match a realm eg(initial default))
350 for (const zonegroup of this.zonegroups) {
351 if (!this.realmIds.includes(zonegroup.realm_id)) {
352 rootNodes = this.rgwZonegroupService.getZonegroupTree(zonegroup, this.defaultZonegroupId);
353 for (const zone of zonegroup.zones) {
354 const zoneResult = this.rgwZoneService.getZoneTree(
360 firstChildNodes = zoneResult['nodes'];
361 this.zoneIds = this.zoneIds.concat(zoneResult['zoneIds']);
362 allFirstChildNodes.push(firstChildNodes);
363 firstChildNodes = {};
365 rootNodes['children'] = allFirstChildNodes;
366 allNodes.push(rootNodes);
367 firstChildNodes = {};
369 allFirstChildNodes = [];
373 if (this.zones.length > 0) {
374 // get tree for standalone zones(zones that do not belong to a zonegroup)
375 for (const zone of this.zones) {
376 if (this.zoneIds.length > 0 && !this.zoneIds.includes(zone.id)) {
377 const zoneResult = this.rgwZoneService.getZoneTree(zone, this.defaultZoneId, this.zones);
378 rootNodes = zoneResult['nodes'];
379 allNodes.push(rootNodes);
384 if (this.realms.length < 1 && this.zonegroups.length < 1 && this.zones.length < 1) {
393 this.getDisableMigrate();
394 this.rgwDaemonService.list().subscribe((data: any) => {
395 const realmName = data.map((item: { [x: string]: any }) => item['realm_name']);
397 this.defaultRealmId != '' &&
398 this.defaultZonegroupId != '' &&
399 this.defaultZoneId != '' &&
400 realmName.includes('')
402 this.restartGatewayMessage = true;
409 defaultRealmId: string,
410 defaultZonegroupId: string,
411 defaultZoneId: string
413 const defaultRealm = this.realms.find((x: { id: string }) => x.id === defaultRealmId);
414 const defaultZonegroup = this.zonegroups.find(
415 (x: { id: string }) => x.id === defaultZonegroupId
417 const defaultZone = this.zones.find((x: { id: string }) => x.id === defaultZoneId);
418 const defaultRealmName = defaultRealm !== undefined ? defaultRealm.name : null;
419 const defaultZonegroupName = defaultZonegroup !== undefined ? defaultZonegroup.name : null;
420 const defaultZoneName = defaultZone !== undefined ? defaultZone.name : null;
422 defaultRealmName: defaultRealmName,
423 defaultZonegroupName: defaultZonegroupName,
424 defaultZoneName: defaultZoneName
428 onNodeSelected(tree: TreeModel, node: TreeNode) {
429 TREE_ACTIONS.ACTIVATE(tree, node, true);
430 this.metadataTitle = node.data.name;
431 this.metadata = node.data.info;
432 node.data.show = true;
436 this.tree.treeModel.expandAll();
440 let isMasterZone = true;
441 if (this.defaultRealmId === '') {
442 return this.messages.noDefaultRealm;
444 this.zonegroups.forEach((zgp: any) => {
445 if (_.isEmpty(zgp.master_zone)) {
446 isMasterZone = false;
451 'Please create a master zone for each existing zonegroup to enable this feature';
452 return this.messages.noMasterZone;
454 this.editTitle = 'Edit';
460 getDisableMigrate() {
462 this.realms.length === 0 &&
463 this.zonegroups.length === 1 &&
464 this.zonegroups[0].name === 'default' &&
465 this.zones.length === 1 &&
466 this.zones[0].name === 'default'
468 this.showMigrateAction = true;
470 this.showMigrateAction = false;
472 return this.showMigrateAction;
475 isDeleteDisabled(node: TreeNode): boolean {
476 let disable: boolean = false;
477 let masterZonegroupCount: number = 0;
478 if (node.data.type === 'realm' && node.data.is_default && this.realms.length < 2) {
482 if (node.data.type === 'zonegroup') {
483 if (this.zonegroups.length < 2) {
484 this.deleteTitle = 'You can not delete the only zonegroup available';
486 } else if (node.data.is_default) {
487 this.deleteTitle = 'You can not delete the default zonegroup';
489 } else if (node.data.is_master) {
490 for (let zonegroup of this.zonegroups) {
491 if (zonegroup.is_master === true) {
492 masterZonegroupCount++;
493 if (masterZonegroupCount > 1) break;
496 if (masterZonegroupCount < 2) {
497 this.deleteTitle = 'You can not delete the only master zonegroup available';
503 if (node.data.type === 'zone') {
504 if (this.zones.length < 2) {
505 this.deleteTitle = 'You can not delete the only zone available';
507 } else if (node.data.is_default) {
508 this.deleteTitle = 'You can not delete the default zone';
510 } else if (node.data.is_master && node.data.zone_zonegroup.zones.length < 2) {
512 'You can not delete the master zone as there are no more zones in this zonegroup';
518 this.deleteTitle = 'Delete';
524 delete(node: TreeNode) {
525 if (node.data.type === 'realm') {
526 this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
527 itemDescription: $localize`${node.data.type} ${node.data.name}`,
528 itemNames: [`${node.data.name}`],
529 submitAction: () => {
530 this.rgwRealmService.delete(node.data.name).subscribe(
532 this.modalRef.close();
533 this.notificationService.show(
534 NotificationType.success,
535 $localize`Realm: '${node.data.name}' deleted successfully`
539 this.modalRef.componentInstance.stopLoadingSpinner();
544 } else if (node.data.type === 'zonegroup') {
545 this.modalRef = this.modalService.show(RgwMultisiteZonegroupDeletionFormComponent, {
548 } else if (node.data.type === 'zone') {
549 this.modalRef = this.modalService.show(RgwMultisiteZoneDeletionFormComponent, {
557 const fnWaitUntilReconnected = () => {
558 observableTimer(2000).subscribe(() => {
559 // Trigger an API request to check if the connection is
561 this.mgrModuleService.list().subscribe(
563 // Resume showing the notification toasties.
564 this.notificationService.suspendToasties(false);
565 // Unblock the whole UI.
567 // Reload the data table content.
568 this.notificationService.show(NotificationType.success, $localize`Enabled RGW Module`);
569 this.router.navigateByUrl('/', { skipLocationChange: true }).then(() => {
570 this.router.navigate(['/rgw/multisite']);
572 // Reload the data table content.
575 fnWaitUntilReconnected();
581 if (!this.rgwModuleStatus) {
582 $obs = this.mgrModuleService.enable('rgw');
587 // Suspend showing the notification toasties.
588 this.notificationService.suspendToasties(true);
589 // Block the whole UI to prevent user interactions until
590 // the connection to the backend is reestablished
591 this.blockUI.start($localize`Reconnecting, please wait ...`);
592 fnWaitUntilReconnected();
597 onNavChange(event: any) {
598 if (event.nextId == 'configuration') {
599 this.metadata = null;
601 It is a known issue with angular2-tree package when tree is hidden (for example inside tab or modal),
602 it is not rendered when it becomes visible. Solution is to call this.tree.sizeChanged() which recalculates
603 the rendered nodes according to the actual viewport size. (https://angular2-tree.readme.io/docs/common-issues)
606 this.tree.sizeChanged();