1 import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
8 } from '@circlon/angular-tree-component';
9 import { NgbActiveModal, 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';
40 import { RgwMultisiteWizardComponent } from '../rgw-multisite-wizard/rgw-multisite-wizard.component';
42 const BASE_URL = 'rgw/multisite';
45 selector: 'cd-rgw-multisite-details',
46 templateUrl: './rgw-multisite-details.component.html',
47 styleUrls: ['./rgw-multisite-details.component.scss']
49 export class RgwMultisiteDetailsComponent implements OnDestroy, OnInit {
50 private sub = new Subscription();
52 @ViewChild('tree') tree: TreeComponent;
55 noDefaultRealm: $localize`Please create a default realm first to enable this feature`,
56 noMasterZone: $localize`Please create a master zone for each zone group to enable this feature`,
57 noRealmExists: $localize`No realm exists`,
58 disableExport: $localize`Please create master zone group and master zone for each of the realms`
65 permission: Permission;
66 selection = new CdTableSelection();
67 createTableActions: CdTableAction[];
68 migrateTableAction: CdTableAction[];
69 importAction: CdTableAction[];
70 exportAction: CdTableAction[];
71 multisiteReplicationActions: CdTableAction[];
72 loadingIndicator = true;
74 treeOptions: ITreeOptions = {
75 useVirtualScroll: true,
80 click: this.onNodeSelected.bind(this)
84 modalRef: NgbModalRef;
86 realms: RgwRealm[] = [];
87 zonegroups: RgwZonegroup[] = [];
88 zones: RgwZone[] = [];
90 metadataTitle: string;
91 bsModalRef: NgbModalRef;
92 realmIds: string[] = [];
93 zoneIds: string[] = [];
95 defaultZonegroupId = '';
97 multisiteInfo: object[] = [];
98 defaultsInfo: string[] = [];
99 showMigrateAndReplicationActions = false;
100 editTitle: string = 'Edit';
101 deleteTitle: string = 'Delete';
102 disableExport = true;
103 rgwModuleStatus: boolean;
104 restartGatewayMessage = false;
105 rgwModuleData: string | any[] = [];
109 public activeModal: NgbActiveModal,
110 private modalService: ModalService,
111 private timerService: TimerService,
112 private authStorageService: AuthStorageService,
113 public actionLabels: ActionLabelsI18n,
114 public timerServiceVariable: TimerServiceInterval,
115 public router: Router,
116 public rgwRealmService: RgwRealmService,
117 public rgwZonegroupService: RgwZonegroupService,
118 public rgwZoneService: RgwZoneService,
119 public rgwDaemonService: RgwDaemonService,
120 public mgrModuleService: MgrModuleService,
121 private notificationService: NotificationService
123 this.permission = this.authStorageService.getPermissions().rgw;
124 const activeId = this.router.getCurrentNavigation()?.extras?.state?.activeId;
126 this.activeId = activeId;
130 openModal(entity: any, edit = false) {
131 const entityName = edit ? entity.data.type : entity;
132 const action = edit ? 'edit' : 'create';
133 const initialState = {
134 resource: entityName,
137 defaultsInfo: this.defaultsInfo,
138 multisiteInfo: this.multisiteInfo
140 if (entityName === 'realm') {
141 this.bsModalRef = this.modalService.show(RgwMultisiteRealmFormComponent, initialState, {
144 } else if (entityName === 'zonegroup') {
145 this.bsModalRef = this.modalService.show(RgwMultisiteZonegroupFormComponent, initialState, {
149 this.bsModalRef = this.modalService.show(RgwMultisiteZoneFormComponent, initialState, {
155 openMultisiteSetupWizard() {
156 this.bsModalRef = this.modalService.show(RgwMultisiteWizardComponent, {
162 const initialState = {
163 multisiteInfo: this.multisiteInfo
165 this.bsModalRef = this.modalService.show(RgwMultisiteMigrateComponent, initialState, {
171 const initialState = {
172 multisiteInfo: this.multisiteInfo
174 this.bsModalRef = this.modalService.show(RgwMultisiteImportComponent, initialState, {
180 const initialState = {
181 defaultsInfo: this.defaultsInfo,
182 multisiteInfo: this.multisiteInfo
184 this.bsModalRef = this.modalService.show(RgwMultisiteExportComponent, initialState, {
190 this.realms.forEach((realm: any) => {
191 this.zonegroups.forEach((zonegroup) => {
192 if (realm.id === zonegroup.realm_id) {
193 if (zonegroup.is_master && zonegroup.master_zone !== '') {
194 this.disableExport = false;
199 if (!this.rgwModuleStatus) {
202 if (this.realms.length < 1) {
203 return this.messages.noRealmExists;
204 } else if (this.disableExport) {
205 return this.messages.disableExport;
212 if (!this.rgwModuleStatus) {
220 this.createTableActions = [
222 permission: 'create',
224 name: this.actionLabels.CREATE + ' Realm',
225 click: () => this.openModal('realm')
228 permission: 'create',
230 name: this.actionLabels.CREATE + ' Zone Group',
231 click: () => this.openModal('zonegroup'),
232 disable: () => this.getDisable()
235 permission: 'create',
237 name: this.actionLabels.CREATE + ' Zone',
238 click: () => this.openModal('zone')
241 this.migrateTableAction = [
243 permission: 'create',
245 name: this.actionLabels.MIGRATE,
246 click: () => this.openMigrateModal()
249 this.importAction = [
251 permission: 'create',
252 icon: Icons.download,
253 name: this.actionLabels.IMPORT,
254 click: () => this.openImportModal(),
255 disable: () => this.getDisableImport()
258 this.exportAction = [
260 permission: 'create',
262 name: this.actionLabels.EXPORT,
263 click: () => this.openExportModal(),
264 disable: () => this.getDisableExport()
267 this.multisiteReplicationActions = [
269 permission: 'create',
271 name: this.actionLabels.SETUP_MULTISITE_REPLICATION,
273 this.router.navigate([BASE_URL, { outlets: { modal: 'setup-multisite-replication' } }])
277 const observables = [
278 this.rgwRealmService.getAllRealmsInfo(),
279 this.rgwZonegroupService.getAllZonegroupsInfo(),
280 this.rgwZoneService.getAllZonesInfo()
282 this.sub = this.timerService
283 .get(() => forkJoin(observables), this.timerServiceVariable.TIMER_SERVICE_PERIOD * 2)
285 (multisiteInfo: [object, object, object]) => {
286 this.multisiteInfo = multisiteInfo;
287 this.loadingIndicator = false;
288 this.nodes = this.abstractTreeData(multisiteInfo);
292 this.mgrModuleService.list().subscribe((moduleData: any) => {
293 this.rgwModuleData = moduleData.filter((module: object) => module['name'] === 'rgw');
294 if (this.rgwModuleData.length > 0) {
295 this.rgwModuleStatus = this.rgwModuleData[0].enabled;
299 /* setConfigValues() {
300 this.rgwDaemonService
302 this.defaultsInfo['defaultRealmName'],
303 this.defaultsInfo['defaultZonegroupName'],
304 this.defaultsInfo['defaultZoneName']
306 .subscribe(() => {});
310 this.sub.unsubscribe();
313 private abstractTreeData(multisiteInfo: [object, object, object]): any[] {
314 let allNodes: object[] = [];
316 let firstChildNodes = {};
317 let allFirstChildNodes = [];
318 let secondChildNodes = {};
319 let allSecondChildNodes: {}[] = [];
320 this.realms = multisiteInfo[0]['realms'];
321 this.zonegroups = multisiteInfo[1]['zonegroups'];
322 this.zones = multisiteInfo[2]['zones'];
323 this.defaultRealmId = multisiteInfo[0]['default_realm'];
324 this.defaultZonegroupId = multisiteInfo[1]['default_zonegroup'];
325 this.defaultZoneId = multisiteInfo[2]['default_zone'];
326 this.defaultsInfo = this.getDefaultsEntities(
328 this.defaultZonegroupId,
331 if (this.realms.length > 0) {
332 // get tree for realm -> zonegroup -> zone
333 for (const realm of this.realms) {
334 const result = this.rgwRealmService.getRealmTree(realm, this.defaultRealmId);
335 rootNodes = result['nodes'];
336 this.realmIds = this.realmIds.concat(result['realmIds']);
337 for (const zonegroup of this.zonegroups) {
338 if (zonegroup.realm_id === realm.id) {
339 firstChildNodes = this.rgwZonegroupService.getZonegroupTree(
341 this.defaultZonegroupId,
344 for (const zone of zonegroup.zones) {
345 const zoneResult = this.rgwZoneService.getZoneTree(
352 secondChildNodes = zoneResult['nodes'];
353 this.zoneIds = this.zoneIds.concat(zoneResult['zoneIds']);
354 allSecondChildNodes.push(secondChildNodes);
355 secondChildNodes = {};
357 firstChildNodes['children'] = allSecondChildNodes;
358 allSecondChildNodes = [];
359 allFirstChildNodes.push(firstChildNodes);
360 firstChildNodes = {};
363 rootNodes['children'] = allFirstChildNodes;
364 allNodes.push(rootNodes);
365 firstChildNodes = {};
366 secondChildNodes = {};
368 allFirstChildNodes = [];
369 allSecondChildNodes = [];
372 if (this.zonegroups.length > 0) {
373 // get tree for zonegroup -> zone (standalone zonegroups that don't match a realm eg(initial default))
374 for (const zonegroup of this.zonegroups) {
375 if (!this.realmIds.includes(zonegroup.realm_id)) {
376 rootNodes = this.rgwZonegroupService.getZonegroupTree(zonegroup, this.defaultZonegroupId);
377 for (const zone of zonegroup.zones) {
378 const zoneResult = this.rgwZoneService.getZoneTree(
384 firstChildNodes = zoneResult['nodes'];
385 this.zoneIds = this.zoneIds.concat(zoneResult['zoneIds']);
386 allFirstChildNodes.push(firstChildNodes);
387 firstChildNodes = {};
389 rootNodes['children'] = allFirstChildNodes;
390 allNodes.push(rootNodes);
391 firstChildNodes = {};
393 allFirstChildNodes = [];
397 if (this.zones.length > 0) {
398 // get tree for standalone zones(zones that do not belong to a zonegroup)
399 for (const zone of this.zones) {
400 if (this.zoneIds.length > 0 && !this.zoneIds.includes(zone.id)) {
401 const zoneResult = this.rgwZoneService.getZoneTree(zone, this.defaultZoneId, this.zones);
402 rootNodes = zoneResult['nodes'];
403 allNodes.push(rootNodes);
408 if (this.realms.length < 1 && this.zonegroups.length < 1 && this.zones.length < 1) {
417 this.evaluateMigrateAndReplicationActions();
418 this.rgwDaemonService.list().subscribe((data: any) => {
419 const realmName = data.map((item: { [x: string]: any }) => item['realm_name']);
421 this.defaultRealmId != '' &&
422 this.defaultZonegroupId != '' &&
423 this.defaultZoneId != '' &&
424 realmName.includes('')
426 this.restartGatewayMessage = true;
433 defaultRealmId: string,
434 defaultZonegroupId: string,
435 defaultZoneId: string
437 const defaultRealm = this.realms.find((x: { id: string }) => x.id === defaultRealmId);
438 const defaultZonegroup = this.zonegroups.find(
439 (x: { id: string }) => x.id === defaultZonegroupId
441 const defaultZone = this.zones.find((x: { id: string }) => x.id === defaultZoneId);
442 const defaultRealmName = defaultRealm !== undefined ? defaultRealm.name : null;
443 const defaultZonegroupName = defaultZonegroup !== undefined ? defaultZonegroup.name : null;
444 const defaultZoneName = defaultZone !== undefined ? defaultZone.name : null;
446 defaultRealmName: defaultRealmName,
447 defaultZonegroupName: defaultZonegroupName,
448 defaultZoneName: defaultZoneName
452 onNodeSelected(tree: TreeModel, node: TreeNode) {
453 TREE_ACTIONS.ACTIVATE(tree, node, true);
454 this.metadataTitle = node.data.name;
455 this.metadata = node.data.info;
456 node.data.show = true;
460 this.tree.treeModel.expandAll();
464 let isMasterZone = true;
465 if (this.defaultRealmId === '') {
466 return this.messages.noDefaultRealm;
468 this.zonegroups.forEach((zgp: any) => {
469 if (_.isEmpty(zgp.master_zone)) {
470 isMasterZone = false;
475 'Please create a master zone for each existing zonegroup to enable this feature';
476 return this.messages.noMasterZone;
478 this.editTitle = 'Edit';
484 evaluateMigrateAndReplicationActions() {
486 this.realms.length === 0 &&
487 this.zonegroups.length === 1 &&
488 this.zonegroups[0].name === 'default' &&
489 this.zones.length === 1 &&
490 this.zones[0].name === 'default'
492 this.showMigrateAndReplicationActions = true;
494 this.showMigrateAndReplicationActions = false;
496 return this.showMigrateAndReplicationActions;
499 isDeleteDisabled(node: TreeNode): boolean {
500 let disable: boolean = false;
501 let masterZonegroupCount: number = 0;
502 if (node.data.type === 'realm' && node.data.is_default && this.realms.length < 2) {
506 if (node.data.type === 'zonegroup') {
507 if (this.zonegroups.length < 2) {
508 this.deleteTitle = 'You can not delete the only zonegroup available';
510 } else if (node.data.is_default) {
511 this.deleteTitle = 'You can not delete the default zonegroup';
513 } else if (node.data.is_master) {
514 for (let zonegroup of this.zonegroups) {
515 if (zonegroup.is_master === true) {
516 masterZonegroupCount++;
517 if (masterZonegroupCount > 1) break;
520 if (masterZonegroupCount < 2) {
521 this.deleteTitle = 'You can not delete the only master zonegroup available';
527 if (node.data.type === 'zone') {
528 if (this.zones.length < 2) {
529 this.deleteTitle = 'You can not delete the only zone available';
531 } else if (node.data.is_default) {
532 this.deleteTitle = 'You can not delete the default zone';
534 } else if (node.data.is_master && node.data.zone_zonegroup.zones.length < 2) {
536 'You can not delete the master zone as there are no more zones in this zonegroup';
542 this.deleteTitle = 'Delete';
548 delete(node: TreeNode) {
549 if (node.data.type === 'realm') {
550 this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
551 itemDescription: $localize`${node.data.type} ${node.data.name}`,
552 itemNames: [`${node.data.name}`],
553 submitAction: () => {
554 this.rgwRealmService.delete(node.data.name).subscribe(
556 this.modalRef.close();
557 this.notificationService.show(
558 NotificationType.success,
559 $localize`Realm: '${node.data.name}' deleted successfully`
563 this.modalRef.componentInstance.stopLoadingSpinner();
568 } else if (node.data.type === 'zonegroup') {
569 this.modalRef = this.modalService.show(RgwMultisiteZonegroupDeletionFormComponent, {
572 } else if (node.data.type === 'zone') {
573 this.modalRef = this.modalService.show(RgwMultisiteZoneDeletionFormComponent, {
581 const fnWaitUntilReconnected = () => {
582 observableTimer(2000).subscribe(() => {
583 // Trigger an API request to check if the connection is
585 this.mgrModuleService.list().subscribe(
587 // Resume showing the notification toasties.
588 this.notificationService.suspendToasties(false);
589 // Unblock the whole UI.
591 // Reload the data table content.
592 this.notificationService.show(NotificationType.success, $localize`Enabled RGW Module`);
593 this.router.navigateByUrl('/', { skipLocationChange: true }).then(() => {
594 this.router.navigate(['/rgw/multisite']);
596 // Reload the data table content.
599 fnWaitUntilReconnected();
605 if (!this.rgwModuleStatus) {
606 $obs = this.mgrModuleService.enable('rgw');
611 // Suspend showing the notification toasties.
612 this.notificationService.suspendToasties(true);
613 // Block the whole UI to prevent user interactions until
614 // the connection to the backend is reestablished
615 this.blockUI.start($localize`Reconnecting, please wait ...`);
616 fnWaitUntilReconnected();
621 onNavChange(event: any) {
622 if (event.nextId == 'configuration') {
623 this.metadata = null;
625 It is a known issue with angular2-tree package when tree is hidden (for example inside tab or modal),
626 it is not rendered when it becomes visible. Solution is to call this.tree.sizeChanged() which recalculates
627 the rendered nodes according to the actual viewport size. (https://angular2-tree.readme.io/docs/common-issues)
630 this.tree.sizeChanged();