]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: add telemetry status to overview-health-card 67950/head
authorPedro Gonzalez Gomez <pegonzal@ibm.com>
Mon, 23 Mar 2026 11:02:29 +0000 (12:02 +0100)
committerPedro Gonzalez Gomez <pegonzal@ibm.com>
Thu, 21 May 2026 10:45:58 +0000 (12:45 +0200)
Fixes: https://tracker.ceph.com/issues/75666
Signed-off-by: Pedro Gonzalez Gomez <pegonzal@ibm.com>
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.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/ceph/overview/overview.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/enum/icons.enum.ts

index a3ea1f11c9ffc2df1e8a7c66fd31fa3d8dc3c52e..0ecd3b5eb518aab6efc0905491826a8990d28709 100644 (file)
@@ -1,6 +1,7 @@
 @let data=(data$ | async);
 @let hwEnabled = (enabled$ | async);
 @let hwSections = (sections$ | async);
+@let telemetryEnabled = (telemetryEnabled$ | async);
 
 @let colorClass="overview-health-card-status--" + vm?.clusterHealth?.icon;
 
         source="fsid">
       </cd-copy-2-clipboard-button>
     </div>
-    <cds-icon-button
-      type="button"
-      kind="ghost"
-      size="sm"
-      description="Check logs"
-      i18n-description
-      [routerLink]="['/logs']">
-      <cd-icon type="dataViewAlt"></cd-icon>
-    </cds-icon-button>
+    <ng-template #telemetryDescription>
+      <div class="cds--type-heading-compact-01 cds-mb-2"
+           i18n>Support & diagnostics</div>
+      <div cdsStack="horizontal"
+           [gap]="10">
+        <span i18n>Telemetry</span>
+        <span>
+          @if (telemetryEnabled) {
+          <cd-icon type="success"></cd-icon>&nbsp;
+          <span i18n>on</span>
+          } @else {
+          <cd-icon type="infoCircle"></cd-icon>&nbsp;
+          <span i18n>off</span>
+          }
+        </span>
+      </div>
+    </ng-template>
+    <div cdsStack="horizontal"
+         [gap]="2">
+      <cds-icon-button
+        type="button"
+        kind="ghost"
+        size="sm"
+        [description]="telemetryDescription">
+        <cd-icon type="cloudMonitoring"></cd-icon>
+      </cds-icon-button>
+      <cds-icon-button
+        type="button"
+        kind="ghost"
+        size="sm"
+        description="Check logs"
+        i18n-description
+        [routerLink]="['/logs']">
+        <cd-icon type="dataViewAlt"></cd-icon>
+      </cds-icon-button>
+    </div>
   </ng-template>
   } @else {
   <cds-skeleton-text
index aa0264a4b9c717fbb1e84340f31420ecc1838dbc..2e1da7f411b6d3a22a8cb3426b404fa4c64a324b 100644 (file)
@@ -11,6 +11,7 @@ import { ComponentsModule } from '~/app/shared/components/components.module';
 import { ProductiveCardComponent } from '~/app/shared/components/productive-card/productive-card.component';
 import { PipesModule } from '~/app/shared/pipes/pipes.module';
 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';
 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
 
@@ -41,6 +42,10 @@ describe('OverviewStorageCardComponent (Jest)', () => {
     getSummary: jest.fn(() => of(null))
   };
 
+  const mockHealthService = {
+    getTelemetryStatus: jest.fn(() => of(false))
+  };
+
   beforeEach(async () => {
     await TestBed.configureTestingModule({
       imports: [
@@ -60,6 +65,7 @@ describe('OverviewStorageCardComponent (Jest)', () => {
         { provide: AuthStorageService, useValue: mockAuthStorageService },
         { provide: MgrModuleService, useValue: mockMgrModuleService },
         { provide: HardwareService, useValue: mockHardwareService },
+        { provide: HealthService, useValue: mockHealthService },
         provideRouter([])
       ]
     }).compileComponents();
index 644353a63a3203403541f9d2c98c27db7a99be7e..7d8855e204e322a1a91aacbe34885572fa3ae795 100644 (file)
@@ -28,6 +28,7 @@ import { UpgradeService } from '~/app/shared/api/upgrade.service';
 import { catchError, filter, map, shareReplay, startWith, switchMap } from 'rxjs/operators';
 import { HealthCardTabSection, HealthCardVM } from '~/app/shared/models/overview';
 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';
 import { RefreshIntervalService } from '~/app/shared/services/refresh-interval.service';
 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
@@ -80,6 +81,7 @@ type HwRowVM = {
 export class OverviewHealthCardComponent {
   private readonly summaryService = inject(SummaryService);
   private readonly upgradeService = inject(UpgradeService);
+  private readonly healthService = inject(HealthService);
   private readonly hardwareService = inject(HardwareService);
   private readonly mgrModuleService = inject(MgrModuleService);
   private readonly refreshIntervalService = inject(RefreshIntervalService);
@@ -159,6 +161,12 @@ export class OverviewHealthCardComponent {
     shareReplay({ bufferSize: 1, refCount: true })
   );
 
+  readonly telemetryEnabled$: Observable<boolean> = this.healthService.getTelemetryStatus().pipe(
+    map((enabled: any) => !!enabled),
+    catchError(() => of(false)),
+    shareReplay({ bufferSize: 1, refCount: true })
+  );
+
   readonly sections$: Observable<HwRowVM[][] | null> = this.hardwareRows$.pipe(
     map((rows) => {
       if (!rows) return null;
index 992f68855d759a79b3175fef46f3488b6dd80d8f..e33fa06683518706905ebc6c7f8fbb9c16fbd976 100644 (file)
@@ -25,7 +25,7 @@ describe('OverviewComponent', () => {
   let component: OverviewComponent;
   let fixture: ComponentFixture<OverviewComponent>;
 
-  let mockHealthService: { getHealthSnapshot: jest.Mock };
+  let mockHealthService: { getHealthSnapshot: jest.Mock; getTelemetryStatus: jest.Mock };
   let mockRefreshIntervalService: { intervalData$: Subject<void> };
   let mockOverviewStorageService: {
     getTrendData: jest.Mock;
@@ -61,7 +61,10 @@ describe('OverviewComponent', () => {
       refreshPrometheusUsable: jest.fn().mockReturnValue(of(true))
     };
 
-    mockHealthService = { getHealthSnapshot: jest.fn() };
+    mockHealthService = {
+      getHealthSnapshot: jest.fn(),
+      getTelemetryStatus: jest.fn().mockReturnValue(of(false))
+    };
     mockRefreshIntervalService = { intervalData$: new Subject<void>() };
 
     mockOverviewStorageService = {
index 899d85c667f0f9aee77d0e907886e3bf0adaa560..5fbd0930b45f01fd50c3e72e508d4c49dd608165 100644 (file)
@@ -131,6 +131,7 @@ import CaretRight16 from '@carbon/icons/es/caret--right/16';
 import Locked16 from '@carbon/icons/es/locked/16';
 import WebServicesCluster20 from '@carbon/icons/es/web-services--cluster/20';
 import WebServicesCluster32 from '@carbon/icons/es/web-services--cluster/32';
+import CloudMonitoring16 from '@carbon/icons/es/cloud--monitoring/16';
 
 import { TearsheetStepComponent } from './tearsheet-step/tearsheet-step.component';
 import { PageHeaderComponent } from './page-header/page-header.component';
@@ -321,7 +322,8 @@ export class ComponentsModule {
       CaretRight16,
       Locked16,
       WebServicesCluster20,
-      WebServicesCluster32
+      WebServicesCluster32,
+      CloudMonitoring16
     ]);
   }
 }
index d99e4664c9a4572d0a3ce6422d35192d7ba99e2e..60de24cc8ed41bb96e6ccaae68df443ec946ae05 100644 (file)
@@ -123,7 +123,8 @@ export enum Icons {
   arrowUpRight = 'arrow--up-right',
   inProgress = 'in-progress',
   arrowDown = 'arrow--down',
-  locked = 'locked' // Access denied, locked state
+  locked = 'locked', // Access denied, locked state
+  cloudMonitoring = 'cloud--monitoring'
 }
 
 export enum IconSize {
@@ -172,7 +173,8 @@ export const ICON_TYPE = {
   angleDoubleRight: 'chevron--right',
   leftArrow: 'caret--left',
   rightArrow: 'caret--right',
-  locked: 'locked'
+  locked: 'locked',
+  cloudMonitoring: 'cloud--monitoring'
 } as const;
 
 export const EMPTY_STATE_IMAGE = {