mirror of
https://github.com/ceph/ceph
synced 2025-03-22 10:17:23 +00:00
mgr/dashboard: Add UI for RBD Trash Purge
Signed-off-by: Tiago Melo <tmelo@suse.com>
This commit is contained in:
parent
11067e0686
commit
1eeaa5016b
@ -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 {}
|
||||
|
@ -15,6 +15,14 @@
|
||||
[selection]="selection"
|
||||
[tableActions]="tableActions">
|
||||
</cd-table-actions>
|
||||
|
||||
<button class="btn btn-sm btn-default btn-label"
|
||||
type="button"
|
||||
(click)="purgeModal()">
|
||||
<i class="fa fa-fw fa-times"
|
||||
aria-hidden="true"></i>
|
||||
<ng-container i18n>Purge Trash</ng-container>
|
||||
</button>
|
||||
</div>
|
||||
</cd-table>
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,53 @@
|
||||
<cd-modal>
|
||||
<ng-container i18n
|
||||
class="modal-title">Purge Trash</ng-container>
|
||||
|
||||
<ng-container class="modal-content">
|
||||
<form name="purgeForm"
|
||||
class="form"
|
||||
#formDir="ngForm"
|
||||
[formGroup]="purgeForm"
|
||||
novalidate>
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
<ng-container i18n>To purge, select one or All images and click</ng-container>
|
||||
<kbd i18n>Purge Trash</kbd>.
|
||||
</p>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="center-block"
|
||||
i18n>Pool:
|
||||
</label>
|
||||
<input class="form-control"
|
||||
type="text"
|
||||
placeholder="Pool name..."
|
||||
i18n-placeholder
|
||||
formControlName="poolName"
|
||||
*ngIf="!poolPermission.read">
|
||||
<select class="form-control"
|
||||
formControlName="poolName"
|
||||
*ngIf="poolPermission.read">
|
||||
<option value=""
|
||||
i18n>All</option>
|
||||
<option *ngFor="let pool of pools"
|
||||
[value]="pool">{{ pool }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<div class="button-group text-right">
|
||||
<cd-submit-button i18n
|
||||
[form]="purgeForm"
|
||||
(submitAction)="purge()">
|
||||
Purge Trash
|
||||
</cd-submit-button>
|
||||
<button i18n
|
||||
type="button"
|
||||
class="btn btn-sm btn-default"
|
||||
(click)="modalRef.hide()">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</ng-container>
|
||||
</cd-modal>
|
@ -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<RbdTrashPurgeModalComponent>;
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
@ -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();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@ -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`,
|
||||
|
Loading…
Reference in New Issue
Block a user