From: Pedro Gonzalez Gomez Date: Mon, 8 May 2023 20:51:40 +0000 (+0200) Subject: mgr/dashboard: fix issues with read-only user on landing page X-Git-Tag: v19.0.0~1115^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=379f0a3a2521a8bd9f52ca9e8b263e13d68c07e5;p=ceph.git mgr/dashboard: fix issues with read-only user on landing page Fixes: https://tracker.ceph.com/issues/61418 Signed-off-by: Pedro Gonzalez Gomez Signed-off-by: Nizamudeen A --- diff --git a/src/pybind/mgr/dashboard/controllers/cluster.py b/src/pybind/mgr/dashboard/controllers/cluster.py index 5d776e063513d..d8170e672e992 100644 --- a/src/pybind/mgr/dashboard/controllers/cluster.py +++ b/src/pybind/mgr/dashboard/controllers/cluster.py @@ -19,7 +19,3 @@ class Cluster(RESTController): parameters={'status': (str, 'Cluster Status')}) def singleton_set(self, status: str): ClusterModel(status).to_db() - - @RESTController.Collection('GET', 'capacity') - def get_capacity(self): - return ClusterModel.get_capacity() diff --git a/src/pybind/mgr/dashboard/controllers/health.py b/src/pybind/mgr/dashboard/controllers/health.py index 992b2bc0277b5..633d37a327eaf 100644 --- a/src/pybind/mgr/dashboard/controllers/health.py +++ b/src/pybind/mgr/dashboard/controllers/health.py @@ -6,6 +6,7 @@ from .. import mgr from ..rest_client import RequestException from ..security import Permission, Scope from ..services.ceph_service import CephService +from ..services.cluster import ClusterModel from ..services.iscsi_cli import IscsiGatewaysConfig from ..services.iscsi_client import IscsiClient from ..tools import partial_dict @@ -291,3 +292,11 @@ class Health(BaseController): responses={200: HEALTH_MINIMAL_SCHEMA}) def minimal(self): return self.health_minimal.all_health() + + @Endpoint() + def get_cluster_capacity(self): + return ClusterModel.get_capacity() + + @Endpoint() + def get_cluster_fsid(self): + return mgr.get('config')['fsid'] diff --git a/src/pybind/mgr/dashboard/controllers/orchestrator.py b/src/pybind/mgr/dashboard/controllers/orchestrator.py index 51d0a459dab46..3864820ea6302 100644 --- a/src/pybind/mgr/dashboard/controllers/orchestrator.py +++ b/src/pybind/mgr/dashboard/controllers/orchestrator.py @@ -2,6 +2,7 @@ from functools import wraps +from .. import mgr from ..exceptions import DashboardException from ..services.orchestrator import OrchClient from . import APIDoc, Endpoint, EndpointDoc, ReadPermission, RESTController, UIRouter @@ -45,3 +46,7 @@ class Orchestrator(RESTController): responses={200: STATUS_SCHEMA}) def status(self): return OrchClient.instance().status() + + @Endpoint() + def get_name(self): + return mgr.get_module_option_ex('orchestrator', 'orchestrator') diff --git a/src/pybind/mgr/dashboard/controllers/prometheus.py b/src/pybind/mgr/dashboard/controllers/prometheus.py index d9a360799d9eb..94860344128f7 100644 --- a/src/pybind/mgr/dashboard/controllers/prometheus.py +++ b/src/pybind/mgr/dashboard/controllers/prometheus.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- - import json import os import tempfile @@ -11,8 +10,9 @@ from .. import mgr from ..exceptions import DashboardException from ..security import Scope from ..services import ceph_service -from ..settings import Settings -from . import APIDoc, APIRouter, BaseController, Endpoint, RESTController, Router +from ..services.settings import SettingsService +from ..settings import Options, Settings +from . import APIDoc, APIRouter, BaseController, Endpoint, RESTController, Router, UIRouter @Router('/api/prometheus_receiver', secure=False) @@ -158,3 +158,16 @@ class PrometheusNotifications(RESTController): return PrometheusReceiver.notifications[-1:] return PrometheusReceiver.notifications[int(f) + 1:] return PrometheusReceiver.notifications + + +@UIRouter('/prometheus', Scope.PROMETHEUS) +class PrometheusSettings(RESTController): + def get(self, name): + with SettingsService.attribute_handler(name) as settings_name: + setting = getattr(Options, settings_name) + return { + 'name': settings_name, + 'default': setting.default_value, + 'type': setting.types_as_str(), + 'value': getattr(Settings, settings_name) + } diff --git a/src/pybind/mgr/dashboard/controllers/settings.py b/src/pybind/mgr/dashboard/controllers/settings.py index 7e81a0822d1d7..3876ce2e56928 100644 --- a/src/pybind/mgr/dashboard/controllers/settings.py +++ b/src/pybind/mgr/dashboard/controllers/settings.py @@ -1,9 +1,6 @@ # -*- coding: utf-8 -*- -from contextlib import contextmanager - -import cherrypy - from ..security import Scope +from ..services.settings import SettingsService, _to_native from ..settings import Options from ..settings import Settings as SettingsModule from . import APIDoc, APIRouter, EndpointDoc, RESTController, UIRouter @@ -22,29 +19,6 @@ class Settings(RESTController): """ Enables to manage the settings of the dashboard (not the Ceph cluster). """ - @contextmanager - def _attribute_handler(self, name): - """ - :type name: str|dict[str, str] - :rtype: str|dict[str, str] - """ - if isinstance(name, dict): - result = { - self._to_native(key): value - for key, value in name.items() - } - else: - result = self._to_native(name) - - try: - yield result - except AttributeError: # pragma: no cover - handling is too obvious - raise cherrypy.NotFound(result) # pragma: no cover - handling is too obvious - - @staticmethod - def _to_native(setting): - return setting.upper().replace('-', '_') - @EndpointDoc("Display Settings Information", parameters={ 'names': (str, 'Name of Settings'), @@ -69,7 +43,7 @@ class Settings(RESTController): return [self._get(name) for name in option_names] def _get(self, name): - with self._attribute_handler(name) as sname: + with SettingsService.attribute_handler(name) as sname: setting = getattr(Options, sname) return { 'name': sname, @@ -89,17 +63,17 @@ class Settings(RESTController): return self._get(name) def set(self, name, value): - with self._attribute_handler(name) as sname: - setattr(SettingsModule, self._to_native(sname), value) + with SettingsService.attribute_handler(name) as sname: + setattr(SettingsModule, _to_native(sname), value) def delete(self, name): - with self._attribute_handler(name) as sname: - delattr(SettingsModule, self._to_native(sname)) + with SettingsService.attribute_handler(name) as sname: + delattr(SettingsModule, _to_native(sname)) def bulk_set(self, **kwargs): - with self._attribute_handler(kwargs) as data: + with SettingsService.attribute_handler(kwargs) as data: for name, value in data.items(): - setattr(SettingsModule, self._to_native(name), value) + setattr(SettingsModule, _to_native(name), value) @UIRouter('/standard_settings') diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.spec.ts index 677ca35905b73..3c7b7e0c283a6 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.spec.ts @@ -8,9 +8,7 @@ import _ from 'lodash'; import { ToastrModule } from 'ngx-toastr'; import { BehaviorSubject, of } from 'rxjs'; -import { ConfigurationService } from '~/app/shared/api/configuration.service'; import { HealthService } from '~/app/shared/api/health.service'; -import { MgrModuleService } from '~/app/shared/api/mgr-module.service'; import { PrometheusService } from '~/app/shared/api/prometheus.service'; import { CssHelper } from '~/app/shared/classes/css-helper'; import { AlertmanagerAlert } from '~/app/shared/models/prometheus-alerts'; @@ -25,6 +23,7 @@ import { CardComponent } from '../card/card.component'; import { DashboardPieComponent } from '../dashboard-pie/dashboard-pie.component'; import { PgSummaryPipe } from '../pg-summary.pipe'; import { DashboardV3Component } from './dashboard-v3.component'; +import { OrchestratorService } from '~/app/shared/api/orchestrator.service'; export class SummaryServiceMock { summaryDataSource = new BehaviorSubject({ @@ -42,8 +41,8 @@ export class SummaryServiceMock { describe('Dashbord Component', () => { let component: DashboardV3Component; let fixture: ComponentFixture; - let configurationService: ConfigurationService; - let orchestratorService: MgrModuleService; + let healthService: HealthService; + let orchestratorService: OrchestratorService; let getHealthSpy: jasmine.Spy; let getAlertsSpy: jasmine.Spy; let fakeFeatureTogglesService: jasmine.Spy; @@ -133,22 +132,9 @@ describe('Dashbord Component', () => { } ]; - const configValueData: any = { - value: [ - { - section: 'mgr', - value: 'e90a0d58-658e-4148-8f61-e896c86f0696' - } - ] - }; + const configValueData: any = 'e90a0d58-658e-4148-8f61-e896c86f0696'; - const orchData: any = { - log_level: '', - log_to_cluster: false, - log_to_cluster_level: 'info', - log_to_file: false, - orchestrator: 'cephadm' - }; + const orchName: any = 'Cephadm'; configureTestBed({ imports: [RouterTestingModule, HttpClientTestingModule, ToastrModule.forRoot(), SharedModule], @@ -186,8 +172,8 @@ describe('Dashbord Component', () => { ); fixture = TestBed.createComponent(DashboardV3Component); component = fixture.componentInstance; - configurationService = TestBed.inject(ConfigurationService); - orchestratorService = TestBed.inject(MgrModuleService); + healthService = TestBed.inject(HealthService); + orchestratorService = TestBed.inject(OrchestratorService); getHealthSpy = spyOn(TestBed.inject(HealthService), 'getMinimalHealth'); getHealthSpy.and.returnValue(of(healthPayload)); spyOn(TestBed.inject(PrometheusService), 'ifAlertmanagerConfigured').and.callFake((fn) => fn()); @@ -206,8 +192,8 @@ describe('Dashbord Component', () => { }); it('should get corresponding data into detailsCardData', () => { - spyOn(configurationService, 'get').and.returnValue(of(configValueData)); - spyOn(orchestratorService, 'getConfig').and.returnValue(of(orchData)); + spyOn(healthService, 'getClusterFsid').and.returnValue(of(configValueData)); + spyOn(orchestratorService, 'getName').and.returnValue(of(orchName)); component.ngOnInit(); expect(component.detailsCardData.fsid).toBe('e90a0d58-658e-4148-8f61-e896c86f0696'); expect(component.detailsCardData.orchestrator).toBe('Cephadm'); 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 1aeb57fccc768..a3bd264c68435 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 @@ -5,10 +5,7 @@ import { Observable, Subscription, timer } from 'rxjs'; import { take } from 'rxjs/operators'; import moment from 'moment'; -import { ClusterService } from '~/app/shared/api/cluster.service'; -import { ConfigurationService } from '~/app/shared/api/configuration.service'; import { HealthService } from '~/app/shared/api/health.service'; -import { MgrModuleService } from '~/app/shared/api/mgr-module.service'; import { OsdService } from '~/app/shared/api/osd.service'; import { PrometheusService } from '~/app/shared/api/prometheus.service'; import { Promqls as queries } from '~/app/shared/enum/dashboard-promqls.enum'; @@ -25,6 +22,7 @@ import { RefreshIntervalService } from '~/app/shared/services/refresh-interval.s import { SummaryService } from '~/app/shared/services/summary.service'; import { PrometheusListHelper } from '~/app/shared/helpers/prometheus-list-helper'; import { PrometheusAlertService } from '~/app/shared/services/prometheus-alert.service'; +import { OrchestratorService } from '~/app/shared/api/orchestrator.service'; @Component({ selector: 'cd-dashboard-v3', @@ -77,9 +75,7 @@ export class DashboardV3Component extends PrometheusListHelper implements OnInit constructor( private summaryService: SummaryService, - private configService: ConfigurationService, - private mgrModuleService: MgrModuleService, - private clusterService: ClusterService, + private orchestratorService: OrchestratorService, private osdService: OsdService, private authStorageService: AuthStorageService, private featureToggles: FeatureTogglesService, @@ -136,12 +132,11 @@ export class DashboardV3Component extends PrometheusListHelper implements OnInit } getDetailsCardData() { - this.configService.get('fsid').subscribe((data) => { - this.detailsCardData.fsid = data['value'][0]['value']; + this.healthService.getClusterFsid().subscribe((data: string) => { + this.detailsCardData.fsid = data; }); - this.mgrModuleService.getConfig('orchestrator').subscribe((data) => { - const orchStr = data['orchestrator']; - this.detailsCardData.orchestrator = orchStr.charAt(0).toUpperCase() + orchStr.slice(1); + this.orchestratorService.getName().subscribe((data: string) => { + this.detailsCardData.orchestrator = data; }); this.summaryService.subscribe((summary) => { const version = summary.version.replace('ceph version ', '').split(' '); @@ -157,7 +152,7 @@ export class DashboardV3Component extends PrometheusListHelper implements OnInit .subscribe((data: any) => { this.osdSettings = data; }); - this.capacityService = this.clusterService.getCapacity().subscribe((data: any) => { + this.capacityService = this.healthService.getClusterCapacity().subscribe((data: any) => { this.capacity = data; }); } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cluster.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cluster.service.ts index f5b8e4d7cc118..6b435d6ffed1d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cluster.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cluster.service.ts @@ -24,8 +24,4 @@ export class ClusterService { { headers: { Accept: 'application/vnd.ceph.api.v0.1+json' } } ); } - - getCapacity() { - return this.http.get(`${this.baseURL}/capacity`, {}); - } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/health.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/health.service.ts index a8f7c467a0ca7..42634a1481cf2 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/health.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/health.service.ts @@ -14,4 +14,16 @@ export class HealthService { getMinimalHealth() { return this.http.get('api/health/minimal'); } + + getClusterCapacity() { + return this.http.get('api/health/get_cluster_capacity'); + } + + getClusterFsid() { + return this.http.get('api/health/get_cluster_fsid'); + } + + getOrchestratorName() { + return this.http.get('api/health/get_orchestrator_name'); + } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/orchestrator.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/orchestrator.service.ts index a6e33e8342acb..a036b3943d2a2 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/orchestrator.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/orchestrator.service.ts @@ -43,4 +43,8 @@ export class OrchestratorService { } return false; } + + getName() { + return this.http.get(`${this.url}/get_name`); + } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/prometheus.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/prometheus.service.spec.ts index c42f6e7ac1273..65fc174b92e34 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/prometheus.service.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/prometheus.service.spec.ts @@ -168,7 +168,7 @@ describe('PrometheusService', () => { let host: string; const receiveConfig = () => { - const req = httpTesting.expectOne('api/settings/alertmanager-api-host'); + const req = httpTesting.expectOne('ui-api/prometheus/alertmanager-api-host'); expect(req.request.method).toBe('GET'); req.flush({ value: host }); }; @@ -209,7 +209,7 @@ describe('PrometheusService', () => { let host: string; const receiveConfig = () => { - const req = httpTesting.expectOne('api/settings/prometheus-api-host'); + const req = httpTesting.expectOne('ui-api/prometheus/prometheus-api-host'); expect(req.request.method).toBe('GET'); req.flush({ value: host }); }; 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 30d7d488649ae..340f89ca38f8f 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 @@ -10,7 +10,6 @@ import { AlertmanagerNotification, PrometheusRuleGroup } from '../models/prometheus-alerts'; -import { SettingsService } from './settings.service'; @Injectable({ providedIn: 'root' @@ -18,30 +17,31 @@ import { SettingsService } from './settings.service'; export class PrometheusService { private baseURL = 'api/prometheus'; private settingsKey = { - alertmanager: 'api/settings/alertmanager-api-host', - prometheus: 'api/settings/prometheus-api-host' + alertmanager: 'ui-api/prometheus/alertmanager-api-host', + prometheus: 'ui-api/prometheus/prometheus-api-host' }; + private settings: { [url: string]: string } = {}; - constructor(private http: HttpClient, private settingsService: SettingsService) {} + constructor(private http: HttpClient) {} getPrometheusData(params: any): any { return this.http.get(`${this.baseURL}/data`, { params }); } ifAlertmanagerConfigured(fn: (value?: string) => void, elseFn?: () => void): void { - this.settingsService.ifSettingConfigured(this.settingsKey.alertmanager, fn, elseFn); + this.ifSettingConfigured(this.settingsKey.alertmanager, fn, elseFn); } disableAlertmanagerConfig(): void { - this.settingsService.disableSetting(this.settingsKey.alertmanager); + this.disableSetting(this.settingsKey.alertmanager); } ifPrometheusConfigured(fn: (value?: string) => void, elseFn?: () => void): void { - this.settingsService.ifSettingConfigured(this.settingsKey.prometheus, fn, elseFn); + this.ifSettingConfigured(this.settingsKey.prometheus, fn, elseFn); } disablePrometheusConfig(): void { - this.settingsService.disableSetting(this.settingsKey.prometheus); + this.disableSetting(this.settingsKey.prometheus); } getAlerts(params = {}): Observable { @@ -83,4 +83,36 @@ export class PrometheusService { }`; return this.http.get(url); } + + ifSettingConfigured(url: string, fn: (value?: string) => void, elseFn?: () => void): void { + const setting = this.settings[url]; + if (setting === undefined) { + this.http.get(url).subscribe( + (data: any) => { + this.settings[url] = this.getSettingsValue(data); + this.ifSettingConfigured(url, fn, elseFn); + }, + (resp) => { + if (resp.status !== 401) { + this.settings[url] = ''; + } + } + ); + } else if (setting !== '') { + fn(setting); + } else { + if (elseFn) { + elseFn(); + } + } + } + + // Easiest way to stop reloading external content that can't be reached + disableSetting(url: string) { + this.settings[url] = ''; + } + + private getSettingsValue(data: any): string { + return data.value || data.instance || ''; + } } diff --git a/src/pybind/mgr/dashboard/openapi.yaml b/src/pybind/mgr/dashboard/openapi.yaml index 120816fdac3d3..a46fbe1767d6f 100644 --- a/src/pybind/mgr/dashboard/openapi.yaml +++ b/src/pybind/mgr/dashboard/openapi.yaml @@ -2151,28 +2151,6 @@ paths: summary: Update the cluster status tags: - Cluster - /api/cluster/capacity: - get: - parameters: [] - responses: - '200': - content: - application/vnd.ceph.api.v1.0+json: - type: object - description: OK - '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: - - Cluster /api/cluster/user: get: description: "\n Get list of ceph users and its respective data\n \ @@ -3290,6 +3268,50 @@ paths: - jwt: [] tags: - Health + /api/health/get_cluster_capacity: + get: + parameters: [] + responses: + '200': + content: + application/vnd.ceph.api.v1.0+json: + type: object + description: OK + '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: + - Health + /api/health/get_cluster_fsid: + get: + parameters: [] + responses: + '200': + content: + application/vnd.ceph.api.v1.0+json: + type: object + description: OK + '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: + - Health /api/health/minimal: get: parameters: [] diff --git a/src/pybind/mgr/dashboard/services/settings.py b/src/pybind/mgr/dashboard/services/settings.py new file mode 100644 index 0000000000000..373d3966ab7a3 --- /dev/null +++ b/src/pybind/mgr/dashboard/services/settings.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +from contextlib import contextmanager + +import cherrypy + + +class SettingsService: + @contextmanager + # pylint: disable=no-self-argument + def attribute_handler(name): + """ + :type name: str|dict[str, str] + :rtype: str|dict[str, str] + """ + if isinstance(name, dict): + result = { + _to_native(key): value + for key, value in name.items() + } + else: + result = _to_native(name) + + try: + yield result + except AttributeError: # pragma: no cover - handling is too obvious + raise cherrypy.NotFound(result) # pragma: no cover - handling is too obvious + + +def _to_native(setting): + return setting.upper().replace('-', '_')