From 1eeaa5016beaa1a23912b60667942a76cca2bce7 Mon Sep 17 00:00:00 2001 From: Tiago Melo Date: Tue, 3 Jul 2018 10:59:03 +0100 Subject: [PATCH] mgr/dashboard: Add UI for RBD Trash Purge Signed-off-by: Tiago Melo --- .../src/app/ceph/block/block.module.ts | 7 +- .../rbd-trash-list.component.html | 8 ++ .../rbd-trash-list.component.ts | 5 + .../rbd-trash-purge-modal.component.html | 53 +++++++++ .../rbd-trash-purge-modal.component.scss | 0 .../rbd-trash-purge-modal.component.spec.ts | 105 ++++++++++++++++++ .../rbd-trash-purge-modal.component.ts | 72 ++++++++++++ .../src/app/shared/api/rbd.service.ts | 6 + 8 files changed, 254 insertions(+), 2 deletions(-) create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-purge-modal/rbd-trash-purge-modal.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-purge-modal/rbd-trash-purge-modal.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-purge-modal/rbd-trash-purge-modal.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-purge-modal/rbd-trash-purge-modal.component.ts diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts index b9c97d6badffd..36606c6f30219 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts @@ -19,6 +19,7 @@ import { RbdSnapshotFormComponent } from './rbd-snapshot-form/rbd-snapshot-form. import { RbdSnapshotListComponent } from './rbd-snapshot-list/rbd-snapshot-list.component'; import { RbdTrashListComponent } from './rbd-trash-list/rbd-trash-list.component'; import { RbdTrashMoveModalComponent } from './rbd-trash-move-modal/rbd-trash-move-modal.component'; +import { RbdTrashPurgeModalComponent } from './rbd-trash-purge-modal/rbd-trash-purge-modal.component'; import { RbdTrashRestoreModalComponent } from './rbd-trash-restore-modal/rbd-trash-restore-modal.component'; @NgModule({ @@ -26,7 +27,8 @@ import { RbdTrashRestoreModalComponent } from './rbd-trash-restore-modal/rbd-tra RbdDetailsComponent, RbdSnapshotFormComponent, RbdTrashMoveModalComponent, - RbdTrashRestoreModalComponent + RbdTrashRestoreModalComponent, + RbdTrashPurgeModalComponent ], imports: [ CommonModule, @@ -53,7 +55,8 @@ import { RbdTrashRestoreModalComponent } from './rbd-trash-restore-modal/rbd-tra RbdTrashListComponent, RbdTrashMoveModalComponent, RbdImagesComponent, - RbdTrashRestoreModalComponent + RbdTrashRestoreModalComponent, + RbdTrashPurgeModalComponent ] }) export class BlockModule {} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-list/rbd-trash-list.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-list/rbd-trash-list.component.html index e033ab841ff9b..a16ece60f8bcd 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-list/rbd-trash-list.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-list/rbd-trash-list.component.html @@ -15,6 +15,14 @@ [selection]="selection" [tableActions]="tableActions"> + + diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-list/rbd-trash-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-list/rbd-trash-list.component.ts index 6f0f6437c6f72..6a505b3f6bddb 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-list/rbd-trash-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-list/rbd-trash-list.component.ts @@ -19,6 +19,7 @@ import { CdDatePipe } from '../../../shared/pipes/cd-date.pipe'; import { AuthStorageService } from '../../../shared/services/auth-storage.service'; import { TaskListService } from '../../../shared/services/task-list.service'; import { TaskWrapperService } from '../../../shared/services/task-wrapper.service'; +import { RbdTrashPurgeModalComponent } from '../rbd-trash-purge-modal/rbd-trash-purge-modal.component'; import { RbdTrashRestoreModalComponent } from '../rbd-trash-restore-modal/rbd-trash-restore-modal.component'; @Component({ @@ -197,4 +198,8 @@ export class RbdTrashListComponent implements OnInit { isExpired(expiresAt): boolean { return moment().isAfter(expiresAt); } + + purgeModal() { + this.modalService.show(RbdTrashPurgeModalComponent); + } } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-purge-modal/rbd-trash-purge-modal.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-purge-modal/rbd-trash-purge-modal.component.html new file mode 100644 index 0000000000000..1a3e52ac119c8 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-purge-modal/rbd-trash-purge-modal.component.html @@ -0,0 +1,53 @@ + + Purge Trash + + +
+ + + +
+
+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-purge-modal/rbd-trash-purge-modal.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-purge-modal/rbd-trash-purge-modal.component.scss new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-purge-modal/rbd-trash-purge-modal.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-purge-modal/rbd-trash-purge-modal.component.spec.ts new file mode 100644 index 0000000000000..95ad297283c94 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-purge-modal/rbd-trash-purge-modal.component.spec.ts @@ -0,0 +1,105 @@ +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { ReactiveFormsModule } from '@angular/forms'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { ToastModule } from 'ng2-toastr'; +import { BsModalRef } from 'ngx-bootstrap'; + +import { configureTestBed } from '../../../../testing/unit-test-helper'; +import { Permission } from '../../../shared/models/permissions'; +import { NotificationService } from '../../../shared/services/notification.service'; +import { SharedModule } from '../../../shared/shared.module'; +import { RbdTrashPurgeModalComponent } from './rbd-trash-purge-modal.component'; + +describe('RbdTrashPurgeModalComponent', () => { + let component: RbdTrashPurgeModalComponent; + let fixture: ComponentFixture; + let httpTesting: HttpTestingController; + + configureTestBed({ + imports: [ + HttpClientTestingModule, + ReactiveFormsModule, + SharedModule, + ToastModule.forRoot(), + RouterTestingModule + ], + declarations: [RbdTrashPurgeModalComponent], + providers: [BsModalRef] + }); + + beforeEach(() => { + fixture = TestBed.createComponent(RbdTrashPurgeModalComponent); + httpTesting = TestBed.get(HttpTestingController); + component = fixture.componentInstance; + }); + + it('should create', () => { + fixture.detectChanges(); + expect(component).toBeTruthy(); + }); + + it( + 'should finish ngOnInit', + fakeAsync(() => { + component.poolPermission = new Permission(['read', 'create', 'update', 'delete']); + fixture.detectChanges(); + const req = httpTesting.expectOne('api/pool?attrs=pool_name,application_metadata'); + req.flush([ + { + application_metadata: ['foo'], + pool_name: 'bar' + }, + { + application_metadata: ['rbd'], + pool_name: 'baz' + } + ]); + tick(); + expect(component.pools).toEqual(['baz']); + expect(component.purgeForm).toBeTruthy(); + }) + ); + + it('should call ngOnInit without pool permissions', () => { + component.poolPermission = new Permission([]); + component.ngOnInit(); + httpTesting.expectOne('api/summary'); + httpTesting.verify(); + }); + + describe('should call purge', () => { + let notificationService: NotificationService; + let modalRef: BsModalRef; + let req; + + beforeEach(() => { + fixture.detectChanges(); + notificationService = TestBed.get(NotificationService); + modalRef = TestBed.get(BsModalRef); + + component.purgeForm.patchValue({ poolName: 'foo' }); + + spyOn(modalRef, 'hide').and.stub(); + spyOn(component.purgeForm, 'setErrors').and.stub(); + spyOn(notificationService, 'show').and.stub(); + + component.purge(); + + req = httpTesting.expectOne('api/block/image/trash/purge/?pool_name=foo'); + }); + + it('with success', () => { + req.flush(null); + expect(component.purgeForm.setErrors).toHaveBeenCalledTimes(0); + expect(component.modalRef.hide).toHaveBeenCalledTimes(1); + }); + + it('with failure', () => { + req.flush(null, { status: 500, statusText: 'failure' }); + expect(component.purgeForm.setErrors).toHaveBeenCalledTimes(1); + expect(component.modalRef.hide).toHaveBeenCalledTimes(0); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-purge-modal/rbd-trash-purge-modal.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-purge-modal/rbd-trash-purge-modal.component.ts new file mode 100644 index 0000000000000..cda71e538c30a --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-trash-purge-modal/rbd-trash-purge-modal.component.ts @@ -0,0 +1,72 @@ +import { Component, OnInit } from '@angular/core'; + +import { BsModalRef } from 'ngx-bootstrap'; + +import { PoolService } from '../../../shared/api/pool.service'; +import { RbdService } from '../../../shared/api/rbd.service'; +import { CdFormBuilder } from '../../../shared/forms/cd-form-builder'; +import { CdFormGroup } from '../../../shared/forms/cd-form-group'; +import { FinishedTask } from '../../../shared/models/finished-task'; +import { Permission } from '../../../shared/models/permissions'; +import { AuthStorageService } from '../../../shared/services/auth-storage.service'; +import { TaskWrapperService } from '../../../shared/services/task-wrapper.service'; + +@Component({ + selector: 'cd-rbd-trash-purge-modal', + templateUrl: './rbd-trash-purge-modal.component.html', + styleUrls: ['./rbd-trash-purge-modal.component.scss'] +}) +export class RbdTrashPurgeModalComponent implements OnInit { + poolPermission: Permission; + purgeForm: CdFormGroup; + pools: any[]; + + constructor( + private authStorageService: AuthStorageService, + private rbdService: RbdService, + public modalRef: BsModalRef, + private fb: CdFormBuilder, + private poolService: PoolService, + private taskWrapper: TaskWrapperService + ) { + this.poolPermission = this.authStorageService.getPermissions().pool; + } + + createForm() { + this.purgeForm = this.fb.group({ + poolName: '' + }); + } + + ngOnInit() { + if (this.poolPermission.read) { + this.poolService.list(['pool_name', 'application_metadata']).then((resp) => { + this.pools = resp + .filter((pool) => pool.application_metadata.includes('rbd')) + .map((pool) => pool.pool_name); + }); + } + + this.createForm(); + } + + purge() { + const poolName = this.purgeForm.getValue('poolName') || ''; + this.taskWrapper + .wrapTaskAroundCall({ + task: new FinishedTask('rbd/trash/purge', { + pool_name: poolName + }), + call: this.rbdService.purgeTrash(poolName) + }) + .subscribe( + undefined, + () => { + this.purgeForm.setErrors({ cdSubmitButton: true }); + }, + () => { + this.modalRef.hide(); + } + ); + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rbd.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rbd.service.ts index 765d2054f8687..02e1bcdc1522e 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rbd.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rbd.service.ts @@ -108,6 +108,12 @@ export class RbdService { ); } + purgeTrash(poolName) { + return this.http.post(`api/block/image/trash/purge/?pool_name=${poolName}`, null, { + observe: 'response' + }); + } + restoreTrash(poolName, imageId, newImageName) { return this.http.post( `api/block/image/trash/${poolName}/${imageId}/restore`, -- 2.39.5