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