]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboards: multi-cluster improvements and bug fixes 55783/head
authorAashish Sharma <aasharma@li-e74156cc-2f67-11b2-a85c-e98659a63c5c.ibm.com>
Mon, 26 Feb 2024 04:17:34 +0000 (09:47 +0530)
committerAashish Sharma <aasharma@li-e74156cc-2f67-11b2-a85c-e98659a63c5c.ibm.com>
Tue, 26 Mar 2024 04:57:15 +0000 (10:27 +0530)
Fixes: https://tracker.ceph.com/issues/64880
Signed-off-by: Aashish Sharma <aasharma@redhat.com>
17 files changed:
src/pybind/mgr/dashboard/controllers/auth.py
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-form/multi-cluster-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-form/multi-cluster-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-list/multi-cluster-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard-area-chart/dashboard-area-chart.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard-area-chart/dashboard-area-chart.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard-area-chart/dashboard-area-chart.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard-pie/dashboard-pie.component.ts
src/pybind/mgr/dashboard/frontend/src/app/core/navigation/navigation/navigation.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/multi-cluster.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/prometheus.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/enum/dashboard-promqls.enum.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/services/api-interceptor.service.ts

index eca87a9a84edb5dc3aca06a03a4e58d7dbf8acc8..c2287ef51a80f97d7b6335927ed5de9859060b0f 100644 (file)
@@ -54,50 +54,6 @@ class Auth(RESTController, ControllerAuthMixin):
                 pwd_expiration_date = user_data.get('pwdExpirationDate', None)
                 pwd_update_required = user_data.get('pwdUpdateRequired', False)
 
-            if isinstance(Settings.MULTICLUSTER_CONFIG, str):
-                try:
-                    item_to_dict = json.loads(Settings.MULTICLUSTER_CONFIG)
-                except json.JSONDecodeError:
-                    item_to_dict = {}
-                multicluster_config = item_to_dict.copy()
-            else:
-                multicluster_config = Settings.MULTICLUSTER_CONFIG.copy()
-            try:
-                if fsid in multicluster_config['config']:
-                    existing_entries = multicluster_config['config'][fsid]
-                    if not any(entry['user'] == username for entry in existing_entries):
-                        existing_entries.append({
-                            "name": fsid,
-                            "url": origin,
-                            "cluster_alias": "local-cluster",
-                            "user": username
-                        })
-                else:
-                    multicluster_config['config'][fsid] = [{
-                        "name": fsid,
-                        "url": origin,
-                        "cluster_alias": "local-cluster",
-                        "user": username
-                    }]
-
-            except KeyError:
-                multicluster_config = {
-                    'current_url': origin,
-                    'current_user': username,
-                    'hub_url': origin,
-                    'config': {
-                        fsid: [
-                            {
-                                "name": fsid,
-                                "url": origin,
-                                "cluster_alias": "local-cluster",
-                                "user": username
-                            }
-                        ]
-                    }
-                }
-            Settings.MULTICLUSTER_CONFIG = multicluster_config
-
             if user_perms is not None:
                 url_prefix = 'https' if mgr.get_localized_module_option('ssl') else 'http'
 
@@ -110,6 +66,49 @@ class Auth(RESTController, ControllerAuthMixin):
                 token = token.decode('utf-8') if isinstance(token, bytes) else token
 
                 self._set_token_cookie(url_prefix, token)
