]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/nfs: NFS Cluster and Export Listing 61873/head
authorAchintk1491 <achintk1491@gmail.com>
Tue, 18 Feb 2025 12:31:22 +0000 (18:01 +0530)
committerDnyaneshwari <dnyaneshwari@li-9c9fbecc-2d5c-11b2-a85c-e2a7cc8a424f.ibm.com>
Thu, 20 Mar 2025 06:50:54 +0000 (12:20 +0530)
Fixes: https://tracker.ceph.com/issues/70022
Signed-off-by: Achint Kaur <ackaur@redhat.com>
20 files changed:
src/pybind/mgr/dashboard/controllers/nfs.py
src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/models/nfs-cluster-config.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-cluster-details/nfs-cluster-details.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-cluster-details/nfs-cluster-details.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-cluster-details/nfs-cluster-details.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-cluster-details/nfs-cluster-details.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-cluster/nfs-cluster.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-cluster/nfs-cluster.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-cluster/nfs-cluster.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-cluster/nfs-cluster.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-list/nfs-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-list/nfs-list.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-list/nfs-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/nfs.service.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/nfs.service.ts
src/pybind/mgr/dashboard/openapi.yaml
src/pybind/mgr/nfs/module.py

index b417a585c299c823ebd392fdb9a2826e5176a059..e170054b444cbcad59fec33ca967c03802dd01fd 100644 (file)
@@ -14,6 +14,7 @@ from ..security import Scope
 from ..services.cephfs import CephFS
 from ..services.exception import DashboardException, handle_cephfs_error, \
     serialize_dashboard_exception
+from ..tools import str_to_bool
 from . import APIDoc, APIRouter, BaseController, Endpoint, EndpointDoc, \
     ReadPermission, RESTController, Task, UIRouter
 from ._version import APIVersion
@@ -87,7 +88,11 @@ def NfsTask(name, metadata, wait_for):  # noqa: N802
 class NFSGaneshaCluster(RESTController):
     @ReadPermission
     @RESTController.MethodMap(version=APIVersion.EXPERIMENTAL)
-    def list(self):
+    def list(self, info: Optional[bool] = False):
+        if str_to_bool(info):
+            return [
+                {"name": key, **value} for key, value in mgr.remote('nfs', 'cluster_info').items()
+            ]
         return mgr.remote('nfs', 'cluster_ls')
 
 
@@ -109,11 +114,11 @@ class NFSGaneshaExports(RESTController):
         export['fsal'] = schema_fsal_info
         return export
 
