diff --git a/security-c4po-angular/src/app/app-routing.module.ts b/security-c4po-angular/src/app/app-routing.module.ts index 3377d5a..2bf96f4 100644 --- a/security-c4po-angular/src/app/app-routing.module.ts +++ b/security-c4po-angular/src/app/app-routing.module.ts @@ -17,6 +17,16 @@ const routes: Routes = [ loadChildren: () => import('./project-overview').then(mod => mod.ProjectOverviewModule), canActivate: [AuthGuardService] }, + { + path: Route.OBJECTIVE_OVERVIEW, + loadChildren: () => import('./project-overview/project').then(mod => mod.ProjectModule), + canActivate: [AuthGuardService] + }, + { + path: Route.PENTEST_OBJECTIVE, + loadChildren: () => import('./pentest').then(mod => mod.PentestModule), + canActivate: [AuthGuardService] + }, // ToDo: Remove after default Keycloak login mask got reworked /*{ path: 'login', diff --git a/security-c4po-angular/src/app/objective-overview/objective-header/objective-header.component.html b/security-c4po-angular/src/app/objective-overview/objective-header/objective-header.component.html index f34c70e..d4cec49 100644 --- a/security-c4po-angular/src/app/objective-overview/objective-header/objective-header.component.html +++ b/security-c4po-angular/src/app/objective-overview/objective-header/objective-header.component.html @@ -28,10 +28,10 @@ diff --git a/security-c4po-angular/src/app/objective-overview/objective-header/objective-header.component.ts b/security-c4po-angular/src/app/objective-overview/objective-header/objective-header.component.ts index 868dc19..b8ca420 100644 --- a/security-c4po-angular/src/app/objective-overview/objective-header/objective-header.component.ts +++ b/security-c4po-angular/src/app/objective-overview/objective-header/objective-header.component.ts @@ -42,7 +42,11 @@ export class ObjectiveHeaderComponent implements OnInit { untilDestroyed(this) ).subscribe({ next: (selectedProject: Project) => { - this.selectedProject$.next(selectedProject); + if (selectedProject) { + this.selectedProject$.next(selectedProject); + } else { + this.router.navigate([Route.PROJECT_OVERVIEW]); + } }, error: err => { console.error(err); @@ -90,7 +94,7 @@ export class ObjectiveHeaderComponent implements OnInit { }); } - onClickExportPentestReport(): void { + onClickGeneratePentestReport(): void { this.exportReportDialogService.openExportReportDialog( ExportReportDialogComponent, this.selectedProject$.getValue(), diff --git a/security-c4po-angular/src/app/objective-overview/objective-overview.module.ts b/security-c4po-angular/src/app/objective-overview/objective-overview.module.ts index 541b6c1..84946c4 100644 --- a/security-c4po-angular/src/app/objective-overview/objective-overview.module.ts +++ b/security-c4po-angular/src/app/objective-overview/objective-overview.module.ts @@ -23,6 +23,8 @@ import {FlexLayoutModule} from '@angular/flex-layout'; import {CommonAppModule} from '../common-app.module'; import {ObjectiveOverviewRoutingModule} from './objective-overview-routing.module'; import {ExportReportDialogModule} from '@shared/modules/export-report-dialog/export-report-dialog.module'; +import {ProjectDialogModule} from '@shared/modules/project-dialog/project-dialog.module'; +import {CommentWidgetModule} from '@shared/widgets/comment-widget/comment-widget.module'; @NgModule({ declarations: [ @@ -43,7 +45,6 @@ import {ExportReportDialogModule} from '@shared/modules/export-report-dialog/exp NbTreeGridModule, TranslateModule, StatusTagModule, - FindigWidgetModule, RouterModule, NbMenuModule, FormsModule, @@ -52,7 +53,11 @@ import {ExportReportDialogModule} from '@shared/modules/export-report-dialog/exp FlexLayoutModule, NbActionsModule, ExportReportDialogModule, - ObjectiveOverviewRoutingModule + ProjectDialogModule, + ObjectiveOverviewRoutingModule, + // Table Widgets + FindigWidgetModule, + CommentWidgetModule ], exports: [ ObjectiveHeaderComponent, diff --git a/security-c4po-angular/src/app/objective-overview/objective-table/objective-table.component.html b/security-c4po-angular/src/app/objective-overview/objective-table/objective-table.component.html index 194518b..d9cf8e6 100644 --- a/security-c4po-angular/src/app/objective-overview/objective-table/objective-table.component.html +++ b/security-c4po-angular/src/app/objective-overview/objective-table/objective-table.component.html @@ -3,10 +3,7 @@ + (click)="onClickRouteToObjectivePentest(pentest.data)"> @@ -44,11 +41,14 @@ - {{ 'pentest.findings' | translate }} + {{ 'pentest.findings&comments' | translate }} - - +
+ + / + +
diff --git a/security-c4po-angular/src/app/objective-overview/objective-table/objective-table.component.ts b/security-c4po-angular/src/app/objective-overview/objective-table/objective-table.component.ts index 3639526..364be14 100644 --- a/security-c4po-angular/src/app/objective-overview/objective-table/objective-table.component.ts +++ b/security-c4po-angular/src/app/objective-overview/objective-table/objective-table.component.ts @@ -3,13 +3,14 @@ import {NbGetters, NbTreeGridDataSource, NbTreeGridDataSourceBuilder} from '@neb import {Pentest, ObjectiveEntry, transformPentestsToObjectiveEntries} from '@shared/models/pentest.model'; import {PentestService} from '@shared/services/api/pentest.service'; import {Store} from '@ngxs/store'; -import {ProjectState} from '@shared/stores/project-state/project-state'; +import {PROJECT_STATE_NAME, ProjectState} from '@shared/stores/project-state/project-state'; import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy'; import {catchError, switchMap, tap} from 'rxjs/operators'; import {BehaviorSubject, Observable, of} from 'rxjs'; import {getTitleKeyForRefNumber} from '@shared/functions/categories/get-title-key-for-ref-number.function'; import {Router} from '@angular/router'; import {ChangePentest} from '@shared/stores/project-state/project-state.actions'; +import {Route} from '@shared/models/route.enum'; @UntilDestroy() @Component({ @@ -20,7 +21,7 @@ import {ChangePentest} from '@shared/stores/project-state/project-state.actions' export class ObjectiveTableComponent implements OnInit { loading$: BehaviorSubject = new BehaviorSubject(true); - columns: Array = [ObjectiveColumns.TEST_ID, ObjectiveColumns.TITLE, ObjectiveColumns.STATUS, ObjectiveColumns.FINDINGS]; + columns: Array = [ObjectiveColumns.TEST_ID, ObjectiveColumns.TITLE, ObjectiveColumns.STATUS, ObjectiveColumns.FINDINGS_AND_COMMENTS]; dataSource: NbTreeGridDataSource; private data: ObjectiveEntry[] = []; @@ -36,7 +37,7 @@ export class ObjectiveTableComponent implements OnInit { private store: Store, private pentestService: PentestService, private dataSourceBuilder: NbTreeGridDataSourceBuilder, - private readonly router: Router + private router: Router ) { this.dataSource = dataSourceBuilder.create(this.data, this.getters); } @@ -53,7 +54,6 @@ export class ObjectiveTableComponent implements OnInit { untilDestroyed(this) ).subscribe({ next: (pentests: Pentest[]) => { - // ToDo: Change assignement here this.pentests$.next(pentests); this.data = transformPentestsToObjectiveEntries(pentests); this.dataSource.setData(this.data, this.getters); @@ -66,16 +66,14 @@ export class ObjectiveTableComponent implements OnInit { }); } - selectPentest(selectedPentest: Pentest): void { - /* ToDo: Include again after fixing pentest route - this.router.navigate([Route.PENTEST]) + onClickRouteToObjectivePentest(selectedPentest: Pentest): void { + this.router.navigate([Route.PENTEST_OBJECTIVE]) .then( () => this.store.reset({ ...this.store.snapshot(), - // [PROJECT_STATE_NAME]: pentest }) ).finally(); - */ + // Change Pentest State const statePentest: Pentest = this.pentests$.getValue().find(pentest => pentest.refNumber === selectedPentest.refNumber); if (statePentest) { this.store.dispatch(new ChangePentest(statePentest)); @@ -111,5 +109,5 @@ enum ObjectiveColumns { TEST_ID = 'testId', TITLE = 'title', STATUS = 'status', - FINDINGS = 'findings' + FINDINGS_AND_COMMENTS = 'findings&comments' } diff --git a/security-c4po-angular/src/app/pentest/pentest-content/pentest-comments/pentest-comments.component.html b/security-c4po-angular/src/app/pentest/pentest-content/pentest-comments/pentest-comments.component.html index a53374e..ec1cb2f 100644 --- a/security-c4po-angular/src/app/pentest/pentest-content/pentest-comments/pentest-comments.component.html +++ b/security-c4po-angular/src/app/pentest/pentest-content/pentest-comments/pentest-comments.component.html @@ -39,7 +39,7 @@ - + {{ 'comment.no.relatedFindings' | translate }} diff --git a/security-c4po-angular/src/app/pentest/pentest-content/pentest-content.component.html b/security-c4po-angular/src/app/pentest/pentest-content/pentest-content.component.html index 3ed76ee..9195082 100644 --- a/security-c4po-angular/src/app/pentest/pentest-content/pentest-content.component.html +++ b/security-c4po-angular/src/app/pentest/pentest-content/pentest-content.component.html @@ -9,7 +9,7 @@ + badgeText="{{currentNumberOfComments$.getValue()}}" badgeStatus="control"> diff --git a/security-c4po-angular/src/app/pentest/pentest-content/pentest-content.component.ts b/security-c4po-angular/src/app/pentest/pentest-content/pentest-content.component.ts index f692de9..2117c49 100644 --- a/security-c4po-angular/src/app/pentest/pentest-content/pentest-content.component.ts +++ b/security-c4po-angular/src/app/pentest/pentest-content/pentest-content.component.ts @@ -7,6 +7,8 @@ import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy'; import {Pentest} from '@shared/models/pentest.model'; import {PentestService} from '@shared/services/api/pentest.service'; import {NotificationService} from '@shared/services/toaster-service/notification.service'; +import {Router} from '@angular/router'; +import {Route} from '@shared/models/route.enum'; @UntilDestroy() @Component({ @@ -25,6 +27,7 @@ export class PentestContentComponent implements OnInit { constructor( private readonly pentestService: PentestService, private notificationService: NotificationService, + private router: Router, private store: Store) { } @@ -33,11 +36,15 @@ export class PentestContentComponent implements OnInit { untilDestroyed(this) ).subscribe({ next: (selectedPentest: Pentest) => { - this.pentest$.next(selectedPentest); - const findings = selectedPentest.findingIds ? selectedPentest.findingIds.length : 0; - this.currentNumberOfFindings$.next(findings); - const comments = selectedPentest.commentIds ? selectedPentest.commentIds.length : 0; - this.currentNumberOfComments$.next(comments); + if (selectedPentest) { + this.pentest$.next(selectedPentest); + const findings = selectedPentest.findingIds ? selectedPentest.findingIds.length : 0; + this.currentNumberOfFindings$.next(findings); + const comments = selectedPentest.commentIds ? selectedPentest.commentIds.length : 0; + this.currentNumberOfComments$.next(comments); + } else { + this.router.navigate([Route.PROJECT_OVERVIEW]); + } }, error: err => { console.error(err); diff --git a/security-c4po-angular/src/app/pentest/pentest-header/pentest-header.component.ts b/security-c4po-angular/src/app/pentest/pentest-header/pentest-header.component.ts index 68d1ac9..d8a32ee 100644 --- a/security-c4po-angular/src/app/pentest/pentest-header/pentest-header.component.ts +++ b/security-c4po-angular/src/app/pentest/pentest-header/pentest-header.component.ts @@ -79,7 +79,7 @@ export class PentestHeaderComponent implements OnInit { onClickRouteBack(): void { // ToDo: Change to Objective Overview after routing is fixed - this.router.navigate([Route.PROJECT_OVERVIEW]) + this.router.navigate([Route.OBJECTIVE_OVERVIEW]) .then( () => { this.store.dispatch(new ChangePentest(null)); diff --git a/security-c4po-angular/src/app/pentest/pentest-routing.module.ts b/security-c4po-angular/src/app/pentest/pentest-routing.module.ts index e46ee7e..ec2a531 100644 --- a/security-c4po-angular/src/app/pentest/pentest-routing.module.ts +++ b/security-c4po-angular/src/app/pentest/pentest-routing.module.ts @@ -1,12 +1,7 @@ import {NgModule} from '@angular/core'; import {RouterModule, Routes} from '@angular/router'; -import {PentestComponent} from './pentest.component'; const routes: Routes = [ - { - path: '', - component: PentestComponent - }, ]; @NgModule({ diff --git a/security-c4po-angular/src/app/project-overview/project-overview-routing.module.ts b/security-c4po-angular/src/app/project-overview/project-overview-routing.module.ts index 2618199..1f9a77b 100644 --- a/security-c4po-angular/src/app/project-overview/project-overview-routing.module.ts +++ b/security-c4po-angular/src/app/project-overview/project-overview-routing.module.ts @@ -1,14 +1,10 @@ import { NgModule } from '@angular/core'; import {RouterModule, Routes} from '@angular/router'; -import {ProjectOverviewComponent} from './project-overview.component'; +import {Route} from '@shared/models/route.enum'; const routes: Routes = [ { - path: '', - component: ProjectOverviewComponent, - }, - { - path: 'id', + path: Route.OBJECTIVE_OVERVIEW, loadChildren: () => import('./project').then(mod => mod.ProjectModule), } ]; diff --git a/security-c4po-angular/src/app/project-overview/project-overview.component.html b/security-c4po-angular/src/app/project-overview/project-overview.component.html index 2cbcc18..be1d742 100644 --- a/security-c4po-angular/src/app/project-overview/project-overview.component.html +++ b/security-c4po-angular/src/app/project-overview/project-overview.component.html @@ -5,13 +5,13 @@ routerLink="id" fragment="{{project.id}}" class="project-link project-header" - [state]="{selectedProject:project}"> + (click)="onClickRouteToProject(project)">

{{project?.title}}

+ (click)="onClickRouteToProject(project)">

{{'project.client' | translate}}:

diff --git a/security-c4po-angular/src/app/project-overview/project-overview.component.ts b/security-c4po-angular/src/app/project-overview/project-overview.component.ts index cefc269..9865178 100644 --- a/security-c4po-angular/src/app/project-overview/project-overview.component.ts +++ b/security-c4po-angular/src/app/project-overview/project-overview.component.ts @@ -9,6 +9,10 @@ import {catchError, filter, mergeMap, switchMap, tap} from 'rxjs/operators'; import {DialogService} from '@shared/services/dialog-service/dialog.service'; import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component'; import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service'; +import {Router} from '@angular/router'; +import {Route} from '@shared/models/route.enum'; +import {InitProjectState} from '@shared/stores/project-state/project-state.actions'; +import {Store} from '@ngxs/store'; @UntilDestroy() @Component({ @@ -25,6 +29,8 @@ export class ProjectOverviewComponent implements OnInit { constructor( private readonly notificationService: NotificationService, + private store: Store, + private router: Router, private projectService: ProjectService, private dialogService: DialogService, private projectDialogService: ProjectDialogService) { @@ -163,6 +169,18 @@ export class ProjectOverviewComponent implements OnInit { } } + onClickRouteToProject(project): void { + this.router.navigate([Route.OBJECTIVE_OVERVIEW]).then(() => { + this.store.dispatch(new InitProjectState( + project, + [], + [] + )).pipe(untilDestroyed(this)).subscribe(); + }, err => { + console.error(err); + }); + } + // HTML only isLoading(): Observable { return this.loading$.asObservable(); diff --git a/security-c4po-angular/src/app/project-overview/project-overview.module.ts b/security-c4po-angular/src/app/project-overview/project-overview.module.ts index bc89afd..c30838a 100644 --- a/security-c4po-angular/src/app/project-overview/project-overview.module.ts +++ b/security-c4po-angular/src/app/project-overview/project-overview.module.ts @@ -11,6 +11,7 @@ import {ProjectDialogModule} from '@shared/modules/project-dialog/project-dialog import {CommonAppModule} from '../common-app.module'; import {ConfirmDialogModule} from '@shared/modules/confirm-dialog/confirm-dialog.module'; import {SecurityConfirmDialogModule} from '@shared/modules/security-confirm-dialog/security-confirm-dialog.module'; +import {RouterModule} from '@angular/router'; @NgModule({ declarations: [ @@ -20,6 +21,10 @@ import {SecurityConfirmDialogModule} from '@shared/modules/security-confirm-dial imports: [ CommonModule, CommonAppModule, + RouterModule.forChild([{ + path: '', + component: ProjectOverviewComponent + }]), NbCardModule, NbButtonModule, NbProgressBarModule, diff --git a/security-c4po-angular/src/app/project-overview/project/project-routing.module.ts b/security-c4po-angular/src/app/project-overview/project/project-routing.module.ts index 820ac2c..5972e2c 100644 --- a/security-c4po-angular/src/app/project-overview/project/project-routing.module.ts +++ b/security-c4po-angular/src/app/project-overview/project/project-routing.module.ts @@ -1,14 +1,10 @@ import {NgModule} from '@angular/core'; import {RouterModule, Routes} from '@angular/router'; -import {ProjectComponent} from './project.component'; +import {Route} from '@shared/models/route.enum'; const routes: Routes = [ { - path: '', - component: ProjectComponent - }, - { - path: 'pentest', + path: Route.PENTEST_OBJECTIVE, loadChildren: () => import('../../pentest').then(mod => mod.PentestModule), }, ]; diff --git a/security-c4po-angular/src/app/project-overview/project/project.component.ts b/security-c4po-angular/src/app/project-overview/project/project.component.ts index b121c95..4ace5fb 100644 --- a/security-c4po-angular/src/app/project-overview/project/project.component.ts +++ b/security-c4po-angular/src/app/project-overview/project/project.component.ts @@ -1,9 +1,9 @@ import {Component, OnInit} from '@angular/core'; import {Store} from '@ngxs/store'; -import {InitProjectState} from '@shared/stores/project-state/project-state.actions'; import {Router} from '@angular/router'; -import {Route} from '@shared/models/route.enum'; import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy'; +import {Route} from '@shared/models/route.enum'; +import {ProjectState} from '@shared/stores/project-state/project-state'; import {Project} from '@shared/models/project.model'; @UntilDestroy() @@ -20,19 +20,23 @@ export class ProjectComponent implements OnInit { } ngOnInit(): void { - if (history?.state && 'selectedProject' in history?.state) { - this.initProjectStore(); - } else { - this.router.navigate([Route.PROJECT_OVERVIEW]).finally(); - } + this.store.select(ProjectState.project).pipe( + untilDestroyed(this) + ).subscribe({ + next: (selectedProject: Project) => { + this.initProjectStore(); + }, + error: err => { + console.error(err); + } + }); } private initProjectStore(): void { - const project: Project = history?.state?.selectedProject ? history?.state?.selectedProject : null; - this.store.dispatch(new InitProjectState( - project, - [], - [] - )).pipe(untilDestroyed(this)).subscribe(); + this.router.navigate([Route.OBJECTIVE_OVERVIEW]).then(() => { + }, err => { + this.router.navigate([Route.PROJECT_OVERVIEW]); + console.error(err); + }); } } diff --git a/security-c4po-angular/src/assets/i18n/de-DE.json b/security-c4po-angular/src/assets/i18n/de-DE.json index fa93e35..3937b6d 100644 --- a/security-c4po-angular/src/assets/i18n/de-DE.json +++ b/security-c4po-angular/src/assets/i18n/de-DE.json @@ -216,6 +216,7 @@ "title": "Titel", "findings": "Funde", "comments": "Kommentare", + "findings&comments": "Funde & Kommentare", "status": "Status", "statusText": { "not_started": "Nicht angefangen", diff --git a/security-c4po-angular/src/assets/i18n/en-US.json b/security-c4po-angular/src/assets/i18n/en-US.json index 3a38877..a5d3455 100644 --- a/security-c4po-angular/src/assets/i18n/en-US.json +++ b/security-c4po-angular/src/assets/i18n/en-US.json @@ -216,6 +216,7 @@ "title": "Title", "findings": "Findings", "comments": "Comments", + "findings&comments": "Findings & Comments", "status": "Status", "statusText": { "not_started": "Not Started", diff --git a/security-c4po-angular/src/shared/functions/calculate-percentage.ts b/security-c4po-angular/src/shared/functions/calculate-percentage.ts new file mode 100644 index 0000000..5b24cb5 --- /dev/null +++ b/security-c4po-angular/src/shared/functions/calculate-percentage.ts @@ -0,0 +1,11 @@ +import {Loading, LoadingState} from '@shared/models/loading.model'; + +export function calculatePercentage(download: Loading, event): Loading { + return { + progress: event.total + ? Math.round((100 * event.loaded) / event.total) + : download.progress, + state: LoadingState.IN_PROGRESS, + content: null + }; +} diff --git a/security-c4po-angular/src/shared/functions/download-file.function.ts b/security-c4po-angular/src/shared/functions/download-file.function.ts index 43e6f03..3900f08 100644 --- a/security-c4po-angular/src/shared/functions/download-file.function.ts +++ b/security-c4po-angular/src/shared/functions/download-file.function.ts @@ -8,6 +8,7 @@ export function downloadFile(data: any, type: string): void { const url = window.URL.createObjectURL(blob); const pwa = window.open(url); if (!pwa || pwa.closed || typeof pwa.closed === 'undefined') { - alert('Please disable your Pop-up blocker and try again.'); + // ToDo: Do not use! Alert bugs loading progress bar + // alert('Please disable your Pop-up blocker and try again.'); } } diff --git a/security-c4po-angular/src/shared/functions/load-content.ts b/security-c4po-angular/src/shared/functions/load-content.ts new file mode 100644 index 0000000..f7d48c2 --- /dev/null +++ b/security-c4po-angular/src/shared/functions/load-content.ts @@ -0,0 +1,49 @@ +import {HttpEvent, HttpEventType, HttpProgressEvent, HttpResponse} from '@angular/common/http'; +import {Observable} from 'rxjs'; +import {distinctUntilChanged, scan} from 'rxjs/operators'; +import {calculatePercentage} from '@shared/functions/calculate-percentage'; +import {Loading, LoadingState} from '@shared/models/loading.model'; + +function isHttpResponse(event: HttpEvent): event is HttpResponse { + return event.type === HttpEventType.Response; +} + +function isHttpProgressEvent( + event: HttpEvent +): event is HttpProgressEvent { + return ( + event.type === HttpEventType.DownloadProgress || + event.type === HttpEventType.UploadProgress + ); +} + +export function loadContent( + media?: (b: T) => void +): (source: Observable>) => Observable> { + return (source: Observable>) => + source.pipe( + scan( + (download: Loading, event): Loading => { + if (isHttpProgressEvent(event)) { + return calculatePercentage(download, event); + } + if (isHttpResponse(event)) { + if (media) { + media(event.body); + } + return { + progress: 100, + state: LoadingState.DONE, + content: event.body + }; + } + return download; + }, + {state: LoadingState.PENDING, progress: 0, content: null} + ), + distinctUntilChanged((a, b) => a.state === b.state + && a.progress === b.progress + && a.content === b.content + ) + ); +} diff --git a/security-c4po-angular/src/shared/models/loading.model.ts b/security-c4po-angular/src/shared/models/loading.model.ts new file mode 100644 index 0000000..7207bf6 --- /dev/null +++ b/security-c4po-angular/src/shared/models/loading.model.ts @@ -0,0 +1,11 @@ +export class Loading { + content?: T | null; + progress: number; + state: LoadingState; +} + +export enum LoadingState { + PENDING = 'PENDING', + IN_PROGRESS = 'IN_PROGRESS', + DONE = 'DONE' +} diff --git a/security-c4po-angular/src/shared/models/pentest.model.ts b/security-c4po-angular/src/shared/models/pentest.model.ts index 9f36c16..0673585 100644 --- a/security-c4po-angular/src/shared/models/pentest.model.ts +++ b/security-c4po-angular/src/shared/models/pentest.model.ts @@ -63,6 +63,7 @@ export function transformPentestsToObjectiveEntries(pentests: Pentest[]): Object refNumber: value.refNumber, status: value.status, findings: value.findingIds ? value.findingIds.length : 0, + comments: value.commentIds ? value.commentIds.length : 0, kind: value.childEntries ? 'dir' : 'cell', childEntries: value.childEntries ? value.childEntries : null, expanded: !!value.childEntries diff --git a/security-c4po-angular/src/shared/models/route.enum.ts b/security-c4po-angular/src/shared/models/route.enum.ts index ed0ce6c..eedc634 100644 --- a/security-c4po-angular/src/shared/models/route.enum.ts +++ b/security-c4po-angular/src/shared/models/route.enum.ts @@ -1,5 +1,8 @@ export enum Route { + LOGIN = 'login', HOME = 'home', PROJECT_OVERVIEW = 'projects', + OBJECTIVE_OVERVIEW = 'project/objectives', + PENTEST_OBJECTIVE = 'project/objectives/pentest', PENTEST_OVERVIEW = 'pentest' } diff --git a/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.html b/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.html index 1c2130b..9debb2f 100644 --- a/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.html +++ b/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.html @@ -8,11 +8,12 @@ {{ 'global.project' | translate }}: - {{selectedProject$.getValue()?.title}} + {{dialogData.options[0].additionalData.title}}
- + {{'popup.info' | translate}} {{ 'report.hint' | translate: this.completedProjectPentests$?.getValue() }} @@ -64,4 +65,4 @@ - + diff --git a/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.spec.ts b/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.spec.ts index 4c9d806..4640d90 100644 --- a/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.spec.ts +++ b/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.spec.ts @@ -82,7 +82,7 @@ describe('ExportReportDialogComponent', () => { TestBed.overrideProvider(NB_DIALOG_CONFIG, {useValue: mockedExportPentestDialogData}); fixture = TestBed.createComponent(ExportReportDialogComponent); component = fixture.componentInstance; - component.selectedProject$.next(mockProject); + component.selectedEvaluatedProject$.next(mockProject); fixture.detectChanges(); }); diff --git a/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.ts b/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.ts index c2cf810..f8570d8 100644 --- a/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.ts +++ b/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.component.ts @@ -10,8 +10,10 @@ import {BehaviorSubject, Observable} from 'rxjs'; import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service'; import {ProjectService} from '@shared/services/api/project.service'; import {PentestStatus} from '@shared/models/pentest-status.model'; -import {tap} from 'rxjs/operators'; +import {shareReplay, tap} from 'rxjs/operators'; import {downloadFile} from '@shared/functions/download-file.function'; +import {Loading, LoadingState} from '@shared/models/loading.model'; +import {HttpEvent, HttpEventType} from '@angular/common/http'; @Component({ selector: 'app-export-report-dialog', @@ -41,10 +43,14 @@ export class ExportReportDialogComponent implements OnInit { dialogData: GenericDialogData; - selectedProject$: BehaviorSubject = new BehaviorSubject(null); + selectedEvaluatedProject$: BehaviorSubject = new BehaviorSubject(null); completedProjectPentests$: BehaviorSubject = new BehaviorSubject({completedObjectivesNumber: 0}); loading$: BehaviorSubject = new BehaviorSubject(true); + // Loading Bar Property + downloadPentestReport$: Observable>; + progress = 0; + ngOnInit(): void { this.dialogData = this.data; this.loadEvaluatedProject(); @@ -61,7 +67,7 @@ export class ExportReportDialogComponent implements OnInit { ) .subscribe({ next: (project: Project) => { - this.selectedProject$.next(project); + this.selectedEvaluatedProject$.next(project); const completedPentestObjectives = project.projectPentests.filter(pentest => pentest.status === PentestStatus.COMPLETED); this.completedProjectPentests$.next({completedObjectivesNumber: completedPentestObjectives.length}); this.loading$.next(false); @@ -75,18 +81,26 @@ export class ExportReportDialogComponent implements OnInit { } onClickExport(reportFormat: string, reportLanguage: string): void { + // Get project id from dialog data + const projectId = this.dialogData.options[0].additionalData.id; // Loading is true as long as there is a response from the reporting service this.loading$.next(true); // Export pentest in choosen format switch (reportFormat) { case ExportFormatOptions.PDF: { - this.reportingService.getReportPDFforProjectById(this.selectedProject$.getValue().id).pipe( - untilDestroyed(this) - ).subscribe({ + // @ts-ignore + this.downloadPentestReport$ = this.reportingService.getReportPDFforProjectById(projectId) + .pipe( + shareReplay(), + untilDestroyed(this) + ); + this.downloadPentestReport$.subscribe({ next: (response) => { - downloadFile(response, 'application/pdf'); - this.loading$.next(false); - this.notificationService.showPopup('report.popup.generation.success', PopupType.SUCCESS); + if (response.state === LoadingState.DONE) { + downloadFile(response.content, 'application/pdf'); + this.loading$.next(false); + this.notificationService.showPopup('report.popup.generation.success', PopupType.SUCCESS); + } }, error: error => { console.error(error); diff --git a/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.module.ts b/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.module.ts index 3e94bc2..5f7ac8f 100644 --- a/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.module.ts +++ b/security-c4po-angular/src/shared/modules/export-report-dialog/export-report-dialog.module.ts @@ -10,25 +10,27 @@ import {ExportReportDialogComponent} from '@shared/modules/export-report-dialog/ import {ExportReportDialogService} from '@shared/modules/export-report-dialog/service/export-report-dialog.service'; import {ReportingService} from '@shared/services/reporting/reporting.service'; import {ObjectiveChartModule} from '@shared/modules/objective-chart/objective-chart.module'; +import {LoadingBarModule} from '@shared/widgets/loading-bar/loading-bar.module'; @NgModule({ declarations: [ ExportReportDialogComponent ], - imports: [ - CommonModule, - CommonAppModule, - NbCardModule, - NbButtonModule, - NbFormFieldModule, - NbInputModule, - FlexLayoutModule, - FontAwesomeModule, - TranslateModule, - ReactiveFormsModule, - NbRadioModule, - ObjectiveChartModule - ], + imports: [ + CommonModule, + CommonAppModule, + NbCardModule, + NbButtonModule, + NbFormFieldModule, + NbInputModule, + FlexLayoutModule, + FontAwesomeModule, + TranslateModule, + ReactiveFormsModule, + NbRadioModule, + ObjectiveChartModule, + LoadingBarModule + ], providers: [ ExportReportDialogService, ReportingService diff --git a/security-c4po-angular/src/shared/services/reporting/reporting.service.ts b/security-c4po-angular/src/shared/services/reporting/reporting.service.ts index 1d04fe8..b61468a 100644 --- a/security-c4po-angular/src/shared/services/reporting/reporting.service.ts +++ b/security-c4po-angular/src/shared/services/reporting/reporting.service.ts @@ -1,7 +1,9 @@ import {Injectable} from '@angular/core'; import {environment} from '../../../environments/environment'; -import {HttpClient} from '@angular/common/http'; -import {Observable} from 'rxjs'; +import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http'; +import {BehaviorSubject, Observable} from 'rxjs'; +import {Loading} from '@shared/models/loading.model'; +import { loadContent } from '@shared/functions/load-content'; @Injectable({ providedIn: 'root' @@ -16,8 +18,31 @@ export class ReportingService { /** * Get PDF Report by project id */ + public getReportPDFforProjectById(projectId: string): Observable> { + return this.http.get(`${this.reportBaseURL}/${projectId}/pdf`, + { + // @ts-ignore + responseType: 'arraybuffer', + reportProgress: true, + observe: 'events' + } + ).pipe(loadContent()); + } + + /* ToDo: Remove report function without observing report progress public getReportPDFforProjectById(projectId: string): Observable { // @ts-ignore return this.http.get(`${this.reportBaseURL}/${projectId}/pdf`, {responseType: 'arraybuffer'}) - } + }*/ +} + +export interface ReportDownloadConfiguration { + options: { + headers: HttpHeaders; + params: HttpParams; + observe: 'events'; + reportProgress: true; + responseType: 'arraybuffer'; + withCredentials?: boolean; + }; } diff --git a/security-c4po-angular/src/shared/widgets/comment-widget/comment-widget.component.html b/security-c4po-angular/src/shared/widgets/comment-widget/comment-widget.component.html new file mode 100644 index 0000000..0589e8f --- /dev/null +++ b/security-c4po-angular/src/shared/widgets/comment-widget/comment-widget.component.html @@ -0,0 +1,9 @@ +
+ + + {{numberOfComments}} + + + {{'-'}} + +
diff --git a/security-c4po-angular/src/shared/widgets/comment-widget/comment-widget.component.scss b/security-c4po-angular/src/shared/widgets/comment-widget/comment-widget.component.scss new file mode 100644 index 0000000..4431b1e --- /dev/null +++ b/security-c4po-angular/src/shared/widgets/comment-widget/comment-widget.component.scss @@ -0,0 +1,14 @@ +@import '../../../assets/@theme/styles/themes'; + +.comment-widget { + align-items: center; + + .comment-icon { + color: nb-theme(basic-text-color); + } + + .comments-count { + margin-left: 0.45rem; + color: nb-theme(basic-text-color); + } +} diff --git a/security-c4po-angular/src/shared/widgets/comment-widget/comment-widget.component.spec.ts b/security-c4po-angular/src/shared/widgets/comment-widget/comment-widget.component.spec.ts new file mode 100644 index 0000000..8f9873a --- /dev/null +++ b/security-c4po-angular/src/shared/widgets/comment-widget/comment-widget.component.spec.ts @@ -0,0 +1,31 @@ +import {ComponentFixture, TestBed} from '@angular/core/testing'; + +import {CommentWidgetComponent} from './comment-widget.component'; +import {FontAwesomeTestingModule} from '@fortawesome/angular-fontawesome/testing'; + +describe('CommentWidgetComponent', () => { + let component: CommentWidgetComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ + CommentWidgetComponent + ], + imports: [ + FontAwesomeTestingModule + ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CommentWidgetComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/security-c4po-angular/src/shared/widgets/comment-widget/comment-widget.component.ts b/security-c4po-angular/src/shared/widgets/comment-widget/comment-widget.component.ts new file mode 100644 index 0000000..800f86b --- /dev/null +++ b/security-c4po-angular/src/shared/widgets/comment-widget/comment-widget.component.ts @@ -0,0 +1,20 @@ +import {Component, Input, OnInit} from '@angular/core'; +import * as FA from '@fortawesome/free-solid-svg-icons'; + +@Component({ + selector: 'app-comment-widget', + templateUrl: './comment-widget.component.html', + styleUrls: ['./comment-widget.component.scss'] +}) +export class CommentWidgetComponent implements OnInit { + @Input() numberOfComments: number; + + // HTML only + readonly fa = FA; + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/security-c4po-angular/src/shared/widgets/comment-widget/comment-widget.module.ts b/security-c4po-angular/src/shared/widgets/comment-widget/comment-widget.module.ts new file mode 100644 index 0000000..b966531 --- /dev/null +++ b/security-c4po-angular/src/shared/widgets/comment-widget/comment-widget.module.ts @@ -0,0 +1,20 @@ +import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {FontAwesomeModule} from '@fortawesome/angular-fontawesome'; +import {CommentWidgetComponent} from '@shared/widgets/comment-widget/comment-widget.component'; + + +@NgModule({ + declarations: [ + CommentWidgetComponent + ], + imports: [ + CommonModule, + FontAwesomeModule + ], + exports: [ + CommentWidgetComponent + ] +}) +export class CommentWidgetModule { +} diff --git a/security-c4po-angular/src/shared/widgets/findig-widget/findig-widget.component.html b/security-c4po-angular/src/shared/widgets/findig-widget/findig-widget.component.html index 2e1bd5e..4c6de06 100644 --- a/security-c4po-angular/src/shared/widgets/findig-widget/findig-widget.component.html +++ b/security-c4po-angular/src/shared/widgets/findig-widget/findig-widget.component.html @@ -1,7 +1,7 @@
- + - {{numberOfFindigs}} + {{numberOfFindings}} {{'-'}} diff --git a/security-c4po-angular/src/shared/widgets/findig-widget/findig-widget.component.ts b/security-c4po-angular/src/shared/widgets/findig-widget/findig-widget.component.ts index ae8d179..d56e53a 100644 --- a/security-c4po-angular/src/shared/widgets/findig-widget/findig-widget.component.ts +++ b/security-c4po-angular/src/shared/widgets/findig-widget/findig-widget.component.ts @@ -7,7 +7,7 @@ import * as FA from '@fortawesome/free-solid-svg-icons'; styleUrls: ['./findig-widget.component.scss'] }) export class FindigWidgetComponent implements OnInit { - @Input() numberOfFindigs: number; + @Input() numberOfFindings: number; // HTML only readonly fa = FA; diff --git a/security-c4po-angular/src/shared/widgets/findig-widget/findig-widget.module.ts b/security-c4po-angular/src/shared/widgets/findig-widget/findig-widget.module.ts index 25ae0cb..dec481c 100644 --- a/security-c4po-angular/src/shared/widgets/findig-widget/findig-widget.module.ts +++ b/security-c4po-angular/src/shared/widgets/findig-widget/findig-widget.module.ts @@ -7,13 +7,13 @@ import {FontAwesomeModule} from '@fortawesome/angular-fontawesome'; declarations: [ FindigWidgetComponent ], - exports: [ - FindigWidgetComponent - ], imports: [ CommonModule, FontAwesomeModule - ] + ], + exports: [ + FindigWidgetComponent + ], }) export class FindigWidgetModule { } diff --git a/security-c4po-angular/src/shared/widgets/findig-widget/findig.module.ts b/security-c4po-angular/src/shared/widgets/findig-widget/findig.module.ts deleted file mode 100644 index 075b008..0000000 --- a/security-c4po-angular/src/shared/widgets/findig-widget/findig.module.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; - - - -@NgModule({ - declarations: [], - imports: [ - CommonModule - ] -}) -export class FindigModule { } diff --git a/security-c4po-angular/src/shared/widgets/loading-bar/loading-bar.component.html b/security-c4po-angular/src/shared/widgets/loading-bar/loading-bar.component.html new file mode 100644 index 0000000..1f71e3b --- /dev/null +++ b/security-c4po-angular/src/shared/widgets/loading-bar/loading-bar.component.html @@ -0,0 +1,7 @@ +
+ + +
diff --git a/security-c4po-angular/src/shared/widgets/loading-bar/loading-bar.component.scss b/security-c4po-angular/src/shared/widgets/loading-bar/loading-bar.component.scss new file mode 100644 index 0000000..ad988b5 --- /dev/null +++ b/security-c4po-angular/src/shared/widgets/loading-bar/loading-bar.component.scss @@ -0,0 +1,22 @@ +@import '../../../assets/@theme/styles/themes'; + +.loading-overlay { + position: fixed; + top: 0; + left: 0; + + width: 100%; + height: 100%; + z-index: 1000; + + background-color: nb-theme(layout-background-color); + opacity: .5; + + .progress-bar { + width: 80%; + height: max-content; + // Overwrites the loading overlay + opacity: 100 !important; + z-index: 1001 !important; + } +} diff --git a/security-c4po-angular/src/shared/widgets/loading-bar/loading-bar.component.spec.ts b/security-c4po-angular/src/shared/widgets/loading-bar/loading-bar.component.spec.ts new file mode 100644 index 0000000..1f6e481 --- /dev/null +++ b/security-c4po-angular/src/shared/widgets/loading-bar/loading-bar.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoadingBarComponent } from './loading-bar.component'; + +describe('LoadingBarComponent', () => { + let component: LoadingBarComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ LoadingBarComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(LoadingBarComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/security-c4po-angular/src/shared/widgets/loading-bar/loading-bar.component.ts b/security-c4po-angular/src/shared/widgets/loading-bar/loading-bar.component.ts new file mode 100644 index 0000000..aa66912 --- /dev/null +++ b/security-c4po-angular/src/shared/widgets/loading-bar/loading-bar.component.ts @@ -0,0 +1,35 @@ +import {ChangeDetectorRef, Component, Input, OnChanges, OnInit} from '@angular/core'; +import {Observable, of} from 'rxjs'; +import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy'; + +@UntilDestroy() +@Component({ + selector: 'app-loading-bar', + templateUrl: './loading-bar.component.html', + styleUrls: ['./loading-bar.component.scss'] +}) +export class LoadingBarComponent implements OnInit, OnChanges { + + @Input() isLoading$: Observable = of(false); + @Input() loadingProgress = 0; + + loading: boolean; + + constructor() { + } + + ngOnInit(): void { + this.loading = false; + this.isLoading$ + .pipe(untilDestroyed(this)) + .subscribe((value: boolean): void => { + this.loading = value; + }); + } + + ngOnChanges(): void { + // tslint:disable-next-line:no-console + console.info('Current loading progress: ', this.loadingProgress); + } + +} diff --git a/security-c4po-angular/src/shared/widgets/loading-bar/loading-bar.module.ts b/security-c4po-angular/src/shared/widgets/loading-bar/loading-bar.module.ts new file mode 100644 index 0000000..2ed3cc7 --- /dev/null +++ b/security-c4po-angular/src/shared/widgets/loading-bar/loading-bar.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import {LoadingBarComponent} from '@shared/widgets/loading-bar/loading-bar.component'; +import {NbProgressBarModule} from '@nebular/theme'; +import {FlexLayoutModule} from '@angular/flex-layout'; + +@NgModule({ + declarations: [ + LoadingBarComponent + ], + imports: [ + CommonModule, + NbProgressBarModule, + FlexLayoutModule + ], + exports: [ + LoadingBarComponent + ] +}) +export class LoadingBarModule { } diff --git a/security-c4po-reporting/src/main/kotlin/com/securityc4po/reporting/remote/model/ProjectReport.kt b/security-c4po-reporting/src/main/kotlin/com/securityc4po/reporting/remote/model/ProjectReport.kt index 22709d4..bfd8e1d 100644 --- a/security-c4po-reporting/src/main/kotlin/com/securityc4po/reporting/remote/model/ProjectReport.kt +++ b/security-c4po-reporting/src/main/kotlin/com/securityc4po/reporting/remote/model/ProjectReport.kt @@ -1,10 +1,12 @@ package com.securityc4po.reporting.remote.model +import java.util.Date + data class ProjectReport( val id: String, val client: String, val title: String, - val createdAt: String, + val createdAt: Date, val tester: String, val summary: String? = null, var projectPentestReport: MutableList = mutableListOf(), diff --git a/security-c4po-reporting/src/main/kotlin/com/securityc4po/reporting/remote/model/api/Project.kt b/security-c4po-reporting/src/main/kotlin/com/securityc4po/reporting/remote/model/api/Project.kt index 3544ac8..c6effb7 100644 --- a/security-c4po-reporting/src/main/kotlin/com/securityc4po/reporting/remote/model/api/Project.kt +++ b/security-c4po-reporting/src/main/kotlin/com/securityc4po/reporting/remote/model/api/Project.kt @@ -2,6 +2,9 @@ package com.securityc4po.reporting.remote.model.api import com.securityc4po.reporting.remote.model.PentestReport import com.securityc4po.reporting.remote.model.ProjectReport +import java.time.Instant +import java.util.Date + data class Project( val id: String, @@ -19,7 +22,8 @@ fun Project.toProjectReport(): ProjectReport { id = this.id, client = this.client, title = this.title, - createdAt = this.createdAt, + /* Use the time of report creation */ + createdAt = Date.from(Instant.now()), tester = this.tester, summary = this.summary, projectPentestReport = mutableListOf(), diff --git a/security-c4po-reporting/src/main/resources/jasper/reports/c4po_cover.jrxml b/security-c4po-reporting/src/main/resources/jasper/reports/c4po_cover.jrxml index 580c5e5..5b82a52 100644 --- a/security-c4po-reporting/src/main/resources/jasper/reports/c4po_cover.jrxml +++ b/security-c4po-reporting/src/main/resources/jasper/reports/c4po_cover.jrxml @@ -19,7 +19,7 @@ - + @@ -144,7 +144,7 @@ - +