]> git.apps.os.sepia.ceph.com Git - ceph.git/blob
5fc1d5d6d06d7afd5a940ef67860219773d756af
[ceph.git] /
1 import { Component, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
2 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
3 import { MultiClusterService } from '~/app/shared/api/multi-cluster.service';
4 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
5 import { Icons } from '~/app/shared/enum/icons.enum';
6 import { CdTableAction } from '~/app/shared/models/cd-table-action';
7 import { CdTableColumn } from '~/app/shared/models/cd-table-column';
8 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
9 import { ModalService } from '~/app/shared/services/modal.service';
10 import { MultiClusterFormComponent } from '../multi-cluster-form/multi-cluster-form.component';
11 import { TableComponent } from '~/app/shared/datatable/table/table.component';
12 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
13 import { Permissions } from '~/app/shared/models/permissions';
14 import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
15 import { NotificationService } from '~/app/shared/services/notification.service';
16 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
17 import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
18 import { MultiCluster } from '~/app/shared/models/multi-cluster';
19 import { Router } from '@angular/router';
20 import { CookiesService } from '~/app/shared/services/cookie.service';
21 import { Observable, Subscription } from 'rxjs';
22 import { SettingsService } from '~/app/shared/api/settings.service';
23
24 @Component({
25   selector: 'cd-multi-cluster-list',
26   templateUrl: './multi-cluster-list.component.html',
27   styleUrls: ['./multi-cluster-list.component.scss']
28 })
29 export class MultiClusterListComponent implements OnInit, OnDestroy {
30   @ViewChild(TableComponent)
31   table: TableComponent;
32   @ViewChild('urlTpl', { static: true })
33   public urlTpl: TemplateRef<any>;
34   @ViewChild('durationTpl', { static: true })
35   durationTpl: TemplateRef<any>;
36   private subs = new Subscription();
37   permissions: Permissions;
38   tableActions: CdTableAction[];
39   clusterTokenStatus: object = {};
40   columns: Array<CdTableColumn> = [];
41   data: any;
42   selection = new CdTableSelection();
43   bsModalRef: NgbModalRef;
44   clustersTokenMap: Map<string, string> = new Map<string, string>();
45   newData: any;
46   modalRef: NgbModalRef;
47   hubUrl: string;
48   currentUrl: string;
49   icons = Icons;
50   managedByConfig$: Observable<any>;
51
52   constructor(
53     private multiClusterService: MultiClusterService,
54     private router: Router,
55     public actionLabels: ActionLabelsI18n,
56     private notificationService: NotificationService,
57     private authStorageService: AuthStorageService,
58     private modalService: ModalService,
59     private cookieService: CookiesService,
60     private settingsService: SettingsService
61   ) {
62     this.tableActions = [
63       {
64         permission: 'create',
65         icon: Icons.add,
66         name: this.actionLabels.CONNECT,
67         disable: (selection: CdTableSelection) => this.getDisable('connect', selection),
68         click: () => this.openRemoteClusterInfoModal('connect')
69       },
70       {
71         permission: 'update',
72         icon: Icons.edit,
73         name: this.actionLabels.EDIT,
74         disable: (selection: CdTableSelection) => this.getDisable('edit', selection),
75         click: () => this.openRemoteClusterInfoModal('edit')
76       },
77       {
78         permission: 'update',
79         icon: Icons.refresh,
80         name: this.actionLabels.RECONNECT,
81         disable: (selection: CdTableSelection) => this.getDisable('reconnect', selection),
82         click: () => this.openRemoteClusterInfoModal('reconnect')
83       },
84       {
85         permission: 'delete',
86         icon: Icons.destroy,
87         name: this.actionLabels.DISCONNECT,
88         disable: (selection: CdTableSelection) => this.getDisable('disconnect', selection),
89         click: () => this.openDeleteClusterModal()
90       }
91     ];
92     this.permissions = this.authStorageService.getPermissions();
93   }
94
95   ngOnInit(): void {
96     this.subs.add(
97       this.multiClusterService.subscribe((resp: object) => {
98         if (resp && resp['config']) {
99           this.hubUrl = resp['hub_url'];
100           this.currentUrl = resp['current_url'];
101           const clusterDetailsArray = Object.values(resp['config']).flat();
102           this.data = clusterDetailsArray;
103           this.checkClusterConnectionStatus();
104           this.data.forEach((cluster: any) => {
105             cluster['remainingTimeWithoutSeconds'] = 0;
106             if (cluster['ttl'] && cluster['ttl'] > 0) {
107               cluster['ttl'] = cluster['ttl'] * 1000;
108               cluster['remainingTimeWithoutSeconds'] = this.getRemainingTimeWithoutSeconds(
109                 cluster['ttl']
110               );
111               cluster['remainingDays'] = this.getRemainingDays(cluster['ttl']);
112             }
113           });
114         }
115       })
116     );
117
118     this.columns = [
119       {
120         prop: 'cluster_alias',
121         name: $localize`Alias`,
122         flexGrow: 2
123       },
124       {
125         prop: 'cluster_connection_status',
126         name: $localize`Connection`,
127         flexGrow: 2,
128         cellTransformation: CellTemplate.badge,
129         customTemplateConfig: {
130           map: {
131             1: { value: 'DISCONNECTED', class: 'badge-danger' },
132             0: { value: 'CONNECTED', class: 'badge-success' },
133             2: { value: 'CHECKING..', class: 'badge-info' }
134           }
135         }
136       },
137       {
138         prop: 'name',
139         name: $localize`FSID`,
140         flexGrow: 2
141       },
142       {
143         prop: 'url',
144         name: $localize`URL`,
145         flexGrow: 2,
146         cellTemplate: this.urlTpl
147       },
148       {
149         prop: 'user',
150         name: $localize`User`,
151         flexGrow: 2
152       },
153       {
154         prop: 'ttl',
155         name: $localize`Token expires`,
156         flexGrow: 2,
157         cellTemplate: this.durationTpl
158       }
159     ];
160
161     this.subs.add(
162       this.multiClusterService.subscribeClusterTokenStatus((resp: object) => {
163         this.clusterTokenStatus = resp;
164         this.checkClusterConnectionStatus();
165       })
166     );
167
168     this.managedByConfig$ = this.settingsService.getValues('MANAGED_BY_CLUSTERS');
169   }
170
171   ngOnDestroy(): void {
172     this.subs.unsubscribe();
173   }
174
175   getRemainingDays(time: number): number {
176     if (time === undefined || time == null) {
177       return undefined;
178     }
179     if (time < 0) {
180       return 0;
181     }
182     const toDays = 1000 * 60 * 60 * 24;
183     return Math.max(0, Math.floor(time / toDays));
184   }
185
186   getRemainingTimeWithoutSeconds(time: number): number {
187     return Math.floor(time / (1000 * 60)) * 60 * 1000;
188   }
189
190   checkClusterConnectionStatus() {
191     if (this.clusterTokenStatus && this.data) {
192       this.data.forEach((cluster: MultiCluster) => {
193         const clusterStatus = this.clusterTokenStatus[cluster.name];
194         if (clusterStatus !== undefined) {
195           cluster.cluster_connection_status = clusterStatus.status;
196           cluster.ttl = clusterStatus.time_left;
197         } else {
198           cluster.cluster_connection_status = 2;
199         }
200         if (cluster.cluster_alias === 'local-cluster') {
201           cluster.cluster_connection_status = 0;
202         }
203       });
204     }
205   }
206
207   openRemoteClusterInfoModal(action: string) {
208     const initialState = {
209       clustersData: this.data,
210       action: action,
211       cluster: this.selection.first()
212     };
213     this.bsModalRef = this.modalService.show(MultiClusterFormComponent, initialState, {
214       size: 'xl'
215     });
216     this.bsModalRef.componentInstance.submitAction.subscribe(() => {
217       const currentRoute = this.router.url.split('?')[0];
218       this.multiClusterService.refreshMultiCluster(currentRoute);
219       this.checkClusterConnectionStatus();
220       this.multiClusterService.isClusterAdded(true);
221     });
222   }
223
224   updateSelection(selection: CdTableSelection) {
225     this.selection = selection;
226   }
227
228   openDeleteClusterModal() {
229     const cluster = this.selection.first();
230     this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
231       infoMessage: $localize`Please note that the data for the disconnected cluster will be visible for a duration of ~ 5 minutes. After this period, it will be automatically removed.`,
232       actionDescription: $localize`Disconnect`,
233       itemDescription: $localize`Cluster`,
234       itemNames: [cluster['cluster_alias'] + ' - ' + cluster['user']],
235       submitAction: () =>
236         this.multiClusterService.deleteCluster(cluster['name'], cluster['user']).subscribe(() => {
237           this.cookieService.deleteToken(`${cluster['name']}-${cluster['user']}`);
238           this.multiClusterService.showPrometheusDelayMessage(true);
239           this.modalRef.close();
240           this.notificationService.show(
241             NotificationType.success,
242             $localize`Disconnected cluster '${cluster['cluster_alias']}'`
243           );
244           const currentRoute = this.router.url.split('?')[0];
245           this.multiClusterService.refreshMultiCluster(currentRoute);
246         })
247     });
248   }
249
250   getDisable(action: string, selection: CdTableSelection): string | boolean {
251     if (this.hubUrl !== this.currentUrl) {
252       return $localize`Please switch to the local-cluster to ${action} a remote cluster`;
253     }
254     if (!selection.hasSelection && action !== 'connect') {
255       return $localize`Please select one or more clusters to ${action}`;
256     }
257     if (selection.hasSingleSelection) {
258       const cluster = selection.first();
259       if (cluster['cluster_alias'] === 'local-cluster' && action !== 'connect') {
260         return $localize`Cannot ${action} local cluster`;
261       }
262     }
263     return false;
264   }
265 }