feat: As an user I want a retry dialog guard, in order to resend a failed request
This commit is contained in:
parent
9fddff7740
commit
2c7ac85f6e
|
@ -36,6 +36,7 @@ import {ProjectState} from '@shared/stores/project-state/project-state';
|
||||||
import {CustomOverlayContainer} from '@shared/modules/custom-overlay-container.component';
|
import {CustomOverlayContainer} from '@shared/modules/custom-overlay-container.component';
|
||||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||||
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||||
|
import {RetryDialogModule} from '@shared/modules/retry-dialog/retry-dialog.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@ -72,6 +73,7 @@ import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||||
}),
|
}),
|
||||||
HeaderModule,
|
HeaderModule,
|
||||||
HomeModule,
|
HomeModule,
|
||||||
|
RetryDialogModule
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
HttpClient,
|
HttpClient,
|
||||||
|
|
|
@ -5,13 +5,11 @@ import * as FA from '@fortawesome/free-solid-svg-icons';
|
||||||
import {NbGetters, NbTreeGridDataSource, NbTreeGridDataSourceBuilder} from '@nebular/theme';
|
import {NbGetters, NbTreeGridDataSource, NbTreeGridDataSourceBuilder} from '@nebular/theme';
|
||||||
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service';
|
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service';
|
||||||
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||||
import {catchError, filter, mergeMap, switchMap, tap} from 'rxjs/operators';
|
import {filter, tap} from 'rxjs/operators';
|
||||||
import {
|
import {
|
||||||
Comment,
|
Comment,
|
||||||
CommentDialogBody,
|
|
||||||
CommentEntry,
|
CommentEntry,
|
||||||
transformCommentsToObjectiveEntries,
|
transformCommentsToObjectiveEntries
|
||||||
transformCommentToRequestBody
|
|
||||||
} from '@shared/models/comment.model';
|
} from '@shared/models/comment.model';
|
||||||
import {isNotNullOrUndefined} from 'codelyzer/util/isNotNullOrUndefined';
|
import {isNotNullOrUndefined} from 'codelyzer/util/isNotNullOrUndefined';
|
||||||
import {ProjectState} from '@shared/stores/project-state/project-state';
|
import {ProjectState} from '@shared/stores/project-state/project-state';
|
||||||
|
@ -115,25 +113,13 @@ export class PentestCommentsComponent implements OnInit {
|
||||||
hasScroll: false,
|
hasScroll: false,
|
||||||
autoFocus: true,
|
autoFocus: true,
|
||||||
closeOnBackdropClick: false
|
closeOnBackdropClick: false
|
||||||
}
|
},
|
||||||
|
this.pentestInfo$.getValue()
|
||||||
).pipe(
|
).pipe(
|
||||||
filter(value => !!value),
|
|
||||||
mergeMap((value: CommentDialogBody) =>
|
|
||||||
this.commentService.saveComment(
|
|
||||||
this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '',
|
|
||||||
transformCommentToRequestBody(value)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
untilDestroyed(this)
|
untilDestroyed(this)
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: (newComment: Comment) => {
|
next: (newComment: Comment) => {
|
||||||
this.store.dispatch(new UpdatePentestComments(newComment.id));
|
|
||||||
this.loadCommentsData();
|
this.loadCommentsData();
|
||||||
this.notificationService.showPopup('comment.popup.save.success', PopupType.SUCCESS);
|
|
||||||
},
|
|
||||||
error: err => {
|
|
||||||
console.error(err);
|
|
||||||
this.notificationService.showPopup('comment.popup.save.failed', PopupType.FAILURE);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -154,24 +140,13 @@ export class PentestCommentsComponent implements OnInit {
|
||||||
hasScroll: false,
|
hasScroll: false,
|
||||||
autoFocus: true,
|
autoFocus: true,
|
||||||
closeOnBackdropClick: false
|
closeOnBackdropClick: false
|
||||||
}
|
},
|
||||||
|
this.pentestInfo$.getValue()
|
||||||
).pipe(
|
).pipe(
|
||||||
filter(value => !!value),
|
|
||||||
mergeMap((value: CommentDialogBody) =>
|
|
||||||
this.commentService.updateComment(
|
|
||||||
commentEntry.data.commentId,
|
|
||||||
transformCommentToRequestBody(value)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
untilDestroyed(this)
|
untilDestroyed(this)
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: (updatedComment: Comment) => {
|
next: (updatedComment: Comment) => {
|
||||||
this.loadCommentsData();
|
this.loadCommentsData();
|
||||||
this.notificationService.showPopup('comment.popup.update.success', PopupType.SUCCESS);
|
|
||||||
},
|
|
||||||
error: err => {
|
|
||||||
console.error(err);
|
|
||||||
this.notificationService.showPopup('comment.popup.update.failed', PopupType.FAILURE);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -206,23 +181,10 @@ export class PentestCommentsComponent implements OnInit {
|
||||||
this.dialogService.openConfirmDialog(
|
this.dialogService.openConfirmDialog(
|
||||||
message
|
message
|
||||||
).onClose.pipe(
|
).onClose.pipe(
|
||||||
filter((confirm) => !!confirm),
|
|
||||||
switchMap(() => this.commentService.deleteCommentByPentestAndCommentId(
|
|
||||||
this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '',
|
|
||||||
commentEntry.data.commentId)
|
|
||||||
),
|
|
||||||
catchError(() => {
|
|
||||||
this.notificationService.showPopup('comment.popup.delete.failed', PopupType.FAILURE);
|
|
||||||
return [];
|
|
||||||
}),
|
|
||||||
untilDestroyed(this)
|
untilDestroyed(this)
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: (deletedComment: any) => {
|
next: () => {
|
||||||
this.store.dispatch(new UpdatePentestComments(deletedComment.id));
|
this.deleteComment(commentEntry);
|
||||||
this.loadCommentsData();
|
|
||||||
this.notificationService.showPopup('comment.popup.delete.success', PopupType.SUCCESS);
|
|
||||||
}, error: error => {
|
|
||||||
console.error(error);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -231,6 +193,37 @@ export class PentestCommentsComponent implements OnInit {
|
||||||
isLoading(): Observable<boolean> {
|
isLoading(): Observable<boolean> {
|
||||||
return this.loading$.asObservable();
|
return this.loading$.asObservable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private deleteComment(commentEntry): void {
|
||||||
|
this.commentService.deleteCommentByPentestAndCommentId(
|
||||||
|
this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '',
|
||||||
|
commentEntry.data.commentId)
|
||||||
|
.pipe(
|
||||||
|
untilDestroyed(this)
|
||||||
|
).subscribe({
|
||||||
|
next: (deletedComment: any) => {
|
||||||
|
this.store.dispatch(new UpdatePentestComments(deletedComment.id));
|
||||||
|
this.loadCommentsData();
|
||||||
|
this.notificationService.showPopup('comment.popup.delete.success', PopupType.SUCCESS);
|
||||||
|
}, error: error => {
|
||||||
|
console.error(error);
|
||||||
|
this.onRequestFailed(commentEntry);
|
||||||
|
this.notificationService.showPopup('comment.popup.delete.failed', PopupType.FAILURE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private onRequestFailed(retryParameter: any): void {
|
||||||
|
this.dialogService.openRetryDialog({key: 'global.retry.dialog', data: null}).onClose
|
||||||
|
.pipe(
|
||||||
|
untilDestroyed(this)
|
||||||
|
)
|
||||||
|
.subscribe((ref) => {
|
||||||
|
if (ref.retry) {
|
||||||
|
this.deleteComment(retryParameter);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CommentColumns {
|
enum CommentColumns {
|
||||||
|
|
|
@ -2,14 +2,12 @@ import {Component, OnInit} from '@angular/core';
|
||||||
import {BehaviorSubject, Observable} from 'rxjs';
|
import {BehaviorSubject, Observable} from 'rxjs';
|
||||||
import {Pentest} from '@shared/models/pentest.model';
|
import {Pentest} from '@shared/models/pentest.model';
|
||||||
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||||
import {catchError, filter, mergeMap, switchMap, tap} from 'rxjs/operators';
|
import { filter, tap} from 'rxjs/operators';
|
||||||
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service';
|
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service';
|
||||||
import {
|
import {
|
||||||
Finding,
|
Finding,
|
||||||
FindingDialogBody,
|
|
||||||
FindingEntry,
|
FindingEntry,
|
||||||
transformFindingsToObjectiveEntries,
|
transformFindingsToObjectiveEntries,
|
||||||
transformFindingToRequestBody,
|
|
||||||
} from '@shared/models/finding.model';
|
} from '@shared/models/finding.model';
|
||||||
import {NbGetters, NbTreeGridDataSource, NbTreeGridDataSourceBuilder} from '@nebular/theme';
|
import {NbGetters, NbTreeGridDataSource, NbTreeGridDataSourceBuilder} from '@nebular/theme';
|
||||||
import * as FA from '@fortawesome/free-solid-svg-icons';
|
import * as FA from '@fortawesome/free-solid-svg-icons';
|
||||||
|
@ -31,9 +29,9 @@ import {FindingService} from '@shared/services/api/finding.service';
|
||||||
})
|
})
|
||||||
export class PentestFindingsComponent implements OnInit {
|
export class PentestFindingsComponent implements OnInit {
|
||||||
|
|
||||||
constructor(private readonly findingService: FindingService,
|
constructor(private findingService: FindingService,
|
||||||
private dataSourceBuilder: NbTreeGridDataSourceBuilder<FindingEntry>,
|
private dataSourceBuilder: NbTreeGridDataSourceBuilder<FindingEntry>,
|
||||||
private notificationService: NotificationService,
|
private readonly notificationService: NotificationService,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
private findingDialogService: FindingDialogService,
|
private findingDialogService: FindingDialogService,
|
||||||
private store: Store) {
|
private store: Store) {
|
||||||
|
@ -111,26 +109,13 @@ export class PentestFindingsComponent implements OnInit {
|
||||||
hasScroll: false,
|
hasScroll: false,
|
||||||
autoFocus: true,
|
autoFocus: true,
|
||||||
closeOnBackdropClick: false
|
closeOnBackdropClick: false
|
||||||
}
|
},
|
||||||
|
this.pentestInfo$.getValue()
|
||||||
).pipe(
|
).pipe(
|
||||||
filter(value => !!value),
|
|
||||||
/*tap((value) => console.warn('FindingDialogBody: ', value)),*/
|
|
||||||
mergeMap((value: FindingDialogBody) =>
|
|
||||||
this.findingService.saveFinding(
|
|
||||||
this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '',
|
|
||||||
transformFindingToRequestBody(value)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
untilDestroyed(this)
|
untilDestroyed(this)
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: (newFinding: Finding) => {
|
next: (newFinding: Finding) => {
|
||||||
this.store.dispatch(new UpdatePentestFindings(newFinding.id));
|
|
||||||
this.loadFindingsData();
|
this.loadFindingsData();
|
||||||
this.notificationService.showPopup('finding.popup.save.success', PopupType.SUCCESS);
|
|
||||||
},
|
|
||||||
error: err => {
|
|
||||||
console.error(err);
|
|
||||||
this.notificationService.showPopup('finding.popup.save.failed', PopupType.FAILURE);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -150,25 +135,13 @@ export class PentestFindingsComponent implements OnInit {
|
||||||
hasScroll: false,
|
hasScroll: false,
|
||||||
autoFocus: true,
|
autoFocus: true,
|
||||||
closeOnBackdropClick: false
|
closeOnBackdropClick: false
|
||||||
}
|
},
|
||||||
|
this.pentestInfo$.getValue()
|
||||||
).pipe(
|
).pipe(
|
||||||
filter(value => !!value),
|
|
||||||
/*tap((value) => console.warn('FindingDialogBody: ', value)),*/
|
|
||||||
mergeMap((value: FindingDialogBody) =>
|
|
||||||
this.findingService.updateFinding(
|
|
||||||
findingEntry.data.findingId,
|
|
||||||
transformFindingToRequestBody(value)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
untilDestroyed(this)
|
untilDestroyed(this)
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: (updatedFinding: Finding) => {
|
next: (updatedFinding: Finding) => {
|
||||||
this.loadFindingsData();
|
this.loadFindingsData();
|
||||||
this.notificationService.showPopup('finding.popup.update.success', PopupType.SUCCESS);
|
|
||||||
},
|
|
||||||
error: err => {
|
|
||||||
console.error(err);
|
|
||||||
this.notificationService.showPopup('finding.popup.update.failed', PopupType.FAILURE);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -190,23 +163,10 @@ export class PentestFindingsComponent implements OnInit {
|
||||||
this.dialogService.openConfirmDialog(
|
this.dialogService.openConfirmDialog(
|
||||||
message
|
message
|
||||||
).onClose.pipe(
|
).onClose.pipe(
|
||||||
filter((confirm) => !!confirm),
|
|
||||||
switchMap(() => this.findingService.deleteFindingByPentestAndFindingId(
|
|
||||||
this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '',
|
|
||||||
findingEntry.data.findingId)
|
|
||||||
),
|
|
||||||
catchError(() => {
|
|
||||||
this.notificationService.showPopup('finding.popup.delete.failed', PopupType.FAILURE);
|
|
||||||
return [];
|
|
||||||
}),
|
|
||||||
untilDestroyed(this)
|
untilDestroyed(this)
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: (deletedFinding: any) => {
|
next: () => {
|
||||||
this.store.dispatch(new UpdatePentestFindings(deletedFinding.id));
|
this.deleteFinding(findingEntry);
|
||||||
this.loadFindingsData();
|
|
||||||
this.notificationService.showPopup('finding.popup.delete.success', PopupType.SUCCESS);
|
|
||||||
}, error: error => {
|
|
||||||
console.error(error);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -214,6 +174,37 @@ export class PentestFindingsComponent implements OnInit {
|
||||||
isLoading(): Observable<boolean> {
|
isLoading(): Observable<boolean> {
|
||||||
return this.loading$.asObservable();
|
return this.loading$.asObservable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private deleteFinding(findingEntry): void {
|
||||||
|
this.findingService.deleteFindingByPentestAndFindingId(
|
||||||
|
this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '',
|
||||||
|
findingEntry.data.findingId)
|
||||||
|
.pipe(
|
||||||
|
untilDestroyed(this)
|
||||||
|
).subscribe({
|
||||||
|
next: (deletedFinding: any) => {
|
||||||
|
this.store.dispatch(new UpdatePentestFindings(deletedFinding.id));
|
||||||
|
this.loadFindingsData();
|
||||||
|
this.notificationService.showPopup('finding.popup.delete.success', PopupType.SUCCESS);
|
||||||
|
}, error: error => {
|
||||||
|
console.error(error);
|
||||||
|
this.onRequestFailed(findingEntry);
|
||||||
|
this.notificationService.showPopup('finding.popup.delete.failed', PopupType.FAILURE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private onRequestFailed(retryParameter: any): void {
|
||||||
|
this.dialogService.openRetryDialog({key: 'global.retry.dialog', data: null}).onClose
|
||||||
|
.pipe(
|
||||||
|
untilDestroyed(this)
|
||||||
|
)
|
||||||
|
.subscribe((ref) => {
|
||||||
|
if (ref.retry) {
|
||||||
|
this.deleteFinding(retryParameter);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum FindingColumns {
|
enum FindingColumns {
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import * as FA from '@fortawesome/free-solid-svg-icons';
|
import * as FA from '@fortawesome/free-solid-svg-icons';
|
||||||
import {Project, ProjectDialogBody} from '@shared/models/project.model';
|
import {Project} from '@shared/models/project.model';
|
||||||
import {BehaviorSubject, Observable} from 'rxjs';
|
import {BehaviorSubject, Observable} from 'rxjs';
|
||||||
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||||
import {ProjectService} from '@shared/services/api/project.service';
|
import {ProjectService} from '@shared/services/api/project.service';
|
||||||
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service';
|
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service';
|
||||||
import {catchError, filter, mergeMap, switchMap, tap} from 'rxjs/operators';
|
import {filter, tap} from 'rxjs/operators';
|
||||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||||
import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
|
import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
|
||||||
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
|
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
|
||||||
|
@ -70,17 +70,10 @@ export class ProjectOverviewComponent implements OnInit {
|
||||||
closeOnBackdropClick: false
|
closeOnBackdropClick: false
|
||||||
}
|
}
|
||||||
).pipe(
|
).pipe(
|
||||||
filter(value => !!value),
|
|
||||||
mergeMap((value: ProjectDialogBody) => this.projectService.saveProject(value)),
|
|
||||||
untilDestroyed(this)
|
untilDestroyed(this)
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.loadProjects();
|
this.loadProjects();
|
||||||
this.notificationService.showPopup('project.popup.save.success', PopupType.SUCCESS);
|
|
||||||
},
|
|
||||||
error: err => {
|
|
||||||
console.error(err);
|
|
||||||
this.notificationService.showPopup('project.popup.save.failed', PopupType.FAILURE);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -96,17 +89,10 @@ export class ProjectOverviewComponent implements OnInit {
|
||||||
closeOnBackdropClick: false
|
closeOnBackdropClick: false
|
||||||
}
|
}
|
||||||
).pipe(
|
).pipe(
|
||||||
filter(value => !!value),
|
|
||||||
mergeMap((value: ProjectDialogBody) => this.projectService.updateProject(project.id, value)),
|
|
||||||
untilDestroyed(this)
|
untilDestroyed(this)
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.loadProjects();
|
this.loadProjects();
|
||||||
this.notificationService.showPopup('project.popup.update.success', PopupType.SUCCESS);
|
|
||||||
},
|
|
||||||
error: error => {
|
|
||||||
console.error(error);
|
|
||||||
this.notificationService.showPopup('project.popup.update.failed', PopupType.FAILURE);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -124,18 +110,10 @@ export class ProjectOverviewComponent implements OnInit {
|
||||||
message
|
message
|
||||||
).onClose.pipe(
|
).onClose.pipe(
|
||||||
filter((confirm) => !!confirm),
|
filter((confirm) => !!confirm),
|
||||||
switchMap(() => this.projectService.deleteProjectById(project.id)),
|
|
||||||
catchError(() => {
|
|
||||||
this.notificationService.showPopup('project.popup.delete.failed', PopupType.FAILURE);
|
|
||||||
return [];
|
|
||||||
}),
|
|
||||||
untilDestroyed(this)
|
untilDestroyed(this)
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.loadProjects();
|
this.deleteProject(project);
|
||||||
this.notificationService.showPopup('project.popup.delete.success', PopupType.SUCCESS);
|
|
||||||
}, error: error => {
|
|
||||||
console.error(error);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -152,18 +130,10 @@ export class ProjectOverviewComponent implements OnInit {
|
||||||
secMessage
|
secMessage
|
||||||
).onClose.pipe(
|
).onClose.pipe(
|
||||||
filter((confirm) => !!confirm),
|
filter((confirm) => !!confirm),
|
||||||
switchMap(() => this.projectService.deleteProjectById(project.id)),
|
|
||||||
catchError(() => {
|
|
||||||
this.notificationService.showPopup('project.popup.delete.failed', PopupType.FAILURE);
|
|
||||||
return [];
|
|
||||||
}),
|
|
||||||
untilDestroyed(this)
|
untilDestroyed(this)
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.loadProjects();
|
this.deleteProject(project);
|
||||||
this.notificationService.showPopup('project.popup.delete.success', PopupType.SUCCESS);
|
|
||||||
}, error: error => {
|
|
||||||
console.error(error);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -185,4 +155,31 @@ export class ProjectOverviewComponent implements OnInit {
|
||||||
isLoading(): Observable<boolean> {
|
isLoading(): Observable<boolean> {
|
||||||
return this.loading$.asObservable();
|
return this.loading$.asObservable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private deleteProject(project: Project): void {
|
||||||
|
this.projectService.deleteProjectById(project.id).pipe(
|
||||||
|
untilDestroyed(this)
|
||||||
|
).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.loadProjects();
|
||||||
|
this.notificationService.showPopup('project.popup.delete.success', PopupType.SUCCESS);
|
||||||
|
}, error: error => {
|
||||||
|
this.notificationService.showPopup('project.popup.delete.failed', PopupType.FAILURE);
|
||||||
|
this.onRequestFailed(project);
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private onRequestFailed(retryParameter: any): void {
|
||||||
|
this.dialogService.openRetryDialog({key: 'global.retry.dialog', data: null}).onClose
|
||||||
|
.pipe(
|
||||||
|
untilDestroyed(this)
|
||||||
|
)
|
||||||
|
.subscribe((ref) => {
|
||||||
|
if (ref.retry) {
|
||||||
|
this.deleteProject(retryParameter);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,14 @@
|
||||||
"username": "Nutzername",
|
"username": "Nutzername",
|
||||||
"password": "Passwort",
|
"password": "Passwort",
|
||||||
"no.progress": "Kein Fortschritt",
|
"no.progress": "Kein Fortschritt",
|
||||||
|
"project": "Projekt",
|
||||||
"validationMessage": {
|
"validationMessage": {
|
||||||
"inputNotMatching": "Eingabe stimmt nicht überein!"
|
"inputNotMatching": "Eingabe stimmt nicht überein!"
|
||||||
},
|
},
|
||||||
"project": "Projekt"
|
"retry.dialog": {
|
||||||
|
"title": "Etwas ist schief gelaufen...",
|
||||||
|
"information": "Fehler beim verarbeiten Ihrer Anfrage. \nBitte versuchen Sie es erneut oder zu einem späteren Zeitpunkt."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"languageKeys":{
|
"languageKeys":{
|
||||||
"de-DE": "Deutsch",
|
"de-DE": "Deutsch",
|
||||||
|
|
|
@ -21,10 +21,14 @@
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"no.progress": "No progress",
|
"no.progress": "No progress",
|
||||||
|
"project": "Project",
|
||||||
"validationMessage": {
|
"validationMessage": {
|
||||||
"inputNotMatching": "Input does not match!"
|
"inputNotMatching": "Input does not match!"
|
||||||
},
|
},
|
||||||
"project": "Project"
|
"retry.dialog": {
|
||||||
|
"title": "Something went wrong...",
|
||||||
|
"information": "An error occured while processing your request. \nPlease retry or try it again later."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"languageKeys":{
|
"languageKeys":{
|
||||||
"de-DE": "German",
|
"de-DE": "German",
|
||||||
|
|
|
@ -4,7 +4,13 @@ import {GenericDialogData, GenericFormFieldConfig} from '@shared/models/generic-
|
||||||
import * as FA from '@fortawesome/free-solid-svg-icons';
|
import * as FA from '@fortawesome/free-solid-svg-icons';
|
||||||
import deepEqual from 'deep-equal';
|
import deepEqual from 'deep-equal';
|
||||||
import {NB_DIALOG_CONFIG, NbDialogRef} from '@nebular/theme';
|
import {NB_DIALOG_CONFIG, NbDialogRef} from '@nebular/theme';
|
||||||
import {UntilDestroy} from '@ngneat/until-destroy';
|
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||||
|
import {Comment, transformCommentToRequestBody} from '@shared/models/comment.model';
|
||||||
|
import {UpdatePentestComments} from '@shared/stores/project-state/project-state.actions';
|
||||||
|
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service';
|
||||||
|
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||||
|
import {Store} from '@ngxs/store';
|
||||||
|
import {CommentService} from '@shared/services/api/comment.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-comment-dialog',
|
selector: 'app-comment-dialog',
|
||||||
|
@ -26,7 +32,11 @@ export class CommentDialogComponent implements OnInit {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(NB_DIALOG_CONFIG) private data: GenericDialogData,
|
@Inject(NB_DIALOG_CONFIG) private data: GenericDialogData,
|
||||||
private fb: FormBuilder,
|
private fb: FormBuilder,
|
||||||
protected dialogRef: NbDialogRef<CommentDialogComponent>
|
protected dialogRef: NbDialogRef<CommentDialogComponent>,
|
||||||
|
private commentService: CommentService,
|
||||||
|
private readonly notificationService: NotificationService,
|
||||||
|
private dialogService: DialogService,
|
||||||
|
private store: Store
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,11 +60,13 @@ export class CommentDialogComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickSave(value: any): void {
|
onClickSave(value: any): void {
|
||||||
this.dialogRef.close({
|
if (this.dialogData.options[0].headerLabelKey.includes('create')) {
|
||||||
title: value.commentTitle,
|
// Save
|
||||||
description: value.commentDescription,
|
this.saveComment(value);
|
||||||
// relatedFindings: this.selectedFindings ? this.selectedFindings : []
|
} else {
|
||||||
});
|
// Update
|
||||||
|
this.updateComment(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickClose(): void {
|
onClickClose(): void {
|
||||||
|
@ -91,4 +103,67 @@ export class CommentDialogComponent implements OnInit {
|
||||||
});
|
});
|
||||||
return commentData;
|
return commentData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saveComment(value): void {
|
||||||
|
const dialogRes = {
|
||||||
|
title: value.commentTitle,
|
||||||
|
description: value.commentDescription,
|
||||||
|
// relatedFindings: this.selectedFindings ? this.selectedFindings : []
|
||||||
|
};
|
||||||
|
|
||||||
|
this.commentService.saveComment(
|
||||||
|
this.dialogData.options[0].additionalData.id,
|
||||||
|
transformCommentToRequestBody(dialogRes)
|
||||||
|
).pipe(
|
||||||
|
untilDestroyed(this)
|
||||||
|
).subscribe({
|
||||||
|
next: (newComment: Comment) => {
|
||||||
|
this.store.dispatch(new UpdatePentestComments(newComment.id));
|
||||||
|
this.dialogRef.close();
|
||||||
|
this.notificationService.showPopup('comment.popup.save.success', PopupType.SUCCESS);
|
||||||
|
},
|
||||||
|
error: err => {
|
||||||
|
console.error(err);
|
||||||
|
this.onRequestFailed(value);
|
||||||
|
this.notificationService.showPopup('comment.popup.save.failed', PopupType.FAILURE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateComment(value): void {
|
||||||
|
const dialogRes = {
|
||||||
|
title: value.commentTitle,
|
||||||
|
description: value.commentDescription,
|
||||||
|
// relatedFindings: this.selectedFindings ? this.selectedFindings : []
|
||||||
|
};
|
||||||
|
|
||||||
|
this.commentService.updateComment(
|
||||||
|
this.dialogData.options[0].additionalData.id,
|
||||||
|
transformCommentToRequestBody(dialogRes)
|
||||||
|
).pipe(
|
||||||
|
untilDestroyed(this)
|
||||||
|
).subscribe({
|
||||||
|
next: (updatedComment: Comment) => {
|
||||||
|
this.dialogRef.close();
|
||||||
|
this.notificationService.showPopup('comment.popup.update.success', PopupType.SUCCESS);
|
||||||
|
},
|
||||||
|
error: err => {
|
||||||
|
console.error(err);
|
||||||
|
this.onRequestFailed(value);
|
||||||
|
this.notificationService.showPopup('comment.popup.update.failed', PopupType.FAILURE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private onRequestFailed(retryParameter: any): void {
|
||||||
|
this.dialogService.openRetryDialog({key: 'global.retry.dialog', data: null}).onClose
|
||||||
|
.pipe(
|
||||||
|
untilDestroyed(this)
|
||||||
|
)
|
||||||
|
.subscribe((ref) => {
|
||||||
|
if (ref.retry) {
|
||||||
|
this.onClickSave(retryParameter);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {Observable} from 'rxjs';
|
||||||
import {Validators} from '@angular/forms';
|
import {Validators} from '@angular/forms';
|
||||||
import {CommentDialogComponent} from '@shared/modules/comment-dialog/comment-dialog.component';
|
import {CommentDialogComponent} from '@shared/modules/comment-dialog/comment-dialog.component';
|
||||||
import {Comment} from '@shared/models/comment.model';
|
import {Comment} from '@shared/models/comment.model';
|
||||||
|
import {Pentest} from '@shared/models/pentest.model';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CommentDialogService {
|
export class CommentDialogService {
|
||||||
|
@ -31,16 +32,18 @@ export class CommentDialogService {
|
||||||
public openCommentDialog(componentOrTemplateRef: ComponentType<any>,
|
public openCommentDialog(componentOrTemplateRef: ComponentType<any>,
|
||||||
findingIds: string[],
|
findingIds: string[],
|
||||||
comment?: Comment,
|
comment?: Comment,
|
||||||
config?: Partial<NbDialogConfig<Partial<any> | string>>): Observable<any> {
|
config?: Partial<NbDialogConfig<Partial<any> | string>>,
|
||||||
|
pentestInfo?: Pentest): Observable<any> {
|
||||||
let dialogOptions: Partial<NbDialogConfig<Partial<any> | string>>;
|
let dialogOptions: Partial<NbDialogConfig<Partial<any> | string>>;
|
||||||
let dialogData: GenericDialogData;
|
let dialogData: GenericDialogData;
|
||||||
// Preselect attachments
|
// Preselect attachments
|
||||||
const attachments: string[] = [];
|
const attachments: string[] = [];
|
||||||
|
/* ToDo: Use after file upload is implemented
|
||||||
if (comment && comment.attachments.length > 0) {
|
if (comment && comment.attachments.length > 0) {
|
||||||
comment.attachments.forEach(attachment => {
|
comment.attachments.forEach(attachment => {
|
||||||
// Load attachment to show
|
// Load attachment to show
|
||||||
});
|
});
|
||||||
}
|
}*/
|
||||||
// Setup CommentDialogBody
|
// Setup CommentDialogBody
|
||||||
dialogData = {
|
dialogData = {
|
||||||
form: {
|
form: {
|
||||||
|
@ -78,7 +81,8 @@ export class CommentDialogService {
|
||||||
{
|
{
|
||||||
headerLabelKey: 'comment.edit.header',
|
headerLabelKey: 'comment.edit.header',
|
||||||
buttonKey: 'global.action.update',
|
buttonKey: 'global.action.update',
|
||||||
accentColor: 'warning'
|
accentColor: 'warning',
|
||||||
|
additionalData: comment
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
|
@ -86,7 +90,8 @@ export class CommentDialogService {
|
||||||
{
|
{
|
||||||
headerLabelKey: 'comment.create.header',
|
headerLabelKey: 'comment.create.header',
|
||||||
buttonKey: 'global.action.save',
|
buttonKey: 'global.action.save',
|
||||||
accentColor: 'info'
|
accentColor: 'info',
|
||||||
|
additionalData: pentestInfo
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {shareReplay, tap} from 'rxjs/operators';
|
||||||
import {downloadFile} from '@shared/functions/download-file.function';
|
import {downloadFile} from '@shared/functions/download-file.function';
|
||||||
import {Loading, LoadingState} from '@shared/models/loading.model';
|
import {Loading, LoadingState} from '@shared/models/loading.model';
|
||||||
import {HttpEvent, HttpEventType} from '@angular/common/http';
|
import {HttpEvent, HttpEventType} from '@angular/common/http';
|
||||||
|
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-export-report-dialog',
|
selector: 'app-export-report-dialog',
|
||||||
|
@ -28,7 +29,8 @@ export class ExportReportDialogComponent implements OnInit {
|
||||||
private projectService: ProjectService,
|
private projectService: ProjectService,
|
||||||
private reportingService: ReportingService,
|
private reportingService: ReportingService,
|
||||||
private readonly notificationService: NotificationService,
|
private readonly notificationService: NotificationService,
|
||||||
protected dialogRef: NbDialogRef<ExportReportDialogComponent>
|
protected dialogRef: NbDialogRef<ExportReportDialogComponent>,
|
||||||
|
private dialogService: DialogService
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,6 +107,7 @@ export class ExportReportDialogComponent implements OnInit {
|
||||||
error: error => {
|
error: error => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
this.loading$.next(false);
|
this.loading$.next(false);
|
||||||
|
this.onRequestFailed(reportFormat, reportLanguage);
|
||||||
this.notificationService.showPopup('report.popup.generation.failed', PopupType.FAILURE);
|
this.notificationService.showPopup('report.popup.generation.failed', PopupType.FAILURE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -136,6 +139,19 @@ export class ExportReportDialogComponent implements OnInit {
|
||||||
isLoading(): Observable<boolean> {
|
isLoading(): Observable<boolean> {
|
||||||
return this.loading$.asObservable();
|
return this.loading$.asObservable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onRequestFailed(reportFormat: string, reportLanguage: string): void {
|
||||||
|
this.dialogService.openRetryDialog({key: 'global.retry.dialog', data: null}).onClose
|
||||||
|
.pipe(
|
||||||
|
untilDestroyed(this)
|
||||||
|
)
|
||||||
|
.subscribe((ref) => {
|
||||||
|
if (ref.retry) {
|
||||||
|
// ToDo: Send same request again
|
||||||
|
this.onClickExport(reportFormat, reportLanguage);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ExportFormatOptions {
|
export enum ExportFormatOptions {
|
||||||
|
|
|
@ -3,9 +3,15 @@ import {FormBuilder, FormGroup} from '@angular/forms';
|
||||||
import {GenericDialogData, GenericFormFieldConfig} from '@shared/models/generic-dialog-data';
|
import {GenericDialogData, GenericFormFieldConfig} from '@shared/models/generic-dialog-data';
|
||||||
import {NB_DIALOG_CONFIG, NbDialogRef, NbTagComponent} from '@nebular/theme';
|
import {NB_DIALOG_CONFIG, NbDialogRef, NbTagComponent} from '@nebular/theme';
|
||||||
import deepEqual from 'deep-equal';
|
import deepEqual from 'deep-equal';
|
||||||
import {UntilDestroy} from '@ngneat/until-destroy';
|
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||||
import {Severity} from '@shared/models/severity.enum';
|
import {Severity} from '@shared/models/severity.enum';
|
||||||
import * as FA from '@fortawesome/free-solid-svg-icons';
|
import * as FA from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import {Finding, transformFindingToRequestBody} from '@shared/models/finding.model';
|
||||||
|
import {FindingService} from '@shared/services/api/finding.service';
|
||||||
|
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service';
|
||||||
|
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||||
|
import {UpdatePentestFindings} from '@shared/stores/project-state/project-state.actions';
|
||||||
|
import {Store} from '@ngxs/store';
|
||||||
|
|
||||||
@UntilDestroy()
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -38,7 +44,11 @@ export class FindingDialogComponent implements OnInit {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(NB_DIALOG_CONFIG) private data: GenericDialogData,
|
@Inject(NB_DIALOG_CONFIG) private data: GenericDialogData,
|
||||||
private fb: FormBuilder,
|
private fb: FormBuilder,
|
||||||
protected dialogRef: NbDialogRef<FindingDialogComponent>
|
protected dialogRef: NbDialogRef<FindingDialogComponent>,
|
||||||
|
private findingService: FindingService,
|
||||||
|
private readonly notificationService: NotificationService,
|
||||||
|
private dialogService: DialogService,
|
||||||
|
private store: Store
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,18 +74,6 @@ export class FindingDialogComponent implements OnInit {
|
||||||
return this.fb.group(config);
|
return this.fb.group(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickSave(value: any): void {
|
|
||||||
this.dialogRef.close({
|
|
||||||
title: value.findingTitle,
|
|
||||||
severity: this.formArray[1].controlsConfig[0].value,
|
|
||||||
description: value.findingDescription,
|
|
||||||
impact: value.findingImpact,
|
|
||||||
affectedUrls: this.affectedUrls ? this.affectedUrls : [],
|
|
||||||
reproduction: value.findingReproduction,
|
|
||||||
mitigation: value.findingMitigation
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
renderAffectedUrls(affectedUrls: string[]): void {
|
renderAffectedUrls(affectedUrls: string[]): void {
|
||||||
affectedUrls.forEach(url => this.initialAffectedUrls.push(url));
|
affectedUrls.forEach(url => this.initialAffectedUrls.push(url));
|
||||||
affectedUrls.forEach(url => this.affectedUrls.push(url));
|
affectedUrls.forEach(url => this.affectedUrls.push(url));
|
||||||
|
@ -95,6 +93,16 @@ export class FindingDialogComponent implements OnInit {
|
||||||
this.affectedUrls = this.affectedUrls.filter(t => t !== tagToRemove.text);
|
this.affectedUrls = this.affectedUrls.filter(t => t !== tagToRemove.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onClickSave(value: any): void {
|
||||||
|
if (this.dialogData.options[0].headerLabelKey.includes('create')) {
|
||||||
|
// Save
|
||||||
|
this.saveFinding(value);
|
||||||
|
} else {
|
||||||
|
// Update
|
||||||
|
this.updateFinding(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onClickClose(): void {
|
onClickClose(): void {
|
||||||
this.dialogRef.close();
|
this.dialogRef.close();
|
||||||
}
|
}
|
||||||
|
@ -168,6 +176,77 @@ export class FindingDialogComponent implements OnInit {
|
||||||
});
|
});
|
||||||
return findingData;
|
return findingData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private saveFinding(value): void {
|
||||||
|
const dialogRes = {
|
||||||
|
title: value.findingTitle,
|
||||||
|
severity: this.formArray[1].controlsConfig[0].value,
|
||||||
|
description: value.findingDescription,
|
||||||
|
impact: value.findingImpact,
|
||||||
|
affectedUrls: this.affectedUrls ? this.affectedUrls : [],
|
||||||
|
reproduction: value.findingReproduction,
|
||||||
|
mitigation: value.findingMitigation
|
||||||
|
};
|
||||||
|
|
||||||
|
this.findingService.saveFinding(
|
||||||
|
this.dialogData.options[0].additionalData.id,
|
||||||
|
transformFindingToRequestBody(dialogRes)
|
||||||
|
).pipe(
|
||||||
|
untilDestroyed(this)
|
||||||
|
).subscribe({
|
||||||
|
next: (newFinding: Finding) => {
|
||||||
|
this.store.dispatch(new UpdatePentestFindings(newFinding.id));
|
||||||
|
this.dialogRef.close();
|
||||||
|
this.notificationService.showPopup('finding.popup.save.success', PopupType.SUCCESS);
|
||||||
|
},
|
||||||
|
error: err => {
|
||||||
|
console.error(err);
|
||||||
|
this.onRequestFailed(value);
|
||||||
|
this.notificationService.showPopup('finding.popup.save.failed', PopupType.FAILURE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateFinding(value): void {
|
||||||
|
const dialogRes = {
|
||||||
|
title: value.findingTitle,
|
||||||
|
severity: this.formArray[1].controlsConfig[0].value,
|
||||||
|
description: value.findingDescription,
|
||||||
|
impact: value.findingImpact,
|
||||||
|
affectedUrls: this.affectedUrls ? this.affectedUrls : [],
|
||||||
|
reproduction: value.findingReproduction,
|
||||||
|
mitigation: value.findingMitigation
|
||||||
|
};
|
||||||
|
|
||||||
|
this.findingService.updateFinding(
|
||||||
|
this.dialogData.options[0].additionalData.id,
|
||||||
|
transformFindingToRequestBody(dialogRes)
|
||||||
|
).pipe(
|
||||||
|
untilDestroyed(this)
|
||||||
|
).subscribe({
|
||||||
|
next: (newFinding: Finding) => {
|
||||||
|
this.dialogRef.close();
|
||||||
|
this.notificationService.showPopup('finding.popup.update.success', PopupType.SUCCESS);
|
||||||
|
},
|
||||||
|
error: err => {
|
||||||
|
console.error(err);
|
||||||
|
this.onRequestFailed(value);
|
||||||
|
this.notificationService.showPopup('finding.popup.update.failed', PopupType.FAILURE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private onRequestFailed(retryParameter: any): void {
|
||||||
|
this.dialogService.openRetryDialog({key: 'global.retry.dialog', data: null}).onClose
|
||||||
|
.pipe(
|
||||||
|
untilDestroyed(this)
|
||||||
|
)
|
||||||
|
.subscribe((ref) => {
|
||||||
|
if (ref.retry) {
|
||||||
|
this.onClickSave(retryParameter);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SeverityText {
|
interface SeverityText {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {Validators} from '@angular/forms';
|
||||||
import {FindingDialogComponent} from '@shared/modules/finding-dialog/finding-dialog.component';
|
import {FindingDialogComponent} from '@shared/modules/finding-dialog/finding-dialog.component';
|
||||||
import {Finding} from '@shared/models/finding.model';
|
import {Finding} from '@shared/models/finding.model';
|
||||||
import {Severity} from '@shared/models/severity.enum';
|
import {Severity} from '@shared/models/severity.enum';
|
||||||
|
import {Pentest} from '@shared/models/pentest.model';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FindingDialogService {
|
export class FindingDialogService {
|
||||||
|
@ -31,7 +32,8 @@ export class FindingDialogService {
|
||||||
|
|
||||||
public openFindingDialog(componentOrTemplateRef: ComponentType<any>,
|
public openFindingDialog(componentOrTemplateRef: ComponentType<any>,
|
||||||
finding?: Finding,
|
finding?: Finding,
|
||||||
config?: Partial<NbDialogConfig<Partial<any> | string>>): Observable<any> {
|
config?: Partial<NbDialogConfig<Partial<any> | string>>,
|
||||||
|
pentestInfo?: Pentest): Observable<any> {
|
||||||
let dialogOptions: Partial<NbDialogConfig<Partial<any> | string>>;
|
let dialogOptions: Partial<NbDialogConfig<Partial<any> | string>>;
|
||||||
let dialogData: GenericDialogData;
|
let dialogData: GenericDialogData;
|
||||||
let severity;
|
let severity;
|
||||||
|
@ -141,7 +143,8 @@ export class FindingDialogService {
|
||||||
{
|
{
|
||||||
headerLabelKey: 'finding.edit.header',
|
headerLabelKey: 'finding.edit.header',
|
||||||
buttonKey: 'global.action.update',
|
buttonKey: 'global.action.update',
|
||||||
accentColor: 'warning'
|
accentColor: 'warning',
|
||||||
|
additionalData: finding
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
|
@ -149,7 +152,8 @@ export class FindingDialogService {
|
||||||
{
|
{
|
||||||
headerLabelKey: 'finding.create.header',
|
headerLabelKey: 'finding.create.header',
|
||||||
buttonKey: 'global.action.save',
|
buttonKey: 'global.action.save',
|
||||||
accentColor: 'info'
|
accentColor: 'info',
|
||||||
|
additionalData: pentestInfo
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,10 @@ import {NB_DIALOG_CONFIG, NbDialogRef} from '@nebular/theme';
|
||||||
import {FormBuilder, FormGroup} from '@angular/forms';
|
import {FormBuilder, FormGroup} from '@angular/forms';
|
||||||
import {GenericFormFieldConfig, GenericDialogData} from '@shared/models/generic-dialog-data';
|
import {GenericFormFieldConfig, GenericDialogData} from '@shared/models/generic-dialog-data';
|
||||||
import deepEqual from 'deep-equal';
|
import deepEqual from 'deep-equal';
|
||||||
import {UntilDestroy} from '@ngneat/until-destroy';
|
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||||
|
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||||
|
import {ProjectService} from '@shared/services/api/project.service';
|
||||||
|
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service';
|
||||||
|
|
||||||
@UntilDestroy()
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -21,6 +24,9 @@ export class ProjectDialogComponent implements OnInit {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(NB_DIALOG_CONFIG) private data: GenericDialogData,
|
@Inject(NB_DIALOG_CONFIG) private data: GenericDialogData,
|
||||||
private fb: FormBuilder,
|
private fb: FormBuilder,
|
||||||
|
private dialogService: DialogService,
|
||||||
|
private projectService: ProjectService,
|
||||||
|
private readonly notificationService: NotificationService,
|
||||||
protected dialogRef: NbDialogRef<ProjectDialogComponent>
|
protected dialogRef: NbDialogRef<ProjectDialogComponent>
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
@ -40,12 +46,13 @@ export class ProjectDialogComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickSave(value): void {
|
onClickSave(value): void {
|
||||||
this.dialogRef.close({
|
if (this.dialogData.options[0].headerLabelKey.includes('create')) {
|
||||||
title: value.projectTitle,
|
// Save
|
||||||
client: value.projectClient,
|
this.saveProject(value);
|
||||||
tester: value.projectTester,
|
} else {
|
||||||
summary: value.projectSummary
|
// Update
|
||||||
});
|
this.updateProject(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickClose(): void {
|
onClickClose(): void {
|
||||||
|
@ -85,4 +92,64 @@ export class ProjectDialogComponent implements OnInit {
|
||||||
});
|
});
|
||||||
return projectData;
|
return projectData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private saveProject(value): void {
|
||||||
|
const dialogRes = {
|
||||||
|
title: value.projectTitle,
|
||||||
|
client: value.projectClient,
|
||||||
|
tester: value.projectTester,
|
||||||
|
summary: value.projectSummary
|
||||||
|
};
|
||||||
|
this.projectService.saveProject(dialogRes).pipe(
|
||||||
|
untilDestroyed(this)
|
||||||
|
).subscribe(
|
||||||
|
{
|
||||||
|
next: () => {
|
||||||
|
this.notificationService.showPopup('project.popup.save.success', PopupType.SUCCESS);
|
||||||
|
this.dialogRef.close();
|
||||||
|
},
|
||||||
|
error: err => {
|
||||||
|
console.error(err);
|
||||||
|
this.onRequestFailed(value);
|
||||||
|
this.notificationService.showPopup('project.popup.save.failed', PopupType.FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateProject(value): void {
|
||||||
|
const dialogRes = {
|
||||||
|
title: value.projectTitle,
|
||||||
|
client: value.projectClient,
|
||||||
|
tester: value.projectTester,
|
||||||
|
summary: value.projectSummary
|
||||||
|
};
|
||||||
|
this.projectService.updateProject(this.dialogData.options[0].additionalData.id, dialogRes).pipe(
|
||||||
|
untilDestroyed(this)
|
||||||
|
).subscribe(
|
||||||
|
{
|
||||||
|
next: () => {
|
||||||
|
this.notificationService.showPopup('project.popup.update.success', PopupType.SUCCESS);
|
||||||
|
this.dialogRef.close();
|
||||||
|
},
|
||||||
|
error: err => {
|
||||||
|
console.error(err);
|
||||||
|
this.onRequestFailed(value);
|
||||||
|
this.notificationService.showPopup('project.popup.update.failed', PopupType.FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onRequestFailed(retryParameter: any): void {
|
||||||
|
this.dialogService.openRetryDialog({key: 'global.retry.dialog', data: null}).onClose
|
||||||
|
.pipe(
|
||||||
|
untilDestroyed(this)
|
||||||
|
)
|
||||||
|
.subscribe((ref) => {
|
||||||
|
if (ref.retry) {
|
||||||
|
this.onClickSave(retryParameter);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,7 +96,8 @@ export class ProjectDialogService {
|
||||||
{
|
{
|
||||||
headerLabelKey: 'project.edit.header',
|
headerLabelKey: 'project.edit.header',
|
||||||
buttonKey: 'global.action.update',
|
buttonKey: 'global.action.update',
|
||||||
accentColor: 'warning'
|
accentColor: 'warning',
|
||||||
|
additionalData: project
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
|
@ -104,7 +105,8 @@ export class ProjectDialogService {
|
||||||
{
|
{
|
||||||
headerLabelKey: 'project.create.header',
|
headerLabelKey: 'project.create.header',
|
||||||
buttonKey: 'global.action.save',
|
buttonKey: 'global.action.save',
|
||||||
accentColor: 'info'
|
accentColor: 'info',
|
||||||
|
additionalData: project
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
<nb-card accent="danger">
|
||||||
|
<nb-card-header fxLayoutAlign="start center" class="dialog-header confirm">
|
||||||
|
{{ 'global.retry.dialog.title' | translate }}
|
||||||
|
</nb-card-header>
|
||||||
|
<nb-card-body class="dialog-body">
|
||||||
|
{{ 'global.retry.dialog.information' | translate }}
|
||||||
|
</nb-card-body>
|
||||||
|
<nb-card-footer fxLayout="row" fxLayoutGap="1.5rem" fxLayoutAlign="end end">
|
||||||
|
<button nbButton size="small"
|
||||||
|
class="retry-dialog-button"
|
||||||
|
status="danger"
|
||||||
|
(click)="onClickRetry()">
|
||||||
|
<fa-icon [icon]="fa.faRedoAlt" class="dialog-button-icon"></fa-icon>
|
||||||
|
{{ 'global.action.retry' | translate }}
|
||||||
|
</button>
|
||||||
|
<button nbButton size="small"
|
||||||
|
class="cancel-dialog-button"
|
||||||
|
(click)="onClickCancel()">
|
||||||
|
{{ 'global.action.cancel' | translate }}
|
||||||
|
</button>
|
||||||
|
</nb-card-footer>
|
||||||
|
</nb-card>
|
|
@ -0,0 +1,11 @@
|
||||||
|
@import "../../../assets/@theme/styles/_dialog.scss";
|
||||||
|
|
||||||
|
* {
|
||||||
|
}
|
||||||
|
|
||||||
|
.retry-dialog-button {
|
||||||
|
// margin: 6rem 2rem 6rem 0;
|
||||||
|
.dialog-button-icon {
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||||
|
|
||||||
|
import {RetryDialogComponent} from './retry-dialog.component';
|
||||||
|
import {CommonModule} from '@angular/common';
|
||||||
|
import {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';
|
||||||
|
|
||||||
|
describe('RetryDialogComponent', () => {
|
||||||
|
let component: RetryDialogComponent;
|
||||||
|
let fixture: ComponentFixture<RetryDialogComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [
|
||||||
|
RetryDialogComponent
|
||||||
|
],
|
||||||
|
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: {}}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(RetryDialogComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,31 @@
|
||||||
|
import {Component, Input} from '@angular/core';
|
||||||
|
import {NbDialogRef} from '@nebular/theme';
|
||||||
|
import * as FA from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-retry-dialog',
|
||||||
|
templateUrl: './retry-dialog.component.html',
|
||||||
|
styleUrls: ['./retry-dialog.component.scss']
|
||||||
|
})
|
||||||
|
export class RetryDialogComponent {
|
||||||
|
/**
|
||||||
|
* @param data contains all relevant information the dialog needs
|
||||||
|
* @param data.title The translation key for the dialog title
|
||||||
|
* @param data.key The translation key for the shown message
|
||||||
|
* @param data.data The data that may be used in the message translation key
|
||||||
|
*/
|
||||||
|
@Input() data: any;
|
||||||
|
// HTML only
|
||||||
|
readonly fa = FA;
|
||||||
|
|
||||||
|
constructor(protected dialogRef: NbDialogRef<any>) {
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickRetry(): void {
|
||||||
|
this.dialogRef.close({retry: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickCancel(): void {
|
||||||
|
this.dialogRef.close({retry: false});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import {CommonAppModule} from '../../../app/common-app.module';
|
||||||
|
import {NbButtonModule, NbCardModule, NbLayoutModule, NbSelectModule} from '@nebular/theme';
|
||||||
|
import {FlexLayoutModule} from '@angular/flex-layout';
|
||||||
|
import {TranslateModule} from '@ngx-translate/core';
|
||||||
|
import {RetryDialogComponent} from '@shared/modules/retry-dialog/retry-dialog.component';
|
||||||
|
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
RetryDialogComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
CommonAppModule,
|
||||||
|
NbCardModule,
|
||||||
|
NbButtonModule,
|
||||||
|
FlexLayoutModule,
|
||||||
|
TranslateModule,
|
||||||
|
NbLayoutModule,
|
||||||
|
NbSelectModule,
|
||||||
|
FontAwesomeModule
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
RetryDialogComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class RetryDialogModule { }
|
|
@ -0,0 +1,20 @@
|
||||||
|
import {untilDestroyed} from '@ngneat/until-destroy';
|
||||||
|
|
||||||
|
// ToDo: PoC for handling failed requests
|
||||||
|
function onRequestFailed(retryParameter: any): void {
|
||||||
|
this.dialogService.openRetryDialog({key: 'global.retry.dialog', data: null}).onClose
|
||||||
|
.pipe(
|
||||||
|
untilDestroyed(this)
|
||||||
|
)
|
||||||
|
.subscribe((ref) => {
|
||||||
|
console.warn(ref);
|
||||||
|
if (ref.retry) {
|
||||||
|
// ToDo: Send same request again
|
||||||
|
console.warn('Retry');
|
||||||
|
this.METHODTHATNEEDSTOBEEXECUTED(retryParameter);
|
||||||
|
} else {
|
||||||
|
// ToDo: Cancel action
|
||||||
|
console.warn('Cancel');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -20,6 +20,10 @@ export class DialogServiceMock implements Required<DialogService> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openRetryDialog(message: DialogMessage): NbDialogRef<any> {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
openSecurityConfirmDialog(message: SecurityDialogMessage): NbDialogRef<SecurityConfirmDialogComponent> {
|
openSecurityConfirmDialog(message: SecurityDialogMessage): NbDialogRef<SecurityConfirmDialogComponent> {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {ComponentType} from '@angular/cdk/overlay';
|
||||||
import {DialogMessage, SecurityDialogMessage} 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 {ConfirmDialogComponent} from '@shared/modules/confirm-dialog/confirm-dialog.component';
|
||||||
import {SecurityConfirmDialogComponent} from '@shared/modules/security-confirm-dialog/security-confirm-dialog.component';
|
import {SecurityConfirmDialogComponent} from '@shared/modules/security-confirm-dialog/security-confirm-dialog.component';
|
||||||
|
import {RetryDialogComponent} from '@shared/modules/retry-dialog/retry-dialog.component';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
|
@ -44,6 +45,21 @@ export class DialogService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
openRetryDialog(message: DialogMessage): NbDialogRef<RetryDialogComponent> {
|
||||||
|
return this.dialog.open(RetryDialogComponent, {
|
||||||
|
closeOnEsc: true,
|
||||||
|
hasScroll: false,
|
||||||
|
autoFocus: true,
|
||||||
|
closeOnBackdropClick: false,
|
||||||
|
context: {data: message}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param message.key The translation key for the shown 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.data The data that may be used in the message translation key (Set it null if it's not required in the key)
|
||||||
|
|
|
@ -71,7 +71,7 @@ class ProjectController(private val projectService: ProjectService, private val
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
fun deleteProject(@PathVariable(value = "id") id: String): Mono<ResponseEntity<ResponseBody>> {
|
fun deleteProject(@PathVariable(value = "id") id: String): Mono<ResponseEntity<ResponseBody>> {
|
||||||
return this.projectService.deleteProject(id).flatMap { project: Project ->
|
return this.projectService.deleteProject(id).flatMap { project: Project ->
|
||||||
// If the project has pentest the will be deleted as well as all associated findings & comments
|
// If the project has pentest they will be deleted as well as all associated findings & comments
|
||||||
if (project.projectPentests.isNotEmpty()) {
|
if (project.projectPentests.isNotEmpty()) {
|
||||||
this.pentestDeletionService.deletePentestsAndAllAssociatedFindingsAndComments(project).collectList()
|
this.pentestDeletionService.deletePentestsAndAllAssociatedFindingsAndComments(project).collectList()
|
||||||
.flatMap { prunedProject: Any ->
|
.flatMap { prunedProject: Any ->
|
||||||
|
|
Loading…
Reference in New Issue