]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: added cephfs mount details 55551/head
authorIvo Almeida <ialmeida@redhat.com>
Mon, 12 Feb 2024 10:08:09 +0000 (10:08 +0000)
committerIvo Almeida <ialmeida@redhat.com>
Wed, 14 Feb 2024 11:31:42 +0000 (11:31 +0000)
Fixes: https://tracker.ceph.com/issues/64405
Signed-off-by: Ivo Almeida <ialmeida@redhat.com>
15 files changed:
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-list/cephfs-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mount-details/cephfs-mount-details.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mount-details/cephfs-mount-details.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mount-details/cephfs-mount-details.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-mount-details/cephfs-mount-details.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-list/cephfs-subvolume-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/code-block/code-block.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/components/code-block/code-block.component.scss
src/pybind/mgr/dashboard/frontend/src/app/shared/components/code-block/code-block.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/copy2clipboard-button/copy2clipboard-button.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/constants/app.constants.ts

index 26e79727c12d185e2549e3de2232e5c5200fc7f5..0943ed82574eaf526863055c2391b902988c9656 100644 (file)
@@ -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 (file)
index 0000000..a8c30af
--- /dev/null
@@ -0,0 +1,38 @@
+<cd-modal (hide)="cancel()">
+  <ng-container class="modal-title">
+    <span i18n>Attach commands</span>
+  </ng-container>
+  <ng-container class="modal-content">
+    <div class="modal-body">
+      <h5 class="fw-bold"
+          i18n>
+        Using Mount command
+      </h5>
+      <cd-code-block textWrap="true"
+                     [codes]="[mount]"></cd-code-block>
+
+      <h5 class="fw-bold"
+          i18n>
+        Using FUSE command
+      </h5>
+      <cd-code-block textWrap="true"
+                     [codes]="[fuse]"></cd-code-block>
+
+      <h5 class="fw-bold"
+          i18n>
+          Using NFS Command
+      </h5>
+      <cd-code-block textWrap="true"
+                     [codes]="[nfs]"></cd-code-block>
+    </div>
+    <div class="modal-footer">
+      <cd-submit-button (submitAction)="cancel()"
+                        i18n>
+        Close
+      </cd-submit-button>
+    </div>
+  </ng-container>
+</cd-modal>
+
+
+
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 (file)
index 0000000..e69de29
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 (file)
index 0000000..141ae42
--- /dev/null
@@ -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<CephfsMountDetailsComponent>;
+
+  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 (file)
index 0000000..77a3f4a
--- /dev/null
@@ -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 = '<MOUNT_DIRECTORY>';
+  mountData!: Record<string, any>;
+  constructor(public activeModal: NgbActiveModal) {}
+  mount!: string;
+  fuse!: string;
+  nfs!: string;
+
+  ngOnInit(): void {
+    this.mount = `sudo <CLIENT_USER>@${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=<PORT> <IP of active_mds daemon>:${this.mountData?.rootPath} ${this.MOUNT_DIRECTORY}`;
+  }
+
+  ngOnDestroy(): void {
+    if (this.onCancel && this.canceled) {
+      this.onCancel();
+    }
+  }
+
+  cancel() {
+    this.canceled = true;
+    this.activeModal.close();
+  }
+}
index f26f63e755a6e58227b16d294bf6c0e1e0352e78..4142724f7cfe60e4a692cf8828e127d7ec4b6fe5 100644 (file)
     class="fw-bold"
     [ngbTooltip]="fullpathTpl"
     triggers="click:blur">
-    {{ row.path?.split?.("@")?.[0] | path }}
+    {{ row.pathForSelection?.split?.("@")?.[0] | path }}
   </span>
 
   <span
   *ngIf="row.active; else inactiveStatusTpl">
     <i
       [ngClass]="[icons.success, icons.large]"
-      ngbTooltip="{{ row.path?.split?.('@')?.[0] }} is active"
+      ngbTooltip="{{ row.pathForSelection?.split?.('@')?.[0] }} is active"
       class="text-success"
     ></i>
   </span>
     <i
       [ngClass]="[icons.warning, icons.large]"
       class="text-warning"
