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';
25 selector: 'cd-multi-cluster-list',
26 templateUrl: './multi-cluster-list.component.html',
27 styleUrls: ['./multi-cluster-list.component.scss']
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> = [];
42 selection = new CdTableSelection();
43 bsModalRef: NgbModalRef;
44 clustersTokenMap: Map<string, string> = new Map<string, string>();
46 modalRef: NgbModalRef;
50 managedByConfig$: Observable<any>;
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
66 name: this.actionLabels.CONNECT,
67 disable: (selection: CdTableSelection) => this.getDisable('connect', selection),
68 click: () => this.openRemoteClusterInfoModal('connect')
73 name: this.actionLabels.EDIT,
74 disable: (selection: CdTableSelection) => this.getDisable('edit', selection),
75 click: () => this.openRemoteClusterInfoModal('edit')
80 name: this.actionLabels.RECONNECT,
81 disable: (selection: CdTableSelection) => this.getDisable('reconnect', selection),
82 click: () => this.openRemoteClusterInfoModal('reconnect')
87 name: this.actionLabels.DISCONNECT,
88 disable: (selection: CdTableSelection) => this.getDisable('disconnect', selection),
89 click: () => this.openDeleteClusterModal()
92 this.permissions = this.authStorageService.getPermissions();
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(
111 cluster['remainingDays'] = this.getRemainingDays(cluster['ttl']);
120 prop: 'cluster_alias',
121 name: $localize`Alias`,
125 prop: 'cluster_connection_status',
126 name: $localize`Connection`,
128 cellTransformation: CellTemplate.badge,
129 customTemplateConfig: {
131 1: { value: 'DISCONNECTED', class: 'badge-danger' },
132 0: { value: 'CONNECTED', class: 'badge-success' },
133 2: { value: 'CHECKING..', class: 'badge-info' }
139 name: $localize`FSID`,
144 name: $localize`URL`,
146 cellTemplate: this.urlTpl
150 name: $localize`User`,
155 name: $localize`Token expires`,
157 cellTemplate: this.durationTpl
162 this.multiClusterService.subscribeClusterTokenStatus((resp: object) => {
163 this.clusterTokenStatus = resp;
164 this.checkClusterConnectionStatus();
168 this.managedByConfig$ = this.settingsService.getValues('MANAGED_BY_CLUSTERS');
171 ngOnDestroy(): void {
172 this.subs.unsubscribe();
175 getRemainingDays(time: number): number {
176 if (time === undefined || time == null) {
182 const toDays = 1000 * 60 * 60 * 24;
183 return Math.max(0, Math.floor(time / toDays));
186 getRemainingTimeWithoutSeconds(time: number): number {
187 return Math.floor(time / (1000 * 60)) * 60 * 1000;
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;
198 cluster.cluster_connection_status = 2;
200 if (cluster.cluster_alias === 'local-cluster') {
201 cluster.cluster_connection_status = 0;
207 openRemoteClusterInfoModal(action: string) {
208 const initialState = {
209 clustersData: this.data,
211 cluster: this.selection.first()
213 this.bsModalRef = this.modalService.show(MultiClusterFormComponent, initialState, {
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);
224 updateSelection(selection: CdTableSelection) {
225 this.selection = selection;
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']],
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']}'`
244 const currentRoute = this.router.url.split('?')[0];
245 this.multiClusterService.refreshMultiCluster(currentRoute);
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`;
254 if (!selection.hasSelection && action !== 'connect') {
255 return $localize`Please select one or more clusters to ${action}`;
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`;