From e2f8d35a9d0cebf47a4e235064a479d5e24348ee Mon Sep 17 00:00:00 2001 From: Ivo Almeida Date: Mon, 12 Feb 2024 10:08:09 +0000 Subject: [PATCH] mgr/dashboard: added cephfs mount details Fixes: https://tracker.ceph.com/issues/64405 Signed-off-by: Ivo Almeida --- .../cephfs-list/cephfs-list.component.ts | 39 ++++++++++++++++++- .../cephfs-mount-details.component.html | 38 ++++++++++++++++++ .../cephfs-mount-details.component.scss | 0 .../cephfs-mount-details.component.spec.ts | 30 ++++++++++++++ .../cephfs-mount-details.component.ts | 37 ++++++++++++++++++ ...ephfs-snapshotschedule-list.component.html | 16 ++++---- .../cephfs-snapshotschedule-list.component.ts | 9 ++++- .../cephfs-subvolume-list.component.ts | 29 +++++++++++++- .../src/app/ceph/cephfs/cephfs.module.ts | 4 +- .../src/app/shared/api/cephfs.service.ts | 4 ++ .../code-block/code-block.component.html | 8 ++-- .../code-block/code-block.component.scss | 4 +- .../code-block/code-block.component.ts | 6 +++ .../copy2clipboard-button.component.ts | 2 +- .../src/app/shared/constants/app.constants.ts | 3 ++ 15 files changed, 208 insertions(+), 21 deletions(-) create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mount-details/cephfs-mount-details.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mount-details/cephfs-mount-details.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mount-details/cephfs-mount-details.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mount-details/cephfs-mount-details.component.ts diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-list/cephfs-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-list/cephfs-list.component.ts index 26e79727c12d1..0943ed82574ea 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-list/cephfs-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-list/cephfs-list.component.ts @@ -21,6 +21,10 @@ import { ModalService } from '~/app/shared/services/modal.service'; import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service'; import { FinishedTask } from '~/app/shared/models/finished-task'; import { NotificationService } from '~/app/shared/services/notification.service'; +import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; +import { CephfsMountDetailsComponent } from '../cephfs-mount-details/cephfs-mount-details.component'; +import { map, switchMap } from 'rxjs/operators'; +import { HealthService } from '~/app/shared/api/health.service'; const BASE_URL = 'cephfs'; @@ -38,6 +42,7 @@ export class CephfsListComponent extends ListWithDetails implements OnInit { permissions: Permissions; icons = Icons; monAllowPoolDelete = false; + modalRef!: NgbModalRef; constructor( private authStorageService: AuthStorageService, @@ -48,7 +53,8 @@ export class CephfsListComponent extends ListWithDetails implements OnInit { private configurationService: ConfigurationService, private modalService: ModalService, private taskWrapper: TaskWrapperService, - public notificationService: NotificationService + public notificationService: NotificationService, + private healthService: HealthService ) { super(); this.permissions = this.authStorageService.getPermissions(); @@ -89,6 +95,13 @@ export class CephfsListComponent extends ListWithDetails implements OnInit { click: () => this.router.navigate([this.urlBuilder.getEdit(String(this.selection.first().id))]) }, + { + name: this.actionLabels.ATTACH, + permission: 'read', + icon: Icons.bars, + disable: () => !this.selection?.hasSelection, + click: () => this.showAttachInfo() + }, { permission: 'delete', icon: Icons.destroy, @@ -125,6 +138,30 @@ export class CephfsListComponent extends ListWithDetails implements OnInit { this.selection = selection; } + showAttachInfo() { + const selectedFileSystem = this.selection?.selected?.[0]; + + this.cephfsService + .getFsRootDirectory(selectedFileSystem.id) + .pipe( + switchMap((fsData) => + this.healthService.getClusterFsid().pipe(map((data) => ({ clusterId: data, fs: fsData }))) + ) + ) + .subscribe({ + next: (val) => { + this.modalRef = this.modalService.show(CephfsMountDetailsComponent, { + onSubmit: () => this.modalRef.close(), + mountData: { + fsId: val.clusterId, + fsName: selectedFileSystem?.mdsmap?.fs_name, + rootPath: val.fs['path'] + } + }); + } + }); + } + removeVolumeModal() { const volName = this.selection.first().mdsmap['fs_name']; this.modalService.show(CriticalConfirmationModalComponent, { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mount-details/cephfs-mount-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mount-details/cephfs-mount-details.component.html new file mode 100644 index 0000000000000..a8c30afb1ebae --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mount-details/cephfs-mount-details.component.html @@ -0,0 +1,38 @@ + + + Attach commands + + + + + + + + + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mount-details/cephfs-mount-details.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mount-details/cephfs-mount-details.component.scss new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mount-details/cephfs-mount-details.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mount-details/cephfs-mount-details.component.spec.ts new file mode 100644 index 0000000000000..141ae428bdd76 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mount-details/cephfs-mount-details.component.spec.ts @@ -0,0 +1,30 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CephfsMountDetailsComponent } from './cephfs-mount-details.component'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { SharedModule } from '~/app/shared/shared.module'; +import { ToastrModule } from 'ngx-toastr'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { configureTestBed } from '~/testing/unit-test-helper'; + +describe('CephfsSnapshotscheduleListComponent', () => { + let component: CephfsMountDetailsComponent; + let fixture: ComponentFixture; + + configureTestBed({ + declarations: [CephfsMountDetailsComponent], + imports: [HttpClientTestingModule, SharedModule, ToastrModule.forRoot(), RouterTestingModule], + providers: [NgbActiveModal] + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CephfsMountDetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mount-details/cephfs-mount-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mount-details/cephfs-mount-details.component.ts new file mode 100644 index 0000000000000..77a3f4afadc6d --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mount-details/cephfs-mount-details.component.ts @@ -0,0 +1,37 @@ +import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; + +@Component({ + selector: 'cd-cephfs-mount-details', + templateUrl: './cephfs-mount-details.component.html', + styleUrls: ['./cephfs-mount-details.component.scss'] +}) +export class CephfsMountDetailsComponent implements OnInit, OnDestroy { + @ViewChild('mountDetailsTpl', { static: true }) + mountDetailsTpl: any; + onCancel?: Function; + private canceled = false; + private MOUNT_DIRECTORY = ''; + mountData!: Record; + constructor(public activeModal: NgbActiveModal) {} + mount!: string; + fuse!: string; + nfs!: string; + + ngOnInit(): void { + this.mount = `sudo @${this.mountData?.fsId}.${this.mountData?.fsName}=${this.mountData?.rootPath} ${this.MOUNT_DIRECTORY}`; + this.fuse = `sudo ceph-fuse ${this.MOUNT_DIRECTORY} -r ${this.mountData?.rootPath} --client_mds_namespace=${this.mountData?.fsName}`; + this.nfs = `sudo mount -t nfs -o port= :${this.mountData?.rootPath} ${this.MOUNT_DIRECTORY}`; + } + + ngOnDestroy(): void { + if (this.onCancel && this.canceled) { + this.onCancel(); + } + } + + cancel() { + this.canceled = true; + this.activeModal.close(); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.html index f26f63e755a6e..4142724f7cfe6 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.html @@ -21,14 +21,14 @@ class="fw-bold" [ngbTooltip]="fullpathTpl" triggers="click:blur"> - {{ row.path?.split?.("@")?.[0] | path }} + {{ row.pathForSelection?.split?.("@")?.[0] | path }} @@ -37,19 +37,19 @@ - + {{ row.path?.split?.("@")?.[0] }} + >{{ row.pathForSelection?.split?.("@")?.[0] }} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.ts index 581ee6e2fa3ae..ecc2036d5dd3a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.ts @@ -130,7 +130,11 @@ export class CephfsSnapshotscheduleListComponent } return this.snapshotScheduleService .getSnapshotScheduleList('/', this.fsName) - .pipe(map((list) => list.map((l) => ({ ...l, path: `${l.path}@${l.schedule}` })))); + .pipe( + map((list) => + list.map((l) => ({ ...l, pathForSelection: `${l.path}@${l.schedule}` })) + ) + ); }), shareReplay(1) ) @@ -138,7 +142,8 @@ export class CephfsSnapshotscheduleListComponent ); this.columns = [ - { prop: 'path', name: $localize`Path`, flexGrow: 3, cellTemplate: this.pathTpl }, + { prop: 'pathForSelection', name: $localize`Path`, flexGrow: 3, cellTemplate: this.pathTpl }, + { prop: 'path', isHidden: true }, { prop: 'subvol', name: $localize`Subvolume`, cellTemplate: this.subvolTpl }, { prop: 'scheduleCopy', name: $localize`Repeat interval` }, { prop: 'schedule', isHidden: true }, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-list/cephfs-subvolume-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-list/cephfs-subvolume-list.component.ts index 92c139f8e5dd5..58d849c901ef5 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-list/cephfs-subvolume-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-list/cephfs-subvolume-list.component.ts @@ -31,6 +31,8 @@ import { CdForm } from '~/app/shared/forms/cd-form'; import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component'; import { CephfsSubvolumeGroupService } from '~/app/shared/api/cephfs-subvolume-group.service'; import { CephfsSubvolumeGroup } from '~/app/shared/models/cephfs-subvolume-group.model'; +import { CephfsMountDetailsComponent } from '../cephfs-mount-details/cephfs-mount-details.component'; +import { HealthService } from '~/app/shared/api/health.service'; @Component({ selector: 'cd-cephfs-subvolume-list', @@ -86,7 +88,8 @@ export class CephfsSubvolumeListComponent extends CdForm implements OnInit, OnCh private modalService: ModalService, private authStorageService: AuthStorageService, private taskWrapper: TaskWrapperService, - private cephfsSubvolumeGroupService: CephfsSubvolumeGroupService + private cephfsSubvolumeGroupService: CephfsSubvolumeGroupService, + private healthService: HealthService ) { super(); this.permissions = this.authStorageService.getPermissions(); @@ -149,6 +152,13 @@ export class CephfsSubvolumeListComponent extends CdForm implements OnInit, OnCh icon: Icons.edit, click: () => this.openModal(true) }, + { + name: this.actionLabels.ATTACH, + permission: 'read', + icon: Icons.bars, + disable: () => !this.selection?.hasSelection, + click: () => this.showAttachInfo() + }, { name: this.actionLabels.REMOVE, permission: 'delete', @@ -188,6 +198,23 @@ export class CephfsSubvolumeListComponent extends CdForm implements OnInit, OnCh this.selection = selection; } + showAttachInfo() { + const selectedSubVolume = this.selection?.selected?.[0]; + + this.healthService.getClusterFsid().subscribe({ + next: (clusterId: string) => { + this.modalRef = this.modalService.show(CephfsMountDetailsComponent, { + onSubmit: () => this.modalRef.close(), + mountData: { + fsId: clusterId, + fsName: this.fsName, + rootPath: selectedSubVolume.info.path + } + }); + } + }); + } + openModal(edit = false) { this.modalService.show( CephfsSubvolumeFormComponent, 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 687dd0b93ee93..14481d8382241 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 @@ -30,6 +30,7 @@ import { CephfsSnapshotscheduleListComponent } from './cephfs-snapshotschedule-l import { DataTableModule } from '../../shared/datatable/datatable.module'; import { CephfsSubvolumeSnapshotsFormComponent } from './cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-form/cephfs-subvolume-snapshots-form.component'; import { CephfsSnapshotscheduleFormComponent } from './cephfs-snapshotschedule-form/cephfs-snapshotschedule-form.component'; +import { CephfsMountDetailsComponent } from './cephfs-mount-details/cephfs-mount-details.component'; @NgModule({ imports: [ @@ -64,7 +65,8 @@ import { CephfsSnapshotscheduleFormComponent } from './cephfs-snapshotschedule-f CephfsSubvolumeSnapshotsListComponent, CephfsSnapshotscheduleListComponent, CephfsSnapshotscheduleFormComponent, - CephfsSubvolumeSnapshotsFormComponent + CephfsSubvolumeSnapshotsFormComponent, + CephfsMountDetailsComponent ] }) export class CephfsModule {} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs.service.ts index d2dfbc0e2a763..ab43343f9edd4 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs.service.ts @@ -50,6 +50,10 @@ export class CephfsService { return this.http.get(`${this.baseURL}/${id}/mds_counters`); } + getFsRootDirectory(id: string) { + return this.http.get(`${this.baseURL}/${id}/get_root_directory`); + } + mkSnapshot(id: number, path: string, name?: string) { let params = new HttpParams(); params = params.append('path', path); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/code-block/code-block.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/code-block/code-block.component.html index 7cf78b8d1fbec..47eac6364e483 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/code-block/code-block.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/code-block/code-block.component.html @@ -1,8 +1,8 @@
     
