summaryrefslogtreecommitdiffstats
path: root/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts')
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts336
1 files changed, 336 insertions, 0 deletions
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts
new file mode 100644
index 000000000..df66b0e88
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.ts
@@ -0,0 +1,336 @@
+import {
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
+ Component,
+ Input,
+ OnChanges,
+ OnInit,
+ TemplateRef,
+ ViewChild
+} from '@angular/core';
+
+import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+import moment from 'moment';
+import { of } from 'rxjs';
+
+import { RbdService } from '~/app/shared/api/rbd.service';
+import { CdHelperClass } from '~/app/shared/classes/cd-helper.class';
+import { ConfirmationModalComponent } from '~/app/shared/components/confirmation-modal/confirmation-modal.component';
+import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
+import { CdTableAction } from '~/app/shared/models/cd-table-action';
+import { CdTableColumn } from '~/app/shared/models/cd-table-column';
+import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
+import { ExecutingTask } from '~/app/shared/models/executing-task';
+import { FinishedTask } from '~/app/shared/models/finished-task';
+import { ImageSpec } from '~/app/shared/models/image-spec';
+import { Permission } from '~/app/shared/models/permissions';
+import { Task } from '~/app/shared/models/task';
+import { CdDatePipe } from '~/app/shared/pipes/cd-date.pipe';
+import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { ModalService } from '~/app/shared/services/modal.service';
+import { NotificationService } from '~/app/shared/services/notification.service';
+import { SummaryService } from '~/app/shared/services/summary.service';
+import { TaskListService } from '~/app/shared/services/task-list.service';
+import { TaskManagerService } from '~/app/shared/services/task-manager.service';
+import { RbdSnapshotFormModalComponent } from '../rbd-snapshot-form/rbd-snapshot-form-modal.component';
+import { RbdSnapshotActionsModel } from './rbd-snapshot-actions.model';
+import { RbdSnapshotModel } from './rbd-snapshot.model';
+
+@Component({
+ selector: 'cd-rbd-snapshot-list',
+ templateUrl: './rbd-snapshot-list.component.html',
+ styleUrls: ['./rbd-snapshot-list.component.scss'],
+ providers: [TaskListService],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class RbdSnapshotListComponent implements OnInit, OnChanges {
+ @Input()
+ snapshots: RbdSnapshotModel[] = [];
+ @Input()
+ featuresName: string[];
+ @Input()
+ poolName: string;
+ @Input()
+ namespace: string;
+ @Input()
+ mirroring: string;
+ @Input()
+ rbdName: string;
+ @ViewChild('nameTpl')
+ nameTpl: TemplateRef<any>;
+ @ViewChild('rollbackTpl', { static: true })
+ rollbackTpl: TemplateRef<any>;
+
+ permission: Permission;
+ selection = new CdTableSelection();
+ tableActions: CdTableAction[];
+ rbdTableActions: RbdSnapshotActionsModel;
+ imageSpec: ImageSpec;
+
+ data: RbdSnapshotModel[];
+
+ columns: CdTableColumn[];
+
+ modalRef: NgbModalRef;
+
+ builders = {
+ 'rbd/snap/create': (metadata: any) => {
+ const model = new RbdSnapshotModel();
+ model.name = metadata['snapshot_name'];
+ return model;
+ }
+ };
+
+ constructor(
+ private authStorageService: AuthStorageService,
+ private modalService: ModalService,
+ private dimlessBinaryPipe: DimlessBinaryPipe,
+ private cdDatePipe: CdDatePipe,
+ private rbdService: RbdService,
+ private taskManagerService: TaskManagerService,
+ private notificationService: NotificationService,
+ private summaryService: SummaryService,
+ private taskListService: TaskListService,
+ private actionLabels: ActionLabelsI18n,
+ private cdr: ChangeDetectorRef
+ ) {
+ this.permission = this.authStorageService.getPermissions().rbdImage;
+ }
+
+ ngOnInit() {
+ this.columns = [
+ {
+ name: $localize`Name`,
+ prop: 'name',
+ cellTransformation: CellTemplate.executing,
+ flexGrow: 2
+ },
+ {
+ name: $localize`Size`,
+ prop: 'size',
+ flexGrow: 1,
+ cellClass: 'text-right',
+ pipe: this.dimlessBinaryPipe
+ },
+ {
+ name: $localize`Provisioned`,
+ prop: 'disk_usage',
+ flexGrow: 1,
+ cellClass: 'text-right',
+ pipe: this.dimlessBinaryPipe
+ },
+ {
+ name: $localize`State`,
+ prop: 'is_protected',
+ flexGrow: 1,
+ cellTransformation: CellTemplate.badge,
+ customTemplateConfig: {
+ map: {
+ true: { value: $localize`PROTECTED`, class: 'badge-success' },
+ false: { value: $localize`UNPROTECTED`, class: 'badge-info' }
+ }
+ }
+ },
+ {
+ name: $localize`Created`,
+ prop: 'timestamp',
+ flexGrow: 1,
+ pipe: this.cdDatePipe
+ }
+ ];
+
+ this.imageSpec = new ImageSpec(this.poolName, this.namespace, this.rbdName);
+ this.rbdTableActions = new RbdSnapshotActionsModel(
+ this.actionLabels,
+ this.featuresName,
+ this.rbdService
+ );
+ this.rbdTableActions.create.click = () => this.openCreateSnapshotModal();
+ this.rbdTableActions.rename.click = () => this.openEditSnapshotModal();
+ this.rbdTableActions.protect.click = () => this.toggleProtection();
+ this.rbdTableActions.unprotect.click = () => this.toggleProtection();
+ const getImageUri = () =>
+ this.selection.first() &&
+ `${this.imageSpec.toStringEncoded()}/${encodeURIComponent(this.selection.first().name)}`;
+ this.rbdTableActions.clone.routerLink = () => `/block/rbd/clone/${getImageUri()}`;
+ this.rbdTableActions.copy.routerLink = () => `/block/rbd/copy/${getImageUri()}`;
+ this.rbdTableActions.rollback.click = () => this.rollbackModal();
+ this.rbdTableActions.deleteSnap.click = () => this.deleteSnapshotModal();
+
+ this.tableActions = this.rbdTableActions.ordering;
+
+ const itemFilter = (entry: any, task: Task) => {
+ return entry.name === task.metadata['snapshot_name'];
+ };
+
+ const taskFilter = (task: Task) => {
+ return (
+ ['rbd/snap/create', 'rbd/snap/delete', 'rbd/snap/edit', 'rbd/snap/rollback'].includes(
+ task.name
+ ) && this.imageSpec.toString() === task.metadata['image_spec']
+ );
+ };
+
+ this.taskListService.init(
+ () => of(this.snapshots),
+ null,
+ (items) => {
+ const hasChanges = CdHelperClass.updateChanged(this, {
+ data: items
+ });
+ if (hasChanges) {
+ this.cdr.detectChanges();
+ this.data = [...this.data];
+ }
+ },
+ () => {
+ const hasChanges = CdHelperClass.updateChanged(this, {
+ data: this.snapshots
+ });
+ if (hasChanges) {
+ this.cdr.detectChanges();
+ this.data = [...this.data];
+ }
+ },
+ taskFilter,
+ itemFilter,
+ this.builders
+ );
+ }
+
+ ngOnChanges() {
+ if (this.columns) {
+ this.imageSpec = new ImageSpec(this.poolName, this.namespace, this.rbdName);
+ if (this.rbdTableActions) {
+ this.rbdTableActions.featuresName = this.featuresName;
+ }
+ this.taskListService.fetch();
+ }
+ }
+
+ private openSnapshotModal(taskName: string, snapName: string = null) {
+ const modalVariables = {
+ mirroring: this.mirroring
+ };
+ this.modalRef = this.modalService.show(RbdSnapshotFormModalComponent, modalVariables);
+ this.modalRef.componentInstance.poolName = this.poolName;
+ this.modalRef.componentInstance.imageName = this.rbdName;
+ this.modalRef.componentInstance.namespace = this.namespace;
+ if (snapName) {
+ this.modalRef.componentInstance.setEditing();
+ } else {
+ // Auto-create a name for the snapshot: <image_name>_<timestamp_ISO_8601>
+ // https://en.wikipedia.org/wiki/ISO_8601
+ snapName = `${this.rbdName}_${moment().toISOString(true)}`;
+ }
+ this.modalRef.componentInstance.setSnapName(snapName);
+ this.modalRef.componentInstance.onSubmit.subscribe((snapshotName: string) => {
+ const executingTask = new ExecutingTask();
+ executingTask.name = taskName;
+ executingTask.metadata = {
+ image_spec: this.imageSpec.toString(),
+ snapshot_name: snapshotName
+ };
+ this.summaryService.addRunningTask(executingTask);
+ });
+ }
+
+ openCreateSnapshotModal() {
+ this.openSnapshotModal('rbd/snap/create');
+ }
+
+ openEditSnapshotModal() {
+ this.openSnapshotModal('rbd/snap/edit', this.selection.first().name);
+ }
+
+ toggleProtection() {
+ const snapshotName = this.selection.first().name;
+ const isProtected = this.selection.first().is_protected;
+ const finishedTask = new FinishedTask();
+ finishedTask.name = 'rbd/snap/edit';
+ const imageSpec = new ImageSpec(this.poolName, this.namespace, this.rbdName);
+ finishedTask.metadata = {
+ image_spec: imageSpec.toString(),
+ snapshot_name: snapshotName
+ };
+ this.rbdService
+ .protectSnapshot(imageSpec, snapshotName, !isProtected)
+ .toPromise()
+ .then(() => {
+ const executingTask = new ExecutingTask();
+ executingTask.name = finishedTask.name;
+ executingTask.metadata = finishedTask.metadata;
+ this.summaryService.addRunningTask(executingTask);
+ this.taskManagerService.subscribe(
+ finishedTask.name,
+ finishedTask.metadata,
+ (asyncFinishedTask: FinishedTask) => {
+ this.notificationService.notifyTask(asyncFinishedTask);
+ }
+ );
+ });
+ }
+
+ _asyncTask(task: string, taskName: string, snapshotName: string) {
+ const finishedTask = new FinishedTask();
+ finishedTask.name = taskName;
+ finishedTask.metadata = {
+ image_spec: new ImageSpec(this.poolName, this.namespace, this.rbdName).toString(),
+ snapshot_name: snapshotName
+ };
+ const imageSpec = new ImageSpec(this.poolName, this.namespace, this.rbdName);
+ this.rbdService[task](imageSpec, snapshotName)
+ .toPromise()
+ .then(() => {
+ const executingTask = new ExecutingTask();
+ executingTask.name = finishedTask.name;
+ executingTask.metadata = finishedTask.metadata;
+ this.summaryService.addRunningTask(executingTask);
+ this.modalRef.close();
+ this.taskManagerService.subscribe(
+ executingTask.name,
+ executingTask.metadata,
+ (asyncFinishedTask: FinishedTask) => {
+ this.notificationService.notifyTask(asyncFinishedTask);
+ }
+ );
+ })
+ .catch(() => {
+ this.modalRef.componentInstance.stopLoadingSpinner();
+ });
+ }
+
+ rollbackModal() {
+ const snapshotName = this.selection.selected[0].name;
+ const imageSpec = new ImageSpec(this.poolName, this.namespace, this.rbdName).toString();
+ const initialState = {
+ titleText: $localize`RBD snapshot rollback`,
+ buttonText: $localize`Rollback`,
+ bodyTpl: this.rollbackTpl,
+ bodyData: {
+ snapName: `${imageSpec}@${snapshotName}`
+ },
+ onSubmit: () => {
+ this._asyncTask('rollbackSnapshot', 'rbd/snap/rollback', snapshotName);
+ }
+ };
+
+ this.modalRef = this.modalService.show(ConfirmationModalComponent, initialState);
+ }
+
+ deleteSnapshotModal() {
+ const snapshotName = this.selection.selected[0].name;
+ this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
+ itemDescription: $localize`RBD snapshot`,
+ itemNames: [snapshotName],
+ submitAction: () => this._asyncTask('deleteSnapshot', 'rbd/snap/delete', snapshotName)
+ });
+ }
+
+ updateSelection(selection: CdTableSelection) {
+ this.selection = selection;
+ }
+}