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';
40 import { RgwMultisiteWizardComponent } from '../rgw-multisite-wizard/rgw-multisite-wizard.component';
41 import { RgwMultisiteSyncPolicyComponent } from '../rgw-multisite-sync-policy/rgw-multisite-sync-policy.component';
42 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
43 import { RgwMultisiteService } from '~/app/shared/api/rgw-multisite.service';
45 const BASE_URL = 'rgw/multisite/configuration';
48 selector: 'cd-rgw-multisite-details',
49 templateUrl: './rgw-multisite-details.component.html',
50 styleUrls: ['./rgw-multisite-details.component.scss']
52 export class RgwMultisiteDetailsComponent implements OnDestroy, OnInit {
53 private sub = new Subscription();
55 @ViewChild('tree') tree: TreeComponent;
56 @ViewChild(RgwMultisiteSyncPolicyComponent) syncPolicyComp: RgwMultisiteSyncPolicyComponent;
59 noDefaultRealm: $localize`Please create a default realm first to enable this feature`,
60 noMasterZone: $localize`Please create a master zone for each zone group to enable this feature`,
61 noRealmExists: $localize`No realm exists`,
62 disableExport: $localize`Please create master zone group and master zone for each of the realms`
69 permission: Permission;
70 selection = new CdTableSelection();
71 createTableActions: CdTableAction[];
72 migrateTableAction: CdTableAction[];
73 importAction: CdTableAction[];
74 exportAction: CdTableAction[];
75 multisiteReplicationActions: CdTableAction[];
76 loadingIndicator = true;
78 treeOptions: ITreeOptions = {
79 useVirtualScroll: true,
84 click: this.onNodeSelected.bind(this)
88 modalRef: NgbModalRef;
90 realms: RgwRealm[] = [];
91 zonegroups: RgwZonegroup[] = [];
92 zones: RgwZone[] = [];
94 metadataTitle: string;
95 bsModalRef: NgbModalRef;
96 realmIds: string[] = [];
97 zoneIds: string[] = [];
99 defaultZonegroupId = '';
101 multisiteInfo: object[] = [];
102 defaultsInfo: string[] = [];
103 showMigrateAndReplicationActions = false;
104 editTitle: string = 'Edit';
105 deleteTitle: string = 'Delete';
106 disableExport = true;
107 rgwModuleStatus: boolean;
108 restartGatewayMessage = false;
109 rgwModuleData: string | any[] = [];
113 private modalService: ModalService,
114 private timerService: TimerService,
115 private authStorageService: AuthStorageService,
116 public actionLabels: ActionLabelsI18n,
117 public timerServiceVariable: TimerServiceInterval,
118 public router: Router,
119 public rgwRealmService: RgwRealmService,
120 public rgwZonegroupService: RgwZonegroupService,
121 public rgwZoneService: RgwZoneService,
122 public rgwDaemonService: RgwDaemonService,
123 public mgrModuleService: MgrModuleService,
124 private notificationService: NotificationService,
125 private cdsModalService: ModalCdsService,
126 private rgwMultisiteService: RgwMultisiteService
128 this.permission = this.authStorageService.getPermissions().rgw;
131 openModal(entity: any, edit = false) {
132 const entityName = edit ? entity.data.type : entity;
133 const action = edit ? 'edit' : 'create';
134 const initialState = {
135 resource: entityName,
138 defaultsInfo: this.defaultsInfo,
139 multisiteInfo: this.multisiteInfo
141 if (entityName === 'realm') {
142 this.bsModalRef = this.cdsModalService.show(RgwMultisiteRealmFormComponent, initialState);
143 } else if (entityName === 'zonegroup') {
144 this.bsModalRef = this.modalService.show(RgwMultisiteZonegroupFormComponent, initialState, {
148 this.bsModalRef = this.modalService.show(RgwMultisiteZoneFormComponent, initialState, {
154 openMultisiteSetupWizard() {
155 this.bsModalRef = this.cdsModalService.show(RgwMultisiteWizardComponent);
159 const initialState = {
160 multisiteInfo: this.multisiteInfo
162 this.bsModalRef = this.modalService.show(RgwMultisiteMigrateComponent, initialState, {
168 const initialState = {
169 multisiteInfo: this.multisiteInfo
171 this.bsModalRef = this.modalService.show(RgwMultisiteImportComponent, initialState, {
177 const initialState = {
178 defaultsInfo: this.defaultsInfo,
179 multisiteInfo: this.multisiteInfo
181 this.bsModalRef = this.modalService.show(RgwMultisiteExportComponent, initialState, {
187 this.realms.forEach((realm: any) => {
188 this.zonegroups.forEach((zonegroup) => {
189 if (realm.id === zonegroup.realm_id) {
190 if (zonegroup.is_master && zonegroup.master_zone !== '') {
191 this.disableExport = false;
196 if (!this.rgwModuleStatus) {
199 if (this.realms.length < 1) {
200 return this.messages.noRealmExists;
201 } else if (this.disableExport) {
202 return this.messages.disableExport;
209 if (!this.rgwModuleStatus) {
217 this.createTableActions = [
219 permission: 'create',
221 name: this.actionLabels.CREATE + ' Realm',
222 click: () => this.openModal('realm')
225 permission: 'create',
227 name: this.actionLabels.CREATE + ' Zone Group',
228 click: () => this.openModal('zonegroup'),
229 disable: () => this.getDisable()
232 permission: 'create',
234 name: this.actionLabels.CREATE + ' Zone',
235 click: () => this.openModal('zone')
238 this.migrateTableAction = [
240 permission: 'create',
242 name: this.actionLabels.MIGRATE,
243 click: () => this.openMigrateModal()
246 this.importAction = [
248 permission: 'create',
249 icon: Icons.download,
250 name: this.actionLabels.IMPORT,
251 click: () => this.openImportModal(),
252 disable: () => this.getDisableImport()
255 this.exportAction = [
257 permission: 'create',
259 name: this.actionLabels.EXPORT,
260 click: () => this.openExportModal(),
261 disable: () => this.getDisableExport()
264 this.multisiteReplicationActions = [
266 permission: 'create',
268 name: this.actionLabels.SETUP_MULTISITE_REPLICATION,
270 this.router.navigate([BASE_URL, { outlets: { modal: 'setup-multisite-replication' } }])
274 const observables = [
275 this.rgwRealmService.getAllRealmsInfo(),
276 this.rgwZonegroupService.getAllZonegroupsInfo(),
277 this.rgwZoneService.getAllZonesInfo()
279 this.sub = this.timerService
280 .get(() => forkJoin(observables), this.timerServiceVariable.TIMER_SERVICE_PERIOD * 2)
282 (multisiteInfo: [object, object, object]) => {
283 this.multisiteInfo = multisiteInfo;
284 this.loadingIndicator = false;
285 this.nodes = this.abstractTreeData(multisiteInfo);
289 this.mgrModuleService.list().subscribe((moduleData: any) => {
290 this.rgwModuleData = moduleData.filter((module: object) => module['name'] === 'rgw');
291 if (this.rgwModuleData.length > 0) {
292 this.rgwModuleStatus = this.rgwModuleData[0].enabled;
296 /* setConfigValues() {
297 this.rgwDaemonService
299 this.defaultsInfo['defaultRealmName'],
300 this.defaultsInfo['defaultZonegroupName'],
301 this.defaultsInfo['defaultZoneName']
303 .subscribe(() => {});
307 this.sub.unsubscribe();
310 private abstractTreeData(multisiteInfo: [object, object, object]): any[] {
311 let allNodes: object[] = [];
313 let firstChildNodes = {};
314 let allFirstChildNodes = [];
315 let secondChildNodes = {};
316 let allSecondChildNodes: {}[] = [];
317 this.realms = multisiteInfo[0]['realms'];
318 this.zonegroups = multisiteInfo[1]['zonegroups'];
319 this.zones = multisiteInfo[2]['zones'];
320 this.defaultRealmId = multisiteInfo[0]['default_realm'];
321 this.defaultZonegroupId = multisiteInfo[1]['default_zonegroup'];
322 this.defaultZoneId = multisiteInfo[2]['default_zone'];
323 this.defaultsInfo = this.getDefaultsEntities(
325 this.defaultZonegroupId,
328 if (this.realms.length > 0) {
329 // get tree for realm -> zonegroup -> zone
330 for (const realm of this.realms) {
331 const result = this.rgwRealmService.getRealmTree(realm, this.defaultRealmId);
332 rootNodes = result['nodes'];
333 this.realmIds = this.realmIds.concat(result['realmIds']);
334 for (const zonegroup of this.zonegroups) {
335 if (zonegroup.realm_id === realm.id) {
336 firstChildNodes = this.rgwZonegroupService.getZonegroupTree(
338 this.defaultZonegroupId,
341 for (const zone of zonegroup.zones) {
342 const zoneResult = this.rgwZoneService.getZoneTree(
349 secondChildNodes = zoneResult['nodes'];
350 this.zoneIds = this.zoneIds.concat(zoneResult['zoneIds']);
351 allSecondChildNodes.push(secondChildNodes);
352 secondChildNodes = {};
354 firstChildNodes['children'] = allSecondChildNodes;
355 allSecondChildNodes = [];
356 allFirstChildNodes.push(firstChildNodes);
357 firstChildNodes = {};
360 rootNodes['children'] = allFirstChildNodes;
361 allNodes.push(rootNodes);
362 firstChildNodes = {};
363 secondChildNodes = {};
365 allFirstChildNodes = [];
366 allSecondChildNodes = [];
369 if (this.zonegroups.length > 0) {
370 // get tree for zonegroup -> zone (standalone zonegroups that don't match a realm eg(initial default))
371 for (const zonegroup of this.zonegroups) {
372 if (!this.realmIds.includes(zonegroup.realm_id)) {
373 rootNodes = this.rgwZonegroupService.getZonegroupTree(zonegroup, this.defaultZonegroupId);
374 for (const zone of zonegroup.zones) {
375 const zoneResult = this.rgwZoneService.getZoneTree(
381 firstChildNodes = zoneResult['nodes'];
382 this.zoneIds = this.zoneIds.concat(zoneResult['zoneIds']);
383 allFirstChildNodes.push(firstChildNodes);
384 firstChildNodes = {};
386 rootNodes['children'] = allFirstChildNodes;
387 allNodes.push(rootNodes);
388 firstChildNodes = {};
390 allFirstChildNodes = [];
394 if (this.zones.length > 0) {
395 // get tree for standalone zones(zones that do not belong to a zonegroup)
396 for (const zone of this.zones) {
397 if (this.zoneIds.length > 0 && !this.zoneIds.includes(zone.id)) {
398 const zoneResult = this.rgwZoneService.getZoneTree(zone, this.defaultZoneId, this.zones);
399 rootNodes = zoneResult['nodes'];
400 allNodes.push(rootNodes);
405 if (this.realms.length < 1 && this.zonegroups.length < 1 && this.zones.length < 1) {
414 this.evaluateMigrateAndReplicationActions();
415 this.rgwMultisiteService.restartGatewayMessage$.subscribe((value) => {
416 if (value !== null) {
417 this.restartGatewayMessage = value;
419 this.checkRestartGatewayMessage();
425 checkRestartGatewayMessage() {
426 this.rgwDaemonService.list().subscribe((data: any) => {
427 const realmName = data.map((item: { [x: string]: any }) => item['realm_name']);
429 this.defaultRealmId !== '' &&
430 this.defaultZonegroupId !== '' &&
431 this.defaultZoneId !== '' &&
432 realmName.includes('')
434 this.restartGatewayMessage = true;
436 this.restartGatewayMessage = false;
442 defaultRealmId: string,
443 defaultZonegroupId: string,
444 defaultZoneId: string
446 const defaultRealm = this.realms?.find((x: { id: string }) => x.id === defaultRealmId);
447 const defaultZonegroup = this.zonegroups?.find(
448 (x: { id: string }) => x.id === defaultZonegroupId
450 const defaultZone = this.zones?.find((x: { id: string }) => x.id === defaultZoneId);
453 defaultRealmName: defaultRealm?.name,
454 defaultZonegroupName: defaultZonegroup?.name,
455 defaultZoneName: defaultZone?.name
459 onNodeSelected(tree: TreeModel, node: TreeNode) {
460 TREE_ACTIONS.ACTIVATE(tree, node, true);
461 this.metadataTitle = node.data.name;
462 this.metadata = node.data.info;
463 node.data.show = true;
467 this.tree.treeModel.expandAll();
471 let isMasterZone = true;
472 if (this.defaultRealmId === '') {
473 return this.messages.noDefaultRealm;
475 this.zonegroups.forEach((zgp: any) => {
476 if (_.isEmpty(zgp.master_zone)) {
477 isMasterZone = false;
482 'Please create a master zone for each existing zonegroup to enable this feature';
483 return this.messages.noMasterZone;
485 this.editTitle = 'Edit';
491 evaluateMigrateAndReplicationActions() {
493 this.realms.length === 0 &&
494 this.zonegroups.length === 1 &&
495 this.zonegroups[0].name === 'default' &&
496 this.zones.length === 1 &&
497 this.zones[0].name === 'default'
499 this.showMigrateAndReplicationActions = true;
501 this.showMigrateAndReplicationActions = false;
503 return this.showMigrateAndReplicationActions;
506 isDeleteDisabled(node: TreeNode): boolean {
507 let disable: boolean = false;
508 let masterZonegroupCount: number = 0;
509 if (node.data.type === 'realm' && node.data.is_default && this.realms.length < 2) {
513 if (node.data.type === 'zonegroup') {
514 if (this.zonegroups.length < 2) {
515 this.deleteTitle = 'You can not delete the only zonegroup available';
517 } else if (node.data.is_default) {
518 this.deleteTitle = 'You can not delete the default zonegroup';
520 } else if (node.data.is_master) {
521 for (let zonegroup of this.zonegroups) {
522 if (zonegroup.is_master === true) {
523 masterZonegroupCount++;
524 if (masterZonegroupCount > 1) break;
527 if (masterZonegroupCount < 2) {
528 this.deleteTitle = 'You can not delete the only master zonegroup available';
534 if (node.data.type === 'zone') {
535 if (this.zones.length < 2) {
536 this.deleteTitle = 'You can not delete the only zone available';
538 } else if (node.data.is_default) {
539 this.deleteTitle = 'You can not delete the default zone';
541 } else if (node.data.is_master && node.data.zone_zonegroup.zones.length < 2) {
543 'You can not delete the master zone as there are no more zones in this zonegroup';
549 this.deleteTitle = 'Delete';
555 delete(node: TreeNode) {
556 if (node.data.type === 'realm') {
557 const modalRef = this.cdsModalService.show(CriticalConfirmationModalComponent, {
558 itemDescription: $localize`${node.data.type} ${node.data.name}`,
559 itemNames: [`${node.data.name}`],
560 submitAction: () => {
561 this.rgwRealmService.delete(node.data.name).subscribe(
563 this.notificationService.show(
564 NotificationType.success,
565 $localize`Realm: '${node.data.name}' deleted successfully`
567 this.cdsModalService.dismissAll();
570 this.cdsModalService.stopLoadingSpinner(modalRef.deletionForm);
575 } else if (node.data.type === 'zonegroup') {
576 this.modalRef = this.modalService.show(RgwMultisiteZonegroupDeletionFormComponent, {
579 } else if (node.data.type === 'zone') {
580 this.modalRef = this.modalService.show(RgwMultisiteZoneDeletionFormComponent, {
588 const fnWaitUntilReconnected = () => {
589 observableTimer(2000).subscribe(() => {
590 // Trigger an API request to check if the connection is
592 this.mgrModuleService.list().subscribe(
594 // Resume showing the notification toasties.
595 this.notificationService.suspendToasties(false);
596 // Unblock the whole UI.
598 // Reload the data table content.
599 this.notificationService.show(NotificationType.success, $localize`Enabled RGW Module`);
600 this.router.navigateByUrl('/', { skipLocationChange: true }).then(() => {
601 this.router.navigate(['/rgw/multisite']);
603 // Reload the data table content.
606 fnWaitUntilReconnected();
612 if (!this.rgwModuleStatus) {
613 $obs = this.mgrModuleService.enable('rgw');
618 // Suspend showing the notification toasties.
619 this.notificationService.suspendToasties(true);
620 // Block the whole UI to prevent user interactions until
621 // the connection to the backend is reestablished
622 this.blockUI.start($localize`Reconnecting, please wait ...`);
623 fnWaitUntilReconnected();