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