--- /dev/null
+<div class="form-group">
+ <label class="col-sm-3 control-label"
+ i18n>Clients</label>
+
+ <div class="col-sm-9"
+ [formGroup]="form"
+ #formDir="ngForm">
+ <span *ngIf="form.get('clients').value.length === 0"
+ class="form-control no-border text-muted">
+ <span class="text-muted"
+ i18n>Any client can access</span>
+ </span>
+
+ <ng-container formArrayName="clients">
+ <div *ngFor="let item of form.get('clients').value; let index = index; trackBy: trackByFn">
+ <div class="panel panel-default"
+ [formGroupName]="index">
+ <div class="panel-heading">
+ <h3 class="panel-title">{{ (index + 1) | ordinal }}
+ <span class="pull-right clickable"
+ (click)="removeClient(index)"
+ tooltip="Remove">×</span>
+ </h3>
+ </div>
+
+ <div class="panel-body">
+ <!-- Addresses -->
+ <div class="form-group"
+ [ngClass]="{ 'has-error': showError(index, 'addresses', formDir) }">
+ <label i18n
+ class="col-sm-3 control-label"
+ for="addresses">Addresses</label>
+ <div class="col-sm-9">
+ <input type="text"
+ class="form-control"
+ name="addresses"
+ id="addresses"
+ formControlName="addresses"
+ placeholder="192.168.0.10, 192.168.1.0/8">
+ <span class="help-block">
+ <span *ngIf="showError(index, 'addresses', formDir, 'required')"
+ i18n>Required field</span>
+
+ <span *ngIf="showError(index, 'addresses', formDir, 'pattern')">
+ <ng-container i18n>Must contain one or more comma-separated values</ng-container>
+ <br>
+ <ng-container i18n>For example:</ng-container> 192.168.0.10, 192.168.1.0/8
+ </span>
+ </span>
+ </div>
+ </div>
+
+ <!-- Access Type-->
+ <div class="form-group">
+ <label i18n
+ class="col-sm-3 control-label"
+ for="access_type">Access Type</label>
+ <div class="col-sm-9">
+ <select class="form-control"
+ name="access_type"
+ id="access_type"
+ formControlName="access_type">
+ <option [value]="form.getValue('access_type')">{{ getNoAccessTypeDescr() }}</option>
+ <option *ngFor="let item of nfsAccessType"
+ [value]="item.value">{{ item.value }}</option>
+ </select>
+ <span class="help-block"
+ *ngIf="getValue(index, 'access_type')">
+ {{ getAccessTypeHelp(index) }}
+ </span>
+ </div>
+ </div>
+
+ <!-- Squash -->
+ <div class="form-group">
+ <label i18n
+ class="col-sm-3 control-label"
+ for="squash">Squash</label>
+ <div class="col-sm-9">
+ <select class="form-control"
+ name="squash"
+ id="squash"
+ formControlName="squash">
+ <option [value]="form.getValue('squash')">{{ getNoSquashDescr() }}</option>
+ <option *ngFor="let squash of nfsSquash"
+ [value]="squash">{{ squash }}</option>
+ </select>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </ng-container>
+
+ <span class="form-control no-border">
+ <button class="btn btn-default btn-label pull-right"
+ (click)="addClient()">
+ <i class="fa fa-fw fa-plus"></i>
+ <ng-container i18n>Add clients</ng-container>
+ </button>
+ </span>
+ <hr>
+ </div>
+</div>
--- /dev/null
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormControl, ReactiveFormsModule } from '@angular/forms';
+
+import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
+import { CdFormBuilder } from '../../../shared/forms/cd-form-builder';
+import { CdFormGroup } from '../../../shared/forms/cd-form-group';
+import { SharedModule } from '../../../shared/shared.module';
+import { NfsFormClientComponent } from './nfs-form-client.component';
+
+describe('NfsFormClientComponent', () => {
+ let component: NfsFormClientComponent;
+ let fixture: ComponentFixture<NfsFormClientComponent>;
+
+ configureTestBed({
+ declarations: [NfsFormClientComponent],
+ imports: [ReactiveFormsModule, SharedModule, HttpClientTestingModule],
+ providers: i18nProviders
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(NfsFormClientComponent);
+ const formBuilder = TestBed.get(CdFormBuilder);
+ component = fixture.componentInstance;
+
+ component.form = this.nfsForm = new CdFormGroup({
+ access_type: new FormControl(''),
+ clients: formBuilder.array([]),
+ squash: new FormControl('')
+ });
+
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should add a client', () => {
+ expect(component.form.getValue('clients')).toEqual([]);
+ component.addClient();
+ expect(component.form.getValue('clients')).toEqual([
+ { access_type: '', addresses: '', squash: '' }
+ ]);
+ });
+
+ it('should return form access_type', () => {
+ expect(component.getNoAccessTypeDescr()).toBe('-- Select the access type --');
+
+ component.form.patchValue({ access_type: 'RW' });
+ expect(component.getNoAccessTypeDescr()).toBe('RW (inherited from global config)');
+ });
+
+ it('should return form squash', () => {
+ expect(component.getNoSquashDescr()).toBe(
+ '-- Select what kind of user id squashing is performed --'
+ );
+
+ component.form.patchValue({ squash: 'root_id_squash' });
+ expect(component.getNoSquashDescr()).toBe('root_id_squash (inherited from global config)');
+ });
+
+ it('should remove client', () => {
+ component.addClient();
+ expect(component.form.getValue('clients')).toEqual([
+ { access_type: '', addresses: '', squash: '' }
+ ]);
+
+ component.removeClient(0);
+ expect(component.form.getValue('clients')).toEqual([]);
+ });
+});
--- /dev/null
+import { Component, Input } from '@angular/core';
+import { FormArray, FormControl, Validators } from '@angular/forms';
+
+import { I18n } from '@ngx-translate/i18n-polyfill';
+import * as _ from 'lodash';
+
+import { NfsService } from '../../../shared/api/nfs.service';
+import { CdFormGroup } from '../../../shared/forms/cd-form-group';
+
+@Component({
+ selector: 'cd-nfs-form-client',
+ templateUrl: './nfs-form-client.component.html',
+ styleUrls: ['./nfs-form-client.component.scss']
+})
+export class NfsFormClientComponent {
+ @Input()
+ form: CdFormGroup;
+
+ nfsSquash: any[] = this.nfsService.nfsSquash;
+ nfsAccessType: any[] = this.nfsService.nfsAccessType;
+
+ constructor(private nfsService: NfsService, private i18n: I18n) {}
+
+ getNoAccessTypeDescr() {
+ if (this.form.getValue('access_type')) {
+ return `${this.form.getValue('access_type')} ${this.i18n('(inherited from global config)')}`;
+ }
+ return this.i18n('-- Select the access type --');
+ }
+
+ getAccessTypeHelp(index) {
+ const accessTypeItem = this.nfsAccessType.find((currentAccessTypeItem) => {
+ return this.getValue(index, 'access_type') === currentAccessTypeItem.value;
+ });
+ return _.isObjectLike(accessTypeItem) ? accessTypeItem.help : '';
+ }
+
+ getNoSquashDescr() {
+ if (this.form.getValue('squash')) {
+ return `${this.form.getValue('squash')} (${this.i18n('inherited from global config')})`;
+ }
+ return this.i18n('-- Select what kind of user id squashing is performed --');
+ }
+
+ addClient() {
+ const clients = this.form.get('clients') as FormArray;
+
+ const REGEX_IP = `(([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\.([0-9]{1,3})([/](\\d|[1-2]\\d|3[0-2]))?)`;
+ const REGEX_LIST_IP = `${REGEX_IP}([ ,]{1,2}${REGEX_IP})*`;
+
+ const fg = new CdFormGroup({
+ addresses: new FormControl('', {
+ validators: [Validators.required, Validators.pattern(REGEX_LIST_IP)]
+ }),
+ access_type: new FormControl(this.form.getValue('access_type')),
+ squash: new FormControl(this.form.getValue('squash'))
+ });
+
+ clients.push(fg);
+ return fg;
+ }
+
+ removeClient(index) {
+ const clients = this.form.get('clients') as FormArray;
+ clients.removeAt(index);
+ }
+
+ showError(index, control, formDir, x) {
+ return (<any>this.form.controls.clients).controls[index].showError(control, formDir, x);
+ }
+
+ getValue(index, control) {
+ const clients = this.form.get('clients') as FormArray;
+ const client = clients.at(index) as CdFormGroup;
+ return client.getValue(control);
+ }
+
+ resolveModel(clients: any[]) {
+ _.forEach(clients, (client) => {
+ const fg = this.addClient();
+ fg.patchValue(client);
+ });
+ }
+
+ trackByFn(index) {
+ return index;
+ }
+}
--- /dev/null
+<div class="col-sm-12 col-lg-6">
+ <form name="nfsForm"
+ class="form-horizontal"
+ #formDir="ngForm"
+ [formGroup]="nfsForm"
+ novalidate>
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title"
+ i18n>NFS export {{ export_id ? cluster_id + ':' + export_id : '' }}</h3>
+ </div>
+
+ <div class="panel-body">
+
+ <!-- cluster_id -->
+ <div class="form-group"
+ [ngClass]="{'has-error': nfsForm.showError('cluster_id', formDir)}"
+ *ngIf="!isDefaultCluster">
+ <label class="col-sm-3 control-label"
+ for="cluster_id">
+ <ng-container i18n>Cluster</ng-container>
+ <span class="required"></span>
+ </label>
+ <div class="col-sm-9">
+ <select class="form-control"
+ formControlName="cluster_id"
+ name="cluster_id"
+ id="cluster_id"
+ (change)="onClusterChange()">
+ <option *ngIf="allClusters === null"
+ value=""
+ i18n>Loading...</option>
+ <option *ngIf="allClusters !== null && allClusters.length === 0"
+ value=""
+ i18n>-- No cluster available --</option>
+ <option *ngIf="allClusters !== null && allClusters.length > 0"
+ value=""
+ i18n>-- Select the cluster --</option>
+ <option *ngFor="let cluster of allClusters"
+ [value]="cluster">{{ cluster }}</option>
+ </select>
+ <span class="help-block"
+ *ngIf="nfsForm.showError('cluster_id', formDir, 'required')"
+ i18n>Required field</span>
+ </div>
+ </div>
+
+ <!-- daemons -->
+ <div class="form-group"
+ [ngClass]="{'has-error': nfsForm.showError('daemons', formDir)}">
+ <label class="col-sm-3 control-label"
+ for="daemons">
+ <ng-container i18n>Daemons</ng-container>
+ </label>
+ <div class="col-sm-9">
+ <ng-container *ngFor="let daemon of nfsForm.getValue('daemons'); let i = index">
+ <div class="input-group cd-mb">
+ <input class="form-control"
+ type="text"
+ [value]="daemon"
+ disabled />
+ <span class="input-group-btn">
+ <button class="btn btn-default"
+ type="button"
+ (click)="removeDaemon(i, daemon)">
+ <i class="fa fa-remove fa-fw"
+ aria-hidden="true"></i>
+ </button>
+ </span>
+ </div>
+ </ng-container>
+
+ <div class="row">
+ <div class="col-md-12">
+ <cd-select [data]="nfsForm.get('daemons').value"
+ [options]="daemonsSelections"
+ [messages]="daemonsMessages"
+ (selection)="onDaemonSelection()"
+ elemClass="btn btn-default pull-right">
+ <i class="fa fa-fw fa-plus"></i>
+ <ng-container i18n>Add daemon</ng-container>
+ </cd-select>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- FSAL -->
+ <div formGroupName="fsal">
+ <!-- Name -->
+ <div class="form-group"
+ [ngClass]="{'has-error': nfsForm.showError('name', formDir)}">
+ <label class="col-sm-3 control-label"
+ for="name">
+ <ng-container i18n>Storage Backend</ng-container>
+ <span class="required"></span>
+ </label>
+ <div class="col-sm-9">
+ <select class="form-control"
+ formControlName="name"
+ name="name"
+ id="name"
+ (change)="fsalChangeHandler()">
+ <option *ngIf="allFsals === null"
+ value=""
+ i18n>Loading...</option>
+ <option *ngIf="allFsals !== null && allFsals.length === 0"
+ value=""
+ i18n>-- No data pools available --</option>
+ <option *ngIf="allFsals !== null && allFsals.length > 0"
+ value=""
+ i18n>-- Select the storage backend --</option>
+ <option *ngFor="let fsal of allFsals"
+ [value]="fsal.value">{{ fsal.descr }}</option>
+ </select>
+ <span class="help-block"
+ *ngIf="nfsForm.showError('name', formDir, 'required')"
+ i18n>Required field</span>
+ </div>
+ </div>
+
+ <!-- RGW user -->
+ <div class="form-group"
+ [ngClass]="{'has-error': nfsForm.showError('rgw_user_id', formDir)}"
+ *ngIf="nfsForm.getValue('name') === 'RGW'">
+ <label class="col-sm-3 control-label"
+ for="rgw_user_id">
+ <ng-container i18n>Object Gateway User</ng-container>
+ <span class="required"></span>
+ </label>
+ <div class="col-sm-9">
+ <select class="form-control"
+ formControlName="rgw_user_id"
+ name="rgw_user_id"
+ id="rgw_user_id"
+ (change)="rgwUserIdChangeHandler()">
+ <option *ngIf="allRgwUsers === null"
+ value=""
+ i18n>Loading...</option>
+ <option *ngIf="allRgwUsers !== null && allRgwUsers.length === 0"
+ value=""
+ i18n>-- No users available --</option>
+ <option *ngIf="allRgwUsers !== null && allRgwUsers.length > 0"
+ value=""
+ i18n>-- Select the object gateway user --</option>
+ <option *ngFor="let rgwUserId of allRgwUsers"
+ [value]="rgwUserId">{{ rgwUserId }}</option>
+ </select>
+ <span class="help-block"
+ *ngIf="nfsForm.showError('rgw_user_id', formDir, 'required')"
+ i18n>Required field</span>
+ </div>
+ </div>
+
+ <!-- CephFS user_id -->
+ <div class="form-group"
+ [ngClass]="{'has-error': nfsForm.showError('user_id', formDir)}"
+ *ngIf="nfsForm.getValue('name') === 'CEPH'">
+ <label class="col-sm-3 control-label"
+ for="user_id">
+ <ng-container i18n>CephFS User ID</ng-container>
+ <span class="required"></span>
+ </label>
+ <div class="col-sm-9">
+ <select class="form-control"
+ formControlName="user_id"
+ name="user_id"
+ id="user_id">
+ <option *ngIf="allCephxClients === null"
+ value=""
+ i18n>Loading...</option>
+ <option *ngIf="allCephxClients !== null && allCephxClients.length === 0"
+ value=""
+ i18n>-- No clients available --</option>
+ <option *ngIf="allCephxClients !== null && allCephxClients.length > 0"
+ value=""
+ i18n>-- Select the cephx client --</option>
+ <option *ngFor="let client of allCephxClients"
+ [value]="client">{{ client }}</option>
+ </select>
+ <span class="help-block"
+ *ngIf="nfsForm.showError('user_id', formDir, 'required')"
+ i18n>Required field</span>
+ </div>
+ </div>
+
+ <!-- CephFS fs_name -->
+ <div class="form-group"
+ [ngClass]="{'has-error': nfsForm.showError('fs_name', formDir)}"
+ *ngIf="nfsForm.getValue('name') === 'CEPH'">
+ <label class="col-sm-3 control-label"
+ for="fs_name">
+ <ng-container i18n>CephFS Name</ng-container>
+ <span class="required"></span>
+ </label>
+ <div class="col-sm-9">
+ <select class="form-control"
+ formControlName="fs_name"
+ name="fs_name"
+ id="fs_name"
+ (change)="rgwUserIdChangeHandler()">
+ <option *ngIf="allFsNames === null"
+ value=""
+ i18n>Loading...</option>
+ <option *ngIf="allFsNames !== null && allFsNames.length === 0"
+ value=""
+ i18n>-- No users available --</option>
+ <option *ngIf="allFsNames !== null && allFsNames.length > 0"
+ value=""
+ i18n>-- Select the object gateway user --</option>
+ <option *ngFor="let filesystem of allFsNames"
+ [value]="filesystem.name">{{ filesystem.name }}</option>
+ </select>
+ <span class="help-block"
+ *ngIf="nfsForm.showError('fs_name', formDir, 'required')"
+ i18n>Required field</span>
+ </div>
+ </div>
+ </div>
+
+ <!-- Secutiry Label -->
+ <div class="form-group"
+ [ngClass]="{'has-error': nfsForm.showError('security_label', formDir)}"
+ *ngIf="nfsForm.getValue('name') === 'CEPH'">
+ <label class="col-sm-3 control-label"
+ for="security_label">
+ <ng-container i18n>Security Label</ng-container>
+ <span class="required"
+ *ngIf="nfsForm.getValue('security_label')"></span>
+ </label>
+
+ <div class="col-sm-9">
+ <div class="checkbox checkbox-primary">
+ <input type="checkbox"
+ formControlName="security_label"
+ name="security_label"
+ id="security_label">
+ <label for="security_label"
+ i18n>Enable security label</label>
+ </div>
+
+ <br>
+
+ <input type="text"
+ *ngIf="nfsForm.getValue('security_label')"
+ class="form-control"
+ name="sec_label_xattr"
+ id="sec_label_xattr"
+ formControlName="sec_label_xattr">
+
+ <span class="help-block"
+ *ngIf="nfsForm.showError('sec_label_xattr', formDir, 'required')"
+ i18n>Required field</span>
+ </div>
+ </div>
+
+ <!-- Path -->
+ <div class="form-group"
+ [ngClass]="{'has-error': nfsForm.showError('path', formDir)}"
+ *ngIf="nfsForm.getValue('name') === 'CEPH'">
+ <label class="col-sm-3 control-label"
+ for="path">
+ <ng-container i18n>CephFS Path</ng-container>
+ <span class="required"></span>
+ </label>
+ <div class="col-sm-9">
+ <input type="text"
+ class="form-control"
+ name="path"
+ id="path"
+ formControlName="path"
+ [typeahead]="pathDataSource"
+ (typeaheadOnSelect)="pathChangeHandler()"
+ (blur)="pathChangeHandler()">
+ <span class="help-block"
+ *ngIf="nfsForm.showError('path', formDir, 'required')"
+ i18n>Required field</span>
+
+ <span class="help-block"
+ *ngIf="nfsForm.showError('path', formDir, 'pattern')"
+ i18n>Path need to start with a '/' and can be followed by a word</span>
+ <span class="help-block"
+ *ngIf="isNewDirectory && !nfsForm.showError('path', formDir)"
+ i18n>New directory will be created</span>
+ </div>
+ </div>
+
+ <!-- Bucket -->
+ <div class="form-group"
+ [ngClass]="{'has-error': nfsForm.showError('path', formDir)}"
+ *ngIf="nfsForm.getValue('name') === 'RGW'">
+ <label class="col-sm-3 control-label"
+ for="path">
+ <ng-container i18n>Path</ng-container>
+ <span class="required"></span>
+ </label>
+ <div class="col-sm-9">
+ <input type="text"
+ class="form-control"
+ name="path"
+ id="path"
+ formControlName="path"
+ [typeahead]="bucketDataSource"
+ (typeaheadOnSelect)="bucketChangeHandler()"
+ (blur)="bucketChangeHandler()">
+
+ <span class="help-block"
+ *ngIf="nfsForm.showError('path', formDir, 'required')"
+ i18n>Required field</span>
+
+ <span class="help-block"
+ *ngIf="nfsForm.showError('path', formDir, 'pattern')"
+ i18n>Path can only be a single '/' or a word</span>
+
+ <span class="help-block"
+ *ngIf="isNewBucket && !nfsForm.showError('path', formDir)"
+ i18n>New bucket will be created</span>
+ </div>
+ </div>
+
+ <!-- NFS Protocol -->
+ <div class="form-group"
+ [ngClass]="{'has-error': nfsForm.showError('protocolNfsv3', formDir) || nfsForm.showError('protocolNfsv4', formDir)}">
+ <label class="col-sm-3 control-label"
+ for="protocols">
+ <ng-container i18n>NFS Protocol</ng-container>
+ <span class="required"></span>
+ </label>
+ <div class="col-sm-9">
+ <div class="checkbox checkbox-primary">
+ <input type="checkbox"
+ id="protocolNfsv3"
+ name="protocolNfsv3"
+ formControlName="protocolNfsv3">
+ <label i18n
+ for="protocolNfsv3">NFSv3</label>
+ </div>
+ <div class="checkbox checkbox-primary">
+ <input type="checkbox"
+ formControlName="protocolNfsv4"
+ name="protocolNfsv4"
+ id="protocolNfsv4">
+ <label i18n
+ for="protocolNfsv4">NFSv4</label>
+ </div>
+ <span class="help-block"
+ *ngIf="nfsForm.showError('protocolNfsv3', formDir, 'required') ||
+ nfsForm.showError('protocolNfsv4', formDir, 'required')"
+ i18n>Required field</span>
+ </div>
+ </div>
+
+ <!-- Tag -->
+ <div class="form-group"
+ *ngIf="nfsForm.getValue('protocolNfsv3')">
+ <label class="col-sm-3 control-label"
+ for="tag">
+ <ng-container i18n>NFS Tag</ng-container>
+ <cd-helper>
+ <p i18n>Alternative access for <strong>NFS v3</strong> mounts (it must not have a leading /).</p>
+ <p i18n>Clients may not mount subdirectories (i.e. if Tag = foo, the client may not mount foo/baz).</p>
+ <p i18n>By using different Tag options, the same Path may be exported multiple times.</p>
+ </cd-helper>
+ </label>
+ <div class="col-sm-9">
+ <input type="text"
+ class="form-control"
+ name="tag"
+ id="tag"
+ formControlName="tag">
+ </div>
+ </div>
+
+ <!-- Pseudo -->
+ <div class="form-group"
+ [ngClass]="{'has-error': nfsForm.showError('pseudo', formDir)}"
+ *ngIf="nfsForm.getValue('protocolNfsv4')">
+ <label class="col-sm-3 control-label"
+ for="pseudo">
+ <ng-container i18n>Pseudo</ng-container>
+ <span class="required"></span>
+ <cd-helper>
+ <p i18n>The position that this <strong>NFS v4</strong> export occupies
+ in the <strong>Pseudo FS</strong> (it must be unique).</p>
+ <p i18n>By using different Pseudo options, the same Path may be exported multiple times.</p>
+ </cd-helper>
+ </label>
+ <div class="col-sm-9">
+ <input type="text"
+ class="form-control"
+ name="pseudo"
+ id="pseudo"
+ formControlName="pseudo">
+ <span class="help-block"
+ *ngIf="nfsForm.showError('pseudo', formDir, 'required')"
+ i18n>Required field</span>
+ <span class="help-block"
+ *ngIf="nfsForm.showError('pseudo', formDir, 'pattern')"
+ i18n>Wrong format</span>
+ </div>
+ </div>
+
+ <!-- Access Type -->
+ <div class="form-group"
+ [ngClass]="{'has-error': nfsForm.showError('access_type', formDir)}">
+ <label class="col-sm-3 control-label"
+ for="access_type">
+ <ng-container i18n>Access Type</ng-container>
+ <span class="required"></span>
+ </label>
+ <div class="col-sm-9">
+ <select class="form-control"
+ formControlName="access_type"
+ name="access_type"
+ id="access_type">
+ <option *ngIf="nfsAccessType === null"
+ value=""
+ i18n>Loading...</option>
+ <option *ngIf="nfsAccessType !== null && nfsAccessType.length === 0"
+ value=""
+ i18n>-- No access type available --</option>
+ <option *ngIf="nfsAccessType !== null && nfsAccessType.length > 0"
+ value=""
+ i18n>-- Select the access type --</option>
+ <option *ngFor="let accessType of nfsAccessType"
+ [value]="accessType.value">{{ accessType.value }}</option>
+ </select>
+ <span class="help-block"
+ *ngIf="nfsForm.getValue('access_type')">
+ {{ getAccessTypeHelp(nfsForm.getValue('access_type')) }}
+ </span>
+ <span class="help-block"
+ *ngIf="nfsForm.showError('access_type', formDir, 'required')"
+ i18n>Required field</span>
+ </div>
+ </div>
+
+ <!-- Squash -->
+ <div class="form-group"
+ [ngClass]="{'has-error': nfsForm.showError('squash', formDir)}">
+ <label class="col-sm-3 control-label"
+ for="squash">
+ <ng-container i18n>Squash</ng-container>
+ <span class="required"></span>
+ </label>
+ <div class="col-sm-9">
+ <select class="form-control"
+ name="squash"
+ formControlName="squash"
+ id="squash">
+ <option *ngIf="nfsSquash === null"
+ value=""
+ i18n>Loading...</option>
+ <option *ngIf="nfsSquash !== null && nfsSquash.length === 0"
+ value=""
+ i18n>-- No squash available --</option>
+ <option *ngIf="nfsSquash !== null && nfsSquash.length > 0"
+ value=""
+ i18n>--Select what kind of user id squashing is performed --</option>
+ <option *ngFor="let squash of nfsSquash"
+ [value]="squash">{{ squash }}</option>
+
+ </select>
+ <span class="help-block"
+ *ngIf="nfsForm.showError('squash', formDir,'required')"
+ i18n>Required field</span>
+ </div>
+ </div>
+
+ <!-- Transport Protocol -->
+ <div class="form-group"
+ [ngClass]="{'has-error': nfsForm.showError('transportUDP', formDir) || nfsForm.showError('transportTCP', formDir)}">
+ <label class="col-sm-3 control-label"
+ for="transports">
+ <ng-container i18n>Transport Protocol</ng-container>
+ <span class="required"></span>
+ </label>
+ <div class="col-sm-9">
+ <div class="checkbox checkbox-primary">
+ <input type="checkbox"
+ formControlName="transportUDP"
+ name="transportUDP"
+ id="transportUDP">
+ <label for="transportUDP"
+ i18n>UDP</label>
+ </div>
+ <div class="checkbox checkbox-primary">
+ <input type="checkbox"
+ formControlName="transportTCP"
+ name="transportTCP"
+ id="transportTCP">
+ <label for="transportTCP"
+ i18n>TCP</label>
+ </div>
+ <span class="help-block"
+ *ngIf="nfsForm.showError('transportUDP', formDir, 'required') ||
+ nfsForm.showError('transportTCP', formDir, 'required')"
+ i18n>Required field</span>
+ <hr>
+ </div>
+ </div>
+
+ <!-- Clients -->
+ <cd-nfs-form-client [form]="nfsForm"
+ #nfsClients>
+ </cd-nfs-form-client>
+
+ </div>
+
+ <div class="panel-footer">
+ <div class="button-group text-right">
+ <cd-submit-button [form]="formDir"
+ type="button"
+ (submitAction)="submitAction()">
+ <ng-container i18n>Submit</ng-container>
+ </cd-submit-button>
+ <button type="button"
+ class="btn btn-sm btn-default"
+ (click)="cancelAction()"
+ i18n>Back</button>
+ </div>
+ </div>
+ </div>
+ </form>
+</div>
--- /dev/null
+.cd-mb {
+ margin-bottom: 10px;
+}
--- /dev/null
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ReactiveFormsModule } from '@angular/forms';
+import { ActivatedRoute } from '@angular/router';
+import { RouterTestingModule } from '@angular/router/testing';
+
+import { ToastModule } from 'ng2-toastr';
+import { TypeaheadModule } from 'ngx-bootstrap/typeahead';
+
+import { ActivatedRouteStub } from '../../../../testing/activated-route-stub';
+import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
+import { SummaryService } from '../../../shared/services/summary.service';
+import { SharedModule } from '../../../shared/shared.module';
+import { NfsFormClientComponent } from '../nfs-form-client/nfs-form-client.component';
+import { NfsFormComponent } from './nfs-form.component';
+
+describe('NfsFormComponent', () => {
+ let component: NfsFormComponent;
+ let fixture: ComponentFixture<NfsFormComponent>;
+ let httpTesting: HttpTestingController;
+ let activatedRoute: ActivatedRouteStub;
+
+ configureTestBed(
+ {
+ declarations: [NfsFormComponent, NfsFormClientComponent],
+ imports: [
+ HttpClientTestingModule,
+ ReactiveFormsModule,
+ RouterTestingModule,
+ SharedModule,
+ ToastModule.forRoot(),
+ TypeaheadModule.forRoot()
+ ],
+ providers: [
+ {
+ provide: ActivatedRoute,
+ useValue: new ActivatedRouteStub({ cluster_id: undefined, export_id: undefined })
+ },
+ i18nProviders
+ ]
+ },
+ true
+ );
+
+ beforeEach(() => {
+ const summaryService = TestBed.get(SummaryService);
+ spyOn(summaryService, 'refresh').and.callFake(() => true);
+
+ fixture = TestBed.createComponent(NfsFormComponent);
+ component = fixture.componentInstance;
+ httpTesting = TestBed.get(HttpTestingController);
+ activatedRoute = TestBed.get(ActivatedRoute);
+ fixture.detectChanges();
+
+ httpTesting.expectOne('api/summary').flush([]);
+ httpTesting
+ .expectOne('api/nfs-ganesha/daemon')
+ .flush([
+ { daemon_id: 'node1', cluster_id: 'cluster1' },
+ { daemon_id: 'node2', cluster_id: 'cluster1' },
+ { daemon_id: 'node5', cluster_id: 'cluster2' }
+ ]);
+ httpTesting.expectOne('ui-api/nfs-ganesha/fsals').flush(['CEPH', 'RGW']);
+ httpTesting.expectOne('ui-api/nfs-ganesha/cephx/clients').flush(['admin', 'fs', 'rgw']);
+ httpTesting.expectOne('ui-api/nfs-ganesha/cephfs/filesystems').flush([{ id: 1, name: 'a' }]);
+ httpTesting.expectOne('api/rgw/user').flush(['test', 'dev']);
+ httpTesting.expectOne('api/rgw/user/dev').flush({ suspended: 0, user_id: 'dev', keys: ['a'] });
+ httpTesting
+ .expectOne('api/rgw/user/test')
+ .flush({ suspended: 1, user_id: 'test', keys: ['a'] });
+ httpTesting.verify();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should process all data', () => {
+ expect(component.allDaemons).toEqual({ cluster1: ['node1', 'node2'], cluster2: ['node5'] });
+ expect(component.isDefaultCluster).toEqual(false);
+ expect(component.allFsals).toEqual([
+ { descr: 'CephFS', value: 'CEPH' },
+ { descr: 'Object Gateway', value: 'RGW' }
+ ]);
+ expect(component.allCephxClients).toEqual(['admin', 'fs', 'rgw']);
+ expect(component.allFsNames).toEqual([{ id: 1, name: 'a' }]);
+ expect(component.allRgwUsers).toEqual(['dev']);
+ });
+
+ it('should create the form', () => {
+ expect(component.nfsForm.value).toEqual({
+ access_type: 'RW',
+ clients: [],
+ cluster_id: '',
+ daemons: [],
+ fsal: { fs_name: 'a', name: '', rgw_user_id: '', user_id: '' },
+ path: '',
+ protocolNfsv3: true,
+ protocolNfsv4: true,
+ pseudo: '',
+ sec_label_xattr: 'security.selinux',
+ security_label: false,
+ squash: 'None',
+ tag: '',
+ transportTCP: true,
+ transportUDP: true
+ });
+ });
+
+ it('should prepare data when selecting an cluster', () => {
+ expect(component.allDaemons).toEqual({ cluster1: ['node1', 'node2'], cluster2: ['node5'] });
+ expect(component.daemonsSelections).toEqual([]);
+
+ component.nfsForm.patchValue({ cluster_id: 'cluster1' });
+ component.onClusterChange();
+
+ expect(component.daemonsSelections).toEqual([
+ { description: '', name: 'node1', selected: false },
+ { description: '', name: 'node2', selected: false }
+ ]);
+ });
+
+ it('should clean data when changing cluster', () => {
+ component.nfsForm.patchValue({ cluster_id: 'cluster1', daemons: ['node1'] });
+ component.nfsForm.patchValue({ cluster_id: 'node2' });
+ component.onClusterChange();
+
+ expect(component.nfsForm.getValue('daemons')).toEqual([]);
+ });
+
+ describe('should submit request', () => {
+ beforeEach(() => {
+ component.nfsForm.patchValue({
+ access_type: 'RW',
+ clients: [],
+ cluster_id: 'cluster1',
+ daemons: ['node2'],
+ fsal: { name: 'CEPH', user_id: 'fs', fs_name: 1, rgw_user_id: '' },
+ path: '/foo',
+ protocolNfsv3: true,
+ protocolNfsv4: true,
+ pseudo: '/baz',
+ squash: 'no_root_squash',
+ tag: 'bar',
+ transportTCP: true,
+ transportUDP: true
+ });
+ });
+
+ it('should call update', () => {
+ activatedRoute.setParams({ cluster_id: 'cluster1', export_id: '1' });
+ component.isEdit = true;
+ component.cluster_id = 'cluster1';
+ component.export_id = '1';
+ component.nfsForm.patchValue({ export_id: 1 });
+ component.submitAction();
+
+ const req = httpTesting.expectOne('api/nfs-ganesha/export/cluster1/1');
+ expect(req.request.method).toBe('PUT');
+ expect(req.request.body).toEqual({
+ access_type: 'RW',
+ clients: [],
+ cluster_id: 'cluster1',
+ daemons: ['node2'],
+ export_id: '1',
+ fsal: { fs_name: 1, name: 'CEPH', sec_label_xattr: null, user_id: 'fs' },
+ path: '/foo',
+ protocols: [3, 4],
+ pseudo: '/baz',
+ security_label: false,
+ squash: 'no_root_squash',
+ tag: 'bar',
+ transports: ['TCP', 'UDP']
+ });
+ });
+
+ it('should call create', () => {
+ activatedRoute.setParams({ cluster_id: undefined, export_id: undefined });
+ component.submitAction();
+
+ const req = httpTesting.expectOne('api/nfs-ganesha/export');
+ expect(req.request.method).toBe('POST');
+ expect(req.request.body).toEqual({
+ access_type: 'RW',
+ clients: [],
+ cluster_id: 'cluster1',
+ daemons: ['node2'],
+ fsal: {
+ fs_name: 1,
+ name: 'CEPH',
+ sec_label_xattr: null,
+ user_id: 'fs'
+ },
+ path: '/foo',
+ protocols: [3, 4],
+ pseudo: '/baz',
+ security_label: false,
+ squash: 'no_root_squash',
+ tag: 'bar',
+ transports: ['TCP', 'UDP']
+ });
+ });
+ });
+});
--- /dev/null
+import { ChangeDetectorRef, Component, OnInit, ViewChild } from '@angular/core';
+import { FormControl, Validators } from '@angular/forms';
+import { ActivatedRoute, Router } from '@angular/router';
+
+import { I18n } from '@ngx-translate/i18n-polyfill';
+import * as _ from 'lodash';
+import { forkJoin, Observable, of } from 'rxjs';
+import { map, mergeMap } from 'rxjs/operators';
+
+import { NfsService } from '../../../shared/api/nfs.service';
+import { RgwUserService } from '../../../shared/api/rgw-user.service';
+import { SelectMessages } from '../../../shared/components/select/select-messages.model';
+import { SelectOption } from '../../../shared/components/select/select-option.model';
+import { CdFormBuilder } from '../../../shared/forms/cd-form-builder';
+import { CdFormGroup } from '../../../shared/forms/cd-form-group';
+import { CdValidators } from '../../../shared/forms/cd-validators';
+import { FinishedTask } from '../../../shared/models/finished-task';
+import { Permission } from '../../../shared/models/permissions';
+import { AuthStorageService } from '../../../shared/services/auth-storage.service';
+import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
+import { NfsFormClientComponent } from '../nfs-form-client/nfs-form-client.component';
+
+@Component({
+ selector: 'cd-nfs-form',
+ templateUrl: './nfs-form.component.html',
+ styleUrls: ['./nfs-form.component.scss']
+})
+export class NfsFormComponent implements OnInit {
+ @ViewChild('nfsClients')
+ nfsClients: NfsFormClientComponent;
+
+ permission: Permission;
+ nfsForm: CdFormGroup;
+ isEdit = false;
+
+ cluster_id = null;
+ export_id = null;
+
+ isNewDirectory = false;
+ isNewBucket = false;
+ isDefaultCluster = false;
+
+ allClusters: string[] = null;
+ allDaemons = {};
+
+ allFsals: any[] = [];
+ allRgwUsers: any[] = [];
+ allCephxClients: any[] = null;
+ allFsNames: any[] = null;
+
+ nfsAccessType: any[] = this.nfsService.nfsAccessType;
+ nfsSquash: any[] = this.nfsService.nfsSquash;
+
+ daemonsSelections: SelectOption[] = [];
+ daemonsMessages = new SelectMessages(
+ { noOptions: this.i18n('There are no daemons available.') },
+ this.i18n
+ );
+
+ pathDataSource: Observable<any> = Observable.create((observer: any) => {
+ observer.next(this.nfsForm.getValue('path'));
+ }).pipe(
+ mergeMap((token: string) => this.getPathTypeahead(token)),
+ map((val: any) => val.paths)
+ );
+
+ bucketDataSource: Observable<any> = Observable.create((observer: any) => {
+ observer.next(this.nfsForm.getValue('path'));
+ }).pipe(mergeMap((token: string) => this.getBucketTypeahead(token)));
+
+ constructor(
+ private authStorageService: AuthStorageService,
+ private nfsService: NfsService,
+ private route: ActivatedRoute,
+ private router: Router,
+ private rgwUserService: RgwUserService,
+ private formBuilder: CdFormBuilder,
+ private taskWrapper: TaskWrapperService,
+ private cdRef: ChangeDetectorRef,
+ private i18n: I18n
+ ) {
+ this.permission = this.authStorageService.getPermissions().pool;
+ this.createForm();
+ }
+
+ ngOnInit() {
+ const promises: any[] = [
+ this.nfsService.daemon(),
+ this.nfsService.fsals(),
+ this.nfsService.clients(),
+ this.nfsService.filesystems()
+ ];
+
+ if (this.router.url.startsWith('/nfs/edit')) {
+ this.isEdit = true;
+ }
+
+ if (this.isEdit) {
+ this.route.params.subscribe((params: { cluster_id: string; export_id: string }) => {
+ this.cluster_id = decodeURIComponent(params.cluster_id);
+ this.export_id = decodeURIComponent(params.export_id);
+ promises.push(this.nfsService.get(this.cluster_id, this.export_id));
+
+ this.getData(promises);
+ });
+ } else {
+ this.getData(promises);
+ }
+ }
+
+ getData(promises) {
+ forkJoin(promises).subscribe(
+ (data: any[]) => {
+ this.resolveDaemons(data[0]);
+ this.resolvefsals(data[1]);
+ this.resolveClients(data[2]);
+ this.resolveFilesystems(data[3]);
+ if (data[4]) {
+ this.resolveModel(data[4]);
+ }
+ },
+ (error) => {
+ // this.error = error;
+ }
+ );
+ }
+
+ createForm() {
+ this.nfsForm = new CdFormGroup({
+ cluster_id: new FormControl('', {
+ validators: [Validators.required]
+ }),
+ daemons: new FormControl([]),
+ fsal: new CdFormGroup({
+ name: new FormControl('', {
+ validators: [Validators.required]
+ }),
+ user_id: new FormControl('', {
+ validators: [
+ CdValidators.requiredIf({
+ name: 'CEPH'
+ })
+ ]
+ }),
+ fs_name: new FormControl('', {
+ validators: [
+ CdValidators.requiredIf({
+ name: 'CEPH'
+ })
+ ]
+ }),
+ rgw_user_id: new FormControl('', {
+ validators: [
+ CdValidators.requiredIf({
+ name: 'RGW'
+ })
+ ]
+ })
+ }),
+ path: new FormControl(''),
+ protocolNfsv3: new FormControl(true, {
+ validators: [
+ CdValidators.requiredIf({ protocolNfsv4: false }, (value) => {
+ return !value;
+ })
+ ]
+ }),
+ protocolNfsv4: new FormControl(true, {
+ validators: [
+ CdValidators.requiredIf({ protocolNfsv3: false }, (value) => {
+ return !value;
+ })
+ ]
+ }),
+ tag: new FormControl(''),
+ pseudo: new FormControl('', {
+ validators: [Validators.required, Validators.pattern('^/[^><|&()]*$')]
+ }),
+ access_type: new FormControl('RW', {
+ validators: [Validators.required]
+ }),
+ squash: new FormControl('None', {
+ validators: [Validators.required]
+ }),
+ transportUDP: new FormControl(true, {
+ validators: [
+ CdValidators.requiredIf({ transportTCP: false }, (value) => {
+ return !value;
+ })
+ ]
+ }),
+ transportTCP: new FormControl(true, {
+ validators: [
+ CdValidators.requiredIf({ transportUDP: false }, (value) => {
+ return !value;
+ })
+ ]
+ }),
+ clients: this.formBuilder.array([]),
+ security_label: new FormControl(false),
+ sec_label_xattr: new FormControl(
+ 'security.selinux',
+ CdValidators.requiredIf({ security_label: true, 'fsal.name': 'CEPH' })
+ )
+ });
+ }
+
+ resolveModel(res) {
+ if (res.fsal.name === 'CEPH') {
+ res.sec_label_xattr = res.fsal.sec_label_xattr;
+ }
+
+ this.daemonsSelections = _.map(
+ this.allDaemons[res.cluster_id],
+ (daemon) => new SelectOption(res.daemons.indexOf(daemon) !== -1, daemon, '')
+ );
+ this.daemonsSelections = [...this.daemonsSelections];
+
+ res.protocolNfsv3 = res.protocols.indexOf(3) !== -1;
+ res.protocolNfsv4 = res.protocols.indexOf(4) !== -1;
+ delete res.protocols;
+
+ res.transportTCP = res.transports.indexOf('TCP') !== -1;
+ res.transportUDP = res.transports.indexOf('UDP') !== -1;
+ delete res.transports;
+
+ res.clients.forEach((client) => {
+ let addressStr = '';
+ client.addresses.forEach((address) => {
+ addressStr += address + ', ';
+ });
+ if (addressStr.length >= 2) {
+ addressStr = addressStr.substring(0, addressStr.length - 2);
+ }
+ client.addresses = addressStr;
+ });
+
+ this.nfsForm.patchValue(res);
+ this.setPathValidation();
+ this.nfsClients.resolveModel(res.clients);
+ }
+
+ resolveDaemons(daemons) {
+ daemons = _.sortBy(daemons, ['daemon_id']);
+
+ this.allClusters = _(daemons)
+ .map((daemon) => daemon.cluster_id)
+ .sortedUniq()
+ .value();
+
+ _.forEach(this.allClusters, (cluster) => {
+ this.allDaemons[cluster] = [];
+ });
+
+ _.forEach(daemons, (daemon) => {
+ this.allDaemons[daemon.cluster_id].push(daemon.daemon_id);
+ });
+
+ const hasOneCluster = _.isArray(this.allClusters) && this.allClusters.length === 1;
+ this.isDefaultCluster = hasOneCluster && this.allClusters[0] === '_default_';
+ if (hasOneCluster) {
+ this.nfsForm.patchValue({
+ cluster_id: this.allClusters[0]
+ });
+ this.onClusterChange();
+ }
+ }
+
+ resolvefsals(res: string[]) {
+ res.forEach((fsal) => {
+ const fsalItem = this.nfsService.nfsFsal.find((currentFsalItem) => {
+ return fsal === currentFsalItem.value;
+ });
+
+ if (_.isObjectLike(fsalItem)) {
+ this.allFsals.push(fsalItem);
+ if (fsalItem.value === 'RGW') {
+ this.rgwUserService.list().subscribe((result: any) => {
+ result.forEach((user) => {
+ if (user.suspended === 0 && user.keys.length > 0) {
+ this.allRgwUsers.push(user.user_id);
+ }
+ });
+ });
+ }
+ }
+ });
+
+ if (this.allFsals.length === 1 && _.isUndefined(this.nfsForm.getValue('fsal'))) {
+ this.nfsForm.patchValue({
+ fsal: this.allFsals[0]
+ });
+ }
+ }
+
+ resolveClients(clients) {
+ this.allCephxClients = clients;
+ }
+
+ resolveFilesystems(filesystems) {
+ this.allFsNames = filesystems;
+ if (filesystems.length === 1) {
+ this.nfsForm.patchValue({
+ fsal: {
+ fs_name: filesystems[0].name
+ }
+ });
+ }
+ }
+
+ fsalChangeHandler() {
+ this.nfsForm.patchValue({
+ tag: this._generateTag(),
+ pseudo: this._generatePseudo()
+ });
+
+ this.setPathValidation();
+
+ this.cdRef.detectChanges();
+ }
+
+ setPathValidation() {
+ if (this.nfsForm.getValue('name') === 'RGW') {
+ this.nfsForm
+ .get('path')
+ .setValidators([Validators.required, Validators.pattern('^(/|[^/><|&()#?]+)$')]);
+ } else {
+ this.nfsForm
+ .get('path')
+ .setValidators([Validators.required, Validators.pattern('^/[^><|&()?]*$')]);
+ }
+ }
+
+ rgwUserIdChangeHandler() {
+ this.nfsForm.patchValue({
+ pseudo: this._generatePseudo()
+ });
+ }
+
+ getAccessTypeHelp(accessType) {
+ const accessTypeItem = this.nfsAccessType.find((currentAccessTypeItem) => {
+ if (accessType === currentAccessTypeItem.value) {
+ return currentAccessTypeItem;
+ }
+ });
+ return _.isObjectLike(accessTypeItem) ? accessTypeItem.help : '';
+ }
+
+ getId() {
+ if (
+ _.isString(this.nfsForm.getValue('cluster_id')) &&
+ _.isString(this.nfsForm.getValue('path'))
+ ) {
+ return this.nfsForm.getValue('cluster_id') + ':' + this.nfsForm.getValue('path');
+ }
+ return '';
+ }
+
+ getPathTypeahead(path) {
+ if (!_.isString(path) || path === '/') {
+ return of([]);
+ }
+
+ return this.nfsService.lsDir(path);
+ }
+
+ pathChangeHandler() {
+ this.nfsForm.patchValue({
+ pseudo: this._generatePseudo()
+ });
+
+ const path = this.nfsForm.getValue('path');
+ this.getPathTypeahead(path).subscribe((res: any) => {
+ this.isNewDirectory = path !== '/' && res.paths.indexOf(path) === -1;
+ });
+ }
+
+ bucketChangeHandler() {
+ this.nfsForm.patchValue({
+ tag: this._generateTag(),
+ pseudo: this._generatePseudo()
+ });
+
+ const bucket = this.nfsForm.getValue('path');
+ this.getBucketTypeahead(bucket).subscribe((res: any) => {
+ this.isNewBucket = bucket !== '' && res.indexOf(bucket) === -1;
+ });
+ }
+
+ getBucketTypeahead(path: string): Observable<any> {
+ const rgwUserId = this.nfsForm.getValue('rgw_user_id');
+
+ if (_.isString(rgwUserId) && _.isString(path) && path !== '/' && path !== '') {
+ return this.nfsService.buckets(rgwUserId);
+ } else {
+ return of([]);
+ }
+ }
+
+ _generateTag() {
+ let newTag = this.nfsForm.getValue('tag');
+ if (!this.nfsForm.get('tag').dirty) {
+ newTag = undefined;
+ if (this.nfsForm.getValue('fsal') === 'RGW') {
+ newTag = this.nfsForm.getValue('path');
+ }
+ }
+ return newTag;
+ }
+
+ _generatePseudo() {
+ let newPseudo = this.nfsForm.getValue('pseudo');
+ if (this.nfsForm.get('pseudo') && !this.nfsForm.get('pseudo').dirty) {
+ newPseudo = undefined;
+ if (this.nfsForm.getValue('fsal') === 'CEPH') {
+ newPseudo = '/cephfs';
+ if (_.isString(this.nfsForm.getValue('path'))) {
+ newPseudo += this.nfsForm.getValue('path');
+ }
+ } else if (this.nfsForm.getValue('fsal') === 'RGW') {
+ if (_.isString(this.nfsForm.getValue('rgw_user_id'))) {
+ newPseudo = '/' + this.nfsForm.getValue('rgw_user_id');
+ if (_.isString(this.nfsForm.getValue('path'))) {
+ newPseudo += '/' + this.nfsForm.getValue('path');
+ }
+ }
+ }
+ }
+ return newPseudo;
+ }
+
+ onClusterChange() {
+ const cluster_id = this.nfsForm.getValue('cluster_id');
+ this.daemonsSelections = _.map(
+ this.allDaemons[cluster_id],
+ (daemon) => new SelectOption(false, daemon, '')
+ );
+ this.daemonsSelections = [...this.daemonsSelections];
+ this.nfsForm.patchValue({ daemons: [] });
+ }
+
+ removeDaemon(index, daemon) {
+ this.daemonsSelections.forEach((value) => {
+ if (value.name === daemon) {
+ value.selected = false;
+ }
+ });
+
+ const daemons = this.nfsForm.get('daemons');
+ daemons.value.splice(index, 1);
+ daemons.setValue(daemons.value);
+
+ return false;
+ }
+
+ onDaemonSelection() {
+ this.nfsForm.get('daemons').setValue(this.nfsForm.getValue('daemons'));
+ }
+
+ submitAction() {
+ let action: Observable<any>;
+ const requestModel = this._buildRequest();
+
+ if (this.isEdit) {
+ action = this.taskWrapper.wrapTaskAroundCall({
+ task: new FinishedTask('nfs/edit', {
+ cluster_id: this.cluster_id,
+ export_id: this.export_id
+ }),
+ call: this.nfsService.update(this.cluster_id, this.export_id, requestModel)
+ });
+ } else {
+ // Create
+ action = this.taskWrapper.wrapTaskAroundCall({
+ task: new FinishedTask('nfs/create', {
+ path: requestModel.path,
+ fsal: requestModel.fsal,
+ cluster_id: requestModel.cluster_id
+ }),
+ call: this.nfsService.create(requestModel)
+ });
+ }
+
+ action.subscribe(
+ undefined,
+ () => this.nfsForm.setErrors({ cdSubmitButton: true }),
+ () => this.router.navigate(['/nfs'])
+ );
+ }
+
+ _buildRequest() {
+ const requestModel: any = _.cloneDeep(this.nfsForm.value);
+
+ if (_.isUndefined(requestModel.tag) || requestModel.tag === '') {
+ requestModel.tag = null;
+ }
+
+ if (this.isEdit) {
+ requestModel.export_id = this.export_id;
+ }
+
+ if (requestModel.fsal.name === 'CEPH') {
+ delete requestModel.fsal.rgw_user_id;
+ } else {
+ delete requestModel.fsal.fs_name;
+ delete requestModel.fsal.user_id;
+ }
+
+ requestModel.protocols = [];
+ if (requestModel.protocolNfsv3) {
+ delete requestModel.protocolNfsv3;
+ requestModel.protocols.push(3);
+ } else {
+ requestModel.tag = null;
+ }
+ if (requestModel.protocolNfsv4) {
+ delete requestModel.protocolNfsv4;
+ requestModel.protocols.push(4);
+ } else {
+ requestModel.pseudo = null;
+ }
+
+ requestModel.transports = [];
+ if (requestModel.transportTCP) {
+ delete requestModel.transportTCP;
+ requestModel.transports.push('TCP');
+ }
+ if (requestModel.transportUDP) {
+ delete requestModel.transportUDP;
+ requestModel.transports.push('UDP');
+ }
+
+ requestModel.clients.forEach((client) => {
+ if (_.isString(client.addresses)) {
+ client.addresses = _(client.addresses)
+ .split(/[ ,]+/)
+ .uniq()
+ .filter((address) => address !== '')
+ .value();
+ } else {
+ client.addresses = [];
+ }
+ });
+
+ if (requestModel.security_label === false || requestModel.fsal.name === 'RGW') {
+ requestModel.fsal.sec_label_xattr = null;
+ } else {
+ requestModel.fsal.sec_label_xattr = requestModel.sec_label_xattr;
+ }
+ delete requestModel.sec_label_xattr;
+
+ return requestModel;
+ }
+
+ cancelAction() {
+ this.router.navigate(['/nfs']);
+ }
+}
--- /dev/null
+import { OrdinalPipe } from './ordinal.pipe';
+
+describe('OrdinalPipe', () => {
+ it('create an instance', () => {
+ const pipe = new OrdinalPipe();
+ expect(pipe).toBeTruthy();
+ });
+});
--- /dev/null
+import { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({
+ name: 'ordinal'
+})
+export class OrdinalPipe implements PipeTransform {
+ transform(value: any): any {
+ const num = parseInt(value, 10);
+ if (isNaN(num)) {
+ return value;
+ }
+ return (
+ value +
+ (Math.floor(num / 10) === 1
+ ? 'th'
+ : num % 10 === 1
+ ? 'st'
+ : num % 10 === 2
+ ? 'nd'
+ : num % 10 === 3
+ ? 'rd'
+ : 'th')
+ );
+ }
+}
import { HealthColorPipe } from './health-color.pipe';
import { ListPipe } from './list.pipe';
import { LogPriorityPipe } from './log-priority.pipe';
+import { OrdinalPipe } from './ordinal.pipe';
import { RelativeDatePipe } from './relative-date.pipe';
import { RoundPipe } from './round.pipe';
CdDatePipe,
EmptyPipe,
EncodeUriPipe,
- RoundPipe
+ RoundPipe,
+ OrdinalPipe
],
exports: [
DimlessBinaryPipe,
CdDatePipe,
EmptyPipe,
EncodeUriPipe,
- RoundPipe
+ RoundPipe,
+ OrdinalPipe
],
providers: [
DatePipe,
LogPriorityPipe,
CdDatePipe,
EmptyPipe,
- EncodeUriPipe
+ EncodeUriPipe,
+ OrdinalPipe
]
})
export class PipesModule {}
<context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
<context context-type="linenumber">47</context>
</context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">21</context>
+ </context-group>
</trans-unit><trans-unit id="099b441d49333b3c6d30b36dc0a4763e64c78920" datatype="html">
<source>Hosts</source>
<context-group purpose="location">
<context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
<context context-type="linenumber">158</context>
</context-group>
+ </trans-unit><trans-unit id="a24eabd99ea5af20f5f94c4484649cd30370042b" datatype="html">
+ <source>NFS</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
+ <context context-type="linenumber">168</context>
+ </context-group>
</trans-unit><trans-unit id="a4eff72d97b7ced051398d581f10968218057ddc" datatype="html">
<source>Filesystems</source>
<context-group purpose="location">
<context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
- <context context-type="linenumber">169</context>
+ <context context-type="linenumber">176</context>
</context-group>
</trans-unit><trans-unit id="2190548d236ca5f7bc7ab2bca334b860c5ff2ad4" datatype="html">
<source>Object Gateway</source>
<context-group purpose="location">
<context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
- <context context-type="linenumber">180</context>
+ <context context-type="linenumber">187</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-list/nfs-list.component.html</context>
+ <context context-type="linenumber">32</context>
</context-group>
</trans-unit><trans-unit id="9e24f9e2d42104ffc01599db4d566d1cc518f9e6" datatype="html">
<source>Daemons</source>
<context-group purpose="location">
<context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
- <context context-type="linenumber">189</context>
+ <context context-type="linenumber">196</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">app/ceph/block/iscsi/iscsi.component.html</context>
<context context-type="sourcefile">app/ceph/block/mirroring/overview/overview.component.html</context>
<context context-type="linenumber">5</context>
</context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">53</context>
+ </context-group>
</trans-unit><trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be" datatype="html">
<source>Users</source>
<context-group purpose="location">
<context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
- <context context-type="linenumber">195</context>
+ <context context-type="linenumber">202</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">app/core/auth/user-tabs/user-tabs.component.html</context>
<source>Buckets</source>
<context-group purpose="location">
<context context-type="sourcefile">app/core/navigation/navigation/navigation.component.html</context>
- <context context-type="linenumber">201</context>
+ <context context-type="linenumber">208</context>
</context-group>
</trans-unit><trans-unit id="797f8214e8148f4bf0d244baaa7341706b419549" datatype="html">
<source>Retrieving data<x id="START_TAG_SPAN_1" ctype="x-span" equiv-text="<span>"/> for
<context context-type="sourcefile">app/ceph/cluster/configuration/configuration-form/configuration-form.component.html</context>
<context context-type="linenumber">159</context>
</context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">520</context>
+ </context-group>
<context-group purpose="location">
<context context-type="sourcefile">app/ceph/pool/pool-form/pool-form.component.html</context>
<context context-type="linenumber">443</context>
<context context-type="sourcefile">app/ceph/cluster/osd/osd-scrub-modal/osd-scrub-modal.component.html</context>
<context context-type="linenumber">21</context>
</context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">515</context>
+ </context-group>
<context-group purpose="location">
<context context-type="sourcefile">app/ceph/block/mirroring/pool-edit-peer-modal/pool-edit-peer-modal.component.html</context>
<context context-type="linenumber">106</context>
<context context-type="sourcefile">app/ceph/block/rbd-form/rbd-form.component.html</context>
<context context-type="linenumber">139</context>
</context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">32</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">106</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">139</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">171</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">204</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">418</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">453</context>
+ </context-group>
<context-group purpose="location">
<context context-type="sourcefile">app/ceph/pool/erasure-code-profile-form/erasure-code-profile-form.component.html</context>
<context context-type="linenumber">59</context>
<context context-type="sourcefile">app/ceph/block/rbd-form/rbd-form.component.html</context>
<context context-type="linenumber">142</context>
</context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">109</context>
+ </context-group>
</trans-unit><trans-unit id="7faaaa08f56427999f3be41df1093ce4089bbd75" datatype="html">
<source>Size</source>
<context-group purpose="location">
<context context-type="sourcefile">app/ceph/cephfs/cephfs-detail/cephfs-detail.component.html</context>
<context context-type="linenumber">3</context>
</context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-details/nfs-details.component.html</context>
+ <context context-type="linenumber">2</context>
+ </context-group>
</trans-unit><trans-unit id="d2bcd3296d2850de762fb943060b7e086a893181" datatype="html">
<source>Health</source>
<context-group purpose="location">
<context context-type="sourcefile">app/ceph/dashboard/dashboard/dashboard.component.html</context>
<context context-type="linenumber">8</context>
</context-group>
+ </trans-unit><trans-unit id="7ffe39df9d88c972792bd8688b215392deb8313d" datatype="html">
+ <source>Clients</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form-client/nfs-form-client.component.html</context>
+ <context context-type="linenumber">3</context>
+ </context-group>
+ </trans-unit><trans-unit id="f2dae0bda66f6a349444951c0379c28cda47d6d1" datatype="html">
+ <source>Any client can access</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form-client/nfs-form-client.component.html</context>
+ <context context-type="linenumber">11</context>
+ </context-group>
+ </trans-unit><trans-unit id="7882f2edb1d4139800b276b6b0bbf5ae0b2234ef" datatype="html">
+ <source>Addresses</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form-client/nfs-form-client.component.html</context>
+ <context context-type="linenumber">32</context>
+ </context-group>
+ </trans-unit><trans-unit id="9bd96fcf50863e685c74d0490392f46689ffbdb6" datatype="html">
+ <source>Required field</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form-client/nfs-form-client.component.html</context>
+ <context context-type="linenumber">42</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">44</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">118</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">151</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">183</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">216</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">253</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">277</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">309</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">349</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">396</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">434</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">466</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">498</context>
+ </context-group>
+ </trans-unit><trans-unit id="a5f3f74c0f6925826cb2188576391c0da01a23f0" datatype="html">
+ <source>Must contain one or more comma-separated values</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form-client/nfs-form-client.component.html</context>
+ <context context-type="linenumber">45</context>
+ </context-group>
+ </trans-unit><trans-unit id="8bb5b2073697f3f4378c44a49b7524934c9268f4" datatype="html">
+ <source>For example:</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form-client/nfs-form-client.component.html</context>
+ <context context-type="linenumber">47</context>
+ </context-group>
+ </trans-unit><trans-unit id="8f969c655b3fbe4fba7e277caf4cd2c459f9fca5" datatype="html">
+ <source>Access Type</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form-client/nfs-form-client.component.html</context>
+ <context context-type="linenumber">57</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">408</context>
+ </context-group>
+ </trans-unit><trans-unit id="28952831a284cfe2b4fc39ca610e80b52598905a" datatype="html">
+ <source>Squash</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form-client/nfs-form-client.component.html</context>
+ <context context-type="linenumber">78</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">443</context>
+ </context-group>
+ </trans-unit><trans-unit id="0660ae339068979854ade34a96546980723dede3" datatype="html">
+ <source>Add clients</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form-client/nfs-form-client.component.html</context>
+ <context context-type="linenumber">99</context>
+ </context-group>
+ </trans-unit><trans-unit id="6ecb266eacdb295d503d754e8a4a4c631efa841b" datatype="html">
+ <source>NFS export <x id="INTERPOLATION" equiv-text="{{ export_id ? cluster_id + ':' + export_id : '' }}"/></source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">10</context>
+ </context-group>
+ </trans-unit><trans-unit id="135b91a2d908d5814b782695470a6a786c99d9d2" datatype="html">
+ <source>-- No cluster available --</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">35</context>
+ </context-group>
+ </trans-unit><trans-unit id="c501dba379f566885919240ea277b5bc10c14d18" datatype="html">
+ <source>-- Select the cluster --</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">38</context>
+ </context-group>
+ </trans-unit><trans-unit id="cf85b1ee58326aa9da63da41b2629c9db7c9a5b9" datatype="html">
+ <source>Add daemon</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">81</context>
+ </context-group>
+ </trans-unit><trans-unit id="b3f6ba7fe84d6508705cdfe234f0fcc8ff85c9cf" datatype="html">
+ <source>Storage Backend</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">95</context>
+ </context-group>
+ </trans-unit><trans-unit id="b6fee356d1db954255a56d8169405a89595246b9" datatype="html">
+ <source>-- Select the storage backend --</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">112</context>
+ </context-group>
+ </trans-unit><trans-unit id="76d67035c3ab3d8e56f725859f820f03fda41cfc" datatype="html">
+ <source>Object Gateway User</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">128</context>
+ </context-group>
+ </trans-unit><trans-unit id="fade7788bace74337f306ae209f10fc187ef4671" datatype="html">
+ <source>-- No users available --</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">142</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">207</context>
+ </context-group>
+ </trans-unit><trans-unit id="6d30b7b36cf8f6364167321bdb4ba35d4cefce7b" datatype="html">
+ <source>-- Select the object gateway user --</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">145</context>
+ </context-group>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">210</context>
+ </context-group>
+ </trans-unit><trans-unit id="589ce20d3ba3e3ac44f75decfaadc4ea8f0aec2d" datatype="html">
+ <source>CephFS User ID</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">161</context>
+ </context-group>
+ </trans-unit><trans-unit id="c4b88a53ac3b0ece46ba9b3ad72355a3c190cce7" datatype="html">
+ <source>-- No clients available --</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">174</context>
+ </context-group>
+ </trans-unit><trans-unit id="da52835b80497a0002d24414b057dc46ae44ce38" datatype="html">
+ <source>-- Select the cephx client --</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">177</context>
+ </context-group>
+ </trans-unit><trans-unit id="fd3419e8957d928d7f7ba19c93356a0dbff02871" datatype="html">
+ <source>CephFS Name</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">193</context>
+ </context-group>
+ </trans-unit><trans-unit id="957512d0321f73e9f115bce1bd823fa635170c41" datatype="html">
+ <source>Security Label</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">227</context>
+ </context-group>
+ </trans-unit><trans-unit id="65ce0fa4da1ed55e658aeb31d1644a29f06bb342" datatype="html">
+ <source>Enable security label</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">239</context>
+ </context-group>
+ </trans-unit><trans-unit id="7e808f804130c7b6ff719509cbc06ebb27393a48" datatype="html">
+ <source>CephFS Path</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">263</context>
+ </context-group>
+ </trans-unit><trans-unit id="5ecc0107badb6625466aaa3f975b5c05276f432f" datatype="html">
+ <source>Path need to start with a '/' and can be followed by a word</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">281</context>
+ </context-group>
+ </trans-unit><trans-unit id="2d02916f44fc63e13ab16d1cbe72aa6cb51feab3" datatype="html">
+ <source>New directory will be created</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">284</context>
+ </context-group>
+ </trans-unit><trans-unit id="766c66ad5cc981c531aaf3fe3a2a7a346ddc8d83" datatype="html">
+ <source>Path</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">294</context>
+ </context-group>
+ </trans-unit><trans-unit id="7ec35c722a50b976620f22612f7be619c12ceb90" datatype="html">
+ <source>Path can only be a single '/' or a word</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">313</context>
+ </context-group>
+ </trans-unit><trans-unit id="aebb6a5090c24511de4530195694bb3f3dcf0342" datatype="html">
+ <source>New bucket will be created</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">317</context>
+ </context-group>
+ </trans-unit><trans-unit id="bee6900143996c0e908a10564532eba3da0b30fb" datatype="html">
+ <source>NFS Protocol</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">326</context>
+ </context-group>
+ </trans-unit><trans-unit id="2f534178c01ebf1307da2eaeef04bc6801ebc729" datatype="html">
+ <source>NFSv3</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">336</context>
+ </context-group>
+ </trans-unit><trans-unit id="f5043c0921e709935ab026bb3253ffe1f159fca1" datatype="html">
+ <source>NFSv4</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">344</context>
+ </context-group>
+ </trans-unit><trans-unit id="92488963d23095985a47c0d6e62304e11d333f19" datatype="html">
+ <source>NFS Tag</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">358</context>
+ </context-group>
+ </trans-unit><trans-unit id="aae93362720aea94623682996dd3fcd0f906f056" datatype="html">
+ <source>Alternative access for <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="<strong>"/>NFS v3<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="</strong>"/> mounts (it must not have a leading /).</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">360</context>
+ </context-group>
+ </trans-unit><trans-unit id="45d6db77dcf1a3eeb921033abc7882e517a541cc" datatype="html">
+ <source>Clients may not mount subdirectories (i.e. if Tag = foo, the client may not mount foo/baz).</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">361</context>
+ </context-group>
+ </trans-unit><trans-unit id="a1c7a8676b55e882a97c6a6fb205204f9c761afa" datatype="html">
+ <source>By using different Tag options, the same Path may be exported multiple times.</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">362</context>
+ </context-group>
+ </trans-unit><trans-unit id="6d2c39708a32910f89701dd7e1cfb9ec1c195768" datatype="html">
+ <source>Pseudo</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">380</context>
+ </context-group>
+ </trans-unit><trans-unit id="1f8be2ae25947bec0b84c2338201580ea053f34e" datatype="html">
+ <source>The position that this <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="<strong>"/>NFS v4<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="</strong>"/> export occupies
+ in the <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="<strong>"/>Pseudo FS<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="</strong>"/> (it must be unique).</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">383</context>
+ </context-group>
+ </trans-unit><trans-unit id="f3af55f7fd5b1d9e5a53e030c80116dc635bfb9f" datatype="html">
+ <source>By using different Pseudo options, the same Path may be exported multiple times.</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">385</context>
+ </context-group>
+ </trans-unit><trans-unit id="a90eaec5c5d9b248bcc5a9765c21d80a5d210765" datatype="html">
+ <source>Wrong format</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">399</context>
+ </context-group>
+ </trans-unit><trans-unit id="27eb35c4b4ac08781a7253a2ab40f8f7d957ba51" datatype="html">
+ <source>-- No access type available --</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">421</context>
+ </context-group>
+ </trans-unit><trans-unit id="509ce016c9110a54028dafd741f15ceacbe74b5a" datatype="html">
+ <source>-- Select the access type --</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">424</context>
+ </context-group>
+ </trans-unit><trans-unit id="4deda03573eaaff77e63f6a238a1f0ca7816950a" datatype="html">
+ <source>-- No squash available --</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">456</context>
+ </context-group>
+ </trans-unit><trans-unit id="a0e82a4da88e7fdf270444f838d45849676e9d4b" datatype="html">
+ <source>--Select what kind of user id squashing is performed --</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">459</context>
+ </context-group>
+ </trans-unit><trans-unit id="d01b7c3f7f06712c53d054cfbe4f53d446b038e8" datatype="html">
+ <source>Transport Protocol</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">475</context>
+ </context-group>
+ </trans-unit><trans-unit id="d2a6ad6e8bc315f07911722c05767ac79c136d99" datatype="html">
+ <source>UDP</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">485</context>
+ </context-group>
+ </trans-unit><trans-unit id="9c030f11e0aae9b24d2c048c57f29f590be621df" datatype="html">
+ <source>TCP</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-form/nfs-form.component.html</context>
+ <context context-type="linenumber">493</context>
+ </context-group>
+ </trans-unit><trans-unit id="3bd5ad3144184479c23ec67ceda089f9883840aa" datatype="html">
+ <source>To apply any changes you have to restart the Ganisha services.</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-list/nfs-list.component.html</context>
+ <context context-type="linenumber">4</context>
+ </context-group>
+ </trans-unit><trans-unit id="5a9de53b931d4b73b84f3fd729abfbbb20a9619e" datatype="html">
+ <source>Orchestrator not available</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-list/nfs-list.component.html</context>
+ <context context-type="linenumber">2</context>
+ </context-group>
+ </trans-unit><trans-unit id="734c9905951a774870497c5aaae8e3ee833b6196" datatype="html">
+ <source>CephFS</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">app/ceph/nfs/nfs-list/nfs-list.component.html</context>
+ <context context-type="linenumber">30</context>
+ </context-group>
</trans-unit><trans-unit id="594cd9429597cf6bede5560b3d8fe578821213de" datatype="html">
<source>Add erasure code profile</source>
<context-group purpose="location">
<context context-type="linenumber">1</context>
</context-group>
</trans-unit>
+ <trans-unit id="af1396bdc49f45ea6c4a1e414769f5e2a382c834" datatype="html">
+ <source>Transport</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/ceph/nfs/nfs-details/nfs-details.component.ts</context>
+ <context context-type="linenumber">1</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="b6a0e176d96c4feed2e975a039c044fcf822e152" datatype="html">
+ <source>CephFS User</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/ceph/nfs/nfs-details/nfs-details.component.ts</context>
+ <context context-type="linenumber">1</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="44dc610cf201a163e8c38da810acec9596930bb3" datatype="html">
+ <source>CephFS Filesystem</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/ceph/nfs/nfs-details/nfs-details.component.ts</context>
+ <context context-type="linenumber">1</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="db6dc7124be83c7055cb0f2719e31f2f9d46fd3d" datatype="html">
+ <source>(inherited from global config)</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/ceph/nfs/nfs-form-client/nfs-form-client.component.ts</context>
+ <context context-type="linenumber">1</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="e1f97bb86f991553ec7b535cb39e7eaa99dfcfe2" datatype="html">
+ <source>inherited from global config</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/ceph/nfs/nfs-form-client/nfs-form-client.component.ts</context>
+ <context context-type="linenumber">1</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="e751800766277e7c2edd652cec7a7a1a68a37852" datatype="html">
+ <source>-- Select what kind of user id squashing is performed --</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/ceph/nfs/nfs-form-client/nfs-form-client.component.ts</context>
+ <context context-type="linenumber">1</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="4b58387f911e8a7b135a0c4d76c70335c65bbb32" datatype="html">
+ <source>There are no daemons available.</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/ceph/nfs/nfs-form/nfs-form.component.ts</context>
+ <context context-type="linenumber">1</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="5f19586aba912ec6eb123ec5ab609a3bcc073066" datatype="html">
+ <source>Export</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/ceph/nfs/nfs-list/nfs-list.component.ts</context>
+ <context context-type="linenumber">1</context>
+ </context-group>
+ </trans-unit>
<trans-unit id="5049e204c14c648691ac775a64fb504467aeb549" datatype="html">
<source>Value</source>
<context-group purpose="location">
<context context-type="linenumber">1</context>
</context-group>
</trans-unit>
+ <trans-unit id="c8f10184a8433d132ede0a2c0c1aa96f291cacfa" datatype="html">
+ <source>Allows all operations</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/shared/api/nfs.service.ts</context>
+ <context context-type="linenumber">1</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="82077ee97c11ee76efc3adf253b7b26654544317" datatype="html">
+ <source>Allows only operations that do not modify the server</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/shared/api/nfs.service.ts</context>
+ <context context-type="linenumber">1</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="22b5212ec10449dbf0a0af0ad34122eac51b5f2a" datatype="html">
+ <source>Does not allow read or write operations, but allows any other operation</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/shared/api/nfs.service.ts</context>
+ <context context-type="linenumber">1</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="9799d0f8c4b7727ae239fedfe3c0ce127feb7a50" datatype="html">
+ <source>Does not allow read, write, or any operation that modifies file attributes or directory content</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/shared/api/nfs.service.ts</context>
+ <context context-type="linenumber">1</context>
+ </context-group>
+ </trans-unit>
+ <trans-unit id="aeddefe0af87025d8f00699aa94bed8216a99a58" datatype="html">
+ <source>Allows no access at all</source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/shared/api/nfs.service.ts</context>
+ <context context-type="linenumber">1</context>
+ </context-group>
+ </trans-unit>
<trans-unit id="a436c6a4025a749198e93cac239de8deede72211" datatype="html">
<source>-- Select the priority --</source>
<context-group purpose="location">
<context context-type="linenumber">1</context>
</context-group>
</trans-unit>
+ <trans-unit id="cf41310917cb19b335e5a950972bbff2346f7a47" datatype="html">
+ <source>NFS <x id="INTERPOLATION" equiv-text="{{nfs_id}}"/></source>
+ <context-group purpose="location">
+ <context context-type="sourcefile">src/app/shared/services/task-message.service.ts</context>
+ <context context-type="linenumber">1</context>
+ </context-group>
+ </trans-unit>
</body>
</file>
</xliff>