]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: disable multi-cluster feature for non-hub clusters 56190/head
authorAashish Sharma <aasharma@li-e74156cc-2f67-11b2-a85c-e98659a63c5c.ibm.com>
Thu, 14 Mar 2024 11:57:29 +0000 (17:27 +0530)
committerAashish Sharma <aasharma@li-e74156cc-2f67-11b2-a85c-e98659a63c5c.ibm.com>
Wed, 27 Mar 2024 11:11:04 +0000 (16:41 +0530)
Fixes: https://tracker.ceph.com/issues/65056
Signed-off-by: Aashish Sharma <aasharma@redhat.com>
15 files changed:
src/pybind/mgr/dashboard/controllers/multi_cluster.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.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-list/multi-cluster-list.component.scss
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.scss
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/dashboard-v3.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.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/openapi.yaml
src/pybind/mgr/dashboard/settings.py

index d69a7da26094c5f109b70af5957c751a45dad03e..b1aebddb6f6bff8c9f75ee74ea895c9e2109fc7c 100644 (file)
@@ -56,40 +56,93 @@ class MultiCluster(RESTController):
     def auth(self, url: str, cluster_alias: str, username: str,
              password=None, token=None, hub_url=None, cluster_fsid=None,
              prometheus_api_url=None, ssl_verify=False, ssl_certificate=None):
+        try:
+            hub_fsid = mgr.get('config')['fsid']
+        except KeyError:
+            hub_fsid = ''
+
         if password:
             payload = {
                 'username': username,
                 'password': password
             }
-            content = self._proxy('POST', url, 'api/auth', payload=payload)
-            if 'token' not in content:
-                raise DashboardException(
-                    "Could not authenticate to remote cluster",
-                    http_status_code=400,
-                    component='dashboard')
-
-            cluster_token = content['token']
+            cluster_token = self.check_cluster_connection(url, payload, username,
+                                                          ssl_verify, ssl_certificate)
 
             self._proxy('PUT', url, 'ui-api/multi-cluster/set_cors_endpoint',
                         payload={'url': hub_url}, token=cluster_token, verify=ssl_verify,
                         cert=ssl_certificate)
+
             fsid = self._proxy('GET', url, 'api/health/get_cluster_fsid', token=cluster_token)
 
+            managed_by_clusters_content = self._proxy('GET', url,
+                                                      'api/settings/MANAGED_BY_CLUSTERS',
+                                                      token=cluster_token)
+
+            managed_by_clusters_config = managed_by_clusters_content['value']
+
+            if managed_by_clusters_config is not None:
+                managed_by_clusters_config.append({'url': hub_url, 'fsid': hub_fsid})
+
+            self._proxy('PUT', url, 'api/settings/MANAGED_BY_CLUSTERS',
+                        payload={'value': managed_by_clusters_config}, token=cluster_token,
+                        verify=ssl_verify, cert=ssl_certificate)
+
             # add prometheus targets
             prometheus_url = self._proxy('GET', url, 'api/settings/PROMETHEUS_API_HOST',
                                          token=cluster_token)
+
             _set_prometheus_targets(prometheus_url['value'])
 
             self.set_multi_cluster_config(fsid, username, url, cluster_alias,
                                           cluster_token, prometheus_url['value'],
                                           ssl_verify, ssl_certificate)
-            return
+            return True
 
         if token and cluster_fsid and prometheus_api_url:
             _set_prometheus_targets(prometheus_api_url)
             self.set_multi_cluster_config(cluster_fsid, username, url,
                                           cluster_alias, token, prometheus_api_url,
                                           ssl_verify, ssl_certificate)
+        return True
+
+    def check_cluster_connection(self, url, payload, username, ssl_verify, ssl_certificate):
+        try:
+            content = self._proxy('POST', url, 'api/auth', payload=payload,
+                                  verify=ssl_verify, cert=ssl_certificate)
+            if 'token' not in content:
+                raise DashboardException(msg=content['detail'], code='invalid_credentials',
+                                         component='multi-cluster')
+
+            user_content = self._proxy('GET', url, f'api/user/{username}',
+                                       token=content['token'])
+
+            if 'status' in user_content and user_content['status'] == '403 Forbidden':
+                raise DashboardException(msg='User is not an administrator',
+                                         code='invalid_permission', component='multi-cluster')
+            if 'roles' in user_content and 'administrator' not in user_content['roles']:
+                raise DashboardException(msg='User is not an administrator',
+                                         code='invalid_permission', component='multi-cluster')
+
+        except Exception as e:
+            if '[Errno 111] Connection refused' in str(e):
+                raise DashboardException(msg='Connection refused',
+                                         code='connection_refused', component='multi-cluster')
+            raise DashboardException(msg=str(e), code='connection_failed',
+                                     component='multi-cluster')
+
+        cluster_token = content['token']
+
+        managed_by_clusters_content = self._proxy('GET', url, 'api/settings/MANAGED_BY_CLUSTERS',
+                                                  token=cluster_token)
+
+        managed_by_clusters_config = managed_by_clusters_content['value']
+
+        if len(managed_by_clusters_config) > 1:
+            raise DashboardException(msg='Cluster is already managed by another cluster',
+                                     code='cluster_managed_by_another_cluster',
+                                     component='multi-cluster')
+        return cluster_token
 
     def set_multi_cluster_config(self, fsid, username, url, cluster_alias, token,
                                  prometheus_url=None, ssl_verify=False, ssl_certificate=None):
@@ -144,7 +197,7 @@ class MultiCluster(RESTController):
 
     @Endpoint('PUT')
     @UpdatePermission
-    # pylint: disable=unused-variable
+    # pylint: disable=W0613
     def reconnect_cluster(self, url: str, username=None, password=None, token=None,
                           ssl_verify=False, ssl_certificate=None):
         multicluster_config = self.load_multi_cluster_config()
@@ -153,24 +206,18 @@ class MultiCluster(RESTController):
                 'username': username,
                 'password': password
             }
-            content = self._proxy('POST', url, 'api/auth', payload=payload,
-                                  verify=ssl_verify, cert=ssl_certificate)
-            if 'token' not in content:
-                raise DashboardException(
-                    "Could not authenticate to remote cluster",
-                    http_status_code=400,
-                    component='dashboard')
 
-            token = content['token']
+            cluster_token = self.check_cluster_connection(url, payload, username,
+                                                          ssl_verify, ssl_certificate)
 
-        if username and token:
+        if username and cluster_token:
             if "config" in multicluster_config:
                 for _, cluster_details in multicluster_config["config"].items():
                     for cluster in cluster_details:
                         if cluster["url"] == url and cluster["user"] == username:
-                            cluster['token'] = token
+                            cluster['token'] = cluster_token
             Settings.MULTICLUSTER_CONFIG = multicluster_config
