'rgw daemons': { url: '#/rgw/daemon', id: 'cd-rgw-daemon-list' },
// CephFS
- cephfs: { url: '#/cephfs', id: 'cd-cephfs-list' },
- 'create cephfs': { url: '#/cephfs/create', id: 'cd-cephfs-form' }
+ cephfs: { url: '#/cephfs/fs', id: 'cd-cephfs-list' },
+ 'create cephfs': { url: '#/cephfs/fs/create', id: 'cd-cephfs-form' }
};
}
beforeEach(() => {
cy.login();
- nfsExport.navigateTo();
});
describe('breadcrumb test', () => {
it('should open and show breadcrumb', () => {
+ nfsExport.navigateTo('rgw_index');
nfsExport.expectBreadcrumbText('NFS');
});
});
buckets.navigateTo('create');
buckets.create(bucketName, 'dashboard');
- nfsExport.navigateTo();
+ nfsExport.navigateTo('rgw_index');
nfsExport.existTableCell(rgwPseudo, false);
- nfsExport.navigateTo('create');
+ nfsExport.navigateTo('rgw_create');
nfsExport.create(backends[1], squash, client, rgwPseudo, bucketName);
nfsExport.existTableCell(rgwPseudo);
});
// @TODO: uncomment this when a CephFS volume can be created through Dashboard.
// it('should create a nfs-export with CephFS backend', () => {
- // nfsExport.navigateTo();
+ // nfsExport.navigateTo('cephfs_index');
// nfsExport.existTableCell(fsPseudo, false);
- // nfsExport.navigateTo('create');
+ // nfsExport.navigateTo('cephfs_create');
// nfsExport.create(backends[0], squash, client, fsPseudo);
// nfsExport.existTableCell(fsPseudo);
// });
it('should show Clients', () => {
+ nfsExport.navigateTo('rgw_index');
nfsExport.clickTab('cd-nfs-details', rgwPseudo, 'Clients (1)');
cy.get('cd-nfs-details').within(() => {
nfsExport.getTableCount('total').should('be.gte', 0);
});
it('should edit an export', () => {
- nfsExport.editExport(rgwPseudo, editPseudo);
+ nfsExport.editExport(rgwPseudo, editPseudo, 'rgw_index');
nfsExport.existTableCell(editPseudo);
});
it('should delete exports and bucket', () => {
+ nfsExport.navigateTo('rgw_index');
nfsExport.delete(editPseudo);
buckets.navigateTo();
/* tslint:enable*/
const pages = {
- index: { url: '#/nfs', id: 'cd-nfs-list' },
- create: { url: '#/nfs/create', id: 'cd-nfs-form' }
+ cephfs_index: { url: '#cephfs/nfs', id: 'cd-nfs-list' },
+ cephfs_create: { url: '#cephfs/nfs/create', id: 'cd-nfs-form' },
+ rgw_index: { url: '#rgw/nfs', id: 'cd-nfs-list' },
+ rgw_create: { url: '#rgw/nfs/create', id: 'cd-nfs-form' }
};
export class NFSPageHelper extends PageHelper {
pages = pages;
-
- @PageHelper.restrictTo(pages.create.url)
create(backend: string, squash: string, client: object, pseudo: string, rgwPath?: string) {
this.selectOption('cluster_id', 'testnfs');
- // select a storage backend
- this.selectOption('name', backend);
if (backend === 'CephFS') {
this.selectOption('fs_name', 'myfs');
-
cy.get('#security_label').click({ force: true });
} else {
cy.get('input[data-testid=rgw_path]').type(rgwPath);
cy.get('cd-submit-button').click();
}
- editExport(pseudo: string, editPseudo: string) {
- this.navigateEdit(pseudo);
+ editExport(pseudo: string, editPseudo: string, url: string) {
+ this.navigateEdit(pseudo, true, true, url);
cy.get('input[name=pseudo]').clear().type(editPseudo);
/**
* Navigates to the edit page
*/
- navigateEdit(name: string, select = true, breadcrumb = true) {
+ navigateEdit(name: string, select = true, breadcrumb = true, navigateTo: string = null) {
if (select) {
- this.navigateTo();
+ this.navigateTo(navigateTo);
this.getFirstTableCell(name).click();
}
cy.contains('Creating...').should('not.exist');
{
path: 'cephfs',
canActivate: [FeatureTogglesGuardService],
- data: { breadcrumbs: 'File/File Systems' },
children: [
- { path: '', component: CephfsListComponent },
{
- path: URLVerbs.CREATE,
+ path: 'fs',
+ component: CephfsListComponent,
+ data: { breadcrumbs: 'File/File Systems' }
+ },
+ {
+ path: `fs/${URLVerbs.CREATE}`,
component: CephfsVolumeFormComponent,
data: { breadcrumbs: ActionLabels.CREATE }
},
{
- path: `${URLVerbs.EDIT}/:id`,
+ path: `fs/${URLVerbs.EDIT}/:id`,
component: CephfsVolumeFormComponent,
data: { breadcrumbs: ActionLabels.EDIT }
+ },
+ {
+ path: 'nfs',
+ canActivateChild: [FeatureTogglesGuardService, ModuleStatusGuardService],
+ data: {
+ moduleStatusGuardConfig: {
+ uiApiPath: 'nfs-ganesha',
+ redirectTo: 'error',
+ section: 'nfs-ganesha',
+ section_info: 'NFS GANESHA',
+ header: 'NFS-Ganesha is not configured'
+ },
+ breadcrumbs: 'File/NFS'
+ },
+ children: [
+ { path: '', component: NfsListComponent },
+ {
+ path: URLVerbs.CREATE,
+ component: NfsFormComponent,
+ data: { breadcrumbs: ActionLabels.CREATE }
+ },
+ {
+ path: `${URLVerbs.EDIT}/:cluster_id/:export_id`,
+ component: NfsFormComponent,
+ data: { breadcrumbs: ActionLabels.EDIT }
+ }
+ ]
}
]
},
data: { breadcrumbs: ActionLabels.EDIT }
}
]
- },
- // NFS
- {
- path: 'nfs',
- canActivateChild: [FeatureTogglesGuardService, ModuleStatusGuardService],
- data: {
- moduleStatusGuardConfig: {
- uiApiPath: 'nfs-ganesha',
- redirectTo: 'error',
- section: 'nfs-ganesha',
- section_info: 'NFS GANESHA',
- header: 'NFS-Ganesha is not configured'
- },
- breadcrumbs: 'NFS'
- },
- children: [
- { path: '', component: NfsListComponent },
- {
- path: URLVerbs.CREATE,
- component: NfsFormComponent,
- data: { breadcrumbs: ActionLabels.CREATE }
- },
- {
- path: `${URLVerbs.EDIT}/:cluster_id/:export_id`,
- component: NfsFormComponent,
- data: { breadcrumbs: ActionLabels.EDIT }
- }
- ]
}
]
},
private route: ActivatedRoute
) {
super();
- this.editing = this.router.url.startsWith(`/cephfs/${URLVerbs.EDIT}`);
+ this.editing = this.router.url.startsWith(`/cephfs/fs/${URLVerbs.EDIT}`);
this.action = this.editing ? this.actionLabels.EDIT : this.actionLabels.CREATE;
this.resource = $localize`File System`;
this.hosts = {
this.form.setErrors({ cdSubmitButton: true });
},
complete: () => {
- this.router.navigate([BASE_URL]);
+ this.router.navigate([`${BASE_URL}/fs`]);
}
});
} else {
self.form.setErrors({ cdSubmitButton: true });
},
complete: () => {
- this.router.navigate([BASE_URL]);
+ this.router.navigate([`${BASE_URL}/fs`]);
}
});
}
import { HealthService } from '~/app/shared/api/health.service';
import { CephfsAuthModalComponent } from '~/app/ceph/cephfs/cephfs-auth-modal/cephfs-auth-modal.component';
-const BASE_URL = 'cephfs';
+const BASE_URL = 'cephfs/fs';
@Component({
selector: 'cd-cephfs-list',
+export enum SUPPORTED_FSAL {
+ CEPH = 'CEPH',
+ RGW = 'RGW'
+}
export interface NfsFSAbstractionLayer {
- value: string;
+ value: SUPPORTED_FSAL;
descr: string;
disabled: boolean;
}
for="cluster_id">
<span class="required"
i18n>Cluster</span>
- <cd-helper>
- <p i18n>This is the ID of an NFS Service.</p>
- </cd-helper>
</label>
<div class="cd-col-form-input">
<select class="form-select"
<option *ngFor="let cluster of allClusters"
[value]="cluster.cluster_id">{{ cluster.cluster_id }}</option>
</select>
+ <cd-help-text>
+ <p i18n>This is the ID of an NFS Service.</p>
+ </cd-help-text>
<span class="invalid-feedback"
*ngIf="nfsForm.showError('cluster_id', formDir, 'required') || allClusters?.length === 0"
i18n>This field is required.
<!-- FSAL -->
<div formGroupName="fsal">
- <!-- Name -->
- <div class="form-group row">
- <label class="cd-col-form-label required"
- for="name"
- i18n>Storage Backend</label>
- <div class="cd-col-form-input">
- <select class="form-select"
- 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"
- [disabled]="fsal.disabled">{{ fsal.descr }}</option>
- </select>
- <span class="invalid-feedback"
- *ngIf="nfsForm.showError('name', formDir, 'required')"
- i18n>This field is required.</span>
- <span class="invalid-feedback"
- *ngIf="fsalAvailabilityError"
- i18n>{{ fsalAvailabilityError }}</span>
- </div>
- </div>
-
<!-- CephFS Volume -->
<div class="form-group row"
- *ngIf="nfsForm.getValue('name') === 'CEPH'">
+ *ngIf="storageBackend === 'CEPH'">
<label class="cd-col-form-label required"
for="fs_name"
i18n>Volume</label>
<!-- Security Label -->
<div class="form-group row"
- *ngIf="nfsForm.getValue('name') === 'CEPH'">
+ *ngIf="storageBackend === 'CEPH'">
<label class="cd-col-form-label"
[ngClass]="{'required': nfsForm.getValue('security_label')}"
for="security_label"
<!-- Path -->
<div class="form-group row"
- *ngIf="nfsForm.getValue('name') === 'CEPH'">
+ *ngIf="storageBackend === 'CEPH'">
<label class="cd-col-form-label"
for="path">
<span class="required"
i18n>CephFS Path</span>
- <cd-helper>
- <p i18n>A path in a CephFS file system.</p>
- </cd-helper>
</label>
<div class="cd-col-form-input">
<input type="text"
[ngbTypeahead]="pathDataSource"
(selectItem)="pathChangeHandler()"
(blur)="pathChangeHandler()">
+ <cd-help-text>
+ <p i18n>A path in a CephFS file system.</p>
+ </cd-help-text>
<span class="invalid-feedback"
*ngIf="nfsForm.showError('path', formDir, 'required')"
i18n>This field is required.</span>
<!-- Bucket -->
<div class="form-group row"
- *ngIf="nfsForm.getValue('name') === 'RGW'">
+ *ngIf="storageBackend === 'RGW'">
<label class="cd-col-form-label"
for="path">
<span class="required"
for="pseudo">
<span class="required"
i18n>Pseudo</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="cd-col-form-input">
<input type="text"
id="pseudo"
formControlName="pseudo"
minlength="2">
+ <cd-help-text>
+ <span i18n>The position this export occupies in the Pseudo FS. It must be unique.</span><br/>
+ <span i18n>By using different Pseudo options, the same Path may be exported multiple times.</span>
+ </cd-help-text>
<span class="invalid-feedback"
*ngIf="nfsForm.showError('pseudo', formDir, 'required')"
i18n>This field is required.</span>
{{ getAccessTypeHelp(nfsForm.getValue('access_type')) }}
</span>
<span class="form-text text-warning"
- *ngIf="nfsForm.getValue('access_type') === 'RW' && nfsForm.getValue('name') === 'RGW'"
+ *ngIf="nfsForm.getValue('access_type') === 'RW' && storageBackend === 'RGW'"
i18n>The Object Gateway NFS backend has a number of
limitations which will seriously affect applications writing to
the share. Please consult the <cd-doc section="rgw-nfs"></cd-doc>
<label class="cd-col-form-label"
for="squash">
<span i18n>Squash</span>
- <ng-container *ngTemplateOutlet="squashHelper"></ng-container>
</label>
<div class="cd-col-form-input">
<select class="form-select"
[value]="squash">{{ squash }}</option>
</select>
+ <cd-help-text>
+ <span *ngIf="nfsForm.getValue('squash') === 'root_squash'"
+ i18n>Maps the root user on the NFS client to an anonymous user/group with limited privileges. This prevents a root client user from having total control over the NFS export.</span>
+
+ <span *ngIf="nfsForm.getValue('squash') === 'root_id_squash'"
+ i18n>Maps the root user on the NFS client to an anonymous user/group with limited privileges, preventing root access but retaining non-root group privileges.</span>
+
+ <span *ngIf="nfsForm.getValue('squash') === 'all_squash'"
+ i18n>Maps all users on the NFS client to an anonymous user/group with limited privileges, ensuring that no user has special privileges on the NFS export.</span>
+
+ <span *ngIf="nfsForm.getValue('squash') === 'no_root_squash'"
+ i18n>Allows the root user on the NFS client to retain full root privileges on the NFS server, which may pose security risks.</span>
+
+ </cd-help-text>
<span class="invalid-feedback"
*ngIf="nfsForm.showError('squash', formDir,'required')"
i18n>This field is required.</span>
<cd-nfs-form-client [form]="nfsForm"
[clients]="clients"
#nfsClients>
- <ng-template #squashHelper>
- <cd-helper>
- <ul class="squash-helper">
- <li>
- <span class="squash-helper-item-value">no_root_squash: </span>
- <span i18n>No user id squashing is performed.</span>
- </li>
- <li>
- <span class="squash-helper-item-value">root_id_squash: </span>
- <span i18n>uid 0 and gid 0 are squashed to the Anonymous_Uid and Anonymous_Gid gid 0 in alt_groups lists is also squashed.</span>
- </li>
- <li>
- <span class="squash-helper-item-value">root_squash: </span>
- <span i18n>uid 0 and gid of any value are squashed to the Anonymous_Uid and Anonymous_Gid alt_groups lists is discarded.</span>
- </li>
- <li>
- <span class="squash-helper-item-value">all_squash: </span>
- <span i18n>All users are squashed.</span>
- </li>
- </ul>
- </cd-helper>
- </ng-template>
</cd-nfs-form-client>
+ <!-- Errors -->
+ <cd-alert-panel type="error"
+ *ngIf="!!storageBackendError">
+ {{storageBackendError}}
+ </cd-alert-panel>
</div>
-
<div class="card-footer">
<cd-form-button-panel (submitActionEvent)="submitAction()"
[form]="nfsForm"
+ [disabled]="!!storageBackendError"
[submitText]="(action | titlecase) + ' ' + (resource | upperFirst)"
wrappingClass="text-right"></cd-form-button-panel>
</div>
.cd-mb {
margin-bottom: 10px;
}
-
-.squash-helper {
- padding-left: 1rem;
-}
-
-.squash-helper-item-value {
- font-weight: bold;
-}
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 { ActivatedRoute, Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap';
let fixture: ComponentFixture<NfsFormComponent>;
let httpTesting: HttpTestingController;
let activatedRoute: ActivatedRouteStub;
+ let router: Router;
configureTestBed(
{
const matchSquash = (backendSquashValue: string, uiSquashValue: string) => {
component.ngOnInit();
- httpTesting.expectOne('ui-api/nfs-ganesha/fsals').flush(['CEPH', 'RGW']);
- httpTesting.expectOne('ui-api/nfs-ganesha/cephfs/filesystems').flush([{ id: 1, name: 'a' }]);
httpTesting.expectOne('api/nfs-ganesha/cluster').flush(['mynfs']);
+ httpTesting.expectOne('ui-api/nfs-ganesha/cephfs/filesystems').flush([{ id: 1, name: 'a' }]);
httpTesting.expectOne('api/nfs-ganesha/export/mynfs/1').flush({
fsal: {
name: 'RGW'
component = fixture.componentInstance;
httpTesting = TestBed.inject(HttpTestingController);
activatedRoute = <ActivatedRouteStub>TestBed.inject(ActivatedRoute);
+ router = TestBed.inject(Router);
+
+ Object.defineProperty(router, 'url', {
+ get: jasmine.createSpy('url').and.returnValue('/cephfs/nfs')
+ });
RgwHelper.selectDaemon();
fixture.detectChanges();
- httpTesting.expectOne('ui-api/nfs-ganesha/fsals').flush(['CEPH', 'RGW']);
- httpTesting.expectOne('ui-api/nfs-ganesha/cephfs/filesystems').flush([{ id: 1, name: 'a' }]);
httpTesting.expectOne('api/nfs-ganesha/cluster').flush(['mynfs']);
+ httpTesting.expectOne('ui-api/nfs-ganesha/cephfs/filesystems').flush([{ id: 1, name: 'a' }]);
httpTesting.verify();
});
expect(component).toBeTruthy();
});
- it('should process all data', () => {
- expect(component.allFsals).toEqual([
- { descr: 'CephFS', value: 'CEPH', disabled: false },
- { descr: 'Object Gateway', value: 'RGW', disabled: false }
- ]);
- expect(component.allFsNames).toEqual([{ id: 1, name: 'a' }]);
- expect(component.allClusters).toEqual([{ cluster_id: 'mynfs' }]);
- });
-
it('should create the form', () => {
expect(component.nfsForm.value).toEqual({
access_type: 'RW',
-import { ChangeDetectorRef, Component, OnInit, ViewChild } from '@angular/core';
+import { Component, OnInit, ViewChild } from '@angular/core';
import {
AbstractControl,
AsyncValidatorFn,
import { forkJoin, Observable, of } from 'rxjs';
import { catchError, debounceTime, distinctUntilChanged, map, mergeMap } from 'rxjs/operators';
-import { NfsFSAbstractionLayer } from '~/app/ceph/nfs/models/nfs.fsal';
+import { SUPPORTED_FSAL } from '~/app/ceph/nfs/models/nfs.fsal';
import { Directory, NfsService } from '~/app/shared/api/nfs.service';
import { RgwBucketService } from '~/app/shared/api/rgw-bucket.service';
import { RgwSiteService } from '~/app/shared/api/rgw-site.service';
import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
import { NfsFormClientComponent } from '../nfs-form-client/nfs-form-client.component';
+import { getFsalFromRoute, getPathfromFsal } from '../utils';
@Component({
selector: 'cd-nfs-form',
allClusters: { cluster_id: string }[] = null;
icons = Icons;
- allFsals: any[] = [];
allFsNames: any[] = null;
- fsalAvailabilityError: string = null;
+
+ storageBackend: SUPPORTED_FSAL;
+ storageBackendError: string = null;
defaultAccessType = { RGW: 'RO' };
nfsAccessType: any[] = [];
private rgwSiteService: RgwSiteService,
private formBuilder: CdFormBuilder,
private taskWrapper: TaskWrapperService,
- private cdRef: ChangeDetectorRef,
public actionLabels: ActionLabelsI18n
) {
super();
this.permission = this.authStorageService.getPermissions().pool;
this.resource = $localize`NFS export`;
+ this.storageBackend = getFsalFromRoute(this.router.url);
}
ngOnInit() {
this.nfsAccessType = this.nfsService.nfsAccessType;
this.nfsSquash = Object.keys(this.nfsService.nfsSquash);
this.createForm();
- const promises: Observable<any>[] = [
- this.nfsService.listClusters(),
- this.nfsService.fsals(),
- this.nfsService.filesystems()
- ];
+ const promises: Observable<any>[] = [this.nfsService.listClusters()];
- if (this.router.url.startsWith('/nfs/edit')) {
+ if (this.storageBackend === 'RGW') {
+ promises.push(this.rgwSiteService.get('realms'));
+ } else {
+ promises.push(this.nfsService.filesystems());
+ }
+
+ if (this.router.url.startsWith(`/${getPathfromFsal(this.storageBackend)}/nfs/edit`)) {
this.isEdit = true;
}
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);
});
this.nfsForm.get('cluster_id').disable();
forkJoin(promises).subscribe((data: any[]) => {
this.resolveClusters(data[0]);
this.resolveFsals(data[1]);
- this.resolveFilesystems(data[2]);
- if (data[3]) {
- this.resolveModel(data[3]);
+ if (data[2]) {
+ this.resolveModel(data[2]);
}
-
this.loadingReady();
});
}
validators: [Validators.required]
}),
fsal: new CdFormGroup({
- name: new UntypedFormControl('', {
+ name: new UntypedFormControl(this.storageBackend, {
validators: [Validators.required]
}),
fs_name: new UntypedFormControl('', {
]
})
}),
- path: new UntypedFormControl('/'),
+ path: new UntypedFormControl('/', {
+ validators: [Validators.required]
+ }),
protocolNfsv3: new UntypedFormControl(true, {
validators: [
CdValidators.requiredIf({ protocolNfsv4: false }, (value: boolean) => {
}
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 (!this.isEdit && this.allFsals.length > 0) {
+ if (this.storageBackend === 'RGW') {
+ this.setPathValidation();
+ this.resolveRealms(res);
+ } else {
+ this.resolveFilesystems(res);
+ }
+ if (!this.isEdit && this.storageBackend === SUPPORTED_FSAL.RGW) {
this.nfsForm.patchValue({
- fsal: {
- name: this.allFsals[0].value
- }
+ path: '',
+ access_type: this.defaultAccessType[SUPPORTED_FSAL.RGW]
});
}
}
}
}
- fsalChangeHandler() {
- this.setPathValidation();
- const fsalValue = this.nfsForm.getValue('name');
- const checkAvailability =
- fsalValue === 'RGW'
- ? this.rgwSiteService.get('realms').pipe(
- mergeMap((realms: string[]) =>
- realms.length === 0
- ? of(true)
- : this.rgwSiteService.isDefaultRealm().pipe(
- mergeMap((isDefaultRealm) => {
- if (!isDefaultRealm) {
- throw new Error('Selected realm is not the default.');
- }
- return of(true);
- })
- )
- )
- )
- : this.nfsService.filesystems();
-
- checkAvailability.subscribe({
- next: () => {
- this.setFsalAvailability(fsalValue, true);
- if (!this.isEdit) {
- this.nfsForm.patchValue({
- path: fsalValue === 'RGW' ? '' : '/',
- pseudo: this.generatePseudo(),
- access_type: this.updateAccessType()
- });
- }
-
- this.cdRef.detectChanges();
- },
- error: (error) => {
- this.setFsalAvailability(fsalValue, false, error);
- this.nfsForm.get('name').setValue('');
- }
- });
- }
-
- private setFsalAvailability(fsalValue: string, available: boolean, errorMessage: string = '') {
- this.allFsals = this.allFsals.map((fsalItem: NfsFSAbstractionLayer) => {
- if (fsalItem.value === fsalValue) {
- fsalItem.disabled = !available;
-
- this.fsalAvailabilityError = fsalItem.disabled
- ? $localize`${fsalItem.descr} backend is not available. ${errorMessage}`
- : null;
- }
- return fsalItem;
- });
+ resolveRealms(realms: string[]) {
+ if (realms.length !== 0) {
+ this.rgwSiteService
+ .isDefaultRealm()
+ .pipe(
+ mergeMap((isDefaultRealm) => {
+ if (!isDefaultRealm) {
+ throw new Error('Selected realm is not the default.');
+ }
+ return of(true);
+ })
+ )
+ .subscribe({
+ error: (error) => {
+ const fsalDescr = this.nfsService.nfsFsal.find((f) => f.value === this.storageBackend)
+ .descr;
+ this.storageBackendError = $localize`${fsalDescr} backend is not available. ${error}`;
+ }
+ });
+ }
}
accessTypeChangeHandler() {
setPathValidation() {
const path = this.nfsForm.get('path');
- path.setValidators([Validators.required]);
- if (this.nfsForm.getValue('name') === 'RGW') {
+ if (this.storageBackend === SUPPORTED_FSAL.RGW) {
path.setAsyncValidators([CdValidators.bucketExistence(true, this.rgwBucketService)]);
} else {
path.setAsyncValidators([this.pathExistence(true)]);
let newPseudo = this.nfsForm.getValue('pseudo');
if (this.nfsForm.get('pseudo') && !this.nfsForm.get('pseudo').dirty) {
newPseudo = undefined;
- if (this.nfsForm.getValue('fsal') === 'CEPH') {
+ if (this.storageBackend === 'CEPH') {
newPseudo = '/cephfs';
if (_.isString(this.nfsForm.getValue('path'))) {
newPseudo += this.nfsForm.getValue('path');
return newPseudo;
}
- private updateAccessType() {
- const name = this.nfsForm.getValue('name');
- let accessType = this.defaultAccessType[name];
-
- if (!accessType) {
- accessType = 'RW';
- }
-
- return accessType;
- }
-
submitAction() {
let action: Observable<any>;
const requestModel = this.buildRequest();
action.subscribe({
error: (errorResponse: CdHttpErrorResponse) => this.setFormErrors(errorResponse),
- complete: () => this.router.navigate(['/nfs'])
+ complete: () => this.router.navigate([`/${getPathfromFsal(this.storageBackend)}/nfs`])
});
}
import { configureTestBed, expectItemTasks, PermissionHelper } from '~/testing/unit-test-helper';
import { NfsDetailsComponent } from '../nfs-details/nfs-details.component';
import { NfsListComponent } from './nfs-list.component';
+import { SUPPORTED_FSAL } from '../models/nfs.fsal';
describe('NfsListComponent', () => {
let component: NfsListComponent;
beforeEach(() => {
fixture = TestBed.createComponent(NfsListComponent);
component = fixture.componentInstance;
+ component.fsal = SUPPORTED_FSAL.CEPH;
summaryService = TestBed.inject(SummaryService);
nfsService = TestBed.inject(NfsService);
httpTesting = TestBed.inject(HttpTestingController);
const model = {
export_id: export_id,
path: 'path_' + export_id,
- fsal: 'fsal_' + export_id,
+ fsal: {
+ name: 'CEPH'
+ },
cluster_id: 'cluster_' + export_id
};
exports.push(model);
case 'nfs/create':
task.metadata = {
path: 'path_' + export_id,
- fsal: 'fsal_' + export_id,
+ fsal: {
+ name: 'CEPH'
+ },
cluster_id: 'cluster_' + export_id
};
break;
import { Component, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
+import { Router } from '@angular/router';
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import _ from 'lodash';
import { ModalService } from '~/app/shared/services/modal.service';
import { TaskListService } from '~/app/shared/services/task-list.service';
import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
+import { getFsalFromRoute, getPathfromFsal } from '../utils';
+import { SUPPORTED_FSAL } from '../models/nfs.fsal';
@Component({
selector: 'cd-nfs-list',
exports: any[];
tableActions: CdTableAction[];
isDefaultCluster = false;
+ fsal: SUPPORTED_FSAL;
modalRef: NgbModalRef;
private nfsService: NfsService,
private taskListService: TaskListService,
private taskWrapper: TaskWrapperService,
+ private router: Router,
public actionLabels: ActionLabelsI18n
) {
super();
this.permission = this.authStorageService.getPermissions().nfs;
+ this.fsal = getFsalFromRoute(this.router.url);
+ const prefix = getPathfromFsal(this.fsal);
const getNfsUri = () =>
this.selection.first() &&
`${encodeURI(this.selection.first().cluster_id)}/${encodeURI(
const createAction: CdTableAction = {
permission: 'create',
icon: Icons.add,
- routerLink: () => '/nfs/create',
+ routerLink: () => `/${prefix}/nfs/create`,
canBePrimary: (selection: CdTableSelection) => !selection.hasSingleSelection,
name: this.actionLabels.CREATE
};
const editAction: CdTableAction = {
permission: 'update',
icon: Icons.edit,
- routerLink: () => `/nfs/edit/${getNfsUri()}`,
+ routerLink: () => `/${prefix}/nfs/edit/${getNfsUri()}`,
name: this.actionLabels.EDIT
};
ngOnInit() {
this.columns = [
{
- name: $localize`Path`,
+ name: this.fsal === SUPPORTED_FSAL.CEPH ? $localize`Path` : $localize`Bucket`,
prop: 'path',
flexGrow: 2,
cellTransformation: CellTemplate.executing
prepareResponse(resp: any): any[] {
let result: any[] = [];
- resp.forEach((nfs: any) => {
+ const filteredresp = resp.filter((nfs: any) => nfs.fsal?.name === this.fsal);
+ filteredresp.forEach((nfs: any) => {
nfs.id = `${nfs.cluster_id}:${nfs.export_id}`;
nfs.state = 'LOADING';
result = result.concat(nfs);
});
-
return result;
}
NgbTypeaheadModule,
NgbTooltipModule
],
+ exports: [NfsListComponent, NfsFormComponent, NfsDetailsComponent],
declarations: [NfsListComponent, NfsDetailsComponent, NfsFormComponent, NfsFormClientComponent]
})
export class NfsModule {}
--- /dev/null
+import { SUPPORTED_FSAL } from './models/nfs.fsal';
+
+export const getFsalFromRoute = (url: string): SUPPORTED_FSAL =>
+ url.startsWith('/rgw/nfs') ? SUPPORTED_FSAL.RGW : SUPPORTED_FSAL.CEPH;
+
+export const getPathfromFsal = (fsal: SUPPORTED_FSAL): string =>
+ fsal === SUPPORTED_FSAL.CEPH ? 'cephfs' : 'rgw';
import { ActionLabels, URLVerbs } from '~/app/shared/constants/app.constants';
import { CRUDTableComponent } from '~/app/shared/datatable/crud-table/crud-table.component';
+import { FeatureTogglesGuardService } from '~/app/shared/services/feature-toggles-guard.service';
+import { ModuleStatusGuardService } from '~/app/shared/services/module-status-guard.service';
import { SharedModule } from '~/app/shared/shared.module';
import { PerformanceCounterModule } from '../performance-counter/performance-counter.module';
import { RgwSyncMetadataInfoComponent } from './rgw-sync-metadata-info/rgw-sync-metadata-info.component';
import { RgwSyncDataInfoComponent } from './rgw-sync-data-info/rgw-sync-data-info.component';
import { BucketTagModalComponent } from './bucket-tag-modal/bucket-tag-modal.component';
+import { NfsListComponent } from '../nfs/nfs-list/nfs-list.component';
+import { NfsFormComponent } from '../nfs/nfs-form/nfs-form.component';
@NgModule({
imports: [
path: 'multisite',
data: { breadcrumbs: 'Multi-site' },
children: [{ path: '', component: RgwMultisiteDetailsComponent }]
+ },
+ {
+ path: 'nfs',
+ canActivateChild: [FeatureTogglesGuardService, ModuleStatusGuardService],
+ data: {
+ moduleStatusGuardConfig: {
+ uiApiPath: 'nfs-ganesha',
+ redirectTo: 'error',
+ section: 'nfs-ganesha',
+ section_info: 'NFS GANESHA',
+ header: 'NFS-Ganesha is not configured'
+ },
+ breadcrumbs: 'NFS'
+ },
+ children: [
+ { path: '', component: NfsListComponent },
+ {
+ path: URLVerbs.CREATE,
+ component: NfsFormComponent,
+ data: { breadcrumbs: ActionLabels.CREATE }
+ },
+ {
+ path: `${URLVerbs.EDIT}/:cluster_id/:export_id`,
+ component: NfsFormComponent,
+ data: { breadcrumbs: ActionLabels.EDIT }
+ }
+ ]
}
];
<div class="cd-navbar-main">
-<!-- ************************ -->
-<!-- NOTIFICATIONS -->
-<!-- ************************ -->
-<cd-notifications-sidebar></cd-notifications-sidebar>
-<!-- ************************ -->
-<!-- HEADER -->
-<!-- ************************ -->
-<cds-header name="Ceph Dashboard"
- class="cd-navbar-top"
- [brand]="brandTemplate">
- <cds-hamburger [active]="showMenuSidebar"
- data-testid="main-menu-toggler"
- (selected)="showMenuSidebar = !showMenuSidebar"></cds-hamburger>
- <cds-header-global>
- <cds-header-navigation>
- <cd-language-selector class="d-flex"></cd-language-selector>
- </cds-header-navigation>
- <div class="cds--btn cds--btn--icon-only cds--header__action">
- <cd-notifications (click)="toggleRightSidebar()"></cd-notifications>
+ <!-- ************************ -->
+ <!-- NOTIFICATIONS -->
+ <!-- ************************ -->
+ <cd-notifications-sidebar></cd-notifications-sidebar>
+ <!-- ************************ -->
+ <!-- HEADER -->
+ <!-- ************************ -->
+ <cds-header name="Ceph Dashboard"
+ class="cd-navbar-top"
+ [brand]="brandTemplate">
+ <cds-hamburger [active]="showMenuSidebar"
+ data-testid="main-menu-toggler"
+ (selected)="showMenuSidebar = !showMenuSidebar"></cds-hamburger>
+ <cds-header-global>
+ <cds-header-navigation>
+ <cd-language-selector class="d-flex"></cd-language-selector>
+ </cds-header-navigation>
+ <div class="cds--btn cds--btn--icon-only cds--header__action">
+ <cd-notifications (click)="toggleRightSidebar()"></cd-notifications>
+ </div>
+ <div class="cds--btn cds--btn--icon-only cds--header__action">
+ <cd-dashboard-help></cd-dashboard-help>
+ </div>
+ <div class="cds--btn cds--btn--icon-only cds--header__action">
+ <cd-administration></cd-administration>
+ </div>
+ <div class="cds--btn cds--btn--icon-only cds--header__action">
+ <cd-identity></cd-identity>
+ </div>
+ </cds-header-global>
+ </cds-header>
+ <!-- ***************************** -->
+ <!-- LOGO BRAND TEMPLATE -->
+ <!-- ***************************** -->
+ <ng-template #brandTemplate>
+ <a class="cds--header__name navbar-brand ms-3"
+ routerLink="/dashboard">
+ <img src="assets/Ceph_Ceph_Logo_with_text_white.svg"
+ alt="Ceph" />
+ </a>
+ </ng-template>
+ <!-- **************************************** -->
+ <!-- WRAPPER AROUND SIDENAV AND MAIN CONTAINT -->
+ <!-- **************************************** -->
+ <div class="wrapper">
+ <!-- Content -->
+ <nav id="sidebar"
+ [ngClass]="{'active': !showMenuSidebar}">
+ <ng-container *ngTemplateOutlet="cd_menu"></ng-container>
+ </nav>
+ <!-- Page Content -->
+ <div id="content"
+ [ngClass]="{'active': !showMenuSidebar}">
+ <ng-content></ng-content>
</div>
- <div class="cds--btn cds--btn--icon-only cds--header__action">
- <cd-dashboard-help></cd-dashboard-help>
- </div>
- <div class="cds--btn cds--btn--icon-only cds--header__action">
- <cd-administration></cd-administration>
- </div>
- <div class="cds--btn cds--btn--icon-only cds--header__action">
- <cd-identity></cd-identity>
- </div>
- </cds-header-global>
-</cds-header>
-<!-- ***************************** -->
-<!-- LOGO BRAND TEMPLATE -->
-<!-- ***************************** -->
-<ng-template #brandTemplate>
- <a class="cds--header__name navbar-brand ms-3"
- routerLink="/dashboard">
- <img src="assets/Ceph_Ceph_Logo_with_text_white.svg"
- alt="Ceph" />
- </a>
-</ng-template>
-<!-- **************************************** -->
-<!-- WRAPPER AROUND SIDENAV AND MAIN CONTAINT -->
-<!-- **************************************** -->
-<div class="wrapper">
- <!-- Content -->
- <nav id="sidebar"
- [ngClass]="{'active': !showMenuSidebar}">
- <ng-container *ngTemplateOutlet="cd_menu"></ng-container>
- </nav>
- <!-- Page Content -->
- <div id="content"
- [ngClass]="{'active': !showMenuSidebar}">
- <ng-content></ng-content>
</div>
-</div>
-<!-- ************************ -->
-<!-- SIDENAV -->
-<!-- ************************ -->
-<ng-template #cd_menu>
- <ng-container *ngIf="enabledFeature$ | async as enabledFeature">
- <div cdsTheme="theme">
- <cds-sidenav [expanded]="showMenuSidebar"
- class="mt-5">
- <!-- Dashboard -->
- <cds-sidenav-item route="/dashboard"
- [useRouter]="true"
- title="Dashboard"
- i18n-title
- class="nav-item tc_menuitem_dashboard">
- <svg cdsIcon="template"
- icon
- size="20"></svg>
- <span i18n>
- Dashboard</span>
- </cds-sidenav-item>
- <!-- Multi-cluster Dashboard -->
- <cds-sidenav-menu title="Multi-Cluster"
- i18n-title>
- <svg cdsIcon="edge-cluster"
- icon
- size="20"></svg>
- <cds-sidenav-item route="/multi-cluster/overview"
- title="Overview"
- i18n-title
- [useRouter]="true"
- class="tc_submenuitem tc_submenuitem_multiCluster_overview"><span i18n>Overview</span></cds-sidenav-item>
- <cds-sidenav-item route="/multi-cluster/manage-clusters"
- title="Manager Cluster"
- i18n-title
- [useRouter]="true"
- class="tc_submenuitem tc_submenuitem_multiCluster_manage_clusters"><span i18n>Manager Cluster</span></cds-sidenav-item>
- </cds-sidenav-menu>
- <!-- Cluster -->
- <cds-sidenav-menu title="Cluster"
- i18n-title
- *ngIf="permissions.hosts.read || permissions.monitor.read || permissions.osd.read || permissions.pool.read"
- class="tc_menuitem_cluster">
- <svg cdsIcon="web-services--cluster"
- icon
- size="20"></svg>
- <cds-sidenav-item route="/pool"
- [useRouter]="true"
- title="Pools"
- i18n-title
- *ngIf="permissions.pool.read"
- class="tc_submenuitem tc_submenuitem_cluster_pool"><span i18n>Pools</span></cds-sidenav-item>
- <cds-sidenav-item route="/hosts"
- [useRouter]="true"
- title="Hosts"
- i18n-title
- *ngIf="permissions.hosts.read"
- class="tc_submenuitem tc_submenuitem_cluster_hosts"><span i18n>Hosts</span></cds-sidenav-item>
- <cds-sidenav-item route="/osd"
- [useRouter]="true"
- title="OSDs"
- i18n-title
- *ngIf="permissions.osd.read"
- class="tc_submenuitem tc_submenuitem_cluster_osds"><span i18n>OSDs</span></cds-sidenav-item>
- <cds-sidenav-item route="/inventory"
+ <!-- ************************ -->
+ <!-- SIDENAV -->
+ <!-- ************************ -->
+ <ng-template #cd_menu>
+ <ng-container *ngIf="enabledFeature$ | async as enabledFeature">
+ <div cdsTheme="theme">
+ <cds-sidenav [expanded]="showMenuSidebar"
+ class="mt-5">
+ <!-- Dashboard -->
+ <cds-sidenav-item route="/dashboard"
[useRouter]="true"
- title="Physical Disks"
+ title="Dashboard"
i18n-title
- *ngIf="permissions.hosts.read"
- class="tc_submenuitem tc_submenuitem_cluster_inventory"><span i18n>Physical Disks</span></cds-sidenav-item>
- <cds-sidenav-item route="/crush-map"
- [useRouter]="true"
- title="CRUSH Map"
- i18n-title
- *ngIf="permissions.osd.read"
- class="tc_submenuitem tc_submenuitem_cluster_crush"><span i18n>CRUSH Map</span></cds-sidenav-item>
- <cds-sidenav-item route="/monitor"
- [useRouter]="true"
- title="Monitors"
- i18n-title
- *ngIf="permissions.monitor.read"
- class="tc_submenuitem tc_submenuitem_cluster_monitor"><span i18n>Monitors</span></cds-sidenav-item>
- </cds-sidenav-menu>
- <!-- Block Storage -->
- <cds-sidenav-menu title="Block"
- i18n-title
- *ngIf="(permissions.rbdImage.read || permissions.rbdMirroring.read|| permissions.iscsi.read) && (enabledFeature.rbd || enabledFeature.mirroring || enabledFeature.iscsi)"
- class="tc_menuitem_block">
- <svg cdsIcon="datastore"
- icon
- size="20"></svg>
- <cds-sidenav-item route="/block/rbd"
- [useRouter]="true"
- title="Images"
- i18n-title
- *ngIf="permissions.rbdImage.read && enabledFeature.rbd"
- class="tc_submenuitem tc_submenuitem_block_images"><span i18n>Images</span></cds-sidenav-item>
- <cds-sidenav-item route="/block/mirroring"
- [useRouter]="true"
- title="Mirroring"
- i18n-title
- *ngIf="permissions.rbdMirroring.read && enabledFeature.mirroring"
- class="tc_submenuitem tc_submenuitem_block_mirroring">
- <span i18n>Mirroring
- <small *ngIf="summaryData?.rbd_mirroring?.warnings !== 0"
- class="badge badge-warning">{{ summaryData?.rbd_mirroring?.warnings }}</small>
- <small *ngIf="summaryData?.rbd_mirroring?.errors !== 0"
- class="badge badge-danger">{{ summaryData?.rbd_mirroring?.errors }}</small>
- </span>
- </cds-sidenav-item>
- <cds-sidenav-item route="/block/iscsi"
- [useRouter]="true"
- title="iSCSI"
- i18n-title
- *ngIf="permissions.iscsi.read && enabledFeature.iscsi"
- class="tc_submenuitem tc_submenuitem_block_iscsi"><span i18n>iSCSI</span></cds-sidenav-item>
- </cds-sidenav-menu>
- <!-- Object Storage -->
- <cds-sidenav-menu title="Object"
- i18n-title
- *ngIf="permissions.rgw.read && enabledFeature.rgw"
- class="nav-item tc_menuitem_rgw">
- <svg cdsIcon="object-storage"
- icon
- size="20"></svg>
- <cds-sidenav-item route="/rgw/overview"
- title="Overview"
- i18n-title
- [useRouter]="true"
- class="tc_submenuitem tc_submenuitem_rgw_overview"><span i18n>Overview</span></cds-sidenav-item>
- <cds-sidenav-item route="/rgw/bucket"
- title="Buckets"
- i18n-title
- [useRouter]="true"
- class="tc_submenuitem tc_submenuitem_rgw_buckets"><span i18n>Buckets</span></cds-sidenav-item>
- <cds-sidenav-item route="/rgw/user"
- title="Users"
- i18n-title
- [useRouter]="true"
- class="tc_submenuitem tc_submenuitem_rgw_users"><span i18n>Users</span></cds-sidenav-item>
- <cds-sidenav-item route="/rgw/multisite"
- title="Multi-site"
- i18n-title
- [useRouter]="true"
- class="tc_submenuitem tc_submenuitem_rgw_buckets"><span i18n>Multi-site</span></cds-sidenav-item>
- <cds-sidenav-item route="/rgw/daemon"
- title="Gateways"
- i18n-title
- [useRouter]="true"
- class="tc_submenuitem tc_submenuitem_rgw_daemons"><span i18n>Gateways</span></cds-sidenav-item>
- <cds-sidenav-item route="/nfs"
- [useRouter]="true"
- title="NFS"
- i18n-title
- *ngIf="permissions.nfs.read && enabledFeature.nfs"
- class="tc_submenuitem tc_submenuitem_rgw_nfs"><span i18n>NFS</span></cds-sidenav-item>
- </cds-sidenav-menu>
- <!-- Filesystem -->
- <cds-sidenav-menu title="File"
- i18n-title
- *ngIf="permissions.nfs.read && enabledFeature.nfs || permissions.cephfs.read && enabledFeature.cephfs"
- class="tc_menuitem_file">
- <svg cdsIcon="file-storage"
- icon
- size="20"></svg>
- <cds-sidenav-item route="/cephfs"
- [useRouter]="true"
- title="File Systems"
- i18n-title
- *ngIf="permissions.cephfs.read && enabledFeature.cephfs"
- class="tc_submenuitem tc_submenuitem_file_cephfs"><span i18n>File Systems</span></cds-sidenav-item>
- <cds-sidenav-item route="/nfs"
- [useRouter]="true"
- title="NFS"
- i18n-title
- *ngIf="permissions.nfs.read && enabledFeature.nfs"
- class="tc_submenuitem tc_submenuitem_file_nfs"><span i18n>NFS</span></cds-sidenav-item>
- </cds-sidenav-menu>
- <!-- Observability -->
- <cds-sidenav-menu title="Observability"
- i18n-title
- *ngIf="permissions.log.read || permissions.prometheus.read"
- class="tc_menuitem_observe">
- <svg cdsIcon="observed--hail"
- icon
- size="20"></svg>
- <cds-sidenav-item route="/logs"
- [useRouter]="true"
- title="Logs"
- i18n-title
- *ngIf="permissions.log.read"
- class="tc_submenuitem tc_submenuitem_observe_log"><span i18n>Logs</span></cds-sidenav-item>
- <cds-sidenav-item route="/monitoring"
- [useRouter]="true"
- title="Alerts"
- i18n-title
- *ngIf="permissions.prometheus.read"
- class="tc_submenuitem tc_submenuitem_observe_monitoring">
+ class="nav-item tc_menuitem_dashboard">
+ <svg cdsIcon="template"
+ icon
+ size="20"></svg>
<span i18n>
- <ng-container>Alerts</ng-container>
- <small *ngIf="prometheusAlertService.activeCriticalAlerts > 0"
- class="badge badge-danger ms-1">{{ prometheusAlertService.activeCriticalAlerts }}</small>
- <small *ngIf="prometheusAlertService.activeWarningAlerts > 0"
- class="badge badge-warning ms-1">{{ prometheusAlertService.activeWarningAlerts }}</small>
- </span>
+ Dashboard</span>
</cds-sidenav-item>
- </cds-sidenav-menu>
- <!-- Administration -->
- <cds-sidenav-menu title="Administration"
- i18n-title
- *ngIf="permissions.configOpt.read || permissions.hosts.read"
- class="tc_menuitem_admin">
- <svg cdsIcon="network--admin-control"
- icon
- size="20"></svg>
- <cds-sidenav-item route="/services/"
- [useRouter]="true"
- title="Services"
+ <!-- Multi-cluster Dashboard -->
+ <cds-sidenav-menu title="Multi-Cluster"
+ i18n-title>
+ <svg cdsIcon="edge-cluster"
+ icon
+ size="20"></svg>
+ <cds-sidenav-item route="/multi-cluster/overview"
+ title="Overview"
+ i18n-title
+ [useRouter]="true"
+ class="tc_submenuitem tc_submenuitem_multiCluster_overview"><span i18n>Overview</span></cds-sidenav-item>
+ <cds-sidenav-item route="/multi-cluster/manage-clusters"
+ title="Manager Cluster"
+ i18n-title
+ [useRouter]="true"
+ class="tc_submenuitem tc_submenuitem_multiCluster_manage_clusters"><span i18n>Manager Cluster</span></cds-sidenav-item>
+ </cds-sidenav-menu>
+ <!-- Cluster -->
+ <cds-sidenav-menu title="Cluster"
i18n-title
- *ngIf="permissions.hosts.read"
- class="tc_submenuitem tc_submenuitem_admin_services"><span i18n>Services</span></cds-sidenav-item>
- <cds-sidenav-item route="/upgrade"
- [useRouter]="true"
- title="Upgrade"
+ *ngIf="permissions.hosts.read || permissions.monitor.read || permissions.osd.read || permissions.pool.read"
+ class="tc_menuitem_cluster">
+ <svg cdsIcon="web-services--cluster"
+ icon
+ size="20"></svg>
+ <cds-sidenav-item route="/pool"
+ [useRouter]="true"
+ title="Pools"
+ i18n-title
+ *ngIf="permissions.pool.read"
+ class="tc_submenuitem tc_submenuitem_cluster_pool"><span i18n>Pools</span></cds-sidenav-item>
+ <cds-sidenav-item route="/hosts"
+ [useRouter]="true"
+ title="Hosts"
+ i18n-title
+ *ngIf="permissions.hosts.read"
+ class="tc_submenuitem tc_submenuitem_cluster_hosts"><span i18n>Hosts</span></cds-sidenav-item>
+ <cds-sidenav-item route="/osd"
+ [useRouter]="true"
+ title="OSDs"
+ i18n-title
+ *ngIf="permissions.osd.read"
+ class="tc_submenuitem tc_submenuitem_cluster_osds"><span i18n>OSDs</span></cds-sidenav-item>
+ <cds-sidenav-item route="/inventory"
+ [useRouter]="true"
+ title="Physical Disks"
+ i18n-title
+ *ngIf="permissions.hosts.read"
+ class="tc_submenuitem tc_submenuitem_cluster_inventory"><span i18n>Physical Disks</span></cds-sidenav-item>
+ <cds-sidenav-item route="/crush-map"
+ [useRouter]="true"
+ title="CRUSH Map"
+ i18n-title
+ *ngIf="permissions.osd.read"
+ class="tc_submenuitem tc_submenuitem_cluster_crush"><span i18n>CRUSH Map</span></cds-sidenav-item>
+ <cds-sidenav-item route="/monitor"
+ [useRouter]="true"
+ title="Monitors"
+ i18n-title
+ *ngIf="permissions.monitor.read"
+ class="tc_submenuitem tc_submenuitem_cluster_monitor"><span i18n>Monitors</span></cds-sidenav-item>
+ </cds-sidenav-menu>
+ <!-- Block Storage -->
+ <cds-sidenav-menu title="Block"
i18n-title
- *ngIf="permissions.configOpt.read"
- class="tc_submenuitem tc_submenuitem_admin_upgrade"><span i18n>Upgrade</span></cds-sidenav-item>
- <cds-sidenav-item route="/ceph-users"
- [useRouter]="true"
- title="Ceph Users"
+ *ngIf="(permissions.rbdImage.read || permissions.rbdMirroring.read|| permissions.iscsi.read) && (enabledFeature.rbd || enabledFeature.mirroring || enabledFeature.iscsi)"
+ class="tc_menuitem_block">
+ <svg cdsIcon="datastore"
+ icon
+ size="20"></svg>
+ <cds-sidenav-item route="/block/rbd"
+ [useRouter]="true"
+ title="Images"
+ i18n-title
+ *ngIf="permissions.rbdImage.read && enabledFeature.rbd"
+ class="tc_submenuitem tc_submenuitem_block_images"><span i18n>Images</span></cds-sidenav-item>
+ <cds-sidenav-item route="/block/mirroring"
+ [useRouter]="true"
+ title="Mirroring"
+ i18n-title
+ *ngIf="permissions.rbdMirroring.read && enabledFeature.mirroring"
+ class="tc_submenuitem tc_submenuitem_block_mirroring">
+ <span i18n>Mirroring
+ <small *ngIf="summaryData?.rbd_mirroring?.warnings !== 0"
+ class="badge badge-warning">{{ summaryData?.rbd_mirroring?.warnings }}</small>
+ <small *ngIf="summaryData?.rbd_mirroring?.errors !== 0"
+ class="badge badge-danger">{{ summaryData?.rbd_mirroring?.errors }}</small>
+ </span>
+ </cds-sidenav-item>
+ <cds-sidenav-item route="/block/iscsi"
+ [useRouter]="true"
+ title="iSCSI"
+ i18n-title
+ *ngIf="permissions.iscsi.read && enabledFeature.iscsi"
+ class="tc_submenuitem tc_submenuitem_block_iscsi"><span i18n>iSCSI</span></cds-sidenav-item>
+ </cds-sidenav-menu>
+ <!-- Object Storage -->
+ <cds-sidenav-menu title="Object"
i18n-title
- *ngIf="permissions.configOpt.read"
- class="tc_submenuitem tc_submenuitem_admin_users"><span i18n>Ceph Users</span></cds-sidenav-item>
- <cds-sidenav-item route="/mgr-modules"
- [useRouter]="true"
- title="Manager Modules"
+ *ngIf="permissions.rgw.read && enabledFeature.rgw"
+ class="nav-item tc_menuitem_rgw">
+ <svg cdsIcon="object-storage"
+ icon
+ size="20"></svg>
+ <cds-sidenav-item route="/rgw/overview"
+ title="Overview"
+ i18n-title
+ [useRouter]="true"
+ class="tc_submenuitem tc_submenuitem_rgw_overview"><span i18n>Overview</span></cds-sidenav-item>
+ <cds-sidenav-item route="/rgw/bucket"
+ title="Buckets"
+ i18n-title
+ [useRouter]="true"
+ class="tc_submenuitem tc_submenuitem_rgw_buckets"><span i18n>Buckets</span></cds-sidenav-item>
+ <cds-sidenav-item route="/rgw/user"
+ title="Users"
+ i18n-title
+ [useRouter]="true"
+ class="tc_submenuitem tc_submenuitem_rgw_users"><span i18n>Users</span></cds-sidenav-item>
+ <cds-sidenav-item route="/rgw/multisite"
+ title="Multi-site"
+ i18n-title
+ [useRouter]="true"
+ class="tc_submenuitem tc_submenuitem_rgw_buckets"><span i18n>Multi-site</span></cds-sidenav-item>
+ <cds-sidenav-item route="/rgw/daemon"
+ title="Gateways"
+ i18n-title
+ [useRouter]="true"
+ class="tc_submenuitem tc_submenuitem_rgw_daemons"><span i18n>Gateways</span></cds-sidenav-item>
+ <cds-sidenav-item route="/nfs"
+ [useRouter]="true"
+ title="NFS"
+ i18n-title
+ *ngIf="permissions.nfs.read && enabledFeature.nfs"
+ class="tc_submenuitem tc_submenuitem_rgw_nfs"><span i18n>NFS</span></cds-sidenav-item>
+ </cds-sidenav-menu>
+ <!-- Filesystem -->
+ <cds-sidenav-menu title="File"
i18n-title
- *ngIf="permissions.configOpt.read"
- class="tc_submenuitem tc_submenuitem_admin_modules"><span i18n>Manager Modules</span></cds-sidenav-item>
- <cds-sidenav-item route="/configuration"
- [useRouter]="true"
- title="Configuration"
+ *ngIf="permissions.nfs.read && enabledFeature.nfs || permissions.cephfs.read && enabledFeature.cephfs"
+ class="tc_menuitem_file">
+ <svg cdsIcon="file-storage"
+ icon
+ size="20"></svg>
+ <cds-sidenav-item route="/cephfs"
+ [useRouter]="true"
+ title="File Systems"
+ i18n-title
+ *ngIf="permissions.cephfs.read && enabledFeature.cephfs"
+ class="tc_submenuitem tc_submenuitem_file_cephfs"><span i18n>File Systems</span></cds-sidenav-item>
+ <cds-sidenav-item route="/nfs"
+ [useRouter]="true"
+ title="NFS"
+ i18n-title
+ *ngIf="permissions.nfs.read && enabledFeature.nfs"
+ class="tc_submenuitem tc_submenuitem_file_nfs"><span i18n>NFS</span></cds-sidenav-item>
+ </cds-sidenav-menu>
+ <!-- Observability -->
+ <cds-sidenav-menu title="Observability"
+ i18n-title
+ *ngIf="permissions.log.read || permissions.prometheus.read"
+ class="tc_menuitem_observe">
+ <svg cdsIcon="observed--hail"
+ icon
+ size="20"></svg>
+ <cds-sidenav-item route="/logs"
+ [useRouter]="true"
+ title="Logs"
+ i18n-title
+ *ngIf="permissions.log.read"
+ class="tc_submenuitem tc_submenuitem_observe_log"><span i18n>Logs</span></cds-sidenav-item>
+ <cds-sidenav-item route="/monitoring"
+ [useRouter]="true"
+ title="Alerts"
+ i18n-title
+ *ngIf="permissions.prometheus.read"
+ class="tc_submenuitem tc_submenuitem_observe_monitoring">
+ <span i18n>
+ <ng-container>Alerts</ng-container>
+ <small *ngIf="prometheusAlertService.activeCriticalAlerts > 0"
+ class="badge badge-danger ms-1">{{ prometheusAlertService.activeCriticalAlerts }}</small>
+ <small *ngIf="prometheusAlertService.activeWarningAlerts > 0"
+ class="badge badge-warning ms-1">{{ prometheusAlertService.activeWarningAlerts }}</small>
+ </span>
+ </cds-sidenav-item>
+ </cds-sidenav-menu>
+ <!-- Administration -->
+ <cds-sidenav-menu title="Administration"
i18n-title
- *ngIf="permissions.configOpt.read"
- class="tc_submenuitem tc_submenuitem_admin_configuration"><span i18n>Configuration</span></cds-sidenav-item>
- </cds-sidenav-menu>
- </cds-sidenav>
+ *ngIf="permissions.configOpt.read || permissions.hosts.read"
+ class="tc_menuitem_admin">
+ <svg cdsIcon="network--admin-control"
+ icon
+ size="20"></svg>
+ <cds-sidenav-item route="/services/"
+ [useRouter]="true"
+ title="Services"
+ i18n-title
+ *ngIf="permissions.hosts.read"
+ class="tc_submenuitem tc_submenuitem_admin_services"><span i18n>Services</span></cds-sidenav-item>
+ <cds-sidenav-item route="/upgrade"
+ [useRouter]="true"
+ title="Upgrade"
+ i18n-title
+ *ngIf="permissions.configOpt.read"
+ class="tc_submenuitem tc_submenuitem_admin_upgrade"><span i18n>Upgrade</span></cds-sidenav-item>
+ <cds-sidenav-item route="/ceph-users"
+ [useRouter]="true"
+ title="Ceph Users"
+ i18n-title
+ *ngIf="permissions.configOpt.read"
+ class="tc_submenuitem tc_submenuitem_admin_users"><span i18n>Ceph Users</span></cds-sidenav-item>
+ <cds-sidenav-item route="/mgr-modules"
+ [useRouter]="true"
+ title="Manager Modules"
+ i18n-title
+ *ngIf="permissions.configOpt.read"
+ class="tc_submenuitem tc_submenuitem_admin_modules"><span i18n>Manager Modules</span></cds-sidenav-item>
+ <cds-sidenav-item route="/configuration"
+ [useRouter]="true"
+ title="Configuration"
+ i18n-title
+ *ngIf="permissions.configOpt.read"
+ class="tc_submenuitem tc_submenuitem_admin_configuration"><span i18n>Configuration</span></cds-sidenav-item>
+ </cds-sidenav-menu>
+ </cds-sidenav>
+ </div>
+ </ng-container>
+ </ng-template>
</div>
- </ng-container>
-</ng-template>
-</div>
import { Observable, throwError } from 'rxjs';
-import { NfsFSAbstractionLayer } from '~/app/ceph/nfs/models/nfs.fsal';
+import { NfsFSAbstractionLayer, SUPPORTED_FSAL } from '~/app/ceph/nfs/models/nfs.fsal';
import { ApiClient } from '~/app/shared/api/api-client';
export interface Directory {
nfsFsal: NfsFSAbstractionLayer[] = [
{
- value: 'CEPH',
+ value: SUPPORTED_FSAL.CEPH,
descr: $localize`CephFS`,
disabled: false
},
{
- value: 'RGW',
+ value: SUPPORTED_FSAL.RGW,
descr: $localize`Object Gateway`,
disabled: false
}