]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: add NFS export route from subvolume/subvolume grp 58441/head
authorAvan Thakkar <athakkar@redhat.com>
Fri, 5 Jul 2024 09:29:13 +0000 (14:59 +0530)
committerAvan Thakkar <athakkar@redhat.com>
Wed, 17 Jul 2024 11:21:47 +0000 (16:51 +0530)
Signed-off-by: Avan Thakkar <athakkar@redhat.com>
src/pybind/mgr/dashboard/controllers/cephfs.py
src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-subvolume-group/cephfs-subvolume-group.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/nfs/nfs-form/nfs-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-form/nfs-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/constants/app.constants.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/enum/icons.enum.ts

index 587e7677b0ed65ce1ee8ef835b22a06d231b165d..9f9b7501f44cfc6213ba26a5c23fcad9f550ec19 100644 (file)
@@ -871,8 +871,17 @@ class CephFSSubvolumeGroups(RESTController):
         if error_code != 0:
             raise DashboardException(
                 f'Failed to get info for subvolume group {group_name}: {err}'
+
             )
-        return json.loads(out)
+        group = json.loads(out)
+        error_code, out, err = mgr.remote('volumes', '_cmd_fs_subvolumegroup_getpath', None, {
+            'vol_name': vol_name, 'group_name': group_name})
+        if error_code != 0:
+            raise DashboardException(
+                f'Failed to get path for subvolume group {group_name}: {err}'
+            )
+        group['path'] = out
+        return group
 
     def create(self, vol_name: str, group_name: str, **kwargs):
         error_code, _, err = mgr.remote('volumes', '_cmd_fs_subvolumegroup_create', None, {
index 2316896863e47c38056b1efb3b31c8e25eca4195..935be11e98b062a4b0036b5d0dbdafb0006e635b 100644 (file)
@@ -407,7 +407,12 @@ const routes: Routes = [
             children: [
               { path: '', component: NfsListComponent },
               {
-                path: URLVerbs.CREATE,
+                path: `${URLVerbs.CREATE}/:fs_name/:subvolume_group`,
+                component: NfsFormComponent,
+                data: { breadcrumbs: ActionLabels.CREATE }
+              },
+              {
+                path: `${URLVerbs.CREATE}`,
                 component: NfsFormComponent,
                 data: { breadcrumbs: ActionLabels.CREATE }
               },
index a91daf8cb930d8319d9956e372fb9362920db59d..a8b62556f28bebfd31dcf65310bf4b57ec558719 100644 (file)
@@ -18,6 +18,8 @@ import { CriticalConfirmationModalComponent } from '~/app/shared/components/crit
 import { FinishedTask } from '~/app/shared/models/finished-task';
 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
 import { CephfsSubvolumeGroup } from '~/app/shared/models/cephfs-subvolume-group.model';
+import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+import _ from 'lodash';
 
 @Component({
   selector: 'cd-cephfs-subvolume-group',
@@ -54,6 +56,8 @@ export class CephfsSubvolumeGroupComponent implements OnInit, OnChanges {
   subvolumeGroup$: Observable<CephfsSubvolumeGroup[]>;
   subject = new BehaviorSubject<CephfsSubvolumeGroup[]>([]);
 
+  modalRef: NgbModalRef;
+
   constructor(
     private cephfsSubvolumeGroup: CephfsSubvolumeGroupService,
     private actionLabels: ActionLabelsI18n,
@@ -116,6 +120,13 @@ export class CephfsSubvolumeGroupComponent implements OnInit, OnChanges {
         icon: Icons.edit,
         click: () => this.openModal(true)
       },
+      {
+        name: this.actionLabels.NFS_EXPORT,
+        permission: 'create',
+        icon: Icons.nfsExport,
+        routerLink: () => ['/cephfs/nfs/create', this.fsName, this.selection?.first()?.name],
+        disable: () => !this.selection.hasSingleSelection
+      },
       {
         name: this.actionLabels.REMOVE,
         permission: 'delete',
index 58d849c901ef57bad6a3abf55fcea7d329c812f2..2d646f968dc543a4ffe78616cfcfd77d8c4c0bc7 100644 (file)
@@ -33,6 +33,9 @@ import { CephfsSubvolumeGroupService } from '~/app/shared/api/cephfs-subvolume-g
 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';
+import _ from 'lodash';
+
+const DEFAULT_SUBVOLUME_GROUP = '_nogroup';
 
 @Component({
   selector: 'cd-cephfs-subvolume-list',
@@ -159,6 +162,18 @@ export class CephfsSubvolumeListComponent extends CdForm implements OnInit, OnCh
         disable: () => !this.selection?.hasSelection,
         click: () => this.showAttachInfo()
       },
+      {
+        name: this.actionLabels.NFS_EXPORT,
+        permission: 'create',
+        icon: Icons.nfsExport,
+        routerLink: () => [
+          '/cephfs/nfs/create',
+          this.fsName,
+          _.isEmpty(this.activeGroupName) ? DEFAULT_SUBVOLUME_GROUP : this.activeGroupName,
+          { subvolume: this.selection?.first()?.name }
+        ],
+        disable: () => !this.selection?.hasSingleSelection
+      },
       {
         name: this.actionLabels.REMOVE,
         permission: 'delete',
index 01350b4def4a84cb48bf1477f6af309ccc82c5ee..ef78407b7e5eef1db9552c83121ca99d38f0893f 100644 (file)
         </div>
 
         <div class="form-group row"
-             *ngIf="storageBackend === 'CEPH'">
+             *ngIf="storageBackend === 'CEPH' && nfsForm.getValue('fsal').fs_name">
           <label class="cd-col-form-label"
                  for="subvolume_group"
                  i18n>Subvolume Group</label>
                       i18n>-- Select the CephFS subvolume group --</option>
               <option *ngFor="let subvol_grp of allsubvolgrps"
                       [value]="subvol_grp.name">{{ subvol_grp.name }}</option>
+              <option [value]="defaultSubVolGroup">{{ defaultSubVolGroup }}</option>
             </select>
           </div>
         </div>
 
       <div class="form-group row"
-           *ngIf="storageBackend === 'CEPH'">
+           *ngIf="storageBackend === 'CEPH' && nfsForm.getValue('fsal').fs_name">
         <label class="cd-col-form-label"
                for="subvolume"
                i18n>Subvolume</label>
                   formControlName="subvolume"
                   name="subvolume"
                   id="subvolume"
-                  (change)="getPath()">
+                  (change)="setSubVolPath()">
             <option *ngIf="allsubvols === null"
                     value=""
                     i18n>Loading...</option>
index f43067f231d913027604216807f2f0b4cc153c5c..18d91e2cddfdf6a02dc2041511c904b188e3a68d 100644 (file)
@@ -67,7 +67,11 @@ export class NfsFormComponent extends CdForm implements OnInit {
 
   allsubvolgrps: any[] = [];
   allsubvols: any[] = [];
-  fsPath: string = null;
+
+  selectedFsName: string = '';
+  selectedSubvolGroup: string = '';
+  selectedSubvol: string = '';
+  defaultSubVolGroup = '_nogroup';
 
   pathDataSource = (text$: Observable<string>) => {
     return text$.pipe(
@@ -132,6 +136,14 @@ export class NfsFormComponent extends CdForm implements OnInit {
       this.nfsForm.get('cluster_id').disable();
     } else {
       this.action = this.actionLabels.CREATE;
+      this.route.params.subscribe(
+        (params: { fs_name: string; subvolume_group: string; subvolume?: string }) => {
+          this.selectedFsName = params.fs_name;
+          this.selectedSubvolGroup = params.subvolume_group;
+          if (params.subvolume) this.selectedSubvol = params.subvolume;
+        }
+      );
+
       this.getData(promises);
     }
   }
@@ -153,40 +165,69 @@ export class NfsFormComponent extends CdForm implements OnInit {
     this.getSubVolGrp(fs_name);
   }
 
-  getSubVol() {
-    this.getPath();
+  async getSubVol() {
     const fs_name = this.nfsForm.getValue('fsal').fs_name;
     const subvolgrp = this.nfsForm.getValue('subvolume_group');
-    return this.subvolService.get(fs_name, subvolgrp).subscribe((data: any) => {
+    await this.setSubVolGrpPath();
+
+    (subvolgrp === this.defaultSubVolGroup
+      ? this.subvolService.get(fs_name)
+      : this.subvolService.get(fs_name, subvolgrp)
+    ).subscribe((data: any) => {
       this.allsubvols = data;
     });
   }
 
   getSubVolGrp(fs_name: string) {
-    return this.subvolgrpService.get(fs_name).subscribe((data: any) => {
+    this.subvolgrpService.get(fs_name).subscribe((data: any) => {
       this.allsubvolgrps = data;
     });
   }
 
-  getFsPath(volList: any[], value: string) {
-    const match = volList.find((vol) => vol.name === value);
-    if (match) {
-      return match.info.path;
-    }
+  setSubVolGrpPath(): Promise<void> {
+    return new Promise<void>((resolve, reject) => {
+      const subvolGroup = this.nfsForm.getValue('subvolume_group');
+      const fs_name = this.nfsForm.getValue('fsal').fs_name;
+
+      if (subvolGroup === this.defaultSubVolGroup) {
+        this.updatePath('/volumes/' + this.defaultSubVolGroup);
+        resolve();
+      } else {
+        this.subvolgrpService
+          .info(fs_name, subvolGroup)
+          .pipe(map((data) => data['path']))
+          .subscribe(
+            (path) => {
+              this.updatePath(path);
+              resolve();
+            },
+            (error) => reject(error)
+          );
+      }
+    });
   }
 
-  getPath() {
-    const subvol = this.nfsForm.getValue('subvolume');
-    if (subvol === '') {
+  setSubVolPath(): Promise<void> {
+    return new Promise<void>((resolve, reject) => {
+      const subvol = this.nfsForm.getValue('subvolume');
       const subvolGroup = this.nfsForm.getValue('subvolume_group');
-      this.fsPath = this.getFsPath(this.allsubvolgrps, subvolGroup);
-    } else {
-      this.fsPath = this.getFsPath(this.allsubvols, subvol);
-    }
-    this.nfsForm.patchValue({
-      path: this.fsPath
+      const fs_name = this.nfsForm.getValue('fsal').fs_name;
+
+      this.subvolService
+        .info(fs_name, subvol, subvolGroup === this.defaultSubVolGroup ? '' : subvolGroup)
+        .pipe(map((data) => data['path']))
+        .subscribe(
+          (path) => {
+            this.updatePath(path);
+            resolve();
+          },
+          (error) => reject(error)
+        );
     });
+  }
 
+  updatePath(path: string) {
+    this.nfsForm.patchValue({ path: path });
     this.pathChangeHandler();
   }
 
@@ -317,8 +358,34 @@ export class NfsFormComponent extends CdForm implements OnInit {
     }
   }
 
+  resolveRouteParams() {
+    if (!_.isEmpty(this.selectedFsName)) {
+      this.nfsForm.patchValue({
+        fsal: {
+          fs_name: this.selectedFsName
+        }
+      });
+      this.volumeChangeHandler();
+    }
+    if (!_.isEmpty(this.selectedSubvolGroup)) {
+      this.nfsForm.patchValue({
+        subvolume_group: this.selectedSubvolGroup
+      });
+      this.getSubVol();
+    }
+    if (!_.isEmpty(this.selectedSubvol)) {
+      this.nfsForm.patchValue({
+        subvolume: this.selectedSubvol
+      });
+      this.setSubVolPath();
+    }
+  }
+
   resolveFilesystems(filesystems: any[]) {
     this.allFsNames = filesystems;
+    if (!this.isEdit) {
+      this.resolveRouteParams();
+    }
   }
 
   resolveRealms(realms: string[]) {
index 3140f481f45768b08abf817c579788a26484f3e4..96bb439bec3204fbc02776b34b55d36571fcf21b 100644 (file)
@@ -150,6 +150,7 @@ export class ActionLabelsI18n {
   AUTHORIZE: string;
   EXPAND_CLUSTER: string;
   SETUP_MULTISITE_REPLICATION: string;
+  NFS_EXPORT: string;
 
   constructor() {
     /* Create a new item */
@@ -239,6 +240,8 @@ export class ActionLabelsI18n {
     this.DISCONNECT = $localize`Disconnect`;
     this.RECONNECT = $localize`Reconnect`;
     this.EXPAND_CLUSTER = $localize`Expand Cluster`;
+
+    this.NFS_EXPORT = $localize`Create NFS Export`;
   }
 }
 
index 8f90a51cf86735a8939dcb74006e8416b0673a88..6219c73305163e035b1caa149f17bdb63e50bcb1 100644 (file)
@@ -84,6 +84,7 @@ export enum Icons {
   eye = 'fa fa-eye', // Observability
   calendar = 'fa fa-calendar',
   externalUrl = 'fa fa-external-link', // links to external page
+  nfsExport = 'fa fa-server', // NFS export
 
   /* Icons for special effect */
   large = 'fa fa-lg', // icon becomes 33% larger