]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Fix spacings in donut chart in resiliency card
authorAfreen Misbah <afreen@ibm.com>
Tue, 21 Jul 2026 19:05:58 +0000 (00:35 +0530)
committerAfreen Misbah <afreen@ibm.com>
Wed, 22 Jul 2026 08:03:07 +0000 (13:33 +0530)
Fixes https://tracker.ceph.com/issues/78507

Signed-off-by: Afreen Misbah <afreen@ibm.com>
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/prometheus/active-alert-list/active-alert-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/health-card/overview-health-card.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/health-card/overview-health-card.component.scss
src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/health-card/overview-health-card.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/health-card/overview-health-card.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/models/overview.spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/src/app/shared/services/prometheus-alert.service.ts

index 2930710b55bb1a56e484b58ef369660e6c735fe2..c367ad9ba6a6f23ccc1257f825ffc2cde6069743 100644 (file)
@@ -5,6 +5,7 @@ import { PrometheusService } from '~/app/shared/api/prometheus.service';
 import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
 import { Icons } from '~/app/shared/enum/icons.enum';
 import { PrometheusListHelper } from '~/app/shared/helpers/prometheus-list-helper';
+import { TableComponent } from '~/app/shared/datatable/table/table.component';
 import { CdTableAction } from '~/app/shared/models/cd-table-action';
 import { CdTableColumn } from '~/app/shared/models/cd-table-column';
 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
@@ -32,6 +33,7 @@ const SeverityMap = {
 export class ActiveAlertListComponent extends PrometheusListHelper implements OnInit {
   @ViewChild('externalLinkTpl', { static: true })
   externalLinkTpl: TemplateRef<any>;
+  @ViewChild(TableComponent) table: TableComponent;
   columns: CdTableColumn[];
   innerColumns: CdTableColumn[];
   tableActions: CdTableAction[];
@@ -162,6 +164,14 @@ export class ActiveAlertListComponent extends PrometheusListHelper implements On
     this.route.queryParams.subscribe((params) => {
       const severity = params['severity'];
       this.filters[1].filterInitValue = SeverityMap[severity];
+      if (params['search']) {
+        setTimeout(() => {
+          if (this.table) {
+            this.table.search = params['search'];
+            this.table.updateFilter();
+          }
+        });
+      }
     });
   }
 
index 80be6845654b86349cf56266fb41ebf1a8264517..c641fbd244fa5354bee02e5ac083dc412ecc1d26 100644 (file)
                 size="sm"
                 class="overview-health-card-resiliency-btn"
                 [routerLink]="['/monitoring/active-alerts']"
+                [queryParams]="{ search: 'CephPG' }"
               >
                 <span i18n>View alerts ({{ pgAlertCount }})</span>
                 <cd-icon type="arrowRight"></cd-icon>
             <div class="overview-health-card-resiliency-chart-text">
               <p
                 i18n
-                class="cds--type-helper-text-01 overview-health-card-secondary-text cds-mt-2"
+                class="cds--type-helper-text-01 overview-health-card-secondary-text cds-mt-2 cds-mb-3"
               >
                 <em>Data resiliency</em> reflects data availability and replication (% of placement
                 groups that are active and clean).
                   let isLast = $last
                 ) {
                   @if (item.count) {
-                    <p
-                      [class.cds-mb-2]="!isLast"
-                      [class.cds-mb-0]="isLast"
-                    >
+                    <p class="cds-mb-0">
                       <span class="cds--type-label-01 cds-mr-1">{{ item?.state }}:</span>
                       <span class="cds--type-label-01 overview-health-card-bold"
                         >{{ item.count }} %</span
index 58a04a5ff55cec2badee06a0881215685e224868..a460a65d1c701e6714980051840aca3879d54763 100644 (file)
@@ -38,7 +38,7 @@
   }
 
   &-tab-content {
-    padding: var(--cds-spacing-04) 0;
+    padding: var(--cds-spacing-04) 0 0 0;
   }
 
   &-tab-content-item {
index 138d4e01ac6a56b84f484c05efcbc435be25d6df..61562d0d3c5c83d559c5e3fb9e8faf3a392ce5e3 100644 (file)
@@ -15,6 +15,7 @@ import { HealthService } from '~/app/shared/api/health.service';
 import { MgrModuleService } from '~/app/shared/api/mgr-module.service';
 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
 import { HardwareNameMapping } from '~/app/shared/enum/hardware.enum';
+import { PrometheusAlertService } from '~/app/shared/services/prometheus-alert.service';
 
 const MOCK_HW_SUMMARY = {
   total: {
@@ -93,6 +94,7 @@ describe('OverviewHealthCardComponent', () => {
         { provide: MgrModuleService, useValue: mockMgrModuleService },
         { provide: HardwareService, useValue: mockHardwareService },
         { provide: HealthService, useValue: mockHealthService },
+        { provide: PrometheusAlertService, useValue: { totalAlerts$: of(0), alerts: [] } },
         provideRouter([])
       ]
     }).compileComponents();
@@ -250,6 +252,7 @@ describe('OverviewHealthCardComponent (all healthy)', () => {
         },
         { provide: HardwareService, useValue: { getSummary: jest.fn(() => of(healthyMock)) } },
         { provide: HealthService, useValue: { getTelemetryStatus: jest.fn(() => of(false)) } },
+        { provide: PrometheusAlertService, useValue: { totalAlerts$: of(0), alerts: [] } },
         provideRouter([])
       ]
     }).compileComponents();
@@ -326,6 +329,7 @@ describe('OverviewHealthCardComponent (warn only)', () => {
         },
         { provide: HardwareService, useValue: { getSummary: jest.fn(() => of(warnMock)) } },
         { provide: HealthService, useValue: { getTelemetryStatus: jest.fn(() => of(false)) } },
+        { provide: PrometheusAlertService, useValue: { totalAlerts$: of(0), alerts: [] } },
         provideRouter([])
       ]
     }).compileComponents();
