fix: Bugfixes and QoL Improvements
This commit is contained in:
parent
e9aec4ec3e
commit
a4536e9735
|
@ -11,6 +11,15 @@
|
|||
<div class="filler"></div>
|
||||
<div fxLayoutGap="4rem">
|
||||
<nb-actions size="medium">
|
||||
<nb-action class="toggle-theme">
|
||||
<button nbButton
|
||||
(click)="onClickGoToLink('https://owasp.org/www-project-web-security-testing-guide/v42/')">
|
||||
<fa-icon [icon]="fa.faFileInvoice" class="new-element-icon" href="https://www.google.com">
|
||||
</fa-icon>
|
||||
<span class="owasp-redirect-button">OWASP</span>
|
||||
</button>
|
||||
</nb-action>
|
||||
|
||||
<nb-action class="toggle-theme">
|
||||
<button nbButton
|
||||
(click)="onClickSwitchTheme()">
|
||||
|
|
|
@ -8,6 +8,10 @@
|
|||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.owasp-redirect-button {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.languageContainer {
|
||||
display: flex;
|
||||
max-width: 8rem;
|
||||
|
|
|
@ -33,6 +33,11 @@ export class HeaderComponent implements OnInit{
|
|||
this.selectedLanguage = this.translateService.currentLang;
|
||||
}
|
||||
|
||||
// HTML only
|
||||
onClickGoToLink(url: string): void {
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
|
||||
onClickSwitchTheme(): void {
|
||||
if (this.currentTheme === 'corporate') {
|
||||
this.themeService.changeTheme('dark');
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
.export-button-container {
|
||||
display: flex;
|
||||
align-content: flex-end;
|
||||
margin-right: 0.5rem;
|
||||
// ToDo: Fix so that longer / shorter name won't change needed margin
|
||||
margin-right: 2.25rem;
|
||||
|
||||
.export-element-icon {
|
||||
}
|
||||
|
|
|
@ -76,8 +76,24 @@ export class ObjectiveTableComponent implements OnInit {
|
|||
})
|
||||
).finally();
|
||||
*/
|
||||
const statePentest = this.pentests$.getValue().find(pentest => pentest.refNumber === selectedPentest.refNumber);
|
||||
this.store.dispatch(new ChangePentest(statePentest));
|
||||
const statePentest: Pentest = this.pentests$.getValue().find(pentest => pentest.refNumber === selectedPentest.refNumber);
|
||||
if (statePentest) {
|
||||
this.store.dispatch(new ChangePentest(statePentest));
|
||||
} else {
|
||||
let childEntryStatePentest;
|
||||
// ToDo: Fix wrong selection
|
||||
// tslint:disable-next-line:prefer-for-of
|
||||
for (let i = 0; i < this.pentests$.getValue().length; i++) {
|
||||
if (this.pentests$.getValue()[i].childEntries) {
|
||||
const findingResult = this.pentests$.getValue()[i].childEntries.find(cE => cE.refNumber === selectedPentest.refNumber);
|
||||
if (findingResult) {
|
||||
childEntryStatePentest = findingResult;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.store.dispatch(new ChangePentest(childEntryStatePentest));
|
||||
}
|
||||
}
|
||||
|
||||
// HTML only
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
size="small"
|
||||
shape="round"
|
||||
class="add-comment-button"
|
||||
[disabled]="pentestInfo$.getValue().status === notStartedStatus"
|
||||
(click)="onClickAddComment()">
|
||||
<fa-icon [icon]="fa.faPlus" class="new-comment-icon"></fa-icon>
|
||||
{{'comment.add' | translate}}
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
.comment-cell {
|
||||
// Add style here
|
||||
height: 4.5rem !important;
|
||||
max-height: 4.5rem !important;
|
||||
}
|
||||
|
||||
.comment-cell:hover {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {BehaviorSubject, Observable} from 'rxjs';
|
||||
import {Pentest} from '@shared/models/pentest.model';
|
||||
import * as FA from '@fortawesome/free-solid-svg-icons';
|
||||
|
@ -9,6 +9,9 @@ import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
|||
import {filter, tap} from 'rxjs/operators';
|
||||
import {Comment, CommentEntry, transformCommentsToObjectiveEntries} from '@shared/models/comment.model';
|
||||
import {isNotNullOrUndefined} from 'codelyzer/util/isNotNullOrUndefined';
|
||||
import {ProjectState} from '@shared/stores/project-state/project-state';
|
||||
import {Store} from '@ngxs/store';
|
||||
import {PentestStatus} from '@shared/models/pentest-status.model';
|
||||
|
||||
@UntilDestroy()
|
||||
@Component({
|
||||
|
@ -18,11 +21,11 @@ import {isNotNullOrUndefined} from 'codelyzer/util/isNotNullOrUndefined';
|
|||
})
|
||||
export class PentestCommentsComponent implements OnInit {
|
||||
|
||||
@Input()
|
||||
pentestInfo$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
|
||||
|
||||
// HTML only
|
||||
readonly fa = FA;
|
||||
notStartedStatus: PentestStatus = PentestStatus.NOT_STARTED;
|
||||
|
||||
pentestInfo$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
|
||||
// comments$: BehaviorSubject<Comment[]> = new BehaviorSubject<Comment[]>(null);
|
||||
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
||||
|
||||
|
@ -41,24 +44,39 @@ export class PentestCommentsComponent implements OnInit {
|
|||
|
||||
constructor(private readonly pentestService: PentestService,
|
||||
private dataSourceBuilder: NbTreeGridDataSourceBuilder<CommentEntry>,
|
||||
private store: Store,
|
||||
private notificationService: NotificationService) {
|
||||
this.dataSource = dataSourceBuilder.create(this.data, this.getters);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadCommentsData();
|
||||
this.store.select(ProjectState.pentest).pipe(
|
||||
untilDestroyed(this)
|
||||
).subscribe({
|
||||
next: (selectedPentest: Pentest) => {
|
||||
this.pentestInfo$.next(selectedPentest);
|
||||
this.loadCommentsData();
|
||||
},
|
||||
error: err => {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadCommentsData(): void {
|
||||
this.pentestService.getCommentsByPentestId(this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '')
|
||||
.pipe(
|
||||
untilDestroyed(this),
|
||||
filter(isNotNullOrUndefined),
|
||||
/*filter(isNotNullOrUndefined),*/
|
||||
tap(() => this.loading$.next(true))
|
||||
)
|
||||
.subscribe({
|
||||
next: (comments: Comment[]) => {
|
||||
this.data = transformCommentsToObjectiveEntries(comments);
|
||||
if (comments) {
|
||||
this.data = transformCommentsToObjectiveEntries(comments);
|
||||
} else {
|
||||
this.data = [];
|
||||
}
|
||||
this.dataSource.setData(this.data, this.getters);
|
||||
this.loading$.next(false);
|
||||
},
|
||||
|
|
|
@ -2,52 +2,21 @@
|
|||
<div class="content">
|
||||
<nb-tabset>
|
||||
<nb-tab class="pentest-tabset" tabTitle="{{ 'global.action.info' | translate }}">
|
||||
<app-pentest-info [pentestInfo$]=pentest$></app-pentest-info>
|
||||
<app-pentest-info></app-pentest-info>
|
||||
</nb-tab>
|
||||
<nb-tab class="pentest-tabset" tabTitle="{{ 'pentest.findings' | translate }}"
|
||||
badgeText="{{currentNumberOfFindings$.getValue()}}" badgeStatus="danger">
|
||||
<app-pentest-findings [pentestInfo$]=pentest$></app-pentest-findings>
|
||||
<app-pentest-findings></app-pentest-findings>
|
||||
</nb-tab>
|
||||
<nb-tab class="pentest-tabset" tabTitle="{{ 'pentest.comments' | translate }}"
|
||||
badgeText="{{currentNumberOfComments$.getValue()}}" badgeStatus="info">
|
||||
<app-pentest-comments [pentestInfo$]=pentest$></app-pentest-comments>
|
||||
<app-pentest-comments></app-pentest-comments>
|
||||
</nb-tab>
|
||||
</nb-tabset>
|
||||
</div>
|
||||
|
||||
<div fxLayoutAlign="end end" class="content-footer">
|
||||
<!-- Pentest Status Selection -->
|
||||
<div class="pentest-status-dialog">
|
||||
<nb-select class="status"
|
||||
[(selected)]="currentStatus"
|
||||
shape="round" filled
|
||||
status="{{getPentestFillStatus(currentStatus)}}">
|
||||
<nb-option *ngFor="let status of statusTexts" [value]="status.value">
|
||||
{{ status.translationText | translate }}
|
||||
</nb-option>
|
||||
</nb-select>
|
||||
</div>
|
||||
<div *ngIf="!pentest$.getValue().id; else updatePentest">
|
||||
<button nbButton
|
||||
class="save-pentest-button"
|
||||
status="primary"
|
||||
[disabled]="!pentestStatusChanged()"
|
||||
title="{{ 'global.action.save' | translate }}"
|
||||
(click)="onClickSavePentest()">
|
||||
<span class="exit-element-text"> {{ 'global.action.save' | translate }} </span>
|
||||
</button>
|
||||
</div>
|
||||
<ng-template #updatePentest>
|
||||
<button nbButton
|
||||
class="save-pentest-button"
|
||||
status="primary"
|
||||
[disabled]="!pentestStatusChanged()"
|
||||
title="{{ 'global.action.update' | translate }}"
|
||||
(click)="onClickUpdatePentest()">
|
||||
<span class="exit-element-text"> {{ 'global.action.update' | translate }} </span>
|
||||
</button>
|
||||
</ng-template>
|
||||
|
||||
<!--ToDo: Use to put element in bottom-right corner -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -10,33 +10,9 @@
|
|||
/*nb-tab {
|
||||
position: fixed;
|
||||
}*/
|
||||
}
|
||||
|
||||
.content-footer {
|
||||
height: 5%;
|
||||
|
||||
.pentest-status-dialog {
|
||||
margin: 1rem 2.25rem 1rem 0;
|
||||
|
||||
.status {
|
||||
width: 12rem;
|
||||
}
|
||||
|
||||
.basic {
|
||||
background-color: nb-theme(color-basic-default);
|
||||
}
|
||||
.info {
|
||||
background-color: nb-theme(color-info-default);
|
||||
}
|
||||
.warning {
|
||||
background-color: nb-theme(color-warning-default);
|
||||
}
|
||||
.success {
|
||||
background-color: nb-theme(color-success-default);
|
||||
}
|
||||
}
|
||||
|
||||
.save-pentest-button {
|
||||
.content-footer {
|
||||
height: 5%;
|
||||
margin: 1rem 6rem 1rem 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,12 @@
|
|||
import {Component, OnInit} from '@angular/core';
|
||||
import * as FA from '@fortawesome/free-solid-svg-icons';
|
||||
import {BehaviorSubject, Observable} from 'rxjs';
|
||||
import {Select, Store} from '@ngxs/store';
|
||||
import {BehaviorSubject} from 'rxjs';
|
||||
import {Store} from '@ngxs/store';
|
||||
import {ProjectState} from '@shared/stores/project-state/project-state';
|
||||
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||
import {Pentest, transformPentestToRequestBody} from '@shared/models/pentest.model';
|
||||
import {PentestStatus} from '@shared/models/pentest-status.model';
|
||||
import {StatusText} from '@shared/widgets/status-tag/status-tag.component';
|
||||
import {Pentest} from '@shared/models/pentest.model';
|
||||
import {PentestService} from '@shared/services/pentest.service';
|
||||
import {NotificationService, PopupType} from '@shared/services/notification.service';
|
||||
import {Project} from '@shared/models/project.model';
|
||||
import {isNotNullOrUndefined} from 'codelyzer/util/isNotNullOrUndefined';
|
||||
import {filter} from 'rxjs/operators';
|
||||
import {NotificationService} from '@shared/services/notification.service';
|
||||
|
||||
@UntilDestroy()
|
||||
@Component({
|
||||
|
@ -23,28 +18,10 @@ export class PentestContentComponent implements OnInit {
|
|||
// HTML only
|
||||
readonly fa = FA;
|
||||
|
||||
@Select(ProjectState.project)
|
||||
selectedProject$: Observable<Project>;
|
||||
selectedProjectId: string;
|
||||
|
||||
pentest$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
|
||||
pentestChanged$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
currentNumberOfFindings$: BehaviorSubject<number> = new BehaviorSubject<number>(0);
|
||||
currentNumberOfComments$: BehaviorSubject<number> = new BehaviorSubject<number>(0);
|
||||
|
||||
// Pentest Status Handler
|
||||
currentStatus: PentestStatus = PentestStatus.NOT_STARTED;
|
||||
private initialPentestStatus: PentestStatus;
|
||||
status = PentestStatus;
|
||||
readonly statusTexts: Array<StatusText> = [
|
||||
{value: PentestStatus.NOT_STARTED, translationText: 'pentest.statusText.not_started'},
|
||||
/* ToDo: Disabled not needed inside pentest */
|
||||
/*{value: PentestStatus.DISABLED, translationText: 'pentest.statusText.disabled'},*/
|
||||
{value: PentestStatus.OPEN, translationText: 'pentest.statusText.open'},
|
||||
{value: PentestStatus.IN_PROGRESS, translationText: 'pentest.statusText.in_progress'},
|
||||
{value: PentestStatus.COMPLETED, translationText: 'pentest.statusText.completed'}
|
||||
];
|
||||
|
||||
constructor(
|
||||
private readonly pentestService: PentestService,
|
||||
private notificationService: NotificationService,
|
||||
|
@ -52,26 +29,11 @@ export class PentestContentComponent implements OnInit {
|
|||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.selectedProject$.pipe(
|
||||
filter(isNotNullOrUndefined),
|
||||
untilDestroyed(this)
|
||||
).subscribe({
|
||||
next: (project) => {
|
||||
this.selectedProjectId = project.id;
|
||||
},
|
||||
error: (err) => {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
this.store.select(ProjectState.pentest).pipe(
|
||||
untilDestroyed(this)
|
||||
).subscribe({
|
||||
next: (selectedPentest: Pentest) => {
|
||||
console.warn(selectedPentest);
|
||||
this.pentest$.next(selectedPentest);
|
||||
this.currentStatus = selectedPentest.status;
|
||||
this.initialPentestStatus = selectedPentest.status;
|
||||
const findings = selectedPentest.findingIds ? selectedPentest.findingIds.length : 0;
|
||||
this.currentNumberOfFindings$.next(findings);
|
||||
const comments = selectedPentest.commentIds ? selectedPentest.commentIds.length : 0;
|
||||
|
@ -82,78 +44,4 @@ export class PentestContentComponent implements OnInit {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
onClickSavePentest(): void {
|
||||
this.pentest$.next({...this.pentest$.getValue(), status: this.currentStatus});
|
||||
this.pentestService.savePentest(this.selectedProjectId, transformPentestToRequestBody(this.pentest$.getValue()))
|
||||
.subscribe({
|
||||
next: (pentest: Pentest) => {
|
||||
this.pentest$.next(pentest);
|
||||
this.initialPentestStatus = pentest.status;
|
||||
this.notificationService.showPopup('pentest.popup.save.success', PopupType.SUCCESS);
|
||||
},
|
||||
error: err => {
|
||||
console.log(err);
|
||||
this.notificationService.showPopup('pentest.popup.save.failed', PopupType.FAILURE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onClickUpdatePentest(): void {
|
||||
this.pentest$.next({...this.pentest$.getValue(), status: this.currentStatus});
|
||||
this.pentestService.updatePentest(transformPentestToRequestBody(this.pentest$.getValue()))
|
||||
.subscribe({
|
||||
next: (pentest: Pentest) => {
|
||||
this.pentest$.next(pentest);
|
||||
this.initialPentestStatus = pentest.status;
|
||||
this.notificationService.showPopup('pentest.popup.update.success', PopupType.SUCCESS);
|
||||
},
|
||||
error: err => {
|
||||
console.log(err);
|
||||
this.notificationService.showPopup('pentest.popup.update.failed', PopupType.FAILURE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if initial pentest Status is different from current pentest status
|
||||
*/
|
||||
pentestStatusChanged(): boolean {
|
||||
if (this.initialPentestStatus !== this.currentStatus) {
|
||||
this.pentestChanged$.next(true);
|
||||
} else {
|
||||
this.pentestChanged$.next(false);
|
||||
}
|
||||
return this.pentestChanged$.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the correct nb-status for current pentest-status
|
||||
*/
|
||||
getPentestFillStatus(value: PentestStatus): string {
|
||||
let pentestFillStatus;
|
||||
switch (value) {
|
||||
case PentestStatus.NOT_STARTED: {
|
||||
pentestFillStatus = 'basic';
|
||||
break;
|
||||
}
|
||||
case PentestStatus.OPEN: {
|
||||
pentestFillStatus = 'info';
|
||||
break;
|
||||
}
|
||||
case PentestStatus.IN_PROGRESS: {
|
||||
pentestFillStatus = 'warning';
|
||||
break;
|
||||
}
|
||||
case PentestStatus.COMPLETED: {
|
||||
pentestFillStatus = 'success';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
pentestFillStatus = 'basic';
|
||||
break;
|
||||
}
|
||||
}
|
||||
return pentestFillStatus;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef class="cell-severity">
|
||||
{{ 'finding.severity' | translate }}
|
||||
</th>
|
||||
<td nbTreeGridCell *nbTreeGridCellDef="let finding" class="cell-severity border-style" fxFill fxLayoutAlign="center center">
|
||||
<td nbTreeGridCell *nbTreeGridCellDef="let finding" class="cell-severity border-style" fxLayoutAlign="center center">
|
||||
<app-severity-tag [currentSeverity]="finding.data['severity']"></app-severity-tag>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
.finding-cell {
|
||||
// Add style here
|
||||
height: 4.5rem !important;
|
||||
max-height: 4.5rem !important;
|
||||
// max-height: 4.5rem !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.finding-cell:hover {
|
||||
|
@ -19,9 +20,15 @@
|
|||
width: 125px;
|
||||
max-width: 125px;
|
||||
// border-style: none;
|
||||
// ToDo: Fix size issue on lower screen resolution
|
||||
height: 4.5rem !important;
|
||||
}
|
||||
|
||||
.cell {
|
||||
height: 4.5rem !important;
|
||||
max-height: 4.5rem !important;
|
||||
}
|
||||
|
||||
.border-style {
|
||||
border-top-style: none;
|
||||
border-left-style: none;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {PentestService} from '@shared/services/pentest.service';
|
||||
import {BehaviorSubject, Observable} from 'rxjs';
|
||||
import {Pentest, transformPentestToRequestBody} from '@shared/models/pentest.model';
|
||||
import {Pentest} from '@shared/models/pentest.model';
|
||||
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||
import {filter, mergeMap, tap} from 'rxjs/operators';
|
||||
import {NotificationService, PopupType} from '@shared/services/notification.service';
|
||||
|
@ -18,6 +18,9 @@ import {isNotNullOrUndefined} from 'codelyzer/util/isNotNullOrUndefined';
|
|||
import {FindingDialogService} from '@shared/modules/finding-dialog/service/finding-dialog.service';
|
||||
import {FindingDialogComponent} from '@shared/modules/finding-dialog/finding-dialog.component';
|
||||
import {PentestStatus} from '@shared/models/pentest-status.model';
|
||||
import {Store} from '@ngxs/store';
|
||||
import {UpdatePentestFindings} from '@shared/stores/project-state/project-state.actions';
|
||||
import {ProjectState} from '@shared/stores/project-state/project-state';
|
||||
|
||||
@UntilDestroy()
|
||||
@Component({
|
||||
|
@ -30,16 +33,17 @@ export class PentestFindingsComponent implements OnInit {
|
|||
constructor(private readonly pentestService: PentestService,
|
||||
private dataSourceBuilder: NbTreeGridDataSourceBuilder<FindingEntry>,
|
||||
private notificationService: NotificationService,
|
||||
private findingDialogService: FindingDialogService) {
|
||||
private findingDialogService: FindingDialogService,
|
||||
private store: Store) {
|
||||
this.dataSource = dataSourceBuilder.create(this.data, this.getters);
|
||||
}
|
||||
|
||||
@Input()
|
||||
pentestInfo$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
|
||||
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
||||
|
||||
// HTML only
|
||||
readonly fa = FA;
|
||||
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
||||
notStartedStatus: PentestStatus = PentestStatus.NOT_STARTED;
|
||||
|
||||
columns: Array<FindingColumns> = [
|
||||
FindingColumns.FINDING_ID, FindingColumns.SEVERITY, FindingColumns.TITLE, FindingColumns.IMPACT, FindingColumns.ACTIONS
|
||||
|
@ -54,23 +58,35 @@ export class PentestFindingsComponent implements OnInit {
|
|||
expandedGetter: (node: FindingEntry) => !!node.expanded,
|
||||
};
|
||||
|
||||
// HTML only
|
||||
notStartedStatus: PentestStatus = PentestStatus.NOT_STARTED;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadFindingsData();
|
||||
this.store.select(ProjectState.pentest).pipe(
|
||||
untilDestroyed(this)
|
||||
).subscribe({
|
||||
next: (selectedPentest: Pentest) => {
|
||||
this.pentestInfo$.next(selectedPentest);
|
||||
this.loadFindingsData();
|
||||
},
|
||||
error: err => {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadFindingsData(): void {
|
||||
this.pentestService.getFindingsByPentestId(this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '')
|
||||
.pipe(
|
||||
untilDestroyed(this),
|
||||
filter(isNotNullOrUndefined),
|
||||
/*filter(isNotNullOrUndefined),*/
|
||||
tap(() => this.loading$.next(true))
|
||||
)
|
||||
.subscribe({
|
||||
next: (findings: Finding[]) => {
|
||||
this.data = transformFindingsToObjectiveEntries(findings);
|
||||
// ToDo: Handle this case before in pipe
|
||||
if (findings) {
|
||||
this.data = transformFindingsToObjectiveEntries(findings);
|
||||
} else {
|
||||
this.data = [];
|
||||
}
|
||||
this.dataSource.setData(this.data, this.getters);
|
||||
this.loading$.next(false);
|
||||
},
|
||||
|
@ -104,8 +120,8 @@ export class PentestFindingsComponent implements OnInit {
|
|||
),
|
||||
untilDestroyed(this)
|
||||
).subscribe({
|
||||
next: () => {
|
||||
// ToDo: Parse new Counter to overview / -> dispatch to store maybe already update it
|
||||
next: (finding) => {
|
||||
this.store.dispatch(new UpdatePentestFindings(finding.id));
|
||||
this.loadFindingsData();
|
||||
this.notificationService.showPopup('finding.popup.save.success', PopupType.SUCCESS);
|
||||
},
|
||||
|
|
|
@ -9,14 +9,41 @@ import {ThemeModule} from '@assets/@theme/theme.module';
|
|||
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
|
||||
import {HttpLoaderFactory} from '../../../common-app.module';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {NgxsModule} from '@ngxs/store';
|
||||
import {ProjectState} from '@shared/stores/project-state/project-state';
|
||||
import {NgxsModule, Store} from '@ngxs/store';
|
||||
import {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/stores/project-state/project-state';
|
||||
import {Category} from '@shared/models/category.model';
|
||||
import {PentestStatus} from '@shared/models/pentest-status.model';
|
||||
|
||||
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||
selectedProject: {
|
||||
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
||||
client: 'E Corp',
|
||||
title: 'Some Mock API (v1.0) Scanning',
|
||||
createdAt: new Date('2019-01-10T09:00:00'),
|
||||
tester: 'Novatester',
|
||||
testingProgress: 0,
|
||||
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
|
||||
},
|
||||
// Manages Categories
|
||||
disabledCategories: [],
|
||||
selectedCategory: Category.INFORMATION_GATHERING,
|
||||
// Manages Pentests of Category
|
||||
disabledPentests: [],
|
||||
selectedPentest: {
|
||||
id: '56c47c56-3bcd-45f1-a05b-c197dbd33112',
|
||||
category: Category.INFORMATION_GATHERING,
|
||||
refNumber: 'OTF-001',
|
||||
childEntries: [],
|
||||
status: PentestStatus.NOT_STARTED,
|
||||
findingIds: ['56c47c56-3bcd-45f1-a05b-c197dbd33112'],
|
||||
commentIds: []
|
||||
},
|
||||
};
|
||||
|
||||
describe('PentestInfoComponent', () => {
|
||||
let component: PentestInfoComponent;
|
||||
let fixture: ComponentFixture<PentestInfoComponent>;
|
||||
let store: Store;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
|
@ -44,6 +71,11 @@ describe('PentestInfoComponent', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(PentestInfoComponent);
|
||||
store = TestBed.inject(Store);
|
||||
store.reset({
|
||||
...store.snapshot(),
|
||||
[PROJECT_STATE_NAME]: DESIRED_PROJECT_STATE_SESSION
|
||||
});
|
||||
component = fixture.componentInstance;
|
||||
component.pentestInfo$.next({
|
||||
id: '56c47c56-3bcd-45f1-a05b-c197dbd33112',
|
||||
|
|
|
@ -3,7 +3,11 @@ import {BehaviorSubject} from 'rxjs';
|
|||
import {Pentest} from '@shared/models/pentest.model';
|
||||
import {getPentestInfoForObjective} from '@shared/functions/infos/get-pentest-info-for-objective';
|
||||
import {getTitleKeyForRefNumber} from '@shared/functions/categories/get-title-key-for-ref-number.function';
|
||||
import {ProjectState} from '@shared/stores/project-state/project-state';
|
||||
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||
import {Store} from '@ngxs/store';
|
||||
|
||||
@UntilDestroy()
|
||||
@Component({
|
||||
selector: 'app-pentest-info',
|
||||
templateUrl: './pentest-info.component.html',
|
||||
|
@ -11,12 +15,21 @@ import {getTitleKeyForRefNumber} from '@shared/functions/categories/get-title-ke
|
|||
})
|
||||
export class PentestInfoComponent implements OnInit {
|
||||
|
||||
@Input()
|
||||
pentestInfo$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
|
||||
|
||||
constructor() { }
|
||||
constructor(private store: Store) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.store.selectOnce(ProjectState.pentest).pipe(
|
||||
untilDestroyed(this)
|
||||
).subscribe({
|
||||
next: (selectedPentest: Pentest) => {
|
||||
this.pentestInfo$.next(selectedPentest);
|
||||
},
|
||||
error: err => {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getPentestHeaderForObjective(refNumber: string): string {
|
||||
|
|
|
@ -14,7 +14,41 @@
|
|||
<h4>{{selectedProjectTitle$.getValue()}} / {{pentest$.getValue().refNumber}}</h4>
|
||||
|
||||
<div class="pentest-status-container">
|
||||
<app-status-tag [currentStatus]="pentest$.getValue().status"></app-status-tag>
|
||||
<!--ToDo: Add changing dialog here-->
|
||||
<!--<app-status-tag [currentStatus]="pentest$.getValue().status"></app-status-tag>-->
|
||||
|
||||
<!-- Pentest Status Selection -->
|
||||
<div class="pentest-status-dialog">
|
||||
<nb-select class="status"
|
||||
[(selected)]="currentStatus"
|
||||
shape="round" filled
|
||||
status="{{getPentestFillStatus(currentStatus)}}">
|
||||
<nb-option *ngFor="let status of statusTexts" [value]="status.value">
|
||||
{{ status.translationText | translate }}
|
||||
</nb-option>
|
||||
</nb-select>
|
||||
</div>
|
||||
<div *ngIf="!pentest$.getValue().id; else updatePentest">
|
||||
<button nbButton
|
||||
class="save-pentest-button"
|
||||
status="primary"
|
||||
[disabled]="!pentestStatusChanged()"
|
||||
title="{{ 'global.action.save' | translate }}"
|
||||
(click)="onClickSavePentest()">
|
||||
<span class="exit-element-text"> {{ 'global.action.save' | translate }} </span>
|
||||
</button>
|
||||
</div>
|
||||
<ng-template #updatePentest>
|
||||
<button nbButton
|
||||
class="save-pentest-button"
|
||||
status="primary"
|
||||
[disabled]="!pentestStatusChanged()"
|
||||
title="{{ 'global.action.update' | translate }}"
|
||||
(click)="onClickUpdatePentest()">
|
||||
<span class="exit-element-text"> {{ 'global.action.update' | translate }} </span>
|
||||
</button>
|
||||
</ng-template>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
.exit-button-container {
|
||||
.exit-element-icon {
|
||||
}
|
||||
|
||||
.exit-element-text {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
@ -12,6 +13,35 @@
|
|||
.pentest-status-container {
|
||||
display: flex;
|
||||
align-content: flex-end;
|
||||
margin-right: 0.5rem;
|
||||
// margin-right: 0.5rem;
|
||||
// height: 5%;
|
||||
|
||||
.pentest-status-dialog {
|
||||
margin: 1rem 2.25rem 1rem 0;
|
||||
|
||||
.status {
|
||||
width: 12rem;
|
||||
}
|
||||
|
||||
.basic {
|
||||
background-color: nb-theme(color-basic-default);
|
||||
}
|
||||
|
||||
.info {
|
||||
background-color: nb-theme(color-info-default);
|
||||
}
|
||||
|
||||
.warning {
|
||||
background-color: nb-theme(color-warning-default);
|
||||
}
|
||||
|
||||
.success {
|
||||
background-color: nb-theme(color-success-default);
|
||||
}
|
||||
}
|
||||
|
||||
.save-pentest-button {
|
||||
margin: 1rem 0 1rem 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,8 +11,10 @@ import {NgxsModule, Store} from '@ngxs/store';
|
|||
import {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/stores/project-state/project-state';
|
||||
import {Category} from '@shared/models/category.model';
|
||||
import {PentestStatus} from '@shared/models/pentest-status.model';
|
||||
import {NotificationService} from '@shared/services/notification.service';
|
||||
import {NotificationServiceMock} from '@shared/services/notification.service.mock';
|
||||
|
||||
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||
selectedProject: {
|
||||
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
||||
client: 'E Corp',
|
||||
|
@ -58,6 +60,9 @@ describe('PentestHeaderComponent', () => {
|
|||
}),
|
||||
RouterTestingModule.withRoutes([]),
|
||||
NgxsModule.forRoot([ProjectState])
|
||||
],
|
||||
providers: [
|
||||
{provide: NotificationService, useValue: new NotificationServiceMock()},
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
|
|
|
@ -6,10 +6,13 @@ import {Store} from '@ngxs/store';
|
|||
import {Router} from '@angular/router';
|
||||
import {ChangePentest} from '@shared/stores/project-state/project-state.actions';
|
||||
import {BehaviorSubject} from 'rxjs';
|
||||
import {PentestStatus} from '@shared/models/pentest-status.model';
|
||||
import {ProjectState} from '@shared/stores/project-state/project-state';
|
||||
import {Project} from '@shared/models/project.model';
|
||||
import {Pentest} from '@shared/models/pentest.model';
|
||||
import {Pentest, transformPentestToRequestBody} from '@shared/models/pentest.model';
|
||||
import {NotificationService, PopupType} from '@shared/services/notification.service';
|
||||
import {PentestStatus} from '@shared/models/pentest-status.model';
|
||||
import {PentestService} from '@shared/services/pentest.service';
|
||||
import {StatusText} from '@shared/widgets/status-tag/status-tag.component';
|
||||
|
||||
@UntilDestroy()
|
||||
@Component({
|
||||
|
@ -23,8 +26,27 @@ export class PentestHeaderComponent implements OnInit {
|
|||
|
||||
pentest$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
|
||||
selectedProjectTitle$: BehaviorSubject<string> = new BehaviorSubject<string>('');
|
||||
pentestChanged$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
|
||||
// Pentest Status Handler
|
||||
status = PentestStatus;
|
||||
currentStatus: PentestStatus = PentestStatus.NOT_STARTED;
|
||||
private initialPentestStatus: PentestStatus;
|
||||
// Status Text Translation Texts
|
||||
readonly statusTexts: Array<StatusText> = [
|
||||
{value: PentestStatus.NOT_STARTED, translationText: 'pentest.statusText.not_started'},
|
||||
/* ToDo: Disabled not needed inside pentest */
|
||||
/*{value: PentestStatus.DISABLED, translationText: 'pentest.statusText.disabled'},*/
|
||||
{value: PentestStatus.OPEN, translationText: 'pentest.statusText.open'},
|
||||
{value: PentestStatus.IN_PROGRESS, translationText: 'pentest.statusText.in_progress'},
|
||||
{value: PentestStatus.COMPLETED, translationText: 'pentest.statusText.completed'}
|
||||
];
|
||||
|
||||
selectedProjectId$: BehaviorSubject<string> = new BehaviorSubject<string>('');
|
||||
|
||||
constructor(private store: Store,
|
||||
private pentestService: PentestService,
|
||||
private notificationService: NotificationService,
|
||||
private readonly router: Router) {
|
||||
}
|
||||
|
||||
|
@ -33,6 +55,7 @@ export class PentestHeaderComponent implements OnInit {
|
|||
untilDestroyed(this)
|
||||
).subscribe({
|
||||
next: (selectedProject: Project) => {
|
||||
this.selectedProjectId$.next(selectedProject.id);
|
||||
this.selectedProjectTitle$.next(selectedProject?.title);
|
||||
},
|
||||
error: err => {
|
||||
|
@ -44,6 +67,8 @@ export class PentestHeaderComponent implements OnInit {
|
|||
untilDestroyed(this)
|
||||
).subscribe({
|
||||
next: (selectedPentest: Pentest) => {
|
||||
this.currentStatus = selectedPentest.status;
|
||||
this.initialPentestStatus = selectedPentest.status;
|
||||
this.pentest$.next(selectedPentest);
|
||||
},
|
||||
error: err => {
|
||||
|
@ -61,4 +86,76 @@ export class PentestHeaderComponent implements OnInit {
|
|||
}
|
||||
).finally();
|
||||
}
|
||||
|
||||
onClickSavePentest(): void {
|
||||
this.pentest$.next({...this.pentest$.getValue(), status: this.currentStatus});
|
||||
this.pentestService.savePentest(this.selectedProjectId$.getValue(), transformPentestToRequestBody(this.pentest$.getValue()))
|
||||
.subscribe({
|
||||
next: (pentest: Pentest) => {
|
||||
this.store.dispatch(new ChangePentest(pentest));
|
||||
this.notificationService.showPopup('pentest.popup.save.success', PopupType.SUCCESS);
|
||||
},
|
||||
error: err => {
|
||||
console.log(err);
|
||||
this.notificationService.showPopup('pentest.popup.save.failed', PopupType.FAILURE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onClickUpdatePentest(): void {
|
||||
this.pentest$.next({...this.pentest$.getValue(), status: this.currentStatus});
|
||||
this.pentestService.updatePentest(transformPentestToRequestBody(this.pentest$.getValue()))
|
||||
.subscribe({
|
||||
next: (pentest: Pentest) => {
|
||||
this.store.dispatch(new ChangePentest(pentest));
|
||||
this.notificationService.showPopup('pentest.popup.update.success', PopupType.SUCCESS);
|
||||
},
|
||||
error: err => {
|
||||
console.log(err);
|
||||
this.notificationService.showPopup('pentest.popup.update.failed', PopupType.FAILURE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if initial pentest Status is different from current pentest status
|
||||
*/
|
||||
pentestStatusChanged(): boolean {
|
||||
if (this.initialPentestStatus !== this.currentStatus) {
|
||||
this.pentestChanged$.next(true);
|
||||
} else {
|
||||
this.pentestChanged$.next(false);
|
||||
}
|
||||
return this.pentestChanged$.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the correct nb-status for current pentest-status
|
||||
*/
|
||||
getPentestFillStatus(value: PentestStatus): string {
|
||||
let pentestFillStatus;
|
||||
switch (value) {
|
||||
case PentestStatus.NOT_STARTED: {
|
||||
pentestFillStatus = 'basic';
|
||||
break;
|
||||
}
|
||||
case PentestStatus.OPEN: {
|
||||
pentestFillStatus = 'info';
|
||||
break;
|
||||
}
|
||||
case PentestStatus.IN_PROGRESS: {
|
||||
pentestFillStatus = 'warning';
|
||||
break;
|
||||
}
|
||||
case PentestStatus.COMPLETED: {
|
||||
pentestFillStatus = 'success';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
pentestFillStatus = 'basic';
|
||||
break;
|
||||
}
|
||||
}
|
||||
return pentestFillStatus;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,7 @@ import {Store} from '@ngxs/store';
|
|||
import {ProjectState} from '@shared/stores/project-state/project-state';
|
||||
import {catchError, map, switchMap} from 'rxjs/operators';
|
||||
import {getTempPentestsForCategory} from '@shared/functions/categories/get-temp-pentests-for-category.function';
|
||||
import {Finding, FindingDialogBody} from '@shared/models/finding.model';
|
||||
import {Severity} from '@shared/models/severity.enum';
|
||||
import {Finding} from '@shared/models/finding.model';
|
||||
import {Comment} from '@shared/models/comment.model';
|
||||
import {v4 as UUID} from 'uuid';
|
||||
|
||||
|
@ -85,46 +84,43 @@ export class PentestService {
|
|||
* @param pentestId the id of the project
|
||||
*/
|
||||
public getFindingsByPentestId(pentestId: string): Observable<Finding[]> {
|
||||
if (pentestId) {
|
||||
return this.http.get<Finding[]>(`${this.apiBaseURL}/${pentestId}/findings`);
|
||||
} else {
|
||||
// return of([]);
|
||||
// Todo: Remove mocked Findings
|
||||
return of([
|
||||
{
|
||||
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
|
||||
title: 'This is a creative title',
|
||||
description: 'test',
|
||||
impact: 'This impacts only the UI',
|
||||
severity: Severity.LOW,
|
||||
reproduction: ''
|
||||
},
|
||||
{
|
||||
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
|
||||
title: 'This is a creative title',
|
||||
description: 'test',
|
||||
impact: 'This is impacts some things',
|
||||
severity: Severity.MEDIUM,
|
||||
reproduction: ''
|
||||
},
|
||||
{
|
||||
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
|
||||
title: 'This is a creative title',
|
||||
description: 'test',
|
||||
impact: 'This is impacts a lot',
|
||||
severity: Severity.HIGH,
|
||||
reproduction: ''
|
||||
},
|
||||
{
|
||||
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
|
||||
title: 'This is a creative title',
|
||||
description: 'test',
|
||||
impact: 'This is impacts a lot',
|
||||
severity: Severity.CRITICAL,
|
||||
reproduction: ''
|
||||
}
|
||||
]);
|
||||
}
|
||||
return this.http.get<Finding[]>(`${this.apiBaseURL}/${pentestId}/findings`);
|
||||
// return of([]);
|
||||
/*Todo: Remove mocked Findings?
|
||||
return of([
|
||||
{
|
||||
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
|
||||
title: 'This is a creative title',
|
||||
description: 'test',
|
||||
impact: 'This impacts only the UI',
|
||||
severity: Severity.LOW,
|
||||
reproduction: ''
|
||||
},
|
||||
{
|
||||
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
|
||||
title: 'This is a creative title',
|
||||
description: 'test',
|
||||
impact: 'This is impacts some things',
|
||||
severity: Severity.MEDIUM,
|
||||
reproduction: ''
|
||||
},
|
||||
{
|
||||
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
|
||||
title: 'This is a creative title',
|
||||
description: 'test',
|
||||
impact: 'This is impacts a lot',
|
||||
severity: Severity.HIGH,
|
||||
reproduction: ''
|
||||
},
|
||||
{
|
||||
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
|
||||
title: 'This is a creative title',
|
||||
description: 'test',
|
||||
impact: 'This is impacts a lot',
|
||||
severity: Severity.CRITICAL,
|
||||
reproduction: ''
|
||||
}
|
||||
]);*/
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -141,26 +137,22 @@ export class PentestService {
|
|||
* @param pentestId the id of the project
|
||||
*/
|
||||
public getCommentsByPentestId(pentestId: string): Observable<Comment[]> {
|
||||
console.warn('Comments for:', pentestId);
|
||||
if (pentestId) {
|
||||
return this.http.get<Comment[]>(`${this.apiBaseURL}/${pentestId}/comments`);
|
||||
} else {
|
||||
// return of([]);
|
||||
// Todo: Remove mocked Comments
|
||||
return of([
|
||||
{
|
||||
id: 'ca96cc19-88ff-4874-8406-dc892620afd2',
|
||||
title: 'This is a creative title',
|
||||
description: 'This is a creative description',
|
||||
relatedFindings: ['ca96cc19-88ff-4874-8406-dc892620afd4'],
|
||||
},
|
||||
{
|
||||
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
|
||||
title: 'This is a creative title',
|
||||
description: 'This is a creative description',
|
||||
relatedFindings: [],
|
||||
}
|
||||
]);
|
||||
}
|
||||
return this.http.get<Comment[]>(`${this.apiBaseURL}/${pentestId}/comments`);
|
||||
// return of([]);
|
||||
/* ToDo: Use mocked Comments?
|
||||
return of([
|
||||
{
|
||||
id: 'ca96cc19-88ff-4874-8406-dc892620afd2',
|
||||
title: 'This is a creative title',
|
||||
description: 'This is a creative description',
|
||||
relatedFindings: ['ca96cc19-88ff-4874-8406-dc892620afd4'],
|
||||
},
|
||||
{
|
||||
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
|
||||
title: 'This is a creative title',
|
||||
description: 'This is a creative description',
|
||||
relatedFindings: [],
|
||||
}
|
||||
]);*/
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,3 +33,10 @@ export class ChangePentest {
|
|||
constructor(public pentest: Pentest) {
|
||||
}
|
||||
}
|
||||
|
||||
export class UpdatePentestFindings {
|
||||
static readonly type = '[ProjectState] UpdatePentestFindings';
|
||||
|
||||
constructor(public findingId: string) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
import {Action, Selector, State, StateContext} from '@ngxs/store';
|
||||
import {Injectable} from '@angular/core';
|
||||
import {Project} from '@shared/models/project.model';
|
||||
import {ChangeCategory, ChangePentest, ChangeProject, InitProjectState} from '@shared/stores/project-state/project-state.actions';
|
||||
import {
|
||||
ChangeCategory,
|
||||
ChangePentest,
|
||||
ChangeProject,
|
||||
InitProjectState,
|
||||
UpdatePentestFindings
|
||||
} from '@shared/stores/project-state/project-state.actions';
|
||||
import {Category} from '@shared/models/category.model';
|
||||
import {Pentest} from '@shared/models/pentest.model';
|
||||
|
||||
|
@ -80,4 +86,26 @@ export class ProjectState {
|
|||
selectedPentest: {...pentest, projectId: state.selectedProject.id}
|
||||
});
|
||||
}
|
||||
|
||||
@Action(UpdatePentestFindings)
|
||||
updatePentestFindings(ctx: StateContext<ProjectStateModel>, {findingId}: UpdatePentestFindings): void {
|
||||
const state = ctx.getState();
|
||||
let stateSelectedPentest: Pentest = state.selectedPentest;
|
||||
const stateFindingIds: Array<string> = stateSelectedPentest.findingIds || [];
|
||||
let updatedFindingIds: Array<string> = [];
|
||||
if (!stateFindingIds.includes(findingId)) {
|
||||
updatedFindingIds = [...stateFindingIds, findingId];
|
||||
} else {
|
||||
// ToDo: Add logic to remove findingId from array
|
||||
}
|
||||
// overwrites only findingIds
|
||||
stateSelectedPentest = {
|
||||
...stateSelectedPentest,
|
||||
findingIds: updatedFindingIds
|
||||
};
|
||||
// path project state
|
||||
ctx.patchState({
|
||||
selectedPentest: stateSelectedPentest
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ class ProjectController(private val projectService: ProjectService) {
|
|||
|
||||
@DeleteMapping("/{id}")
|
||||
fun deleteProject(@PathVariable(value = "id") id: String): Mono<ResponseEntity<ResponseBody>> {
|
||||
// ToDo: Delete all associated Pentests, Findings and Comments
|
||||
return this.projectService.deleteProject(id).map{
|
||||
ResponseEntity.ok().body(it.toProjectDeleteResponseBody())
|
||||
}.switchIfEmpty {
|
||||
|
|
Loading…
Reference in New Issue