import { ModalService } from '~/app/shared/services/modal.service';
import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
import { PoolEditPeerModalComponent } from '../pool-edit-peer-modal/pool-edit-peer-modal.component';
+import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
const BASE_URL = '/block/mirroring';
@Component({
const peerUUID = this.getPeerUUID();
this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
+ impact: DeletionImpact.high,
itemDescription: $localize`mirror peer`,
itemNames: [`${poolName} (${peerUUID})`],
submitActionObservable: () =>
import { RbdParentModel } from '../rbd-form/rbd-parent.model';
import { RbdTrashMoveModalComponent } from '../rbd-trash-move-modal/rbd-trash-move-modal.component';
import { RBDImageFormat, RbdModel } from './rbd-model';
-
+import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
const BASE_URL = 'block/rbd';
@Component({
const imageSpec = new ImageSpec(poolName, namespace, imageName);
this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
+ impact: DeletionImpact.high,
itemDescription: 'RBD',
- itemNames: [imageSpec],
+ itemNames: [imageSpec.imageName],
bodyTemplate: this.deleteTpl,
bodyContext: {
hasSnapshots: this.hasSnapshots(),
import { RbdSnapshotFormModalComponent } from '../rbd-snapshot-form/rbd-snapshot-form-modal.component';
import { RbdSnapshotActionsModel } from './rbd-snapshot-actions.model';
import { RbdSnapshotModel } from './rbd-snapshot.model';
+import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
@Component({
selector: 'cd-rbd-snapshot-list',
deleteSnapshotModal() {
const snapshotName = this.selection.selected[0].name;
this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
+ impact: DeletionImpact.high,
itemDescription: $localize`RBD snapshot`,
itemNames: [snapshotName],
submitAction: () => this._asyncTask('deleteSnapshot', 'rbd/snap/delete', snapshotName)
[columns]="snapshot.columns"
identifier="name"
forceIdentifier="true"
- selectionType="multiClick"
+ selectionType="single"
(updateSelection)="snapshot.updateSelection($event)">
<cd-table-actions class="table-actions"
[permission]="permission"
import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
import { FormModalComponent } from '~/app/shared/components/form-modal/form-modal.component';
import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
import { Icons } from '~/app/shared/enum/icons.enum';
import { NotificationType } from '~/app/shared/enum/notification-type.enum';
import { CdValidators } from '~/app/shared/forms/cd-validators';
name: this.actionLabels.DELETE,
icon: Icons.destroy,
permission: 'delete',
- click: () => this.deleteSnapshotModal(),
- canBePrimary: (selection) => selection.hasSelection,
- disable: (selection) => !selection.hasSelection
+ click: () => this.deleteSnapshotModal()
}
]
};
deleteSnapshotModal() {
this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
+ impact: DeletionImpact.high,
itemDescription: $localize`CephFs Snapshot`,
itemNames: this.snapshot.selection.selected.map((snapshot: CephfsSnapshot) => snapshot.name),
submitAction: () => this.deleteSnapshot()
import { map, switchMap } from 'rxjs/operators';
import { HealthService } from '~/app/shared/api/health.service';
import { CephfsAuthModalComponent } from '~/app/ceph/cephfs/cephfs-auth-modal/cephfs-auth-modal.component';
+import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
const BASE_URL = 'cephfs';
removeVolumeModal() {
const volName = this.selection.first().mdsmap['fs_name'];
this.modalService.show(CriticalConfirmationModalComponent, {
+ impact: DeletionImpact.high,
itemDescription: 'File System',
itemNames: [volName],
actionDescription: 'remove',
import { CephfsSubvolumeGroup } from '~/app/shared/models/cephfs-subvolume-group.model';
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import _ from 'lodash';
+import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
@Component({
selector: 'cd-cephfs-subvolume-group',
removeSubVolumeModal() {
const name = this.selection.first().name;
this.modalService.show(CriticalConfirmationModalComponent, {
+ impact: DeletionImpact.high,
itemDescription: 'subvolume group',
itemNames: [name],
actionDescription: 'remove',
import { CephfsMountDetailsComponent } from '../cephfs-mount-details/cephfs-mount-details.component';
import { HealthService } from '~/app/shared/api/health.service';
import _ from 'lodash';
+import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
const DEFAULT_SUBVOLUME_GROUP = '_nogroup';
this.errorMessage = '';
this.selectedName = this.selection.first().name;
this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
+ impact: DeletionImpact.high,
actionDescription: 'Remove',
itemNames: [this.selectedName],
itemDescription: 'Subvolume',
import { Validators } from '@angular/forms';
import { CdValidators } from '~/app/shared/forms/cd-validators';
import { DEFAULT_SUBVOLUME_GROUP } from '~/app/shared/constants/cephfs';
+import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
@Component({
selector: 'cd-cephfs-subvolume-snapshots-list',
const subVolumeGroupName = this.activeGroupName;
const fsName = this.fsName;
this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
- actionDescription: this.actionLabels.REMOVE,
+ impact: DeletionImpact.high,
+ actionDescription: 'remove',
itemNames: [snapshotName],
itemDescription: 'Snapshot',
submitAction: () =>
import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
import { URLBuilderService } from '~/app/shared/services/url-builder.service';
import { HostFormComponent } from './host-form/host-form.component';
+import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
const BASE_URL = 'hosts';
deleteAction() {
const hostname = this.selection.first().hostname;
this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
+ impact: DeletionImpact.high,
itemDescription: 'Host',
itemNames: [hostname],
actionDescription: 'remove',
<cd-table [data]="osds"
(fetchData)="getOsdList($event)"
[columns]="columns"
- selectionType="multiClick"
+ selectionType="single"
[hasDetails]="true"
(setExpandedRow)="setExpandedRow($event)"
(updateSelection)="updateSelection($event)"
import { OsdScrubModalComponent } from '../osd-scrub-modal/osd-scrub-modal.component';
import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
import { Osd } from '~/app/shared/models/osd.model';
+import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
const BASE_URL = 'osd';
childFormGroupTemplate?: TemplateRef<any>
): void {
check(this.getSelectedOsdIds()).subscribe((result) => {
+ const osdIds = this.getSelectedOsdIds();
const modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
+ impact: DeletionImpact.high,
+ itemNames: osdIds,
actionDescription: actionDescription,
itemDescription: itemDescription,
bodyTemplate: this.criticalConfirmationTpl,
missingStats: result.missing_stats,
storedPgs: result.stored_pgs,
actionDescription: templateItemDescription,
- osdIds: this.getSelectedOsdIds()
+ osdIds
},
childFormGroup: childFormGroup,
childFormGroupTemplate: childFormGroupTemplate,
import { URLBuilderService } from '~/app/shared/services/url-builder.service';
import { PlacementPipe } from './placement.pipe';
import { ServiceFormComponent } from './service-form/service-form.component';
+import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
const BASE_URL = 'services';
deleteAction() {
const service = this.selection.first();
this.modalService.show(CriticalConfirmationModalComponent, {
+ impact: DeletionImpact.high,
itemDescription: $localize`Service`,
itemNames: [service.service_name],
actionDescription: 'delete',
import { ModalService } from '~/app/shared/services/modal.service';
import { TaskListService } from '~/app/shared/services/task-list.service';
import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
+import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
@Component({
selector: 'cd-nfs-list',
const export_id = this.selection.first().export_id;
this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
+ impact: DeletionImpact.high,
itemDescription: $localize`NFS export`,
itemNames: [`${cluster_id}:${export_id}`],
submitActionObservable: () =>
import { URLBuilderService } from '~/app/shared/services/url-builder.service';
import { Pool } from '../pool';
import { PoolStat, PoolStats } from '../pool-stat';
+import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
const BASE_URL = 'pool';
deletePoolModal() {
const name = this.selection.first().pool_name;
this.modalService.show(CriticalConfirmationModalComponent, {
+ impact: DeletionImpact.high,
itemDescription: 'Pool',
itemNames: [name],
submitActionObservable: () =>
[data]="buckets"
[columns]="columns"
columnMode="flex"
- selectionType="multiClick"
+ selectionType="single"
[hasDetails]="true"
(setExpandedRow)="setExpandedRow($event)"
(updateSelection)="updateSelection($event)"
import { ModalService } from '~/app/shared/services/modal.service';
import { URLBuilderService } from '~/app/shared/services/url-builder.service';
import { Bucket } from '../models/rgw-bucket';
+import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
const BASE_URL = 'rgw/bucket';
permission: 'delete',
icon: Icons.destroy,
click: () => this.deleteAction(),
- disable: () => !this.selection.hasSelection,
- name: this.actionLabels.DELETE,
- canBePrimary: (selection: CdTableSelection) => selection.hasMultiSelection
+ name: this.actionLabels.DELETE
};
this.tableActions = [addAction, editAction, deleteAction];
this.setTableRefreshTimeout();
}
deleteAction() {
+ const itemNames = this.selection.selected.map((bucket: any) => bucket['bid']);
this.modalService.show(CriticalConfirmationModalComponent, {
- itemDescription: this.selection.hasSingleSelection ? $localize`bucket` : $localize`buckets`,
- itemNames: this.selection.selected.map((bucket: any) => bucket['bid']),
+ itemDescription: $localize`bucket`,
+ impact: DeletionImpact.high,
+ itemNames: itemNames,
submitActionObservable: () => {
return new Observable((observer: Subscriber<any>) => {
// Delete all selected data table rows.
[data]="users"
[columns]="columns"
columnMode="flex"
- selectionType="multiClick"
+ selectionType="single"
[hasDetails]="true"
(setExpandedRow)="setExpandedRow($event)"
(updateSelection)="updateSelection($event)"
import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
import { TableComponent } from '~/app/shared/datatable/table/table.component';
import { CellTemplate } from '~/app/shared/enum/cell-template.enum';
+import { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
import { Icons } from '~/app/shared/enum/icons.enum';
import { CdTableAction } from '~/app/shared/models/cd-table-action';
import { CdTableColumn } from '~/app/shared/models/cd-table-column';
icon: Icons.destroy,
click: () => this.deleteAction(),
disable: () => !this.selection.hasSelection,
- name: this.actionLabels.DELETE,
- canBePrimary: (selection: CdTableSelection) => selection.hasMultiSelection
+ name: this.actionLabels.DELETE
};
this.tableActions = [addAction, editAction, deleteAction];
this.setTableRefreshTimeout();
deleteAction() {
this.modalService.show(CriticalConfirmationModalComponent, {
+ impact: DeletionImpact.high,
itemDescription: this.selection.hasSingleSelection ? $localize`user` : $localize`users`,
itemNames: this.selection.selected.map((user: any) => user['uid']),
submitActionObservable: (): Observable<any> => {
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 { DeletionImpact } from '~/app/shared/enum/critical-confirmation-modal-impact.enum';
import { Icons } from '~/app/shared/enum/icons.enum';
import { NotificationType } from '~/app/shared/enum/notification-type.enum';
import { CdTableAction } from '~/app/shared/models/cd-table-action';
}
this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
+ impact: DeletionImpact.high,
itemDescription: 'User',
itemNames: [username],
submitAction: () => this.deleteUser(username)
</ng-template>
<ng-container *ngTemplateOutlet="childFormGroupTemplate; context:{form:deletionForm}"></ng-container>
<div class="form-group">
- <div class="custom-control custom-checkbox">
- <input type="checkbox"
- class="custom-control-input"
- name="confirmation"
- id="confirmation"
- formControlName="confirmation"
- autofocus>
- <label class="custom-control-label"
- for="confirmation"
- i18n>Yes, I am sure.</label>
- </div>
+ <ng-container *ngIf="impact == impactEnum.medium; else highImpactDeletion">
+ <div class="custom-control custom-checkbox">
+ <input type="checkbox"
+ class="custom-control-input"
+ name="confirmation"
+ id="confirmation"
+ formControlName="confirmation"
+ autofocus>
+ <label class="custom-control-label"
+ for="confirmation"
+ i18n>Yes, I am sure.</label>
+ </div>
+ </ng-container>
+ <ng-template #highImpactDeletion>
+ <label i18n
+ for="name">Resource Name</label>
+ <div class="cd-col-form-input">
+ <input class="form-control"
+ type="text"
+ placeholder="Enter resource name to delete"
+ id="resource_name"
+ name="resource_name"
+ formControlName="confirmInput">
+ <span *ngIf="deletionForm.showError('confirmInput', formDir, 'required')"
+ class="invalid-feedback">
+ <ng-container i18n>This field is required.</ng-container>
+ </span>
+ <span *ngIf="deletionForm.showError('confirmInput', formDir, 'matchResource')"
+ class="invalid-feedback">
+ <ng-container i18n>Enter the correct resource name.</ng-container>
+ </span>
+ </div>
+ </ng-template>
</div>
</div>
</div>
<ng-template #deletionHeading>
{{ actionDescription | titlecase }} {{ itemDescription }}
-</ng-template>
+</ng-template>
\ No newline at end of file
import { AlertPanelComponent } from '../alert-panel/alert-panel.component';
import { LoadingPanelComponent } from '../loading-panel/loading-panel.component';
import { CriticalConfirmationModalComponent } from './critical-confirmation-modal.component';
+import { DeletionImpact } from '../../enum/critical-confirmation-modal-impact.enum';
@NgModule({})
export class MockModule {}
});
describe('component functions', () => {
- const changeValue = (value: boolean) => {
- const ctrl = component.deletionForm.get('confirmation');
+ const changeValue = (formControl: string, value: any) => {
+ const ctrl = component.deletionForm.get(formControl);
ctrl.setValue(value);
ctrl.markAsDirty();
ctrl.updateValueAndValidity();
beforeEach(() => {
component.deletionForm.reset();
+ const ctrl = component.deletionForm.get('impact');
+ ctrl.setValue(DeletionImpact.normal);
+ ctrl.markAsDirty();
+ ctrl.updateValueAndValidity();
+ component.deletionForm.get('confirmation').updateValueAndValidity();
});
it('should test empty values', () => {
- component.deletionForm.reset();
testValidation(false, undefined, false);
testValidation(true, 'required', true);
+ changeValue('confirmation', true);
+ changeValue('confirmation', false);
+ testValidation(true, 'required', true);
+ });
+ });
+
+ describe('validate confirmInput', () => {
+ const testValidation = (submitted: boolean, error: string, expected: boolean) => {
+ expect(
+ component.deletionForm.showError('confirmInput', <NgForm>{ submitted: submitted }, error)
+ ).toBe(expected);
+ };
+
+ beforeEach(() => {
component.deletionForm.reset();
- changeValue(true);
- changeValue(false);
+ const ctrl = component.deletionForm.get('impact');
+ ctrl.setValue(DeletionImpact.high);
+ ctrl.markAsDirty();
+ ctrl.updateValueAndValidity();
+ component.deletionForm.get('confirmInput').updateValueAndValidity();
+ });
+
+ it('should test empty values', () => {
+ testValidation(true, 'required', true);
+ changeValue('confirmInput', 'dummytext');
+ changeValue('confirmInput', '');
testValidation(true, 'required', true);
});
+
+ it('should give error, if entered resource name is wrong', () => {
+ component.itemNames = ['resource1'];
+ changeValue('confirmInput', 'dummytext');
+ testValidation(true, 'matchResource', true);
+ });
+
+ it('should give error, if entered resource name is correct', () => {
+ component.itemNames = ['resource1'];
+ changeValue('confirmInput', 'resource1');
+ testValidation(true, 'matchResource', false);
+ });
});
describe('deletion call', () => {
import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
-import { UntypedFormControl, Validators } from '@angular/forms';
+import { AbstractControl, UntypedFormControl, ValidationErrors, Validators } from '@angular/forms';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { Observable } from 'rxjs';
import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
import { SubmitButtonComponent } from '../submit-button/submit-button.component';
+import { CdValidators } from '../../forms/cd-validators';
+import { DeletionImpact } from '../../enum/critical-confirmation-modal-impact.enum';
@Component({
selector: 'cd-deletion-modal',
itemDescription: 'entry';
itemNames: string[];
actionDescription = 'delete';
-
+ impactEnum = DeletionImpact;
childFormGroup: CdFormGroup;
childFormGroupTemplate: TemplateRef<any>;
-
- constructor(public activeModal: NgbActiveModal) {}
+ impact: DeletionImpact;
+ constructor(public activeModal: NgbActiveModal) {
+ this.impact = this.impact || DeletionImpact.normal;
+ }
ngOnInit() {
const controls = {
- confirmation: new UntypedFormControl(false, [Validators.requiredTrue])
+ impact: new UntypedFormControl(this.impact),
+ confirmation: new UntypedFormControl(false, {
+ validators: [
+ CdValidators.composeIf(
+ {
+ impact: DeletionImpact.normal
+ },
+ [Validators.requiredTrue]
+ )
+ ]
+ }),
+ confirmInput: new UntypedFormControl('', [
+ CdValidators.composeIf({ impact: this.impactEnum.high }, [
+ this.matchResourceName.bind(this),
+ Validators.required
+ ])
+ ])
};
if (this.childFormGroup) {
controls['child'] = this.childFormGroup;
}
}
+ matchResourceName(control: AbstractControl): ValidationErrors | null {
+ if (this.itemNames && control.value !== String(this.itemNames?.[0])) {
+ return { matchResource: true };
+ }
+ return null;
+ }
+
callSubmitAction() {
if (this.submitActionObservable) {
this.submitActionObservable().subscribe({
stopLoadingSpinner() {
this.deletionForm.setErrors({ cdSubmitButton: true });
}
-}
+}
\ No newline at end of file
--- /dev/null
+export enum DeletionImpact {
+ normal = 'normal',
+ high = 'high' // NOTE: User should be able to select only single resource while deleting
+}