-      {{code}}
+          class="d-flex px-2 py-3 align-items-center justify-content-between text-dark">
+      {{code}}
       
@@ -11,9 +11,9 @@
 
 
 
-  
-    {{codes}}
+    {{codes}}
     
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/code-block/code-block.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/code-block/code-block.component.scss
index f601dfe6609fb..d22855f751986 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/code-block/code-block.component.scss
+++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/code-block/code-block.component.scss
@@ -1,6 +1,4 @@
-@use './src/styles/vendor/variables' as vv;
-
 pre {
-  background-color: vv.$code-block-bg;
+  background-color: var(--gray-200);
   border-radius: 0.5rem;
 }
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/code-block/code-block.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/code-block/code-block.component.ts
index 91d2d991f3788..1021b8c975784 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/code-block/code-block.component.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/code-block/code-block.component.ts
@@ -8,4 +8,10 @@ import { Component, Input } from '@angular/core';
 export class CodeBlockComponent {
   @Input()
   codes: string[];
+
+  @Input()
+  textWrap: boolean = false;
+
+  @Input()
+  grayBg: boolean = false;
 }
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/copy2clipboard-button/copy2clipboard-button.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/copy2clipboard-button/copy2clipboard-button.component.ts
index 80c7acbf28aea..b6b8ca77e8adb 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/copy2clipboard-button/copy2clipboard-button.component.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/copy2clipboard-button/copy2clipboard-button.component.ts
@@ -26,7 +26,7 @@ export class Copy2ClipboardButtonComponent {
 
   private getText(): string {
     const element = document.getElementById(this.source) as HTMLInputElement;
-    return element.value;
+    return element?.value || element?.textContent;
   }
 
   @HostListener('click')
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/constants/app.constants.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/constants/app.constants.ts
index 2cf3f1047bab6..fc32efd1d38ad 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/shared/constants/app.constants.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/constants/app.constants.ts
@@ -143,6 +143,7 @@ export class ActionLabelsI18n {
   START_UPGRADE: string;
   ACTIVATE: string;
   DEACTIVATE: string;
+  ATTACH: string;
 
   constructor() {
     /* Create a new item */
@@ -223,6 +224,8 @@ export class ActionLabelsI18n {
 
     this.ACTIVATE = $localize`Activate`;
     this.DEACTIVATE = $localize`Deactivate`;
+
+    this.ATTACH = $localize`Attach`;
   }
 }
 
-- 
2.39.5