]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: loading state for inventory card 64911/head
authorNizamudeen A <nia@redhat.com>
Wed, 6 Aug 2025 09:23:22 +0000 (14:53 +0530)
committerNizamudeen A <nia@redhat.com>
Mon, 11 Aug 2025 06:22:01 +0000 (11:52 +0530)
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 <nia@redhat.com>
16 files changed:
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard-v3.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/dashboard/dashboard-v3.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard-v3/pg-summary.pipe.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/card-row/card-row.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/components/card-row/card-row.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/models/health.interface.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mds-summary.pipe.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mds-summary.pipe.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mgr-summary.pipe.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/mgr-summary.pipe.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/osd-summary.pipe.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/osd-summary.pipe.ts

index 440986c535aadccaf937ecfa451a0ee4c277fd2f..2962a074cf8077a802976b3d9386331ccd38e991 100644 (file)
@@ -83,9 +83,9 @@
         <h5>
           <i class="text-success"
              [ngClass]="[icons.success]"
-             *ngIf="(healthData.mgr_map | mgrSummary).total > 1; else warningIcon">
+             *ngIf="(healthData.mgr_map | mgrSummary)?.total > 1; else warningIcon">
           </i>
-          {{ (healthData.mgr_map | mgrSummary).total }}
+          {{ (healthData.mgr_map | mgrSummary)?.total }}
         </h5>
       </div>
     </cd-card>
            *ngIf="info$ | async as info; else checkingForUpgradeStatus">
         <ng-container *ngIf="info.versions.length > 0; else noUpgradesAvailable">
           <div i18n-ngbTooltip
-               [ngbTooltip]="(healthData.mgr_map | mgrSummary).total <= 1 ? 'To upgrade, you need minimum 2 mgr daemons.' : ''">
+               [ngbTooltip]="(healthData.mgr_map | mgrSummary)?.total <= 1 ? 'To upgrade, you need minimum 2 mgr daemons.' : ''">
             <button class="btn btn-accent mt-2"
                     id="upgrade"
                     aria-label="Upgrade now"
                     (click)="upgradeNow(info.versions[info.versions.length - 1])"
-                    [disabled]="(healthData.mgr_map | mgrSummary).total <= 1"
+                    [disabled]="(healthData.mgr_map | mgrSummary)?.total <= 1"
                     i18n>Upgrade to {{ info.versions[info.versions.length - 1] }}</button>
           </div>
           <a class="mt-2 link-primary mb-2"
index 6c47a8b7ebf332a79a15649a926ddc4bdc3bf977..e43c88e57bd6fc62171f46db63e2d71d5991658d 100644 (file)
@@ -14,7 +14,7 @@ import { DashboardPieComponent } from './dashboard-pie/dashboard-pie.component';
 import { DashboardTimeSelectorComponent } from './dashboard-time-selector/dashboard-time-selector.component';
 import { DashboardV3Component } from './dashboard/dashboard-v3.component';
 import { PgSummaryPipe } from './pg-summary.pipe';