+                if isinstance(Settings.MULTICLUSTER_CONFIG, str):
+                    try:
+                        item_to_dict = json.loads(Settings.MULTICLUSTER_CONFIG)
+                    except json.JSONDecodeError:
+                        item_to_dict = {}
+                    multicluster_config = item_to_dict.copy()
+                else:
+                    multicluster_config = Settings.MULTICLUSTER_CONFIG.copy()
+                try:
+                    if fsid in multicluster_config['config']:
+                        existing_entries = multicluster_config['config'][fsid]
+                        if not any((entry['user'] == username or entry['cluster_alias'] == 'local-cluster') for entry in existing_entries):  # noqa E501 #pylint: disable=line-too-long
+                            existing_entries.append({
+                                "name": fsid,
+                                "url": origin,
+                                "cluster_alias": "local-cluster",
+                                "user": username
+                            })
+                    else:
+                        multicluster_config['config'][fsid] = [{
+                            "name": fsid,
+                            "url": origin,
+                            "cluster_alias": "local-cluster",
+                            "user": username
+                        }]
+
+                except KeyError:
+                    multicluster_config = {
+                        'current_url': origin,
+                        'current_user': username,
+                        'hub_url': origin,
+                        'config': {
+                            fsid: [
+                                {
+                                    "name": fsid,
+                                    "url": origin,
+                                    "cluster_alias": "local-cluster",
+                                    "user": username
+                                }
+                            ]
+                        }
+                    }
+                Settings.MULTICLUSTER_CONFIG = multicluster_config
                 return {
                     'token': token,
                     'username': username,
index a2d36e4232aa40030fbce715acb09e371a2129af..68af8c1672dc2c95e5fd09f77fa9f5dfc360adbc 100644 (file)
@@ -20,7 +20,7 @@
         <cd-alert-panel type="info"
                         spacingClass="mb-3"
                         i18n
-                        *ngIf="connectionVerified !== undefined && !connectionVerified && connectionMessage === 'Connection refused'">
+                        *ngIf="connectionVerified !== undefined && !connectionVerified && connectionMessage === 'Connection refused' || remoteClusterForm.getValue('showToken')">
         <p>You need to set this cluster's url as the cross origin url in the remote cluster you are trying to connect.
           You can do it by running this CLI command in your remote cluster and proceed with authentication via token.</p>
           <cd-code-block [codes]="[crossOriginCmd]"></cd-code-block>
           <label class="cd-col-form-label required"
                  for="remoteClusterUrl"
                  i18n>Cluster API URL
-            <cd-helper>Enter the Dashboard API URL. You can retrieve it from the CLI with: <b>ceph mgr services</b></cd-helper>
+            <cd-helper>
+              <span>
+                <p>Enter the Dashboard API URL. You can retrieve it from the CLI with: <b>{{ clusterApiUrlCmd }} </b>
+                  <cd-copy-2-clipboard-button [source]="clusterApiUrlCmd"
+                                              [byId]="false"></cd-copy-2-clipboard-button>
+                </p>
+              </span>
+            </cd-helper>
           </label>
           <div class="cd-col-form-input">
             <input class="form-control"
             </span>
           </div>
         </div>
+        <div class="form-group row"
+             *ngIf="remoteClusterForm.getValue('showToken') && action !== 'edit'">
+          <label class="cd-col-form-label required"
+                 for="prometheusApiUrl"
+                 i18n>Prometheus API URL
+            <cd-helper>
+              <span>
+                <p>Enter the Prometheus API URL. You can retrieve it from the CLI with: <b>{{ prometheusApiUrlCmd }} </b>
+                  <cd-copy-2-clipboard-button [source]="prometheusApiUrlCmd"
+                                              [byId]="false"></cd-copy-2-clipboard-button>
+                </p>
+              </span>
+            </cd-helper>
+          </label>
+          <div class="cd-col-form-input">
+            <input id="prometheusApiUrl"
+                   name="prometheusApiUrl"
+                   class="form-control"
+                   type="text"
+                   formControlName="prometheusApiUrl">
+            <span class="invalid-feedback"
+                  *ngIf="remoteClusterForm.showError('prometheusApiUrl', frm, 'required')"
+                  i18n>This field is required.
+            </span>
+          </div>
+        </div>
         <div class="form-group row"
              *ngIf="!remoteClusterForm.getValue('showToken') && !showCrossOriginError && action !== 'edit'">
           <label class="cd-col-form-label required"
             </div>
           </div>
         <div class="form-group row"
-             *ngIf="!showCrossOriginError && action !== 'edit' && !remoteClusterForm.getValue('showToken')">
+             *ngIf="!showCrossOriginError && action !== 'edit' && !remoteClusterForm.getValue('showToken') && !connectionVerified">
           <div class="cd-col-form-offset">
             <div class="custom-control">
               <button class="btn btn-primary"
index ee39a51d47001132b73b54c489ed60f92f32c8b7..b5b5f9ca2e16886cf3114befae8be1060396c963 100644 (file)
@@ -22,13 +22,15 @@ export class MultiClusterFormComponent implements OnInit, OnDestroy {
   readonly endpoints = /^((https?:\/\/)|(www.))(?:([a-zA-Z]+)|(\d+\.\d+.\d+.\d+)):\d{2,5}\/?$/;
   readonly ipv4Rgx = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/i;
   readonly ipv6Rgx = /^(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}$/i;
+  clusterApiUrlCmd = 'ceph mgr services';
+  prometheusApiUrlCmd = 'ceph config get mgr mgr/dashboard/PROMETHEUS_API_HOST';
+  crossOriginCmd = `ceph dashboard set-cross-origin-url ${window.location.origin}`;
   remoteClusterForm: CdFormGroup;
   showToken = false;
   connectionVerified: boolean;
   connectionMessage = '';
   private subs = new Subscription();
   showCrossOriginError = false;
-  crossOriginCmd: string;
   action: string;
   cluster: MultiCluster;
   clustersData: MultiCluster[];
@@ -99,6 +101,11 @@ export class MultiClusterFormComponent implements OnInit, OnDestroy {
           showToken: true
         })
       ]),
