]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Add new landing page component
authorAfreen Misbah <afreen@ibm.com>
Tue, 13 Jan 2026 17:26:34 +0000 (22:56 +0530)
committerAfreen Misbah <afreen@ibm.com>
Fri, 16 Jan 2026 14:24:09 +0000 (19:54 +0530)
Fixes https://tracker.ceph.com/issues/74409

- flagged by DASHBOARD fetaure flag
- added tests
- added layout for overview page
- dropped using get API which is getting polled freqently for perf

Signed-off-by: Afreen Misbah <afreen@ibm.com>
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard/dashboard.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard/dashboard.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard/dashboard.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/overview.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/overview.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/overview.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/overview.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/services/feature-toggles.service.ts
src/pybind/mgr/dashboard/openapi.yaml
src/pybind/mgr/dashboard/plugins/feature_toggles.py

index 56cdf1ea40d0149bd85d47b5761e456d51d4537b..0e400e019941b70f54746b0e3ec82ad957bcb227 100644 (file)
@@ -13,6 +13,7 @@ import { FeedbackComponent } from '../shared/feedback/feedback.component';
 import { DashboardComponent } from './dashboard/dashboard.component';
 import { HealthPieComponent } from './health-pie/health-pie.component';
 import { InputModule, ModalModule, SelectModule, ToggletipModule } from 'carbon-components-angular';
+import { OverviewComponent } from '../overview/overview.component';
 
 @NgModule({
   imports: [
@@ -28,8 +29,10 @@ import { InputModule, ModalModule, SelectModule, ToggletipModule } from 'carbon-
     ToggletipModule,
     ModalModule,
     InputModule,
-    SelectModule
+    SelectModule,
+    OverviewComponent
   ],
+  exports: [OverviewComponent],
   declarations: [DashboardComponent, HealthPieComponent, FeedbackComponent],
   providers: [provideCharts(withDefaultRegisterables())]
 })
index ea7ff283198838edc263549fd8a64f553b7d489f..8066c6b319dbe30cf1a0ce991a138f0b9dd5efde 100644 (file)
@@ -1,8 +1,9 @@
-<main aria-label="Dashboard">
-  <a href="#main"
-     class="sr-only">skip to content</a>
-
-  <ng-container class="main-padding">
-    <cd-dashboard-v3></cd-dashboard-v3>
-  </ng-container>
+<main aria-label="Cluster Overview">
+@if (useDeprecated === true) {
+<!-- OLD OVERVIEW -->
+<cd-dashboard-v3 data-testid="cd-dashboard-v3"></cd-dashboard-v3>
+} @else {
+<!-- NEWER OVERVIEW -->
+<cd-overview data-testid="cd-overview"></cd-overview>
+}
 </main>
index 9c20e4438f11c73048e334ed5b7f1a43fc53e41e..d3b4f5e8bb97b836b7a6fb4ee2386fc08a092970 100644 (file)
@@ -1,6 +1,7 @@
 import { HttpClientTestingModule } from '@angular/common/http/testing';
 import { NO_ERRORS_SCHEMA } from '@angular/core';
 import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
 
 import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
 import { FeatureTogglesService } from '~/app/shared/services/feature-toggles.service';
