From bd2118219c8dc84018a99132b1ae891acedb229b Mon Sep 17 00:00:00 2001 From: Aashish Sharma Date: Thu, 14 Mar 2024 17:27:29 +0530 Subject: [PATCH] mgr/dashboard: disable multi-cluster feature for non-hub clusters Fixes: https://tracker.ceph.com/issues/65056 Signed-off-by: Aashish Sharma --- .../dashboard/controllers/multi_cluster.py | 154 ++++---- .../multi-cluster-form.component.html | 55 +-- .../multi-cluster-form.component.ts | 182 ++++----- .../multi-cluster-list.component.html | 78 ++-- .../multi-cluster-list.component.scss | 6 + .../multi-cluster-list.component.ts | 44 ++- .../multi-cluster.component.html | 367 +++++++++--------- .../multi-cluster.component.scss | 1 - .../multi-cluster/multi-cluster.component.ts | 32 +- .../dashboard/dashboard-v3.component.html | 12 + .../dashboard/dashboard-v3.component.ts | 4 + .../app/shared/api/multi-cluster.service.ts | 18 - .../src/app/shared/api/prometheus.service.ts | 11 +- src/pybind/mgr/dashboard/openapi.yaml | 46 --- src/pybind/mgr/dashboard/settings.py | 2 +- 15 files changed, 503 insertions(+), 509 deletions(-) diff --git a/src/pybind/mgr/dashboard/controllers/multi_cluster.py b/src/pybind/mgr/dashboard/controllers/multi_cluster.py index d69a7da2609..b1aebddb6f6 100644 --- a/src/pybind/mgr/dashboard/controllers/multi_cluster.py +++ b/src/pybind/mgr/dashboard/controllers/multi_cluster.py @@ -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): diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-form/multi-cluster-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-form/multi-cluster-form.component.html index 68af8c1672d..7f92a26dece 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-form/multi-cluster-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-form/multi-cluster-form.component.html @@ -220,40 +220,27 @@ -
- -
- - - This field is required. - Invalid SSL certificate. -
-
-
-
-
- -
+
+ +
+ + + This field is required. + Invalid SSL certificate.
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-form/multi-cluster-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-form/multi-cluster-form.component.ts index b5b5f9ca2e1..e3174e23081 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-form/multi-cluster-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-form/multi-cluster-form.component.ts @@ -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() { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-list/multi-cluster-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-list/multi-cluster-list.component.html index 74cfc78ab8a..70b657b59f6 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-list/multi-cluster-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-list/multi-cluster-list.component.html @@ -1,29 +1,56 @@ - + + + +
+ +
+
+
@@ -34,4 +61,3 @@ -
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-list/multi-cluster-list.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-list/multi-cluster-list.component.scss index e69de29bb2d..ac230d921c3 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-list/multi-cluster-list.component.scss +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-list/multi-cluster-list.component.scss @@ -0,0 +1,6 @@ +@use '../../../../../styles/vendor/variables' as vv; + +.fa-wrench { + color: vv.$info; + font-size: 6em; +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-list/multi-cluster-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-list/multi-cluster-list.component.ts index da5e08dfebe..8b3a0f712e6 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-list/multi-cluster-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster-list/multi-cluster-list.component.ts @@ -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; + 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; 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() { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster.component.html index 46d6bab35a9..3e9e27c33ba 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster.component.html @@ -1,16 +1,29 @@ -
+
+ [ngClass]="[icons.large, icons.wrench]"> -
+

Connect Cluster

Upgrade your current cluster to a multi-cluster setup effortlessly. Click on the "Connect Cluster" button to begin the process.

-
+
+

This cluster is already managed by cluster - + + {{ managedByConfig['MANAGED_BY_CLUSTERS'][0]['fsid'] }} + + +

+
+
-
+ @@ -49,194 +62,196 @@
-
- - -

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.

-
- -
-
- - -

{{ queriesResults.CLUSTER_COUNT[0][1] }}

-

- - {{ queriesResults.HEALTH_ERROR_COUNT[0][1] }} -

-

- - {{ queriesResults.HEALTH_WARNING_COUNT[0][1] }} -

-
-
- - -

- {{ queriesResults['ALERTS_COUNT'][0][1] }} -

-

- - {{ queriesResults['CRITICAL_ALERTS_COUNT'][0][1] }} -

-

- - {{ queriesResults['WARNING_ALERTS_COUNT'][0][1] }} -

-
-
-
-
- - -

- - {{ connectionErrorsCount }} -

-
-
- - -

{{ queriesResults['TOTAL_HOSTS'][0][1] }}

-
-
-
- -
- - - - - - -
-
- -
- -
- - - - - - - - + +
+ + +

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.

+
+ +
+
+ + +

{{ queriesResults.CLUSTER_COUNT[0][1] }}

+

+ + {{ queriesResults.HEALTH_ERROR_COUNT[0][1] }} +

+

+ + {{ queriesResults.HEALTH_WARNING_COUNT[0][1] }} +

+
+
+ + +

+ {{ queriesResults['ALERTS_COUNT'][0][1] }} +

+

+ + {{ queriesResults['CRITICAL_ALERTS_COUNT'][0][1] }} +

+

+ + {{ queriesResults['WARNING_ALERTS_COUNT'][0][1] }} +

+
+
+
+
+ + +

+ + {{ connectionErrorsCount }} +

+
+
+ + +

{{ queriesResults['TOTAL_HOSTS'][0][1] }}

+
+
- -
-
- -
- - -
-
+
+ + + + + + +
+
- -
-
- +
- + + dataUnits="B" + [dataArray]="capacityValues" + [truncateLabel]="true" + *ngIf="capacityLabels && capacityValues"> + [dataArray]="iopsValues" + [truncateLabel]="true" + *ngIf="iopsLabels && iopsValues"> - + [dataArray]="throughputValues" + [truncateLabel]="true" + *ngIf="throughputLabels && throughputLabels">
-
-
-
-
+ + + +
+ + +
+
+ + +
+
+ +
+ + + + + + + + +
+
+
+
+
+ +
+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster.component.scss index 2931ef94fba..b7cf93dcdb7 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster.component.scss +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster.component.scss @@ -3,5 +3,4 @@ .fa-wrench { color: vv.$info; font-size: 6em; - margin-top: 200px; } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster.component.ts index 18dc8040616..7f9db85517a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/multi-cluster/multi-cluster.component.ts @@ -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; 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); } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.html index bad69c50122..17df92f3f24 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.html @@ -43,6 +43,18 @@ + + +
Managed By
+
+ + {{ managedByConfig['MANAGED_BY_CLUSTERS'][0]['fsid'] }} + + +
+
+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.ts index 853eed2d695..d8528f6cff8 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.ts @@ -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; hardwareSummary$: Observable; hardwareSubject = new BehaviorSubject([]); + managedByConfig$: Observable; 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 { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/multi-cluster.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/multi-cluster.service.ts index 5e6ab6e3606..a5b9a0f89f5 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/multi-cluster.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/multi-cluster.service.ts @@ -142,24 +142,6 @@ export class MultiClusterService { }); } - verifyConnection( - url: string, - username: string, - password: string, - token = '', - ssl = false, - cert = '' - ): Observable { - 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); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/prometheus.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/prometheus.service.ts index eaa1696dc87..2b469e837d2 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/prometheus.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/prometheus.service.ts @@ -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++) { diff --git a/src/pybind/mgr/dashboard/openapi.yaml b/src/pybind/mgr/dashboard/openapi.yaml index dbf6a3e299e..fcab8fdafda 100644 --- a/src/pybind/mgr/dashboard/openapi.yaml +++ b/src/pybind/mgr/dashboard/openapi.yaml @@ -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: [] diff --git a/src/pybind/mgr/dashboard/settings.py b/src/pybind/mgr/dashboard/settings.py index acff17e94e5..e98383070e2 100644 --- a/src/pybind/mgr/dashboard/settings.py +++ b/src/pybind/mgr/dashboard/settings.py @@ -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 -- 2.39.5