fix: Bugfixes and QoL Improvements
This commit is contained in:
parent
e9aec4ec3e
commit
a4536e9735
|
@ -11,6 +11,15 @@
|
||||||
<div class="filler"></div>
|
<div class="filler"></div>
|
||||||
<div fxLayoutGap="4rem">
|
<div fxLayoutGap="4rem">
|
||||||
<nb-actions size="medium">
|
<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">
|
<nb-action class="toggle-theme">
|
||||||
<button nbButton
|
<button nbButton
|
||||||
(click)="onClickSwitchTheme()">
|
(click)="onClickSwitchTheme()">
|
||||||
|
|
|
@ -8,6 +8,10 @@
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.owasp-redirect-button {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.languageContainer {
|
.languageContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
max-width: 8rem;
|
max-width: 8rem;
|
||||||
|
|
|
@ -33,6 +33,11 @@ export class HeaderComponent implements OnInit{
|
||||||
this.selectedLanguage = this.translateService.currentLang;
|
this.selectedLanguage = this.translateService.currentLang;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTML only
|
||||||
|
onClickGoToLink(url: string): void {
|
||||||
|
window.open(url, '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
onClickSwitchTheme(): void {
|
onClickSwitchTheme(): void {
|
||||||
if (this.currentTheme === 'corporate') {
|
if (this.currentTheme === 'corporate') {
|
||||||
this.themeService.changeTheme('dark');
|
this.themeService.changeTheme('dark');
|
||||||
|
|
|
@ -9,7 +9,8 @@
|
||||||
.export-button-container {
|
.export-button-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-content: flex-end;
|
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 {
|
.export-element-icon {
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,8 +76,24 @@ export class ObjectiveTableComponent implements OnInit {
|
||||||
})
|
})
|
||||||
).finally();
|
).finally();
|
||||||
*/
|
*/
|
||||||
const statePentest = this.pentests$.getValue().find(pentest => pentest.refNumber === selectedPentest.refNumber);
|
const statePentest: Pentest = this.pentests$.getValue().find(pentest => pentest.refNumber === selectedPentest.refNumber);
|
||||||
this.store.dispatch(new ChangePentest(statePentest));
|
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
|
// HTML only
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
size="small"
|
size="small"
|
||||||
shape="round"
|
shape="round"
|
||||||
class="add-comment-button"
|
class="add-comment-button"
|
||||||
|
[disabled]="pentestInfo$.getValue().status === notStartedStatus"
|
||||||
(click)="onClickAddComment()">
|
(click)="onClickAddComment()">
|
||||||
<fa-icon [icon]="fa.faPlus" class="new-comment-icon"></fa-icon>
|
<fa-icon [icon]="fa.faPlus" class="new-comment-icon"></fa-icon>
|
||||||
{{'comment.add' | translate}}
|
{{'comment.add' | translate}}
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
|
|
||||||
.comment-cell {
|
.comment-cell {
|
||||||
// Add style here
|
// Add style here
|
||||||
|
height: 4.5rem !important;
|
||||||
|
max-height: 4.5rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-cell:hover {
|
.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 {BehaviorSubject, Observable} from 'rxjs';
|
||||||
import {Pentest} from '@shared/models/pentest.model';
|
import {Pentest} from '@shared/models/pentest.model';
|
||||||
import * as FA from '@fortawesome/free-solid-svg-icons';
|
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 {filter, tap} from 'rxjs/operators';
|
||||||
import {Comment, CommentEntry, transformCommentsToObjectiveEntries} from '@shared/models/comment.model';
|
import {Comment, CommentEntry, transformCommentsToObjectiveEntries} 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 {Store} from '@ngxs/store';
|
||||||
|
import {PentestStatus} from '@shared/models/pentest-status.model';
|
||||||
|
|
||||||
@UntilDestroy()
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -18,11 +21,11 @@ import {isNotNullOrUndefined} from 'codelyzer/util/isNotNullOrUndefined';
|
||||||
})
|
})
|
||||||
export class PentestCommentsComponent implements OnInit {
|
export class PentestCommentsComponent implements OnInit {
|
||||||
|
|
||||||
@Input()
|
|
||||||
pentestInfo$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
|
|
||||||
|
|
||||||
// HTML only
|
// HTML only
|
||||||
readonly fa = FA;
|
readonly fa = FA;
|
||||||
|
notStartedStatus: PentestStatus = PentestStatus.NOT_STARTED;
|
||||||
|
|
||||||
|
pentestInfo$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
|
||||||
// comments$: BehaviorSubject<Comment[]> = new BehaviorSubject<Comment[]>(null);
|
// comments$: BehaviorSubject<Comment[]> = new BehaviorSubject<Comment[]>(null);
|
||||||
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
||||||
|
|
||||||
|
@ -41,24 +44,39 @@ export class PentestCommentsComponent implements OnInit {
|
||||||
|
|
||||||
constructor(private readonly pentestService: PentestService,
|
constructor(private readonly pentestService: PentestService,
|
||||||
private dataSourceBuilder: NbTreeGridDataSourceBuilder<CommentEntry>,
|
private dataSourceBuilder: NbTreeGridDataSourceBuilder<CommentEntry>,
|
||||||
|
private store: Store,
|
||||||
private notificationService: NotificationService) {
|
private notificationService: NotificationService) {
|
||||||
this.dataSource = dataSourceBuilder.create(this.data, this.getters);
|
this.dataSource = dataSourceBuilder.create(this.data, this.getters);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
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 {
|
loadCommentsData(): void {
|
||||||
this.pentestService.getCommentsByPentestId(this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '')
|
this.pentestService.getCommentsByPentestId(this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '')
|
||||||
.pipe(
|
.pipe(
|
||||||
untilDestroyed(this),
|
untilDestroyed(this),
|
||||||
filter(isNotNullOrUndefined),
|
/*filter(isNotNullOrUndefined),*/
|
||||||
tap(() => this.loading$.next(true))
|
tap(() => this.loading$.next(true))
|
||||||
)
|
)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (comments: Comment[]) => {
|
next: (comments: Comment[]) => {
|
||||||
this.data = transformCommentsToObjectiveEntries(comments);
|
if (comments) {
|
||||||
|
this.data = transformCommentsToObjectiveEntries(comments);
|
||||||
|
} else {
|
||||||
|
this.data = [];
|
||||||
|
}
|
||||||
this.dataSource.setData(this.data, this.getters);
|
this.dataSource.setData(this.data, this.getters);
|
||||||
this.loading$.next(false);
|
this.loading$.next(false);
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,52 +2,21 @@
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<nb-tabset>
|
<nb-tabset>
|
||||||
<nb-tab class="pentest-tabset" tabTitle="{{ 'global.action.info' | translate }}">
|
<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>
|
||||||
<nb-tab class="pentest-tabset" tabTitle="{{ 'pentest.findings' | translate }}"
|
<nb-tab class="pentest-tabset" tabTitle="{{ 'pentest.findings' | translate }}"
|
||||||
badgeText="{{currentNumberOfFindings$.getValue()}}" badgeStatus="danger">
|
badgeText="{{currentNumberOfFindings$.getValue()}}" badgeStatus="danger">
|
||||||
<app-pentest-findings [pentestInfo$]=pentest$></app-pentest-findings>
|
<app-pentest-findings></app-pentest-findings>
|
||||||
</nb-tab>
|
</nb-tab>
|
||||||
<nb-tab class="pentest-tabset" tabTitle="{{ 'pentest.comments' | translate }}"
|
<nb-tab class="pentest-tabset" tabTitle="{{ 'pentest.comments' | translate }}"
|
||||||
badgeText="{{currentNumberOfComments$.getValue()}}" badgeStatus="info">
|
badgeText="{{currentNumberOfComments$.getValue()}}" badgeStatus="info">
|
||||||
<app-pentest-comments [pentestInfo$]=pentest$></app-pentest-comments>
|
<app-pentest-comments></app-pentest-comments>
|
||||||
</nb-tab>
|
</nb-tab>
|
||||||
</nb-tabset>
|
</nb-tabset>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div fxLayoutAlign="end end" class="content-footer">
|
<div fxLayoutAlign="end end" class="content-footer">
|
||||||
<!-- Pentest Status Selection -->
|
<!--ToDo: Use to put element in bottom-right corner -->
|
||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -10,33 +10,9 @@
|
||||||
/*nb-tab {
|
/*nb-tab {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
}*/
|
}*/
|
||||||
}
|
|
||||||
|
|
||||||
.content-footer {
|
.content-footer {
|
||||||
height: 5%;
|
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 6rem 1rem 0;
|
margin: 1rem 6rem 1rem 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,12 @@
|
||||||
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 {BehaviorSubject, Observable} from 'rxjs';
|
import {BehaviorSubject} from 'rxjs';
|
||||||
import {Select, Store} from '@ngxs/store';
|
import {Store} from '@ngxs/store';
|
||||||
import {ProjectState} from '@shared/stores/project-state/project-state';
|
import {ProjectState} from '@shared/stores/project-state/project-state';
|
||||||
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||||
import {Pentest, transformPentestToRequestBody} from '@shared/models/pentest.model';
|
import {Pentest} from '@shared/models/pentest.model';
|
||||||
import {PentestStatus} from '@shared/models/pentest-status.model';
|
|
||||||
import {StatusText} from '@shared/widgets/status-tag/status-tag.component';
|
|
||||||
import {PentestService} from '@shared/services/pentest.service';
|
import {PentestService} from '@shared/services/pentest.service';
|
||||||
import {NotificationService, PopupType} from '@shared/services/notification.service';
|
import {NotificationService} from '@shared/services/notification.service';
|
||||||
import {Project} from '@shared/models/project.model';
|
|
||||||
import {isNotNullOrUndefined} from 'codelyzer/util/isNotNullOrUndefined';
|
|
||||||
import {filter} from 'rxjs/operators';
|
|
||||||
|
|
||||||
@UntilDestroy()
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -23,28 +18,10 @@ export class PentestContentComponent implements OnInit {
|
||||||
// HTML only
|
// HTML only
|
||||||
readonly fa = FA;
|
readonly fa = FA;
|
||||||
|
|
||||||
@Select(ProjectState.project)
|
|
||||||
selectedProject$: Observable<Project>;
|
|
||||||
selectedProjectId: string;
|
|
||||||
|
|
||||||
pentest$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
|
pentest$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
|
||||||
pentestChanged$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
|
||||||
currentNumberOfFindings$: BehaviorSubject<number> = new BehaviorSubject<number>(0);
|
currentNumberOfFindings$: BehaviorSubject<number> = new BehaviorSubject<number>(0);
|
||||||
currentNumberOfComments$: 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(
|
constructor(
|
||||||
private readonly pentestService: PentestService,
|
private readonly pentestService: PentestService,
|
||||||
private notificationService: NotificationService,
|
private notificationService: NotificationService,
|
||||||
|
@ -52,26 +29,11 @@ export class PentestContentComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
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(
|
this.store.select(ProjectState.pentest).pipe(
|
||||||
untilDestroyed(this)
|
untilDestroyed(this)
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: (selectedPentest: Pentest) => {
|
next: (selectedPentest: Pentest) => {
|
||||||
console.warn(selectedPentest);
|
|
||||||
this.pentest$.next(selectedPentest);
|
this.pentest$.next(selectedPentest);
|
||||||
this.currentStatus = selectedPentest.status;
|
|
||||||
this.initialPentestStatus = selectedPentest.status;
|
|
||||||
const findings = selectedPentest.findingIds ? selectedPentest.findingIds.length : 0;
|
const findings = selectedPentest.findingIds ? selectedPentest.findingIds.length : 0;
|
||||||
this.currentNumberOfFindings$.next(findings);
|
this.currentNumberOfFindings$.next(findings);
|
||||||
const comments = selectedPentest.commentIds ? selectedPentest.commentIds.length : 0;
|
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">
|
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef class="cell-severity">
|
||||||
{{ 'finding.severity' | translate }}
|
{{ 'finding.severity' | translate }}
|
||||||
</th>
|
</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>
|
<app-severity-tag [currentSeverity]="finding.data['severity']"></app-severity-tag>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
.finding-cell {
|
.finding-cell {
|
||||||
// Add style here
|
// Add style here
|
||||||
height: 4.5rem !important;
|
height: 4.5rem !important;
|
||||||
max-height: 4.5rem !important;
|
// max-height: 4.5rem !important;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.finding-cell:hover {
|
.finding-cell:hover {
|
||||||
|
@ -19,9 +20,15 @@
|
||||||
width: 125px;
|
width: 125px;
|
||||||
max-width: 125px;
|
max-width: 125px;
|
||||||
// border-style: none;
|
// border-style: none;
|
||||||
|
// ToDo: Fix size issue on lower screen resolution
|
||||||
height: 4.5rem !important;
|
height: 4.5rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cell {
|
||||||
|
height: 4.5rem !important;
|
||||||
|
max-height: 4.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
.border-style {
|
.border-style {
|
||||||
border-top-style: none;
|
border-top-style: none;
|
||||||
border-left-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 {PentestService} from '@shared/services/pentest.service';
|
||||||
import {BehaviorSubject, Observable} from 'rxjs';
|
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 {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||||
import {filter, mergeMap, tap} from 'rxjs/operators';
|
import {filter, mergeMap, tap} from 'rxjs/operators';
|
||||||
import {NotificationService, PopupType} from '@shared/services/notification.service';
|
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 {FindingDialogService} from '@shared/modules/finding-dialog/service/finding-dialog.service';
|
||||||
import {FindingDialogComponent} from '@shared/modules/finding-dialog/finding-dialog.component';
|
import {FindingDialogComponent} from '@shared/modules/finding-dialog/finding-dialog.component';
|
||||||
import {PentestStatus} from '@shared/models/pentest-status.model';
|
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()
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -30,16 +33,17 @@ export class PentestFindingsComponent implements OnInit {
|
||||||
constructor(private readonly pentestService: PentestService,
|
constructor(private readonly pentestService: PentestService,
|
||||||
private dataSourceBuilder: NbTreeGridDataSourceBuilder<FindingEntry>,
|
private dataSourceBuilder: NbTreeGridDataSourceBuilder<FindingEntry>,
|
||||||
private notificationService: NotificationService,
|
private notificationService: NotificationService,
|
||||||
private findingDialogService: FindingDialogService) {
|
private findingDialogService: FindingDialogService,
|
||||||
|
private store: Store) {
|
||||||
this.dataSource = dataSourceBuilder.create(this.data, this.getters);
|
this.dataSource = dataSourceBuilder.create(this.data, this.getters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Input()
|
|
||||||
pentestInfo$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
|
pentestInfo$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
|
||||||
|
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
||||||
|
|
||||||
// HTML only
|
// HTML only
|
||||||
readonly fa = FA;
|
readonly fa = FA;
|
||||||
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
notStartedStatus: PentestStatus = PentestStatus.NOT_STARTED;
|
||||||
|
|
||||||
columns: Array<FindingColumns> = [
|
columns: Array<FindingColumns> = [
|
||||||
FindingColumns.FINDING_ID, FindingColumns.SEVERITY, FindingColumns.TITLE, FindingColumns.IMPACT, FindingColumns.ACTIONS
|
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,
|
expandedGetter: (node: FindingEntry) => !!node.expanded,
|
||||||
};
|
};
|
||||||
|
|
||||||
// HTML only
|
|
||||||
notStartedStatus: PentestStatus = PentestStatus.NOT_STARTED;
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
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 {
|
loadFindingsData(): void {
|
||||||
this.pentestService.getFindingsByPentestId(this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '')
|
this.pentestService.getFindingsByPentestId(this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '')
|
||||||
.pipe(
|
.pipe(
|
||||||
untilDestroyed(this),
|
untilDestroyed(this),
|
||||||
filter(isNotNullOrUndefined),
|
/*filter(isNotNullOrUndefined),*/
|
||||||
tap(() => this.loading$.next(true))
|
tap(() => this.loading$.next(true))
|
||||||
)
|
)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (findings: Finding[]) => {
|
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.dataSource.setData(this.data, this.getters);
|
||||||
this.loading$.next(false);
|
this.loading$.next(false);
|
||||||
},
|
},
|
||||||
|
@ -104,8 +120,8 @@ export class PentestFindingsComponent implements OnInit {
|
||||||
),
|
),
|
||||||
untilDestroyed(this)
|
untilDestroyed(this)
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: () => {
|
next: (finding) => {
|
||||||
// ToDo: Parse new Counter to overview / -> dispatch to store maybe already update it
|
this.store.dispatch(new UpdatePentestFindings(finding.id));
|
||||||
this.loadFindingsData();
|
this.loadFindingsData();
|
||||||
this.notificationService.showPopup('finding.popup.save.success', PopupType.SUCCESS);
|
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 {TranslateLoader, TranslateModule} from '@ngx-translate/core';
|
||||||
import {HttpLoaderFactory} from '../../../common-app.module';
|
import {HttpLoaderFactory} from '../../../common-app.module';
|
||||||
import {HttpClient} from '@angular/common/http';
|
import {HttpClient} from '@angular/common/http';
|
||||||
import {NgxsModule} from '@ngxs/store';
|
import {NgxsModule, Store} from '@ngxs/store';
|
||||||
import {ProjectState} from '@shared/stores/project-state/project-state';
|
import {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/stores/project-state/project-state';
|
||||||
import {Category} from '@shared/models/category.model';
|
import {Category} from '@shared/models/category.model';
|
||||||
import {PentestStatus} from '@shared/models/pentest-status.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', () => {
|
describe('PentestInfoComponent', () => {
|
||||||
let component: PentestInfoComponent;
|
let component: PentestInfoComponent;
|
||||||
let fixture: ComponentFixture<PentestInfoComponent>;
|
let fixture: ComponentFixture<PentestInfoComponent>;
|
||||||
|
let store: Store;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
|
@ -44,6 +71,11 @@ describe('PentestInfoComponent', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(PentestInfoComponent);
|
fixture = TestBed.createComponent(PentestInfoComponent);
|
||||||
|
store = TestBed.inject(Store);
|
||||||
|
store.reset({
|
||||||
|
...store.snapshot(),
|
||||||
|
[PROJECT_STATE_NAME]: DESIRED_PROJECT_STATE_SESSION
|
||||||
|
});
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
component.pentestInfo$.next({
|
component.pentestInfo$.next({
|
||||||
id: '56c47c56-3bcd-45f1-a05b-c197dbd33112',
|
id: '56c47c56-3bcd-45f1-a05b-c197dbd33112',
|
||||||
|
|
|
@ -3,7 +3,11 @@ import {BehaviorSubject} from 'rxjs';
|
||||||
import {Pentest} from '@shared/models/pentest.model';
|
import {Pentest} from '@shared/models/pentest.model';
|
||||||
import {getPentestInfoForObjective} from '@shared/functions/infos/get-pentest-info-for-objective';
|
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 {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({
|
@Component({
|
||||||
selector: 'app-pentest-info',
|
selector: 'app-pentest-info',
|
||||||
templateUrl: './pentest-info.component.html',
|
templateUrl: './pentest-info.component.html',
|
||||||
|
@ -11,12 +15,21 @@ import {getTitleKeyForRefNumber} from '@shared/functions/categories/get-title-ke
|
||||||
})
|
})
|
||||||
export class PentestInfoComponent implements OnInit {
|
export class PentestInfoComponent implements OnInit {
|
||||||
|
|
||||||
@Input()
|
|
||||||
pentestInfo$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
|
pentestInfo$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
|
||||||
|
|
||||||
constructor() { }
|
constructor(private store: Store) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
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 {
|
getPentestHeaderForObjective(refNumber: string): string {
|
||||||
|
|
|
@ -14,7 +14,41 @@
|
||||||
<h4>{{selectedProjectTitle$.getValue()}} / {{pentest$.getValue().refNumber}}</h4>
|
<h4>{{selectedProjectTitle$.getValue()}} / {{pentest$.getValue().refNumber}}</h4>
|
||||||
|
|
||||||
<div class="pentest-status-container">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
.exit-button-container {
|
.exit-button-container {
|
||||||
.exit-element-icon {
|
.exit-element-icon {
|
||||||
}
|
}
|
||||||
|
|
||||||
.exit-element-text {
|
.exit-element-text {
|
||||||
padding-left: 0.5rem;
|
padding-left: 0.5rem;
|
||||||
}
|
}
|
||||||
|
@ -12,6 +13,35 @@
|
||||||
.pentest-status-container {
|
.pentest-status-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-content: flex-end;
|
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 {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/stores/project-state/project-state';
|
||||||
import {Category} from '@shared/models/category.model';
|
import {Category} from '@shared/models/category.model';
|
||||||
import {PentestStatus} from '@shared/models/pentest-status.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: {
|
selectedProject: {
|
||||||
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
||||||
client: 'E Corp',
|
client: 'E Corp',
|
||||||
|
@ -58,6 +60,9 @@ describe('PentestHeaderComponent', () => {
|
||||||
}),
|
}),
|
||||||
RouterTestingModule.withRoutes([]),
|
RouterTestingModule.withRoutes([]),
|
||||||
NgxsModule.forRoot([ProjectState])
|
NgxsModule.forRoot([ProjectState])
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{provide: NotificationService, useValue: new NotificationServiceMock()},
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
|
@ -6,10 +6,13 @@ import {Store} from '@ngxs/store';
|
||||||
import {Router} from '@angular/router';
|
import {Router} from '@angular/router';
|
||||||
import {ChangePentest} from '@shared/stores/project-state/project-state.actions';
|
import {ChangePentest} from '@shared/stores/project-state/project-state.actions';
|
||||||
import {BehaviorSubject} from 'rxjs';
|
import {BehaviorSubject} from 'rxjs';
|
||||||
import {PentestStatus} from '@shared/models/pentest-status.model';
|
|
||||||
import {ProjectState} from '@shared/stores/project-state/project-state';
|
import {ProjectState} from '@shared/stores/project-state/project-state';
|
||||||
import {Project} from '@shared/models/project.model';
|
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()
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -23,8 +26,27 @@ export class PentestHeaderComponent implements OnInit {
|
||||||
|
|
||||||
pentest$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
|
pentest$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
|
||||||
selectedProjectTitle$: BehaviorSubject<string> = new BehaviorSubject<string>('');
|
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,
|
constructor(private store: Store,
|
||||||
|
private pentestService: PentestService,
|
||||||
|
private notificationService: NotificationService,
|
||||||
private readonly router: Router) {
|
private readonly router: Router) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +55,7 @@ export class PentestHeaderComponent implements OnInit {
|
||||||
untilDestroyed(this)
|
untilDestroyed(this)
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: (selectedProject: Project) => {
|
next: (selectedProject: Project) => {
|
||||||
|
this.selectedProjectId$.next(selectedProject.id);
|
||||||
this.selectedProjectTitle$.next(selectedProject?.title);
|
this.selectedProjectTitle$.next(selectedProject?.title);
|
||||||
},
|
},
|
||||||
error: err => {
|
error: err => {
|
||||||
|
@ -44,6 +67,8 @@ export class PentestHeaderComponent implements OnInit {
|
||||||
untilDestroyed(this)
|
untilDestroyed(this)
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: (selectedPentest: Pentest) => {
|
next: (selectedPentest: Pentest) => {
|
||||||
|
this.currentStatus = selectedPentest.status;
|
||||||
|
this.initialPentestStatus = selectedPentest.status;
|
||||||
this.pentest$.next(selectedPentest);
|
this.pentest$.next(selectedPentest);
|
||||||
},
|
},
|
||||||
error: err => {
|
error: err => {
|
||||||
|
@ -61,4 +86,76 @@ export class PentestHeaderComponent implements OnInit {
|
||||||
}
|
}
|
||||||
).finally();
|
).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 {ProjectState} from '@shared/stores/project-state/project-state';
|
||||||
import {catchError, map, switchMap} from 'rxjs/operators';
|
import {catchError, map, switchMap} from 'rxjs/operators';
|
||||||
import {getTempPentestsForCategory} from '@shared/functions/categories/get-temp-pentests-for-category.function';
|
import {getTempPentestsForCategory} from '@shared/functions/categories/get-temp-pentests-for-category.function';
|
||||||
import {Finding, FindingDialogBody} from '@shared/models/finding.model';
|
import {Finding} from '@shared/models/finding.model';
|
||||||
import {Severity} from '@shared/models/severity.enum';
|
|
||||||
import {Comment} from '@shared/models/comment.model';
|
import {Comment} from '@shared/models/comment.model';
|
||||||
import {v4 as UUID} from 'uuid';
|
import {v4 as UUID} from 'uuid';
|
||||||
|
|
||||||
|
@ -85,46 +84,43 @@ export class PentestService {
|
||||||
* @param pentestId the id of the project
|
* @param pentestId the id of the project
|
||||||
*/
|
*/
|
||||||
public getFindingsByPentestId(pentestId: string): Observable<Finding[]> {
|
public getFindingsByPentestId(pentestId: string): Observable<Finding[]> {
|
||||||
if (pentestId) {
|
return this.http.get<Finding[]>(`${this.apiBaseURL}/${pentestId}/findings`);
|
||||||
return this.http.get<Finding[]>(`${this.apiBaseURL}/${pentestId}/findings`);
|
// return of([]);
|
||||||
} else {
|
/*Todo: Remove mocked Findings?
|
||||||
// return of([]);
|
return of([
|
||||||
// Todo: Remove mocked Findings
|
{
|
||||||
return of([
|
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
|
||||||
{
|
title: 'This is a creative title',
|
||||||
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
|
description: 'test',
|
||||||
title: 'This is a creative title',
|
impact: 'This impacts only the UI',
|
||||||
description: 'test',
|
severity: Severity.LOW,
|
||||||
impact: 'This impacts only the UI',
|
reproduction: ''
|
||||||
severity: Severity.LOW,
|
},
|
||||||
reproduction: ''
|
{
|
||||||
},
|
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
|
||||||
{
|
title: 'This is a creative title',
|
||||||
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
|
description: 'test',
|
||||||
title: 'This is a creative title',
|
impact: 'This is impacts some things',
|
||||||
description: 'test',
|
severity: Severity.MEDIUM,
|
||||||
impact: 'This is impacts some things',
|
reproduction: ''
|
||||||
severity: Severity.MEDIUM,
|
},
|
||||||
reproduction: ''
|
{
|
||||||
},
|
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
|
||||||
{
|
title: 'This is a creative title',
|
||||||
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
|
description: 'test',
|
||||||
title: 'This is a creative title',
|
impact: 'This is impacts a lot',
|
||||||
description: 'test',
|
severity: Severity.HIGH,
|
||||||
impact: 'This is impacts a lot',
|
reproduction: ''
|
||||||
severity: Severity.HIGH,
|
},
|
||||||
reproduction: ''
|
{
|
||||||
},
|
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
|
||||||
{
|
title: 'This is a creative title',
|
||||||
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
|
description: 'test',
|
||||||
title: 'This is a creative title',
|
impact: 'This is impacts a lot',
|
||||||
description: 'test',
|
severity: Severity.CRITICAL,
|
||||||
impact: 'This is impacts a lot',
|
reproduction: ''
|
||||||
severity: Severity.CRITICAL,
|
}
|
||||||
reproduction: ''
|
]);*/
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -141,26 +137,22 @@ export class PentestService {
|
||||||
* @param pentestId the id of the project
|
* @param pentestId the id of the project
|
||||||
*/
|
*/
|
||||||
public getCommentsByPentestId(pentestId: string): Observable<Comment[]> {
|
public getCommentsByPentestId(pentestId: string): Observable<Comment[]> {
|
||||||
console.warn('Comments for:', pentestId);
|
return this.http.get<Comment[]>(`${this.apiBaseURL}/${pentestId}/comments`);
|
||||||
if (pentestId) {
|
// return of([]);
|
||||||
return this.http.get<Comment[]>(`${this.apiBaseURL}/${pentestId}/comments`);
|
/* ToDo: Use mocked Comments?
|
||||||
} else {
|
return of([
|
||||||
// return of([]);
|
{
|
||||||
// Todo: Remove mocked Comments
|
id: 'ca96cc19-88ff-4874-8406-dc892620afd2',
|
||||||
return of([
|
title: 'This is a creative title',
|
||||||
{
|
description: 'This is a creative description',
|
||||||
id: 'ca96cc19-88ff-4874-8406-dc892620afd2',
|
relatedFindings: ['ca96cc19-88ff-4874-8406-dc892620afd4'],
|
||||||
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',
|
||||||
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
|
relatedFindings: [],
|
||||||
title: 'This is a creative title',
|
}
|
||||||
description: 'This is a creative description',
|
]);*/
|
||||||
relatedFindings: [],
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,3 +33,10 @@ export class ChangePentest {
|
||||||
constructor(public pentest: Pentest) {
|
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 {Action, Selector, State, StateContext} from '@ngxs/store';
|
||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {Project} from '@shared/models/project.model';
|
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 {Category} from '@shared/models/category.model';
|
||||||
import {Pentest} from '@shared/models/pentest.model';
|
import {Pentest} from '@shared/models/pentest.model';
|
||||||
|
|
||||||
|
@ -80,4 +86,26 @@ export class ProjectState {
|
||||||
selectedPentest: {...pentest, projectId: state.selectedProject.id}
|
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}")
|
@DeleteMapping("/{id}")
|
||||||
fun deleteProject(@PathVariable(value = "id") id: String): Mono<ResponseEntity<ResponseBody>> {
|
fun deleteProject(@PathVariable(value = "id") id: String): Mono<ResponseEntity<ResponseBody>> {
|
||||||
|
// ToDo: Delete all associated Pentests, Findings and Comments
|
||||||
return this.projectService.deleteProject(id).map{
|
return this.projectService.deleteProject(id).map{
|
||||||
ResponseEntity.ok().body(it.toProjectDeleteResponseBody())
|
ResponseEntity.ok().body(it.toProjectDeleteResponseBody())
|
||||||
}.switchIfEmpty {
|
}.switchIfEmpty {
|
||||||
|
|
Loading…
Reference in New Issue