-import { ToggletipModule } from 'carbon-components-angular';
+import { InlineLoadingModule, ToggletipModule } from 'carbon-components-angular';
 
 @NgModule({
   imports: [
@@ -28,7 +28,8 @@ import { ToggletipModule } from 'carbon-components-angular';
     ReactiveFormsModule,
     SimplebarAngularModule,
     BaseChartDirective,
-    ToggletipModule
+    ToggletipModule,
+    InlineLoadingModule
   ],
   declarations: [
     DashboardV3Component,
index c4b8ec5e7cb26bab84fb3514bf303e01997040f7..f405c1b89f1d58254518dcc2c9a1e8a6b348293f 100644 (file)
@@ -1,6 +1,4 @@
-<div class="container-fluid p-4"
-     *ngIf="healthData && enabledFeature$ | async as enabledFeature">
-
+<div class="container-fluid p-4">
   <div class="row d-flex flex-row ps-3">
 
     <!-- First Grid to hold Details and Inventory Card-->
                i18n-title
                class="pt-4"
                aria-label="Inventory card">
-        <!-- Hosts -->
-        <cd-card-row [data]="healthData.hosts"
-                     link="/hosts"
-                     title="Host"
-                     summaryType="simplified"
-                     *ngIf="healthData.hosts != null"
-                     [dropdownData]="(isHardwareEnabled$ | async) && (hardwareSummary$ | async)">
-        </cd-card-row>
-        <!-- Monitors -->
-        <cd-card-row [data]="healthData.mon_status.monmap.mons.length"
-                     link="/monitor"
-                     title="Monitor"
-                     summaryType="simplified"
-                     *ngIf="healthData.mon_status"></cd-card-row>
-        <!-- Managers -->
-        <cd-card-row [data]="healthData.mgr_map | mgrSummary"
-                     title="Manager"
-                     *ngIf="healthData.mgr_map"></cd-card-row>
+        <ng-container *ngIf="enabledFeature$ | async as enabledFeature">
+          <!-- Hosts -->
+          <cd-card-row [data]="hostsCount"
+                       link="/hosts"
+                       title="Host"
+                       summaryType="simplified"
+                       [dropdownData]="(isHardwareEnabled$ | async) && (hardwareSummary$ | async)">
+          </cd-card-row>
+          <!-- Monitors -->
+          <cd-card-row [data]="monMap?.monmap.mons.length"
+                       link="/monitor"
+                       title="Monitor"
+                       summaryType="simplified"></cd-card-row>
+          <!-- Managers -->
+          <cd-card-row [data]="mgrMap | mgrSummary"
+                       title="Manager"></cd-card-row>
 
-        <!-- OSDs -->
-        <cd-card-row [data]="healthData.osd_map | osdSummary"
-                     link="/osd"
-                     title="OSD"
-                     summaryType="osd"
-                     *ngIf="healthData.osd_map"></cd-card-row>
+          <!-- OSDs -->
+          <cd-card-row [data]="osdMap | osdSummary"
+                       link="/osd"
+                       title="OSD"
+                       summaryType="osd"></cd-card-row>
 
-        <!-- Pools -->
-        <cd-card-row [data]="healthData.pools.length"
-                     link="/pool"
-                     title="Pool"
-                     summaryType="simplified"
-                     *ngIf="healthData.pools"></cd-card-row>
+          <!-- Pools -->
+          <cd-card-row [data]="poolStatus?.length"
+                       link="/pool"
+                       title="Pool"
+                       summaryType="simplified"></cd-card-row>
 
-        <!-- PG Info -->
-        <cd-card-row [data]="healthData.pg_info | pgSummary"
-                     title="PG"
-                     *ngIf="healthData.pg_info"></cd-card-row>
+          <!-- PG Info -->
+          <cd-card-row [data]="pgStatus | pgSummary"
+                       title="PG"></cd-card-row>
 
-        <!-- Object gateways -->
-        <cd-card-row [data]="healthData.rgw"
-                     link="/rgw/daemon"
-                     title="Object Gateway"
-                     summaryType="simplified"
-                     id="rgw-item"
-                     *ngIf="enabledFeature.rgw && healthData.rgw || healthData.rgw === 0 "></cd-card-row>
+          <!-- Object gateways -->
+          <cd-card-row [data]="rgwCount"
+                       link="/rgw/daemon"
+                       title="Object Gateway"
+                       summaryType="simplified"
+                       id="rgw-item"
+                       *ngIf="enabledFeature.rgw"></cd-card-row>
 
-        <!-- Metadata Servers -->
-        <cd-card-row [data]="healthData.fs_map | mdsSummary"
-                     title="Metadata Server"
-                     id="mds-item"
-                     *ngIf="enabledFeature.cephfs && healthData.fs_map"></cd-card-row>
-        <!-- iSCSI Gateways -->
-        <cd-card-row [data]="healthData.iscsi_daemons"
-                     link="/iscsi/daemon"
-                     title="iSCSI Gateway"
-                     summaryType="iscsi"
-                     id="iscsi-item"
-                     *ngIf="enabledFeature.iscsi && healthData.iscsi_daemons"></cd-card-row>
+          <!-- Metadata Servers -->
+          <cd-card-row [data]="mdsMap | mdsSummary"
+                       title="Metadata Server"
+                       id="mds-item"
+                       *ngIf="enabledFeature.cephfs"></cd-card-row>
+          <!-- iSCSI Gateways -->
+          <cd-card-row [data]="iscsiMap"
+                       link="/iscsi/daemon"
+                       title="iSCSI Gateway"
+                       summaryType="iscsi"
+                       id="iscsi-item"
+                       *ngIf="enabledFeature.iscsi"></cd-card-row>
+        </ng-container>
       </cd-card>
     </div>
 
             </div>
             <div class="d-flex flex-column ms-4 me-4 mt-4 mb-4">
               <div class="d-flex flex-row col-md-3 ms-4">
-                <i  *ngIf="healthData.health?.status"
-                    [ngClass]="[healthData.health.status | healthIcon, icons.large2x]"
-                    [ngStyle]="healthData.health.status | healthColor"
-                    [title]="healthData.health.status">
+                <i  *ngIf="healthData?.status else loadingTpl"
+                    [ngClass]="[healthData.status | healthIcon, icons.large2x]"
+                    [ngStyle]="healthData.status | healthColor"
+                    [title]="healthData.status">
                 </i>
               <span class="ms-2 mt-n1 lead"
-                    *ngIf="!healthData.health?.checks?.length"
+                    *ngIf="!healthData?.checks?.length"
                     i18n>Cluster</span>
               <cds-toggletip  [dropShadow]="true"
                               [autoAlign]="true">
               <div cdsToggletipButton>
                 <a class="ms-2 mt-n1 lead text-primary"
                    popoverClass="info-card-popover-cluster-status"
-                   *ngIf="healthData.health?.checks?.length"
+                   *ngIf="healthData?.checks?.length"
                    i18n>Cluster
                 </a>
               </div>
               <div cdsToggletipContent
                    #healthCheck>
                 <div class="cds--popover-scroll-container">
-                  <cd-health-checks *ngIf="healthData?.health?.checks"
-                                    [healthData]="healthData.health.checks">
+                  <cd-health-checks *ngIf="healthData?.checks"
+                                    [healthData]="healthData.checks">
                   </cd-health-checks>
                 </div>
               </div>
        i18n><i [ngClass]="[icons.infoCircle]"></i> See <a routerLink="/logs">Logs</a> for more details.</p>
   </ng-container>
 </ng-template>
+
+<ng-template #loadingTpl>
+  <cds-inline-loading></cds-inline-loading>
+</ng-template>
index c122d3bc01422b8f0478216a4860b249c1700095..6d17aacceab6acc6b0371c9899ee9b4dc003e556 100644 (file)
@@ -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<any>([]);
   managedByConfig$: Observable<any>;
   private subs = new Subscription();
+  private destroy$ = new Subject<void>();
+
+  hostsCount: number = null;
+  monMap: MonMap = null;
+  mgrMap: MgrMap = null;
+  osdMap: OsdMap = null;
+  poolStatus: Record<string, any>[] = 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();
+      }
+    });
+  }
 }