+      prometheusApiUrl: new FormControl('', [
+        CdValidators.requiredIf({
+          showToken: true
+        })
+      ]),
       password: new FormControl('', []),
       remoteClusterUrl: new FormControl(null, {
         validators: [
@@ -152,6 +159,7 @@ export class MultiClusterFormComponent implements OnInit, OnDestroy {
     const url = this.remoteClusterForm.getValue('remoteClusterUrl');
     const updatedUrl = url.endsWith('/') ? url.slice(0, -1) : url;
     const clusterAlias = this.remoteClusterForm.getValue('clusterAlias');
+    const prometheusApiUrl = this.remoteClusterForm.getValue('prometheusApiUrl');
     const username = this.remoteClusterForm.getValue('username');
     const password = this.remoteClusterForm.getValue('password');
     const token = this.remoteClusterForm.getValue('apiToken');
@@ -210,6 +218,7 @@ export class MultiClusterFormComponent implements OnInit, OnDestroy {
             token,
             window.location.origin,
             clusterFsid,
+            prometheusApiUrl,
             ssl,
             ssl_certificate
           )
@@ -256,7 +265,6 @@ export class MultiClusterFormComponent implements OnInit, OnDestroy {
               this.connectionVerified = false;
               this.showCrossOriginError = true;
               this.connectionMessage = resp;
-              this.crossOriginCmd = `ceph config set mgr mgr/dashboard/cross_origin_url ${window.location.origin} `;
               this.notificationService.show(
                 NotificationType.error,
                 $localize`Connection to the cluster failed`
index 07238019c19973969257c241cb60f581a1bc5c9e..da5e08dfebea4fc8af3a5c2cc862b8bc3ce38bbf 100644 (file)
@@ -16,7 +16,6 @@ import { NotificationService } from '~/app/shared/services/notification.service'
 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
 import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
 import { MultiCluster } from '~/app/shared/models/multi-cluster';
-import { SummaryService } from '~/app/shared/services/summary.service';
 import { Router } from '@angular/router';
 import { CookiesService } from '~/app/shared/services/cookie.service';
 
@@ -41,11 +40,12 @@ export class MultiClusterListComponent {
   clustersTokenMap: Map<string, string> = new Map<string, string>();
   newData: any;
   modalRef: NgbModalRef;
+  hubUrl: string;
+  currentUrl: string;
 
   constructor(
     private multiClusterService: MultiClusterService,
     private router: Router,
-    private summaryService: SummaryService,
     public actionLabels: ActionLabelsI18n,
     private notificationService: NotificationService,
     private authStorageService: AuthStorageService,
@@ -57,6 +57,7 @@ export class MultiClusterListComponent {
         permission: 'create',
         icon: Icons.add,
         name: this.actionLabels.CONNECT,
+        disable: (selection: CdTableSelection) => this.getDisable('connect', selection),
         click: () => this.openRemoteClusterInfoModal('connect')
       },
       {
@@ -87,6 +88,8 @@ export class MultiClusterListComponent {
   ngOnInit(): void {
     this.multiClusterService.subscribe((resp: object) => {
       if (resp && resp['config']) {
+        this.hubUrl = resp['hub_url'];
+        this.currentUrl = resp['current_url'];
         const clusterDetailsArray = Object.values(resp['config']).flat();
         this.data = clusterDetailsArray;
         this.checkClusterConnectionStatus();
@@ -139,7 +142,7 @@ export class MultiClusterListComponent {
   checkClusterConnectionStatus() {
     if (this.clusterTokenStatus && this.data) {
       this.data.forEach((cluster: MultiCluster) => {
-        const clusterStatus = this.clusterTokenStatus[cluster.name];
+        const clusterStatus = this.clusterTokenStatus[cluster.name.trim()];
 
         if (clusterStatus !== undefined) {
           cluster.cluster_connection_status = clusterStatus.status;
@@ -164,18 +167,10 @@ export class MultiClusterListComponent {
       size: 'xl'
     });
     this.bsModalRef.componentInstance.submitAction.subscribe(() => {
-      this.multiClusterService.refresh();
-      this.summaryService.refresh();
       const currentRoute = this.router.url.split('?')[0];
-      if (currentRoute.includes('dashboard')) {
-        this.router.navigateByUrl('/pool', { skipLocationChange: true }).then(() => {
-          this.router.navigate([currentRoute]);
-        });
-      } else {
-        this.router.navigateByUrl('/', { skipLocationChange: true }).then(() => {
-          this.router.navigate([currentRoute]);
-        });
-      }
+      this.multiClusterService.refreshMultiCluster(currentRoute);
+      this.checkClusterConnectionStatus();
+      this.multiClusterService.isClusterAdded(true);
     });
   }
 
@@ -186,28 +181,35 @@ export class MultiClusterListComponent {
   openDeleteClusterModal() {
     const cluster = this.selection.first();
     this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
+      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.`,
       actionDescription: $localize`Disconnect`,
       itemDescription: $localize`Cluster`,
       itemNames: [cluster['cluster_alias'] + ' - ' + cluster['user']],
       submitAction: () =>
         this.multiClusterService.deleteCluster(cluster['name'], cluster['user']).subscribe(() => {
           this.cookieService.deleteToken(`${cluster['name']}-${cluster['user']}`);
+          this.multiClusterService.showPrometheusDelayMessage(true);
           this.modalRef.close();
           this.notificationService.show(
             NotificationType.success,
             $localize`Disconnected cluster '${cluster['cluster_alias']}'`
           );
+          const currentRoute = this.router.url.split('?')[0];
+          this.multiClusterService.refreshMultiCluster(currentRoute);
         })
     });
   }
 
   getDisable(action: string, selection: CdTableSelection): string | boolean {
-    if (!selection.hasSelection) {
+    if (this.hubUrl !== this.currentUrl) {
+      return $localize`Please switch to the local-cluster to ${action} a remote cluster`;
+    }
+    if (!selection.hasSelection && action !== 'connect') {
       return $localize`Please select one or more clusters to ${action}`;
     }
     if (selection.hasSingleSelection) {
       const cluster = selection.first();
-      if (cluster['cluster_alias'] === 'local-cluster') {
+      if (cluster['cluster_alias'] === 'local-cluster' && action !== 'connect') {
         return $localize`Cannot ${action} local cluster`;
       }
     }
index 0542b1868bae3e7243c170d34585bb942835c90b..46d6bab35a9ccf1ab0a91110edeb8eb598cf896c 100644 (file)
@@ -42,6 +42,8 @@
         <i class="mx-auto d-block"
            [ngClass]="[icons.large3x, icons.spinner, icons.spin]">
         </i>
+        <p class="text-center mt-3"
+           i18n>Loading data, Please wait...</p>
       </div>
     </div>
   </div>
 <div class="container-fluid h-100 p-4"
      *ngIf="isMultiCluster; else emptyCluster">
   <ng-container *ngIf="!loading; else loadingTpl">
+    <cd-alert-panel type="info"
+                    spacingClass="mb-3"
+                    [showTitle]="false"
+                    size="slim"
+                    *ngIf="showDeletionMessage"
+                    (dismissed)="onDismissed()"
+                    [dismissible]="true"
+                    i18n>
+      <p>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.</p>
+    </cd-alert-panel>
     <cd-card-group>
       <div class="col-lg-4">
         <div class="row">
                    i18n-title
                    class="col-sm-6 m-0 p-0 ps-2 pe-2"
                    aria-label="Total number of hosts"
-                   *ngIf="queriesResults['TOTAL_HOSTS'][0][1] !== '0'">
+                   *ngIf="queriesResults['TOTAL_HOSTS'] && queriesResults['TOTAL_HOSTS'][0]">
             <span class="text-center">
               <h3>{{ queriesResults['TOTAL_HOSTS'][0][1] }}</h3>
             </span>
                  aria-label="Cluster Utilization card"
                  *ngIf="clusters">
           <div class="ms-4 me-4 mt-0">
-            <cd-dashboard-time-selector (selectedTime)="getPrometheusData($event)">
+            <cd-dashboard-time-selector (selectedTime)="getPrometheusData($event, 'clusterUtilization')">
             </cd-dashboard-time-selector>
             <cd-dashboard-area-chart  chartTitle="Capacity"
                                       [labelsArray]="capacityLabels"
+                                      isMultiCluster="true"
                                       dataUnits="B"
                                       [dataArray]="capacityValues"
                                       [truncateLabel]="true"
                                      [labelsArray]="iopsLabels"
                                      dataUnits=""
                                      decimals="0"
+                                     isMultiCluster="true"
                                      [dataArray]="iopsValues"
                                      [truncateLabel]="true"
                                      *ngIf="iopsLabels && iopsValues">
                                      [labelsArray]="throughputLabels"
                                      dataUnits="B/s"
                                      decimals="2"
+                                     isMultiCluster="true"
                                      [dataArray]="throughputValues"
                                      [truncateLabel]="true"
                                      *ngIf="throughputLabels && throughputLabels">
                    aria-label="Pools Utilization card"
                    *ngIf="clusters">
             <div class="ms-4 me-4 mt-0">
-              <cd-dashboard-time-selector (selectedTime)="getPrometheusData($event)">
+              <cd-dashboard-time-selector (selectedTime)="getPrometheusData($event, 'poolUtilization')">
               </cd-dashboard-time-selector>
               <cd-dashboard-area-chart  chartTitle="Capacity"
                                         [labelsArray]="poolCapacityLabels"
                                         dataUnits="B"
+                                        isMultiCluster="true"
                                         [dataArray]="poolCapacityValues"
                                         *ngIf="poolCapacityLabels && poolCapacityValues"
                                         [truncateLabel]="true">
                                        [labelsArray]="poolIOPSLabels"
                                        dataUnits=""
                                        decimals="0"
+                                       isMultiCluster="true"
                                        [dataArray]="poolIOPSValues"
                                        *ngIf="poolIOPSLabels && poolIOPSValues"
                                        [truncateLabel]="true">
                                        [labelsArray]="poolThroughputLabels"
                                        dataUnits="B/s"
                                        decimals="2"
+                                       isMultiCluster="true"
                                        [dataArray]="poolThroughputValues"
                                        *ngIf="poolThroughputLabels && poolThroughputValues"
                                        [truncateLabel]="true">
index ab8b413e73623d5e557424fc499e42c6839cb88e..18dc80406169e3b3cb5cefd619e795d5a07be103 100644 (file)
@@ -1,4 +1,4 @@
-import { Component, OnInit, ViewChild } from '@angular/core';
+import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
 import { Subscription } from 'rxjs';
 import { MultiClusterService } from '~/app/shared/api/multi-cluster.service';
@@ -6,17 +6,23 @@ import { Icons } from '~/app/shared/enum/icons.enum';
 import { ModalService } from '~/app/shared/services/modal.service';
 import { MultiClusterFormComponent } from './multi-cluster-form/multi-cluster-form.component';
 import { PrometheusService } from '~/app/shared/api/prometheus.service';
-import { MultiClusterPromqls as queries } from '~/app/shared/enum/dashboard-promqls.enum';
 import { CdTableColumn } from '~/app/shared/models/cd-table-column';
 import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
 import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
+import { Router } from '@angular/router';
+
+import {
+  MultiClusterPromqls as allQueries,
+  MultiClusterPromqlsForClusterUtilization as ClusterUltilizationQueries,
+  MultiClusterPromqlsForPoolUtilization as PoolUltilizationQueries
+} from '~/app/shared/enum/dashboard-promqls.enum';
 
 @Component({
   selector: 'cd-multi-cluster',
   templateUrl: './multi-cluster.component.html',
   styleUrls: ['./multi-cluster.component.scss']
 })
-export class MultiClusterComponent implements OnInit {
+export class MultiClusterComponent implements OnInit, OnDestroy {
   COUNT_OF_UTILIZATION_CHARTS = 5;
 
   @ViewChild('nameTpl', { static: true })
@@ -56,7 +62,7 @@ export class MultiClusterComponent implements OnInit {
   isMultiCluster = true;
   clusterTokenStatus: object = {};
   localClusterName: string;
-  clusters: any;
+  clusters: any = [];
   connectionErrorsCount = 0;
 
   capacityLabels: string[] = [];
@@ -72,13 +78,38 @@ export class MultiClusterComponent implements OnInit {
   poolIOPSValues: string[] = [];
   poolCapacityValues: string[] = [];
   poolThroughputValues: string[] = [];
+  showDeletionMessage = false;
+  isClusterAdded = false;
+  selectedQueries: any;
+  PROMETHEUS_DELAY = 20000;
+  LOAD_DELAY = 5000;
+  CLUSTERS_REFRESH_INTERVAL = 30000;
+  interval: NodeJS.Timer;
+  selectedTime: any;
+  multiClusterQueries: any = {};
 
   constructor(
     private multiClusterService: MultiClusterService,
     private modalService: ModalService,
+    private router: Router,
     private prometheusService: PrometheusService,
     private dimlessBinaryPipe: DimlessBinaryPipe
-  ) {}
+  ) {
+    this.multiClusterQueries = {
+      cluster: {
+        queries: ClusterUltilizationQueries,
+        selectedTime: this.prometheusService.lastHourDateObject
+      },
+      pool: {
+        queries: PoolUltilizationQueries,
+        selectedTime: this.prometheusService.lastHourDateObject
+      },
+      all: {
+        queries: allQueries,
+        selectedTime: this.prometheusService.lastHourDateObject
+      }
+    };
+  }
 
   ngOnInit(): void {
     this.columns = [
@@ -160,7 +191,24 @@ export class MultiClusterComponent implements OnInit {
         this.clusterTokenStatus = resp;
       })
     );
-    this.getPrometheusData(this.prometheusService.lastHourDateObject);
+
+    this.isClusterAdded = this.multiClusterService.isClusterAdded();
+
+    if (this.isClusterAdded) {
+      setTimeout(() => {
+        this.getPrometheusData(this.prometheusService.lastHourDateObject);
+        this.multiClusterService.isClusterAdded(false);
+      }, this.PROMETHEUS_DELAY);
+    } else {
+      this.showDeletionMessage = this.multiClusterService.showPrometheusDelayMessage();
+      if (this.showDeletionMessage) {
+        setTimeout(() => {
+          this.getPrometheusData(this.prometheusService.lastHourDateObject);
+        }, this.LOAD_DELAY);
+      } else {
+        this.getPrometheusData(this.prometheusService.lastHourDateObject);
+      }
+    }
   }
 
   openRemoteClusterInfoModal() {
@@ -170,17 +218,69 @@ export class MultiClusterComponent implements OnInit {
     this.bsModalRef = this.modalService.show(MultiClusterFormComponent, initialState, {
       size: 'lg'
     });
+    this.bsModalRef.componentInstance.submitAction.subscribe(() => {
+      this.loading = true;
+      setTimeout(() => {
+        const currentRoute = this.router.url.split('?')[0];
+        this.multiClusterService.refreshMultiCluster(currentRoute);
+        this.getPrometheusData(this.prometheusService.lastHourDateObject);
+      }, this.PROMETHEUS_DELAY);
+    });
   }
 
-  getPrometheusData(selectedTime: any) {
+  getPrometheusData(selectedTime: any, selectedQueries?: string) {
+    const validRangeQueries = [
+      'CLUSTER_CAPACITY_UTILIZATION',
+      'CLUSTER_IOPS_UTILIZATION',
+      'CLUSTER_THROUGHPUT_UTILIZATION',
+      'POOL_CAPACITY_UTILIZATION',
+      'POOL_IOPS_UTILIZATION',
+      'POOL_THROUGHPUT_UTILIZATION'
+    ];
+    const validQueries = [
+      'ALERTS',
+      'MGR_METADATA',
+      'HEALTH_STATUS',
+      'TOTAL_CAPACITY',
+      'USED_CAPACITY',
+      'POOLS',
+      'OSDS',
+      'CLUSTER_CAPACITY_UTILIZATION',
+      'CLUSTER_IOPS_UTILIZATION',
+      'CLUSTER_THROUGHPUT_UTILIZATION',
+      'POOL_CAPACITY_UTILIZATION',
+      'POOL_IOPS_UTILIZATION',
+      'POOL_THROUGHPUT_UTILIZATION',
+      'HOSTS',
+      'CLUSTER_ALERTS'
+    ];
+
+    if (selectedQueries) {
+      if (selectedQueries === 'poolUtilization') {
+        this.multiClusterQueries.pool['selectedTime'] = selectedTime;
+      }
+
+      if (selectedQueries === 'clusterUtilization') {
+        this.multiClusterQueries.cluster.selectedTime = selectedTime;
+      }
+    }
+
     this.prometheusService
-      .getMultiClusterQueriesData(selectedTime, queries, this.queriesResults)
+      .getMultiClusterQueriesData(
+        this.queriesResults,
+        validQueries,
+        validRangeQueries,
+        this.multiClusterQueries
+      )
       .subscribe((data: any) => {
         this.queriesResults = data;
         this.loading = false;
         this.alerts = this.queriesResults.ALERTS;
         this.getAlertsInfo();
         this.getClustersInfo();
+        this.interval = setInterval(() => {
+          this.getClustersInfo();
+        }, this.CLUSTERS_REFRESH_INTERVAL);
       });
   }
 
@@ -224,7 +324,6 @@ export class MultiClusterComponent implements OnInit {
     }
 
     const clusters: ClusterInfo[] = [];
-
     this.queriesResults.TOTAL_CAPACITY?.forEach((totalCapacityMetric: any) => {
       const clusterName = totalCapacityMetric.metric.cluster;
       const totalCapacity = parseInt(totalCapacityMetric.value[1]);
@@ -240,7 +339,7 @@ export class MultiClusterComponent implements OnInit {
       const available_capacity = totalCapacity - usedCapacity;
 
       clusters.push({
-        cluster: clusterName,
+        cluster: clusterName.trim(),
         status,
         alert,
         total_capacity: totalCapacity,
@@ -318,7 +417,6 @@ export class MultiClusterComponent implements OnInit {
       }
       labels.push(label);
     }
-    // console.log(labels)
     return labels;
   }
 
@@ -329,4 +427,13 @@ export class MultiClusterComponent implements OnInit {
     }
     return values;
   }
+
+  onDismissed() {
+    this.showDeletionMessage = false;
+    this.multiClusterService.showPrometheusDelayMessage(false);
+  }
+
+  ngOnDestroy(): void {
+    clearInterval(this.interval);
+  }
 }
index 2b4878e995d2a14c92a86efa3c76e9d8be977156..99a71181b70e3d98cd22143901f3e2ec65477f3e 100644 (file)
@@ -24,7 +24,7 @@
   </div>
 
   <div class="col-9 d-flex flex-column">
-    <div class="chart mt-3">
+    <div [ngClass]="{'chart mt-3': !isMultiCluster, 'mt-3': isMultiCluster}">
       <canvas baseChart
               [datasets]="chartData.dataset"
               [options]="options"
index 80cdb1ea23692f9b93a497d29ec6a05c3a4e974b..bcfd5a892b672bc7823d02554747f25784a28ca9 100644 (file)
@@ -63,22 +63,22 @@ describe('DashboardAreaChartComponent', () => {
 
   it('should set label', () => {
     component.ngOnChanges({ dataArray: new SimpleChange(null, component.dataArray, false) });
-    expect(component.chartData.dataset[0].label).toEqual('Read');
+    expect(component.chartData.dataset[0].label).toEqual('Total');
     expect(component.chartData.dataset[1].label).toEqual('Write');
-    expect(component.chartData.dataset[2].label).toEqual('Total');
+    expect(component.chartData.dataset[2].label).toEqual('Read');
   });
 
   it('should transform and update data', () => {
     component.ngOnChanges({ dataArray: new SimpleChange(null, component.dataArray, false) });
     expect(component.chartData.dataset[0].data).toEqual([
-      { x: 1000, y: 110 },
-      { x: 3000, y: 130 }
+      { x: 5000, y: 150 },
+      { x: 6000, y: 160 }
     ]);
   });
 
   it('should set currentData to last value', () => {
     component.ngOnChanges({ dataArray: new SimpleChange(null, component.dataArray, false) });
-    expect(component.currentChartData.dataset[0].currentData).toBe('130');
+    expect(component.currentChartData.dataset[0].currentData).toBe('160');
   });
 
   it('should keep data units consistency', () => {
index 607a3b7d51ad7221334e35b639f475b5f9b8e8a5..8a267b4782aa8587ee25dd2f99fee78aa0a274d4 100644 (file)
@@ -31,6 +31,8 @@ export class DashboardAreaChartComponent implements OnChanges {
   decimals?: number = 1;
   @Input()
   truncateLabel = false;
+  @Input()
+  isMultiCluster?: boolean = false;
 
   currentDataUnits: string;
   currentData: number;
@@ -214,8 +216,13 @@ export class DashboardAreaChartComponent implements OnChanges {
           [this.maxConvertedValue, this.maxConvertedValueUnits] = this.convertUnits(
             this.maxValue
           ).split(' ');
+          this.currentChartData.dataset[index]['currentDataValue'] = currentDataValue;
         }
       }
+      this.currentChartData.dataset.sort(
+        (a: { currentDataValue: string }, b: { currentDataValue: string }) =>
+          parseFloat(b['currentDataValue']) - parseFloat(a['currentDataValue'])
+      );
     }
 
     if (this.chart) {
index b0c253c33e9d8c7c436c827ff5eb7a35668b02e4..fa194024db9eefb13613de6d515ad5754f29f41a 100644 (file)
@@ -157,8 +157,10 @@ export class DashboardPieComponent implements OnChanges, OnInit {
   private prepareRawUsage(chart: Record<string, any>, data: Record<string, any>) {
     const nearFullRatioPercent = this.lowThreshold * 100;
     const fullRatioPercent = this.highThreshold * 100;
-    const percentAvailable = this.calcPercentage(data.max - data.current, data.max);
-    const percentUsed = this.calcPercentage(data.current, data.max);
+    const max = typeof data.max === 'string' ? parseFloat(data.max) : data.max;
+    const current = typeof data.current === 'string' ? parseFloat(data.current) : data.current;
+    const percentAvailable = this.calcPercentage(max - current, max);
+    const percentUsed = this.calcPercentage(current, max);
 
     if (fullRatioPercent >= 0 && percentUsed >= fullRatioPercent) {
       this.color = 'chart-color-red';
index de8b0a267db82b6573ce9c184d4a00c68def8c77..6f52dc6cf339142024bae644bb608ab6b19a16bd 100644 (file)
@@ -71,6 +71,7 @@ export class NavigationComponent implements OnInit, OnDestroy {
       this.multiClusterService.subscribe((resp: object) => {
         const clustersConfig = resp['config'];
         if (clustersConfig) {
+          this.clustersMap.clear();
           Object.keys(clustersConfig).forEach((clusterKey: string) => {
             const clusterDetailsList = clustersConfig[clusterKey];
             clusterDetailsList.forEach((clusterDetails: MultiCluster) => {
@@ -212,9 +213,6 @@ export class NavigationComponent implements OnInit, OnDestroy {
       },
       () => {},
       () => {
-        this.multiClusterService.refresh();
-        this.summaryService.refresh();
-
         // force refresh grafana api url to get the correct url for the selected cluster
         this.settingsService.ifSettingConfigured(
           'api/grafana/url',
@@ -223,15 +221,7 @@ export class NavigationComponent implements OnInit, OnDestroy {
           true
         );
         const currentRoute = this.router.url.split('?')[0];
-        if (currentRoute.includes('dashboard')) {
-          this.router.navigateByUrl('/pool', { skipLocationChange: true }).then(() => {
-            this.router.navigate([currentRoute]);
-          });
-        } else {
-          this.router.navigateByUrl('/', { skipLocationChange: true }).then(() => {
-            this.router.navigate([currentRoute]);
-          });
-        }
+        this.multiClusterService.refreshMultiCluster(currentRoute);
       }
     );
   }
index ffb312de4d9d723c4f4f34ae010afe8681c0f145..5e6ab6e3606f90d2513fae43cba2fb6b5bf8a138 100644 (file)
@@ -3,6 +3,8 @@ import { Injectable } from '@angular/core';
 import { BehaviorSubject, Observable, Subscription } from 'rxjs';
 import { TimerService } from '../services/timer.service';
 import { filter } from 'rxjs/operators';
+import { SummaryService } from '../services/summary.service';
+import { Router } from '@angular/router';
 
 @Injectable({
   providedIn: 'root'
@@ -13,7 +15,14 @@ export class MultiClusterService {
   msData$ = this.msSource.asObservable();
   private tokenStatusSource = new BehaviorSubject<any>(null);
   tokenStatusSource$ = this.tokenStatusSource.asObservable();
-  constructor(private http: HttpClient, private timerService: TimerService) {}
+  showDeletionMessage = false;
+  isClusterAddedFlag = false;
+  constructor(
+    private http: HttpClient,
+    private timerService: TimerService,
+    private summaryService: SummaryService,
+    private router: Router
+  ) {}
 
   startPolling(): Subscription {
     return this.timerService
@@ -41,7 +50,9 @@ export class MultiClusterService {
 
         if (tempMap.size > 0) {
           clustersTokenMap = tempMap;
-          dataSubscription.unsubscribe();
+          if (dataSubscription) {
+            dataSubscription.unsubscribe();
+          }
           this.checkAndStartTimer(clustersTokenMap);
         }
       }
@@ -95,6 +106,7 @@ export class MultiClusterService {
     token = '',
     hub_url = '',
     clusterFsid = '',
+    prometheusApiUrl = '',
     ssl = false,
     cert = ''
   ) {
@@ -106,6 +118,7 @@ export class MultiClusterService {
       token,
       hub_url,
       cluster_fsid: clusterFsid,
+      prometheus_api_url: prometheusApiUrl,
       ssl_verify: ssl,
       ssl_certificate: cert
     });
@@ -119,7 +132,7 @@ export class MultiClusterService {
     ssl = false,
     cert = ''
   ) {
-    return this.http.post('api/multi-cluster/reconnect_cluster', {
+    return this.http.put('api/multi-cluster/reconnect_cluster', {
       url,
       username,
       password,
@@ -169,4 +182,32 @@ export class MultiClusterService {
 
     return this.http.get<object>('api/multi-cluster/check_token_status', { params });
   }
+
+  showPrometheusDelayMessage(showDeletionMessage?: boolean) {
+    if (showDeletionMessage !== undefined) {
+      this.showDeletionMessage = showDeletionMessage;
+    }
+    return this.showDeletionMessage;
+  }
+
+  isClusterAdded(isClusterAddedFlag?: boolean) {
+    if (isClusterAddedFlag !== undefined) {
+      this.isClusterAddedFlag = isClusterAddedFlag;
+    }
+    return this.isClusterAddedFlag;
+  }
+
+  refreshMultiCluster(currentRoute: string) {
+    this.refresh();
+    this.summaryService.refresh();
+    if (currentRoute.includes('dashboard')) {
+      this.router.navigateByUrl('/pool', { skipLocationChange: true }).then(() => {
+        this.router.navigate([currentRoute]);
+      });
+    } else {
+      this.router.navigateByUrl('/', { skipLocationChange: true }).then(() => {
+        this.router.navigate([currentRoute]);
+      });
+    }
+  }
 }
index b7db0bc2f3cca79f76040f318c56aaeeb52a076d..eaa1696dc873dfd761e2745465b15962e9d16909 100644 (file)
@@ -200,7 +200,12 @@ export class PrometheusService {
     return this.http.get<any>(`${this.baseURL}/data`, { params });
   }
 
-  getMultiClusterQueriesData(selectedTime: any, queries: any, queriesResults: any) {
+  getMultiClusterQueriesData(
+    queriesResults: any,
+    validQueries: string[],
+    validRangeQueries: string[],
+    multiClusterQueries: any
+  ) {
     return new Observable((observer) => {
       this.ifPrometheusConfigured(() => {
         if (this.timerGetPrometheusDataSub) {
@@ -208,63 +213,45 @@ export class PrometheusService {
         }
 
         this.timerGetPrometheusDataSub = timer(0, this.timerTime).subscribe(() => {
-          selectedTime = this.updateTimeStamp(selectedTime);
+          let requests: any[] = [];
+          let queryNames: string[] = [];
 
-          const requests = [];
-          for (const queryName in queries) {
-            if (queries.hasOwnProperty(queryName)) {
-              const validRangeQueries1 = [
-                'CLUSTER_CAPACITY_UTILIZATION',
-                'CLUSTER_IOPS_UTILIZATION',
-                'CLUSTER_THROUGHPUT_UTILIZATION',
-                'POOL_CAPACITY_UTILIZATION',
-                'POOL_IOPS_UTILIZATION',
-                'POOL_THROUGHPUT_UTILIZATION'
-              ];
-              if (validRangeQueries1.includes(queryName)) {
-                const query = queries[queryName];
-                const request = this.getMultiClusterQueryRangeData({
-                  params: encodeURIComponent(query),
-                  start: selectedTime['start'],
-                  end: selectedTime['end'],
-                  step: selectedTime['step']
-                });
-                requests.push(request);
-              } else {
-                const query = queries[queryName];
-                const request = this.getMultiClusterData({
-                  params: encodeURIComponent(query),
-                  start: selectedTime['start'],
-                  end: selectedTime['end'],
-                  step: selectedTime['step']
-                });
-                requests.push(request);
+          Object.entries(multiClusterQueries).forEach(([key, _value]) => {
+            for (const queryName in multiClusterQueries[key].queries) {
+              if (multiClusterQueries[key].queries.hasOwnProperty(queryName)) {
+                const query = multiClusterQueries[key].queries[queryName];
+                const start = this.updateTimeStamp(multiClusterQueries[key].selectedTime)['start'];
+                const end = this.updateTimeStamp(multiClusterQueries[key].selectedTime)['end'];
+                const step = this.updateTimeStamp(multiClusterQueries[key].selectedTime)['step'];
+
+                if (validRangeQueries.includes(queryName)) {
+                  const request = this.getMultiClusterQueryRangeData({
+                    params: encodeURIComponent(query),
+                    start,
+                    end,
+                    step
+                  });
+                  requests.push(request);
+                  queryNames.push(queryName);
+                } else {
+                  const request = this.getMultiClusterData({
+                    params: encodeURIComponent(query),
+                    start,
+                    end,
+                    step
+                  });
+                  requests.push(request);
+                  queryNames.push(queryName);
+                }
               }
             }
-          }
+          });
 
           forkJoin(requests).subscribe(
             (responses: any[]) => {
               for (let i = 0; i < responses.length; i++) {
                 const data = responses[i];
-                const queryName = Object.keys(queries)[i];
-                const validQueries = [
-                  'ALERTS',
-                  'MGR_METADATA',
-                  'HEALTH_STATUS',
-                  'TOTAL_CAPACITY',
-                  'USED_CAPACITY',
-                  'POOLS',
-                  'OSDS',
-                  'CLUSTER_CAPACITY_UTILIZATION',
-                  'CLUSTER_IOPS_UTILIZATION',
-                  'CLUSTER_THROUGHPUT_UTILIZATION',
-                  'POOL_CAPACITY_UTILIZATION',
-                  'POOL_IOPS_UTILIZATION',
-                  'POOL_THROUGHPUT_UTILIZATION',
-                  'HOSTS',
-                  'CLUSTER_ALERTS'
-                ];
+                const queryName = queryNames[i];
                 if (data.result.length) {
                   if (validQueries.includes(queryName)) {
                     queriesResults[queryName] = data.result;
index cc2eded0e3b8f79d9b0d9d36924c2306eea6c15d..41b0ed5ddb7bbd868c73376e2a96cdd9b473ea63 100644 (file)
           [formGroup]="deletionForm"
           novalidate>
       <div class="modal-body">
+        <cd-alert-panel *ngIf="infoMessage"
+                        type="info"
+                        spacingClass="mb-3"
+                        i18n>
+          <p>{{ infoMessage }}</p>
+        </cd-alert-panel>
         <ng-container *ngTemplateOutlet="bodyTemplate; context: bodyContext"></ng-container>
         <div class="question">
           <span *ngIf="itemNames; else noNames">
index 406f992a9df22a7aeed2ea3264044b8023e013d1..65c0a4f5ee2d20d7f91b294360e30e99b406878c 100644 (file)
@@ -25,6 +25,7 @@ export class CriticalConfirmationModalComponent implements OnInit {
   itemDescription: 'entry';
   itemNames: string[];
   actionDescription = 'delete';
+  infoMessage: string;
 
   childFormGroup: CdFormGroup;
   childFormGroupTemplate: TemplateRef<any>;
index 9a85d108a6b566f17b8e4cf58828b15faa4ac2d2..117cc943d51a1d6a3af1894abbe5fa3eb6e5b3e9 100644 (file)
@@ -36,11 +36,17 @@ export enum MultiClusterPromqls {
   ALERTS = 'ALERTS{alertstate="firing"}',
   HOSTS = 'sum by (hostname, cluster) (group by (hostname, cluster) (ceph_osd_metadata)) or vector(0)',
   TOTAL_HOSTS = 'count by (cluster) (ceph_osd_metadata) or vector(0)',
-  CLUSTER_ALERTS = 'count by (cluster) (ALERTS{alertstate="firing"}) or vector(0)',
-  CLUSTER_CAPACITY_UTILIZATION = 'topk(2, ceph_cluster_total_used_bytes)',
-  CLUSTER_IOPS_UTILIZATION = 'topk(2, sum by (cluster) (rate(ceph_pool_wr[1m])) + sum by (cluster) (rate(ceph_pool_rd[1m])) )',
-  CLUSTER_THROUGHPUT_UTILIZATION = 'topk(2, sum by (cluster) (rate(ceph_pool_wr_bytes[1m])) + sum by (cluster) (rate(ceph_pool_rd_bytes[1m])) )',
-  POOL_CAPACITY_UTILIZATION = 'topk(2, ceph_pool_bytes_used/ceph_pool_max_avail * on(pool_id, cluster) group_left(instance, name) ceph_pool_metadata)',
-  POOL_IOPS_UTILIZATION = 'topk(2, (rate(ceph_pool_rd[1m]) + rate(ceph_pool_wr[1m])) * on(pool_id, cluster) group_left(instance, name) ceph_pool_metadata )',
-  POOL_THROUGHPUT_UTILIZATION = 'topk(2, (irate(ceph_pool_rd_bytes[1m]) + irate(ceph_pool_wr_bytes[1m])) * on(pool_id, cluster) group_left(instance, name) ceph_pool_metadata )'
+  CLUSTER_ALERTS = 'count by (cluster) (ALERTS{alertstate="firing"}) or vector(0)'
+}
+
+export enum MultiClusterPromqlsForClusterUtilization {
+  CLUSTER_CAPACITY_UTILIZATION = 'topk(5, ceph_cluster_total_used_bytes)',
+  CLUSTER_IOPS_UTILIZATION = 'topk(5, sum by (cluster) (rate(ceph_pool_wr[1m])) + sum by (cluster) (rate(ceph_pool_rd[1m])) )',
+  CLUSTER_THROUGHPUT_UTILIZATION = 'topk(5, sum by (cluster) (rate(ceph_pool_wr_bytes[1m])) + sum by (cluster) (rate(ceph_pool_rd_bytes[1m])) )'
+}
+
+export enum MultiClusterPromqlsForPoolUtilization {
+  POOL_CAPACITY_UTILIZATION = 'topk(5, ceph_pool_bytes_used/ceph_pool_max_avail * on(pool_id, cluster) group_left(instance, name) ceph_pool_metadata)',
+  POOL_IOPS_UTILIZATION = 'topk(5, (rate(ceph_pool_rd[1m]) + rate(ceph_pool_wr[1m])) * on(pool_id, cluster) group_left(instance, name) ceph_pool_metadata )',
+  POOL_THROUGHPUT_UTILIZATION = 'topk(5, (irate(ceph_pool_rd_bytes[1m]) + irate(ceph_pool_wr_bytes[1m])) * on(pool_id, cluster) group_left(instance, name) ceph_pool_metadata )'
 }
index d1e98487789bec2b418d842e665de9a12ffe34b8..84a630346d47672c73111b9ae2999b187ed24a2f 100644 (file)
@@ -17,8 +17,6 @@ import { NotificationType } from '../enum/notification-type.enum';
 import { CdNotificationConfig } from '../models/cd-notification';
 import { FinishedTask } from '../models/finished-task';
 import { NotificationService } from './notification.service';
-import { MultiClusterService } from '../api/multi-cluster.service';
-import { SummaryService } from './summary.service';
 import { AuthStorageService } from './auth-storage.service';
 import { CookiesService } from './cookie.service';
 
@@ -31,37 +29,12 @@ export class CdHttpErrorResponse extends HttpErrorResponse {
   providedIn: 'root'
 })
 export class ApiInterceptorService implements HttpInterceptor {
-  localClusterDetails: object;
-  dashboardClustersMap: Map<string, string> = new Map<string, string>();
   constructor(
     private router: Router,
     public notificationService: NotificationService,
-    private summaryService: SummaryService,
     private authStorageService: AuthStorageService,
-    private multiClusterService: MultiClusterService,
     private cookieService: CookiesService
-  ) {
-    this.multiClusterService.subscribe((resp: any) => {
-      const clustersConfig = resp['config'];
-      const hub_url = resp['hub_url'];
-      if (clustersConfig) {
-        Object.keys(clustersConfig).forEach((clusterKey: string) => {
-          const clusterDetailsList = clustersConfig[clusterKey];
-
-          clusterDetailsList.forEach((clusterDetails: any) => {
-            const clusterUrl = clusterDetails['url'];
-            const clusterName = clusterDetails['name'];
-
-            this.dashboardClustersMap.set(clusterUrl, clusterName);
-
-            if (clusterDetails['url'] === hub_url) {
-              this.localClusterDetails = clusterDetails;
-            }
-          });
-        });
-      }
-    });
-  }
+  ) {}
 
   intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
     const acceptHeader = request.headers.get('Accept');
@@ -88,8 +61,8 @@ export class ApiInterceptorService implements HttpInterceptor {
     const ALWAYS_TO_HUB_APIs = [
       'api/auth/login',
       'api/auth/logout',
-      'api/multi-cluster/get_config',
       'api/multi-cluster/set_config',
+      'api/multi-cluster/get_config',
       'api/multi-cluster/auth'
     ];
 
@@ -132,26 +105,8 @@ export class ApiInterceptorService implements HttpInterceptor {
               timeoutId = this.notificationService.notifyTask(finishedTask);
               break;
             case 401:
-              if (this.dashboardClustersMap.size > 1) {
-                this.multiClusterService.setCluster(this.localClusterDetails).subscribe(() => {
-                  localStorage.setItem('cluster_api_url', this.localClusterDetails['url']);
-                });
-                this.multiClusterService.refresh();
-                this.summaryService.refresh();
-                const currentRoute = this.router.url.split('?')[0];
-                if (currentRoute.includes('dashboard')) {
-                  this.router.navigateByUrl('/pool', { skipLocationChange: true }).then(() => {
-                    this.router.navigate([currentRoute]);
-                  });
-                } else {
-                  this.router.navigateByUrl('/', { skipLocationChange: true }).then(() => {
-                    this.router.navigate([currentRoute]);
-                  });
-                }
-              } else {
-                this.authStorageService.remove();
-                this.router.navigate(['/login']);
-              }
+              this.authStorageService.remove();
+              this.router.navigate(['/login']);
               break;
             case 403:
               this.router.navigate(['error'], {