From 746d84ecbdec114e982a72770c036749cb3bd3a3 Mon Sep 17 00:00:00 2001 From: Pedro Gonzalez Gomez Date: Thu, 20 Nov 2025 15:09:03 +0100 Subject: [PATCH] mgr/dashboard: Cephfs Mirroring Wizard Fixes: https://tracker.ceph.com/issues/74200 Signed-off-by: Dnyaneshwari Talwekar --- .../frontend/src/app/app-routing.module.ts | 6 ++ .../frontend/src/app/ceph/ceph.module.ts | 4 +- .../cephfs-mirroring-wizard-step.enum.ts | 12 ++++ .../cephfs-mirroring-wizard.component.html | 69 +++++++++++++++++++ .../cephfs-mirroring-wizard.component.scss | 29 ++++++++ .../cephfs-mirroring-wizard.component.spec.ts | 35 ++++++++++ .../cephfs-mirroring-wizard.component.ts | 34 +++++++++ .../cephfs-mirroring-list.component.html | 14 ++-- .../cephfs-mirroring-list.component.ts | 27 +++++++- .../src/app/ceph/cephfs/cephfs.module.ts | 12 +++- 10 files changed, 231 insertions(+), 11 deletions(-) create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs-mirroring/cephfs-mirroring-wizard/cephfs-mirroring-wizard-step.enum.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs-mirroring/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs-mirroring/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs-mirroring/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs-mirroring/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.ts diff --git a/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts index 70a9550f30cb..b06fbabf938f 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts @@ -59,6 +59,7 @@ import { SmbClusterListComponent } from './ceph/smb/smb-cluster-list/smb-cluster import { SmbJoinAuthListComponent } from './ceph/smb/smb-join-auth-list/smb-join-auth-list.component'; import { SmbUsersgroupsListComponent } from './ceph/smb/smb-usersgroups-list/smb-usersgroups-list.component'; import { SmbOverviewComponent } from './ceph/smb/smb-overview/smb-overview.component'; +import { CephfsMirroringWizardComponent } from './ceph/cephfs-mirroring/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component'; import { CephfsMirroringListComponent } from './ceph/cephfs/cephfs-mirroring-list/cephfs-mirroring-list.component'; @Injectable() @@ -412,6 +413,11 @@ const routes: Routes = [ component: CephfsMirroringListComponent, data: { breadcrumbs: 'File/Mirroring' } }, + { + path: `mirroring/${URLVerbs.CREATE}`, + component: CephfsMirroringWizardComponent, + data: { breadcrumbs: ActionLabels.CREATE } + }, { path: 'nfs', canActivateChild: [FeatureTogglesGuardService, ModuleStatusGuardService], diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/ceph.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/ceph.module.ts index d269b6aa912e..700211e68c45 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/ceph.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/ceph.module.ts @@ -8,6 +8,7 @@ import { DashboardModule } from './dashboard/dashboard.module'; import { NfsModule } from './nfs/nfs.module'; import { PerformanceCounterModule } from './performance-counter/performance-counter.module'; import { SmbModule } from './smb/smb.module'; +import { TilesModule } from 'carbon-components-angular'; @NgModule({ imports: [ @@ -18,7 +19,8 @@ import { SmbModule } from './smb/smb.module'; CephfsModule, NfsModule, SmbModule, - SharedModule + SharedModule, + TilesModule ], declarations: [] }) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs-mirroring/cephfs-mirroring-wizard/cephfs-mirroring-wizard-step.enum.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs-mirroring/cephfs-mirroring-wizard/cephfs-mirroring-wizard-step.enum.ts new file mode 100644 index 000000000000..c21358638ad5 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs-mirroring/cephfs-mirroring-wizard/cephfs-mirroring-wizard-step.enum.ts @@ -0,0 +1,12 @@ +export enum StepTitles { + ChooseClusterRole = 'Choose Cluster Role', + SelectFilesystem = 'Select Filesystem', + ImportBootstrapToken = 'Import Bootstrap Token', + Review = 'Review' +} + +export const STEP_TITLES_MIRRORING_CONFIGURED = [ + StepTitles.ChooseClusterRole, + StepTitles.SelectFilesystem, + StepTitles.ImportBootstrapToken +]; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs-mirroring/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs-mirroring/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.html new file mode 100644 index 000000000000..28e988ab5f96 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs-mirroring/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.html @@ -0,0 +1,69 @@ +
+

