feat: As a user I want to have an comments overview
This commit is contained in:
parent
747cade495
commit
14828e5098
|
@ -1 +1,83 @@
|
||||||
<p>pentest-comments works!</p>
|
<div class="comment-table">
|
||||||
|
<table [nbTreeGrid]="dataSource">
|
||||||
|
<tr nbTreeGridHeaderRow *nbTreeGridHeaderRowDef="columns"></tr>
|
||||||
|
<tr nbTreeGridRow *nbTreeGridRowDef="let comment; columns: columns"
|
||||||
|
class="comment-cell"
|
||||||
|
fragment="{{comment.data['commentId']}}">
|
||||||
|
</tr>
|
||||||
|
<!-- Comment ID -->
|
||||||
|
<ng-container [nbTreeGridColumnDef]="columns[0]">
|
||||||
|
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
|
||||||
|
{{ 'comment.commentId' | translate }}
|
||||||
|
</th>
|
||||||
|
<td nbTreeGridCell *nbTreeGridCellDef="let comment">
|
||||||
|
{{ comment.data['commentId'] || '-' }}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<!-- Title -->
|
||||||
|
<ng-container [nbTreeGridColumnDef]="columns[1]">
|
||||||
|
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
|
||||||
|
{{ 'comment.title' | translate }}
|
||||||
|
</th>
|
||||||
|
<td nbTreeGridCell *nbTreeGridCellDef="let comment">
|
||||||
|
{{ comment.data['title'] }}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<!-- Description -->
|
||||||
|
<ng-container [nbTreeGridColumnDef]="columns[2]">
|
||||||
|
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
|
||||||
|
{{ 'comment.description' | translate }}
|
||||||
|
</th>
|
||||||
|
<td nbTreeGridCell *nbTreeGridCellDef="let comment">
|
||||||
|
{{ comment.data['description'] }}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<!-- Related Findings -->
|
||||||
|
<ng-container [nbTreeGridColumnDef]="columns[3]">
|
||||||
|
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
|
||||||
|
{{ 'comment.relatedFindings' | translate }}
|
||||||
|
</th>
|
||||||
|
<td nbTreeGridCell *nbTreeGridCellDef="let comment" class="related-finding-cell">
|
||||||
|
{{ comment.data['relatedFindings'].length ? comment.data['relatedFindings'] : 'comment.no.relatedFindings' | translate }}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<!-- Actions -->
|
||||||
|
<ng-container [nbTreeGridColumnDef]="columns[4]">
|
||||||
|
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef class="cell-actions">
|
||||||
|
<button nbButton hero
|
||||||
|
status="info"
|
||||||
|
size="small"
|
||||||
|
shape="round"
|
||||||
|
class="add-comment-button"
|
||||||
|
(click)="onClickAddComment()">
|
||||||
|
<fa-icon [icon]="fa.faPlus" class="new-comment-icon"></fa-icon>
|
||||||
|
{{'comment.add' | translate}}
|
||||||
|
</button>
|
||||||
|
</th>
|
||||||
|
<td nbTreeGridCell *nbTreeGridCellDef="let comment" class="cell-actions">
|
||||||
|
<div fxLayout="row" fxLayoutAlign="center center" fxLayoutGap="1rem">
|
||||||
|
<button nbButton
|
||||||
|
status="primary"
|
||||||
|
size="small"
|
||||||
|
(click)="onClickEditComment(comment)">
|
||||||
|
<fa-icon [icon]="fa.faPencilAlt"></fa-icon>
|
||||||
|
</button>
|
||||||
|
<button nbButton
|
||||||
|
status="danger"
|
||||||
|
size="small"
|
||||||
|
(click)="onClickDeleteComment(comment)">
|
||||||
|
<fa-icon [icon]="fa.faTrash"></fa-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="data.length === 0 && loading$.getValue() === false" fxLayout="row" fxLayoutAlign="center center">
|
||||||
|
<p class="error-text">
|
||||||
|
{{'comment.no.comments' | translate}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<app-loading-spinner [isLoading$]="isLoading()" *ngIf="isLoading() | async"></app-loading-spinner>
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
@import '../../../../assets/@theme/styles/themes';
|
||||||
|
|
||||||
|
.comment-table {
|
||||||
|
// width: calc(78vw - 18%);
|
||||||
|
width: 90vw;
|
||||||
|
|
||||||
|
.comment-cell {
|
||||||
|
// Add style here
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-cell:hover {
|
||||||
|
// cursor: default;
|
||||||
|
background-color: nb-theme(color-basic-transparent-focus);
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-finding-cell {
|
||||||
|
// cursor: pointer;
|
||||||
|
font-family: Courier, serif;
|
||||||
|
color: nb-theme(color-info-default);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-actions {
|
||||||
|
width: max-content;
|
||||||
|
max-width: 200px;
|
||||||
|
|
||||||
|
.add-comment-button {
|
||||||
|
.new-comment-icon {
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-text {
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
|
@ -1,20 +1,92 @@
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||||
|
|
||||||
import { PentestCommentsComponent } from './pentest-comments.component';
|
import {PentestCommentsComponent} from './pentest-comments.component';
|
||||||
|
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 {NgxsModule, Store} from '@ngxs/store';
|
||||||
|
import {CommonModule} from '@angular/common';
|
||||||
|
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||||
|
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||||
|
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
||||||
|
import {NbButtonModule, NbTreeGridModule} from '@nebular/theme';
|
||||||
|
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 {NotificationService} from '@shared/services/notification.service';
|
||||||
|
import {NotificationServiceMock} from '@shared/services/notification.service.mock';
|
||||||
|
import {MockComponent} from 'ng-mocks';
|
||||||
|
import {LoadingSpinnerComponent} from '@shared/widgets/loading-spinner/loading-spinner.component';
|
||||||
|
|
||||||
|
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,
|
||||||
|
findingsIds: [],
|
||||||
|
commentsIds: ['56c47c56-3bcd-45f1-a05b-c197dbd33112']
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
describe('PentestCommentsComponent', () => {
|
describe('PentestCommentsComponent', () => {
|
||||||
let component: PentestCommentsComponent;
|
let component: PentestCommentsComponent;
|
||||||
let fixture: ComponentFixture<PentestCommentsComponent>;
|
let fixture: ComponentFixture<PentestCommentsComponent>;
|
||||||
|
let store: Store;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [ PentestCommentsComponent ]
|
declarations: [
|
||||||
|
PentestCommentsComponent,
|
||||||
|
MockComponent(LoadingSpinnerComponent)
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
HttpClientTestingModule,
|
||||||
|
FontAwesomeModule,
|
||||||
|
NbButtonModule,
|
||||||
|
NbTreeGridModule,
|
||||||
|
ThemeModule.forRoot(),
|
||||||
|
TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useFactory: HttpLoaderFactory,
|
||||||
|
deps: [HttpClient]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
NgxsModule.forRoot([ProjectState])
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{provide: NotificationService, useValue: new NotificationServiceMock()}
|
||||||
|
]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(PentestCommentsComponent);
|
fixture = TestBed.createComponent(PentestCommentsComponent);
|
||||||
|
store = TestBed.inject(Store);
|
||||||
|
store.reset({
|
||||||
|
...store.snapshot(),
|
||||||
|
[PROJECT_STATE_NAME]: DESIRED_PROJECT_STATE_SESSION
|
||||||
|
});
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,16 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import {Component, Input, 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';
|
||||||
|
import {NbGetters, NbTreeGridDataSource, NbTreeGridDataSourceBuilder} from '@nebular/theme';
|
||||||
|
import {PentestService} from '@shared/services/pentest.service';
|
||||||
|
import {NotificationService, PopupType} from '@shared/services/notification.service';
|
||||||
|
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';
|
||||||
|
|
||||||
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-pentest-comments',
|
selector: 'app-pentest-comments',
|
||||||
templateUrl: './pentest-comments.component.html',
|
templateUrl: './pentest-comments.component.html',
|
||||||
|
@ -7,9 +18,80 @@ import { Component, OnInit } from '@angular/core';
|
||||||
})
|
})
|
||||||
export class PentestCommentsComponent implements OnInit {
|
export class PentestCommentsComponent implements OnInit {
|
||||||
|
|
||||||
constructor() { }
|
@Input()
|
||||||
|
pentestInfo$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
|
||||||
|
|
||||||
ngOnInit(): void {
|
// HTML only
|
||||||
|
readonly fa = FA;
|
||||||
|
// comments$: BehaviorSubject<Comment[]> = new BehaviorSubject<Comment[]>(null);
|
||||||
|
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
||||||
|
|
||||||
|
columns: Array<CommentColumns> = [
|
||||||
|
CommentColumns.COMMENT_ID, CommentColumns.TITLE, CommentColumns.DESCRIPTION, CommentColumns.RELATED_FINDINGS, CommentColumns.ACTIONS
|
||||||
|
];
|
||||||
|
dataSource: NbTreeGridDataSource<CommentEntry>;
|
||||||
|
|
||||||
|
data: CommentEntry[] = [];
|
||||||
|
|
||||||
|
getters: NbGetters<CommentEntry, CommentEntry> = {
|
||||||
|
dataGetter: (node: CommentEntry) => node,
|
||||||
|
childrenGetter: (node: CommentEntry) => node.childEntries || undefined,
|
||||||
|
expandedGetter: (node: CommentEntry) => !!node.expanded,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(private readonly pentestService: PentestService,
|
||||||
|
private dataSourceBuilder: NbTreeGridDataSourceBuilder<CommentEntry>,
|
||||||
|
private notificationService: NotificationService) {
|
||||||
|
this.dataSource = dataSourceBuilder.create(this.data, this.getters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.loadCommentsData();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadCommentsData(): void {
|
||||||
|
this.pentestService.getCommentsByPentestId(this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '')
|
||||||
|
.pipe(
|
||||||
|
untilDestroyed(this),
|
||||||
|
filter(isNotNullOrUndefined),
|
||||||
|
tap(() => this.loading$.next(true))
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next: (comments: Comment[]) => {
|
||||||
|
this.data = transformCommentsToObjectiveEntries(comments);
|
||||||
|
this.dataSource.setData(this.data, this.getters);
|
||||||
|
this.loading$.next(false);
|
||||||
|
},
|
||||||
|
error: err => {
|
||||||
|
console.log(err);
|
||||||
|
this.notificationService.showPopup('comment.popup.not.found', PopupType.FAILURE);
|
||||||
|
this.loading$.next(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickAddComment(): void {
|
||||||
|
console.info('Coming soon..');
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickEditComment(comment): void {
|
||||||
|
console.info('Coming soon..');
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickDeleteComment(comment): void {
|
||||||
|
console.info('Coming soon..');
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML only
|
||||||
|
isLoading(): Observable<boolean> {
|
||||||
|
return this.loading$.asObservable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CommentColumns {
|
||||||
|
COMMENT_ID = 'commentId',
|
||||||
|
TITLE = 'title',
|
||||||
|
DESCRIPTION = 'description',
|
||||||
|
RELATED_FINDINGS = 'relatedFindings',
|
||||||
|
ACTIONS = 'actions'
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
<app-pentest-findings [pentestInfo$] = pentest$></app-pentest-findings>
|
<app-pentest-findings [pentestInfo$] = pentest$></app-pentest-findings>
|
||||||
</nb-tab>
|
</nb-tab>
|
||||||
<nb-tab class="pentest-tabset" tabTitle="{{ 'pentest.comments' | translate }}" badgeText="{{currentNumberOfComments$.getValue()}}" badgeStatus="info">
|
<nb-tab class="pentest-tabset" tabTitle="{{ 'pentest.comments' | translate }}" badgeText="{{currentNumberOfComments$.getValue()}}" badgeStatus="info">
|
||||||
<app-pentest-comments></app-pentest-comments>
|
<app-pentest-comments [pentestInfo$] = pentest$></app-pentest-comments>
|
||||||
</nb-tab>
|
</nb-tab>
|
||||||
</nb-tabset>
|
</nb-tabset>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -14,8 +14,17 @@
|
||||||
{{ finding.data['findingId'] || '-' }}
|
{{ finding.data['findingId'] || '-' }}
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<!-- Title -->
|
<!-- Severity -->
|
||||||
<ng-container [nbTreeGridColumnDef]="columns[1]">
|
<ng-container [nbTreeGridColumnDef]="columns[1]">
|
||||||
|
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef class="cell-severity">
|
||||||
|
{{ 'finding.severity' | translate }}
|
||||||
|
</th>
|
||||||
|
<td nbTreeGridCell *nbTreeGridCellDef="let finding" class="cell-severity" fxFill fxLayoutAlign="center none">
|
||||||
|
<app-severity-tag [currentSeverity]="finding.data['severity']"></app-severity-tag>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<!-- Title -->
|
||||||
|
<ng-container [nbTreeGridColumnDef]="columns[2]">
|
||||||
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
|
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
|
||||||
{{ 'finding.title' | translate }}
|
{{ 'finding.title' | translate }}
|
||||||
</th>
|
</th>
|
||||||
|
@ -24,7 +33,7 @@
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<!-- Impact -->
|
<!-- Impact -->
|
||||||
<ng-container [nbTreeGridColumnDef]="columns[2]">
|
<ng-container [nbTreeGridColumnDef]="columns[3]">
|
||||||
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
|
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
|
||||||
{{ 'finding.impact' | translate }}
|
{{ 'finding.impact' | translate }}
|
||||||
</th>
|
</th>
|
||||||
|
@ -32,15 +41,6 @@
|
||||||
{{ finding.data['impact'] }}
|
{{ finding.data['impact'] }}
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<!-- Severity -->
|
|
||||||
<ng-container [nbTreeGridColumnDef]="columns[3]">
|
|
||||||
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
|
|
||||||
{{ 'finding.severity' | translate }}
|
|
||||||
</th>
|
|
||||||
<td nbTreeGridCell *nbTreeGridCellDef="let finding">
|
|
||||||
<app-severity-tag [currentSeverity]="finding.data['severity']"></app-severity-tag>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
<ng-container [nbTreeGridColumnDef]="columns[4]">
|
<ng-container [nbTreeGridColumnDef]="columns[4]">
|
||||||
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef class="cell-actions">
|
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef class="cell-actions">
|
||||||
|
|
|
@ -13,6 +13,11 @@
|
||||||
background-color: nb-theme(color-basic-transparent-focus);
|
background-color: nb-theme(color-basic-transparent-focus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cell-severity {
|
||||||
|
width: 125px;
|
||||||
|
max-width: 125px;
|
||||||
|
}
|
||||||
|
|
||||||
.cell-actions {
|
.cell-actions {
|
||||||
width: max-content;
|
width: max-content;
|
||||||
max-width: 180px;
|
max-width: 180px;
|
||||||
|
|
|
@ -3,11 +3,12 @@ import {PentestService} from '@shared/services/pentest.service';
|
||||||
import {BehaviorSubject, Observable, of} from 'rxjs';
|
import {BehaviorSubject, Observable, of} from 'rxjs';
|
||||||
import {Pentest} from '@shared/models/pentest.model';
|
import {Pentest} from '@shared/models/pentest.model';
|
||||||
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||||
import {tap} from 'rxjs/operators';
|
import {filter, tap} from 'rxjs/operators';
|
||||||
import {NotificationService, PopupType} from '@shared/services/notification.service';
|
import {NotificationService, PopupType} from '@shared/services/notification.service';
|
||||||
import {Finding, FindingEntry, transformFindingsToObjectiveEntries} from '@shared/models/finding.model';
|
import {Finding, FindingEntry, transformFindingsToObjectiveEntries} from '@shared/models/finding.model';
|
||||||
import {NbGetters, NbTreeGridDataSource, NbTreeGridDataSourceBuilder} from '@nebular/theme';
|
import {NbGetters, NbTreeGridDataSource, NbTreeGridDataSourceBuilder} from '@nebular/theme';
|
||||||
import * as FA from '@fortawesome/free-solid-svg-icons';
|
import * as FA from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import {isNotNullOrUndefined} from 'codelyzer/util/isNotNullOrUndefined';
|
||||||
|
|
||||||
@UntilDestroy()
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -26,7 +27,7 @@ export class PentestFindingsComponent implements OnInit {
|
||||||
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
||||||
|
|
||||||
columns: Array<FindingColumns> = [
|
columns: Array<FindingColumns> = [
|
||||||
FindingColumns.FINDING_ID, FindingColumns.TITLE, FindingColumns.IMPACT, FindingColumns.SEVERITY, FindingColumns.ACTIONS
|
FindingColumns.FINDING_ID, FindingColumns.SEVERITY, FindingColumns.TITLE, FindingColumns.IMPACT, FindingColumns.ACTIONS
|
||||||
];
|
];
|
||||||
dataSource: NbTreeGridDataSource<FindingEntry>;
|
dataSource: NbTreeGridDataSource<FindingEntry>;
|
||||||
|
|
||||||
|
@ -45,7 +46,6 @@ export class PentestFindingsComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
console.warn('Selected Pentest: ', this.pentestInfo$.getValue());
|
|
||||||
this.loadFindingsData();
|
this.loadFindingsData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +53,7 @@ export class PentestFindingsComponent implements OnInit {
|
||||||
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),
|
||||||
tap(() => this.loading$.next(true))
|
tap(() => this.loading$.next(true))
|
||||||
)
|
)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
|
@ -89,8 +90,8 @@ export class PentestFindingsComponent implements OnInit {
|
||||||
|
|
||||||
enum FindingColumns {
|
enum FindingColumns {
|
||||||
FINDING_ID = 'findingId',
|
FINDING_ID = 'findingId',
|
||||||
|
SEVERITY = 'severity',
|
||||||
TITLE = 'title',
|
TITLE = 'title',
|
||||||
IMPACT = 'impact',
|
IMPACT = 'impact',
|
||||||
SEVERITY = 'severity',
|
|
||||||
ACTIONS = 'actions'
|
ACTIONS = 'actions'
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,11 +90,20 @@
|
||||||
},
|
},
|
||||||
"finding": {
|
"finding": {
|
||||||
"findingId": "Fund Id",
|
"findingId": "Fund Id",
|
||||||
"title": "Title",
|
"title": "Titel",
|
||||||
"impact": "Auswirkung",
|
"impact": "Auswirkung",
|
||||||
"severity": "Schwere",
|
"severity": "Schwere",
|
||||||
"add": "Fund hinzufügen",
|
"add": "Fund hinzufügen",
|
||||||
"no.findings": "Keine Funde verfügbar"
|
"no.findings": "Keine Funde verfügbar",
|
||||||
|
"popup": {
|
||||||
|
"not.found": "Keine Funde gefunden",
|
||||||
|
"save.success": "Fund erfolgreich gespeichert",
|
||||||
|
"save.failed": "Fund konnte nicht gespeichert werden",
|
||||||
|
"update.success": "Fund erfolgreich aktualisiert",
|
||||||
|
"update.failed": "Fund konnte nicht aktualisiert werden",
|
||||||
|
"delete.success": "Fund erfolgreich gelöscht",
|
||||||
|
"delete.failed": "Fund konnte nicht gelöscht werden"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"severities": {
|
"severities": {
|
||||||
"low": "Niedrig",
|
"low": "Niedrig",
|
||||||
|
@ -102,6 +111,24 @@
|
||||||
"high": "Hoch",
|
"high": "Hoch",
|
||||||
"critical": "Kritisch"
|
"critical": "Kritisch"
|
||||||
},
|
},
|
||||||
|
"comment": {
|
||||||
|
"commentId": "Kommentar Id",
|
||||||
|
"title": "Titel",
|
||||||
|
"description": "Beschreibung",
|
||||||
|
"relatedFindings": "Verwandte Funde",
|
||||||
|
"add": "Kommentar hinzufügen",
|
||||||
|
"no.relatedFindings": "Nicht verbunden mit Fund",
|
||||||
|
"no.comments": "Keine Kommentare verfügbar",
|
||||||
|
"popup": {
|
||||||
|
"not.found": "Keine Kommentare gefunden",
|
||||||
|
"save.success": "Kommentar erfolgreich gespeichert",
|
||||||
|
"save.failed": "Kommentar konnte nicht gespeichert werden",
|
||||||
|
"update.success": "Kommentar erfolgreich aktualisiert",
|
||||||
|
"update.failed": "Kommentar konnte nicht aktualisiert werden",
|
||||||
|
"delete.success": "Kommentar erfolgreich gelöscht",
|
||||||
|
"delete.failed": "Kommentar konnte nicht gelöscht werden"
|
||||||
|
}
|
||||||
|
},
|
||||||
"pentest": {
|
"pentest": {
|
||||||
"testId": "Nr.",
|
"testId": "Nr.",
|
||||||
"title": "Titel",
|
"title": "Titel",
|
||||||
|
|
|
@ -90,11 +90,20 @@
|
||||||
},
|
},
|
||||||
"finding": {
|
"finding": {
|
||||||
"findingId": "Finding Id",
|
"findingId": "Finding Id",
|
||||||
|
"severity": "Severity",
|
||||||
"title": "Title",
|
"title": "Title",
|
||||||
"impact": "Impact",
|
"impact": "Impact",
|
||||||
"severity": "Severity",
|
|
||||||
"add": "Add finding",
|
"add": "Add finding",
|
||||||
"no.findings": "No findings available"
|
"no.findings": "No findings available",
|
||||||
|
"popup": {
|
||||||
|
"not.found": "No finding found",
|
||||||
|
"save.success": "Finding saved successfully",
|
||||||
|
"save.failed": "Finding could not be saved",
|
||||||
|
"update.success": "Finding updated successfully",
|
||||||
|
"update.failed": "Finding could not be updated",
|
||||||
|
"delete.success": "Finding deleted successfully",
|
||||||
|
"delete.failed": "Finding could not be deleted"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"severities": {
|
"severities": {
|
||||||
"low": "Low",
|
"low": "Low",
|
||||||
|
@ -102,6 +111,24 @@
|
||||||
"high": "High",
|
"high": "High",
|
||||||
"critical": "Critical"
|
"critical": "Critical"
|
||||||
},
|
},
|
||||||
|
"comment": {
|
||||||
|
"commentId": "Comment Id",
|
||||||
|
"title": "Title",
|
||||||
|
"description": "Description",
|
||||||
|
"relatedFindings": "Related Findings",
|
||||||
|
"add": "Add comment",
|
||||||
|
"no.comments": "No comments available",
|
||||||
|
"no.relatedFindings": "Not related to finding",
|
||||||
|
"popup": {
|
||||||
|
"not.found": "No comment found",
|
||||||
|
"save.success": "Comment saved successfully",
|
||||||
|
"save.failed": "Comment could not be saved",
|
||||||
|
"update.success": "Comment updated successfully",
|
||||||
|
"update.failed": "Comment could not be updated",
|
||||||
|
"delete.success": "Comment deleted successfully",
|
||||||
|
"delete.failed": "Comment could not be deleted"
|
||||||
|
}
|
||||||
|
},
|
||||||
"pentest": {
|
"pentest": {
|
||||||
"testId": "No.",
|
"testId": "No.",
|
||||||
"title": "Title",
|
"title": "Title",
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
import {v4 as UUID} from 'uuid';
|
||||||
|
import {Severity} from '@shared/models/severity.enum';
|
||||||
|
|
||||||
|
export class Comment {
|
||||||
|
id?: string;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
relatedFindings?: Array<string>;
|
||||||
|
|
||||||
|
constructor(title: string,
|
||||||
|
description: string,
|
||||||
|
id?: string,
|
||||||
|
relatedFindings?: Array<string>) {
|
||||||
|
this.id = id ? id : UUID();
|
||||||
|
this.title = title;
|
||||||
|
this.description = description;
|
||||||
|
this.relatedFindings = relatedFindings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommentEntry {
|
||||||
|
commentId: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
relatedFindings: Array<string>;
|
||||||
|
kind?: string;
|
||||||
|
childEntries?: [];
|
||||||
|
expanded?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transformCommentsToObjectiveEntries(findings: Comment[]): CommentEntry[] {
|
||||||
|
const findingEntries: CommentEntry[] = [];
|
||||||
|
findings.forEach((value: Comment) => {
|
||||||
|
findingEntries.push({
|
||||||
|
commentId: value.id,
|
||||||
|
title: value.title,
|
||||||
|
description: value.description,
|
||||||
|
relatedFindings: value.relatedFindings,
|
||||||
|
kind: 'cell',
|
||||||
|
childEntries: null,
|
||||||
|
expanded: false
|
||||||
|
} as CommentEntry);
|
||||||
|
});
|
||||||
|
return findingEntries;
|
||||||
|
}
|
|
@ -3,27 +3,27 @@ import {Severity} from '@shared/models/severity.enum';
|
||||||
|
|
||||||
export class Finding {
|
export class Finding {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
severity: Severity;
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
impact: string;
|
impact: string;
|
||||||
severity: Severity;
|
|
||||||
affectedUrls?: Array<string>;
|
affectedUrls?: Array<string>;
|
||||||
reproduction?: string;
|
reproduction?: string;
|
||||||
mitigation?: string;
|
mitigation?: string;
|
||||||
|
|
||||||
constructor(title: string,
|
constructor(title: string,
|
||||||
|
severity: Severity,
|
||||||
description: string,
|
description: string,
|
||||||
impact: string,
|
impact: string,
|
||||||
severity: Severity,
|
|
||||||
reproduction: string,
|
reproduction: string,
|
||||||
id?: string,
|
id?: string,
|
||||||
affectedUrls?: Array<string>,
|
affectedUrls?: Array<string>,
|
||||||
mitigation?: string) {
|
mitigation?: string) {
|
||||||
this.id = id ? id : UUID();
|
this.id = id ? id : UUID();
|
||||||
|
this.severity = severity;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.impact = impact;
|
this.impact = impact;
|
||||||
this.severity = severity;
|
|
||||||
this.affectedUrls = affectedUrls ? affectedUrls : null;
|
this.affectedUrls = affectedUrls ? affectedUrls : null;
|
||||||
this.reproduction = reproduction;
|
this.reproduction = reproduction;
|
||||||
this.mitigation = mitigation ? mitigation : null;
|
this.mitigation = mitigation ? mitigation : null;
|
||||||
|
@ -32,9 +32,9 @@ export class Finding {
|
||||||
|
|
||||||
export interface FindingEntry {
|
export interface FindingEntry {
|
||||||
findingId: string;
|
findingId: string;
|
||||||
|
severity: Severity;
|
||||||
title: string;
|
title: string;
|
||||||
impact: string;
|
impact: string;
|
||||||
severity: Severity;
|
|
||||||
kind?: string;
|
kind?: string;
|
||||||
childEntries?: [];
|
childEntries?: [];
|
||||||
expanded?: boolean;
|
expanded?: boolean;
|
||||||
|
@ -45,9 +45,9 @@ export function transformFindingsToObjectiveEntries(findings: Finding[]): Findin
|
||||||
findings.forEach((value: Finding) => {
|
findings.forEach((value: Finding) => {
|
||||||
findingEntries.push({
|
findingEntries.push({
|
||||||
findingId: value.id,
|
findingId: value.id,
|
||||||
|
severity: value.severity,
|
||||||
title: value.title,
|
title: value.title,
|
||||||
impact: value.impact,
|
impact: value.impact,
|
||||||
severity: value.severity,
|
|
||||||
kind: 'cell',
|
kind: 'cell',
|
||||||
childEntries: null,
|
childEntries: null,
|
||||||
expanded: false
|
expanded: false
|
||||||
|
|
|
@ -10,6 +10,7 @@ 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} from '@shared/models/finding.model';
|
import {Finding} from '@shared/models/finding.model';
|
||||||
import {Severity} from '@shared/models/severity.enum';
|
import {Severity} from '@shared/models/severity.enum';
|
||||||
|
import {Comment} from '@shared/models/comment.model';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
|
@ -92,4 +93,32 @@ export class PentestService {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Comments for Pentest Id
|
||||||
|
* @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 lit test finding ma brother',
|
||||||
|
description: 'fucked up a lot man. better fix it',
|
||||||
|
relatedFindings: ['ca96cc19-88ff-4874-8406-dc892620afd4'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ca96cc19-88ff-4874-8406-dc892620afd4',
|
||||||
|
title: 'This is a lit test finding ma brother',
|
||||||
|
description: 'fucked up a lot man. better fix it',
|
||||||
|
relatedFindings: [],
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package comment
|
||||||
|
|
||||||
|
import org.springframework.data.mongodb.core.index.Indexed
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
data class Comment (
|
||||||
|
@Indexed(background = true, unique = true)
|
||||||
|
val id: String = UUID.randomUUID().toString(),
|
||||||
|
val title: String,
|
||||||
|
val description: String,
|
||||||
|
val relatedFindings: List<String>? = emptyList()
|
||||||
|
)
|
|
@ -0,0 +1,18 @@
|
||||||
|
package comment
|
||||||
|
|
||||||
|
import com.securityc4po.api.BaseEntity
|
||||||
|
import org.springframework.data.mongodb.core.mapping.Document
|
||||||
|
|
||||||
|
@Document(collection = "comments")
|
||||||
|
open class CommentEntity(
|
||||||
|
data: Comment
|
||||||
|
) : BaseEntity<Comment>(data)
|
||||||
|
|
||||||
|
fun CommentEntity.toComment(): Comment {
|
||||||
|
return Comment(
|
||||||
|
this.data.id,
|
||||||
|
this.data.title,
|
||||||
|
this.data.description,
|
||||||
|
this.data.relatedFindings
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package com.securityc4po.api.pentest
|
package finding
|
||||||
|
|
||||||
import org.springframework.data.mongodb.core.index.Indexed
|
import org.springframework.data.mongodb.core.index.Indexed
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -6,10 +6,10 @@ import java.util.*
|
||||||
data class Finding (
|
data class Finding (
|
||||||
@Indexed(background = true, unique = true)
|
@Indexed(background = true, unique = true)
|
||||||
val id: String = UUID.randomUUID().toString(),
|
val id: String = UUID.randomUUID().toString(),
|
||||||
|
val severity: Severity,
|
||||||
val title: String,
|
val title: String,
|
||||||
val description: String,
|
val description: String,
|
||||||
val impact: String,
|
val impact: String,
|
||||||
val severity: Severity,
|
|
||||||
val affectedUrls: List<String>? = emptyList(),
|
val affectedUrls: List<String>? = emptyList(),
|
||||||
val reproduction: String,
|
val reproduction: String,
|
||||||
val mitigation: String
|
val mitigation: String
|
|
@ -0,0 +1,23 @@
|
||||||
|
package finding
|
||||||
|
|
||||||
|
import com.securityc4po.api.BaseEntity
|
||||||
|
import comment.Comment
|
||||||
|
import org.springframework.data.mongodb.core.mapping.Document
|
||||||
|
|
||||||
|
@Document(collection = "findings")
|
||||||
|
open class FindingEntity(
|
||||||
|
data: Finding
|
||||||
|
) : BaseEntity<Finding>(data)
|
||||||
|
|
||||||
|
fun FindingEntity.toFinding(): Finding {
|
||||||
|
return finding.Finding(
|
||||||
|
this.data.id,
|
||||||
|
this.data.severity,
|
||||||
|
this.data.title,
|
||||||
|
this.data.description,
|
||||||
|
this.data.impact,
|
||||||
|
this.data.affectedUrls,
|
||||||
|
this.data.reproduction,
|
||||||
|
this.data.mitigation
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package com.securityc4po.api.pentest
|
package finding
|
||||||
|
|
||||||
enum class Severity {
|
enum class Severity {
|
||||||
LOW,
|
LOW,
|
Loading…
Reference in New Issue