From 05f7cb615b69e4f86a475b08f8f5baee2a1e2a32 Mon Sep 17 00:00:00 2001 From: Nizamudeen A Date: Wed, 6 Aug 2025 14:53:22 +0530 Subject: [PATCH] mgr/dashboard: loading state for inventory card show loading state when inventory details are being loaded. also don't block the UI when inventories are not loaded. Fixes: https://tracker.ceph.com/issues/72494 Signed-off-by: Nizamudeen A --- .../cluster/upgrade/upgrade.component.html | 8 +- .../ceph/dashboard-v3/dashboard-v3.module.ts | 5 +- .../dashboard/dashboard-v3.component.html | 126 +++++++++--------- .../dashboard/dashboard-v3.component.ts | 79 ++++++++--- .../app/ceph/dashboard-v3/pg-summary.pipe.ts | 1 + .../rgw-overview-dashboard.component.ts | 2 +- .../card-row/card-row.component.html | 71 +++++++--- .../components/card-row/card-row.component.ts | 10 +- .../shared/components/components.module.ts | 6 +- .../src/app/shared/models/health.interface.ts | 49 +++++++ .../app/shared/pipes/mds-summary.pipe.spec.ts | 6 +- .../src/app/shared/pipes/mds-summary.pipe.ts | 6 +- .../app/shared/pipes/mgr-summary.pipe.spec.ts | 6 +- .../src/app/shared/pipes/mgr-summary.pipe.ts | 6 +- .../app/shared/pipes/osd-summary.pipe.spec.ts | 2 +- .../src/app/shared/pipes/osd-summary.pipe.ts | 2 +- 16 files changed, 244 insertions(+), 141 deletions(-) create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/models/health.interface.ts diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade.component.html index 440986c535aad..2962a074cf807 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade.component.html @@ -83,9 +83,9 @@
+ *ngIf="(healthData.mgr_map | mgrSummary)?.total > 1; else warningIcon"> - {{ (healthData.mgr_map | mgrSummary).total }} + {{ (healthData.mgr_map | mgrSummary)?.total }}
@@ -139,12 +139,12 @@ *ngIf="info$ | async as info; else checkingForUpgradeStatus">
+ [ngbTooltip]="(healthData.mgr_map | mgrSummary)?.total <= 1 ? 'To upgrade, you need minimum 2 mgr daemons.' : ''">
- +
@@ -66,64 +64,60 @@ i18n-title class="pt-4" aria-label="Inventory card"> - - - - - - - + + + + + + + + - - + + - - + + - - + + - - + + - - - - + + + + +
@@ -148,28 +142,28 @@
- + Cluster
- +
@@ -330,3 +324,7 @@ i18n> See Logs for more details.