index a26097ee005084ef474194f849bf7c58042c0147..c12193accd65fbbce33c90d2864348a3ce617da3 100644 (file)
@@ -9,6 +9,7 @@ export class PgSummaryPipe implements PipeTransform {
   constructor(private pgCategoryService: PgCategoryService) {}
 
   transform(value: any): any {
+    if (!value) return null;
     const categoryPgAmount: Record<string, number> = {};
     let total = 0;
     _.forEach(value.statuses, (pgAmount, pgStatesText) => {
index 062f66b113d7c906ac7072a311c392160a650900..f7658732049a314ae965900a3479e7d8b8a52150 100644 (file)
@@ -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;
index 3ed393aa6fed436f1a2b398af759e49fa84e6022..42b4d25e87107282f1adefdd7d72d6aba9faa436 100644 (file)
@@ -2,15 +2,21 @@
 <li class="list-group-item">
   <div class="d-flex pl-1 pb-2 pt-2 position-relative">
     <div class="ms-4 me-auto">
+    @if (link && data !== null || total) {
+    <a [routerLink]="link"
+       *ngIf="link && total > 0; else noLinkTitle"
+       [ngPlural]="total"
+      i18n>
+        {{ total }}
+      <ng-template ngPluralCase="=0">{{ title }}</ng-template>
+      <ng-template ngPluralCase="=1">{{ title }}</ng-template>
+      <ng-template ngPluralCase="other">{{ title }}s</ng-template>
+    </a>
+    } @else {
       <a [routerLink]="link"
-         *ngIf="link && total > 0; else noLinkTitle"
-         [ngPlural]="total"
-        i18n>
-          {{ total }}
-        <ng-template ngPluralCase="=0">{{ title }}</ng-template>
-        <ng-template ngPluralCase="=1">{{ title }}</ng-template>
-        <ng-template ngPluralCase="other">{{ title }}s</ng-template>
+         *ngIf="link; else noLinkTitle">{{title}}
       </a>
+    }
     </div>
     <span class="me-4">
       <ng-container [ngSwitch]="summaryType">
 </div>
 
 <ng-template #defaultSummary>
-  <span *ngIf="data.success || data.categoryPgAmount?.clean || (data.success === 0 && data.total === 0)">
+  @if (data === null) {
+  <ng-container *ngTemplateOutlet="loadingTpl"></ng-container>
+  } @else {
+    <span *ngIf="data.success || data.categoryPgAmount?.clean || (data.success === 0 && data.total === 0)">
     <span *ngIf="data.success || (data.success === 0 && data.total === 0)">
       {{ data.success }}
     </span>
        [ngClass]="[icons.spinner, icons.spin]">
     </i>
   </span>
+  }
 </ng-template>
 
 <ng-template #osdSummary>
+  @if (data === null) {
+  <ng-container *ngTemplateOutlet="loadingTpl"></ng-container>
+  } @else {
   <span *ngIf="data.up === data.in">
     {{ data.up }}
     <cd-icon
       full
     </span>
   </span>
+  }
 </ng-template>
 
 <ng-template #iscsiSummary>
+  @if (data === null || total === null) {
+  <ng-container *ngTemplateOutlet="loadingTpl"></ng-container>
+  } @else {
   <span>
-    {{ data.up }}
+    {{ data?.up }}
     <cd-icon
-       *ngIf="data.up || data.up === 0"
+       *ngIf="data?.up || data?.up === 0"
        type="success">
     </cd-icon >
   </span>
-  <span *ngIf="data.down"
+  <span *ngIf="data?.down"
         class="ms-2">
-        {{ data.down }}
+        {{ data?.down }}
     <cd-icon
        type="danger">
     </cd-icon >
   </span>
+
+  }
 </ng-template>
 
 <ng-template #simplifiedSummary>
+  @if (data === 0 || data) {
   <span *ngIf="!dropdownTotalError else showErrorNum">
     {{ data }}
     <cd-icon
-       type="success"></cd-icon >
+      type="success"></cd-icon >
   </span>
+  } @else {
+    <ng-container *ngTemplateOutlet="loadingTpl"></ng-container>
+  }
   <ng-template #showErrorNum>
     <span *ngIf="data - dropdownTotalError  > 0">
       {{ data - dropdownTotalError  }}
   </ng-template>
 </ng-template>
 
+<ng-template #loadingTpl>
+  <cds-inline-loading></cds-inline-loading>
+</ng-template>
+
 <ng-template #noLinkTitle>
-  <span *ngIf="total || total === 0"
-        [ngPlural]="total">
-    {{ total }}
-    <ng-template ngPluralCase="=0">{{ title }}</ng-template>
-    <ng-template ngPluralCase="=1">{{ title }}</ng-template>
-    <ng-template ngPluralCase="other">{{ title }}s</ng-template>
-  </span>
+@if (data !== null || total) {
+<span *ngIf="total || total === 0"
+      [ngPlural]="total">
+  {{ total }}
+  <ng-template ngPluralCase="=0">{{ title }}</ng-template>
+  <ng-template ngPluralCase="=1">{{ title }}</ng-template>
+  <ng-template ngPluralCase="other">{{ title }}s</ng-template>
+</span>
+} @else {
+  <span>{{ title }}</span>
+}
 </ng-template>
 
 <ng-template #dropdownTemplate>
index d977e905f531c412c948f4b3354cf5301e6c979c..3c577fff2f1ff8b8fc1bf67a8da9cceba6f98232 100644 (file)
@@ -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;
     }
index c52612e01c27095dd39f813133c7d527472b07eb..2859ed0f0c604c55ff4c5e0f7a61259382c333fd 100644 (file)
@@ -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 (file)
index 0000000..22963b5
--- /dev/null
@@ -0,0 +1,49 @@
+export interface MonMap {
+  monmap: {
+    mons: Record<string, any>[];
+  };
+  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[];
+}
index 846cfb0bc5dcb843c5a9727f6a0bf8be55aa8e6c..4081aa26ec005d28790526034feb74e633c9209a 100644 (file)
@@ -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);
   });
 });
index 77758b71d3d572f6bcdeccd172eff2418776bb40..43004fa0c04fd5b34f2acd9527883256e12749a9 100644 (file)
@@ -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;
index ac7dcc63fb9549b1960297a1f7ed9f5bdb622703..5cc83249c276da845ec0a66796b8e834e5c9f9ba 100644 (file)
@@ -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', () => {
index 14b38095210bb80c4285e1027979c33cb385ef68..6e85f0dbcb6eaf3c6598a4f491065a19cdd8e25c 100644 (file)
@@ -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;
index 2c60fa585c70f013e720df2b9b914c0289079dcd..88e457d858b7c7f886fbf1adcc84e482478bd7bb 100644 (file)
@@ -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', () => {
index 66e86970c631975c707200c870584e948e9c6ccc..cf4ea649dd19813b09893568b0d25da9515568ab 100644 (file)
@@ -8,7 +8,7 @@ import _ from 'lodash';
 export class OsdSummaryPipe implements PipeTransform {
   transform(value: any): any {
     if (!value) {
-      return '';
+      return null;
     }
 
     let inCount = 0;