]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Cephfs Mirroring Wizard 66616/head
authorPedro Gonzalez Gomez <pegonzal@ibm.com>
Thu, 20 Nov 2025 14:09:03 +0000 (15:09 +0100)
committerDnyaneshwari Talwekar <dtalwekar@li-4c4c4544-0038-3510-8056-b5c04f473234.ibm.com>
Thu, 5 Feb 2026 09:27:01 +0000 (14:57 +0530)
Fixes: https://tracker.ceph.com/issues/74200
Signed-off-by: Dnyaneshwari Talwekar <dtalweka@redhat.com>
13 files changed:
src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/ceph.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mirroring-list/cephfs-mirroring-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mirroring-list/cephfs-mirroring-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mirroring-wizard/cephfs-mirroring-wizard-step.enum.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/tearsheet/tearsheet.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/shared.module.ts
src/pybind/mgr/dashboard/frontend/src/styles/ceph-custom/_spacings.scss

index 34cd097b40a230cb71b1ae4b6eb3a2ac2fa2faa1..32d910a031ec0d2b1fd5dd367487c8261adfff60 100644 (file)
@@ -61,6 +61,7 @@ import { SmbOverviewComponent } from './ceph/smb/smb-overview/smb-overview.compo
 import { MultiClusterFormComponent } from './ceph/cluster/multi-cluster/multi-cluster-form/multi-cluster-form.component';
 import { CephfsMirroringListComponent } from './ceph/cephfs/cephfs-mirroring-list/cephfs-mirroring-list.component';
 import { NotificationsPageComponent } from './core/navigation/notification-panel/notifications-page/notifications-page.component';
+import { CephfsMirroringWizardComponent } from './ceph/cephfs/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component';
 
 @Injectable()
 export class PerformanceCounterBreadcrumbsResolver extends BreadcrumbsResolver {
@@ -429,6 +430,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],
index d269b6aa912eee3f2404b2f70a9324008cbb7479..700211e68c45c07f8989094980be96bace396e9c 100644 (file)
@@ -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: []
 })
index 848dc1dc57d7d8cd4a05cb2ca98eb6422b206b46..4559a4d68af2ea0a39ce720a79b1a4f4ae66bc22 100644 (file)
@@ -1,12 +1,17 @@
 <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)"
     (updateSelection)="updateSelection($event)"
-  >
+    (fetchData)="loadDaemonStatus()">
+  <cd-table-actions class="table-actions"
+                    [permission]="permission"
+                    [selection]="selection"
+                    [tableActions]="tableActions">
+  </cd-table-actions>
   </cd-table>
 </ng-container>
