2 ChangeDetectionStrategy,
9 } from '@angular/core';
10 import { Node } from 'carbon-components-angular/treeview/tree-node.types';
11 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
12 import _ from 'lodash';
14 import { forkJoin, Subscription, timer as observableTimer } from 'rxjs';
15 import { RgwRealmService } from '~/app/shared/api/rgw-realm.service';
16 import { RgwZoneService } from '~/app/shared/api/rgw-zone.service';
17 import { RgwZonegroupService } from '~/app/shared/api/rgw-zonegroup.service';
18 import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
19 import { ActionLabelsI18n, TimerServiceInterval } from '~/app/shared/constants/app.constants';
20 import { Icons } from '~/app/shared/enum/icons.enum';
21 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
22 import { CdTableAction } from '~/app/shared/models/cd-table-action';
23 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
24 import { Permission } from '~/app/shared/models/permissions';
25 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
26 import { ModalService } from '~/app/shared/services/modal.service';
27 import { NotificationService } from '~/app/shared/services/notification.service';
28 import { TimerService } from '~/app/shared/services/timer.service';
29 import { RgwRealm, RgwZone, RgwZonegroup } from '../models/rgw-multisite';
30 import { RgwMultisiteMigrateComponent } from '../rgw-multisite-migrate/rgw-multisite-migrate.component';
31 import { RgwMultisiteZoneDeletionFormComponent } from '../models/rgw-multisite-zone-deletion-form/rgw-multisite-zone-deletion-form.component';
32 import { RgwMultisiteZonegroupDeletionFormComponent } from '../models/rgw-multisite-zonegroup-deletion-form/rgw-multisite-zonegroup-deletion-form.component';
33 import { RgwMultisiteExportComponent } from '../rgw-multisite-export/rgw-multisite-export.component';
34 import { RgwMultisiteImportComponent } from '../rgw-multisite-import/rgw-multisite-import.component';
35 import { RgwMultisiteRealmFormComponent } from '../rgw-multisite-realm-form/rgw-multisite-realm-form.component';
36 import { RgwMultisiteZoneFormComponent } from '../rgw-multisite-zone-form/rgw-multisite-zone-form.component';
37 import { RgwMultisiteZonegroupFormComponent } from '../rgw-multisite-zonegroup-form/rgw-multisite-zonegroup-form.component';
38 import { RgwDaemonService } from '~/app/shared/api/rgw-daemon.service';
39 import { MgrModuleService } from '~/app/shared/api/mgr-module.service';
40 import { BlockUI, NgBlockUI } from 'ng-block-ui';
41 import { Router } from '@angular/router';
42 import { RgwMultisiteWizardComponent } from '../rgw-multisite-wizard/rgw-multisite-wizard.component';
43 import { RgwMultisiteSyncPolicyComponent } from '../rgw-multisite-sync-policy/rgw-multisite-sync-policy.component';
44 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
45 import { RgwMultisiteService } from '~/app/shared/api/rgw-multisite.service';
47 const BASE_URL = 'rgw/multisite/configuration';
50 selector: 'cd-rgw-multisite-details',
51 templateUrl: './rgw-multisite-details.component.html',
52 styleUrls: ['./rgw-multisite-details.component.scss'],
53 changeDetection: ChangeDetectionStrategy.OnPush
55 export class RgwMultisiteDetailsComponent implements OnDestroy, OnInit {
56 private sub = new Subscription();
57 @ViewChild('treeNodeTemplate') labelTpl: TemplateRef<any>;
58 @ViewChild(RgwMultisiteSyncPolicyComponent) syncPolicyComp: RgwMultisiteSyncPolicyComponent;
61 noDefaultRealm: $localize`Please create a default realm first to enable this feature`,
62 noMasterZone: $localize`Please create a master zone for each zone group to enable this feature`,
63 noRealmExists: $localize`No realm exists`,
64 disableExport: $localize`Please create master zone group and master zone for each of the realms`
71 permission: Permission;
72 selection = new CdTableSelection();
73 createTableActions: CdTableAction[];
74 migrateTableAction: CdTableAction[];
75 importAction: CdTableAction[];
76 exportAction: CdTableAction[];
77 multisiteReplicationActions: CdTableAction[];
78 loadingIndicator = true;
80 toNode(values: any): Node[] {
81 return values.map((value: any) => ({
90 children: value?.children ? this.toNode(value.children) : []
94 set nodes(values: any) {
95 this._nodes = this.toNode(values);
96 this.changeDetectionRef.detectChanges();
103 private _nodes: Node[] = [];
105 modalRef: NgbModalRef;
107 realms: RgwRealm[] = [];
108 zonegroups: RgwZonegroup[] = [];
109 zones: RgwZone[] = [];
111 metadataTitle: string;
112 bsModalRef: NgbModalRef;
113 realmIds: string[] = [];
114 zoneIds: string[] = [];
116 defaultZonegroupId = '';
118 multisiteInfo: object[] = [];
119 defaultsInfo: string[] = [];
120 showMigrateAndReplicationActions = false;
121 editTitle: string = 'Edit';
122 deleteTitle: string = 'Delete';
123 disableExport = true;
124 rgwModuleStatus: boolean;
125 restartGatewayMessage = false;
126 rgwModuleData: string | any[] = [];
128 activeNodeId?: string;
131 private modalService: ModalService,
132 private timerService: TimerService,
133 private authStorageService: AuthStorageService,
134 public actionLabels: ActionLabelsI18n,
135 public timerServiceVariable: TimerServiceInterval,
136 public router: Router,
137 public rgwRealmService: RgwRealmService,
138 public rgwZonegroupService: RgwZonegroupService,
139 public rgwZoneService: RgwZoneService,
140 public rgwDaemonService: RgwDaemonService,
141 public mgrModuleService: MgrModuleService,
142 private notificationService: NotificationService,
143 private cdsModalService: ModalCdsService,
144 private rgwMultisiteService: RgwMultisiteService,
145 private changeDetectionRef: ChangeDetectorRef
147 this.permission = this.authStorageService.getPermissions().rgw;
150 openModal(entity: any | string, edit = false) {
151 const entityName = edit ? entity?.data?.type : entity;
152 const action = edit ? 'edit' : 'create';
153 const initialState = {
154 resource: entityName,
157 defaultsInfo: this.defaultsInfo,
158 multisiteInfo: this.multisiteInfo
160 if (entityName === 'realm') {
161 this.bsModalRef = this.cdsModalService.show(RgwMultisiteRealmFormComponent, initialState);
162 } else if (entityName === 'zonegroup') {
163 this.bsModalRef = this.modalService.show(RgwMultisiteZonegroupFormComponent, initialState, {
167 this.bsModalRef = this.modalService.show(RgwMultisiteZoneFormComponent, initialState, {
173 openMultisiteSetupWizard() {
174 this.bsModalRef = this.cdsModalService.show(RgwMultisiteWizardComponent);
178 const initialState = {
179 multisiteInfo: this.multisiteInfo
181 this.bsModalRef = this.modalService.show(RgwMultisiteMigrateComponent, initialState, {
187 const initialState = {
188 multisiteInfo: this.multisiteInfo
190 this.bsModalRef = this.modalService.show(RgwMultisiteImportComponent, initialState, {
196 const initialState = {
197 defaultsInfo: this.defaultsInfo,
198 multisiteInfo: this.multisiteInfo
200 this.bsModalRef = this.modalService.show(RgwMultisiteExportComponent, initialState, {
206 this.realms.forEach((realm: any) => {
207 this.zonegroups.forEach((zonegroup) => {
208 if (realm.id === zonegroup.realm_id) {
209 if (zonegroup.is_master && zonegroup.master_zone !== '') {
210 this.disableExport = false;
215 if (!this.rgwModuleStatus) {
218 if (this.realms.length < 1) {
219 return this.messages.noRealmExists;
220 } else if (this.disableExport) {
221 return this.messages.disableExport;
228 if (!this.rgwModuleStatus) {
236 this.createTableActions = [
238 permission: 'create',
240 name: this.actionLabels.CREATE + ' Realm',
241 click: () => this.openModal('realm')
244 permission: 'create',
246 name: this.actionLabels.CREATE + ' Zone Group',
247 click: () => this.openModal('zonegroup'),
248 disable: () => this.getDisable()
251 permission: 'create',
253 name: this.actionLabels.CREATE + ' Zone',
254 click: () => this.openModal('zone')
257 this.migrateTableAction = [
259 permission: 'create',
261 name: this.actionLabels.MIGRATE,
262 click: () => this.openMigrateModal()
265 this.importAction = [
267 permission: 'create',
268 icon: Icons.download,
269 name: this.actionLabels.IMPORT,
270 click: () => this.openImportModal(),
271 disable: () => this.getDisableImport()
274 this.exportAction = [
276 permission: 'create',
278 name: this.actionLabels.EXPORT,
279 click: () => this.openExportModal(),
280 disable: () => this.getDisableExport()
283 this.multisiteReplicationActions = [
285 permission: 'create',
287 name: this.actionLabels.SETUP_MULTISITE_REPLICATION,
289 this.router.navigate([BASE_URL, { outlets: { modal: 'setup-multisite-replication' } }])
293 const observables = [
294 this.rgwRealmService.getAllRealmsInfo(),
295 this.rgwZonegroupService.getAllZonegroupsInfo(),
296 this.rgwZoneService.getAllZonesInfo()
298 this.sub = this.timerService
299 .get(() => forkJoin(observables), this.timerServiceVariable.TIMER_SERVICE_PERIOD * 2)
301 (multisiteInfo: [object, object, object]) => {
302 this.multisiteInfo = multisiteInfo;
303 this.loadingIndicator = false;
304 this.nodes = this.abstractTreeData(multisiteInfo);
308 this.mgrModuleService.list().subscribe((moduleData: any) => {
309 this.rgwModuleData = moduleData.filter((module: object) => module['name'] === 'rgw');
310 if (this.rgwModuleData.length > 0) {
311 this.rgwModuleStatus = this.rgwModuleData[0].enabled;
315 /* setConfigValues() {
316 this.rgwDaemonService
318 this.defaultsInfo['defaultRealmName'],
319 this.defaultsInfo['defaultZonegroupName'],
320 this.defaultsInfo['defaultZoneName']
322 .subscribe(() => {});
326 this.sub.unsubscribe();
329 private abstractTreeData(multisiteInfo: [object, object, object]): any[] {
330 let allNodes: object[] = [];
332 let firstChildNodes = {};
333 let allFirstChildNodes = [];
334 let secondChildNodes = {};
335 let allSecondChildNodes: {}[] = [];
336 this.realms = multisiteInfo[0]['realms'];
337 this.zonegroups = multisiteInfo[1]['zonegroups'];
338 this.zones = multisiteInfo[2]['zones'];
339 this.defaultRealmId = multisiteInfo[0]['default_realm'];
340 this.defaultZonegroupId = multisiteInfo[1]['default_zonegroup'];
341 this.defaultZoneId = multisiteInfo[2]['default_zone'];
342 this.defaultsInfo = this.getDefaultsEntities(
344 this.defaultZonegroupId,
347 if (this.realms.length > 0) {
348 // get tree for realm -> zonegroup -> zone
349 for (const realm of this.realms) {
350 const result = this.rgwRealmService.getRealmTree(realm, this.defaultRealmId);
351 rootNodes = result['nodes'];
352 this.realmIds = this.realmIds.concat(result['realmIds']);
353 for (const zonegroup of this.zonegroups) {
354 if (zonegroup.realm_id === realm.id) {
355 firstChildNodes = this.rgwZonegroupService.getZonegroupTree(
357 this.defaultZonegroupId,
360 for (const zone of zonegroup.zones) {
361 const zoneResult = this.rgwZoneService.getZoneTree(
368 secondChildNodes = zoneResult['nodes'];
369 this.zoneIds = this.zoneIds.concat(zoneResult['zoneIds']);
370 allSecondChildNodes.push(secondChildNodes);
371 secondChildNodes = {};
373 allSecondChildNodes = allSecondChildNodes.map((x) => ({
375 parentNode: firstChildNodes
377 firstChildNodes['children'] = allSecondChildNodes;
378 allSecondChildNodes = [];
379 allFirstChildNodes.push(firstChildNodes);
380 firstChildNodes = {};
383 allFirstChildNodes = allFirstChildNodes.map((x) => ({ ...x, parentNode: rootNodes }));
384 rootNodes['children'] = allFirstChildNodes;
385 allNodes.push({ ...rootNodes, label: rootNodes?.['name'] || rootNodes?.['id'] });
386 firstChildNodes = {};
387 secondChildNodes = {};
389 allFirstChildNodes = [];
390 allSecondChildNodes = [];
393 if (this.zonegroups.length > 0) {
394 // get tree for zonegroup -> zone (standalone zonegroups that don't match a realm eg(initial default))
395 for (const zonegroup of this.zonegroups) {
396 if (!this.realmIds.includes(zonegroup.realm_id)) {
397 rootNodes = this.rgwZonegroupService.getZonegroupTree(zonegroup, this.defaultZonegroupId);
398 for (const zone of zonegroup.zones) {
399 const zoneResult = this.rgwZoneService.getZoneTree(
405 firstChildNodes = zoneResult['nodes'];
406 this.zoneIds = this.zoneIds.concat(zoneResult['zoneIds']);
407 allFirstChildNodes.push(firstChildNodes);
408 firstChildNodes = {};
410 allFirstChildNodes = allFirstChildNodes.map((x) => ({ ...x, parentNode: rootNodes }));
411 rootNodes['children'] = allFirstChildNodes;
412 allNodes.push({ ...rootNodes, label: rootNodes?.['name'] || rootNodes?.['id'] });
413 firstChildNodes = {};
415 allFirstChildNodes = [];
419 if (this.zones.length > 0) {
420 // get tree for standalone zones(zones that do not belong to a zonegroup)
421 for (const zone of this.zones) {
422 if (this.zoneIds.length > 0 && !this.zoneIds.includes(zone.id)) {
423 const zoneResult = this.rgwZoneService.getZoneTree(zone, this.defaultZoneId, this.zones);
424 rootNodes = zoneResult['nodes'];
425 allNodes.push({ ...rootNodes, label: rootNodes?.['name'] || rootNodes?.['id'] });
430 if (this.realms.length < 1 && this.zonegroups.length < 1 && this.zones.length < 1) {
440 this.evaluateMigrateAndReplicationActions();
441 this.rgwMultisiteService.restartGatewayMessage$.subscribe((value) => {
442 if (value !== null) {
443 this.restartGatewayMessage = value;
445 this.checkRestartGatewayMessage();
451 checkRestartGatewayMessage() {
452 this.rgwDaemonService.list().subscribe((data: any) => {
453 const realmName = data.map((item: { [x: string]: any }) => item['realm_name']);
455 this.defaultRealmId !== '' &&
456 this.defaultZonegroupId !== '' &&
457 this.defaultZoneId !== '' &&
458 realmName.includes('')
460 this.restartGatewayMessage = true;
462 this.restartGatewayMessage = false;
468 defaultRealmId: string,
469 defaultZonegroupId: string,
470 defaultZoneId: string
472 const defaultRealm = this.realms?.find((x: { id: string }) => x.id === defaultRealmId);
473 const defaultZonegroup = this.zonegroups?.find(
474 (x: { id: string }) => x.id === defaultZonegroupId
476 const defaultZone = this.zones?.find((x: { id: string }) => x.id === defaultZoneId);
479 defaultRealmName: defaultRealm?.name,
480 defaultZonegroupName: defaultZonegroup?.name,
481 defaultZoneName: defaultZone?.name
485 onNodeSelected(node: Node) {
486 this.metadataTitle = node?.value?.name;
487 this.metadata = node?.value?.info;
488 this.activeNodeId = node?.value?.id;
489 node.expanded = true;
493 let isMasterZone = true;
494 if (this.defaultRealmId === '') {
495 return this.messages.noDefaultRealm;
497 this.zonegroups.forEach((zgp: any) => {
498 if (_.isEmpty(zgp.master_zone)) {
499 isMasterZone = false;
505 'Please create a master zone for each existing zonegroup to enable this feature';
507 return this.messages.noMasterZone;
510 this.editTitle = 'Edit';
517 evaluateMigrateAndReplicationActions() {
519 this.realms.length === 0 &&
520 this.zonegroups.length === 1 &&
521 this.zonegroups[0].name === 'default' &&
522 this.zones.length === 1 &&
523 this.zones[0].name === 'default'
525 this.showMigrateAndReplicationActions = true;
527 this.showMigrateAndReplicationActions = false;
529 return this.showMigrateAndReplicationActions;
532 isDeleteDisabled(node: Node): { isDisabled: boolean; deleteTitle: string } {
533 let isDisabled: boolean = false;
534 let deleteTitle: string = this.deleteTitle;
535 let masterZonegroupCount: number = 0;
536 if (node?.value?.type === 'realm' && node?.data?.is_default && this.realms.length < 2) {
540 if (node?.data?.type === 'zonegroup') {
541 if (this.zonegroups.length < 2) {
542 deleteTitle = 'You can not delete the only zonegroup available';
544 } else if (node?.data?.is_default) {
545 deleteTitle = 'You can not delete the default zonegroup';
547 } else if (node?.data?.is_master) {
548 for (let zonegroup of this.zonegroups) {
549 if (zonegroup.is_master === true) {
550 masterZonegroupCount++;
551 if (masterZonegroupCount > 1) break;
554 if (masterZonegroupCount < 2) {
555 deleteTitle = 'You can not delete the only master zonegroup available';
561 if (node?.data?.type === 'zone') {
562 if (this.zones.length < 2) {
563 deleteTitle = 'You can not delete the only zone available';
565 } else if (node?.data?.is_default) {
566 deleteTitle = 'You can not delete the default zone';
568 } else if (node?.data?.is_master && node?.data?.zone_zonegroup.zones.length < 2) {
570 'You can not delete the master zone as there are no more zones in this zonegroup';
576 this.deleteTitle = 'Delete';
579 return { isDisabled, deleteTitle };
583 if (node?.data?.type === 'realm') {
584 const modalRef = this.cdsModalService.show(CriticalConfirmationModalComponent, {
585 itemDescription: $localize`${node?.data?.type} ${node?.data?.name}`,
586 itemNames: [`${node?.data?.name}`],
587 submitAction: () => {
588 this.rgwRealmService.delete(node?.data?.name).subscribe(
590 this.notificationService.show(
591 NotificationType.success,
592 $localize`Realm: '${node?.data?.name}' deleted successfully`
594 this.cdsModalService.dismissAll();
597 this.cdsModalService.stopLoadingSpinner(modalRef.deletionForm);
602 } else if (node?.data?.type === 'zonegroup') {
603 this.modalRef = this.modalService.show(RgwMultisiteZonegroupDeletionFormComponent, {
606 } else if (node?.data?.type === 'zone') {
607 this.modalRef = this.modalService.show(RgwMultisiteZoneDeletionFormComponent, {
615 const fnWaitUntilReconnected = () => {
616 observableTimer(2000).subscribe(() => {
617 // Trigger an API request to check if the connection is
619 this.mgrModuleService.list().subscribe(
621 // Resume showing the notification toasties.
622 this.notificationService.suspendToasties(false);
623 // Unblock the whole UI.
625 // Reload the data table content.
626 this.notificationService.show(NotificationType.success, $localize`Enabled RGW Module`);
627 this.router.navigateByUrl('/', { skipLocationChange: true }).then(() => {
628 this.router.navigate(['/rgw/multisite']);
630 // Reload the data table content.
633 fnWaitUntilReconnected();
639 if (!this.rgwModuleStatus) {
640 $obs = this.mgrModuleService.enable('rgw');
645 // Suspend showing the notification toasties.
646 this.notificationService.suspendToasties(true);
647 // Block the whole UI to prevent user interactions until
648 // the connection to the backend is reestablished
649 this.blockUI.start($localize`Reconnecting, please wait ...`);
650 fnWaitUntilReconnected();