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 { RbdTrashRestoreModalComponent } from './rbd-trash-restore-modal/rbd-trash-restore-modal.component';
@NgModule({
- entryComponents: [RbdDetailsComponent, RbdSnapshotFormComponent, RbdTrashMoveModalComponent],
+ entryComponents: [
+ RbdDetailsComponent,
+ RbdSnapshotFormComponent,
+ RbdTrashMoveModalComponent,
+ RbdTrashRestoreModalComponent
+ ],
imports: [
CommonModule,
FormsModule,
RbdSnapshotFormComponent,
RbdTrashListComponent,
RbdTrashMoveModalComponent,
- RbdImagesComponent
+ RbdImagesComponent,
+ RbdTrashRestoreModalComponent
]
})
export class BlockModule {}
forceIdentifier="true"
selectionType="single"
(updateSelection)="updateSelection($event)">
+ <div class="table-actions btn-toolbar">
+ <cd-table-actions class="btn-group"
+ [permission]="permission"
+ [selection]="selection"
+ [tableActions]="tableActions">
+ </cd-table-actions>
+ </div>
</cd-table>
<ng-template #expiresTpl
import { TableComponent } from '../../../shared/datatable/table/table.component';
import { CellTemplate } from '../../../shared/enum/cell-template.enum';
import { ViewCacheStatus } from '../../../shared/enum/view-cache-status.enum';
+import { CdTableAction } from '../../../shared/models/cd-table-action';
import { CdTableColumn } from '../../../shared/models/cd-table-column';
import { CdTableSelection } from '../../../shared/models/cd-table-selection';
import { ExecutingTask } from '../../../shared/models/executing-task';
+import { Permission } from '../../../shared/models/permissions';
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 { RbdTrashRestoreModalComponent } from '../rbd-trash-restore-modal/rbd-trash-restore-modal.component';
@Component({
selector: 'cd-rbd-trash-list',
executingTasks: ExecutingTask[] = [];
images: any;
modalRef: BsModalRef;
+ permission: Permission;
retries: number;
selection = new CdTableSelection();
+ tableActions: CdTableAction[];
viewCacheStatusList: any[];
constructor(
+ private authStorageService: AuthStorageService,
private rbdService: RbdService,
private modalService: BsModalService,
private cdDatePipe: CdDatePipe,
private taskListService: TaskListService
- ) {}
+ ) {
+ this.permission = this.authStorageService.getPermissions().rbdImage;
+
+ const restoreAction: CdTableAction = {
+ permission: 'update',
+ icon: 'fa-undo',
+ click: () => this.restoreModal(),
+ name: 'Restore'
+ };
+ this.tableActions = [restoreAction];
+ }
ngOnInit() {
this.columns = [
updateSelection(selection: CdTableSelection) {
this.selection = selection;
}
+
+ restoreModal() {
+ const initialState = {
+ metaType: 'RBD',
+ poolName: this.selection.first().pool_name,
+ imageName: this.selection.first().name,
+ imageId: this.selection.first().id
+ };
+
+ this.modalRef = this.modalService.show(RbdTrashRestoreModalComponent, { initialState });
+ }
}
--- /dev/null
+<cd-modal>
+ <ng-container i18n
+ class="modal-title">Restore Image</ng-container>
+
+ <ng-container class="modal-content">
+ <form name="restoreForm"
+ class="form"
+ #formDir="ngForm"
+ [formGroup]="restoreForm"
+ novalidate>
+ <div class="modal-body">
+ <p>
+ <ng-container i18n>To restore</ng-container>
+ <kbd>{{ poolName }}/{{ imageName }}@{{ imageId }}</kbd>,
+ <ng-container i18n>type the image's new name and click</ng-container>
+ <kbd i18n>Restore Image</kbd>.
+ </p>
+
+ <div class="form-group"
+ [ngClass]="{'has-error': restoreForm.showError('name', formDir)}">
+ <label for="name"
+ i18n>New Name</label>
+ <input type="text"
+ class="form-control"
+ name="name"
+ id="name"
+ autocomplete="off"
+ formControlName="name"
+ autofocus>
+ <span class="help-block"
+ *ngIf="restoreForm.showError('name', formDir, 'required')"
+ i18n>
+ This field is required.
+ </span>
+ </div>
+ </div>
+
+ <div class="modal-footer">
+ <div class="button-group text-right">
+ <cd-submit-button i18n
+ [form]="restoreForm"
+ (submitAction)="restore()">
+ Restore Image
+ </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>
--- /dev/null
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+import { ComponentFixture, TestBed } 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 { NotificationService } from '../../../shared/services/notification.service';
+import { SharedModule } from '../../../shared/shared.module';
+import { RbdTrashRestoreModalComponent } from './rbd-trash-restore-modal.component';
+
+describe('RbdTrashRestoreModalComponent', () => {
+ let component: RbdTrashRestoreModalComponent;
+ let fixture: ComponentFixture<RbdTrashRestoreModalComponent>;
+
+ configureTestBed({
+ declarations: [RbdTrashRestoreModalComponent],
+ imports: [
+ ReactiveFormsModule,
+ HttpClientTestingModule,
+ ToastModule.forRoot(),
+ SharedModule,
+ RouterTestingModule
+ ],
+ providers: [BsModalRef]
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(RbdTrashRestoreModalComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ describe('should call restore', () => {
+ let httpTesting: HttpTestingController;
+ let notificationService: NotificationService;
+ let modalRef: BsModalRef;
+ let req;
+
+ beforeEach(() => {
+ httpTesting = TestBed.get(HttpTestingController);
+ notificationService = TestBed.get(NotificationService);
+ modalRef = TestBed.get(BsModalRef);
+
+ component.poolName = 'foo';
+ component.imageId = 'bar';
+
+ spyOn(modalRef, 'hide').and.stub();
+ spyOn(component.restoreForm, 'setErrors').and.stub();
+ spyOn(notificationService, 'show').and.stub();
+
+ component.restore();
+
+ req = httpTesting.expectOne('api/block/image/trash/foo/bar/restore');
+ });
+
+ it('with success', () => {
+ req.flush(null);
+ expect(component.restoreForm.setErrors).toHaveBeenCalledTimes(0);
+ expect(component.modalRef.hide).toHaveBeenCalledTimes(1);
+ });
+
+ it('with failure', () => {
+ req.flush(null, { status: 500, statusText: 'failure' });
+ expect(component.restoreForm.setErrors).toHaveBeenCalledTimes(1);
+ expect(component.modalRef.hide).toHaveBeenCalledTimes(0);
+ });
+ });
+});
--- /dev/null
+import { Component, OnInit } from '@angular/core';
+
+import { BsModalRef } from 'ngx-bootstrap';
+
+import { RbdService } from '../../../shared/api/rbd.service';
+import { CdFormBuilder } from '../../../shared/forms/cd-form-builder';
+import { CdFormGroup } from '../../../shared/forms/cd-form-group';
+import { ExecutingTask } from '../../../shared/models/executing-task';
+import { FinishedTask } from '../../../shared/models/finished-task';
+import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
+
+@Component({
+ selector: 'cd-rbd-trash-restore-modal',
+ templateUrl: './rbd-trash-restore-modal.component.html',
+ styleUrls: ['./rbd-trash-restore-modal.component.scss']
+})
+export class RbdTrashRestoreModalComponent implements OnInit {
+ metaType: string;
+ poolName: string;
+ imageName: string;
+ imageId: string;
+ executingTasks: ExecutingTask[];
+
+ restoreForm: CdFormGroup;
+
+ constructor(
+ private rbdService: RbdService,
+ public modalRef: BsModalRef,
+ private fb: CdFormBuilder,
+ private taskWrapper: TaskWrapperService
+ ) {}
+
+ ngOnInit() {
+ this.restoreForm = this.fb.group({
+ name: this.imageName
+ });
+ }
+
+ restore() {
+ const name = this.restoreForm.getValue('name');
+
+ this.taskWrapper
+ .wrapTaskAroundCall({
+ task: new FinishedTask('rbd/trash/restore', {
+ pool_name: this.poolName,
+ image_id: this.imageId,
+ image_name: this.imageName,
+ new_image_name: name
+ }),
+ call: this.rbdService.restoreTrash(this.poolName, this.imageId, name)
+ })
+ .subscribe(
+ undefined,
+ () => {
+ this.restoreForm.setErrors({ cdSubmitButton: true });
+ },
+ () => {
+ this.modalRef.hide();
+ }
+ );
+ }
+}
{ observe: 'response' }
);
}
+
+ restoreTrash(poolName, imageId, newImageName) {
+ return this.http.post(
+ `api/block/image/trash/${poolName}/${imageId}/restore`,
+ { new_image_name: newImageName },
+ { observe: 'response' }
+ );
+ }
}
() => ({
2: `Could not find image.`
})
+ ),
+ 'rbd/trash/restore': new TaskMessage(
+ new TaskMessageOperation('Restoring', 'restore', 'Restored'),
+ (metadata) =>
+ `image '${metadata.pool_name}/${metadata.image_name}@${metadata.image_id}' \
+ into '${metadata.pool_name}/${metadata.new_image_name}'`,
+ (metadata) => ({
+ 17: `Image name '${metadata.pool_name}/${metadata.new_image_name}' is already in use.`
+ })
)
};