]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/blob
6e898e789456b2e5afeae279496879cb61f95dab
[ceph-ci.git] /
1 import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
2 import {
3   TreeComponent,
4   ITreeOptions,
5   TreeModel,
6   TreeNode,
7   TREE_ACTIONS
8 } from '@circlon/angular-tree-component';
9 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
10 import _ from 'lodash';
11
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
41 @Component({
42   selector: 'cd-rgw-multisite-details',
43   templateUrl: './rgw-multisite-details.component.html',
44   styleUrls: ['./rgw-multisite-details.component.scss']
45 })
46 export class RgwMultisiteDetailsComponent implements OnDestroy, OnInit {
47   private sub = new Subscription();
48
49   @ViewChild('tree') tree: TreeComponent;
50
51   messages = {
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`
56   };
57
58   @BlockUI()
59   blockUI: NgBlockUI;
60
61   icons = Icons;
62   permission: Permission;
63   selection = new CdTableSelection();
64   createTableActions: CdTableAction[];
65   migrateTableAction: CdTableAction[];
66   importAction: CdTableAction[];
67   exportAction: CdTableAction[];
68   loadingIndicator = true;
69   nodes: object[] = [];
70   treeOptions: ITreeOptions = {
71     useVirtualScroll: true,
72     nodeHeight: 22,
73     levelPadding: 20,
74     actionMapping: {
75       mouse: {
76         click: this.onNodeSelected.bind(this)
77       }
78     }
79   };
80   modalRef: NgbModalRef;
81
82   realms: RgwRealm[] = [];
83   zonegroups: RgwZonegroup[] = [];
84   zones: RgwZone[] = [];
85   metadata: any;
86   metadataTitle: string;
87   bsModalRef: NgbModalRef;
88   realmIds: string[] = [];
89   zoneIds: string[] = [];
90   defaultRealmId = '';
91   defaultZonegroupId = '';
92   defaultZoneId = '';
93   multisiteInfo: object[] = [];
94   defaultsInfo: string[] = [];
95   showMigrateAction: boolean = false;
96   editTitle: string = 'Edit';
97   deleteTitle: string = 'Delete';
98   disableExport = true;
99   rgwModuleStatus: boolean;
100   restartGatewayMessage = false;
101   rgwModuleData: string | any[] = [];
102
103   constructor(
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
116   ) {
117     this.permission = this.authStorageService.getPermissions().rgw;
118   }
119
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,
125       action: action,
126       info: entity,
127       defaultsInfo: this.defaultsInfo,
128       multisiteInfo: this.multisiteInfo
129     };
130     if (entityName === 'realm') {
131       this.bsModalRef = this.modalService.show(RgwMultisiteRealmFormComponent, initialState, {
132         size: 'lg'
133       });
134     } else if (entityName === 'zonegroup') {
135       this.bsModalRef = this.modalService.show(RgwMultisiteZonegroupFormComponent, initialState, {
136         size: 'lg'
137       });
138     } else {
139       this.bsModalRef = this.modalService.show(RgwMultisiteZoneFormComponent, initialState, {
140         size: 'lg'
141       });
142     }
143   }
144
145   openMigrateModal() {
146     const initialState = {
147       multisiteInfo: this.multisiteInfo
148     };
149     this.bsModalRef = this.modalService.show(RgwMultisiteMigrateComponent, initialState, {
150       size: 'lg'
151     });
152   }
153
154   openImportModal() {
155     const initialState = {
156       multisiteInfo: this.multisiteInfo
157     };
158     this.bsModalRef = this.modalService.show(RgwMultisiteImportComponent, initialState, {
159       size: 'lg'
160     });
161   }
162
163   openExportModal() {
164     const initialState = {
165       defaultsInfo: this.defaultsInfo,
166       multisiteInfo: this.multisiteInfo
167     };
168     this.bsModalRef = this.modalService.show(RgwMultisiteExportComponent, initialState, {
169       size: 'lg'
170     });
171   }
172
173   getDisableExport() {
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;
179           }
180         }
181       });
182     });
183     if (!this.rgwModuleStatus) {
184       return true;
185     }
186     if (this.realms.length < 1) {
187       return this.messages.noRealmExists;
188     } else if (this.disableExport) {
189       return this.messages.disableExport;
190     } else {
191       return false;
192     }
193   }
194
195   getDisableImport() {
196     if (!this.rgwModuleStatus) {
197       return true;
198     } else {
199       return false;
200     }
201   }
202
203   ngOnInit() {
204     const createRealmAction: CdTableAction = {
205       permission: 'create',
206       icon: Icons.add,
207       name: this.actionLabels.CREATE + ' Realm',
208       click: () => this.openModal('realm')
209     };
210     const createZonegroupAction: CdTableAction = {
211       permission: 'create',
212       icon: Icons.add,
213       name: this.actionLabels.CREATE + ' Zone Group',
214       click: () => this.openModal('zonegroup'),
215       disable: () => this.getDisable()
216     };
217     const createZoneAction: CdTableAction = {
218       permission: 'create',
219       icon: Icons.add,
220       name: this.actionLabels.CREATE + ' Zone',
221       click: () => this.openModal('zone')
222     };
223     const migrateMultsiteAction: CdTableAction = {
224       permission: 'read',
225       icon: Icons.exchange,
226       name: this.actionLabels.MIGRATE,
227       click: () => this.openMigrateModal()
228     };
229     const importMultsiteAction: CdTableAction = {
230       permission: 'read',
231       icon: Icons.download,
232       name: this.actionLabels.IMPORT,
233       click: () => this.openImportModal(),
234       disable: () => this.getDisableImport()
235     };
236     const exportMultsiteAction: CdTableAction = {
237       permission: 'read',
238       icon: Icons.upload,
239       name: this.actionLabels.EXPORT,
240       click: () => this.openExportModal(),
241       disable: () => this.getDisableExport()
242     };
243     this.createTableActions = [createRealmAction, createZonegroupAction, createZoneAction];
244     this.migrateTableAction = [migrateMultsiteAction];
245     this.importAction = [importMultsiteAction];
246     this.exportAction = [exportMultsiteAction];
247
248     const observables = [
249       this.rgwRealmService.getAllRealmsInfo(),
250       this.rgwZonegroupService.getAllZonegroupsInfo(),
251       this.rgwZoneService.getAllZonesInfo()
252     ];
253     this.sub = this.timerService
254       .get(() => forkJoin(observables), this.timerServiceVariable.TIMER_SERVICE_PERIOD * 2)
255       .subscribe(
256         (multisiteInfo: [object, object, object]) => {
257           this.multisiteInfo = multisiteInfo;
258           this.loadingIndicator = false;
259           this.nodes = this.abstractTreeData(multisiteInfo);
260         },
261         (_error) => {}
262       );
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;
267       }
268     });
269   }
270
271   /* setConfigValues() {
272     this.rgwDaemonService
273       .setMultisiteConfig(
274         this.defaultsInfo['defaultRealmName'],
275         this.defaultsInfo['defaultZonegroupName'],
276         this.defaultsInfo['defaultZoneName']
277       )
278       .subscribe(() => {});
279   }*/
280
281   ngOnDestroy() {
282     this.sub.unsubscribe();
283   }
284
285   private abstractTreeData(multisiteInfo: [object, object, object]): any[] {
286     let allNodes: object[] = [];
287     let rootNodes = {};
288     let firstChildNodes = {};
289     let allFirstChildNodes = [];
290     let secondChildNodes = {};
291     let allSecondChildNodes: {}[] = [];
292     this.realms = multisiteInfo[0]['realms'];
293     this.zonegroups = multisiteInfo[1]['zonegroups'];
294     this.zones = multisiteInfo[2]['zones'];
295     this.defaultRealmId = multisiteInfo[0]['default_realm'];
296     this.defaultZonegroupId = multisiteInfo[1]['default_zonegroup'];
297     this.defaultZoneId = multisiteInfo[2]['default_zone'];
298     this.defaultsInfo = this.getDefaultsEntities(
299       this.defaultRealmId,
300       this.defaultZonegroupId,
301       this.defaultZoneId
302     );
303     if (this.realms.length > 0) {
304       // get tree for realm -> zonegroup -> zone
305       for (const realm of this.realms) {
306         const result = this.rgwRealmService.getRealmTree(realm, this.defaultRealmId);
307         rootNodes = result['nodes'];
308         this.realmIds = this.realmIds.concat(result['realmIds']);
309         for (const zonegroup of this.zonegroups) {
310           if (zonegroup.realm_id === realm.id) {
311             firstChildNodes = this.rgwZonegroupService.getZonegroupTree(
312               zonegroup,
313               this.defaultZonegroupId,
314               realm
315             );
316             for (const zone of zonegroup.zones) {
317               const zoneResult = this.rgwZoneService.getZoneTree(
318                 zone,
319                 this.defaultZoneId,
320                 this.zones,
321                 zonegroup,
322                 realm
323               );
324               secondChildNodes = zoneResult['nodes'];
325               this.zoneIds = this.zoneIds.concat(zoneResult['zoneIds']);
326               allSecondChildNodes.push(secondChildNodes);
327               secondChildNodes = {};
328             }
329             firstChildNodes['children'] = allSecondChildNodes;
330             allSecondChildNodes = [];
331             allFirstChildNodes.push(firstChildNodes);
332             firstChildNodes = {};
333           }
334         }
335         rootNodes['children'] = allFirstChildNodes;
336         allNodes.push(rootNodes);
337         firstChildNodes = {};
338         secondChildNodes = {};
339         rootNodes = {};
340         allFirstChildNodes = [];
341         allSecondChildNodes = [];
342       }
343     }
344     if (this.zonegroups.length > 0) {
345       // get tree for zonegroup -> zone (standalone zonegroups that don't match a realm eg(initial default))
346       for (const zonegroup of this.zonegroups) {
347         if (!this.realmIds.includes(zonegroup.realm_id)) {
348           rootNodes = this.rgwZonegroupService.getZonegroupTree(zonegroup, this.defaultZonegroupId);
349           for (const zone of zonegroup.zones) {
350             const zoneResult = this.rgwZoneService.getZoneTree(
351               zone,
352               this.defaultZoneId,
353               this.zones,
354               zonegroup
355             );
356             firstChildNodes = zoneResult['nodes'];
357             this.zoneIds = this.zoneIds.concat(zoneResult['zoneIds']);
358             allFirstChildNodes.push(firstChildNodes);
359             firstChildNodes = {};
360           }
361           rootNodes['children'] = allFirstChildNodes;
362           allNodes.push(rootNodes);
363           firstChildNodes = {};
364           rootNodes = {};
365           allFirstChildNodes = [];
366         }
367       }
368     }
369     if (this.zones.length > 0) {
370       // get tree for standalone zones(zones that do not belong to a zonegroup)
371       for (const zone of this.zones) {
372         if (this.zoneIds.length > 0 && !this.zoneIds.includes(zone.id)) {
373           const zoneResult = this.rgwZoneService.getZoneTree(zone, this.defaultZoneId, this.zones);
374           rootNodes = zoneResult['nodes'];
375           allNodes.push(rootNodes);
376           rootNodes = {};
377         }
378       }
379     }
380     if (this.realms.length < 1 && this.zonegroups.length < 1 && this.zones.length < 1) {
381       return [
382         {
383           name: 'No nodes!'
384         }
385       ];
386     }
387     this.realmIds = [];
388     this.zoneIds = [];
389     this.getDisableMigrate();
390     this.rgwDaemonService.list().subscribe((data: any) => {
391       const realmName = data.map((item: { [x: string]: any }) => item['realm_name']);
392       if (
393         this.defaultRealmId != '' &&
394         this.defaultZonegroupId != '' &&
395         this.defaultZoneId != '' &&
396         realmName.includes('')
397       ) {
398         this.restartGatewayMessage = true;
399       }
400     });
401     return allNodes;
402   }
403
404   getDefaultsEntities(
405     defaultRealmId: string,
406     defaultZonegroupId: string,
407     defaultZoneId: string
408   ): any {
409     const defaultRealm = this.realms.find((x: { id: string }) => x.id === defaultRealmId);
410     const defaultZonegroup = this.zonegroups.find(
411       (x: { id: string }) => x.id === defaultZonegroupId
412     );
413     const defaultZone = this.zones.find((x: { id: string }) => x.id === defaultZoneId);
414     const defaultRealmName = defaultRealm !== undefined ? defaultRealm.name : null;
415     const defaultZonegroupName = defaultZonegroup !== undefined ? defaultZonegroup.name : null;
416     const defaultZoneName = defaultZone !== undefined ? defaultZone.name : null;
417     return {
418       defaultRealmName: defaultRealmName,
419       defaultZonegroupName: defaultZonegroupName,
420       defaultZoneName: defaultZoneName
421     };
422   }
423
424   onNodeSelected(tree: TreeModel, node: TreeNode) {
425     TREE_ACTIONS.ACTIVATE(tree, node, true);
426     this.metadataTitle = node.data.name;
427     this.metadata = node.data.info;
428     node.data.show = true;
429   }
430
431   onUpdateData() {
432     this.tree.treeModel.expandAll();
433   }
434
435   getDisable() {
436     let isMasterZone = true;
437     if (this.defaultRealmId === '') {
438       return this.messages.noDefaultRealm;
439     } else {
440       this.zonegroups.forEach((zgp: any) => {
441         if (_.isEmpty(zgp.master_zone)) {
442           isMasterZone = false;
443         }
444       });
445       if (!isMasterZone) {
446         this.editTitle =
447           'Please create a master zone for each existing zonegroup to enable this feature';
448         return this.messages.noMasterZone;
449       } else {
450         this.editTitle = 'Edit';
451         return false;
452       }
453     }
454   }
455
456   getDisableMigrate() {
457     if (
458       this.realms.length === 0 &&
459       this.zonegroups.length === 1 &&
460       this.zonegroups[0].name === 'default' &&
461       this.zones.length === 1 &&
462       this.zones[0].name === 'default'
463     ) {
464       this.showMigrateAction = true;
465     } else {
466       this.showMigrateAction = false;
467     }
468     return this.showMigrateAction;
469   }
470
471   isDeleteDisabled(node: TreeNode): boolean {
472     let disable: boolean = false;
473     let masterZonegroupCount: number = 0;
474     if (node.data.type === 'realm' && node.data.is_default && this.realms.length < 2) {
475       disable = true;
476     }
477
478     if (node.data.type === 'zonegroup') {
479       if (this.zonegroups.length < 2) {
480         this.deleteTitle = 'You can not delete the only zonegroup available';
481         disable = true;
482       } else if (node.data.is_default) {
483         this.deleteTitle = 'You can not delete the default zonegroup';
484         disable = true;
485       } else if (node.data.is_master) {
486         for (let zonegroup of this.zonegroups) {
487           if (zonegroup.is_master === true) {
488             masterZonegroupCount++;
489             if (masterZonegroupCount > 1) break;
490           }
491         }
492         if (masterZonegroupCount < 2) {
493           this.deleteTitle = 'You can not delete the only master zonegroup available';
494           disable = true;
495         }
496       }
497     }
498
499     if (node.data.type === 'zone') {
500       if (this.zones.length < 2) {
501         this.deleteTitle = 'You can not delete the only zone available';
502         disable = true;
503       } else if (node.data.is_default) {
504         this.deleteTitle = 'You can not delete the default zone';
505         disable = true;
506       } else if (node.data.is_master && node.data.zone_zonegroup.zones.length < 2) {
507         this.deleteTitle =
508           'You can not delete the master zone as there are no more zones in this zonegroup';
509         disable = true;
510       }
511     }
512
513     if (!disable) {
514       this.deleteTitle = 'Delete';
515     }
516
517     return disable;
518   }
519
520   delete(node: TreeNode) {
521     if (node.data.type === 'realm') {
522       this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
523         itemDescription: $localize`${node.data.type} ${node.data.name}`,
524         itemNames: [`${node.data.name}`],
525         submitAction: () => {
526           this.rgwRealmService.delete(node.data.name).subscribe(
527             () => {
528               this.modalRef.close();
529               this.notificationService.show(
530                 NotificationType.success,
531                 $localize`Realm: '${node.data.name}' deleted successfully`
532               );
533             },
534             () => {
535               this.modalRef.componentInstance.stopLoadingSpinner();
536             }
537           );
538         }
539       });
540     } else if (node.data.type === 'zonegroup') {
541       this.modalRef = this.modalService.show(RgwMultisiteZonegroupDeletionFormComponent, {
542         zonegroup: node.data
543       });
544     } else if (node.data.type === 'zone') {
545       this.modalRef = this.modalService.show(RgwMultisiteZoneDeletionFormComponent, {
546         zone: node.data
547       });
548     }
549   }
550
551   enableRgwModule() {
552     let $obs;
553     const fnWaitUntilReconnected = () => {
554       observableTimer(2000).subscribe(() => {
555         // Trigger an API request to check if the connection is
556         // re-established.
557         this.mgrModuleService.list().subscribe(
558           () => {
559             // Resume showing the notification toasties.
560             this.notificationService.suspendToasties(false);
561             // Unblock the whole UI.
562             this.blockUI.stop();
563             // Reload the data table content.
564             this.notificationService.show(NotificationType.success, $localize`Enabled RGW Module`);
565             this.router.navigateByUrl('/', { skipLocationChange: true }).then(() => {
566               this.router.navigate(['/rgw/multisite']);
567             });
568             // Reload the data table content.
569           },
570           () => {
571             fnWaitUntilReconnected();
572           }
573         );
574       });
575     };
576
577     if (!this.rgwModuleStatus) {
578       $obs = this.mgrModuleService.enable('rgw');
579     }
580     $obs.subscribe(
581       () => undefined,
582       () => {
583         // Suspend showing the notification toasties.
584         this.notificationService.suspendToasties(true);
585         // Block the whole UI to prevent user interactions until
586         // the connection to the backend is reestablished
587         this.blockUI.start($localize`Reconnecting, please wait ...`);
588         fnWaitUntilReconnected();
589       }
590     );
591   }
592 }