From: Abhishek Desai Date: Mon, 6 Jul 2026 11:26:12 +0000 (+0530) Subject: mgr/dashboard: allow read-only users to view Silences page X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=2fba7b733ac7f0b7b9316f96bf5af871a23b0751;p=ceph.git mgr/dashboard: allow read-only users to view Silences page Remove SilenceFormComponent from the silences list page, which incorrectly required create permission and caused a blank page for users with prometheus read access only. Fixes: https://tracker.ceph.com/issues/77952 Signed-off-by: Abhishek Desai Conflicts: src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/prometheus-tabs/prometheus-tabs.component.html - Added permissions --- diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/prometheus-tabs/prometheus-tabs.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/prometheus-tabs/prometheus-tabs.component.html index 40f70bcadd4..803ffad1f62 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/prometheus-tabs/prometheus-tabs.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/prometheus-tabs/prometheus-tabs.component.html @@ -24,17 +24,16 @@ > - - Silences - + @if (canViewSilences) { + + Silences + + } { configureTestBed({ imports: [RouterTestingModule, NgbNavModule], declarations: [PrometheusTabsComponent], - providers: [{ provide: PrometheusAlertService, useValue: { alerts: [] } }] + providers: [ + { provide: PrometheusAlertService, useValue: { alerts: [] } }, + { + provide: AuthStorageService, + useValue: { + getPermissions: () => ({ + prometheus: new Permission(['read']) + }) + } + } + ] }); beforeEach(() => { @@ -26,4 +38,8 @@ describe('PrometheusTabsComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should show silences to users with read access', () => { + expect(component.canViewSilences).toBe(true); + }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/prometheus-tabs/prometheus-tabs.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/prometheus-tabs/prometheus-tabs.component.ts index 423cd386fbc..f3824a299b6 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/prometheus-tabs/prometheus-tabs.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/prometheus-tabs/prometheus-tabs.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { PrometheusAlertService } from '~/app/shared/services/prometheus-alert.service'; +import { AuthStorageService } from '~/app/shared/services/auth-storage.service'; @Component({ selector: 'cd-prometheus-tabs', @@ -9,5 +10,13 @@ import { PrometheusAlertService } from '~/app/shared/services/prometheus-alert.s standalone: false }) export class PrometheusTabsComponent { - constructor(public prometheusAlertService: PrometheusAlertService) {} + canViewSilences: boolean; + + constructor( + public prometheusAlertService: PrometheusAlertService, + private authStorageService: AuthStorageService + ) { + const prometheusPermission = this.authStorageService.getPermissions().prometheus; + this.canViewSilences = prometheusPermission.read; + } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-list/silence-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-list/silence-list.component.spec.ts index be93bb5f42e..e116a311d7a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-list/silence-list.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-list/silence-list.component.spec.ts @@ -54,6 +54,13 @@ describe('SilenceListComponent', () => { expect(component).toBeTruthy(); }); + it('should create for read-only prometheus users', () => { + (authStorageService.getPermissions as jasmine.Spy).and.callFake(() => ({ + prometheus: new Permission(['read']) + })); + expect(() => TestBed.createComponent(SilenceListComponent)).not.toThrow(); + }); + it('should test all TableActions combinations', () => { const permissionHelper: PermissionHelper = new PermissionHelper(component.permission); const tableActions: TableActionsComponent = permissionHelper.setPermissionsAndGetActions( diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-list/silence-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-list/silence-list.component.ts index 75ce093af3f..6b7e3be8185 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-list/silence-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/silence-list/silence-list.component.ts @@ -4,7 +4,6 @@ import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { Observable, Subscriber } from 'rxjs'; import { PrometheusListHelper } from '~/app/shared/helpers/prometheus-list-helper'; -import { SilenceFormComponent } from '~/app/ceph/cluster/prometheus/silence-form/silence-form.component'; import { PrometheusService } from '~/app/shared/api/prometheus.service'; import { DeleteConfirmationModalComponent } from '~/app/shared/components/delete-confirmation-modal/delete-confirmation-modal.component'; import { ActionLabelsI18n, SucceededActionLabelsI18n } from '~/app/shared/constants/app.constants'; @@ -29,10 +28,7 @@ import { CdSortPropDir } from '~/app/shared/models/cd-sort-prop-dir'; const BASE_URL = 'monitoring/silences'; @Component({ - providers: [ - { provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) }, - SilenceFormComponent - ], + providers: [{ provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) }], selector: 'cd-silences-list', templateUrl: './silence-list.component.html', styleUrls: ['./silence-list.component.scss'], @@ -62,7 +58,6 @@ export class SilenceListComponent extends PrometheusListHelper { private urlBuilder: URLBuilderService, private actionLabels: ActionLabelsI18n, private succeededLabels: SucceededActionLabelsI18n, - private silenceFormComponent: SilenceFormComponent, private silenceMatcher: PrometheusSilenceMatcherService, @Inject(PrometheusService) prometheusService: PrometheusService ) { @@ -172,7 +167,7 @@ export class SilenceListComponent extends PrometheusListHelper { const activeSilences = silences.filter( (silence: AlertmanagerSilence) => silence.status.state !== 'expired' ); - this.getAlerts(activeSilences); + this.loadRulesAndMatchAlerts(activeSilences); }, () => { this.prometheusService.disableAlertmanagerConfig(); @@ -185,13 +180,32 @@ export class SilenceListComponent extends PrometheusListHelper { this.selection = selection; } - getAlerts(silences: any) { - const rules = this.silenceFormComponent.getRules(); - silences.forEach((silence: any) => { - silence.matchers.forEach((matcher: any) => { - this.rules = this.silenceMatcher.getMatchedRules(matcher, rules); + private loadRulesAndMatchAlerts(silences: AlertmanagerSilence[]) { + this.prometheusService.ifPrometheusConfigured( + () => + this.prometheusService.getRules().subscribe( + (groups) => { + this.rules = groups.groups.flatMap((group) => group.rules); + this.getAlerts(silences); + }, + () => { + this.rules = []; + this.getAlerts(silences); + } + ), + () => { + this.rules = []; + this.getAlerts(silences); + } + ); + } + + getAlerts(silences: AlertmanagerSilence[]) { + silences.forEach((silence) => { + silence.matchers.forEach((matcher) => { + const matchedRules = this.silenceMatcher.getMatchedRules(matcher, this.rules); const alertNames: string[] = []; - for (const rule of this.rules) { + for (const rule of matchedRules) { alertNames.push(rule.name); } silence.silencedAlerts = alertNames; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/alertmanager-silence.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/alertmanager-silence.ts index 5f69f1e1e81..7a09abae617 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/alertmanager-silence.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/alertmanager-silence.ts @@ -1,5 +1,3 @@ -import { PrometheusRule } from './prometheus-alerts'; - export class AlertmanagerSilenceMatcher { name: string; value: any; @@ -22,5 +20,5 @@ export class AlertmanagerSilence { status?: { state: 'expired' | 'active' | 'pending'; }; - silencedAlerts?: PrometheusRule[]; + silencedAlerts?: string[]; }