security-c4po/security-c4po-angular/src/app/pentest/pentest-content/pentest-comments/pentest-comments.component.ts

161 lines
5.6 KiB
TypeScript

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';
import {NbGetters, NbTreeGridDataSource, NbTreeGridDataSourceBuilder} from '@nebular/theme';
import {NotificationService, PopupType} from '@shared/services/notification.service';
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
import {filter, mergeMap, tap} from 'rxjs/operators';
import {
Comment,
CommentDialogBody,
CommentEntry,
transformCommentsToObjectiveEntries,
transformCommentToRequestBody
} 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';
import {FindingDialogComponent} from '@shared/modules/finding-dialog/finding-dialog.component';
import {DialogService} from '@shared/services/dialog-service/dialog.service';
import {CommentDialogService} from '@shared/modules/comment-dialog/service/comment-dialog.service';
import {CommentService} from '@shared/services/comment.service';
import {UpdatePentestComments, UpdatePentestFindings} from '@shared/stores/project-state/project-state.actions';
@UntilDestroy()
@Component({
selector: 'app-pentest-comments',
templateUrl: './pentest-comments.component.html',
styleUrls: ['./pentest-comments.component.scss']
})
export class PentestCommentsComponent implements OnInit {
// 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);
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 commentService: CommentService,
private dataSourceBuilder: NbTreeGridDataSourceBuilder<CommentEntry>,
private notificationService: NotificationService,
private dialogService: DialogService,
private commentDialogService: CommentDialogService,
private store: Store) {
this.dataSource = dataSourceBuilder.create(this.data, this.getters);
}
ngOnInit(): void {
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.commentService.getCommentsByPentestId(this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '')
.pipe(
untilDestroyed(this),
/*filter(isNotNullOrUndefined),*/
tap(() => this.loading$.next(true))
)
.subscribe({
next: (comments: Comment[]) => {
if (comments) {
this.data = transformCommentsToObjectiveEntries(comments);
} else {
this.data = [];
}
this.dataSource.setData(this.data, this.getters);
this.loading$.next(false);
},
error: err => {
console.error(err);
// ToDo: Implement again after proper lazy loading and routing
// this.notificationService.showPopup('comment.popup.not.found', PopupType.FAILURE);
this.loading$.next(false);
}
});
}
onClickAddComment(): void {
this.commentDialogService.openCommentDialog(
FindingDialogComponent,
this.pentestInfo$.getValue().findingIds,
null,
{
closeOnEsc: false,
hasScroll: false,
autoFocus: false,
closeOnBackdropClick: false
}
).pipe(
filter(value => !!value),
tap((value) => console.warn('CommentDialogBody: ', value)),
mergeMap((value: CommentDialogBody) =>
this.commentService.saveComment(
this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '',
transformCommentToRequestBody(value)
)
),
untilDestroyed(this)
).subscribe({
next: (newComment: Comment) => {
this.store.dispatch(new UpdatePentestComments(newComment.id));
this.loadCommentsData();
// Todo: Fix trans keys
this.notificationService.showPopup('comment.popup.save.success', PopupType.SUCCESS);
},
error: err => {
console.error(err);
// Todo: Fix trans keys
this.notificationService.showPopup('comment.popup.save.failed', PopupType.FAILURE);
}
});
}
onClickEditComment(commentEntry): void {
console.info('Coming soon..', commentEntry);
}
onClickDeleteComment(commentEntry): void {
console.info('Coming soon..', commentEntry);
}
// HTML only
isLoading(): Observable<boolean> {
return this.loading$.asObservable();
}
}
enum CommentColumns {
COMMENT_ID = 'commentId',
TITLE = 'title',
DESCRIPTION = 'description',
RELATED_FINDINGS = 'relatedFindings',
ACTIONS = 'actions'
}