From e0bb7117031408b50416031d9adb9e99e16b2100 Mon Sep 17 00:00:00 2001 From: Ivo Almeida Date: Wed, 7 Feb 2024 14:03:45 +0000 Subject: [PATCH] mgr/dashboard: added delete and activation actions Fixes: https://tracker.ceph.com/issues/64355 Signed-off-by: Ivo Almeida --- .../mgr/dashboard/controllers/cephfs.py | 51 ++++++ ...ephfs-snapshotschedule-list.component.html | 2 +- .../cephfs-snapshotschedule-list.component.ts | 120 ++++++++++++-- .../api/cephfs-snapshot-schedule.service.ts | 30 +++- .../src/app/shared/constants/app.constants.ts | 5 + .../shared/services/task-message.service.ts | 24 ++- src/pybind/mgr/dashboard/openapi.yaml | 147 ++++++++++++++++++ 7 files changed, 357 insertions(+), 22 deletions(-) diff --git a/src/pybind/mgr/dashboard/controllers/cephfs.py b/src/pybind/mgr/dashboard/controllers/cephfs.py index 1437962723ff2..83e9cbfd8533f 100644 --- a/src/pybind/mgr/dashboard/controllers/cephfs.py +++ b/src/pybind/mgr/dashboard/controllers/cephfs.py @@ -1025,3 +1025,54 @@ class CephFSSnapshotSchedule(RESTController): editRetentionPolicies('snap_schedule_retention_add', retention_to_add) return f'Retention policies for snapshot schedule on path {path} updated successfully' + + @RESTController.Resource('DELETE') + def delete_snapshot(self, fs: str, path: str, schedule: str, start: str): + error_code, _, err = mgr.remote('snap_schedule', + 'snap_schedule_rm', + path, + schedule, + start, + fs, + None, + None) + if error_code != 0: + raise DashboardException( + f'Failed to delete snapshot schedule for path {path}: {err}' + ) + + return f'Snapshot schedule for path {path} deleted successfully' + + @RESTController.Resource('POST') + def deactivate(self, fs: str, path: str, schedule: str, start: str): + error_code, _, err = mgr.remote('snap_schedule', + 'snap_schedule_deactivate', + path, + schedule, + start, + fs, + None, + None) + if error_code != 0: + raise DashboardException( + f'Failed to deactivate snapshot schedule for path {path}: {err}' + ) + + return f'Snapshot schedule for path {path} deactivated successfully' + + @RESTController.Resource('POST') + def activate(self, fs: str, path: str, schedule: str, start: str): + error_code, _, err = mgr.remote('snap_schedule', + 'snap_schedule_activate', + path, + schedule, + start, + fs, + None, + None) + if error_code != 0: + raise DashboardException( + f'Failed to activate snapshot schedule for path {path}: {err}' + ) + + return f'Snapshot schedule for path {path} activated successfully' diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.html index 0346de67f9419..f2e93cc742c7a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.html @@ -73,7 +73,7 @@ [permission]="permissions.cephfs" [selection]="selection" class="btn-group" - [tableActions]="tableActions" + [tableActions]="tableActions$ | async" > diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.ts index b6b52a15c99d2..58d3a0cc056af 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cephfs/cephfs-snapshotschedule-list/cephfs-snapshotschedule-list.component.ts @@ -26,8 +26,11 @@ import { MgrModuleService } from '~/app/shared/api/mgr-module.service'; import { NotificationService } from '~/app/shared/services/notification.service'; import { BlockUI, NgBlockUI } from 'ng-block-ui'; import { NotificationType } from '~/app/shared/enum/notification-type.enum'; -import { ActionLabelsI18n } from '~/app/shared/constants/app.constants'; +import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants'; import { CephfsSnapshotscheduleFormComponent } from '../cephfs-snapshotschedule-form/cephfs-snapshotschedule-form.component'; +import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component'; +import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service'; +import { FinishedTask } from '~/app/shared/models/finished-task'; @Component({ selector: 'cd-cephfs-snapshotschedule-list', @@ -51,7 +54,7 @@ export class CephfsSnapshotscheduleListComponent snapScheduleModuleStatus$ = new BehaviorSubject(false); moduleServiceListSub!: Subscription; columns: CdTableColumn[] = []; - tableActions: CdTableAction[] = []; + tableActions$ = new BehaviorSubject([]); context!: CdTableFetchDataContext; selection = new CdTableSelection(); permissions!: Permissions; @@ -59,6 +62,26 @@ export class CephfsSnapshotscheduleListComponent errorMessage: string = ''; selectedName: string = ''; icons = Icons; + tableActions: CdTableAction[] = [ + { + name: this.actionLabels.CREATE, + permission: 'create', + icon: Icons.add, + click: () => this.openModal(false) + }, + { + name: this.actionLabels.EDIT, + permission: 'update', + icon: Icons.edit, + click: () => this.openModal(true) + }, + { + name: this.actionLabels.DELETE, + permission: 'delete', + icon: Icons.trash, + click: () => this.deleteSnapshotSchedule() + } + ]; MODULE_NAME = 'snap_schedule'; ENABLE_MODULE_TIMER = 2 * 1000; @@ -69,7 +92,8 @@ export class CephfsSnapshotscheduleListComponent private modalService: ModalService, private mgrModuleService: MgrModuleService, private notificationService: NotificationService, - private actionLables: ActionLabelsI18n + private actionLabels: ActionLabelsI18n, + private taskWrapper: TaskWrapperService ) { super(); this.permissions = this.authStorageService.getPermissions(); @@ -116,20 +140,7 @@ export class CephfsSnapshotscheduleListComponent { prop: 'created', name: $localize`Created`, cellTransformation: CellTemplate.timeAgo } ]; - this.tableActions = [ - { - name: this.actionLables.CREATE, - permission: 'create', - icon: Icons.add, - click: () => this.openModal(false) - }, - { - name: this.actionLables.EDIT, - permission: 'update', - icon: Icons.edit, - click: () => this.openModal(true) - } - ]; + this.tableActions$.next(this.tableActions); } ngOnDestroy(): void { @@ -142,6 +153,19 @@ export class CephfsSnapshotscheduleListComponent updateSelection(selection: CdTableSelection) { this.selection = selection; + if (!this.selection.hasSelection) return; + const isActive = this.selection.first()?.active; + + this.tableActions$.next([ + ...this.tableActions, + { + name: isActive ? this.actionLabels.DEACTIVATE : this.actionLabels.ACTIVATE, + permission: 'update', + icon: isActive ? Icons.warning : Icons.success, + click: () => + isActive ? this.deactivateSnapshotSchedule() : this.activateSnapshotSchedule() + } + ]); } openModal(edit = false) { @@ -204,4 +228,66 @@ export class CephfsSnapshotscheduleListComponent } ); } + + deactivateSnapshotSchedule() { + const { path, start, fs, schedule } = this.selection.first(); + + this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, { + itemDescription: $localize`snapshot schedule`, + actionDescription: this.actionLabels.DEACTIVATE, + submitActionObservable: () => + this.taskWrapper.wrapTaskAroundCall({ + task: new FinishedTask('cephfs/snapshot/schedule/deactivate', { + path + }), + call: this.snapshotScheduleService.deactivate({ + path, + schedule, + start, + fs + }) + }) + }); + } + + activateSnapshotSchedule() { + const { path, start, fs, schedule } = this.selection.first(); + + this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, { + itemDescription: $localize`snapshot schedule`, + actionDescription: this.actionLabels.ACTIVATE, + submitActionObservable: () => + this.taskWrapper.wrapTaskAroundCall({ + task: new FinishedTask('cephfs/snapshot/schedule/activate', { + path + }), + call: this.snapshotScheduleService.activate({ + path, + schedule, + start, + fs + }) + }) + }); + } + + deleteSnapshotSchedule() { + const { path, start, fs, schedule } = this.selection.first(); + + this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, { + itemDescription: $localize`snapshot schedule`, + submitActionObservable: () => + this.taskWrapper.wrapTaskAroundCall({ + task: new FinishedTask('cephfs/snapshot/schedule/' + URLVerbs.DELETE, { + path + }), + call: this.snapshotScheduleService.delete({ + path, + schedule, + start, + fs + }) + }) + }); + } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs-snapshot-schedule.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs-snapshot-schedule.service.ts index 0719089c249f2..9e07f6057ac1d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs-snapshot-schedule.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/cephfs-snapshot-schedule.service.ts @@ -19,14 +19,38 @@ export class CephfsSnapshotScheduleService { return this.http.post(`${this.baseURL}/snapshot/schedule`, data, { observe: 'response' }); } - update(data: Record): Observable { + update({ fs, path, ...rest }: Record): Observable { return this.http.put( - `${this.baseURL}/snapshot/schedule/${data.fs}/${encodeURIComponent(data.path)}`, - data, + `${this.baseURL}/snapshot/schedule/${fs}/${encodeURIComponent(path)}`, + rest, { observe: 'response' } ); } + activate({ fs, path, ...rest }: Record): Observable { + return this.http.post( + `${this.baseURL}/snapshot/schedule/${fs}/${encodeURIComponent(path)}/activate`, + rest, + { observe: 'response' } + ); + } + + deactivate({ fs, path, ...rest }: Record): Observable { + return this.http.post( + `${this.baseURL}/snapshot/schedule/${fs}/${encodeURIComponent(path)}/deactivate`, + rest, + { observe: 'response' } + ); + } + + delete({ fs, path, schedule, start }: Record): Observable { + return this.http.delete( + `${this.baseURL}/snapshot/schedule/${fs}/${encodeURIComponent( + path + )}/delete_snapshot?schedule=${schedule}&start=${encodeURIComponent(start)}` + ); + } + checkScheduleExists( path: string, fs: string, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/constants/app.constants.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/constants/app.constants.ts index 7edce5ff6671d..2cf3f1047bab6 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/constants/app.constants.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/constants/app.constants.ts @@ -141,6 +141,8 @@ export class ActionLabelsI18n { IMPORT: any; MIGRATE: string; START_UPGRADE: string; + ACTIVATE: string; + DEACTIVATE: string; constructor() { /* Create a new item */ @@ -218,6 +220,9 @@ export class ActionLabelsI18n { this.DEMOTE = $localize`Demote`; this.START_UPGRADE = $localize`Start Upgrade`; + + this.ACTIVATE = $localize`Activate`; + this.DEACTIVATE = $localize`Deactivate`; } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-message.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-message.service.ts index 84e31efea0137..4fbcc09d09027 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-message.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/services/task-message.service.ts @@ -69,7 +69,17 @@ export class TaskMessageService { delete: new TaskMessageOperation($localize`Deleting`, $localize`delete`, $localize`Deleted`), add: new TaskMessageOperation($localize`Adding`, $localize`add`, $localize`Added`), remove: new TaskMessageOperation($localize`Removing`, $localize`remove`, $localize`Removed`), - import: new TaskMessageOperation($localize`Importing`, $localize`import`, $localize`Imported`) + import: new TaskMessageOperation($localize`Importing`, $localize`import`, $localize`Imported`), + activate: new TaskMessageOperation( + $localize`Importing`, + $localize`activate`, + $localize`Activated` + ), + deactivate: new TaskMessageOperation( + $localize`Importing`, + $localize`deactivate`, + $localize`Deactivated` + ) }; rbd = { @@ -393,6 +403,18 @@ export class TaskMessageService { ), 'cephfs/snapshot/schedule/edit': this.newTaskMessage(this.commonOperations.update, (metadata) => this.snapshotSchedule(metadata) + ), + 'cephfs/snapshot/schedule/delete': this.newTaskMessage( + this.commonOperations.delete, + (metadata) => this.snapshotSchedule(metadata) + ), + 'cephfs/snapshot/schedule/activate': this.newTaskMessage( + this.commonOperations.activate, + (metadata) => this.snapshotSchedule(metadata) + ), + 'cephfs/snapshot/schedule/deactivate': this.newTaskMessage( + this.commonOperations.deactivate, + (metadata) => this.snapshotSchedule(metadata) ) }; diff --git a/src/pybind/mgr/dashboard/openapi.yaml b/src/pybind/mgr/dashboard/openapi.yaml index 8457ecb65c6a1..1fbaac399aaa9 100644 --- a/src/pybind/mgr/dashboard/openapi.yaml +++ b/src/pybind/mgr/dashboard/openapi.yaml @@ -1890,6 +1890,153 @@ paths: - jwt: [] tags: - CephFSSnapshotSchedule + /api/cephfs/snapshot/schedule/{fs}/{path}/activate: + post: + parameters: + - in: path + name: fs + required: true + schema: + type: string + - in: path + name: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + properties: + schedule: + type: string + start: + type: string + required: + - schedule + - start + type: object + responses: + '201': + content: + application/vnd.ceph.api.v1.0+json: + type: object + description: Resource created. + '202': + content: + application/vnd.ceph.api.v1.0+json: + type: object + description: Operation is still executing. Please check the task queue. + '400': + description: Operation exception. Please check the response body for details. + '401': + description: Unauthenticated access. Please login first. + '403': + description: Unauthorized access. Please check your permissions. + '500': + description: Unexpected error. Please check the response body for the stack + trace. + security: + - jwt: [] + tags: + - CephFSSnapshotSchedule + /api/cephfs/snapshot/schedule/{fs}/{path}/deactivate: + post: + parameters: + - in: path + name: fs + required: true + schema: + type: string + - in: path + name: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + properties: + schedule: + type: string + start: + type: string + required: + - schedule + - start + type: object + responses: + '201': + content: + application/vnd.ceph.api.v1.0+json: + type: object + description: Resource created. + '202': + content: + application/vnd.ceph.api.v1.0+json: + type: object + description: Operation is still executing. Please check the task queue. + '400': + description: Operation exception. Please check the response body for details. + '401': + description: Unauthenticated access. Please login first. + '403': + description: Unauthorized access. Please check your permissions. + '500': + description: Unexpected error. Please check the response body for the stack + trace. + security: + - jwt: [] + tags: + - CephFSSnapshotSchedule + /api/cephfs/snapshot/schedule/{fs}/{path}/delete_snapshot: + delete: + parameters: + - in: path + name: fs + required: true + schema: + type: string + - in: path + name: path + required: true + schema: + type: string + - in: query + name: schedule + required: true + schema: + type: string + - in: query + name: start + required: true + schema: + type: string + responses: + '202': + content: + application/vnd.ceph.api.v1.0+json: + type: object + description: Operation is still executing. Please check the task queue. + '204': + content: + application/vnd.ceph.api.v1.0+json: + type: object + description: Resource deleted. + '400': + description: Operation exception. Please check the response body for details. + '401': + description: Unauthenticated access. Please login first. + '403': + description: Unauthorized access. Please check your permissions. + '500': + description: Unexpected error. Please check the response body for the stack + trace. + security: + - jwt: [] + tags: + - CephFSSnapshotSchedule /api/cephfs/subvolume: post: parameters: [] -- 2.39.5