Compare commits

...

1 Commits

Author SHA1 Message Date
Marcel Haag d42f5efdc2 As a use I want to have a saver delete dialog 2023-01-03 10:55:26 +01:00
22 changed files with 389 additions and 35 deletions

View File

@ -114,7 +114,7 @@ export class PentestCommentsComponent implements OnInit {
{
closeOnEsc: false,
hasScroll: false,
autoFocus: false,
autoFocus: true,
closeOnBackdropClick: false
}
).pipe(
@ -154,7 +154,7 @@ export class PentestCommentsComponent implements OnInit {
{
closeOnEsc: false,
hasScroll: false,
autoFocus: false,
autoFocus: true,
closeOnBackdropClick: false
}
).pipe(

View File

@ -109,7 +109,7 @@ export class PentestFindingsComponent implements OnInit {
{
closeOnEsc: false,
hasScroll: false,
autoFocus: false,
autoFocus: true,
closeOnBackdropClick: false
}
).pipe(
@ -148,7 +148,7 @@ export class PentestFindingsComponent implements OnInit {
{
closeOnEsc: false,
hasScroll: false,
autoFocus: false,
autoFocus: true,
closeOnBackdropClick: false
}
).pipe(

View File

@ -60,7 +60,7 @@ export class ProjectOverviewComponent implements OnInit {
{
closeOnEsc: false,
hasScroll: false,
autoFocus: false,
autoFocus: true,
closeOnBackdropClick: false
}
).pipe(
@ -86,7 +86,7 @@ export class ProjectOverviewComponent implements OnInit {
{
closeOnEsc: false,
hasScroll: false,
autoFocus: false,
autoFocus: true,
closeOnBackdropClick: false
}
).pipe(
@ -106,29 +106,61 @@ export class ProjectOverviewComponent implements OnInit {
}
onClickDeleteProject(project: Project): void {
// Set dialog message
const message = {
title: 'project.delete.title',
key: 'project.delete.key',
data: {name: project.title},
};
this.dialogService.openConfirmDialog(
message
).onClose.pipe(
filter((confirm) => !!confirm),
switchMap(() => this.projectService.deleteProjectById(project.id)),
catchError(() => {
this.notificationService.showPopup('project.popup.delete.failed', PopupType.FAILURE);
return [];
}),
untilDestroyed(this)
).subscribe({
next: () => {
this.loadProjects();
this.notificationService.showPopup('project.popup.delete.success', PopupType.SUCCESS);
}, error: error => {
console.error(error);
}
});
} as any;
// Check if project is empty
if (project.testingProgress === 0) {
this.dialogService.openConfirmDialog(
message
).onClose.pipe(
filter((confirm) => !!confirm),
switchMap(() => this.projectService.deleteProjectById(project.id)),
catchError(() => {
this.notificationService.showPopup('project.popup.delete.failed', PopupType.FAILURE);
return [];
}),
untilDestroyed(this)
).subscribe({
next: () => {
this.loadProjects();
this.notificationService.showPopup('project.popup.delete.success', PopupType.SUCCESS);
}, error: error => {
console.error(error);
}
});
} else {
const secMessage = {
title: 'project.delete.title',
key: 'project.delete.sec.key',
confirmString: project.title.toString(),
inputPlaceholderKey: 'project.delete.confirmStringPlaceholder',
data: {name: project.title, confirmString: project.title.toString()},
} as any;
// Set confirm string
// message.data.confirmString = project.title;
this.dialogService.openSecurityConfirmDialog(
secMessage
).onClose.pipe(
filter((confirm) => !!confirm),
switchMap(() => this.projectService.deleteProjectById(project.id)),
catchError(() => {
this.notificationService.showPopup('project.popup.delete.failed', PopupType.FAILURE);
return [];
}),
untilDestroyed(this)
).subscribe({
next: () => {
this.loadProjects();
this.notificationService.showPopup('project.popup.delete.success', PopupType.SUCCESS);
}, error: error => {
console.error(error);
}
});
}
}
// HTML only

View File

@ -10,6 +10,7 @@ import {DateTimeFormatPipe} from '@shared/pipes/date-time-format.pipe';
import {ProjectDialogModule} from '@shared/modules/project-dialog/project-dialog.module';
import {CommonAppModule} from '../common-app.module';
import {ConfirmDialogModule} from '@shared/modules/confirm-dialog/confirm-dialog.module';
import {SecurityConfirmDialogModule} from '@shared/modules/security-confirm-dialog/security-confirm-dialog.module';
@NgModule({
declarations: [
@ -26,8 +27,9 @@ import {ConfirmDialogModule} from '@shared/modules/confirm-dialog/confirm-dialog
FlexLayoutModule,
FontAwesomeModule,
TranslateModule,
ProjectDialogModule,
ConfirmDialogModule,
ProjectDialogModule
SecurityConfirmDialogModule
]
})
export class ProjectOverviewModule {

View File

@ -9,6 +9,7 @@
.dialog-body {
font-size: 1rem;
white-space: pre-line;
}
.dialog-button {

View File

@ -15,7 +15,10 @@
"action.no": "Nein",
"username": "Nutzername",
"password": "Passwort",
"no.progress": "Kein Fortschritt"
"no.progress": "Kein Fortschritt",
"validationMessage": {
"inputNotMatching": "Eingabe stimmt nicht überein!"
}
},
"languageKeys":{
"de-DE": "Deutsch",
@ -59,7 +62,9 @@
},
"delete": {
"title": "Projekt löschen",
"key": "Möchten Sie das Projekt \"{{name}}\" unwiderruflich löschen?"
"key": "Möchten Sie das Projekt \"{{name}}\" unwiderruflich löschen?",
"confirmStringPlaceholder": "Geben Sie zur Bestätigung den Projekttitel ein",
"sec.key": "Möchten Sie das Projekt \"{{name}}\" unwiderruflich löschen? \nSie löschen damit auch alle zugehörigen Daten."
},
"validationMessage": {
"titleRequired": "Titel ist erforderlich.",

View File

@ -15,7 +15,10 @@
"action.no": "No",
"username": "Username",
"password": "Password",
"no.progress": "No progress"
"no.progress": "No progress",
"validationMessage": {
"inputNotMatching": "Input does not match!"
}
},
"languageKeys":{
"de-DE": "German",
@ -59,7 +62,9 @@
},
"delete": {
"title": "Delete Project",
"key": "Do you want to permanently delete the project \"{{name}}\"?"
"key": "Do you want to permanently delete the project \"{{name}}\"?",
"confirmStringPlaceholder": "Enter project title to confirm",
"sec.key": "Do you want to permanently delete the project \"{{name}}\"? \nAll related data will also be deleted."
},
"validationMessage": {
"titleRequired": "Title is required.",

View File

@ -0,0 +1 @@
<p>confirm-checkbox-dialog works!</p>

View File

@ -0,0 +1,4 @@
@import "../../../assets/@theme/styles/_dialog.scss";
* {
}

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ConfirmCheckboxDialogComponent } from './confirm-checkbox-dialog.component';
describe('ConfirmCheckboxDialogComponent', () => {
let component: ConfirmCheckboxDialogComponent;
let fixture: ComponentFixture<ConfirmCheckboxDialogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ConfirmCheckboxDialogComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ConfirmCheckboxDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-confirm-checkbox-dialog',
templateUrl: './confirm-checkbox-dialog.component.html',
styleUrls: ['./confirm-checkbox-dialog.component.scss']
})
export class ConfirmCheckboxDialogComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,12 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
declarations: [],
imports: [
CommonModule
]
})
export class ConfirmCheckboxDialogModule { }

View File

@ -27,6 +27,6 @@
.error-text {
float: left;
color: nb-theme(color-danger-default);;
color: nb-theme(color-danger-default);
}
}

View File

@ -0,0 +1,7 @@
import {AbstractControl, ValidatorFn} from '@angular/forms';
export function acceptableInputValidator(confirmString: string): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } | null =>
control.value === confirmString
? null : {wrongConfirmString: control.value};
}

View File

@ -0,0 +1,41 @@
<nb-card #dialog>
<nb-card-header fxLayoutAlign="start center" class="dialog-header confirm">
{{ data?.title | translate }}
</nb-card-header>
<nb-card-body class="dialog-body">
{{ data?.key | translate: data?.data }}
<form id="inputForm">
<nb-form-field>
<input type="text" required
fullWidth nbInput
class="form-field"
cdkFocusInitial
fieldSize="medium"
[formControl]="inputCtrl"
[status]="inputCtrl.dirty ? (inputCtrl.invalid ? 'danger' : 'basic') : 'basic'"
placeholder="{{ data?.inputPlaceholderKey | translate }}">
<div *ngIf="inputCtrl.hasError('wrongConfirmString')">
<span class="error-text">
{{ 'global.validationMessage.inputNotMatching' | translate }}
</span>
</div>
</nb-form-field>
</form>
</nb-card-body>
<nb-card-footer fxLayout="row" fxLayoutGap="1.5rem" fxLayoutAlign="end end">
<button nbButton size="small"
class="dialog-button"
status="danger"
type="submit"
[disabled]="inputCtrl.invalid"
(click)="onClickConfirm()">
{{ 'global.action.yes' | translate }}
</button>
<button nbButton size="small"
class="dialog-button"
type="button"
(click)="onClickClose()">
{{ 'global.action.no' | translate }}
</button>
</nb-card-footer>
</nb-card>

View File

@ -0,0 +1,17 @@
@import "../../../assets/@theme/styles/_dialog.scss";
@import '../../../assets/@theme/styles/themes';
* {
}
#inputForm {
padding-top: 1rem;
.form-field{
margin-bottom: 0.5rem;
}
.error-text {
color: nb-theme(color-danger-default);
}
}