-        return Settings.MULTICLUSTER_CONFIG
+        return True
 
     @Endpoint('PUT')
     @UpdatePermission
@@ -189,10 +236,17 @@ class MultiCluster(RESTController):
     @DeletePermission
     def delete_cluster(self, cluster_name, cluster_user):
         multicluster_config = self.load_multi_cluster_config()
+        try:
+            hub_fsid = mgr.get('config')['fsid']
+        except KeyError:
+            hub_fsid = ''
         if "config" in multicluster_config:
             for key, value in list(multicluster_config['config'].items()):
                 if value[0]['name'] == cluster_name and value[0]['user'] == cluster_user:
-
+                    cluster_url = value[0]['url']
+                    cluster_token = value[0]['token']
+                    cluster_ssl_certificate = value[0]['ssl_certificate']
+                    cluster_ssl_verify = value[0]['ssl_verify']
                     orch_backend = mgr.get_module_option_ex('orchestrator', 'orchestrator')
                     try:
                         if orch_backend == 'cephadm':
@@ -204,55 +258,25 @@ class MultiCluster(RESTController):
                     except KeyError:
                         pass
 
+                    managed_by_clusters_content = self._proxy('GET', cluster_url,
+                                                              'api/settings/MANAGED_BY_CLUSTERS',
+                                                              token=cluster_token)
+
+                    managed_by_clusters_config = managed_by_clusters_content['value']
+                    for cluster in managed_by_clusters_config:
+                        if cluster['fsid'] == hub_fsid:
+                            managed_by_clusters_config.remove(cluster)
+
+                    self._proxy('PUT', cluster_url, 'api/settings/MANAGED_BY_CLUSTERS',
+                                payload={'value': managed_by_clusters_config}, token=cluster_token,
+                                verify=cluster_ssl_verify, cert=cluster_ssl_certificate)
+
                     del multicluster_config['config'][key]
                     break
 
         Settings.MULTICLUSTER_CONFIG = multicluster_config
         return Settings.MULTICLUSTER_CONFIG
 
-    @Endpoint('POST')
-    @CreatePermission
-    # pylint: disable=R0911
-    def verify_connection(self, url=None, username=None, password=None, token=None,
-                          ssl_verify=False, ssl_certificate=None):
-        if token:
-            try:
-                payload = {
-                    'token': token
-                }
-                content = self._proxy('POST', url, 'api/auth/check', payload=payload,
-                                      verify=ssl_verify, cert=ssl_certificate)
-                if 'permissions' not in content:
-                    return content['detail']
-                user_content = self._proxy('GET', url, f'api/user/{username}',
-                                           token=content['token'])
-                if 'status' in user_content and user_content['status'] == '403 Forbidden':
-                    return 'User is not an administrator'
-            except Exception as e:  # pylint: disable=broad-except
-                if '[Errno 111] Connection refused' in str(e):
-                    return 'Connection refused'
-                return 'Connection failed'
-
-        if username and password:
-            try:
-                payload = {
-                    'username': username,
-                    'password': password
-                }
-                content = self._proxy('POST', url, 'api/auth', payload=payload,
-                                      verify=ssl_verify, cert=ssl_certificate)
-                if 'token' not in content:
-                    return content['detail']
-                user_content = self._proxy('GET', url, f'api/user/{username}',
-                                           token=content['token'])
-                if 'status' in user_content and user_content['status'] == '403 Forbidden':
-                    return 'User is not an administrator'
-            except Exception as e:  # pylint: disable=broad-except
-                if '[Errno 111] Connection refused' in str(e):
-                    return 'Connection refused'
-                return 'Connection failed'
-        return 'Connection successful'
-
     @Endpoint()
     @ReadPermission
     def get_config(self):
index 68af8c1672dc2c95e5fd09f77fa9f5dfc360adbc..7f92a26dece7c6dbf7dd5ce3f69e12a1f6429580 100644 (file)
         </div>
 
         <!-- ssl_cert -->
-          <div *ngIf="remoteClusterForm.controls.ssl.value"
-               class="form-group row">
-            <label class="cd-col-form-label"
-                   for="ssl_cert">
-              <span i18n>Certificate</span>
-              <cd-helper i18n>The SSL certificate in PEM format.</cd-helper>
-            </label>
-            <div class="cd-col-form-input">
-              <textarea id="ssl_cert"
-                        class="form-control resize-vertical text-monospace text-pre"
-                        formControlName="ssl_cert"
-                        rows="5">
-              </textarea>
-              <input type="file"
-                     (change)="fileUpload($event.target.files, 'ssl_cert')">
-              <span class="invalid-feedback"
-                    *ngIf="remoteClusterForm.showError('ssl_cert', frm, 'required')"
-                    i18n>This field is required.</span>
-              <span class="invalid-feedback"
-                    *ngIf="remoteClusterForm.showError('ssl_cert', frm, 'pattern')"
-                    i18n>Invalid SSL certificate.</span>
-            </div>
-          </div>
-        <div class="form-group row"
-             *ngIf="!showCrossOriginError && action !== 'edit' && !remoteClusterForm.getValue('showToken') && !connectionVerified">
-          <div class="cd-col-form-offset">
-            <div class="custom-control">
-              <button class="btn btn-primary"
-                      type="button"
-                      [disabled]="(remoteClusterForm.getValue('showToken') && remoteClusterForm.getValue('apiToken') === '') || (!remoteClusterForm.getValue('showToken') && (remoteClusterForm.getValue('username') === '' || remoteClusterForm.getValue('password') === ''))"
-                      (click)="verifyConnection()">
-                Verify Connection
-              </button>
-            </div>
+        <div *ngIf="remoteClusterForm.controls.ssl.value"
+             class="form-group row">
+          <label class="cd-col-form-label"
+                 for="ssl_cert">
+            <span i18n>Certificate</span>
+            <cd-helper i18n>The SSL certificate in PEM format.</cd-helper>
+          </label>
+          <div class="cd-col-form-input">
+            <textarea id="ssl_cert"
+                      class="form-control resize-vertical text-monospace text-pre"
+                      formControlName="ssl_cert"
+                      rows="5">
+            </textarea>
+            <input type="file"
+                   (change)="fileUpload($event.target.files, 'ssl_cert')">
+            <span class="invalid-feedback"
+                  *ngIf="remoteClusterForm.showError('ssl_cert', frm, 'required')"
+                  i18n>This field is required.</span>
+            <span class="invalid-feedback"
+                  *ngIf="remoteClusterForm.showError('ssl_cert', frm, 'pattern')"
+                  i18n>Invalid SSL certificate.</span>
           </div>
         </div>
       </div>
