]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Added variations of alerts card sub total layout 67438/head
authorAfreen Misbah <afreen@ibm.com>
Mon, 23 Feb 2026 20:13:43 +0000 (01:43 +0530)
committerAfreen Misbah <afreen@ibm.com>
Mon, 23 Feb 2026 20:43:37 +0000 (02:13 +0530)
- when health card's tab closed the layout is compact
- when health card's tab open the layout take space

Signed-off-by: Afreen Misbah <afreen@ibm.com>
src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/alerts-card/overview-alerts-card.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/alerts-card/overview-alerts-card.component.scss
src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/alerts-card/overview-alerts-card.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/alerts-card/overview-alerts-card.component.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.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/overview.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/overview.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/models/overview.ts

index f500e96e45ab86cb12562166b592f87378a7102f..6b2e703292c6f4e7c103a2676b055826492594be 100644 (file)
@@ -1,5 +1,6 @@
 <cd-productive-card class="overview-alerts-card">
 @if (vm$ | async; as vm) {
+  <!-- HEADER -->
   <ng-template #header>
     <h2 class="cds--type-heading-compact-02"
         i18n>
@@ -14,7 +15,7 @@
       View all
     </button>
   </ng-template>
-
+  <!-- TOTAL COUNT -->
   <div>
     <span class="cds--type-heading-07">{{ vm.total }}</span>
     <cd-icon [type]="vm.icon"></cd-icon>
     {{ vm.statusText }}
   </small>
 
+  <!-- SUB TOTAL COUNTS -->
   <div class="cds-mt-6">
   @if (vm.badges.length) {
-    <div class="overview-alerts-card-badges">
-      @for (b of vm.badges; track b.key; let first = $first) {
+    <div [cdsStack]="compact? 'horizontal' : 'vertical'">
+      @for (b of vm.badges; track b.key; let last = $last) {
       <span
         class="overview-alerts-card-badge"
-        [class.overview-alerts-card-badge-with-border]="!first">
+        [class.overview-alerts-card-badge-with-border--right]="!last && compact"
+        [class.overview-alerts-card-badge-with-border--bottom]="!compact">
         <cd-icon [type]="b.icon"></cd-icon>
         <a
           cdsLink
           class="cds-ml-3"
           [routerLink]="['/monitoring/active-alerts']"
           [queryParams]="{ severity: b.key }">
+          @if(compact) {
           {{ b.count }}
+          } @else {
+           {{b.key | upperFirst}} ({{ b.count }})
+          }
         </a>
       </span>
       }
index 62b4b62aaeeb41ae794317efb3d0e5f589130ac2..39723dfcaaa8761e8f45fb49c6844b720fd1cbe0 100644 (file)
@@ -1,18 +1,17 @@
 .overview-alerts-card {
-  // height: 100%;
-  &-badges {
-    display: flex;
-    align-items: center;
-  }
-
   &-badge {
     display: inline-flex;
     align-items: center;
+  }
+
+  &-badge-with-border--right {
+    border-right: 1px solid var(--cds-border-subtle);
     padding: 0 var(--cds-spacing-04);
   }
 
-  &-badge-with-border {
-    border-left: 1px solid var(--cds-border-subtle);
+  &-badge-with-border--bottom {
+    border-bottom: 1px solid var(--cds-border-subtle);
+    padding-bottom: var(--cds-spacing-03);
   }
 
   &-need-attention {
     color: var(--cds-text-secondary);
   }
 
+  // Override to make alert card take full available height.
+  // Carbon tiles set a min height of 4 rem which was causing alerts card not to stretch with its sibling card.
   .cds--tile {
     min-block-size: 0;
     height: 100%;
   }
+
+  &-badge cd-icon svg {
+    display: block;
+  }
 }
index 5606ffc13885a5b8d0c898a88a65dc704febf71c..57462b52ac60776cdd082deb4ff0aeefc14a2840 100644 (file)
@@ -109,13 +109,15 @@ describe('OverviewAlertsCardComponent', () => {
     fixture.detectChanges();
 
     const badgeEls = Array.from(
-      fixture.nativeElement.querySelectorAll(
-        '.overview-alerts-card-badges .overview-alerts-card-badge'
-      )
+      fixture.nativeElement.querySelectorAll('.overview-alerts-card-badge')
     ) as HTMLElement[];
 
     expect(badgeEls.length).toBe(2);
-    expect(badgeEls[0].classList.contains('overview-alerts-card-badge-with-border')).toBe(false);
-    expect(badgeEls[1].classList.contains('overview-alerts-card-badge-with-border')).toBe(true);
+    expect(badgeEls[0].classList.contains('overview-alerts-card-badge-with-border--right')).toBe(
+      true
+    );
+    expect(badgeEls[1].classList.contains('overview-alerts-card-badge-with-border--right')).toBe(
+      false
+    );
   });
 });
index 78c6e2d19398e073247f9188d85ae2215e014422..bfd11fb5addb1eaad7b84a1faef42c1cebac680c 100644 (file)
@@ -1,6 +1,7 @@
 import {
   ChangeDetectionStrategy,
   Component,
+  Input,
   OnInit,
   ViewEncapsulation,
   inject
@@ -9,11 +10,18 @@ import { CommonModule } from '@angular/common';
 import { combineLatest } from 'rxjs';
 
 import { PrometheusAlertService } from '~/app/shared/services/prometheus-alert.service';
-import { ButtonModule, GridModule, LinkModule, TilesModule } from 'carbon-components-angular';
+import {
+  ButtonModule,
+  GridModule,
+  LayoutModule,
+  LinkModule,
+  TilesModule
+} from 'carbon-components-angular';
 import { RouterModule } from '@angular/router';
 import { ProductiveCardComponent } from '~/app/shared/components/productive-card/productive-card.component';
 import { ComponentsModule } from '~/app/shared/components/components.module';
 import { map, shareReplay, startWith } from 'rxjs/operators';
+import { PipesModule } from '~/app/shared/pipes/pipes.module';
 
 const AlertIcon = {
   error: 'error',
@@ -32,7 +40,9 @@ const AlertIcon = {
     RouterModule,
     ProductiveCardComponent,
     ButtonModule,
-    LinkModule
+    LinkModule,
+    LayoutModule,
+    PipesModule
   ],
   templateUrl: './overview-alerts-card.component.html',
   styleUrl: './overview-alerts-card.component.scss',
@@ -40,6 +50,7 @@ const AlertIcon = {
   encapsulation: ViewEncapsulation.None
 })
 export class OverviewAlertsCardComponent implements OnInit {
+  @Input() compact = true;
   private readonly prometheusAlertService = inject(PrometheusAlertService);
 
   ngOnInit(): void {
index 49372345ae1bf0dffb78e996258993da597caa23..bf1c855b25ec223564459e27b17ec6a6f7105d18 100644 (file)
@@ -26,15 +26,13 @@ import { PipesModule } from '~/app/shared/pipes/pipes.module';
 import { UpgradeInfoInterface } from '~/app/shared/models/upgrade.interface';
 import { UpgradeService } from '~/app/shared/api/upgrade.service';
 import { catchError, filter, map, startWith } from 'rxjs/operators';
-import { HealthCardVM } from '~/app/shared/models/overview';
+import { HealthCardTabSection, HealthCardVM } from '~/app/shared/models/overview';
 
 type OverviewHealthData = {
   summary: Summary;
   upgrade: UpgradeInfoInterface;
 };
 
-type TabSection = 'system' | 'hardware' | 'resiliency';
-
 interface HealthItemConfig {
   key: 'mon' | 'mgr' | 'osd' | 'hosts';
   label: string;
@@ -69,8 +67,9 @@ export class OverviewHealthCardComponent {
 
   @Input({ required: true }) vm!: HealthCardVM;
   @Output() viewIncidents = new EventEmitter<void>();
+  @Output() activeSectionChange = new EventEmitter<HealthCardTabSection | null>();
 
-  activeSection: TabSection | null = null;
+  activeSection: HealthCardTabSection | null = null;
   healthItems: HealthItemConfig[] = [
     { key: 'mon', label: $localize`Monitor` },
     { key: 'mgr', label: $localize`Manager` },
@@ -78,8 +77,9 @@ export class OverviewHealthCardComponent {
     { key: 'hosts', label: $localize`Nodes` }
   ];
 
-  toggleSection(section: TabSection) {
+  toggleSection(section: HealthCardTabSection) {
     this.activeSection = this.activeSection === section ? null : section;
+    this.activeSectionChange.emit(this.activeSection);
   }
 
   readonly data$: Observable<OverviewHealthData> = combineLatest([
index 1e7f9864170e086709506206a0c85950d5fe2ff3..0e912d2ef8a3476567a123c2a04836e721ea32d1 100644 (file)
       <cd-overview-health-card
           [vm]="health"
           (viewIncidents)="togglePanel()"
+          (activeSectionChange)="activeHealthTab = $event"
         ></cd-overview-health-card>
     </div>
     <div cdsCol
          class="cds-mb-5"
          [columnNumbers]="{lg: 5}">
-      <cd-overview-alerts-card></cd-overview-alerts-card>
+      <cd-overview-alerts-card [compact]="!activeHealthTab"></cd-overview-alerts-card>
     </div>
   </div>
   <div cdsRow>
@@ -45,7 +46,7 @@
     (closed)="togglePanel()">
     <div panel-header-description
          class="cds--type-body-01">
-      <span>Health incidents are Ceph health checks warnings indicating conditions that require attention and remain until resolved.</span>
+      <span i18n>Health incidents are Ceph health checks warnings indicating conditions that require attention and remain until resolved.</span>
     </div>
     <div class="panel-content">
       @for (check of health?.checks; track key) {
index c718314637512a3b82da5b5e23c7f5a5cdee8988..ec4e410bc3cb3dd5168afcc0a739cf5b1560816f 100644 (file)
@@ -98,7 +98,7 @@ describe('OverviewComponent', () => {
 
       expect(vm.mon).toEqual(
         expect.objectContaining({
-          value: '3/3',
+          value: 'Quorum: 3/3',
           severity: expect.any(String)
         })
       );
index 1e7cb7da1d1d2eea3d3ef303a77302da6b0a782b..e2ab38b0201def1087872892df5941016860d02e 100644 (file)
@@ -9,6 +9,7 @@ import { RefreshIntervalService } from '~/app/shared/services/refresh-interval.s
 import { HealthCheck, HealthSnapshotMap } from '~/app/shared/models/health.interface';
 import {
   HealthCardCheckVM,
+  HealthCardTabSection,
   HealthCardVM,
   HealthDisplayVM,
   HealthIconMap,
@@ -131,6 +132,7 @@ export function buildHealthCardVM(d: HealthSnapshotMap): HealthCardVM {
 })
 export class OverviewComponent {
   isHealthPanelOpen = false;
+  activeHealthTab: HealthCardTabSection | null = null;
 
   private readonly healthService = inject(HealthService);
   private readonly refreshIntervalService = inject(RefreshIntervalService);
index 73212636396775ca0824a39176654a69a36ce992..3effe7d82c8bfd587e000e984c74c6d64a74561c 100644 (file)
@@ -72,3 +72,5 @@ export interface HealthCardVM {
   osd: HealthCardSubStateVM;
   hosts: HealthCardSubStateVM;
 }
+
+export type HealthCardTabSection = 'system' | 'hardware' | 'resiliency';