]> git.apps.os.sepia.ceph.com Git - ceph.git/blob
9a131a1e80b7c0247e10ca32c4b4d2210b870b40
[ceph.git] /
1 import {
2   Component,
3   Input,
4   OnChanges,
5   OnDestroy,
6   OnInit,
7   SimpleChanges,
8   ViewChild
9 } from '@angular/core';
10 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
11 import { BehaviorSubject, Observable, Subscription, of, timer } from 'rxjs';
12 import { finalize, map, shareReplay, switchMap } from 'rxjs/operators';
13 import { CephfsSnapshotScheduleService } from '~/app/shared/api/cephfs-snapshot-schedule.service';
14 import { CdForm } from '~/app/shared/forms/cd-form';
15 import { CdTableAction } from '~/app/shared/models/cd-table-action';
16 import { CdTableColumn } from '~/app/shared/models/cd-table-column';
17 import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
18 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
19 import { Permissions } from '~/app/shared/models/permissions';
20 import { SnapshotSchedule } from '~/app/shared/models/snapshot-schedule';
21 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
22 import { ModalService } from '~/app/shared/services/modal.service';
23 import { Icons } from '~/app/shared/enum/icons.enum';
24 import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
25 import { MgrModuleService } from '~/app/shared/api/mgr-module.service';
26 import { NotificationService } from '~/app/shared/services/notification.service';
27 import { BlockUI, NgBlockUI } from 'ng-block-ui';
28 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
29 import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
30 import { CephfsSnapshotscheduleFormComponent } from '../cephfs-snapshotschedule-form/cephfs-snapshotschedule-form.component';
31 import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
32 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
33 import { FinishedTask } from '~/app/shared/models/finished-task';
34
35 @Component({
36   selector: 'cd-cephfs-snapshotschedule-list',
37   templateUrl: './cephfs-snapshotschedule-list.component.html',
38   styleUrls: ['./cephfs-snapshotschedule-list.component.scss']
39 })
40 export class CephfsSnapshotscheduleListComponent
41   extends CdForm
42   implements OnInit, OnChanges, OnDestroy {
43   @Input() fsName!: string;
44   @Input() id!: number;
45
46   @ViewChild('pathTpl', { static: true })
47   pathTpl: any;
48
49   @ViewChild('retentionTpl', { static: true })
50   retentionTpl: any;
51
52   @ViewChild('subvolTpl', { static: true })
53   subvolTpl: any;
54
55   @BlockUI()
56   blockUI: NgBlockUI;
57
58   snapshotSchedules$!: Observable<SnapshotSchedule[]>;
59   subject$ = new BehaviorSubject<SnapshotSchedule[]>([]);
60   snapScheduleModuleStatus$ = new BehaviorSubject<boolean>(false);
61   moduleServiceListSub!: Subscription;
62   columns: CdTableColumn[] = [];
63   tableActions$ = new BehaviorSubject<CdTableAction[]>([]);
64   context!: CdTableFetchDataContext;
65   selection = new CdTableSelection();
66   permissions!: Permissions;
67   modalRef!: NgbModalRef;
68   errorMessage: string = '';
69   selectedName: string = '';
70   icons = Icons;
71   tableActions: CdTableAction[] = [
72     {
73       name: this.actionLabels.CREATE,
74       permission: 'create',
75       icon: Icons.add,
76       click: () => this.openModal(false)
77     },
78     {
79       name: this.actionLabels.EDIT,
80       permission: 'update',
81       icon: Icons.edit,
82       click: () => this.openModal(true)
83     },
84     {
85       name: this.actionLabels.DELETE,
86       permission: 'delete',
87       icon: Icons.trash,
88       click: () => this.deleteSnapshotSchedule()
89     }
90   ];
91
92   MODULE_NAME = 'snap_schedule';
93   ENABLE_MODULE_TIMER = 2 * 1000;
94
95   constructor(
96     private snapshotScheduleService: CephfsSnapshotScheduleService,
97     private authStorageService: AuthStorageService,
98     private modalService: ModalService,
99     private mgrModuleService: MgrModuleService,
100     private notificationService: NotificationService,
101     private actionLabels: ActionLabelsI18n,
102     private taskWrapper: TaskWrapperService
103   ) {
104     super();
105     this.permissions = this.authStorageService.getPermissions();
106   }
107
108   ngOnChanges(changes: SimpleChanges): void {
109     if (changes.fsName) {
110       this.subject$.next([]);
111     }
112   }
113
114   ngOnInit(): void {
115     this.moduleServiceListSub = this.mgrModuleService
116       .list()
117       .pipe(
118         map((modules: any[]) => modules.find((module) => module?.['name'] === this.MODULE_NAME))
119       )
120       .subscribe({
121         next: (module: any) => this.snapScheduleModuleStatus$.next(module?.enabled)
122       });
123
124     this.snapshotSchedules$ = this.subject$.pipe(
125       switchMap(() =>
126         this.snapScheduleModuleStatus$.pipe(
127           switchMap((status) => {
128             if (!status) {
129               return of([]);
130             }
131             return this.snapshotScheduleService
132               .getSnapshotScheduleList('/', this.fsName)
133               .pipe(
134                 map((list) =>
135                   list.map((l) => ({ ...l, pathForSelection: `${l.path}@${l.schedule}` }))
136                 )
137               );
138           }),
139           shareReplay(1)
140         )
141       )
142     );
143
144     this.columns = [
145       { prop: 'pathForSelection', name: $localize`Path`, flexGrow: 3, cellTemplate: this.pathTpl },
146       { prop: 'path', isHidden: true, isInvisible: true },
147       { prop: 'subvol', name: $localize`Subvolume`, cellTemplate: this.subvolTpl },
148       { prop: 'scheduleCopy', name: $localize`Repeat interval` },
149       { prop: 'schedule', isHidden: true, isInvisible: true },
150       { prop: 'retentionCopy', name: $localize`Retention policy`, cellTemplate: this.retentionTpl },
151       { prop: 'retention', isHidden: true, isInvisible: true },
152       { prop: 'created_count', name: $localize`Created Count` },
153       { prop: 'pruned_count', name: $localize`Deleted Count` },
154       { prop: 'start', name: $localize`Start time`, cellTransformation: CellTemplate.timeAgo },
155       { prop: 'created', name: $localize`Created`, cellTransformation: CellTemplate.timeAgo }
156     ];
157
158     this.tableActions$.next(this.tableActions);
159   }
160
161   ngOnDestroy(): void {
162     this.moduleServiceListSub.unsubscribe();
163   }
164
165   fetchData() {
166     this.subject$.next([]);
167   }
168
169   updateSelection(selection: CdTableSelection) {
170     this.selection = selection;
171     if (!this.selection.hasSelection) return;
172     const isActive = this.selection.first()?.active;
173
174     this.tableActions$.next([
175       ...this.tableActions,
176       {
177         name: isActive ? this.actionLabels.DEACTIVATE : this.actionLabels.ACTIVATE,
178         permission: 'update',
179         icon: isActive ? Icons.warning : Icons.success,
180         click: () =>
181           isActive ? this.deactivateSnapshotSchedule() : this.activateSnapshotSchedule()
182       }
183     ]);
184   }
185
186   openModal(edit = false) {
187     this.modalService.show(
188       CephfsSnapshotscheduleFormComponent,
189       {
190         fsName: this.fsName,
191         id: this.id,
192         path: this.selection?.first()?.path,
193         schedule: this.selection?.first()?.schedule,
194         retention: this.selection?.first()?.retention,
195         start: this.selection?.first()?.start,
196         status: this.selection?.first()?.status,
197         isEdit: edit
198       },
199       { size: 'lg' }
200     );
201   }
202
203   enableSnapshotSchedule() {
204     let $obs;
205     const fnWaitUntilReconnected = () => {
206       timer(this.ENABLE_MODULE_TIMER).subscribe(() => {
207         // Trigger an API request to check if the connection is
208         // re-established.
209         this.mgrModuleService.list().subscribe(
210           () => {
211             // Resume showing the notification toasties.
212             this.notificationService.suspendToasties(false);
213             // Unblock the whole UI.
214             this.blockUI.stop();
215             // Reload the data table content.
216             this.notificationService.show(
217               NotificationType.success,
218               $localize`Enabled Snapshot Schedule Module`
219             );
220             // Reload the data table content.
221           },
222           () => {
223             fnWaitUntilReconnected();
224           }
225         );
226       });
227     };
228
229     if (!this.snapScheduleModuleStatus$.value) {
230       $obs = this.mgrModuleService
231         .enable(this.MODULE_NAME)
232         .pipe(finalize(() => this.snapScheduleModuleStatus$.next(true)));
233     }
234     $obs.subscribe(
235       () => undefined,
236       () => {
237         // Suspend showing the notification toasties.
238         this.notificationService.suspendToasties(true);
239         // Block the whole UI to prevent user interactions until
240         // the connection to the backend is reestablished
241         this.blockUI.start($localize`Reconnecting, please wait ...`);
242         fnWaitUntilReconnected();
243       }
244     );
245   }
246
247   deactivateSnapshotSchedule() {
248     const { path, start, fs, schedule, subvol, group } = this.selection.first();
249
250     this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
251       itemDescription: $localize`snapshot schedule`,
252       actionDescription: this.actionLabels.DEACTIVATE,
253       submitActionObservable: () =>
254         this.taskWrapper.wrapTaskAroundCall({
255           task: new FinishedTask('cephfs/snapshot/schedule/deactivate', {
256             path
257           }),
258           call: this.snapshotScheduleService.deactivate({
259             path,
260             schedule,
261             start,
262             fs,
263             subvol,
264             group
265           })
266         })
267     });
268   }
269
270   activateSnapshotSchedule() {
271     const { path, start, fs, schedule, subvol, group } = this.selection.first();
272
273     this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
274       itemDescription: $localize`snapshot schedule`,
275       actionDescription: this.actionLabels.ACTIVATE,
276       submitActionObservable: () =>
277         this.taskWrapper.wrapTaskAroundCall({
278           task: new FinishedTask('cephfs/snapshot/schedule/activate', {
279             path
280           }),
281           call: this.snapshotScheduleService.activate({
282             path,
283             schedule,
284             start,
285             fs,
286             subvol,
287             group
288           })
289         })
290     });
291   }
292
293   deleteSnapshotSchedule() {
294     const { path, start, fs, schedule, subvol, group, retention } = this.selection.first();
295     const retentionPolicy = retention
296       ?.split(/\s/gi)
297       ?.filter((r: string) => !!r)
298       ?.map((r: string) => {
299         const frequency = r.substring(r.length - 1);
300         const interval = r.substring(0, r.length - 1);
301         return `${interval}-${frequency}`;
302       })
303       ?.join('|')
304       ?.toLocaleLowerCase();
305
306     this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
307       itemDescription: $localize`snapshot schedule`,
308       submitActionObservable: () =>
309         this.taskWrapper.wrapTaskAroundCall({
310           task: new FinishedTask('cephfs/snapshot/schedule/' + URLVerbs.DELETE, {
311             path
312           }),
313           call: this.snapshotScheduleService.delete({
314             path,
315             schedule,
316             start,
317             fs,
318             retentionPolicy,
319             subvol,
320             group
321           })
322         })
323     });
324   }
325 }