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()
component: CephfsMirroringListComponent,
data: { breadcrumbs: 'File/Mirroring' }
},
+ {
+ path: `mirroring/${URLVerbs.CREATE}`,
+ component: CephfsMirroringWizardComponent,
+ data: { breadcrumbs: ActionLabels.CREATE }
+ },
{
path: 'nfs',
canActivateChild: [FeatureTogglesGuardService, ModuleStatusGuardService],
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: [
CephfsModule,
NfsModule,
SmbModule,
- SharedModule
+ SharedModule,
+ TilesModule
],
declarations: []
})
--- /dev/null
+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
+];
--- /dev/null
+<div>
+ <p>Configure a new mirroring relationship between clusters</p>
+</div>
+<div cdsRow>
+ <div cdsCol
+ [columnNumbers]="{ lg: 2, md: 3, sm: 4 }">
+ <cd-wizard [stepsTitle]="stepTitles"></cd-wizard>
+ </div>
+ <div cdsCol
+ [columnNumbers]="{ lg: 14, md: 13, sm: 12 }"
+ class="mirroring-main">
+ <div class="mirroring-bg">
+ <div class="mirroring-header">
+ <h5>Choose Cluster Role</h5>
+ <p>Select how the cluster will participate in the mirroring setup.</p>
+ </div>
+ <cds-radio-group name="clusterRole"
+ value="source">
+ <div cdsRow>
+ <div cdsCol
+ [columnNumbers]="{ lg: 6, md: 6, sm: 12 }"
+ class="mirroring-tile-col">
+ <cds-tile class="mirroring-tile">
+ <cds-radio value="source">
+ <div>
+ <h6>Configure as Local Cluster (Source)</h6>
+ <div>
+ This cluster will send snapshots and replicate data to remote clusters for
+ backup and disaster recovery.
+ </div>
+
+ <ul class="mirroring-list">
+ <li class="mirroring-list-item"
+ *ngFor="let item of sourceList">
+ → {{ item }}
+ </li>
+ </ul>
+ </div>
+ </cds-radio>
+ </cds-tile>
+ </div>
+ <div cdsCol
+ [columnNumbers]="{ lg: 6, md: 6, sm: 12 }"
+ class="mirroring-tile-col">
+ <cds-tile class="mirroring-tile">
+ <cds-radio value="target">
+ <div>
+ <h6>Configure as Remote Cluster (Target)</h6>
+ <div>
+ This cluster will receive replication data and generate a token for connection
+ with source clusters.
+ </div>
+ <ul class="mirroring-list">
+ <li class="mirroring-list-item"
+ *ngFor="let item of targetList">
+ → {{ item }}
+ </li>
+ </ul>
+ </div>
+ </cds-radio>
+ </cds-tile>
+ </div>
+ </div>
+ </cds-radio-group>
+ </div>
+ </div>
+</div>
+
+
--- /dev/null
+.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;
+}
--- /dev/null
+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<CephfsMirroringWizardComponent>;
+
+ 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();
+ });
+});
--- /dev/null
+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;
+ }
+}
<ng-container *ngIf="daemonStatus$ | async as daemonStatus">
+
<cd-table
+ #table
[data]="daemonStatus"
[columns]="columns"
+ columnMode="flex"
selectionType="single"
- [hasDetails]="false"
- (setExpandedRow)="setExpandedRow($event)"
- (fetchData)="loadDaemonStatus($event)"
+ identifier="name"
(updateSelection)="updateSelection($event)"
- >
+ (fetchData)="loadDaemonStatus()">
+ <cd-table-actions class="table-actions"
+ [permission]="permission"
+ [selection]="selection"
+ [tableActions]="tableActions">
+ </cd-table-actions>
</cd-table>
</ng-container>
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({
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[];
daemonStatus$: Observable<MirroringRow[]>;
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 = [
{ 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(
}
});
});
-
return of(result);
}),
catchError(() => {
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: [
ComboBoxModule,
IconModule,
BaseChartDirective,
- TabsModule
+ TabsModule,
+ TilesModule,
+ RadioModule
],
declarations: [
CephfsDetailComponent,
CephfsSubvolumeSnapshotsFormComponent,
CephfsMountDetailsComponent,
CephfsAuthModalComponent,
- CephfsMirroringListComponent
+ CephfsMirroringListComponent,
+ CephfsMirroringWizardComponent
],
providers: [provideCharts(withDefaultRegisterables())]
})