From: Afreen Misbah Date: Tue, 13 Jan 2026 17:26:34 +0000 (+0530) Subject: mgr/dashboard: Add new landing page component X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=9a07fbf6e2848025124458faee6db953f6d91a2b;p=ceph.git mgr/dashboard: Add new landing page component 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 --- diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard.module.ts index 56cdf1ea40d..0e400e01994 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard.module.ts @@ -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())] }) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard/dashboard.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard/dashboard.component.html index ea7ff283198..8066c6b319d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard/dashboard.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard/dashboard.component.html @@ -1,8 +1,9 @@ -
- skip to content - - - - +
+@if (useDeprecated === true) { + + +} @else { + + +}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard/dashboard.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard/dashboard.component.spec.ts index 9c20e4438f1..d3b4f5e8bb9 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard/dashboard.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard/dashboard.component.spec.ts @@ -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; + 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); + }); + }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard/dashboard.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard/dashboard.component.ts index e67e6bb952f..559049888ec 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard/dashboard.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/dashboard/dashboard.component.ts @@ -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 index 00000000000..d736a3df24b --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/overview.component.html @@ -0,0 +1,36 @@ +
+
+
+ Health card +
+
+ Alerts card +
+
+
+
+ Storage card +
+
+ Docs card +
+
+
+
+ Performance card +
+
+
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 index 00000000000..a84b2ba119c --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/overview.component.scss @@ -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 index 00000000000..2774459b713 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/overview.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { OverviewComponent } from './overview.component'; + +describe('OverviewComponent', () => { + let component: OverviewComponent; + let fixture: ComponentFixture; + + 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 index 00000000000..4ba2a899556 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/overview/overview.component.ts @@ -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 {} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/feature-toggles.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/feature-toggles.service.ts index bb7f2a0d614..3639f8c79ca 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/feature-toggles.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/feature-toggles.service.ts @@ -12,6 +12,7 @@ export class FeatureTogglesMap { cephfs = true; rgw = true; nfs = true; + dashboard = true; } export type Features = keyof FeatureTogglesMap; export type FeatureTogglesMap$ = Observable; @@ -34,4 +35,8 @@ export class FeatureTogglesService { get(): FeatureTogglesMap$ { return this.featureToggleMap$; } + + isFeatureEnabled(feature: string): boolean { + return this.http.get(this.API_URL)?.[feature]; + } } diff --git a/src/pybind/mgr/dashboard/openapi.yaml b/src/pybind/mgr/dashboard/openapi.yaml index cf32d3396b1..e7d9677ee48 100755 --- a/src/pybind/mgr/dashboard/openapi.yaml +++ b/src/pybind/mgr/dashboard/openapi.yaml @@ -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': diff --git a/src/pybind/mgr/dashboard/plugins/feature_toggles.py b/src/pybind/mgr/dashboard/plugins/feature_toggles.py index 8b6f7180dfc..6ff3931bd14 100644 --- a/src/pybind/mgr/dashboard/plugins/feature_toggles.py +++ b/src/pybind/mgr/dashboard/plugins/feature_toggles.py @@ -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')