import { OsdListComponent } from './ceph/cluster/osd/osd-list/osd-list.component';
import { PrometheusListComponent } from './ceph/cluster/prometheus/prometheus-list/prometheus-list.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 { PoolFormComponent } from './ceph/pool/pool-form/pool-form.component';
import { PoolListComponent } from './ceph/pool/pool-list/pool-list.component';
import { CephfsModule } from './cephfs/cephfs.module';
import { ClusterModule } from './cluster/cluster.module';
import { DashboardModule } from './dashboard/dashboard.module';
+import { NfsModule } from './nfs/nfs.module';
import { PerformanceCounterModule } from './performance-counter/performance-counter.module';
import { PoolModule } from './pool/pool.module';
import { RgwModule } from './rgw/rgw.module';
BlockModule,
PoolModule,
CephfsModule,
+ NfsModule,
SharedModule
],
declarations: []
--- /dev/null
+<tabset *ngIf="selection?.hasSingleSelection">
+ <tab heading="Details"
+ i18n-heading>
+ <cd-table-key-value [data]="data">
+ </cd-table-key-value>
+ </tab>
+</tabset>
--- /dev/null
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import * as _ from 'lodash';
+import { TabsModule } from 'ngx-bootstrap/tabs';
+
+import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
+import { CdTableSelection } from '../../../shared/models/cd-table-selection';
+import { SharedModule } from '../../../shared/shared.module';
+import { NfsDetailsComponent } from './nfs-details.component';
+
+describe('NfsDetailsComponent', () => {
+ let component: NfsDetailsComponent;
+ let fixture: ComponentFixture<NfsDetailsComponent>;
+
+ configureTestBed({
+ declarations: [NfsDetailsComponent],
+ imports: [SharedModule, TabsModule.forRoot(), HttpClientTestingModule],
+ providers: i18nProviders
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(NfsDetailsComponent);
+ component = fixture.componentInstance;
+
+ component.selection = new CdTableSelection();
+ component.selection.selected = [
+ {
+ export_id: 1,
+ path: '/qwe',
+ fsal: { name: 'CEPH', user_id: 'fs', fs_name: 1 },
+ cluster_id: 'cluster1',
+ daemons: ['node1', 'node2'],
+ pseudo: '/qwe',
+ tag: 'asd',
+ access_type: 'RW',
+ squash: 'no_root_squash',
+ protocols: [3, 4],
+ transports: ['TCP', 'UDP'],
+ clients: [],
+ id: 'cluster1:1',
+ state: 'LOADING'
+ }
+ ];
+ component.selection.update();
+
+ fixture.detectChanges();
+ });
+
+ beforeEach(() => {});
+
+ it('should create', () => {
+ component.ngOnChanges();
+ expect(component.data).toBeTruthy();
+ });
+
+ it('should prepare data', () => {
+ component.ngOnChanges();
+ expect(component.data).toEqual({
+ 'Access Type': 'RW',
+ 'CephFS Filesystem': 1,
+ 'CephFS User': 'fs',
+ Cluster: 'cluster1',
+ Daemons: ['node1', 'node2'],
+ 'NFS Protocol': ['NFSv3', 'NFSv4'],
+ Path: '/qwe',
+ Pseudo: '/qwe',
+ 'Security Label': undefined,
+ Squash: 'no_root_squash',
+ 'Storage Backend': 'CephFS',
+ Transport: ['TCP', 'UDP']
+ });
+ });
+
+ it('should prepare data if RGW', () => {
+ const newData = _.assignIn(component.selection.first(), {
+ fsal: {
+ name: 'RGW',
+ rgw_user_id: 'rgw_user_id'
+ }
+ });
+ component.selection.selected = [newData];
+ component.selection.update();
+ component.ngOnChanges();
+ expect(component.data).toEqual({
+ 'Access Type': 'RW',
+ Cluster: 'cluster1',
+ Daemons: ['node1', 'node2'],
+ 'NFS Protocol': ['NFSv3', 'NFSv4'],
+ 'Object Gateway User': 'rgw_user_id',
+ Path: '/qwe',
+ Pseudo: '/qwe',
+ Squash: 'no_root_squash',
+ 'Storage Backend': 'Object Gateway',
+ Transport: ['TCP', 'UDP']
+ });
+ });
+});
--- /dev/null
+import { Component, Input, OnChanges } from '@angular/core';
+
+import { I18n } from '@ngx-translate/i18n-polyfill';
+import * as _ from 'lodash';
+
+import { CdTableSelection } from '../../../shared/models/cd-table-selection';
+
+@Component({
+ selector: 'cd-nfs-details',
+ templateUrl: './nfs-details.component.html',
+ styleUrls: ['./nfs-details.component.scss']
+})
+export class NfsDetailsComponent implements OnChanges {
+ @Input()
+ selection: CdTableSelection;
+
+ selectedItem: any;
+ data: any;
+
+ constructor(private i18n: I18n) {}
+
+ ngOnChanges() {
+ if (this.selection.hasSelection) {
+ this.selectedItem = this.selection.first();
+ this.data = {};
+ this.data[this.i18n('Cluster')] = this.selectedItem.cluster_id;
+ this.data[this.i18n('Daemons')] = this.selectedItem.daemons;
+ this.data[this.i18n('NFS Protocol')] = this.selectedItem.protocols.map(
+ (protocol) => 'NFSv' + protocol
+ );
+ this.data[this.i18n('Pseudo')] = this.selectedItem.pseudo;
+ this.data[this.i18n('Access Type')] = this.selectedItem.access_type;
+ this.data[this.i18n('Squash')] = this.selectedItem.squash;
+ this.data[this.i18n('Transport')] = this.selectedItem.transports;
+ this.data[this.i18n('Path')] = this.selectedItem.path;
+
+ if (this.selectedItem.fsal.name === 'CEPH') {
+ this.data[this.i18n('Storage Backend')] = this.i18n('CephFS');
+ this.data[this.i18n('CephFS User')] = this.selectedItem.fsal.user_id;
+ this.data[this.i18n('CephFS Filesystem')] = this.selectedItem.fsal.fs_name;
+ this.data[this.i18n('Security Label')] = this.selectedItem.fsal.sec_label_xattr;
+ } else {
+ this.data[this.i18n('Storage Backend')] = this.i18n('Object Gateway');
+ this.data[this.i18n('Object Gateway User')] = this.selectedItem.fsal.rgw_user_id;
+ }
+ }
+ }
+}
--- /dev/null
+<cd-info-panel *ngIf="true"
+ title="Orchestrator not available"
+ i18n-title
+ i18n>To apply any changes you have to restart the Ganisha services.</cd-info-panel>
+
+<cd-table #table
+ [data]="exports"
+ columnMode="flex"
+ [columns]="columns"
+ identifier="id"
+ forceIdentifier="true"
+ selectionType="single"
+ (updateSelection)="updateSelection($event)">
+ <div class="table-actions btn-toolbar">
+ <cd-table-actions class="btn-group"
+ [permission]="permission"
+ [selection]="selection"
+ [tableActions]="tableActions">
+ </cd-table-actions>
+ </div>
+
+ <cd-nfs-details cdTableDetail
+ [selection]="selection">
+ </cd-nfs-details>
+</cd-table>
+
+<ng-template #nfsFsal
+ let-value="value">
+ <ng-container *ngIf="value.name==='CEPH'"
+ i18n>CephFS</ng-container>
+ <ng-container *ngIf="value.name==='RGW'"
+ i18n>Object Gateway</ng-container>
+</ng-template>
--- /dev/null
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
+import { RouterTestingModule } from '@angular/router/testing';
+
+import { ToastModule } from 'ng2-toastr';
+import { TabsModule } from 'ngx-bootstrap/tabs';
+import { BehaviorSubject, of } from 'rxjs';
+
+import {
+ configureTestBed,
+ i18nProviders,
+ PermissionHelper
+} from '../../../../testing/unit-test-helper';
+import { NfsService } from '../../../shared/api/nfs.service';
+import { TableActionsComponent } from '../../../shared/datatable/table-actions/table-actions.component';
+import { ExecutingTask } from '../../../shared/models/executing-task';
+import { SummaryService } from '../../../shared/services/summary.service';
+import { TaskListService } from '../../../shared/services/task-list.service';
+import { SharedModule } from '../../../shared/shared.module';
+import { NfsDetailsComponent } from '../nfs-details/nfs-details.component';
+import { NfsListComponent } from './nfs-list.component';
+
+describe('NfsListComponent', () => {
+ let component: NfsListComponent;
+ let fixture: ComponentFixture<NfsListComponent>;
+ let summaryService: SummaryService;
+ let nfsService: NfsService;
+ let httpTesting: HttpTestingController;
+
+ const refresh = (data) => {
+ summaryService['summaryDataSource'].next(data);
+ };
+
+ configureTestBed(
+ {
+ declarations: [NfsListComponent, NfsDetailsComponent],
+ imports: [
+ HttpClientTestingModule,
+ RouterTestingModule,
+ SharedModule,
+ ToastModule.forRoot(),
+ TabsModule.forRoot()
+ ],
+ providers: [TaskListService, i18nProviders]
+ },
+ true
+ );
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(NfsListComponent);
+ component = fixture.componentInstance;
+ summaryService = TestBed.get(SummaryService);
+ nfsService = TestBed.get(NfsService);
+ httpTesting = TestBed.get(HttpTestingController);
+
+ // this is needed because summaryService isn't being reset after each test.
+ summaryService['summaryDataSource'] = new BehaviorSubject(null);
+ summaryService['summaryData$'] = summaryService['summaryDataSource'].asObservable();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ describe('after ngOnInit', () => {
+ beforeEach(() => {
+ fixture.detectChanges();
+ spyOn(nfsService, 'list').and.callThrough();
+ httpTesting.expectOne('api/nfs-ganesha/daemon').flush([]);
+ httpTesting.expectOne('api/summary');
+ });
+
+ afterEach(() => {
+ httpTesting.verify();
+ });
+
+ it('should load exports on init', () => {
+ refresh({});
+ 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();
+ });
+
+ it('should call error function on init when summary service fails', () => {
+ spyOn(component.table, 'reset');
+ summaryService['summaryDataSource'].error(undefined);
+ expect(component.table.reset).toHaveBeenCalled();
+ });
+ });
+
+ describe('handling of executing tasks', () => {
+ let exports: any[];
+
+ const addExport = (export_id) => {
+ const model = {
+ export_id: export_id,
+ path: 'path_' + export_id,
+ fsal: 'fsal_' + export_id,
+ cluster_id: 'cluster_' + export_id
+ };
+ exports.push(model);
+ };
+
+ const addTask = (name: string, export_id: string) => {
+ const task = new ExecutingTask();
+ task.name = name;
+ switch (task.name) {
+ case 'nfs/create':
+ task.metadata = {
+ path: 'path_' + export_id,
+ fsal: 'fsal_' + export_id,
+ cluster_id: 'cluster_' + export_id
+ };
+ break;
+ default:
+ task.metadata = {
+ cluster_id: 'cluster_' + export_id,
+ export_id: export_id
+ };
+ break;
+ }
+ summaryService.addRunningTask(task);
+ };
+
+ const expectExportTasks = (expo: any, executing: string) => {
+ expect(expo.cdExecuting).toEqual(executing);
+ };
+
+ beforeEach(() => {
+ exports = [];
+ addExport('a');
+ addExport('b');
+ addExport('c');
+ component.exports = exports;
+ refresh({ executing_tasks: [], finished_tasks: [] });
+ spyOn(nfsService, 'list').and.callFake(() => of(exports));
+ fixture.detectChanges();
+
+ const req = httpTesting.expectOne('api/nfs-ganesha/daemon');
+ req.flush([]);
+ });
+
+ it('should gets all exports without tasks', () => {
+ expect(component.exports.length).toBe(3);
+ expect(component.exports.every((expo) => !expo.cdExecuting)).toBeTruthy();
+ });
+
+ it('should add a new export from a task', fakeAsync(() => {
+ addTask('nfs/create', 'd');
+ tick();
+ expect(component.exports.length).toBe(4);
+ expectExportTasks(component.exports[0], undefined);
+ expectExportTasks(component.exports[1], undefined);
+ expectExportTasks(component.exports[2], undefined);
+ expectExportTasks(component.exports[3], 'Creating');
+ }));
+
+ it('should show when an existing export is being modified', () => {
+ addTask('nfs/edit', 'a');
+ addTask('nfs/delete', 'b');
+ expect(component.exports.length).toBe(3);
+ expectExportTasks(component.exports[0], 'Updating');
+ expectExportTasks(component.exports[1], 'Deleting');
+ });
+ });
+
+ describe('show action buttons and drop down actions depending on permissions', () => {
+ let tableActions: TableActionsComponent;
+ let scenario: { fn; empty; single };
+ let permissionHelper: PermissionHelper;
+
+ const getTableActionComponent = (): TableActionsComponent => {
+ fixture.detectChanges();
+ return fixture.debugElement.query(By.directive(TableActionsComponent)).componentInstance;
+ };
+
+ beforeEach(() => {
+ permissionHelper = new PermissionHelper(component.permission, () =>
+ getTableActionComponent()
+ );
+ scenario = {
+ fn: () => tableActions.getCurrentButton().name,
+ single: 'Edit',
+ empty: 'Add'
+ };
+ });
+
+ describe('with all', () => {
+ beforeEach(() => {
+ tableActions = permissionHelper.setPermissionsAndGetActions(1, 1, 1);
+ });
+
+ it(`shows 'Edit' for single selection else 'Add' as main action`, () =>
+ permissionHelper.testScenarios(scenario));
+
+ it('shows all actions', () => {
+ expect(tableActions.tableActions.length).toBe(3);
+ expect(tableActions.tableActions).toEqual(component.tableActions);
+ });
+ });
+
+ describe('with read, create and update', () => {
+ beforeEach(() => {
+ tableActions = permissionHelper.setPermissionsAndGetActions(1, 1, 0);
+ });
+
+ it(`shows 'Edit' for single selection else 'Add' as main action`, () =>
+ permissionHelper.testScenarios(scenario));
+
+ it(`shows all actions except for 'Delete'`, () => {
+ expect(tableActions.tableActions.length).toBe(2);
+ component.tableActions.pop();
+ expect(tableActions.tableActions).toEqual(component.tableActions);
+ });
+ });
+
+ describe('with read, create and delete', () => {
+ beforeEach(() => {
+ tableActions = permissionHelper.setPermissionsAndGetActions(1, 0, 1);
+ });
+
+ it(`shows 'Delete' for single selection else 'Add' as main action`, () => {
+ scenario.single = 'Delete';
+ permissionHelper.testScenarios(scenario);
+ });
+
+ it(`shows 'Add', and 'Delete' action`, () => {
+ expect(tableActions.tableActions.length).toBe(2);
+ expect(tableActions.tableActions).toEqual([
+ component.tableActions[0],
+ component.tableActions[2]
+ ]);
+ });
+ });
+
+ describe('with read, edit and delete', () => {
+ beforeEach(() => {
+ tableActions = permissionHelper.setPermissionsAndGetActions(0, 1, 1);
+ });
+
+ it(`shows always 'Edit' as main action`, () => {
+ scenario.empty = 'Edit';
+ permissionHelper.testScenarios(scenario);
+ });
+
+ it(`shows 'Edit' and 'Delete' actions`, () => {
+ expect(tableActions.tableActions.length).toBe(2);
+ expect(tableActions.tableActions).toEqual([
+ component.tableActions[1],
+ component.tableActions[2]
+ ]);
+ });
+ });
+
+ describe('with read and create', () => {
+ beforeEach(() => {
+ tableActions = permissionHelper.setPermissionsAndGetActions(1, 0, 0);
+ });
+
+ it(`always shows 'Add' as main action`, () => {
+ scenario.single = 'Add';
+ permissionHelper.testScenarios(scenario);
+ });
+
+ it(`shows 'Add' action`, () => {
+ expect(tableActions.tableActions.length).toBe(1);
+ expect(tableActions.tableActions).toEqual([component.tableActions[0]]);
+ });
+ });
+
+ describe('with read and edit', () => {
+ beforeEach(() => {
+ tableActions = permissionHelper.setPermissionsAndGetActions(0, 1, 0);
+ });
+
+ it(`shows always 'Edit' as main action`, () => {
+ scenario.empty = 'Edit';
+ permissionHelper.testScenarios(scenario);
+ });
+
+ it(`shows 'Edit' action`, () => {
+ expect(tableActions.tableActions.length).toBe(1);
+ expect(tableActions.tableActions).toEqual([component.tableActions[1]]);
+ });
+ });
+
+ describe('with read and delete', () => {
+ beforeEach(() => {
+ tableActions = permissionHelper.setPermissionsAndGetActions(0, 0, 1);
+ });
+
+ it(`shows always 'Delete' as main action`, () => {
+ scenario.single = 'Delete';
+ scenario.empty = 'Delete';
+ permissionHelper.testScenarios(scenario);
+ });
+
+ it(`shows 'Delete' action`, () => {
+ expect(tableActions.tableActions.length).toBe(1);
+ expect(tableActions.tableActions).toEqual([component.tableActions[2]]);
+ });
+ });
+
+ describe('with only read', () => {
+ beforeEach(() => {
+ tableActions = permissionHelper.setPermissionsAndGetActions(0, 0, 0);
+ });
+
+ it('shows no main action', () => {
+ permissionHelper.testScenarios({
+ fn: () => tableActions.getCurrentButton(),
+ single: undefined,
+ empty: undefined
+ });
+ });
+
+ it('shows no actions', () => {
+ expect(tableActions.tableActions.length).toBe(0);
+ expect(tableActions.tableActions).toEqual([]);
+ });
+ });
+ });
+});
--- /dev/null
+import { Component, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
+
+import { I18n } from '@ngx-translate/i18n-polyfill';
+import * as _ from 'lodash';
+import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
+import { Subscription } from 'rxjs';
+
+import { NfsService } from '../../../shared/api/nfs.service';
+import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
+import { TableComponent } from '../../../shared/datatable/table/table.component';
+import { CellTemplate } from '../../../shared/enum/cell-template.enum';
+import { ViewCacheStatus } from '../../../shared/enum/view-cache-status.enum';
+import { CdTableAction } from '../../../shared/models/cd-table-action';
+import { CdTableColumn } from '../../../shared/models/cd-table-column';
+import { CdTableSelection } from '../../../shared/models/cd-table-selection';
+import { FinishedTask } from '../../../shared/models/finished-task';
+import { Permission } from '../../../shared/models/permissions';
+import { AuthStorageService } from '../../../shared/services/auth-storage.service';
+import { TaskListService } from '../../../shared/services/task-list.service';
+import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
+
+@Component({
+ selector: 'cd-nfs-list',
+ templateUrl: './nfs-list.component.html',
+ styleUrls: ['./nfs-list.component.scss'],
+ providers: [TaskListService]
+})
+export class NfsListComponent implements OnInit, OnDestroy {
+ @ViewChild('nfsState')
+ nfsState: TemplateRef<any>;
+ @ViewChild('nfsFsal')
+ nfsFsal: TemplateRef<any>;
+
+ @ViewChild('table')
+ table: TableComponent;
+
+ columns: CdTableColumn[];
+ permission: Permission;
+ selection = new CdTableSelection();
+ summaryDataSubscription: Subscription;
+ viewCacheStatus: any;
+ exports: any[];
+ tableActions: CdTableAction[];
+ isDefaultCluster = false;
+
+ modalRef: BsModalRef;
+
+ builders = {
+ 'nfs/create': (metadata) => {
+ return {
+ path: metadata['path'],
+ cluster_id: metadata['cluster_id'],
+ fsal: metadata['fsal']
+ };
+ }
+ };
+
+ constructor(
+ private authStorageService: AuthStorageService,
+ private i18n: I18n,
+ private modalService: BsModalService,
+ private nfsService: NfsService,
+ private taskListService: TaskListService,
+ private taskWrapper: TaskWrapperService
+ ) {
+ this.permission = this.authStorageService.getPermissions().nfs;
+ const getNfsUri = () =>
+ this.selection.first() &&
+ `${encodeURI(this.selection.first().cluster_id)}/${encodeURI(
+ this.selection.first().export_id
+ )}`;
+
+ const addAction: CdTableAction = {
+ permission: 'create',
+ icon: 'fa-plus',
+ routerLink: () => '/nfs/add',
+ canBePrimary: (selection: CdTableSelection) => !selection.hasSingleSelection,
+ name: this.i18n('Add')
+ };
+
+ const editAction: CdTableAction = {
+ permission: 'update',
+ icon: 'fa-pencil',
+ routerLink: () => `/nfs/edit/${getNfsUri()}`,
+ name: this.i18n('Edit')
+ };
+
+ const deleteAction: CdTableAction = {
+ permission: 'delete',
+ icon: 'fa-times',
+ click: () => this.deleteNfsModal(),
+ name: this.i18n('Delete')
+ };
+
+ this.tableActions = [addAction, editAction, deleteAction];
+ }
+
+ ngOnInit() {
+ this.columns = [
+ {
+ name: this.i18n('Export'),
+ prop: 'path',
+ flexGrow: 2,
+ cellTransformation: CellTemplate.executing
+ },
+ {
+ name: this.i18n('Cluster'),
+ prop: 'cluster_id',
+ flexGrow: 2
+ },
+ {
+ name: this.i18n('Daemons'),
+ prop: 'daemons',
+ flexGrow: 2
+ },
+ {
+ name: this.i18n('Storage Backend'),
+ prop: 'fsal',
+ flexGrow: 2,
+ cellTemplate: this.nfsFsal
+ },
+ {
+ name: this.i18n('Access Type'),
+ prop: 'access_type',
+ flexGrow: 2
+ }
+ ];
+
+ this.nfsService.daemon().subscribe(
+ (daemons: any) => {
+ const clusters = _(daemons)
+ .map((daemon) => daemon.cluster_id)
+ .uniq()
+ .value();
+
+ this.isDefaultCluster = clusters.length === 1 && clusters[0] === '_default_';
+ this.columns[1].isHidden = this.isDefaultCluster;
+ if (this.table) {
+ this.table.updateColumns();
+ }
+
+ this.taskListService.init(
+ () => this.nfsService.list(),
+ (resp) => this.prepareResponse(resp),
+ (exports) => (this.exports = exports),
+ () => this.onFetchError(),
+ this.taskFilter,
+ this.itemFilter,
+ this.builders
+ );
+ },
+ (error) => {
+ this.onFetchError();
+ }
+ );
+ }
+
+ ngOnDestroy() {
+ if (this.summaryDataSubscription) {
+ this.summaryDataSubscription.unsubscribe();
+ }
+ }
+
+ prepareResponse(resp: any): any[] {
+ let result = [];
+ resp.forEach((nfs) => {
+ nfs.id = `${nfs.cluster_id}:${nfs.export_id}`;
+ nfs.state = 'LOADING';
+ result = result.concat(nfs);
+ });
+
+ return result;
+ }
+
+ onFetchError() {
+ this.table.reset(); // Disable loading indicator.
+ this.viewCacheStatus = { status: ViewCacheStatus.ValueException };
+ }
+
+ itemFilter(entry, task) {
+ return (
+ entry.cluster_id === task.metadata['cluster_id'] &&
+ entry.export_id === task.metadata['export_id']
+ );
+ }
+
+ taskFilter(task) {
+ return ['nfs/create', 'nfs/delete', 'nfs/edit'].includes(task.name);
+ }
+
+ updateSelection(selection: CdTableSelection) {
+ this.selection = selection;
+ }
+
+ deleteNfsModal() {
+ const cluster_id = this.selection.first().cluster_id;
+ const export_id = this.selection.first().export_id;
+
+ this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
+ initialState: {
+ itemDescription: this.i18n('NFS'),
+ submitActionObservable: () =>
+ this.taskWrapper.wrapTaskAroundCall({
+ task: new FinishedTask('nfs/delete', {
+ cluster_id: cluster_id,
+ export_id: export_id
+ }),
+ call: this.nfsService.delete(cluster_id, export_id)
+ })
+ }
+ });
+ }
+}
--- /dev/null
+import { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+import { ReactiveFormsModule } from '@angular/forms';
+import { RouterModule } from '@angular/router';
+
+import { TabsModule } from 'ngx-bootstrap/tabs';
+import { TypeaheadModule } from 'ngx-bootstrap/typeahead';
+
+import { SharedModule } from '../../shared/shared.module';
+import { NfsDetailsComponent } from './nfs-details/nfs-details.component';
+import { NfsFormClientComponent } from './nfs-form-client/nfs-form-client.component';
+import { NfsFormComponent } from './nfs-form/nfs-form.component';
+import { NfsListComponent } from './nfs-list/nfs-list.component';
+
+@NgModule({
+ imports: [
+ ReactiveFormsModule,
+ RouterModule,
+ SharedModule,
+ TabsModule.forRoot(),
+ CommonModule,
+ TypeaheadModule.forRoot()
+ ],
+ declarations: [NfsListComponent, NfsDetailsComponent, NfsFormComponent, NfsFormClientComponent]
+})
+export class NfsModule {}
class="dropdown-item"
routerLink="/block/iscsi">iSCSI</a>
</li>
-
</ul>
</li>
+ <!-- NFS -->
+ <li routerLinkActive="active"
+ class="tc_menuitem tc_menuitem_nfs"
+ *ngIf="permissions?.nfs?.read">
+ <a i18n
+ routerLink="/nfs">NFS</a>
+ </li>
+
<!-- Filesystem -->
<li routerLinkActive="active"
class="tc_menuitem tc_menuitem_cephs"