-    @EndpointDoc("List all NFS-Ganesha exports",
+    @EndpointDoc("List all or cluster specific NFS-Ganesha exports ",
                  responses={200: [EXPORT_SCHEMA]})
-    def list(self) -> List[Dict[str, Any]]:
+    def list(self, cluster_id=None) -> List[Dict[str, Any]]:
         exports = []
-        for export in mgr.remote('nfs', 'export_ls'):
+        for export in mgr.remote('nfs', 'export_ls', cluster_id, True):
             exports.append(self._get_schema_export(export))
 
         return exports
index b9b9d453a0581aed18eabb8b9e1b25dc8e74ca76..82e0c9c98724ebac795b6160b8acba263ec8b7a6 100644 (file)
@@ -26,7 +26,6 @@ import { ServicesComponent } from './ceph/cluster/services/services.component';
 import { TelemetryComponent } from './ceph/cluster/telemetry/telemetry.component';
 import { DashboardComponent } from './ceph/dashboard/dashboard/dashboard.component';
 import { NfsFormComponent } from './ceph/nfs/nfs-form/nfs-form.component';
-import { NfsListComponent } from './ceph/nfs/nfs-list/nfs-list.component';
 import { PerformanceCounterComponent } from './ceph/performance-counter/performance-counter/performance-counter.component';
 import { LoginPasswordFormComponent } from './core/auth/login-password-form/login-password-form.component';
 import { LoginComponent } from './core/auth/login/login.component';
@@ -56,6 +55,7 @@ import { SmbTabsComponent } from './ceph/smb/smb-tabs/smb-tabs.component';
 import { SmbShareFormComponent } from './ceph/smb/smb-share-form/smb-share-form.component';
 import { SmbJoinAuthFormComponent } from './ceph/smb/smb-join-auth-form/smb-join-auth-form.component';
 import { SmbUsersgroupsFormComponent } from './ceph/smb/smb-usersgroups-form/smb-usersgroups-form.component';
+import { NfsClusterComponent } from './ceph/nfs/nfs-cluster/nfs-cluster.component';
 
 @Injectable()
 export class PerformanceCounterBreadcrumbsResolver extends BreadcrumbsResolver {
@@ -417,7 +417,7 @@ const routes: Routes = [
               breadcrumbs: 'File/NFS'
             },
             children: [
-              { path: '', component: NfsListComponent },
+              { path: '', component: NfsClusterComponent },
               {
                 path: `${URLVerbs.CREATE}/:fs_name/:subvolume_group`,
                 component: NfsFormComponent,
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/models/nfs-cluster-config.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/models/nfs-cluster-config.ts
new file mode 100644 (file)
index 0000000..8fa9872
--- /dev/null
@@ -0,0 +1,12 @@
+export interface NFSBackend {
+  hostname: string;
+  ip: string;
+  port: number;
+}
+
+export interface NFSCluster {
+  name: string;
+  virtual_ip: number;
+  port: number;
+  backend: NFSBackend[];
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-cluster-details/nfs-cluster-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-cluster-details/nfs-cluster-details.component.html
new file mode 100644 (file)
index 0000000..eb6a1ef
--- /dev/null
@@ -0,0 +1,11 @@
+  <ng-container *ngIf="!!selection">
+  <legend
+  i18n
+  >
+  {{title | titlecase}}
+  <cd-help-text>
+    Lists exports for a cluster
+  </cd-help-text>
+  </legend>
+  <cd-nfs-list [clusterId]="selection.name"></cd-nfs-list>
+  </ng-container>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-cluster-details/nfs-cluster-details.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-cluster-details/nfs-cluster-details.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-cluster-details/nfs-cluster-details.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-cluster-details/nfs-cluster-details.component.spec.ts
new file mode 100644 (file)
index 0000000..b0f4176
--- /dev/null
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { NfsClusterDetailsComponent } from './nfs-cluster-details.component';
+import { configureTestBed } from '~/testing/unit-test-helper';
+import { SharedModule } from '~/app/shared/shared.module';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+
+describe('NfsClusterDetailsComponent', () => {
+  let component: NfsClusterDetailsComponent;
+  let fixture: ComponentFixture<NfsClusterDetailsComponent>;
+
+  configureTestBed({
+    declarations: [NfsClusterDetailsComponent],
+    imports: [HttpClientTestingModule, SharedModule]
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(NfsClusterDetailsComponent);
+    component = fixture.componentInstance;
+  });
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-cluster-details/nfs-cluster-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-cluster-details/nfs-cluster-details.component.ts
new file mode 100644 (file)
index 0000000..47128c1
--- /dev/null
@@ -0,0 +1,13 @@
+import { Component, Input } from '@angular/core';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+
+@Component({
+  selector: 'cd-nfs-cluster-details',
+  templateUrl: './nfs-cluster-details.component.html',
+  styleUrls: ['./nfs-cluster-details.component.scss']
+})
+export class NfsClusterDetailsComponent {
+  title = $localize`Export`;
+  @Input()
+  selection: CdTableSelection;
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-cluster/nfs-cluster.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-cluster/nfs-cluster.component.html
new file mode 100644 (file)
index 0000000..2a6bdb8
--- /dev/null
@@ -0,0 +1,51 @@
+  <ng-container *ngIf="orchStatus?.available && clusters$ | async as clusters">
+  <cd-table [autoReload]="true"
+            [data]="clusters"
+            columnMode="flex"
+            [columns]="columns"
+            selectionType="single"
+            [hasDetails]="true"
+            (setExpandedRow)="setExpandedRow($event)"
+            (fetchData)="loadData()"
+            (updateSelection)="updateSelection($event)">
+  <cd-table-actions class="table-actions"
+                    [selection]="selection"
+                    [permission]="permission"
+                    [tableActions]="tableActions"></cd-table-actions>
+  <cd-nfs-cluster-details  *cdTableDetail
+                           [selection]="expandedRow"></cd-nfs-cluster-details>
+  </cd-table>
+  </ng-container>
+
+  <ng-template
+  #virtualIpTpl
+  let-row="data.row"
+  >
+  <span *ngIf="row.virtual_ip || row.ports">
+  <cds-tag size="md">
+    {{ row.virtual_ip }}:{{row.ports}}
+  </cds-tag>
+  </span>
+  </ng-template>
+
+  <ng-template
+  #ipAddrTpl
+  let-backends="data.value"
+  >
+  <span *ngFor="let backend of backends">
+  <span *ngIf="backend.ip || backend.port">
+  <cds-tag size="md">
+    {{ backend.ip }}:{{backend.port}}
+  </cds-tag>
+  </span>
+  </span>
+  </ng-template>
+
+  <ng-template #hostnameTpl
+               let-backends="data.value">
+  <span *ngFor="let backend of backends">
+    <span *ngIf="backend.hostname">
+      <cds-tag size="md">{{backend.hostname }}</cds-tag>
+    </span>
+  </span>
+  </ng-template>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-cluster/nfs-cluster.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-cluster/nfs-cluster.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-cluster/nfs-cluster.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-cluster/nfs-cluster.component.spec.ts
new file mode 100644 (file)
index 0000000..7cfa939
--- /dev/null
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { configureTestBed } from '~/testing/unit-test-helper';
+import { SharedModule } from '~/app/shared/shared.module';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { NfsClusterComponent } from './nfs-cluster.component';
+
+describe('NfsClusterComponent', () => {
+  let component: NfsClusterComponent;
+  let fixture: ComponentFixture<NfsClusterComponent>;
+
+  configureTestBed({
+    declarations: [NfsClusterComponent],
+    imports: [HttpClientTestingModule, SharedModule]
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(NfsClusterComponent);
+    component = fixture.componentInstance;
+  });
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-cluster/nfs-cluster.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/nfs/nfs-cluster/nfs-cluster.component.ts
new file mode 100644 (file)
index 0000000..0d9af51
--- /dev/null
@@ -0,0 +1,91 @@
+import { Component, NgZone, OnInit, TemplateRef, ViewChild } from '@angular/core';
+import { NfsService } from '~/app/shared/api/nfs.service';
+import { ListWithDetails } from '~/app/shared/classes/list-with-details.class';
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+import { Permission } from '~/app/shared/models/permissions';
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { URLBuilderService } from '~/app/shared/services/url-builder.service';
+import { NFSCluster } from '../models/nfs-cluster-config';
+import { OrchestratorStatus } from '~/app/shared/models/orchestrator.interface';
+import { OrchestratorService } from '~/app/shared/api/orchestrator.service';
+import { BehaviorSubject, Observable } from 'rxjs';
+import { switchMap } from 'rxjs/operators';
+const BASE_URL = 'cephfs/nfs';
+@Component({
+  selector: 'cd-nfs-cluster',
+  templateUrl: './nfs-cluster.component.html',
+  styleUrls: ['./nfs-cluster.component.scss'],
+  providers: [{ provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) }]
+})
+export class NfsClusterComponent extends ListWithDetails implements OnInit {
+  @ViewChild('hostnameTpl', { static: true })
+  hostnameTpl: TemplateRef<any>;
+
+  @ViewChild('ipAddrTpl', { static: true })
+  ipAddrTpl: TemplateRef<any>;
+
+  @ViewChild('virtualIpTpl', { static: true })
+  virtualIpTpl: TemplateRef<any>;
+
+  columns: CdTableColumn[] = [];
+  selection: CdTableSelection = new CdTableSelection();
+  tableActions: CdTableAction[] = [];
+  permission: Permission;
+  orchStatus: OrchestratorStatus;
+  clusters$: Observable<NFSCluster[]>;
+  subject = new BehaviorSubject<NFSCluster[]>([]);
+
+  constructor(
+    public actionLabels: ActionLabelsI18n,
+    protected ngZone: NgZone,
+    private authStorageService: AuthStorageService,
+    private nfsService: NfsService,
+    private orchService: OrchestratorService
+  ) {
+    super();
+  }
+
+  ngOnInit(): void {
+    this.orchService.status().subscribe((status: OrchestratorStatus) => {
+      this.orchStatus = status;
+    });
+    this.permission = this.authStorageService.getPermissions().nfs;
+    this.clusters$ = this.subject.pipe(switchMap(() => this.nfsService.nfsClusterList()));
+    this.columns = [
+      {
+        name: $localize`Name`,
+        prop: 'name',
+        flexGrow: 1
+      },
+      {
+        name: $localize`Hostnames`,
+        prop: 'backend',
+        flexGrow: 2,
+        cellTemplate: this.hostnameTpl
+      },
+      {
+        name: $localize`IP Address`,
+        prop: 'backend',
+        flexGrow: 2,
+        cellTemplate: this.ipAddrTpl
+      },
+      {
+        name: $localize`Virtual IP Address`,
+        prop: 'virtual_ip',
+        flexGrow: 1,
+        cellTemplate: this.virtualIpTpl
+      }
+    ];
+  }
+
+  loadData() {
+    this.subject.next([]);
+  }
+
+  updateSelection(selection: CdTableSelection) {
+    this.selection = selection;
+  }
+}
index 9adf835d75dcdb2b8f8e175b53c557b3ca519e2a..7ab6beda7801f44f33058a80b658641e9067e470 100644 (file)
@@ -5,7 +5,7 @@
           identifier="id"
           forceIdentifier="true"
           selectionType="single"
-          [hasDetails]="true"
+          [hasDetails]="false"
           (setExpandedRow)="setExpandedRow($event)"
           (updateSelection)="updateSelection($event)">
   <div class="table-actions">
                 i18n>Object Gateway</ng-container>
 </ng-template>
 
+<ng-template #protocolTpl
+             let-protocols="data.value">
+  <span *ngFor="let protocol of protocols">
+    <cds-tag size="md">NFSv{{protocol}}</cds-tag>
+  </span>
+</ng-template>
+
+<ng-template #transportTpl
+             let-transports="data.value">
+  <span *ngFor="let transport of transports">
+    <cds-tag size="md">{{transport}}</cds-tag>
+  </span>
+</ng-template>
+
 <ng-template #pathTmpl
              let-value="data.value">
   <span *ngIf="value === ''"
index b7135f57262b7785c663716fa34e8e765253aa73..91341d3b728f4dfd9fe12400b94765b532aa2c08 100644 (file)
@@ -66,12 +66,6 @@ describe('NfsListComponent', () => {
       httpTesting.verify();
     });
 
-    it('should load exports on init', () => {
-      refresh(new Summary());
-      httpTesting.expectOne('api/nfs-ganesha/export');
-      expect(nfsService.list).toHaveBeenCalled();
-    });
-
     it('should not load images on init because no data', () => {
       refresh(undefined);
       expect(nfsService.list).not.toHaveBeenCalled();
index 53b764be0b637862531da4d528406adafec66d4f..e00fe577625c0c320df4a8620701189ebfca04f3 100644 (file)
@@ -1,4 +1,4 @@
-import { Component, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
+import { Component, Input, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
 import { Router } from '@angular/router';
 
 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
@@ -49,6 +49,15 @@ export class NfsListComponent extends ListWithDetails implements OnInit, OnDestr
   @ViewChild('table', { static: true })
   table: TableComponent;
 
+  @ViewChild('protocolTpl', { static: true })
+  protocolTpl: TemplateRef<any>;
+
+  @ViewChild('transportTpl', { static: true })
+  transportTpl: TemplateRef<any>;
+
+  @Input() clusterId: string;
+  modalRef: NgbModalRef;
+
   columns: CdTableColumn[];
   permission: Permission;
   selection = new CdTableSelection();
@@ -59,8 +68,6 @@ export class NfsListComponent extends ListWithDetails implements OnInit, OnDestr
   isDefaultCluster = false;
   fsal: SUPPORTED_FSAL;
 
-  modalRef: NgbModalRef;
-
   builders = {
     'nfs/create': (metadata: any) => {
       return {
@@ -135,7 +142,8 @@ export class NfsListComponent extends ListWithDetails implements OnInit, OnDestr
         name: this.fsal === SUPPORTED_FSAL.CEPH ? $localize`Path` : $localize`Bucket`,
         prop: 'path',
         flexGrow: 2,
-        cellTemplate: this.pathTmpl
+        cellTemplate: this.pathTmpl,
+        cellTransformation: CellTemplate.path
       },
       {
         name: $localize`Pseudo`,
@@ -157,11 +165,23 @@ export class NfsListComponent extends ListWithDetails implements OnInit, OnDestr
         name: $localize`Access Type`,
         prop: 'access_type',
         flexGrow: 2
+      },
+      {
+        name: $localize`NFS Protocol`,
+        prop: 'protocols',
+        flexGrow: 2,
+        cellTemplate: this.protocolTpl
+      },
+      {
+        name: $localize`Transports`,
+        prop: 'transports',
+        flexGrow: 2,
+        cellTemplate: this.transportTpl
       }
     ];
 
     this.taskListService.init(
-      () => this.nfsService.list(),
+      () => this.nfsService.list(this.clusterId),
       (resp) => this.prepareResponse(resp),
       (exports) => (this.exports = exports),
       () => this.onFetchError(),
index 13d66ac162760b480051e7b386a4ceac26ec7215..f7004c0501ad6b00009f7efb44bcabd2854c24cf 100644 (file)
@@ -18,10 +18,15 @@ import {
   IconService,
   InputModule,
   RadioModule,
-  SelectModule
+  SelectModule,
+  TabsModule,
+  TagModule
 } from 'carbon-components-angular';
 
 import Close from '@carbon/icons/es/close/32';
+import { NfsClusterComponent } from './nfs-cluster/nfs-cluster.component';
+import { ClusterModule } from '../cluster/cluster.module';
+import { NfsClusterDetailsComponent } from './nfs-cluster-details/nfs-cluster-details.component';
 
 @NgModule({
   imports: [
@@ -33,15 +38,25 @@ import Close from '@carbon/icons/es/close/32';
     NgbTypeaheadModule,
     NgbTooltipModule,
     GridModule,
+    TagModule,
     SelectModule,
     InputModule,
     RadioModule,
     CheckboxModule,
     ButtonModule,
-    IconModule
+    IconModule,
+    TabsModule,
+    ClusterModule
   ],
-  exports: [NfsListComponent, NfsFormComponent, NfsDetailsComponent],
-  declarations: [NfsListComponent, NfsDetailsComponent, NfsFormComponent, NfsFormClientComponent]
+  exports: [NfsListComponent, NfsFormComponent, NfsDetailsComponent, NfsClusterComponent],
+  declarations: [
+    NfsListComponent,
+    NfsDetailsComponent,
+    NfsFormComponent,
+    NfsFormClientComponent,
+    NfsClusterComponent,
+    NfsClusterDetailsComponent
+  ]
 })
 export class NfsModule {
   constructor(private iconService: IconService) {
index f65ca55cb768bb7445307b765a74ea9b503b0b7c..c8017df84cb495e3600e2ab2276ade9b1d1fad3f 100644 (file)
@@ -52,7 +52,6 @@ import { RgwSyncPrimaryZoneComponent } from './rgw-sync-primary-zone/rgw-sync-pr
 import { RgwSyncMetadataInfoComponent } from './rgw-sync-metadata-info/rgw-sync-metadata-info.component';
 import { RgwSyncDataInfoComponent } from './rgw-sync-data-info/rgw-sync-data-info.component';
 import { BucketTagModalComponent } from './bucket-tag-modal/bucket-tag-modal.component';
-import { NfsListComponent } from '../nfs/nfs-list/nfs-list.component';
 import { NfsFormComponent } from '../nfs/nfs-form/nfs-form.component';
 import { RgwMultisiteSyncPolicyComponent } from './rgw-multisite-sync-policy/rgw-multisite-sync-policy.component';
 import { RgwMultisiteSyncPolicyFormComponent } from './rgw-multisite-sync-policy-form/rgw-multisite-sync-policy-form.component';
@@ -95,6 +94,7 @@ import { RgwBucketTieringFormComponent } from './rgw-bucket-tiering-form/rgw-buc
 import { RgwBucketLifecycleListComponent } from './rgw-bucket-lifecycle-list/rgw-bucket-lifecycle-list.component';
 import { RgwRateLimitComponent } from './rgw-rate-limit/rgw-rate-limit.component';
 import { RgwRateLimitDetailsComponent } from './rgw-rate-limit-details/rgw-rate-limit-details.component';
+import { NfsClusterComponent } from '../nfs/nfs-cluster/nfs-cluster.component';
 
 @NgModule({
   imports: [
@@ -374,7 +374,7 @@ const routes: Routes = [
       breadcrumbs: 'NFS'
     },
     children: [
-      { path: '', component: NfsListComponent },
+      { path: '', component: NfsClusterComponent },
       {
         path: URLVerbs.CREATE,
         component: NfsFormComponent,
index 139fa490bfd82ef1684ce9fcc5eab6a69503120a..431333bfad5d07fd973bd0d81329798b8f433c18 100644 (file)
@@ -27,8 +27,9 @@ describe('NfsService', () => {
   });
 
   it('should call list', () => {
-    service.list().subscribe();
-    const req = httpTesting.expectOne('api/nfs-ganesha/export');
+    let cluster_id = 'test';
+    service.list(cluster_id).subscribe();
+    const req = httpTesting.expectOne('api/nfs-ganesha/export?cluster_id=test');
     expect(req.request.method).toBe('GET');
   });
 
index 1fcce26e50a4ea9ab21f3024774c7a740394bc00..a9953d330697b1e34b75e4f2eee1d23375029fb6 100644 (file)
@@ -2,6 +2,7 @@ import { HttpClient } from '@angular/common/http';
 import { Injectable } from '@angular/core';
 
 import { Observable, throwError } from 'rxjs';
+import { NFSCluster } from '~/app/ceph/nfs/models/nfs-cluster-config';
 
 import { NfsFSAbstractionLayer, SUPPORTED_FSAL } from '~/app/ceph/nfs/models/nfs.fsal';
 import { ApiClient } from '~/app/shared/api/api-client';
@@ -44,7 +45,6 @@ export class NfsService extends ApiClient {
       disabled: false
     }
   ];
-
   nfsSquash = {
     no_root_squash: ['no_root_squash', 'noidsquash', 'none'],
     root_id_squash: ['root_id_squash', 'rootidsquash', 'rootid'],
@@ -56,8 +56,10 @@ export class NfsService extends ApiClient {
     super();
   }
 
-  list() {
-    return this.http.get(`${this.apiPath}/export`);
+  list(clusterId?: string) {
+    return this.http.get(`${this.apiPath}/export`, {
+      params: { cluster_id: clusterId }
+    });
   }
 
   get(clusterId: string, exportId: string) {
@@ -105,4 +107,11 @@ export class NfsService extends ApiClient {
   filesystems() {
     return this.http.get(`${this.uiApiPath}/cephfs/filesystems`);
   }
+
+  nfsClusterList(): Observable<NFSCluster[]> {
+    return this.http.get<NFSCluster[]>(`${this.apiPath}/cluster`, {
+      headers: { Accept: this.getVersionHeaderValue(0, 1) },
+      params: { info: true }
+    });
+  }
 }
index f35e75ac01e27eb0804f67a6c292109dcd16337d..3a7ae4ae8ffefd48b314f806a66f7cbcb9c1756d 100755 (executable)
@@ -7546,7 +7546,12 @@ paths:
       - Multi-cluster
   /api/nfs-ganesha/cluster:
     get:
-      parameters: []
+      parameters:
+      - default: false
+        in: query
+        name: info
+        schema:
+          type: boolean
       responses:
         '200':
           content:
@@ -7568,7 +7573,12 @@ paths:
       - NFS-Ganesha
   /api/nfs-ganesha/export:
     get:
-      parameters: []
+      parameters:
+      - allowEmptyValue: true
+        in: query
+        name: cluster_id
+        schema:
+          type: string
       responses:
         '200':
           content:
@@ -7672,7 +7682,7 @@ paths:
             trace.
       security:
       - jwt: []
-      summary: List all NFS-Ganesha exports
+      summary: 'List all or cluster specific NFS-Ganesha exports '
       tags:
       - NFS-Ganesha
     post:
index 80490ac8e7fe5f0195cf526a81181f83a397c51b..00fe8b0e60e2d551b7223ad6f51655850b4eec0a 100644 (file)
@@ -183,8 +183,10 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
     def fetch_nfs_export_obj(self) -> ExportMgr:
         return self.export_mgr
 
-    def export_ls(self) -> List[Dict[Any, Any]]:
-        return self.export_mgr.list_all_exports()
+    def export_ls(self, cluster_id: Optional[str] = None, detailed: bool = False) -> List[Dict[Any, Any]]:
+        if not (cluster_id):
+            return self.export_mgr.list_all_exports()
+        return self.export_mgr.list_exports(cluster_id, detailed)
 
     def export_get(self, cluster_id: str, export_id: int) -> Optional[Dict[str, Any]]:
         return self.export_mgr.get_export_by_id(cluster_id, export_id)
@@ -194,3 +196,9 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
 
     def cluster_ls(self) -> List[str]:
         return available_clusters(self)
+
+    def cluster_info(self, cluster_id: Optional[str] = None) -> Dict[str, Any]:
+        return self.nfs.show_nfs_cluster_info(cluster_id=cluster_id)
+
+    def fetch_nfs_cluster_obj(self) -> NFSCluster:
+        return self.nfs