index c207580975d0f70da6d7f3f41e5a09c3cb66860c..7d9e04bb6a38e5b83e3af58aaf71dd1d67b3e32f 100644 (file)
@@ -10,6 +10,9 @@ 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 { Permission } from '~/app/shared/models/permissions';
 
 export const MIRRORING_PATH = 'cephfs/mirroring';
 @Component({
@@ -28,8 +31,16 @@ export class CephfsMirroringListComponent implements OnInit {
   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
+  ) {
+    this.permission = this.authStorageService.getPermissions().cephfs;
+  }
 
   ngOnInit() {
     this.columns = [
@@ -44,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(
@@ -76,7 +96,6 @@ export class CephfsMirroringListComponent implements OnInit {
                 }
               });
             });
-
             return of(result);
           }),
           catchError(() => {
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mirroring-wizard/cephfs-mirroring-wizard-step.enum.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mirroring-wizard/cephfs-mirroring-wizard-step.enum.ts
new file mode 100644 (file)
index 0000000..e8db5f9
--- /dev/null
@@ -0,0 +1,17 @@
+export enum StepTitles {
+  ChooseMirrorPeerRole = 'Choose mirror peer role',
+  SelectFilesystem = 'Select filesystem',
+  CreateOrSelectEntity = 'Create or select entity',
+  GenerateBootstrapToken = 'Generate bootstrap token',
+  Review = 'Review'
+}
+
+export const STEP_TITLES_MIRRORING_CONFIGURED = [
+  StepTitles.ChooseMirrorPeerRole,
+  StepTitles.SelectFilesystem,
+  StepTitles.CreateOrSelectEntity,
+  StepTitles.GenerateBootstrapToken
+];
+
+export const LOCAL_ROLE = 'local';
+export const REMOTE_ROLE = 'remote';
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.html
new file mode 100644 (file)
index 0000000..5ac7760
--- /dev/null
@@ -0,0 +1,111 @@
+<cd-tearsheet
+  [steps]="steps"
+  [title]="title"
+  [description]="description"
+  (submitRequested)="onSubmit()"
+  (closeRequested)="onCancel()">
+  <cd-tearsheet-step>
+    <form [formGroup]="form">
+      <div>
+        <div class="cds--type-heading-03"
+             i18n>Choose mirror peer role</div>
+        <p i18n>Select how the cluster will participate in the CephFS Mirroring relationship.</p>
+      </div>
+
+      <div cdsStack="horizontal">
+        <div class="cds-mr-5">
+          <cds-tile>
+            <cds-radio-group formControlName="localRole">
+              <cds-radio
+                [value]="LOCAL_ROLE"
+                [checked]="form.get('localRole')?.value === LOCAL_ROLE"
+                (click)="onLocalRoleChange()">
+                <div>
+                  <div class="cds--type-heading-compact-02"
+                       i18n>Configure local peer</div>
+                  <div class="cds--type-label-01 cds-mt-3"
+                       i18n>
+                    This cluster will act as the initiating peer and send snapshots to a remote
+                    peer.
+                  </div>
+                  <ul class="cds--type-body-compact-01 cds-mt-6">
+                    @for (item of sourceList; track $index) {
+                    <li class="cds-mb-6 cds-mt-3">&rarr; {{ item }}</li>
+                    }
+                  </ul>
+                </div>
+              </cds-radio>
+            </cds-radio-group>
+          </cds-tile>
+        </div>
+        <cds-tile>
+          <cds-radio-group formControlName="remoteRole">
+            <cds-radio
+              [value]="REMOTE_ROLE"
+              [checked]="form.get('remoteRole')?.value === REMOTE_ROLE"
+              (click)="onRemoteRoleChange()">
+              <div>
+                <div class="cds--type-heading-compact-02"
+                     i18n>Configure remote peer</div>
+                <div class="cds--type-label-01 cds-mt-3"
+                     i18n>
+                  A remote cluster will act as the receiving peer and store replicated snapshots.
+                </div>
+                <ul class="cds--type-body-compact-01 cds-mt-6">
+                  @for (item of targetList; track $index) {
+                  <li class="cds-mb-6 cds-mt-3">&rarr; {{ item }}</li>
+                  }
+                </ul>
+              </div>
+            </cds-radio>
+          </cds-radio-group>
+        </cds-tile>
+      </div>
+
+      @if (form.get('localRole')?.value !== LOCAL_ROLE && showMessage) {
+      <cd-alert-panel
+        type="info"
+        spacingClass="mb-3 mt-3"
+        dismissible="true"
+        (dismissed)="showMessage = false"
+        class="mirroring-alert">
+        <div>
+          <div class="cds--type-heading-compact-01 cds-mb-2"
+               i18n>About Remote Peer Setup</div>
+          <div class="cds--type-body-compact-01 cds-mb-3"
+               i18n>
+            As a remote peer, this cluster prepares to receive mirrored data from an initiating
+            cluster. The setup includes environment validation, enabling filesystem mirroring,
+            creating required Ceph users, and generating a bootstrap token.
+          </div>
+          <div class="cds--type-heading-compact-01 cds-mb-1 cds-mt-6"
+               i18n>What happens next:</div>
+          <ul class="list-disc cds-ml-5 cds--type-body-compact-01">
+            <li i18n>Environment validation</li>
+            <li i18n>Ceph user creation</li>
+            <li i18n>Filesystem mirroring activation</li>
+            <li i18n>Bootstrap token generation</li>
+          </ul>
+        </div>
+      </cd-alert-panel>
+      }
+    </form>
+  </cd-tearsheet-step>
+
+  <!-- Step 1 -->
+  <cd-tearsheet-step>
+    <div>Test 1</div>
+  </cd-tearsheet-step>
+
+  <!-- Step 2 -->
+  <cd-tearsheet-step>
+    <div>Test 2</div>
+  </cd-tearsheet-step>
+
+  <!-- Step 3 -->
+  <cd-tearsheet-step>
+    <div>
+      <p>Test3</p>
+    </div>
+  </cd-tearsheet-step>
+</cd-tearsheet>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.scss
new file mode 100644 (file)
index 0000000..b7a07e3
--- /dev/null
@@ -0,0 +1,7 @@
+:host ::ng-deep cd-alert-panel.mirroring-alert cds-actionable-notification {
+  max-width: 77% !important;
+}
+
+.list-disc {
+  list-style-type: disc;
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.spec.ts
new file mode 100644 (file)
index 0000000..67397af
--- /dev/null
@@ -0,0 +1,101 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { CephfsMirroringWizardComponent } from './cephfs-mirroring-wizard.component';
+import { WizardStepsService } from '~/app/shared/services/wizard-steps.service';
+import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
+import { Router } from '@angular/router';
+import { BehaviorSubject } from 'rxjs';
+import {
+  STEP_TITLES_MIRRORING_CONFIGURED,
+  LOCAL_ROLE,
+  REMOTE_ROLE
+} from './cephfs-mirroring-wizard-step.enum';
+import { WizardStepModel } from '~/app/shared/models/wizard-steps';
+import { NO_ERRORS_SCHEMA } from '@angular/core';
+import { RadioModule } from 'carbon-components-angular';
+
+describe('CephfsMirroringWizardComponent', () => {
+  let component: CephfsMirroringWizardComponent;
+  let fixture: ComponentFixture<CephfsMirroringWizardComponent>;
+  let wizardStepsService: jest.Mocked<WizardStepsService>;
+  let router: jest.Mocked<Router>;
+
+  const mockSteps: WizardStepModel[] = [
+    { stepIndex: 0, isComplete: false },
+    { stepIndex: 1, isComplete: false }
+  ];
+
+  beforeEach(async () => {
+    wizardStepsService = ({
+      setTotalSteps: jest.fn(),
+      setCurrentStep: jest.fn(),
+      steps$: new BehaviorSubject<WizardStepModel[]>(mockSteps)
+    } as unknown) as jest.Mocked<WizardStepsService>;
+
+    router = ({
+      navigate: jest.fn()
+    } as unknown) as jest.Mocked<Router>;
+
+    await TestBed.configureTestingModule({
+      imports: [ReactiveFormsModule, RadioModule],
+      declarations: [CephfsMirroringWizardComponent],
+      providers: [
+        FormBuilder,
+        { provide: WizardStepsService, useValue: wizardStepsService },
+        { provide: Router, useValue: router }
+      ],
+      schemas: [NO_ERRORS_SCHEMA]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(CephfsMirroringWizardComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create the component', () => {
+    expect(component).toBeTruthy();
+  });
+
+  it('should initialize wizard steps on ngOnInit', () => {
+    expect(wizardStepsService.setTotalSteps).toHaveBeenCalledWith(
+      STEP_TITLES_MIRRORING_CONFIGURED.length
+    );
+
+    expect(component.steps.length).toBe(STEP_TITLES_MIRRORING_CONFIGURED.length);
+  });
+
+  it('should navigate to step when goToStep is called', () => {
+    component.goToStep(mockSteps[0]);
+
+    expect(wizardStepsService.setCurrentStep).toHaveBeenCalledWith(mockSteps[0]);
+  });
+
+  it('should initialize form with local role selected', () => {
+    expect(component.form.value).toEqual({
+      localRole: LOCAL_ROLE,
+      remoteRole: null
+    });
+  });
+
+  it('should update form on local role change', () => {
+    component.onLocalRoleChange();
+
+    expect(component.form.value).toEqual({
+      localRole: LOCAL_ROLE,
+      remoteRole: null
+    });
+  });
+
+  it('should update form on remote role change', () => {
+    component.onRemoteRoleChange();
+
+    expect(component.form.value).toEqual({
+      localRole: null,
+      remoteRole: REMOTE_ROLE
+    });
+  });
+
+  it('should navigate to mirroring list on cancel', () => {
+    component.onCancel();
+    expect(router.navigate).toHaveBeenCalledWith(['/cephfs/mirroring']);
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mirroring-wizard/cephfs-mirroring-wizard.component.ts
new file mode 100644 (file)
index 0000000..c247101
--- /dev/null
@@ -0,0 +1,82 @@
+import { Component, OnInit, inject } from '@angular/core';
+import { Step } from 'carbon-components-angular';
+import { Router } from '@angular/router';
+import {
+  STEP_TITLES_MIRRORING_CONFIGURED,
+  LOCAL_ROLE,
+  REMOTE_ROLE
+} from './cephfs-mirroring-wizard-step.enum';
+import { WizardStepsService } from '~/app/shared/services/wizard-steps.service';
+import { WizardStepModel } from '~/app/shared/models/wizard-steps';
+import { FormBuilder, FormGroup } from '@angular/forms';
+@Component({
+  selector: 'cd-cephfs-mirroring-wizard',
+  templateUrl: './cephfs-mirroring-wizard.component.html',
+  standalone: false,
+  styleUrls: ['./cephfs-mirroring-wizard.component.scss']
+})
+export class CephfsMirroringWizardComponent implements OnInit {
+  steps: Step[] = [];
+  title: string = $localize`Create new CephFS Mirroring`;
+  description: string = $localize`Configure a new mirroring relationship between clusters`;
+  form: FormGroup;
+  showMessage: boolean = true;
+
+  LOCAL_ROLE = LOCAL_ROLE;
+  REMOTE_ROLE = REMOTE_ROLE;
+
+  private wizardStepsService = inject(WizardStepsService);
+  private fb = inject(FormBuilder);
+  private router = inject(Router);
+
+  sourceList: string[] = [
+    $localize`Sends data to remote clusters`,
+    $localize`Requires bootstrap token from target`,
+    $localize`Manages snapshot schedules`
+  ];
+
+  targetList: string[] = [
+    $localize`Receives data from source clusters`,
+    $localize`Generates bootstrap token`,
+    $localize`Stores replicated snapshots`
+  ];
+
+  constructor() {
+    this.form = this.fb.group({
+      localRole: [LOCAL_ROLE],
+      remoteRole: [null]
+    });
+  }
+
+  ngOnInit() {
+    this.wizardStepsService.setTotalSteps(STEP_TITLES_MIRRORING_CONFIGURED.length);
+
+    const stepsData = this.wizardStepsService.steps$.value;
+    this.steps = STEP_TITLES_MIRRORING_CONFIGURED.map((title, index) => ({
+      label: title,
+      onClick: () => this.goToStep(stepsData[index])
+    }));
+  }
+
+  goToStep(step: WizardStepModel) {
+    if (step) {
+      this.wizardStepsService.setCurrentStep(step);
+    }
+  }
+
+  onLocalRoleChange() {
+    this.form.patchValue({ localRole: LOCAL_ROLE, remoteRole: null });
+    this.showMessage = false;
+  }
+
+  onRemoteRoleChange() {
+    this.form.patchValue({ localRole: null, remoteRole: REMOTE_ROLE });
+    this.showMessage = true;
+  }
+
+  onSubmit() {}
+
+  onCancel() {
+    this.router.navigate(['/cephfs/mirroring']);
+  }
+}
index afe99c867cd4112e777814db99fdc4d13d090954..548cb4c9821d5edefeb074177ce955b554139bd6 100644 (file)
@@ -31,7 +31,6 @@ import { CephfsSubvolumeSnapshotsFormComponent } from './cephfs-subvolume-snapsh
 import { CephfsSnapshotscheduleFormComponent } from './cephfs-snapshotschedule-form/cephfs-snapshotschedule-form.component';
 import { CephfsMountDetailsComponent } from './cephfs-mount-details/cephfs-mount-details.component';
 import { CephfsAuthModalComponent } from './cephfs-auth-modal/cephfs-auth-modal.component';
-import { CephfsMirroringListComponent } from './cephfs-mirroring-list/cephfs-mirroring-list.component';
 import {
   ButtonModule,
   CheckboxModule,
@@ -49,13 +48,16 @@ import {
   SelectModule,
   TimePickerModule,
   TreeviewModule,
-  TabsModule
+  TabsModule,
+  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 { CephfsMirroringListComponent } from './cephfs-mirroring-list/cephfs-mirroring-list.component';
+import { CephfsMirroringWizardComponent } from './cephfs-mirroring-wizard/cephfs-mirroring-wizard.component';
 
 @NgModule({
   imports: [
@@ -87,7 +89,8 @@ import Trash from '@carbon/icons/es/trash-can/32';
     ComboBoxModule,
     IconModule,
     BaseChartDirective,
-    TabsModule
+    TabsModule,
+    RadioModule
   ],
   declarations: [
     CephfsDetailComponent,
@@ -108,7 +111,8 @@ import Trash from '@carbon/icons/es/trash-can/32';
     CephfsSubvolumeSnapshotsFormComponent,
     CephfsMountDetailsComponent,
     CephfsAuthModalComponent,
-    CephfsMirroringListComponent
+    CephfsMirroringListComponent,
+    CephfsMirroringWizardComponent
   ],
   providers: [provideCharts(withDefaultRegisterables())]
 })
index c2ac38a35ccca324cab64aab2677c0d86898f0a6..e091b203a3d2d181a23ecfa57e3d83207391f9fc 100644 (file)
@@ -63,7 +63,8 @@ export class TearsheetComponent implements OnInit, AfterViewInit, OnDestroy {
   @Input() submitButtonLoadingLabel: string = $localize`Creating`;
   @Input() isSubmitLoading: boolean = true;
 
-  @Output() submitRequested = new EventEmitter<any[]>();
+  @Output() submitRequested = new EventEmitter<void>();
+  @Output() closeRequested = new EventEmitter<void>();
 
   @ContentChildren(TearsheetStepComponent)
   stepContents!: QueryList<TearsheetStepComponent>;
@@ -105,6 +106,7 @@ export class TearsheetComponent implements OnInit, AfterViewInit, OnDestroy {
   }
 
   closeWideTearsheet() {
+    this.closeRequested.emit();
     this.isOpen = false;
     if (this.hasModalOutlet) {
       this.location.back();
index a918ff01d3334e67bb66d43d5b74ae3612d83ef6..97c5da60e5f543b2416fb2926462ce3867a6e3fe 100644 (file)
@@ -17,6 +17,7 @@ import { FormlyObjectTypeComponent } from './forms/crud-form/formly-object-type/
 import { FormlyInputTypeComponent } from './forms/crud-form/formly-input-type/formly-input-type.component';
 import { FormlyTextareaTypeComponent } from './forms/crud-form/formly-textarea-type/formly-textarea-type.component';
 import { BlockUIModule, BlockUIService } from 'ng-block-ui';
+import { TilesModule } from 'carbon-components-angular';
 
 @NgModule({
   imports: [
@@ -39,7 +40,7 @@ import { BlockUIModule, BlockUIService } from 'ng-block-ui';
     BlockUIModule.forRoot()
   ],
   declarations: [FormlyTextareaTypeComponent],
-  exports: [ComponentsModule, PipesModule, DataTableModule, DirectivesModule],
+  exports: [ComponentsModule, PipesModule, DataTableModule, DirectivesModule, TilesModule],
   providers: [AuthStorageService, AuthGuardService, FormatterService, CssHelper, BlockUIService]
 })
 export class SharedModule {}
index 12abe38883520cfd082a8f10d10da1f4623a5475..b4eb98629060b7ceb45283993d1cbdcbe0170522 100644 (file)
   margin-left: layout.$spacing-03;
 }
 
+.cds-ml-5 {
+  margin-left: layout.$spacing-05;
+}
+
 .cds-mr-3 {
   margin-right: layout.$spacing-03;
 }
 
+.cds-mr-5 {
+  margin-right: layout.$spacing-05;
+}
+
+.cds-mb-1 {
+  margin-bottom: layout.$spacing-01;
+}
+
 .cds-mb-2 {
   margin-bottom: layout.$spacing-02;
 }
   margin-bottom: layout.$spacing-05;
 }
 
+.cds-mb-6 {
+  margin-bottom: layout.$spacing-06;
+}
+
+.cds-mt-3 {
+  margin-top: layout.$spacing-03;
+}
+
 .cds-mt-5 {
   margin-top: layout.$spacing-05;
 }
+
+.cds-mt-6 {
+  margin-top: layout.$spacing-06;
+}