]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/blob
09b74b51d7b7f0e39e8fc451d12442c541679cf4
[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 { Icons } from '~/app/shared/enum/icons.enum';
23 import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
24 import { MgrModuleService } from '~/app/shared/api/mgr-module.service';
25 import { NotificationService } from '~/app/shared/services/notification.service';
26 import { BlockUI, NgBlockUI } from 'ng-block-ui';
27 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
28 import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
29 import { CephfsSnapshotscheduleFormComponent } from '../cephfs-snapshotschedule-form/cephfs-snapshotschedule-form.component';
30 import { DeleteConfirmationModalComponent } from '~/app/shared/components/delete-confirmation-modal/delete-confirmation-modal.component';
31 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
32 import { FinishedTask } from '~/app/shared/models/finished-task';
33 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
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   MODULE_NAME = 'snap_schedule';
74   ENABLE_MODULE_TIMER = 2 * 1000;
75
76   constructor(
77     private snapshotScheduleService: CephfsSnapshotScheduleService,
78     private authStorageService: AuthStorageService,
79     private modalService: ModalCdsService,
80     private mgrModuleService: MgrModuleService,
81     private notificationService: NotificationService,
82     private actionLabels: ActionLabelsI18n,
83     private taskWrapper: TaskWrapperService
84   ) {
85     super();
86     this.permissions = this.authStorageService.getPermissions();
87   }
88
89   ngOnChanges(changes: SimpleChanges): void {
90     if (changes.fsName) {
91       this.subject$.next([]);
92     }
93   }
94
95   ngOnInit(): void {
96     this.tableActions = [
97       {
98         name: this.actionLabels.CREATE,
99         permission: 'create',
100         icon: Icons.add,
101         click: () => this.openModal(false)
102       },
103       {
104         name: this.actionLabels.EDIT,
105         permission: 'update',
106         icon: Icons.edit,
107         click: () => this.openModal(true)
108       },
109       {
110         name: this.actionLabels.DELETE,
111         permission: 'delete',
112         icon: Icons.trash,
113         click: () => this.deleteSnapshotSchedule()
114       }
115     ];
116
117     this.moduleServiceListSub = this.mgrModuleService
118       .list()
119       .pipe(
120         map((modules: any[]) => modules.find((module) => module?.['name'] === this.MODULE_NAME))
121       )
122       .subscribe({
123         next: (module: any) => this.snapScheduleModuleStatus$.next(module?.enabled)
124       });
125
126     this.snapshotSchedules$ = this.subject$.pipe(
127       switchMap(() =>
128         this.snapScheduleModuleStatus$.pipe(
129           switchMap((status) => {
130             if (!status) {
131               return of([]);
132             }
133             return this.snapshotScheduleService
134               .getSnapshotScheduleList('/', this.fsName)
135               .pipe(
136                 map((list) =>
137                   list.map((l) => ({ ...l, pathForSelection: `${l.path}@${l.schedule}` }))
138                 )
139               );
140           }),
141           shareReplay(1)
142         )
143       )
144     );
145
146     this.columns = [
147       { prop: 'pathForSelection', name: $localize`Path`, flexGrow: 3, cellTemplate: this.pathTpl },
148       { prop: 'path', isHidden: true, isInvisible: true },
149       { prop: 'subvol', name: $localize`Subvolume`, cellTemplate: this.subvolTpl },
150       { prop: 'scheduleCopy', name: $localize`Repeat interval` },
151       { prop: 'schedule', isHidden: true, isInvisible: true },
152       { prop: 'retentionCopy', name: $localize`Retention policy`, cellTemplate: this.retentionTpl },
153       { prop: 'retention', isHidden: true, isInvisible: true },
154       { prop: 'created_count', name: $localize`Created Count` },
155       { prop: 'pruned_count', name: $localize`Deleted Count` },
156       { prop: 'start', name: $localize`Start time`, cellTransformation: CellTemplate.timeAgo },
157       { prop: 'created', name: $localize`Created`, cellTransformation: CellTemplate.timeAgo }
158     ];
159
160     this.tableActions$.next(this.tableActions);
161   }
162
163   ngOnDestroy(): void {
164     this.moduleServiceListSub.unsubscribe();
165   }
166
167   fetchData() {
168     this.subject$.next([]);
169   }
170
171   updateSelection(selection: CdTableSelection) {
172     this.selection = selection;
173     if (!this.selection.hasSelection) return;
174     const isActive = this.selection.first()?.active;
175
176     this.tableActions$.next([
177       ...this.tableActions,
178       {
179         name: isActive ? this.actionLabels.DEACTIVATE : this.actionLabels.ACTIVATE,
180         permission: 'update',
181         icon: isActive ? Icons.warning : Icons.success,
182         click: () =>
183           isActive ? this.deactivateSnapshotSchedule() : this.activateSnapshotSchedule()
184       }
185     ]);
186   }
187
188   openModal(edit = false) {
189     this.modalService.show(CephfsSnapshotscheduleFormComponent, {
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   }
200
201   enableSnapshotSchedule() {
202     let $obs;
203     const fnWaitUntilReconnected = () => {
204       timer(this.ENABLE_MODULE_TIMER).subscribe(() => {
205         // Trigger an API request to check if the connection is
206         // re-established.
207         this.mgrModuleService.list().subscribe(
208           () => {
209             // Resume showing the notification toasties.
210             this.notificationService.suspendToasties(false);
211             // Unblock the whole UI.
212             this.blockUI.stop();
213             // Reload the data table content.
214             this.notificationService.show(
215               NotificationType.success,
216               $localize`Enabled Snapshot Schedule Module`
217             );
218             // Reload the data table content.
219           },
220           () => {
221             fnWaitUntilReconnected();
222           }
223         );
224       });
225     };
226
227     if (!this.snapScheduleModuleStatus$.value) {
228       $obs = this.mgrModuleService
229         .enable(this.MODULE_NAME)
230         .pipe(finalize(() => this.snapScheduleModuleStatus$.next(true)));
231     }
232     $obs.subscribe(
233       () => undefined,
234       () => {
235         // Suspend showing the notification toasties.
236         this.notificationService.suspendToasties(true);
237         // Block the whole UI to prevent user interactions until
238         // the connection to the backend is reestablished
239         this.blockUI.start($localize`Reconnecting, please wait ...`);
240         fnWaitUntilReconnected();
241       }
242     );
243   }
244
245   deactivateSnapshotSchedule() {
246     const { path, start, fs, schedule, subvol, group } = this.selection.first();
247
248     this.modalRef = this.modalService.show(DeleteConfirmationModalComponent, {
249       itemDescription: $localize`snapshot schedule`,
250       actionDescription: this.actionLabels.DEACTIVATE,
251       submitActionObservable: () =>
252         this.taskWrapper.wrapTaskAroundCall({
253           task: new FinishedTask('cephfs/snapshot/schedule/deactivate', {
254             path
255           }),
256           call: this.snapshotScheduleService.deactivate({
257             path,
258             schedule,
259             start,
260             fs,
261             subvol,
262             group
263           })
264         })
265     });
266   }
267
268   activateSnapshotSchedule() {
269     const { path, start, fs, schedule, subvol, group } = this.selection.first();
270
271     this.modalRef = this.modalService.show(DeleteConfirmationModalComponent, {
272       itemDescription: $localize`snapshot schedule`,
273       actionDescription: this.actionLabels.ACTIVATE,
274       submitActionObservable: () =>
275         this.taskWrapper.wrapTaskAroundCall({
276           task: new FinishedTask('cephfs/snapshot/schedule/activate', {
277             path
278           }),
279           call: this.snapshotScheduleService.activate({
280             path,
281             schedule,
282             start,
283             fs,
284             subvol,
285             group
286           })
287         })
288     });
289   }
290
291   deleteSnapshotSchedule() {
292     const { path, start, fs, schedule, subvol, group, retention } = this.selection.first();
293     const retentionPolicy = retention
294       ?.split(/\s/gi)
295       ?.filter((r: string) => !!r)
296       ?.map((r: string) => {
297         const frequency = r.substring(r.length - 1);
298         const interval = r.substring(0, r.length - 1);
299         return `${interval}-${frequency}`;
300       })
301       ?.join('|');
302
303     this.modalRef = this.modalService.show(DeleteConfirmationModalComponent, {
304       itemDescription: $localize`snapshot schedule`,
305       submitActionObservable: () =>
306         this.taskWrapper.wrapTaskAroundCall({
307           task: new FinishedTask('cephfs/snapshot/schedule/' + URLVerbs.DELETE, {
308             path
309           }),
310           call: this.snapshotScheduleService.delete({
311             path,
312             schedule,
313             start,
314             fs,
315             retentionPolicy,
316             subvol,
317             group
318           })
319         })
320     });
321   }
322 }