View File

@ -0,0 +1,69 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {SecurityConfirmDialogComponent} from './security-confirm-dialog.component';
import {CommonModule} from '@angular/common';
import {NB_DIALOG_CONFIG, NbButtonModule, NbCardModule, NbDialogRef, NbLayoutModule, NbStatusService} from '@nebular/theme';
import {FlexLayoutModule} from '@angular/flex-layout';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {HttpLoaderFactory} from '../../../app/common-app.module';
import {HttpClient, HttpClientModule} from '@angular/common/http';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {MockProvider} from 'ng-mocks';
import {DialogService} from '@shared/services/dialog-service/dialog.service';
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
import {createSpyObj} from '@shared/modules/project-dialog/project-dialog.component.spec';
import {SecurityDialogMessage} from '@shared/services/dialog-service/dialog-message';
describe('SecurityConfirmDialogComponent', () => {
let component: SecurityConfirmDialogComponent;
let fixture: ComponentFixture<SecurityConfirmDialogComponent>;
class DummyMockObj {
public static data = {
key: 'test.key',
confirmString: 'test',
} as SecurityDialogMessage;
}
beforeEach(async () => {
const dialogSpy = createSpyObj('NbDialogRef', ['close']);
await TestBed.configureTestingModule({
declarations: [
SecurityConfirmDialogComponent
],
imports: [
CommonModule,
NbLayoutModule,
NbCardModule,
NbButtonModule,
FlexLayoutModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
}),
HttpClientModule,
HttpClientTestingModule
],
providers: [
MockProvider(NbStatusService),
{provide: DialogService, useClass: DialogServiceMock},
{provide: NbDialogRef, useValue: dialogSpy},
{provide: NB_DIALOG_CONFIG, useValue: { data: DummyMockObj.data } }
]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(SecurityConfirmDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,41 @@
import {Component, Inject, Input, OnInit} from '@angular/core';
import {NB_DIALOG_CONFIG, NbDialogRef} from '@nebular/theme';
import {FormControl} from '@angular/forms';
import {acceptableInputValidator} from '@shared/modules/security-confirm-dialog/acceptableInputValidator';
import {UntilDestroy} from '@ngneat/until-destroy';
import {SecurityDialogMessage} from '@shared/services/dialog-service/dialog-message';
@Component({
selector: 'app-security-confirm-dialog',
templateUrl: './security-confirm-dialog.component.html',
styleUrls: ['./security-confirm-dialog.component.scss']
})
@UntilDestroy()
export class SecurityConfirmDialogComponent implements OnInit{
inputCtrl: FormControl;
constructor(/**
* @param data contains all relevant information the dialog needs
* @param data.title The translation key for the dialog title
* @param data.confirmString The string to confirm the dialog action
* @param data.key The translation key for the shown message
* @param data.data The data that may be used in the message translation key
*/
@Inject(NB_DIALOG_CONFIG) public data: SecurityDialogMessage,
protected dialogRef: NbDialogRef<any>) {
}
ngOnInit(): void {
// Setup FormControl and custom validator
this.inputCtrl = new FormControl('', acceptableInputValidator(this.data.confirmString));
}
onClickConfirm(): void {
this.dialogRef.close({confirm: true});
}
onClickClose(): void {
this.dialogRef.close();
}
}

View File

@ -0,0 +1,31 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {CommonAppModule} from '../../../app/common-app.module';
import {NbButtonModule, NbCardModule, NbFormFieldModule, NbInputModule, NbLayoutModule, NbSelectModule} from '@nebular/theme';
import {FlexLayoutModule} from '@angular/flex-layout';
import {TranslateModule} from '@ngx-translate/core';
import {SecurityConfirmDialogComponent} from '@shared/modules/security-confirm-dialog/security-confirm-dialog.component';
import {ReactiveFormsModule} from '@angular/forms';
@NgModule({
declarations: [
SecurityConfirmDialogComponent
],
imports: [
CommonModule,
CommonAppModule,
NbCardModule,
NbButtonModule,
FlexLayoutModule,
TranslateModule,
NbLayoutModule,
NbSelectModule,
NbInputModule,
NbFormFieldModule,
ReactiveFormsModule
],
entryComponents: [
SecurityConfirmDialogComponent
]
})
export class SecurityConfirmDialogModule { }

View File

@ -4,3 +4,11 @@ export interface DialogMessage {
title?: string;
inputPlaceholderKey?: string;
}
export interface SecurityDialogMessage {
key: string;
confirmString: string;
data?: any;
title?: string;
inputPlaceholderKey?: string;
}

View File

@ -2,7 +2,8 @@ import {DialogService} from '@shared/services/dialog-service/dialog.service';
import {ComponentType} from '@angular/cdk/overlay';
import {TemplateRef} from '@angular/core';
import {NbDialogConfig, NbDialogRef} from '@nebular/theme';
import {DialogMessage} from '@shared/services/dialog-service/dialog-message';
import {DialogMessage, SecurityDialogMessage} from '@shared/services/dialog-service/dialog-message';
import {SecurityConfirmDialogComponent} from '@shared/modules/security-confirm-dialog/security-confirm-dialog.component';
export class DialogServiceMock implements Required<DialogService> {
@ -18,4 +19,8 @@ export class DialogServiceMock implements Required<DialogService> {
openConfirmDialog(message: DialogMessage): NbDialogRef<any> {
return null;
}
openSecurityConfirmDialog(message: SecurityDialogMessage): NbDialogRef<SecurityConfirmDialogComponent> {
return undefined;
}
}

View File

@ -1,8 +1,9 @@
import {Injectable, TemplateRef} from '@angular/core';
import {NbDialogConfig, NbDialogRef, NbDialogService} from '@nebular/theme';
import {ComponentType} from '@angular/cdk/overlay';
import {DialogMessage} from '@shared/services/dialog-service/dialog-message';
import {DialogMessage, SecurityDialogMessage} from '@shared/services/dialog-service/dialog-message';
import {ConfirmDialogComponent} from '@shared/modules/confirm-dialog/confirm-dialog.component';
import {SecurityConfirmDialogComponent} from '@shared/modules/security-confirm-dialog/security-confirm-dialog.component';
@Injectable({
providedIn: 'root'
@ -23,7 +24,7 @@ export class DialogService {
context: config?.context || undefined,
closeOnEsc: config?.closeOnEsc || false,
hasScroll: config?.hasScroll || false,
autoFocus: config?.autoFocus || false,
autoFocus: config?.autoFocus || true,
closeOnBackdropClick: config?.closeOnBackdropClick || false
});
}
@ -37,7 +38,39 @@ export class DialogService {
return this.dialog.open(ConfirmDialogComponent, {
closeOnEsc: true,
hasScroll: false,
autoFocus: false,
autoFocus: true,
closeOnBackdropClick: false,
context: {data: message}
});
}
/**
* @param message.key The translation key for the shown message
* @param message.data The data that may be used in the message translation key (Set it null if it's not required in the key)
* @param message.title The translation key for the dialog title
*/
// ToDo: Implement later on..
/*openConfirmCheckBoxDialog(message: DialogMessage): NbDialogRef<any> {
return this.dialog.open(undefined, {
closeOnEsc: true,
hasScroll: false,
autoFocus: true,
closeOnBackdropClick: false,
context: {data: message}
});
}*/
/**
* @param message.key The translation key for the shown message
* @param message.confirmString The string to be entered for confirmation
* @param message.data The data that may be used in the message translation key (Set it null if it's not required in the key)
* @param message.title The translation key for the dialog title
*/
openSecurityConfirmDialog(message: SecurityDialogMessage): NbDialogRef<SecurityConfirmDialogComponent> {
return this.dialog.open(SecurityConfirmDialogComponent, {
closeOnEsc: false,
hasScroll: false,
autoFocus: true,
closeOnBackdropClick: false,
context: {data: message}
});