@@ -387,6 +391,7 @@ describe('OverviewHealthCardComponent (hw disabled)', () => {
         },
         { provide: HardwareService, useValue: { getSummary: jest.fn(() => of(null)) } },
         { provide: HealthService, useValue: { getTelemetryStatus: jest.fn(() => of(false)) } },
+        { provide: PrometheusAlertService, useValue: { totalAlerts$: of(0), alerts: [] } },
         provideRouter([])
       ]
     }).compileComponents();
@@ -441,6 +446,7 @@ describe('OverviewHealthCardComponent (no permissions)', () => {
         { provide: MgrModuleService, useValue: { getConfig: jest.fn(() => of({})) } },
         { provide: HardwareService, useValue: { getSummary: jest.fn(() => of(null)) } },
         { provide: HealthService, useValue: { getTelemetryStatus: jest.fn(() => of(false)) } },
+        { provide: PrometheusAlertService, useValue: { totalAlerts$: of(0), alerts: [] } },
         provideRouter([])
       ]
     }).compileComponents();
index 1eac035f9b18c9d05d996cba0436446798a3676e..602a3ebf3101a7eb8dcf2f9ccea69aead076574e 100644 (file)
@@ -32,6 +32,7 @@ import {
   HardwareCardVM,
   buildHardwareCardVM
 } from '~/app/shared/models/overview';
+import { AlertState } from '~/app/shared/models/prometheus-alerts';
 import { HardwareService } from '~/app/shared/api/hardware.service';
 import { HealthService } from '~/app/shared/api/health.service';
 import { MgrModuleService } from '~/app/shared/api/mgr-module.service';
@@ -40,6 +41,8 @@ import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
 import { GaugeChartComponent } from '@carbon/charts-angular';
 import { PrometheusAlertService } from '~/app/shared/services/prometheus-alert.service';
 