+ + + + 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 c122d3bc01422..6d17aacceab6a 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 @@ -1,8 +1,8 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import _ from 'lodash'; -import { BehaviorSubject, Observable, Subscription, of } from 'rxjs'; -import { switchMap, take } from 'rxjs/operators'; +import { BehaviorSubject, EMPTY, Observable, Subject, Subscription, of } from 'rxjs'; +import { catchError, exhaustMap, switchMap, take, takeUntil } from 'rxjs/operators'; import { HealthService } from '~/app/shared/api/health.service'; import { OsdService } from '~/app/shared/api/osd.service'; @@ -27,6 +27,14 @@ 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'; import { OsdSettings } from '~/app/shared/models/osd-settings'; +import { + IscsiMap, + MdsMap, + MgrMap, + MonMap, + OsdMap, + PgStatus +} from '~/app/shared/models/health.interface'; @Component({ selector: 'cd-dashboard-v3', @@ -37,7 +45,6 @@ export class DashboardV3Component extends PrometheusListHelper implements OnInit detailsCardData: DashboardDetails = {}; osdSettingsService: any; osdSettings = new OsdSettings(); - interval = new Subscription(); permissions: Permissions; enabledFeature$: FeatureTogglesMap$; color: string; @@ -80,6 +87,17 @@ export class DashboardV3Component extends PrometheusListHelper implements OnInit hardwareSubject = new BehaviorSubject([]); managedByConfig$: Observable; private subs = new Subscription(); + private destroy$ = new Subject(); + + hostsCount: number = null; + monMap: MonMap = null; + mgrMap: MgrMap = null; + osdMap: OsdMap = null; + poolStatus: Record[] = null; + pgStatus: PgStatus = null; + rgwCount: number = null; + mdsMap: MdsMap = null; + iscsiMap: IscsiMap = null; constructor( private summaryService: SummaryService, @@ -103,6 +121,7 @@ export class DashboardV3Component extends PrometheusListHelper implements OnInit ngOnInit() { super.ngOnInit(); if (this.permissions.configOpt.read) { + this.getOsdSettings(); this.isHardwareEnabled$ = this.getHardwareConfig(); this.hardwareSummary$ = this.hardwareSubject.pipe( switchMap(() => @@ -116,12 +135,16 @@ export class DashboardV3Component extends PrometheusListHelper implements OnInit ); this.managedByConfig$ = this.settingsService.getValues('MANAGED_BY_CLUSTERS'); } - this.interval = this.refreshIntervalService.intervalData$.subscribe(() => { - this.getHealth(); - this.getCapacity(); - if (this.permissions.configOpt.read) this.getOsdSettings(); - if (this.hardwareEnabled) this.hardwareSubject.next([]); + + this.loadInventories(); + + // fetch capacity to load the capacity chart + this.refreshIntervalObs(() => this.healthService.getClusterCapacity()).subscribe({ + next: (capacity: any) => { + this.capacity = capacity; + } }); + this.getPrometheusData(this.prometheusService.lastHourDateObject); this.getDetailsCardData(); this.getTelemetryReport(); @@ -136,15 +159,10 @@ export class DashboardV3Component extends PrometheusListHelper implements OnInit Telemetry configration.'; } ngOnDestroy() { - this.interval.unsubscribe(); this.prometheusService.unsubscribe(); this.subs?.unsubscribe(); - } - - getHealth() { - this.healthService.getMinimalHealth().subscribe((data: any) => { - this.healthData = data; - }); + this.destroy$.next(); + this.destroy$.complete(); } toggleAlertsWindow(type: AlertClass) { @@ -167,12 +185,6 @@ export class DashboardV3Component extends PrometheusListHelper implements OnInit ); } - private getCapacity() { - this.capacityService = this.healthService.getClusterCapacity().subscribe((data: any) => { - this.capacity = data; - }); - } - private getOsdSettings() { this.osdSettingsService = this.osdService .getOsdSettings() @@ -208,4 +220,29 @@ export class DashboardV3Component extends PrometheusListHelper implements OnInit }) ); } + + refreshIntervalObs(fn: Function) { + return this.refreshIntervalService.intervalData$.pipe( + exhaustMap(() => fn().pipe(catchError(() => EMPTY))), + takeUntil(this.destroy$) + ); + } + + loadInventories() { + this.refreshIntervalObs(() => this.healthService.getMinimalHealth()).subscribe({ + next: (result: any) => { + this.hostsCount = result.hosts; + this.monMap = result.mon_status; + this.mgrMap = result.mgr_map; + this.osdMap = result.osd_map; + this.poolStatus = result.pools; + this.pgStatus = result.pg_info; + this.rgwCount = result.rgw; + this.mdsMap = result.fs_map; + this.iscsiMap = result.iscsi_daemons; + this.healthData = result.health; + this.enabledFeature$ = this.featureToggles.get(); + } + }); + } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/pg-summary.pipe.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/pg-summary.pipe.ts index a26097ee00508..c12193accd65f 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/pg-summary.pipe.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/pg-summary.pipe.ts @@ -9,6 +9,7 @@ export class PgSummaryPipe implements PipeTransform { constructor(private pgCategoryService: PgCategoryService) {} transform(value: any): any { + if (!value) return null; const categoryPgAmount: Record = {}; let total = 0; _.forEach(value.statuses, (pgAmount, pgStatesText) => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.ts index 062f66b113d7c..f7658732049a3 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.ts @@ -99,7 +99,7 @@ export class RgwOverviewDashboardComponent implements OnInit, OnDestroy { this.getSyncStatus(); }); this.realmSub = this.rgwRealmService.list().subscribe((data: any) => { - this.rgwRealmCount = data['realms'].length; + this.rgwRealmCount = data['realms'].length || 0; }); this.ZonegroupSub = this.rgwZonegroupService.list().subscribe((data: any) => { this.rgwZonegroupCount = data['zonegroups'].length; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/card-row/card-row.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/card-row/card-row.component.html index 3ed393aa6fed4..42b4d25e87107 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/card-row/card-row.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/card-row/card-row.component.html @@ -2,15 +2,21 @@
  • - + @if (data === null) { + + } @else { + {{ data.success }} @@ -98,9 +107,13 @@ [ngClass]="[icons.spinner, icons.spin]"> + } + @if (data === null) { + + } @else { {{ data.up }} + } + @if (data === null || total === null) { + + } @else { - {{ data.up }} + {{ data?.up }} - - {{ data.down }} + {{ data?.down }} + + } + @if (data === 0 || data) { {{ data }} + type="success"> + } @else { + + } {{ data - dropdownTotalError }} @@ -185,14 +208,22 @@ + + + + - - {{ total }} - {{ title }} - {{ title }} - {{ title }}s - +@if (data !== null || total) { + + {{ total }} + {{ title }} + {{ title }} + {{ title }}s + +} @else { + {{ title }} +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/card-row/card-row.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/card-row/card-row.component.ts index d977e905f531c..3c577fff2f1ff 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/card-row/card-row.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/card-row/card-row.component.ts @@ -15,7 +15,7 @@ export class CardRowComponent implements OnChanges { link: string; @Input() - data: any; + data: any = null; @Input() summaryType = 'default'; @@ -25,15 +25,15 @@ export class CardRowComponent implements OnChanges { hwNames = HardwareNameMapping; icons = Icons; - total: number; + total: number = null; dropdownTotalError: number = 0; dropdownToggled: boolean = false; ngOnChanges(): void { - if (this.data.total || this.data.total === 0) { - this.total = this.data.total; + if (this.data?.total || this.data?.total === 0) { + this.total = this.data?.total; } else if (this.summaryType === 'iscsi') { - this.total = this.data.up + this.data.down || 0; + this.total = this.data?.up + this.data?.down; } else { this.total = this.data; } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts index c52612e01c270..2859ed0f0c604 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts @@ -39,7 +39,8 @@ import { PanelModule, LayoutModule, TilesModule, - PopoverModule + PopoverModule, + InlineLoadingModule } from 'carbon-components-angular'; import EditIcon from '@carbon/icons/es/edit/20'; import CodeIcon from '@carbon/icons/es/code/16'; @@ -140,7 +141,8 @@ import CloseIcon from '@carbon/icons/es/close/16'; ChartsModule, LayoutModule, TilesModule, - PopoverModule + PopoverModule, + InlineLoadingModule ], declarations: [ SparklineComponent, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/health.interface.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/health.interface.ts new file mode 100644 index 0000000000000..22963b5de54ce --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/health.interface.ts @@ -0,0 +1,49 @@ +export interface MonMap { + monmap: { + mons: Record[]; + }; + quorum: number[]; +} + +export interface MgrMap { + active_name: string; + standbys: string[]; +} + +export interface OsdMap { + osds: Osd[]; +} + +export interface PgStatus { + object_stats: ObjectStats; + statuses: Status; + pgs_per_osd: number; +} + +export interface MdsMap { + filesystems: any[]; + standbys: any[]; +} + +export interface IscsiMap { + up: number; + down: number; +} + +interface ObjectStats { + num_objects: number; + num_object_copies: number; + num_objects_degraded: number; + num_objects_misplaced: number; + num_objects_unfound: number; +} + +interface Status { + 'active+clean': number; +} + +interface Osd { + in: number; + up: number; + state: string[]; +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mds-summary.pipe.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mds-summary.pipe.spec.ts index 846cfb0bc5dcb..4081aa26ec005 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mds-summary.pipe.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mds-summary.pipe.spec.ts @@ -67,10 +67,6 @@ describe('MdsSummaryPipe', () => { }); it('transforms without value', () => { - expect(pipe.transform(undefined)).toEqual({ - success: 0, - info: 0, - total: 0 - }); + expect(pipe.transform(undefined)).toEqual(null); }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mds-summary.pipe.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mds-summary.pipe.ts index 77758b71d3d57..43004fa0c04fd 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mds-summary.pipe.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mds-summary.pipe.ts @@ -8,11 +8,7 @@ import _ from 'lodash'; export class MdsSummaryPipe implements PipeTransform { transform(value: any): any { if (!value) { - return { - success: 0, - info: 0, - total: 0 - }; + return null; } let activeCount = 0; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mgr-summary.pipe.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mgr-summary.pipe.spec.ts index ac7dcc63fb954..5cc83249c276d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mgr-summary.pipe.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mgr-summary.pipe.spec.ts @@ -19,11 +19,7 @@ describe('MgrSummaryPipe', () => { }); it('transforms without value', () => { - expect(pipe.transform(undefined)).toEqual({ - success: 0, - info: 0, - total: 0 - }); + expect(pipe.transform(undefined)).toEqual(null); }); it('transforms with 1 active and 2 standbys', () => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mgr-summary.pipe.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mgr-summary.pipe.ts index 14b38095210bb..6e85f0dbcb6ea 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mgr-summary.pipe.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mgr-summary.pipe.ts @@ -8,11 +8,7 @@ import _ from 'lodash'; export class MgrSummaryPipe implements PipeTransform { transform(value: any): any { if (!value) { - return { - success: 0, - info: 0, - total: 0 - }; + return null; } let activeCount: number; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/osd-summary.pipe.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/osd-summary.pipe.spec.ts index 2c60fa585c70f..88e457d858b7c 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/osd-summary.pipe.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/osd-summary.pipe.spec.ts @@ -19,7 +19,7 @@ describe('OsdSummaryPipe', () => { }); it('transforms without value', () => { - expect(pipe.transform(undefined)).toBe(''); + expect(pipe.transform(undefined)).toBe(null); }); it('transforms having 3 osd with 3 up, 3 in, 0 down, 0 out', () => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/osd-summary.pipe.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/osd-summary.pipe.ts index 66e86970c6319..cf4ea649dd198 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/osd-summary.pipe.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/osd-summary.pipe.ts @@ -8,7 +8,7 @@ import _ from 'lodash'; export class OsdSummaryPipe implements PipeTransform { transform(value: any): any { if (!value) { - return ''; + return null; } let inCount = 0; -- 2.39.5