@@ -11,6 +12,7 @@ import { DashboardComponent } from './dashboard.component';
 describe('DashboardComponent', () => {
   let component: DashboardComponent;
   let fixture: ComponentFixture<DashboardComponent>;
+  let featureTogglesService: FeatureTogglesService;
 
   configureTestBed({
     imports: [NgbNavModule, HttpClientTestingModule],
@@ -20,12 +22,70 @@ describe('DashboardComponent', () => {
   });
 
   beforeEach(() => {
+    featureTogglesService = TestBed.inject(FeatureTogglesService);
     fixture = TestBed.createComponent(DashboardComponent);
     component = fixture.componentInstance;
-    fixture.detectChanges();
   });
 
   it('should create', () => {
+    spyOn(featureTogglesService, 'isFeatureEnabled').and.returnValue(true);
+
+    fixture.detectChanges();
     expect(component).toBeTruthy();
   });
+
+  it('should call featureTogglesService.isFeatureEnabled() on initialization', () => {
+    const spy = spyOn(featureTogglesService, 'isFeatureEnabled').and.returnValue(true);
+    fixture.detectChanges();
+    expect(spy).toHaveBeenCalled();
+    expect(spy).toHaveBeenCalledWith('dashboard');
+  });
+
+  it('should set useDeprecated based on feature toggle', () => {
+    spyOn(featureTogglesService, 'isFeatureEnabled').and.returnValue(true);
+    fixture.detectChanges();
+    expect(component.useDeprecated).toBe(true);
+  });
+
+  describe('when dashboard feature is enabled (new dashboard)', () => {
+    beforeEach(() => {
+      spyOn(featureTogglesService, 'isFeatureEnabled').and.returnValue(true);
+      fixture.detectChanges();
+    });
+
+    it('should show cd-dashboard-v3 in template when dashboard feature is enabled', () => {
+      const overviewElement = fixture.debugElement.query(By.css('[data-testid="cd-overview"]'));
+      const dashboardV3Element = fixture.debugElement.query(
+        By.css('[data-testid="cd-dashboard-v3"]')
+      );
+
+      expect(overviewElement).toBeNull();
+      expect(dashboardV3Element).toBeTruthy();
+    });
+
+    it('should set useDeprecated to false when feature is enabled', () => {
+      expect(component.useDeprecated).toBe(true);
+    });
+  });
+
+  describe('when dashboard feature is disabled (old dashboard)', () => {
+    beforeEach(() => {
+      spyOn(featureTogglesService, 'isFeatureEnabled').and.returnValue(false);
+      fixture.detectChanges();
+    });
+
+    it('should show cd-overview in template when dashboard feature is disabled', () => {
+      const overviewElement = fixture.debugElement.query(By.css('[data-testid="cd-overview"]'));
+      const dashboardV3Element = fixture.debugElement.query(
+        By.css('[data-testid="cd-dashboard-v3"]')
+      );
+
+      expect(overviewElement).toBeTruthy();
+      expect(dashboardV3Element).toBeNull();
+    });
+
+    it('should set useDeprecated to true when feature is disabled', () => {
+      expect(component.useDeprecated).toBe(false);
+    });
+  });
 });
index e67e6bb952fc6e47fe5f0167de19be58acea3aa5..559049888ecace6b2789a2880b4c5bb01ed52241 100644 (file)
@@ -1,4 +1,5 @@
-import { Component } from '@angular/core';
+import { Component, inject, OnInit } from '@angular/core';
+import { FeatureTogglesService } from '~/app/shared/services/feature-toggles.service';
 
 @Component({
   selector: 'cd-dashboard',
@@ -6,4 +7,12 @@ import { Component } from '@angular/core';
   styleUrls: ['./dashboard.component.scss'],
   standalone: false
 })
-export class DashboardComponent {}
+export class DashboardComponent implements OnInit {
+  useDeprecated: boolean = true;
+
+  private featureToggles = inject(FeatureTogglesService);
+
+  ngOnInit() {
+    this.useDeprecated = this.featureToggles.isFeatureEnabled('dashboard');
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/overview.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/overview.component.html
new file mode 100644 (file)
index 0000000..d736a3d
--- /dev/null
@@ -0,0 +1,36 @@
+<div cdsGrid
+     [narrow]="true"
+     [condensed]="false"
+     [fullWidth]="true"
+     class="overview">
+  <div cdsRow
+       [narrow]="true"
+       class="overview-row">
+    <div cdsCol
+         [columnNumbers]="{md: 16, lg: 11}">
+      <cds-tile>Health card</cds-tile>
+    </div>
+    <div cdsCol
+         [columnNumbers]="{md: 16, lg: 5}">
+      <cds-tile>Alerts card</cds-tile>
+    </div>
+  </div>
+  <div cdsRow
+       [narrow]="true"
+       class="overview-row">
+    <div cdsCol
+         [columnNumbers]="{md: 16, lg: 11}">
+      <cds-tile>Storage card</cds-tile>
+    </div>
+    <div cdsCol
+         [columnNumbers]="{md: 16, lg: 5}">
+      <cds-tile>Docs card</cds-tile>
+    </div>
+  </div>
+  <div cdsRow>
+    <div cdsCol
+         [columnNumbers]="{md: 16, lg: 16}">
+      <cds-tile>Performance card</cds-tile>
+    </div>
+  </div>
+</div>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/overview.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/overview.component.scss
new file mode 100644 (file)
index 0000000..a84b2ba
--- /dev/null
@@ -0,0 +1,8 @@
+.overview {
+  margin-top: var(--cds-spacing-05);
+  margin-bottom: var(--cds-spacing-05);
+}
+
+.overview-row {
+  margin-bottom: var(--cds-spacing-05);
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/overview.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/overview.component.spec.ts
new file mode 100644 (file)
index 0000000..2774459
--- /dev/null
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { OverviewComponent } from './overview.component';
+
+describe('OverviewComponent', () => {
+  let component: OverviewComponent;
+  let fixture: ComponentFixture<OverviewComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [OverviewComponent]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(OverviewComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/overview.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/overview.component.ts
new file mode 100644 (file)
index 0000000..4ba2a89
--- /dev/null
@@ -0,0 +1,11 @@
+import { Component } from '@angular/core';
+import { GridModule, TilesModule } from 'carbon-components-angular';
+
+@Component({
+  selector: 'cd-overview',
+  imports: [GridModule, TilesModule],
+  standalone: true,
+  templateUrl: './overview.component.html',
+  styleUrl: './overview.component.scss'
+})
+export class OverviewComponent {}
index bb7f2a0d6145386a8b2249d99b501a84c542242c..3639f8c79caa0b0f471a036dfe7db467b2bd97f9 100644 (file)
@@ -12,6 +12,7 @@ export class FeatureTogglesMap {
   cephfs = true;
   rgw = true;
   nfs = true;
+  dashboard = true;
 }
 export type Features = keyof FeatureTogglesMap;
 export type FeatureTogglesMap$ = Observable<FeatureTogglesMap>;
@@ -34,4 +35,8 @@ export class FeatureTogglesService {
   get(): FeatureTogglesMap$ {
     return this.featureToggleMap$;
   }
+
+  isFeatureEnabled(feature: string): boolean {
+    return this.http.get<FeatureTogglesMap>(this.API_URL)?.[feature];
+  }
 }
index cf32d3396b13527a3809e4c423798631f5320b16..e7d9677ee486ee432f366023d31df911e518a230 100755 (executable)
@@ -5514,6 +5514,9 @@ paths:
                   cephfs:
                     description: ''
                     type: boolean
+                  dashboard:
+                    description: ''
+                    type: boolean
                   iscsi:
                     description: ''
                     type: boolean
@@ -5536,6 +5539,7 @@ paths:
                 - cephfs
                 - rgw
                 - nfs
+                - dashboard
                 type: object
           description: OK
         '400':
index 8b6f7180dfc27d64c4b8f9d104ebcbe69938a87a..6ff3931bd1479a454286cf7ee70a66492c090963 100644 (file)
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 
 from enum import Enum
-from typing import Dict, List, Optional, Set, no_type_check
+from typing import Dict, List, Optional, Set
 
 import cherrypy
 from mgr_module import CLICommand, Option
@@ -26,6 +26,7 @@ class Features(Enum):
     CEPHFS = 'cephfs'
     RGW = 'rgw'
     NFS = 'nfs'
+    DASHBOARD = 'dashboard'
 
     # if we want to add any custom warning message when enabling a feature
     # we can add it here as key-value pair in warn_msg.
@@ -114,7 +115,6 @@ class FeatureToggles(I.CanMgr, I.Setupable, I.HasOptions,
             return ret, '\n'.join(msg), ''
         return {'handle_command': cmd}
 
-    @no_type_check  # https://github.com/python/mypy/issues/7806
     def _get_feature_from_request(self, request):
         try:
             return self.Controller2Feature[
@@ -123,7 +123,6 @@ class FeatureToggles(I.CanMgr, I.Setupable, I.HasOptions,
             return None
 
     @ttl_cache(ttl=CACHE_TTL, maxsize=CACHE_MAX_SIZE)
-    @no_type_check  # https://github.com/python/mypy/issues/7806
     def _is_feature_enabled(self, feature):
         return self.mgr.get_module_option(self.OPTION_FMT.format(feature))
 
@@ -152,6 +151,7 @@ class FeatureToggles(I.CanMgr, I.Setupable, I.HasOptions,
             "cephfs": (bool, ''),
             "rgw": (bool, ''),
             "nfs": (bool, ''),
+            "dashboard": (bool, '')
         }
 
         @APIRouter('/feature_toggles')