import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
import { FinishedTask } from '~/app/shared/models/finished-task';
import { NotificationService } from '~/app/shared/services/notification.service';
+import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+import { CephfsMountDetailsComponent } from '../cephfs-mount-details/cephfs-mount-details.component';
+import { map, switchMap } from 'rxjs/operators';
+import { HealthService } from '~/app/shared/api/health.service';
const BASE_URL = 'cephfs';
permissions: Permissions;
icons = Icons;
monAllowPoolDelete = false;
+ modalRef!: NgbModalRef;
constructor(
private authStorageService: AuthStorageService,
private configurationService: ConfigurationService,
private modalService: ModalService,
private taskWrapper: TaskWrapperService,
- public notificationService: NotificationService
+ public notificationService: NotificationService,
+ private healthService: HealthService
) {
super();
this.permissions = this.authStorageService.getPermissions();
click: () =>
this.router.navigate([this.urlBuilder.getEdit(String(this.selection.first().id))])
},
+ {
+ name: this.actionLabels.ATTACH,
+ permission: 'read',
+ icon: Icons.bars,
+ disable: () => !this.selection?.hasSelection,
+ click: () => this.showAttachInfo()
+ },
{
permission: 'delete',
icon: Icons.destroy,
this.selection = selection;
}
+ showAttachInfo() {
+ const selectedFileSystem = this.selection?.selected?.[0];
+
+ this.cephfsService
+ .getFsRootDirectory(selectedFileSystem.id)
+ .pipe(
+ switchMap((fsData) =>
+ this.healthService.getClusterFsid().pipe(map((data) => ({ clusterId: data, fs: fsData })))
+ )
+ )
+ .subscribe({
+ next: (val) => {
+ this.modalRef = this.modalService.show(CephfsMountDetailsComponent, {
+ onSubmit: () => this.modalRef.close(),
+ mountData: {
+ fsId: val.clusterId,
+ fsName: selectedFileSystem?.mdsmap?.fs_name,
+ rootPath: val.fs['path']
+ }
+ });
+ }
+ });
+ }
+
removeVolumeModal() {
const volName = this.selection.first().mdsmap['fs_name'];
this.modalService.show(CriticalConfirmationModalComponent, {
--- /dev/null
+<cd-modal (hide)="cancel()">
+ <ng-container class="modal-title">
+ <span i18n>Attach commands</span>
+ </ng-container>
+ <ng-container class="modal-content">
+ <div class="modal-body">
+ <h5 class="fw-bold"
+ i18n>
+ Using Mount command
+ </h5>
+ <cd-code-block textWrap="true"
+ [codes]="[mount]"></cd-code-block>
+
+ <h5 class="fw-bold"
+ i18n>
+ Using FUSE command
+ </h5>
+ <cd-code-block textWrap="true"
+ [codes]="[fuse]"></cd-code-block>
+
+ <h5 class="fw-bold"
+ i18n>
+ Using NFS Command
+ </h5>
+ <cd-code-block textWrap="true"
+ [codes]="[nfs]"></cd-code-block>
+ </div>
+ <div class="modal-footer">
+ <cd-submit-button (submitAction)="cancel()"
+ i18n>
+ Close
+ </cd-submit-button>
+ </div>
+ </ng-container>
+</cd-modal>
+
+
+
--- /dev/null
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { CephfsMountDetailsComponent } from './cephfs-mount-details.component';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { SharedModule } from '~/app/shared/shared.module';
+import { ToastrModule } from 'ngx-toastr';
+import { RouterTestingModule } from '@angular/router/testing';
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import { configureTestBed } from '~/testing/unit-test-helper';
+
+describe('CephfsSnapshotscheduleListComponent', () => {
+ let component: CephfsMountDetailsComponent;
+ let fixture: ComponentFixture<CephfsMountDetailsComponent>;
+
+ configureTestBed({
+ declarations: [CephfsMountDetailsComponent],
+ imports: [HttpClientTestingModule, SharedModule, ToastrModule.forRoot(), RouterTestingModule],
+ providers: [NgbActiveModal]
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(CephfsMountDetailsComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
--- /dev/null
+import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+
+@Component({
+ selector: 'cd-cephfs-mount-details',
+ templateUrl: './cephfs-mount-details.component.html',
+ styleUrls: ['./cephfs-mount-details.component.scss']
+})
+export class CephfsMountDetailsComponent implements OnInit, OnDestroy {
+ @ViewChild('mountDetailsTpl', { static: true })
+ mountDetailsTpl: any;
+ onCancel?: Function;
+ private canceled = false;
+ private MOUNT_DIRECTORY = '<MOUNT_DIRECTORY>';
+ mountData!: Record<string, any>;
+ constructor(public activeModal: NgbActiveModal) {}
+ mount!: string;
+ fuse!: string;
+ nfs!: string;
+
+ ngOnInit(): void {
+ this.mount = `sudo <CLIENT_USER>@${this.mountData?.fsId}.${this.mountData?.fsName}=${this.mountData?.rootPath} ${this.MOUNT_DIRECTORY}`;
+ this.fuse = `sudo ceph-fuse ${this.MOUNT_DIRECTORY} -r ${this.mountData?.rootPath} --client_mds_namespace=${this.mountData?.fsName}`;
+ this.nfs = `sudo mount -t nfs -o port=<PORT> <IP of active_mds daemon>:${this.mountData?.rootPath} ${this.MOUNT_DIRECTORY}`;
+ }
+
+ ngOnDestroy(): void {
+ if (this.onCancel && this.canceled) {
+ this.onCancel();
+ }
+ }
+
+ cancel() {
+ this.canceled = true;
+ this.activeModal.close();
+ }
+}
class="fw-bold"
[ngbTooltip]="fullpathTpl"
triggers="click:blur">
- {{ row.path?.split?.("@")?.[0] | path }}
+ {{ row.pathForSelection?.split?.("@")?.[0] | path }}
</span>
<span
*ngIf="row.active; else inactiveStatusTpl">
<i
[ngClass]="[icons.success, icons.large]"
- ngbTooltip="{{ row.path?.split?.('@')?.[0] }} is active"
+ ngbTooltip="{{ row.pathForSelection?.split?.('@')?.[0] }} is active"
class="text-success"
></i>
</span>
<i
[ngClass]="[icons.warning, icons.large]"
class="text-warning"
- ngbTooltip="{{ row.path?.split?.('@')?.[0] }} has been deactivated"
+ ngbTooltip="{{ row.pathForSelection?.split?.('@')?.[0] }} has been deactivated"
></i>
</ng-template>
- <ng-template #fullpathTpl>
+ <ng-template #fullpathForSelectionTpl>
<span
data-toggle="tooltip"
- [title]="row.path"
+ [title]="row.pathForSelection"
class="font-monospace"
- >{{ row.path?.split?.("@")?.[0] }}
+ >{{ row.pathForSelection?.split?.("@")?.[0] }}
<cd-copy-2-clipboard-button
- *ngIf="row.path"
- [source]="row.path?.split?.('@')?.[0]"
+ *ngIf="row.pathForSelection"
+ [source]="row.pathForSelection?.split?.('@')?.[0]"
[byId]="false"
[showIconOnly]="true"
>
}
return this.snapshotScheduleService
.getSnapshotScheduleList('/', this.fsName)
- .pipe(map((list) => list.map((l) => ({ ...l, path: `${l.path}@${l.schedule}` }))));
+ .pipe(
+ map((list) =>
+ list.map((l) => ({ ...l, pathForSelection: `${l.path}@${l.schedule}` }))
+ )
+ );
}),
shareReplay(1)
)
);
this.columns = [
- { prop: 'path', name: $localize`Path`, flexGrow: 3, cellTemplate: this.pathTpl },
+ { prop: 'pathForSelection', name: $localize`Path`, flexGrow: 3, cellTemplate: this.pathTpl },
+ { prop: 'path', isHidden: true },
{ prop: 'subvol', name: $localize`Subvolume`, cellTemplate: this.subvolTpl },
{ prop: 'scheduleCopy', name: $localize`Repeat interval` },
{ prop: 'schedule', isHidden: true },
import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
import { CephfsSubvolumeGroupService } from '~/app/shared/api/cephfs-subvolume-group.service';
import { CephfsSubvolumeGroup } from '~/app/shared/models/cephfs-subvolume-group.model';
+import { CephfsMountDetailsComponent } from '../cephfs-mount-details/cephfs-mount-details.component';
+import { HealthService } from '~/app/shared/api/health.service';
@Component({
selector: 'cd-cephfs-subvolume-list',
private modalService: ModalService,
private authStorageService: AuthStorageService,
private taskWrapper: TaskWrapperService,
- private cephfsSubvolumeGroupService: CephfsSubvolumeGroupService
+ private cephfsSubvolumeGroupService: CephfsSubvolumeGroupService,
+ private healthService: HealthService
) {
super();
this.permissions = this.authStorageService.getPermissions();
icon: Icons.edit,
click: () => this.openModal(true)
},
+ {
+ name: this.actionLabels.ATTACH,
+ permission: 'read',
+ icon: Icons.bars,
+ disable: () => !this.selection?.hasSelection,
+ click: () => this.showAttachInfo()
+ },
{
name: this.actionLabels.REMOVE,
permission: 'delete',
this.selection = selection;
}
+ showAttachInfo() {
+ const selectedSubVolume = this.selection?.selected?.[0];
+
+ this.healthService.getClusterFsid().subscribe({
+ next: (clusterId: string) => {
+ this.modalRef = this.modalService.show(CephfsMountDetailsComponent, {
+ onSubmit: () => this.modalRef.close(),
+ mountData: {
+ fsId: clusterId,
+ fsName: this.fsName,
+ rootPath: selectedSubVolume.info.path
+ }
+ });
+ }
+ });
+ }
+
openModal(edit = false) {
this.modalService.show(
CephfsSubvolumeFormComponent,
import { DataTableModule } from '../../shared/datatable/datatable.module';
import { CephfsSubvolumeSnapshotsFormComponent } from './cephfs-subvolume-snapshots-list/cephfs-subvolume-snapshots-form/cephfs-subvolume-snapshots-form.component';
import { CephfsSnapshotscheduleFormComponent } from './cephfs-snapshotschedule-form/cephfs-snapshotschedule-form.component';
+import { CephfsMountDetailsComponent } from './cephfs-mount-details/cephfs-mount-details.component';
@NgModule({
imports: [
CephfsSubvolumeSnapshotsListComponent,
CephfsSnapshotscheduleListComponent,
CephfsSnapshotscheduleFormComponent,
- CephfsSubvolumeSnapshotsFormComponent
+ CephfsSubvolumeSnapshotsFormComponent,
+ CephfsMountDetailsComponent
]
})
export class CephfsModule {}
return this.http.get(`${this.baseURL}/${id}/mds_counters`);
}
+ getFsRootDirectory(id: string) {
+ return this.http.get(`${this.baseURL}/${id}/get_root_directory`);
+ }
+
mkSnapshot(id: number, path: string, name?: string) {
let params = new HttpParams();
params = params.append('path', path);
<ng-container *ngIf="codes.length > 1; else singleCodeBlock">
<pre id="bigCodeBlock">
<span *ngFor="let code of codes"
- class="d-flex p-2 align-items-center justify-content-between text-dark">
- <span>{{code}}</span>
+ class="d-flex px-2 py-3 align-items-center justify-content-between text-dark">
+ <span [ngClass]="{'text-wrap': textWrap}">{{code}}</span>
<cd-copy-2-clipboard-button
[source]="code"
[byId]="false"></cd-copy-2-clipboard-button>
</ng-container>
<ng-template #singleCodeBlock>
- <pre class="d-flex p-2 align-items-center justify-content-between text-dark"
+ <pre class="d-flex px-2 py-3 align-items-center justify-content-between text-dark"
id="singleCodeBlock">
- <span>{{codes}}</span>
+ <span [ngClass]="{'text-wrap': textWrap}">{{codes}}</span>
<cd-copy-2-clipboard-button
[source]="codes"
[byId]="false"></cd-copy-2-clipboard-button>
-@use './src/styles/vendor/variables' as vv;
-
pre {
- background-color: vv.$code-block-bg;
+ background-color: var(--gray-200);
border-radius: 0.5rem;
}
export class CodeBlockComponent {
@Input()
codes: string[];
+
+ @Input()
+ textWrap: boolean = false;
+
+ @Input()
+ grayBg: boolean = false;
}
private getText(): string {
const element = document.getElementById(this.source) as HTMLInputElement;
- return element.value;
+ return element?.value || element?.textContent;
}
@HostListener('click')
START_UPGRADE: string;
ACTIVATE: string;
DEACTIVATE: string;
+ ATTACH: string;
constructor() {
/* Create a new item */
this.ACTIVATE = $localize`Activate`;
this.DEACTIVATE = $localize`Deactivate`;
+
+ this.ATTACH = $localize`Attach`;
}
}