Configure a new mirroring relationship between clusters

+
+
+
+ +
+
+
+
+
Choose Cluster Role
+

Select how the cluster will participate in the mirroring setup.

+
+ +
+
+ + +
+
Configure as Local Cluster (Source)
+
+ This cluster will send snapshots and replicate data to remote clusters for + backup and disaster recovery. +
+ +
    +
  • + → {{ item }} +
  • +
+
+
+
+
+
+ + +
+
Configure as Remote Cluster (Target)
+
+ This cluster will receive replication data and generate a token for connection + with source clusters. +
+
    +
  • + → {{ item }} +
  • +
+
+
+
+
+
+
+
+
+
+ + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs-mirroring/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs-mirroring/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.scss new file mode 100644 index 000000000000..211ff9f0822e --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs-mirroring/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.scss @@ -0,0 +1,29 @@ +.mirroring-main { + padding-left: 32px; +} + +.mirroring-bg { + background-color: var(--cds-ui-02); + min-height: 100vh; +} + +.mirroring-header { + margin-left: 30px; + margin-top: 20px; +} + +.mirroring-tile-col { + margin-left: 32px; +} + +.mirroring-tile { + margin-bottom: 30px; +} + +.mirroring-list-item { + margin-bottom: 16px; +} + +.mirroring-list { + margin-top: 16px; +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs-mirroring/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs-mirroring/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.spec.ts new file mode 100644 index 000000000000..a277186cd518 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs-mirroring/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.spec.ts @@ -0,0 +1,35 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { SharedModule } from '~/app/shared/shared.module'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { ToastrModule } from 'ngx-toastr'; +import { CephfsMirroringWizardComponent } from './cephfs-mirroring-wizard.component'; + +describe('CephfsMirroringWizardComponent', () => { + let component: CephfsMirroringWizardComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + BrowserAnimationsModule, + SharedModule, + HttpClientTestingModule, + ToastrModule.forRoot(), + RouterTestingModule + ], + declarations: [CephfsMirroringWizardComponent] + }).compileComponents(); + + fixture = TestBed.createComponent(CephfsMirroringWizardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs-mirroring/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs-mirroring/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.ts new file mode 100644 index 000000000000..fe73aa7a28c8 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs-mirroring/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.ts @@ -0,0 +1,34 @@ +import { Component, OnInit } from '@angular/core'; +import { Step } from 'carbon-components-angular'; +import { STEP_TITLES_MIRRORING_CONFIGURED } from './cephfs-mirroring-wizard-step.enum'; +import { Icons } from '~/app/shared/enum/icons.enum'; + +@Component({ + selector: 'cd-cephfs-mirroring-wizard', + templateUrl: './cephfs-mirroring-wizard.component.html', + styleUrls: ['./cephfs-mirroring-wizard.component.scss'] +}) +export class CephfsMirroringWizardComponent implements OnInit { + stepTitles: Step[] = STEP_TITLES_MIRRORING_CONFIGURED.map((title) => ({ + label: title + })); + selectedRole: string = 'source'; + icons = Icons; + sourceList: string[] = [ + 'Sends data to remote clusters', + 'Requires bootstrap token from target', + 'Manages snapshot schedules' + ]; + + targetList: string[] = [ + 'Receives data from source clusters', + 'Generates bootstrap token', + 'Stores replicated snapshots' + ]; + + ngOnInit(): void {} + + selectRole(role: string) { + this.selectedRole = role; + } +} 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 848dc1dc57d7..388b40d47845 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,12 +1,18 @@ + + (fetchData)="loadDaemonStatus()"> + + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mirroring-list/cephfs-mirroring-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mirroring-list/cephfs-mirroring-list.component.ts index 1b3422b997a8..a9d68e7bc2a6 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mirroring-list/cephfs-mirroring-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mirroring-list/cephfs-mirroring-list.component.ts @@ -10,6 +10,10 @@ import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data import { CdTableAction } from '~/app/shared/models/cd-table-action'; import { URLBuilderService } from '~/app/shared/services/url-builder.service'; import { Daemon, MirroringRow } from '~/app/shared/models/cephfs.model'; +import { Icons } from '~/app/shared/enum/icons.enum'; +import { AuthStorageService } from '~/app/shared/services/auth-storage.service'; +import { CdForm } from '~/app/shared/forms/cd-form'; +import { Permission } from '~/app/shared/models/permissions'; export const MIRRORING_PATH = 'cephfs/mirroring'; @Component({ @@ -18,7 +22,7 @@ export const MIRRORING_PATH = 'cephfs/mirroring'; styleUrls: ['./cephfs-mirroring-list.component.scss'], providers: [{ provide: URLBuilderService, useValue: new URLBuilderService(MIRRORING_PATH) }] }) -export class CephfsMirroringListComponent implements OnInit { +export class CephfsMirroringListComponent extends CdForm implements OnInit { @ViewChild('table', { static: true }) table: TableComponent; columns: CdTableColumn[]; @@ -27,8 +31,17 @@ export class CephfsMirroringListComponent implements OnInit { daemonStatus$: Observable; context: CdTableFetchDataContext; tableActions: CdTableAction[]; + permission: Permission; - constructor(public actionLabels: ActionLabelsI18n, private cephfsService: CephfsService) {} + constructor( + public actionLabels: ActionLabelsI18n, + private authStorageService: AuthStorageService, + private cephfsService: CephfsService, + private urlBuilder: URLBuilderService + ) { + super(); + this.permission = this.authStorageService.getPermissions().cephfs; + } ngOnInit() { this.columns = [ @@ -42,6 +55,15 @@ export class CephfsMirroringListComponent implements OnInit { { name: $localize`Snapshot directories`, prop: 'directory_count', flexGrow: 1 } ]; + const createAction: CdTableAction = { + permission: 'create', + icon: Icons.add, + routerLink: () => this.urlBuilder.getCreate(), + name: this.actionLabels.CREATE, + canBePrimary: (selection: CdTableSelection) => !selection.hasSelection + }; + + this.tableActions = [createAction]; this.daemonStatus$ = this.subject$.pipe( switchMap(() => this.cephfsService.listDaemonStatus()?.pipe( @@ -72,7 +94,6 @@ export class CephfsMirroringListComponent implements OnInit { } }); }); - return of(result); }), catchError(() => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs.module.ts index afe99c867cd4..f999d605a0de 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs.module.ts @@ -49,13 +49,16 @@ import { SelectModule, TimePickerModule, TreeviewModule, - TabsModule + TabsModule, + TilesModule, + RadioModule } from 'carbon-components-angular'; import AddIcon from '@carbon/icons/es/add/32'; import LaunchIcon from '@carbon/icons/es/launch/32'; import Close from '@carbon/icons/es/close/32'; import Trash from '@carbon/icons/es/trash-can/32'; +import { CephfsMirroringWizardComponent } from '../cephfs-mirroring/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component'; @NgModule({ imports: [ @@ -87,7 +90,9 @@ import Trash from '@carbon/icons/es/trash-can/32'; ComboBoxModule, IconModule, BaseChartDirective, - TabsModule + TabsModule, + TilesModule, + RadioModule ], declarations: [ CephfsDetailComponent, @@ -108,7 +113,8 @@ import Trash from '@carbon/icons/es/trash-can/32'; CephfsSubvolumeSnapshotsFormComponent, CephfsMountDetailsComponent, CephfsAuthModalComponent, - CephfsMirroringListComponent + CephfsMirroringListComponent, + CephfsMirroringWizardComponent ], providers: [provideCharts(withDefaultRegisterables())] }) -- 2.47.3