]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/blob
ab03649e6377ffd5931fa5801f5d438c16bb91a4
[ceph-ci.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 { DeleteConfirmationModalComponent } from '~/app/shared/components/delete-confirmation-modal/delete-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               cluster['expiryDate'] = new Date(Date.now() + cluster['ttl']).toLocaleString();
119             }
120           });
121         }
122       })
123     );
124
125     this.columns = [
126       {
127         prop: 'cluster_alias',
128         name: $localize`Alias`,
129         flexGrow: 2
130       },
131       {
132         prop: 'cluster_connection_status',
133         name: $localize`Connection`,
134         flexGrow: 2,
135         cellTransformation: CellTemplate.badge,
136         customTemplateConfig: {
137           map: {
138             1: { value: 'DISCONNECTED', class: 'badge-danger' },
139             0: { value: 'CONNECTED', class: 'badge-success' },
140             2: { value: 'CHECKING..', class: 'badge-info' }
141           }
142         }
143       },
144       {
145         prop: 'name',
146         name: $localize`FSID`,
147         flexGrow: 2
148       },
149       {
150         prop: 'url',
151         name: $localize`URL`,
152         flexGrow: 2,
153         cellTemplate: this.urlTpl
154       },
155       {
156         prop: 'user',
157         name: $localize`User`,
158         flexGrow: 2
159       },
160       {
161         prop: 'ttl',
162         name: $localize`Token expires`,
163         flexGrow: 2,
164         cellTemplate: this.durationTpl
165       }
166     ];
167
168     this.subs.add(
169       this.multiClusterService.subscribeClusterTokenStatus((resp: object) => {
170         this.clusterTokenStatus = resp;
171         this.checkClusterConnectionStatus();
172       })
173     );
174
175     this.managedByConfig$ = this.settingsService.getValues('MANAGED_BY_CLUSTERS');
176   }
177
178   ngOnDestroy(): void {
179     this.subs.unsubscribe();
180   }
181
182   getRemainingDays(time: number): number {
183     if (time === undefined || time == null) {
184       return undefined;
185     }
186     if (time < 0) {
187       return 0;
188     }
189     const toDays = 1000 * 60 * 60 * 24;
190     return Math.max(0, Math.floor(time / toDays));
191   }
192
193   getRemainingTimeWithoutSeconds(time: number): number {
194     return Math.floor(time / (1000 * 60)) * 60 * 1000;
195   }
196
197   checkClusterConnectionStatus() {
198     if (this.clusterTokenStatus && this.data) {
199       this.data.forEach((cluster: MultiCluster) => {
200         const clusterStatus = this.clusterTokenStatus[cluster.name];
201         if (clusterStatus !== undefined) {
202           cluster.cluster_connection_status = clusterStatus.status;
203           cluster.ttl = clusterStatus.time_left;
204         } else {
205           cluster.cluster_connection_status = 2;
206         }
207         if (cluster.cluster_alias === 'local-cluster') {
208           cluster.cluster_connection_status = 0;
209         }
210       });
211     }
212   }
213
214   openRemoteClusterInfoModal(action: string) {
215     const initialState = {
216       clustersData: this.data,
217       action: action,
218       cluster: this.selection.first()
219     };
220     this.bsModalRef = this.modalService.show(MultiClusterFormComponent, initialState, {
221       size: 'xl'
222     });
223     this.bsModalRef.componentInstance.submitAction.subscribe(() => {
224       const currentRoute = this.router.url.split('?')[0];
225       this.multiClusterService.refreshMultiCluster(currentRoute);
226       this.checkClusterConnectionStatus();
227       this.multiClusterService.isClusterAdded(true);
228     });
229   }
230
231   openDeleteClusterModal() {
232     const cluster = this.selection.first();
233     this.modalRef = this.cdsModalService.show(DeleteConfirmationModalComponent, {
234       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.`,
235       actionDescription: $localize`Disconnect`,
236       itemDescription: $localize`Cluster`,
237       itemNames: [cluster['cluster_alias'] + ' - ' + cluster['user']],
238       submitAction: () =>
239         this.multiClusterService.deleteCluster(cluster['name'], cluster['user']).subscribe(() => {
240           this.cookieService.deleteToken(`${cluster['name']}-${cluster['user']}`);
241           this.multiClusterService.showPrometheusDelayMessage(true);
242           this.cdsModalService.dismissAll();
243           this.notificationService.show(
244             NotificationType.success,
245             $localize`Disconnected cluster '${cluster['cluster_alias']}'`
246           );
247           const currentRoute = this.router.url.split('?')[0];
248           this.multiClusterService.refreshMultiCluster(currentRoute);
249         })
250     });
251   }
252
253   getDisable(action: string, selection: CdTableSelection): string | boolean {
254     if (this.hubUrl !== this.currentUrl) {
255       return $localize`Please switch to the local-cluster to ${action} a remote cluster`;
256     }
257     if (!selection.hasSelection && action !== 'connect') {
258       return $localize`Please select one or more clusters to ${action}`;
259     }
260     if (selection.hasSingleSelection) {
261       const cluster = selection.first();
262       if (cluster['cluster_alias'] === 'local-cluster' && action !== 'connect') {
263         return $localize`Cannot ${action} local cluster`;
264       }
265     }
266     return false;
267   }
268
269   updateSelection(selection: CdTableSelection) {
270     this.selection = selection;
271   }
272
273   setExpandedRow(expandedRow: any) {
274     super.setExpandedRow(expandedRow);
275     this.router.navigate(['performance-details'], { relativeTo: this.route });
276   }
277
278   refresh() {
279     this.multiClusterService.refresh();
280     this.multiClusterService.refreshTokenStatus();
281   }
282 }