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';
27 selector: 'cd-multi-cluster-list',
28 templateUrl: './multi-cluster-list.component.html',
29 styleUrls: ['./multi-cluster-list.component.scss']
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> = [];
44 selection = new CdTableSelection();
45 bsModalRef: NgbModalRef;
46 clustersTokenMap: Map<string, string> = new Map<string, string>();
48 modalRef: NgbModalRef;
52 managedByConfig$: Observable<any>;
53 prometheusConnectionError: any[] = [];
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
72 name: this.actionLabels.CONNECT,
73 disable: (selection: CdTableSelection) => this.getDisable('connect', selection),
74 click: () => this.openRemoteClusterInfoModal('connect')
79 name: this.actionLabels.EDIT,
80 disable: (selection: CdTableSelection) => this.getDisable('edit', selection),
81 click: () => this.openRemoteClusterInfoModal('edit')
86 name: this.actionLabels.RECONNECT,
87 disable: (selection: CdTableSelection) => this.getDisable('reconnect', selection),
88 click: () => this.openRemoteClusterInfoModal('reconnect')
93 name: this.actionLabels.DISCONNECT,
94 disable: (selection: CdTableSelection) => this.getDisable('disconnect', selection),
95 click: () => this.openDeleteClusterModal()
98 this.permissions = this.authStorageService.getPermissions();
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(
117 cluster['remainingDays'] = this.getRemainingDays(cluster['ttl']);
126 prop: 'cluster_alias',
127 name: $localize`Alias`,
131 prop: 'cluster_connection_status',
132 name: $localize`Connection`,
134 cellTransformation: CellTemplate.badge,
135 customTemplateConfig: {
137 1: { value: 'DISCONNECTED', class: 'badge-danger' },
138 0: { value: 'CONNECTED', class: 'badge-success' },
139 2: { value: 'CHECKING..', class: 'badge-info' }
145 name: $localize`FSID`,
150 name: $localize`URL`,
152 cellTemplate: this.urlTpl
156 name: $localize`User`,
161 name: $localize`Token expires`,
163 cellTemplate: this.durationTpl
168 this.multiClusterService.subscribeClusterTokenStatus((resp: object) => {
169 this.clusterTokenStatus = resp;
170 this.checkClusterConnectionStatus();
174 this.managedByConfig$ = this.settingsService.getValues('MANAGED_BY_CLUSTERS');
177 ngOnDestroy(): void {
178 this.subs.unsubscribe();
181 getRemainingDays(time: number): number {
182 if (time === undefined || time == null) {
188 const toDays = 1000 * 60 * 60 * 24;
189 return Math.max(0, Math.floor(time / toDays));
192 getRemainingTimeWithoutSeconds(time: number): number {
193 return Math.floor(time / (1000 * 60)) * 60 * 1000;
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;
204 cluster.cluster_connection_status = 2;
206 if (cluster.cluster_alias === 'local-cluster') {
207 cluster.cluster_connection_status = 0;
213 openRemoteClusterInfoModal(action: string) {
214 const initialState = {
215 clustersData: this.data,
217 cluster: this.selection.first()
219 this.bsModalRef = this.modalService.show(MultiClusterFormComponent, initialState, {
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);
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']],
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']}'`
246 const currentRoute = this.router.url.split('?')[0];
247 this.multiClusterService.refreshMultiCluster(currentRoute);
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`;
256 if (!selection.hasSelection && action !== 'connect') {
257 return $localize`Please select one or more clusters to ${action}`;
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`;
268 updateSelection(selection: CdTableSelection) {
269 this.selection = selection;
272 setExpandedRow(expandedRow: any) {
273 super.setExpandedRow(expandedRow);
274 this.router.navigate(['performance-details'], { relativeTo: this.route });