-      ngbTooltip="{{ row.path?.split?.('@')?.[0] }} has been deactivated"
+      ngbTooltip="{{ row.pathForSelection?.split?.('@')?.[0] }} has been deactivated"
     ></i>
   </ng-template>
 
-  <ng-template #fullpathTpl>
+  <ng-template #fullpathForSelectionTpl>
     <span
       data-toggle="tooltip"
-      [title]="row.path"
+      [title]="row.pathForSelection"
       class="font-monospace"
-      >{{ row.path?.split?.("@")?.[0] }}
+      >{{ row.pathForSelection?.split?.("@")?.[0] }}
       <cd-copy-2-clipboard-button
-        *ngIf="row.path"
-        [source]="row.path?.split?.('@')?.[0]"
+        *ngIf="row.pathForSelection"
+        [source]="row.pathForSelection?.split?.('@')?.[0]"
         [byId]="false"
         [showIconOnly]="true"
       >
index 581ee6e2fa3ae9cd2a0128bd68c276ef9e9d4e0f..ecc2036d5dd3a4743a9f1402738b89b86366ddef 100644 (file)
@@ -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 },
index 92c139f8e5dd58cd812384dee8bdc0b4d23bde99..58d849c901ef57bad6a3abf55fcea7d329c812f2 100644 (file)
@@ -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,
index 687dd0b93ee933ac39cbd1248cb11047d424a029..14481d8382241cdd60c3f903af6434ba534dd602 100644 (file)
@@ -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 {}
index d2dfbc0e2a763b79da80640896a6c22fde597939..ab43343f9edd4ffea0c6e82476b72cabb18ef2bc 100644 (file)
@@ -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);
index 7cf78b8d1fbec482aee59b62a349fcb04f59f08f..47eac6364e483e58c831a3bd08976e115c1b2c3a 100644 (file)
@@ -1,8 +1,8 @@
 <ng-container *ngIf="codes.length > 1; else singleCodeBlock">
   <pre id="bigCodeBlock">
     <span *ngFor="let code of codes"
-          class="d-flex p-2 align-items-center justify-content-between text-dark">
-      <span>{{code}}</span>
+          class="d-flex px-2 py-3 align-items-center justify-content-between text-dark">
+      <span [ngClass]="{'text-wrap': textWrap}">{{code}}</span>
       <cd-copy-2-clipboard-button
           [source]="code"
           [byId]="false"></cd-copy-2-clipboard-button>
@@ -11,9 +11,9 @@
 </ng-container>
 
 <ng-template #singleCodeBlock>
-  <pre class="d-flex p-2 align-items-center justify-content-between text-dark"
+  <pre class="d-flex px-2 py-3 align-items-center justify-content-between text-dark"
        id="singleCodeBlock">
-    <span>{{codes}}</span>
+    <span [ngClass]="{'text-wrap': textWrap}">{{codes}}</span>
     <cd-copy-2-clipboard-button
       [source]="codes"
       [byId]="false"></cd-copy-2-clipboard-button>
index f601dfe6609fbf85479eea484a8eef494b7a34c4..d22855f75198610e72ae16f9ce8bc8de94fdb195 100644 (file)
@@ -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;
 }
index 91d2d991f37883e75ce61cf61699d6012f179f7c..1021b8c975784cad75c6cecb6a56b4e65b4e1fad 100644 (file)
@@ -8,4 +8,10 @@ import { Component, Input } from '@angular/core';
 export class CodeBlockComponent {
   @Input()
   codes: string[];
+
+  @Input()
+  textWrap: boolean = false;
+
+  @Input()
+  grayBg: boolean = false;
 }
index 80c7acbf28aeae227a012cd1ede7b5ab90dcadad..b6b8ca77e8adbcfca8a1f019404328e7cf9c8510 100644 (file)
@@ -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')
index 2cf3f1047bab616c8e04c642c8012548c112ed34..fc32efd1d38ad04b6ceaeb3f27fbc650863859b2 100644 (file)
@@ -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`;
   }
 }