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