index b5b5f9ca2e16886cf3114befae8be1060396c963..e3174e230816c1d6061067f85a340ecca3659692 100644 (file)
@@ -155,11 +155,33 @@ export class MultiClusterFormComponent implements OnInit, OnDestroy {
     this.subs.unsubscribe();
   }
 
+  handleError(error: any): void {
+    if (error.error.code === 'connection_refused') {
+      this.connectionVerified = false;
+      this.showCrossOriginError = true;
+      this.connectionMessage = error.error.detail;
+      this.crossOriginCmd = `ceph config set mgr mgr/dashboard/cross_origin_url ${window.location.origin} `;
+    } else {
+      this.connectionVerified = false;
+      this.connectionMessage = error.error.detail;
+    }
+    this.remoteClusterForm.setErrors({ cdSubmitButton: true });
+    this.notificationService.show(
+      NotificationType.error,
+      $localize`Connection to the cluster failed`
+    );
+  }
+
+  handleSuccess(message?: string): void {
+    this.notificationService.show(NotificationType.success, message);
+    this.submitAction.emit();
+    this.activeModal.close();
+  }
+
   onSubmit() {
     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');
@@ -167,121 +189,53 @@ export class MultiClusterFormComponent implements OnInit, OnDestroy {
     const ssl = this.remoteClusterForm.getValue('ssl');
     const ssl_certificate = this.remoteClusterForm.getValue('ssl_cert')?.trim();
 
-    if (this.action === 'edit') {
-      this.subs.add(
-        this.multiClusterService
-          .editCluster(this.cluster.url, clusterAlias, this.cluster.user)
-          .subscribe({
-            error: () => {
-              this.remoteClusterForm.setErrors({ cdSubmitButton: true });
-            },
-            complete: () => {
-              this.notificationService.show(
-                NotificationType.success,
-                $localize`Cluster updated successfully`
-              );
-              this.submitAction.emit();
-              this.activeModal.close();
-            }
-          })
-      );
-    }
+    const commonSubscribtion = {
+      error: (error: any) => this.handleError(error),
+      next: (response: any) => {
+        if (response === true) {
+          this.handleSuccess($localize`Cluster connected successfully`);
+        }
+      }
+    };
 
-    if (this.action === 'reconnect') {
-      this.subs.add(
-        this.multiClusterService
-          .reConnectCluster(updatedUrl, username, password, token, ssl, ssl_certificate)
-          .subscribe({
-            error: () => {
-              this.remoteClusterForm.setErrors({ cdSubmitButton: true });
-            },
-            complete: () => {
-              this.notificationService.show(
-                NotificationType.success,
-                $localize`Cluster reconnected successfully`
-              );
-              this.submitAction.emit();
-              this.activeModal.close();
-            }
-          })
-      );
+    switch (this.action) {
+      case 'edit':
+        this.subs.add(
+          this.multiClusterService
+            .editCluster(this.cluster.url, clusterAlias, this.cluster.user)
+            .subscribe({
+              ...commonSubscribtion,
+              complete: () => this.handleSuccess($localize`Cluster updated successfully`)
+            })
+        );
+        break;
+      case 'reconnect':
+        this.subs.add(
+          this.multiClusterService
+            .reConnectCluster(updatedUrl, username, password, token, ssl, ssl_certificate)
+            .subscribe(commonSubscribtion)
+        );
+        break;
+      case 'connect':
+        this.subs.add(
+          this.multiClusterService
+            .addCluster(
+              updatedUrl,
+              clusterAlias,
+              username,
+              password,
+              token,
+              window.location.origin,
+              clusterFsid,
+              ssl,
+              ssl_certificate
+            )
+            .subscribe(commonSubscribtion)
+        );
+        break;
+      default:
+        break;
     }
-
-    if (this.action === 'connect') {
-      this.subs.add(
-        this.multiClusterService
-          .addCluster(
-            updatedUrl,
-            clusterAlias,
-            username,
-            password,
-            token,
-            window.location.origin,
-            clusterFsid,
-            prometheusApiUrl,
-            ssl,
-            ssl_certificate
-          )
-          .subscribe({
-            error: () => {
-              this.remoteClusterForm.setErrors({ cdSubmitButton: true });
-            },
-            complete: () => {
-              this.notificationService.show(
-                NotificationType.success,
-                $localize`Cluster connected successfully`
-              );
-              this.submitAction.emit();
-              this.activeModal.close();
-            }
-          })
-      );
-    }
-  }
-
-  verifyConnection() {
-    const url = this.remoteClusterForm.getValue('remoteClusterUrl');
-    const username = this.remoteClusterForm.getValue('username');
-    const password = this.remoteClusterForm.getValue('password');
-    const token = this.remoteClusterForm.getValue('apiToken');
-    const ssl = this.remoteClusterForm.getValue('ssl');
-    const ssl_certificate = this.remoteClusterForm.getValue('ssl_cert')?.trim();
-
-    this.subs.add(
-      this.multiClusterService
-        .verifyConnection(url, username, password, token, ssl, ssl_certificate)
-        .subscribe((resp: string) => {
-          switch (resp) {
-            case 'Connection successful':
-              this.connectionVerified = true;
-              this.connectionMessage = 'Connection Verified Successfully';
-              this.notificationService.show(
-                NotificationType.success,
-                $localize`Connection Verified Successfully`
-              );
-              break;
-
-            case 'Connection refused':
-              this.connectionVerified = false;
-              this.showCrossOriginError = true;
-              this.connectionMessage = resp;
-              this.notificationService.show(
-                NotificationType.error,
-                $localize`Connection to the cluster failed`
-              );
-              break;
-
-            default:
-              this.connectionVerified = false;
-              this.connectionMessage = resp;
-              this.notificationService.show(
-                NotificationType.error,
-                $localize`Connection to the cluster failed`
-              );
-              break;
-          }
-        })
-    );
   }
 
   toggleToken() {
index 74cfc78ab8af49dacf25fd473b067efbda9ecb64..70b657b59f643318313b956d39244a7ac8b3771e 100644 (file)
@@ -1,29 +1,56 @@
-<nav ngbNav
-     #nav="ngbNav"
-     class="nav-tabs">
-  <ng-container ngbNavItem>
-    <a ngbNavLink
-       i18n>Clusters List</a>
-    <ng-template ngbNavContent>
-      <cd-table #table
-                [data]="data"
-                [columns]="columns"
-                columnMode="flex"
-                selectionType="single"
-                [maxLimit]="25"
-                (updateSelection)="updateSelection($event)">
-        <div class="table-actions btn-toolbar">
-          <cd-table-actions [permission]="permissions.user"
-                            [selection]="selection"
-                            class="btn-group"
-                            id="cluster-actions"
-                            [tableActions]="tableActions">
-          </cd-table-actions>
-        </div>
-      </cd-table>
-    </ng-template>
+<ng-template #emptyCluster>
+  <ng-container class="container h-75"
+                *ngIf="managedByConfig$ | async as managedByConfig">
+    <div class="row h-100 justify-content-center align-items-center">
+      <div class="blank-page">
+        <i class="mx-auto d-block"
+           [ngClass]="[icons.large, icons.wrench]">
+        </i>
+      <div class="mt-4 text-center">
+        <h4 class="mt-3">This cluster is already managed by cluster -
+          <a target="_blank"
+             [href]="managedByConfig['MANAGED_BY_CLUSTERS'][0]['url']">
+            {{ managedByConfig['MANAGED_BY_CLUSTERS'][0]['fsid'] }}
+            <i class="fa fa-external-link"></i>
+          </a>
+        </h4>
+      </div>
+      </div>
+    </div>
   </ng-container>
-</nav>
+</ng-template>
+
+<ng-container *ngIf="managedByConfig$ | async as managedByConfig">
+  <div *ngIf="managedByConfig['MANAGED_BY_CLUSTERS'].length === 0; else emptyCluster">
+    <nav ngbNav
+         #nav="ngbNav"
+         class="nav-tabs">
+      <ng-container ngbNavItem>
+        <a ngbNavLink
+           i18n>Clusters List</a>
+        <ng-template ngbNavContent>
+          <cd-table #table
+                    [data]="data"
+                    [columns]="columns"
+                    columnMode="flex"
+                    selectionType="single"
+                    [maxLimit]="25"
+                    (updateSelection)="updateSelection($event)">
+            <div class="table-actions btn-toolbar">
+              <cd-table-actions [permission]="permissions.user"
+                                [selection]="selection"
+                                class="btn-group"
+                                id="cluster-actions"
+                                [tableActions]="tableActions">
+              </cd-table-actions>
+            </div>
+          </cd-table>
+        </ng-template>
+      </ng-container>
+    </nav>
+    <div [ngbNavOutlet]="nav"></div>
+  </div>
+</ng-container>
 
 <ng-template #urlTpl
              let-row="row">
@@ -34,4 +61,3 @@
   </a>
 </ng-template>
 
-<div [ngbNavOutlet]="nav"></div>
index da5e08dfebea4fc8af3a5c2cc862b8bc3ce38bbf..8b3a0f712e6354208e862c31c922a3878948ab6e 100644 (file)
@@ -1,4 +1,4 @@
-import { Component, TemplateRef, ViewChild } from '@angular/core';
+import { Component, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
 import { MultiClusterService } from '~/app/shared/api/multi-cluster.service';
 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
@@ -18,18 +18,21 @@ import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
 import { MultiCluster } from '~/app/shared/models/multi-cluster';
 import { Router } from '@angular/router';
 import { CookiesService } from '~/app/shared/services/cookie.service';
+import { Observable, Subscription } from 'rxjs';
+import { SettingsService } from '~/app/shared/api/settings.service';
 
 @Component({
   selector: 'cd-multi-cluster-list',
   templateUrl: './multi-cluster-list.component.html',
   styleUrls: ['./multi-cluster-list.component.scss']
 })
-export class MultiClusterListComponent {
+export class MultiClusterListComponent implements OnInit, OnDestroy {
   @ViewChild(TableComponent)
   table: TableComponent;
   @ViewChild('urlTpl', { static: true })
   public urlTpl: TemplateRef<any>;
 
+  private subs = new Subscription();
   permissions: Permissions;
   tableActions: CdTableAction[];
   clusterTokenStatus: object = {};
@@ -42,9 +45,12 @@ export class MultiClusterListComponent {
   modalRef: NgbModalRef;
   hubUrl: string;
   currentUrl: string;
+  icons = Icons;
+  managedByConfig$: Observable<any>;
 
   constructor(
     private multiClusterService: MultiClusterService,
+    private settingsService: SettingsService,
     private router: Router,
     public actionLabels: ActionLabelsI18n,
     private notificationService: NotificationService,
@@ -86,15 +92,17 @@ 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();
-      }
-    });
+    this.subs.add(
+      this.multiClusterService.subscribe((resp: object) => {
+        if (resp && resp['config']) {
+          const clusterDetailsArray = Object.values(resp['config']).flat();
+          this.data = clusterDetailsArray;
+          this.checkClusterConnectionStatus();
+        }
+      })
+    );
+
+    this.managedByConfig$ = this.settingsService.getValues('MANAGED_BY_CLUSTERS');
 
     this.columns = [
       {
@@ -133,10 +141,16 @@ export class MultiClusterListComponent {
       }
     ];
 
-    this.multiClusterService.subscribeClusterTokenStatus((resp: object) => {
-      this.clusterTokenStatus = resp;
-      this.checkClusterConnectionStatus();
-    });
+    this.subs.add(
+      this.multiClusterService.subscribeClusterTokenStatus((resp: object) => {
+        this.clusterTokenStatus = resp;
+        this.checkClusterConnectionStatus();
+      })
+    );
+  }
+
+  ngOnDestroy() {
+    this.subs.unsubscribe();
   }
 
   checkClusterConnectionStatus() {
index 46d6bab35a9ccf1ab0a91110edeb8eb598cf896c..3e9e27c33bacd24df1af84eb9494ae71286d075d 100644 (file)
@@ -1,16 +1,29 @@
 <ng-template #emptyCluster>
-  <div class="container h-75">
+  <ng-container class="container h-75"
+                *ngIf="managedByConfig$ | async as managedByConfig">
     <div class="row h-100 justify-content-center align-items-center">
       <div class="blank-page">
         <i class="mx-auto d-block"
-           [ngClass]="icons.wrench">
+           [ngClass]="[icons.large, icons.wrench]">
         </i>
-      <div class="mt-4 text-center">
+      <div class="mt-4 text-center"
+           *ngIf="managedByConfig['MANAGED_BY_CLUSTERS'].length === 0">
         <h3 class="fw-bold">Connect Cluster</h3>
         <h4 class="mt-3">Upgrade your current cluster to a multi-cluster setup effortlessly.
             Click on the "Connect Cluster" button to begin the process.</h4>
       </div>
-      <div class="mt-4">
+      <div class="mt-4 text-center"
+           *ngIf="managedByConfig['MANAGED_BY_CLUSTERS'].length > 0">
+        <h4 class="mt-3">This cluster is already managed by cluster -
+          <a target="_blank"
+             [href]="managedByConfig['MANAGED_BY_CLUSTERS'][0]['url']">
+             {{ managedByConfig['MANAGED_BY_CLUSTERS'][0]['fsid'] }}
+            <i class="fa fa-external-link"></i>
+          </a>
+        </h4>
+      </div>
+      <div class="mt-4"
+           *ngIf="managedByConfig['MANAGED_BY_CLUSTERS'].length === 0">
         <div class="text-center">
           <button class="btn btn-primary"
                   (click)="openRemoteClusterInfoModal()">
@@ -22,7 +35,7 @@
       </div>
       </div>
     </div>
-  </div>
+  </ng-container>
 </ng-template>
 
 <ng-template #nametpl>
   </div>
 </ng-template>
 
-<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">
-          <cd-card cardTitle="Clusters"
-                   i18n-title
-                   class="col-sm-6 m-0 p-0 ps-4 pe-2"
-                   aria-label="Clusters"
-                   [fullHeight]="true"
-                   *ngIf="queriesResults.CLUSTER_COUNT && queriesResults.CLUSTER_COUNT[0]">
-            <span class="text-center">
-              <h3 *ngIf="queriesResults['HEALTH_ERROR_COUNT'][0][1] === '0' && queriesResults['HEALTH_WARNING_COUNT'][0][1] === '0'">{{ queriesResults.CLUSTER_COUNT[0][1] }}</h3>
-              <h3 class="text-danger"
-                  *ngIf="queriesResults.HEALTH_ERROR_COUNT[0][1] !== '0'">
-                <i [ngClass]="icons.danger"></i>
-                {{ queriesResults.HEALTH_ERROR_COUNT[0][1] }}
-              </h3>
-              <h3 class="text-warning"
-                  *ngIf="queriesResults.HEALTH_WARNING_COUNT[0][1] !== '0'">
-                <i [ngClass]="icons.warning"></i>
-                  {{ queriesResults.HEALTH_WARNING_COUNT[0][1] }}
-              </h3>
-            </span>
-          </cd-card>
-          <cd-card cardTitle="Alerts"
-                   i18n-title
-                   class="col-sm-6 m-0 p-0 ps-2 pe-2"
-                   aria-label="Alerts"
-                   *ngIf="queriesResults['ALERTS_COUNT'] && queriesResults['ALERTS_COUNT'][0]">
-            <span class="text-center">
-              <h3 *ngIf="queriesResults['CRITICAL_ALERTS_COUNT'][0][1] === '0' && queriesResults['WARNING_ALERTS_COUNT'][0][1] === '0'">
-                  {{ queriesResults['ALERTS_COUNT'][0][1] }}
-              </h3>
-              <h3 class="text-danger"
-                  *ngIf="queriesResults['CRITICAL_ALERTS_COUNT'][0][1] !== '0'">
-                <i [ngClass]="icons.danger"></i>
-                {{ queriesResults['CRITICAL_ALERTS_COUNT'][0][1] }}
-              </h3>
-              <h3 class="text-warning"
-                  *ngIf="queriesResults['WARNING_ALERTS_COUNT'][0][1] !== '0'">
-                <i [ngClass]="icons.warning"></i>
-                  {{ queriesResults['WARNING_ALERTS_COUNT'][0][1] }}
-              </h3>
-            </span>
-          </cd-card>
-        </div>
-        <div class="row pt-3">
-          <cd-card cardTitle="Connection Errors"
-                   i18n-title
-                   class="col-sm-6 m-0 p-0 ps-4 pe-2"
-                   aria-label="Connection Errors">
-            <span class="text-center">
-              <h3 [ngClass]="{'text-danger': connectionErrorsCount > 0}">
-                <i [ngClass]="icons.danger"
-                   *ngIf="connectionErrorsCount > 0"></i>
-                {{ connectionErrorsCount }}
-              </h3>
-            </span>
-          </cd-card>
-          <cd-card cardTitle="Hosts"
-                   i18n-title
-                   class="col-sm-6 m-0 p-0 ps-2 pe-2"
-                   aria-label="Total number of hosts"
-                   *ngIf="queriesResults['TOTAL_HOSTS'] && queriesResults['TOTAL_HOSTS'][0]">
-            <span class="text-center">
-              <h3>{{ queriesResults['TOTAL_HOSTS'][0][1] }}</h3>
-            </span>
-          </cd-card>
-        </div>
-
-        <div class="row pt-3">
-          <cd-card cardTitle="Capacity"
-                   i18n-title
-                   class="col-sm-12 m-0 p-0 ps-4 pe-2"
-                   aria-label="Capacity card"
-                   *ngIf="queriesResults['TOTAL_CLUSTERS_CAPACITY'] && queriesResults['TOTAL_CLUSTERS_CAPACITY'][0] && queriesResults['TOTAL_USED_CAPACITY'] && queriesResults['TOTAL_USED_CAPACITY'][0]">
-            <ng-container class="ms-4 me-4">
-              <cd-dashboard-pie [data]="{max: queriesResults['TOTAL_CLUSTERS_CAPACITY'][0][1], current: queriesResults['TOTAL_USED_CAPACITY'][0][1]}"
-                                lowThreshold=".95"
-                                highThreshold=".99">
-              </cd-dashboard-pie>
-            </ng-container>
-          </cd-card>
-        </div>
-      </div>
-
-      <div class="col-sm-8 ps-2">
-        <cd-card [cardTitle]="'Top ' + COUNT_OF_UTILIZATION_CHARTS + ' Cluster Utilization'"
-                 i18n-title
-                 [fullHeight]="true"
-                 aria-label="Cluster Utilization card"
-                 *ngIf="clusters">
-          <div class="ms-4 me-4 mt-0">
-            <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"
-                                      *ngIf="capacityLabels && capacityValues">
-            </cd-dashboard-area-chart>
-            <cd-dashboard-area-chart chartTitle="IOPS"
-                                     [labelsArray]="iopsLabels"
-                                     dataUnits=""
-                                     decimals="0"
-                                     isMultiCluster="true"
-                                     [dataArray]="iopsValues"
-                                     [truncateLabel]="true"
-                                     *ngIf="iopsLabels && iopsValues">
-            </cd-dashboard-area-chart>
-            <cd-dashboard-area-chart chartTitle="Throughput"
-                                     [labelsArray]="throughputLabels"
-                                     dataUnits="B/s"
-                                     decimals="2"
-                                     isMultiCluster="true"
-                                     [dataArray]="throughputValues"
-                                     [truncateLabel]="true"
-                                     *ngIf="throughputLabels && throughputLabels">
-            </cd-dashboard-area-chart>
+<ng-container *ngIf="managedByConfig$ | async as managedByConfig">
+  <div class="container-fluid h-100 p-4"
+       *ngIf="isMultiCluster && managedByConfig['MANAGED_BY_CLUSTERS'].length === 0; 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">
+            <cd-card cardTitle="Clusters"
+                     i18n-title
+                     class="col-sm-6 m-0 p-0 ps-4 pe-2"
+                     aria-label="Clusters"
+                     [fullHeight]="true"
+                     *ngIf="queriesResults.CLUSTER_COUNT && queriesResults.CLUSTER_COUNT[0]">
+              <span class="text-center">
+                <h3 *ngIf="queriesResults['HEALTH_ERROR_COUNT'][0][1] === '0' && queriesResults['HEALTH_WARNING_COUNT'][0][1] === '0'">{{ queriesResults.CLUSTER_COUNT[0][1] }}</h3>
+                <h3 class="text-danger"
+                    *ngIf="queriesResults.HEALTH_ERROR_COUNT[0][1] !== '0'">
+                  <i [ngClass]="icons.danger"></i>
+                  {{ queriesResults.HEALTH_ERROR_COUNT[0][1] }}
+                </h3>
+                <h3 class="text-warning"
+                    *ngIf="queriesResults.HEALTH_WARNING_COUNT[0][1] !== '0'">
+                  <i [ngClass]="icons.warning"></i>
+                    {{ queriesResults.HEALTH_WARNING_COUNT[0][1] }}
+                </h3>
+              </span>
+            </cd-card>
+            <cd-card cardTitle="Alerts"
+                     i18n-title
+                     class="col-sm-6 m-0 p-0 ps-2 pe-2"
+                     aria-label="Alerts"
+                     *ngIf="queriesResults['ALERTS_COUNT'] && queriesResults['ALERTS_COUNT'][0]">
+              <span class="text-center">
+                <h3 *ngIf="queriesResults['CRITICAL_ALERTS_COUNT'][0][1] === '0' && queriesResults['WARNING_ALERTS_COUNT'][0][1] === '0'">
+                    {{ queriesResults['ALERTS_COUNT'][0][1] }}
+                </h3>
+                <h3 class="text-danger"
+                    *ngIf="queriesResults['CRITICAL_ALERTS_COUNT'][0][1] !== '0'">
+                  <i [ngClass]="icons.danger"></i>
+                  {{ queriesResults['CRITICAL_ALERTS_COUNT'][0][1] }}
+                </h3>
+                <h3 class="text-warning"
+                    *ngIf="queriesResults['WARNING_ALERTS_COUNT'][0][1] !== '0'">
+                  <i [ngClass]="icons.warning"></i>
+                    {{ queriesResults['WARNING_ALERTS_COUNT'][0][1] }}
+                </h3>
+              </span>
+            </cd-card>
+          </div>
+          <div class="row pt-3">
+            <cd-card cardTitle="Connection Errors"
+                     i18n-title
+                     class="col-sm-6 m-0 p-0 ps-4 pe-2"
+                     aria-label="Connection Errors">
+              <span class="text-center">
+                <h3 [ngClass]="{'text-danger': connectionErrorsCount > 0}">
+                  <i [ngClass]="icons.danger"
+                     *ngIf="connectionErrorsCount > 0"></i>
+                  {{ connectionErrorsCount }}
+                </h3>
+              </span>
+            </cd-card>
+            <cd-card cardTitle="Hosts"
+                     i18n-title
+                     class="col-sm-6 m-0 p-0 ps-2 pe-2"
+                     aria-label="Total number of hosts"
+                     *ngIf="queriesResults['TOTAL_HOSTS'] && queriesResults['TOTAL_HOSTS'][0]">
+              <span class="text-center">
+                <h3>{{ queriesResults['TOTAL_HOSTS'][0][1] }}</h3>
+              </span>
+            </cd-card>
           </div>
-        </cd-card>
-      </div>
-    </cd-card-group>
 
-    <cd-card-group>
-      <div class="col-lg-12 mt-3 m-0 p-0 ps-4 pe-4">
-        <cd-table [data]="clusters"
-                  [columns]="columns"
-                  [limit]="5"
-                  *ngIf="clusters">
-        </cd-table>
-      </div>
-    </cd-card-group>
+          <div class="row pt-3">
+            <cd-card cardTitle="Capacity"
+                     i18n-title
+                     class="col-sm-12 m-0 p-0 ps-4 pe-2"
+                     aria-label="Capacity card"
+                     *ngIf="queriesResults['TOTAL_CLUSTERS_CAPACITY'] && queriesResults['TOTAL_CLUSTERS_CAPACITY'][0] && queriesResults['TOTAL_USED_CAPACITY'] && queriesResults['TOTAL_USED_CAPACITY'][0]">
+              <ng-container class="ms-4 me-4">
+                <cd-dashboard-pie [data]="{max: queriesResults['TOTAL_CLUSTERS_CAPACITY'][0][1], current: queriesResults['TOTAL_USED_CAPACITY'][0][1]}"
+                                  lowThreshold=".95"
+                                  highThreshold=".99">
+                </cd-dashboard-pie>
+              </ng-container>
+            </cd-card>
+          </div>
+        </div>
 
-    <cd-card-group>
-      <div class="col-lg-12 mb-4 m-0 p-0 ps-4 pe-4">
-        <div class="row">
-          <cd-card [cardTitle]="'Top ' + COUNT_OF_UTILIZATION_CHARTS + ' Pools Utilization'"
+        <div class="col-sm-8 ps-2">
+          <cd-card [cardTitle]="'Top ' + COUNT_OF_UTILIZATION_CHARTS + ' Cluster Utilization'"
                    i18n-title
-                   aria-label="Pools Utilization card"
+                   [fullHeight]="true"
+                   aria-label="Cluster Utilization card"
                    *ngIf="clusters">
             <div class="ms-4 me-4 mt-0">
-              <cd-dashboard-time-selector (selectedTime)="getPrometheusData($event, 'poolUtilization')">
+              <cd-dashboard-time-selector (selectedTime)="getPrometheusData($event, 'clusterUtilization')">
               </cd-dashboard-time-selector>
               <cd-dashboard-area-chart  chartTitle="Capacity"
-                                        [labelsArray]="poolCapacityLabels"
-                                        dataUnits="B"
+                                        [labelsArray]="capacityLabels"
                                         isMultiCluster="true"
-                                        [dataArray]="poolCapacityValues"
-                                        *ngIf="poolCapacityLabels && poolCapacityValues"
-                                        [truncateLabel]="true">
+                                        dataUnits="B"
+                                        [dataArray]="capacityValues"
+                                        [truncateLabel]="true"
+                                        *ngIf="capacityLabels && capacityValues">
               </cd-dashboard-area-chart>
               <cd-dashboard-area-chart chartTitle="IOPS"
-                                       [labelsArray]="poolIOPSLabels"
+                                       [labelsArray]="iopsLabels"
+                                       isMultiCluster="true"
                                        dataUnits=""
                                        decimals="0"
-                                       isMultiCluster="true"
-                                       [dataArray]="poolIOPSValues"
-                                       *ngIf="poolIOPSLabels && poolIOPSValues"
-                                       [truncateLabel]="true">
+                                       [dataArray]="iopsValues"
+                                       [truncateLabel]="true"
+                                       *ngIf="iopsLabels && iopsValues">
               </cd-dashboard-area-chart>
-              <cd-dashboard-area-chart chartTitle="Client Throughput"
-                                       [labelsArray]="poolThroughputLabels"
+              <cd-dashboard-area-chart chartTitle="Throughput"
+                                       [labelsArray]="throughputLabels"
+                                       isMultiCluster="true"
                                        dataUnits="B/s"
                                        decimals="2"
-                                       isMultiCluster="true"
-                                       [dataArray]="poolThroughputValues"
-                                       *ngIf="poolThroughputLabels && poolThroughputValues"
-                                       [truncateLabel]="true">
+                                       [dataArray]="throughputValues"
+                                       [truncateLabel]="true"
+                                       *ngIf="throughputLabels && throughputLabels">
               </cd-dashboard-area-chart>
             </div>
           </cd-card>
         </div>
-      </div>
-    </cd-card-group>
-  </ng-container>
-</div>
+      </cd-card-group>
+
+      <cd-card-group>
+        <div class="col-lg-12 mt-3 m-0 p-0 ps-4 pe-4">
+          <cd-table [data]="clusters"
+                    [columns]="columns"
+                    [limit]="5"
+                    *ngIf="clusters">
+          </cd-table>
+        </div>
+      </cd-card-group>
+
+      <cd-card-group>
+        <div class="col-lg-12 mb-4 m-0 p-0 ps-4 pe-4">
+          <div class="row">
+            <cd-card [cardTitle]="'Top ' + COUNT_OF_UTILIZATION_CHARTS + ' Pools Utilization'"
+                     i18n-title
+                     aria-label="Pools Utilization card"
+                     *ngIf="clusters">
+              <div class="ms-4 me-4 mt-0">
+                <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">
+                </cd-dashboard-area-chart>
+                <cd-dashboard-area-chart chartTitle="IOPS"
+                                         [labelsArray]="poolIOPSLabels"
+                                         dataUnits=""
+                                         decimals="0"
+                                         isMultiCluster="true"
+                                         [dataArray]="poolIOPSValues"
+                                         *ngIf="poolIOPSLabels && poolIOPSValues"
+                                         [truncateLabel]="true">
+                </cd-dashboard-area-chart>
+                <cd-dashboard-area-chart chartTitle="Client Throughput"
+                                         [labelsArray]="poolThroughputLabels"
+                                         dataUnits="B/s"
+                                         decimals="2"
+                                         isMultiCluster="true"
+                                         [dataArray]="poolThroughputValues"
+                                         *ngIf="poolThroughputLabels && poolThroughputValues"
+                                         [truncateLabel]="true">
+                </cd-dashboard-area-chart>
+              </div>
+            </cd-card>
+          </div>
+        </div>
+      </cd-card-group>
+    </ng-container>
+  </div>
+</ng-container>
index 18dc80406169e3b3cb5cefd619e795d5a07be103..7f9db85517a29e1a13fd7fa0289b9ab70c893f12 100644 (file)
@@ -1,6 +1,6 @@
 import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
-import { Subscription } from 'rxjs';
+import { Observable, Subscription } from 'rxjs';
 import { MultiClusterService } from '~/app/shared/api/multi-cluster.service';
 import { Icons } from '~/app/shared/enum/icons.enum';
 import { ModalService } from '~/app/shared/services/modal.service';
@@ -16,6 +16,7 @@ import {
   MultiClusterPromqlsForClusterUtilization as ClusterUltilizationQueries,
   MultiClusterPromqlsForPoolUtilization as PoolUltilizationQueries
 } from '~/app/shared/enum/dashboard-promqls.enum';
+import { SettingsService } from '~/app/shared/api/settings.service';
 
 @Component({
   selector: 'cd-multi-cluster',
@@ -87,9 +88,11 @@ export class MultiClusterComponent implements OnInit, OnDestroy {
   interval: NodeJS.Timer;
   selectedTime: any;
   multiClusterQueries: any = {};
+  managedByConfig$: Observable<any>;
 
   constructor(
     private multiClusterService: MultiClusterService,
+    private settingsService: SettingsService,
     private modalService: ModalService,
     private router: Router,
     private prometheusService: PrometheusService,
@@ -185,7 +188,7 @@ export class MultiClusterComponent implements OnInit, OnDestroy {
         }
       })
     );
-
+    this.managedByConfig$ = this.settingsService.getValues('MANAGED_BY_CLUSTERS');
     this.subs.add(
       this.multiClusterService.subscribeClusterTokenStatus((resp: object) => {
         this.clusterTokenStatus = resp;
@@ -229,14 +232,14 @@ export class MultiClusterComponent implements OnInit, OnDestroy {
   }
 
   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 validRangeQueries = Object.keys(ClusterUltilizationQueries).concat(
+      Object.keys(PoolUltilizationQueries)
+    );
+
+    const allMultiClusterQueries = Object.keys(allQueries).concat(
+      Object.keys(ClusterUltilizationQueries).concat(Object.keys(PoolUltilizationQueries))
+    );
+
     const validQueries = [
       'ALERTS',
       'MGR_METADATA',
@@ -255,13 +258,17 @@ export class MultiClusterComponent implements OnInit, OnDestroy {
       'CLUSTER_ALERTS'
     ];
 
+    let validSelectedQueries = allMultiClusterQueries;
+
     if (selectedQueries) {
       if (selectedQueries === 'poolUtilization') {
         this.multiClusterQueries.pool['selectedTime'] = selectedTime;
+        validSelectedQueries = Object.keys(PoolUltilizationQueries);
       }
 
       if (selectedQueries === 'clusterUtilization') {
         this.multiClusterQueries.cluster.selectedTime = selectedTime;
+        validSelectedQueries = Object.keys(ClusterUltilizationQueries);
       }
     }
 
@@ -270,7 +277,9 @@ export class MultiClusterComponent implements OnInit, OnDestroy {
         this.queriesResults,
         validQueries,
         validRangeQueries,
-        this.multiClusterQueries
+        this.multiClusterQueries,
+        validSelectedQueries,
+        allMultiClusterQueries
       )
       .subscribe((data: any) => {
         this.queriesResults = data;
@@ -434,6 +443,7 @@ export class MultiClusterComponent implements OnInit, OnDestroy {
   }
 
   ngOnDestroy(): void {
+    this.subs.unsubscribe();
     clearInterval(this.interval);
   }
 }
index bad69c50122b699aef812e150379c02dabd987d3..17df92f3f248cc4a3ab39d58705a4d5be4b0e6c3 100644 (file)
               </a>
             </dd>
           </ng-container>
+          <ng-container *ngIf="managedByConfig$ | async as managedByConfig">
+            <span *ngIf="managedByConfig['MANAGED_BY_CLUSTERS'].length > 0">
+              <dt>Managed By</dt>
+              <dd>
+                <a target="_blank"
+                   [href]="managedByConfig['MANAGED_BY_CLUSTERS'][0]['url']">
+                  {{ managedByConfig['MANAGED_BY_CLUSTERS'][0]['fsid'] }}
+                  <i class="fa fa-external-link"></i>
+                </a>
+              </dd>
+            </span>
+          </ng-container>
         </dl>
       </cd-card>
 
index 853eed2d695f2c3a68e270650f73c7493448a529..d8528f6cff851f13e178c720d8cfaf9d3a97614b 100644 (file)
@@ -25,6 +25,7 @@ import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
 import { MgrModuleService } from '~/app/shared/api/mgr-module.service';
 import { AlertClass } from '~/app/shared/enum/health-icon.enum';
 import { HardwareService } from '~/app/shared/api/hardware.service';
+import { SettingsService } from '~/app/shared/api/settings.service';
 
 @Component({
   selector: 'cd-dashboard-v3',
@@ -76,6 +77,7 @@ export class DashboardV3Component extends PrometheusListHelper implements OnInit
   isHardwareEnabled$: Observable<boolean>;
   hardwareSummary$: Observable<any>;
   hardwareSubject = new BehaviorSubject<any>([]);
+  managedByConfig$: Observable<any>;
 
   constructor(
     private summaryService: SummaryService,
@@ -84,6 +86,7 @@ export class DashboardV3Component extends PrometheusListHelper implements OnInit
     private authStorageService: AuthStorageService,
     private featureToggles: FeatureTogglesService,
     private healthService: HealthService,
+    private settingsService: SettingsService,
     public prometheusService: PrometheusService,
     private mgrModuleService: MgrModuleService,
     private refreshIntervalService: RefreshIntervalService,
@@ -116,6 +119,7 @@ export class DashboardV3Component extends PrometheusListHelper implements OnInit
     this.getPrometheusData(this.prometheusService.lastHourDateObject);
     this.getDetailsCardData();
     this.getTelemetryReport();
+    this.managedByConfig$ = this.settingsService.getValues('MANAGED_BY_CLUSTERS');
   }
 
   getTelemetryText(): string {
index 5e6ab6e3606f90d2513fae43cba2fb6b5bf8a138..a5b9a0f89f58c0de3bcc502764c99957c4b1b0b4 100644 (file)
@@ -142,24 +142,6 @@ export class MultiClusterService {
     });
   }
 
-  verifyConnection(
-    url: string,
-    username: string,
-    password: string,
-    token = '',
-    ssl = false,
-    cert = ''
-  ): Observable<any> {
-    return this.http.post('api/multi-cluster/verify_connection', {
-      url: url,
-      username: username,
-      password: password,
-      token: token,
-      ssl_verify: ssl,
-      ssl_certificate: cert
-    });
-  }
-
   private getClusterObserver() {
     return (data: any) => {
       this.msSource.next(data);
index eaa1696dc873dfd761e2745465b15962e9d16909..2b469e837d22ae39e45a3330c193effe2bc7a60b 100644 (file)
@@ -204,7 +204,9 @@ export class PrometheusService {
     queriesResults: any,
     validQueries: string[],
     validRangeQueries: string[],
-    multiClusterQueries: any
+    multiClusterQueries: any,
+    validSelectedQueries: string[],
+    allMultiClusterQueries: string[]
   ) {
     return new Observable((observer) => {
       this.ifPrometheusConfigured(() => {
@@ -218,7 +220,10 @@ export class PrometheusService {
 
           Object.entries(multiClusterQueries).forEach(([key, _value]) => {
             for (const queryName in multiClusterQueries[key].queries) {
-              if (multiClusterQueries[key].queries.hasOwnProperty(queryName)) {
+              if (
+                multiClusterQueries[key].queries.hasOwnProperty(queryName) &&
+                validSelectedQueries.includes(queryName)
+              ) {
                 const query = multiClusterQueries[key].queries[queryName];
                 const start = this.updateTimeStamp(multiClusterQueries[key].selectedTime)['start'];
                 const end = this.updateTimeStamp(multiClusterQueries[key].selectedTime)['end'];
@@ -247,6 +252,8 @@ export class PrometheusService {
             }
           });
 
+          validSelectedQueries = allMultiClusterQueries;
+
           forkJoin(requests).subscribe(
             (responses: any[]) => {
               for (let i = 0; i < responses.length; i++) {
index dbf6a3e299e54d742eed15e2e35e41f39c445b31..fcab8fdafdaef48db2798990cb4f6cb2ca72fcf6 100644 (file)
@@ -7325,52 +7325,6 @@ paths:
       - jwt: []
       tags:
       - Multi-cluster
-  /api/multi-cluster/verify_connection:
-    post:
-      parameters: []
-      requestBody:
-        content:
-          application/json:
-            schema:
-              properties:
-                password:
-                  type: string
-                ssl_certificate:
-                  type: string
-                ssl_verify:
-                  default: false
-                  type: boolean
-                token:
-                  type: string
-                url:
-                  type: string
-                username:
-                  type: string
-              type: object
-      responses:
-        '201':
-          content:
-            application/vnd.ceph.api.v1.0+json:
-              type: object
-          description: Resource created.
-        '202':
-          content:
-            application/vnd.ceph.api.v1.0+json:
-              type: object
-          description: Operation is still executing. Please check the task queue.
-        '400':
-          description: Operation exception. Please check the response body for details.
-        '401':
-          description: Unauthenticated access. Please login first.
-        '403':
-          description: Unauthorized access. Please check your permissions.
-        '500':
-          description: Unexpected error. Please check the response body for the stack
-            trace.
-      security:
-      - jwt: []
-      tags:
-      - Multi-cluster
   /api/nfs-ganesha/cluster:
     get:
       parameters: []
index acff17e94e5f542ddce512dcc8fce7c7cedc0a83..e98383070e29c4878ea5f29d49188ad5a214f348 100644 (file)
@@ -120,7 +120,7 @@ class Options(object):
                                         [str])
 
     MULTICLUSTER_CONFIG = Setting({}, [dict, str])
-
+    MANAGED_BY_CLUSTERS = Setting([], [dict, list])
     UNSAFE_TLS_v1_2 = Setting(False, [bool])
 
     @staticmethod