]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr/dashboard: add page header component
authorPedro Gonzalez Gomez <pegonzal@ibm.com>
Wed, 28 Jan 2026 11:44:09 +0000 (12:44 +0100)
committerPedro Gonzalez Gomez <pegonzal@ibm.com>
Tue, 10 Feb 2026 09:05:46 +0000 (10:05 +0100)
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 <pegonzal@ibm.com>
src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/page-header.e2e-spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/cypress/e2e/ui/page-header.po.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mirroring-list/cephfs-mirroring-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/page-header/page-header.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/page-header/page-header.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/page-header/page-header.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/page-header/page-header.component.ts [new file with mode: 0644]

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 (file)
index 0000000..9fb83fa
--- /dev/null
@@ -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 (file)
index 0000000..b04b98f
--- /dev/null
@@ -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');
+  }
+}
index 848dc1dc57d7d8cd4a05cb2ca98eb6422b206b46..511263648e946156ec5924888c3c520e74d690de 100644 (file)
@@ -1,3 +1,10 @@
+<cd-page-header
+  i18n-title
+  title="CephFS Mirroring"
+  i18n-description
+  description="Centralised view of all CephFS Mirroring relationships.">
+</cd-page-header>
+
 <ng-container *ngIf="daemonStatus$ | async as daemonStatus">
   <cd-table
     [data]="daemonStatus"
index 07d3ccea59c30c32a4678da7eaedd9f80f3e4bb3..d6fc52343edfc7360967cbd7c4a4375283766ef3 100644 (file)
@@ -105,6 +105,7 @@ import IdeaIcon from '@carbon/icons/es/idea/20';
 import CloseIcon from '@carbon/icons/es/close/16';
 import { TearsheetStepComponent } from './tearsheet-step/tearsheet-step.component';
 import { ProductiveCardComponent } from './productive-card/productive-card.component';
+import { PageHeaderComponent } from './page-header/page-header.component';
 
 @NgModule({
   imports: [
@@ -202,7 +203,8 @@ import { ProductiveCardComponent } from './productive-card/productive-card.compo
     ToastComponent,
     TearsheetComponent,
     TearsheetStepComponent,
-    ProductiveCardComponent
+    ProductiveCardComponent,
+    PageHeaderComponent
   ],
   providers: [provideCharts(withDefaultRegisterables())],
   exports: [
@@ -248,7 +250,8 @@ import { ProductiveCardComponent } from './productive-card/productive-card.compo
     ToastComponent,
     TearsheetComponent,
     TearsheetStepComponent,
-    ProductiveCardComponent
+    ProductiveCardComponent,
+    PageHeaderComponent
   ]
 })
 export class ComponentsModule {
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/page-header/page-header.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/page-header/page-header.component.html
new file mode 100644 (file)
index 0000000..3b51bd5
--- /dev/null
@@ -0,0 +1,10 @@
+<header data-testid="page-header">
+  <div>
+    <cds-tile class="border-top padding-inline-0">
+      <p class="cds--type-heading-04">{{ title }}</p>
+      @if(description) {
+      <p class="cds--type-body-01">{{ description }}</p>
+      }
+    </cds-tile>
+  </div>
+</header>
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 (file)
index 0000000..938aec4
--- /dev/null
@@ -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 (file)
index 0000000..941d6a3
--- /dev/null
@@ -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<PageHeaderComponent>;
+
+  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 (file)
index 0000000..cb2ac7d
--- /dev/null
@@ -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:
+ * <cd-page-header title="Page title" description="Optional description">
+ * </cd-page-header>
+ */
+@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 = '';
+}