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