]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/blob
d5e0fd063f8d52c0f0eed7420b844ec3fd3b481a
[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   /* 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     this.rgwDaemonService.list().subscribe((data: any) => {
390       const realmName = data.map((item: { [x: string]: any }) => item['realm_name']);
391       if (
392         this.defaultRealmId != '' &&
393         this.defaultZonegroupId != '' &&
394         this.defaultZoneId != '' &&
395         realmName.includes('')
396       ) {
397         this.restartGatewayMessage = true;
398       }
399     });
400     return allNodes;
401   }
402
403   getDefaultsEntities(
404     defaultRealmId: string,
405     defaultZonegroupId: string,
406     defaultZoneId: string
407   ): any {
408     const defaultRealm = this.realms.find((x: { id: string }) => x.id === defaultRealmId);
409     const defaultZonegroup = this.zonegroups.find(
410       (x: { id: string }) => x.id === defaultZonegroupId
411     );
412     const defaultZone = this.zones.find((x: { id: string }) => x.id === defaultZoneId);
413     const defaultRealmName = defaultRealm !== undefined ? defaultRealm.name : null;
414     const defaultZonegroupName = defaultZonegroup !== undefined ? defaultZonegroup.name : null;
415     const defaultZoneName = defaultZone !== undefined ? defaultZone.name : null;
416     return {
417       defaultRealmName: defaultRealmName,
418       defaultZonegroupName: defaultZonegroupName,
419       defaultZoneName: defaultZoneName
420     };
421   }
422
423   onNodeSelected(tree: TreeModel, node: TreeNode) {
424     TREE_ACTIONS.ACTIVATE(tree, node, true);
425     this.metadataTitle = node.data.name;
426     this.metadata = node.data.info;
427     node.data.show = true;
428   }
429
430   onUpdateData() {
431     this.tree.treeModel.expandAll();
432   }
433
434   getDisable() {
435     let isMasterZone = true;
436     if (this.defaultRealmId === '') {
437       return this.messages.noDefaultRealm;
438     } else {
439       this.zonegroups.forEach((zgp: any) => {
440         if (_.isEmpty(zgp.master_zone)) {
441           isMasterZone = false;
442         }
443       });
444       if (!isMasterZone) {
445         this.editTitle =
446           'Please create a master zone for each existing zonegroup to enable this feature';
447         return this.messages.noMasterZone;
448       } else {
449         this.editTitle = 'Edit';
450         return false;
451       }
452     }
453   }
454
455   getDisableMigrate() {
456     if (
457       this.realms.length === 0 &&
458       this.zonegroups.length === 1 &&
459       this.zonegroups[0].name === 'default' &&
460       this.zones.length === 1 &&
461       this.zones[0].name === 'default'
462     ) {
463       this.showMigrateAction = true;
464     } else {
465       this.showMigrateAction = false;
466     }
467     return this.showMigrateAction;
468   }
469
470   isDeleteDisabled(node: TreeNode): boolean {
471     let disable: boolean = false;
472     let masterZonegroupCount: number = 0;
473     if (node.data.type === 'realm' && node.data.is_default && this.realms.length < 2) {
474       disable = true;
475     }
476
477     if (node.data.type === 'zonegroup') {
478       if (this.zonegroups.length < 2) {
479         this.deleteTitle = 'You can not delete the only zonegroup available';
480         disable = true;
481       } else if (node.data.is_default) {
482         this.deleteTitle = 'You can not delete the default zonegroup';
483         disable = true;
484       } else if (node.data.is_master) {
485         for (let zonegroup of this.zonegroups) {
486           if (zonegroup.is_master === true) {
487             masterZonegroupCount++;
488             if (masterZonegroupCount > 1) break;
489           }
490         }
491         if (masterZonegroupCount < 2) {
492           this.deleteTitle = 'You can not delete the only master zonegroup available';
493           disable = true;
494         }
495       }
496     }
497
498     if (node.data.type === 'zone') {
499       if (this.zones.length < 2) {
500         this.deleteTitle = 'You can not delete the only zone available';
501         disable = true;
502       } else if (node.data.is_default) {
503         this.deleteTitle = 'You can not delete the default zone';
504         disable = true;
505       } else if (node.data.is_master && node.data.zone_zonegroup.zones.length < 2) {
506         this.deleteTitle =
507           'You can not delete the master zone as there are no more zones in this zonegroup';
508         disable = true;
509       }
510     }
511
512     if (!disable) {
513       this.deleteTitle = 'Delete';
514     }
515
516     return disable;
517   }
518
519   delete(node: TreeNode) {
520     if (node.data.type === 'realm') {
521       this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
522         itemDescription: $localize`${node.data.type} ${node.data.name}`,
523         itemNames: [`${node.data.name}`],
524         submitAction: () => {
525           this.rgwRealmService.delete(node.data.name).subscribe(
526             () => {
527               this.modalRef.close();
528               this.notificationService.show(
529                 NotificationType.success,
530                 $localize`Realm: '${node.data.name}' deleted successfully`
531               );
532             },
533             () => {
534               this.modalRef.componentInstance.stopLoadingSpinner();
535             }
536           );
537         }
538       });
539     } else if (node.data.type === 'zonegroup') {
540       this.modalRef = this.modalService.show(RgwMultisiteZonegroupDeletionFormComponent, {
541         zonegroup: node.data
542       });
543     } else if (node.data.type === 'zone') {
544       this.modalRef = this.modalService.show(RgwMultisiteZoneDeletionFormComponent, {
545         zone: node.data
546       });
547     }
548   }
549
550   enableRgwModule() {
551     let $obs;
552     const fnWaitUntilReconnected = () => {
553       observableTimer(2000).subscribe(() => {
554         // Trigger an API request to check if the connection is
555         // re-established.
556         this.mgrModuleService.list().subscribe(
557           () => {
558             // Resume showing the notification toasties.
559             this.notificationService.suspendToasties(false);
560             // Unblock the whole UI.
561             this.blockUI.stop();
562             // Reload the data table content.
563             this.notificationService.show(NotificationType.success, $localize`Enabled RGW Module`);
564             this.router.navigateByUrl('/', { skipLocationChange: true }).then(() => {
565               this.router.navigate(['/rgw/multisite']);
566             });
567             // Reload the data table content.
568           },
569           () => {
570             fnWaitUntilReconnected();
571           }
572         );
573       });
574     };
575
576     if (!this.rgwModuleStatus) {
577       $obs = this.mgrModuleService.enable('rgw');
578     }
579     $obs.subscribe(
580       () => undefined,
581       () => {
582         // Suspend showing the notification toasties.
583         this.notificationService.suspendToasties(true);
584         // Block the whole UI to prevent user interactions until
585         // the connection to the backend is reestablished
586         this.blockUI.start($localize`Reconnecting, please wait ...`);
587         fnWaitUntilReconnected();
588       }
589     );
590   }
591
592   onNavChange(event: any) {
593     if (event.nextId == 'configuration') {
594       this.metadata = null;
595       /*
596         It is a known issue with angular2-tree package when tree is hidden (for example inside tab or modal),
597         it is not rendered when it becomes visible. Solution is to call this.tree.sizeChanged() which recalculates
598         the rendered nodes according to the actual viewport size. (https://angular2-tree.readme.io/docs/common-issues)
599       */
600       setTimeout(() => {
601         this.tree.sizeChanged();
602         this.onUpdateData();
603       }, 200);
604     }
605   }
606 }