]> git.apps.os.sepia.ceph.com Git - ceph.git/blob
06650c69b62af0e14274d7943a8e1a2aa12680ae
[ceph.git] /
1 import {
2   Component,
3   Input,
4   OnChanges,
5   OnInit,
6   SimpleChanges,
7   TemplateRef,
8   ViewChild
9 } from '@angular/core';
10 import { BehaviorSubject, Observable, of } from 'rxjs';
11 import { catchError, switchMap, tap } from 'rxjs/operators';
12 import { CephfsSubvolumeService } from '~/app/shared/api/cephfs-subvolume.service';
13 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
14 import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
15 import { Icons } from '~/app/shared/enum/icons.enum';
16 import { CdTableAction } from '~/app/shared/models/cd-table-action';
17 import { CdTableColumn } from '~/app/shared/models/cd-table-column';
18 import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
19 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
20 import { CephfsSubvolume } from '~/app/shared/models/cephfs-subvolume.model';
21 import { ModalService } from '~/app/shared/services/modal.service';
22 import { CephfsSubvolumeFormComponent } from '../cephfs-subvolume-form/cephfs-subvolume-form.component';
23 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
24 import { Permissions } from '~/app/shared/models/permissions';
25 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
26 import { FinishedTask } from '~/app/shared/models/finished-task';
27 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
28 import { FormControl } from '@angular/forms';
29 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
30 import { CdForm } from '~/app/shared/forms/cd-form';
31 import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
32 import { CephfsSubvolumeGroupService } from '~/app/shared/api/cephfs-subvolume-group.service';
33 import { CephfsSubvolumeGroup } from '~/app/shared/models/cephfs-subvolume-group.model';
34 import { CephfsMountDetailsComponent } from '../cephfs-mount-details/cephfs-mount-details.component';
35 import { HealthService } from '~/app/shared/api/health.service';
36 import _ from 'lodash';
37 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
38
39 const DEFAULT_SUBVOLUME_GROUP = '_nogroup';
40
41 @Component({
42   selector: 'cd-cephfs-subvolume-list',
43   templateUrl: './cephfs-subvolume-list.component.html',
44   styleUrls: ['./cephfs-subvolume-list.component.scss']
45 })
46 export class CephfsSubvolumeListComponent extends CdForm implements OnInit, OnChanges {
47   @ViewChild('quotaUsageTpl', { static: true })
48   quotaUsageTpl: any;
49
50   @ViewChild('typeTpl', { static: true })
51   typeTpl: any;
52
53   @ViewChild('modeToHumanReadableTpl', { static: true })
54   modeToHumanReadableTpl: any;
55
56   @ViewChild('nameTpl', { static: true })
57   nameTpl: any;
58
59   @ViewChild('quotaSizeTpl', { static: true })
60   quotaSizeTpl: any;
61
62   @ViewChild('removeTmpl', { static: true })
63   removeTmpl: TemplateRef<any>;
64
65   @Input() fsName: string;
66   @Input() pools: any[];
67
68   columns: CdTableColumn[] = [];
69   tableActions: CdTableAction[];
70   context: CdTableFetchDataContext;
71   selection = new CdTableSelection();
72   removeForm: CdFormGroup;
73   icons = Icons;
74   permissions: Permissions;
75   modalRef: NgbModalRef;
76   errorMessage: string = '';
77   selectedName: string = '';
78
79   subVolumes$: Observable<CephfsSubvolume[]>;
80   subVolumeGroups$: Observable<CephfsSubvolumeGroup[]>;
81   subject = new BehaviorSubject<CephfsSubvolume[]>([]);
82   groupsSubject = new BehaviorSubject<CephfsSubvolume[]>([]);
83
84   subvolumeGroupList: string[] = [];
85   subVolumesList: CephfsSubvolume[] = [];
86
87   activeGroupName: string = '';
88
89   constructor(
90     private cephfsSubVolumeService: CephfsSubvolumeService,
91     private actionLabels: ActionLabelsI18n,
92     private modalService: ModalService,
93     private authStorageService: AuthStorageService,
94     private taskWrapper: TaskWrapperService,
95     private cephfsSubvolumeGroupService: CephfsSubvolumeGroupService,
96     private healthService: HealthService,
97     private cdsModalService: ModalCdsService
98   ) {
99     super();
100     this.permissions = this.authStorageService.getPermissions();
101   }
102
103   ngOnInit(): void {
104     this.columns = [
105       {
106         name: $localize`Name`,
107         prop: 'name',
108         flexGrow: 1,
109         cellTemplate: this.nameTpl
110       },
111       {
112         name: $localize`Data Pool`,
113         prop: 'info.data_pool',
114         flexGrow: 0.7,
115         cellTransformation: CellTemplate.badge,
116         customTemplateConfig: {
117           class: 'badge-background-primary'
118         }
119       },
120       {
121         name: $localize`Usage`,
122         prop: 'info.bytes_pcent',
123         flexGrow: 0.7,
124         cellTemplate: this.quotaUsageTpl,
125         cellClass: 'text-right'
126       },
127       {
128         name: $localize`Path`,
129         prop: 'info.path',
130         flexGrow: 1,
131         cellTransformation: CellTemplate.path
132       },
133       {
134         name: $localize`Mode`,
135         prop: 'info.mode',
136         flexGrow: 0.5,
137         cellTemplate: this.modeToHumanReadableTpl
138       },
139       {
140         name: $localize`Created`,
141         prop: 'info.created_at',
142         flexGrow: 0.5,
143         cellTransformation: CellTemplate.timeAgo
144       }
145     ];
146
147     this.tableActions = [
148       {
149         name: this.actionLabels.CREATE,
150         permission: 'create',
151         icon: Icons.add,
152         click: () => this.openModal()
153       },
154       {
155         name: this.actionLabels.EDIT,
156         permission: 'update',
157         icon: Icons.edit,
158         click: () => this.openModal(true)
159       },
160       {
161         name: this.actionLabels.ATTACH,
162         permission: 'read',
163         icon: Icons.bars,
164         disable: () => !this.selection?.hasSelection,
165         click: () => this.showAttachInfo()
166       },
167       {
168         name: this.actionLabels.NFS_EXPORT,
169         permission: 'create',
170         icon: Icons.nfsExport,
171         routerLink: () => [
172           '/cephfs/nfs/create',
173           this.fsName,
174           _.isEmpty(this.activeGroupName) ? DEFAULT_SUBVOLUME_GROUP : this.activeGroupName,
175           { subvolume: this.selection?.first()?.name }
176         ],
177         disable: () => !this.selection?.hasSingleSelection
178       },
179       {
180         name: this.actionLabels.REMOVE,
181         permission: 'delete',
182         icon: Icons.destroy,
183         click: () => this.removeSubVolumeModal()
184       }
185     ];
186
187     this.subVolumeGroups$ = this.groupsSubject.pipe(
188       switchMap(() =>
189         this.cephfsSubvolumeGroupService.get(this.fsName, false).pipe(
190           tap((groups) => {
191             this.subvolumeGroupList = groups.map((group) => group.name);
192             this.subvolumeGroupList.unshift('');
193           }),
194           catchError(() => {
195             this.context.error();
196             return of(null);
197           })
198         )
199       )
200     );
201   }
202
203   fetchData() {
204     this.subject.next([]);
205   }
206
207   ngOnChanges(changes: SimpleChanges) {
208     if (changes.fsName) {
209       this.subject.next([]);
210       this.groupsSubject.next([]);
211     }
212   }
213
214   updateSelection(selection: CdTableSelection) {
215     this.selection = selection;
216   }
217
218   showAttachInfo() {
219     const selectedSubVolume = this.selection?.selected?.[0];
220
221     this.healthService.getClusterFsid().subscribe({
222       next: (clusterId: string) => {
223         this.modalRef = this.modalService.show(CephfsMountDetailsComponent, {
224           onSubmit: () => this.modalRef.close(),
225           mountData: {
226             fsId: clusterId,
227             fsName: this.fsName,
228             rootPath: selectedSubVolume.info.path
229           }
230         });
231       }
232     });
233   }
234
235   openModal(edit = false) {
236     this.modalService.show(
237       CephfsSubvolumeFormComponent,
238       {
239         fsName: this.fsName,
240         subVolumeName: this.selection?.first()?.name,
241         subVolumeGroupName: this.activeGroupName,
242         pools: this.pools,
243         isEdit: edit
244       },
245       { size: 'lg' }
246     );
247   }
248
249   removeSubVolumeModal() {
250     this.removeForm = new CdFormGroup({
251       retainSnapshots: new FormControl(false)
252     });
253     this.errorMessage = '';
254     this.selectedName = this.selection.first().name;
255     this.modalRef = this.cdsModalService.show(CriticalConfirmationModalComponent, {
256       actionDescription: 'Remove',
257       itemNames: [this.selectedName],
258       itemDescription: 'Subvolume',
259       childFormGroup: this.removeForm,
260       childFormGroupTemplate: this.removeTmpl,
261       submitAction: () =>
262         this.taskWrapper
263           .wrapTaskAroundCall({
264             task: new FinishedTask('cephfs/subvolume/remove', { subVolumeName: this.selectedName }),
265             call: this.cephfsSubVolumeService.remove(
266               this.fsName,
267               this.selectedName,
268               this.activeGroupName,
269               this.removeForm.getValue('retainSnapshots')
270             )
271           })
272           .subscribe({
273             complete: () => this.cdsModalService.dismissAll(),
274             error: (error) => {
275               this.modalRef.componentInstance.stopLoadingSpinner();
276               this.errorMessage = error.error.detail;
277             }
278           })
279     });
280   }
281
282   selectSubVolumeGroup(subVolumeGroupName: string) {
283     this.activeGroupName = subVolumeGroupName;
284     this.getSubVolumes();
285   }
286
287   getSubVolumes() {
288     this.subVolumes$ = this.subject.pipe(
289       switchMap(() =>
290         this.cephfsSubVolumeService.get(this.fsName, this.activeGroupName).pipe(
291           catchError(() => {
292             this.context?.error();
293             return of(null);
294           })
295         )
296       )
297     );
298   }
299 }