+const PG_ALERT_PREFIX = 'CephPG';
+
 type OverviewHealthData = {
   summary: Summary;
   upgrade: UpgradeInfoInterface | null;
@@ -148,7 +151,16 @@ export class OverviewHealthCardComponent {
     shareReplay({ bufferSize: 1, refCount: true })
   );
 
-  readonly pgAlertCount$ = this.prometheusAlertService.pgAlerts$.pipe(startWith(0));
+  readonly pgAlertCount$ = this.prometheusAlertService.totalAlerts$.pipe(
+    map(
+      () =>
+        this.prometheusAlertService.alerts.filter(
+          (a) =>
+            a.status.state === AlertState.ACTIVE && a.labels.alertname?.startsWith(PG_ALERT_PREFIX)
+        ).length
+    ),
+    startWith(0)
+  );
 
   readonly telemetryEnabled$: Observable<boolean> = this.healthService.getTelemetryStatus().pipe(
     map((enabled: any) => !!enabled),
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/overview.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/overview.spec.ts
deleted file mode 100644 (file)
index 7fbb8d6..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-import { buildHealthCardVM } from './overview';
-import { HealthSnapshotMap } from './health.interface';
-
-function makeSnapshot(checks: Record<string, any> = {}): HealthSnapshotMap {
-  return {
-    fsid: 'test-fsid',
-    health: { status: 'HEALTH_OK', checks, mutes: [] },
-    monmap: { num_mons: 3 },
-    osdmap: { in: 3, up: 3, num_osds: 3 },
-    pgmap: {
-      pgs_by_state: [{ state_name: 'active+clean', count: 100 }],
-      num_pools: 1,
-      bytes_used: 0,
-      bytes_total: 1000,
-      num_pgs: 100,
-      write_bytes_sec: 0,
-      read_bytes_sec: 0,
-      recovering_bytes_per_sec: 0
-    },
-    mgrmap: { num_active: 1, num_standbys: 1 },
-    fsmap: { num_active: 0, num_standbys: 0 },
-    num_rgw_gateways: 0,
-    num_iscsi_gateways: { up: 0, down: 0 },
-    num_hosts: 3
-  } as any;
-}
-
-describe('buildHealthCardVM', () => {
-  it('should not include pgAlertCount in the VM', () => {
-    const vm = buildHealthCardVM(makeSnapshot());
-    expect('pgAlertCount' in vm).toBe(false);
-  });
-
-  it('should build resiliencyHealth from health checks', () => {
-    const vm = buildHealthCardVM(
-      makeSnapshot({
-        PG_DEGRADED: {
-          severity: 'HEALTH_WARN',
-          summary: { message: 'degraded', count: 5 },
-          muted: false
-        }
-      })
-    );
-    expect(vm.resiliencyHealth.severity).toBe('warn');
-  });
-
-  it('should report ok resiliency when no PG checks', () => {
-    const vm = buildHealthCardVM(makeSnapshot());
-    expect(vm.resiliencyHealth.severity).toBe('ok');
-  });
-});
index bfed174febe34fa4e82c7d6cfd336fef441957ba..4273f8b4f95f090c8f3568ac2c3d55e2a4ef6964 100644 (file)
@@ -34,9 +34,6 @@ export class PrometheusAlertService {
   private warningSubject = new BehaviorSubject<number>(0);
   readonly warningAlerts$ = this.warningSubject.asObservable();
 
-  private pgAlertsSubject = new BehaviorSubject<number>(0);
-  readonly pgAlerts$ = this.pgAlertsSubject.asObservable();
-
   constructor(
     private alertFormatter: PrometheusAlertFormatter,
     private prometheusService: PrometheusService
@@ -120,16 +117,9 @@ export class PrometheusAlertService {
       0
     );
 
-    const activePgAlerts = alerts.filter(
-      (alert) =>
-        alert.status.state === AlertState.ACTIVE &&
-        alert.labels.alertname?.startsWith('CephPG')
-    ).length;
-
     this.totalSubject.next(this.activeAlerts);
     this.criticalSubject.next(this.activeCriticalAlerts);
     this.warningSubject.next(this.activeWarningAlerts);
-    this.pgAlertsSubject.next(activePgAlerts);
 
     this.alerts = alerts
       .reverse()