From: Pedro Gonzalez Gomez Date: Wed, 28 Jan 2026 11:44:09 +0000 (+0100) Subject: mgr/dashboard: add page header component X-Git-Tag: testing/wip-vshankar-testing-20260212.053105~10^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=cf436c5048cb7f67c23b46b5bd86660fda5c38b4;p=ceph-ci.git mgr/dashboard: add page header component Adds page header component and applies it to the CephFS Mirroring list Fixes: https://tracker.ceph.com/issues/74626 Signed-off-by: Pedro Gonzalez Gomez --- diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/page-header.e2e-spec.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/page-header.e2e-spec.ts new file mode 100644 index 00000000000..9fb83fa4cec --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/page-header.e2e-spec.ts @@ -0,0 +1,26 @@ +import { PageHeaderPageHelper } from './page-header.po'; + +describe('Page header component', () => { + const pageHeader = new PageHeaderPageHelper(); + + beforeEach(() => { + cy.login(); + pageHeader.navigateToCephfsMirroring(); + }); + + it('should display the page header on CephFS Mirroring page', () => { + pageHeader.getPageHeader().should('be.visible'); + }); + + it('should show the expected title in the page header', () => { + pageHeader.getHeaderTitle().then((text) => { + expect(text.trim()).to.equal('CephFS Mirroring'); + }); + }); + + it('should show the expected description in the page header', () => { + pageHeader.getHeaderDescription().then((text) => { + expect(text.trim()).to.equal('Centralised view of all CephFS Mirroring relationships.'); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/page-header.po.ts b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/page-header.po.ts new file mode 100644 index 00000000000..b04b98f1ec7 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/page-header.po.ts @@ -0,0 +1,26 @@ +import { PageHelper } from '../page-helper.po'; + +const pages = { + cephfsMirroring: { url: '#/cephfs/mirroring', id: 'cd-cephfs-mirroring-list' } +}; + +export class PageHeaderPageHelper extends PageHelper { + pages = pages; + + navigateToCephfsMirroring() { + cy.visit(pages.cephfsMirroring.url); + cy.get(pages.cephfsMirroring.id, { timeout: 10000 }); + } + + getPageHeader() { + return cy.get('[data-testid="page-header"]'); + } + + getHeaderTitle() { + return this.getPageHeader().find('.cds--type-heading-04').invoke('text'); + } + + getHeaderDescription() { + return this.getPageHeader().find('.cds--type-body-01').invoke('text'); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mirroring-list/cephfs-mirroring-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mirroring-list/cephfs-mirroring-list.component.html index 848dc1dc57d..511263648e9 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mirroring-list/cephfs-mirroring-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mirroring-list/cephfs-mirroring-list.component.html @@ -1,3 +1,10 @@ + + + +
+ +

{{ title }}

+ @if(description) { +

{{ description }}

+ } +
+
+ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/page-header/page-header.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/page-header/page-header.component.scss new file mode 100644 index 00000000000..938aec43d33 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/page-header/page-header.component.scss @@ -0,0 +1,17 @@ +@use '../../../../styles/vendor/variables' as vv; + +cd-page-header { + /* Extend to viewport edges so top border goes full width */ + .border-top.padding-inline-0 { + margin-left: calc(-1 * vv.$grid-gutter-width); + margin-right: calc(-1 * vv.$grid-gutter-width); + width: calc(100% + 2 * vv.$grid-gutter-width); + + /* Restore horizontal padding so content stays aligned with rest of page */ + padding-inline: vv.$grid-gutter-width; + } + + .border-top { + border-top: 1px solid var(--cds-border-subtle-01); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/page-header/page-header.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/page-header/page-header.component.spec.ts new file mode 100644 index 00000000000..941d6a346f7 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/page-header/page-header.component.spec.ts @@ -0,0 +1,24 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageHeaderComponent } from './page-header.component'; + +describe('PageHeaderComponent', () => { + let component: PageHeaderComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [PageHeaderComponent], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + + fixture = TestBed.createComponent(PageHeaderComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/page-header/page-header.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/page-header/page-header.component.ts new file mode 100644 index 00000000000..cb2ac7d3fdb --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/page-header/page-header.component.ts @@ -0,0 +1,21 @@ +import { Component, Input, ViewEncapsulation } from '@angular/core'; + +/** + * Page header component inspired by the Carbon Design System Page Header react component. + * @see https://ibm-products.carbondesignsystem.com/?path=/docs/components-pageheader--overview + * + * Usage: + * + * + */ +@Component({ + selector: 'cd-page-header', + templateUrl: './page-header.component.html', + styleUrls: ['./page-header.component.scss'], + encapsulation: ViewEncapsulation.None, + standalone: false +}) +export class PageHeaderComponent { + @Input({ required: true }) title: string; + @Input() description: string = ''; +}