fix: As a user I want to refactor the current routing logic

This commit is contained in:
Marcel Haag 2023-02-25 12:35:48 +01:00
parent 79a2493c37
commit 49b1d61647
47 changed files with 473 additions and 118 deletions

View File

@ -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',

View File

@ -28,10 +28,10 @@
<button nbButton hero
status="info"
shape="round"
(click)="onClickExportPentestReport()">
<fa-icon [icon]="fa.faFileExport"
(click)="onClickGeneratePentestReport()">
<fa-icon [icon]="fa.faFileAlt"
class="element-icon fa-lg"></fa-icon>
<span class="element-text">{{ 'global.action.export' | translate }}</span>
<span class="element-text">{{ 'global.action.report' | translate }}</span>
</button>
</nb-action>
</nb-actions>

View File

@ -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(),

View File

@ -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,

View File

@ -3,10 +3,7 @@
<tr nbTreeGridHeaderRow *nbTreeGridHeaderRowDef="columns"></tr>
<tr nbTreeGridRow *nbTreeGridRowDef="let pentest; columns: columns"
class="pentest-cell"
routerLink="pentest"
fragment="{{pentest.data['refNumber']}}"
(click)="selectPentest(pentest.data)"
[skipLocationChange]="true">
(click)="onClickRouteToObjectivePentest(pentest.data)">
</tr>
<!-- Test ID -->
<ng-container [nbTreeGridColumnDef]="columns[0]">
@ -44,11 +41,14 @@
<!-- Findings -->
<ng-container [nbTreeGridColumnDef]="columns[3]">
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
{{ 'pentest.findings' | translate }}
{{ 'pentest.findings&comments' | translate }}
</th>
<td nbTreeGridCell *nbTreeGridCellDef="let pentest">
<app-findig-widget [numberOfFindigs]="pentest.data['findings']"></app-findig-widget>
<!--ToDo: Add comments {{pentest.data['comments'] || '-'}}-->
<div fxLayout="row" fxLayoutGap="0.5rem" fxLayoutAlign="start start">
<app-findig-widget [numberOfFindings]="pentest.data['findings']"></app-findig-widget>
<span> / </span>
<app-comment-widget [numberOfComments]="pentest.data['comments']"></app-comment-widget>
</div>
</td>
</ng-container>
</table>

View File

@ -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<boolean> = new BehaviorSubject<boolean>(true);
columns: Array<ObjectiveColumns> = [ObjectiveColumns.TEST_ID, ObjectiveColumns.TITLE, ObjectiveColumns.STATUS, ObjectiveColumns.FINDINGS];
columns: Array<ObjectiveColumns> = [ObjectiveColumns.TEST_ID, ObjectiveColumns.TITLE, ObjectiveColumns.STATUS, ObjectiveColumns.FINDINGS_AND_COMMENTS];
dataSource: NbTreeGridDataSource<ObjectiveEntry>;
private data: ObjectiveEntry[] = [];
@ -36,7 +37,7 @@ export class ObjectiveTableComponent implements OnInit {
private store: Store,
private pentestService: PentestService,
private dataSourceBuilder: NbTreeGridDataSourceBuilder<ObjectiveEntry>,
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'
}

View File

@ -39,7 +39,7 @@
</th>
<td nbTreeGridCell *nbTreeGridCellDef="let comment" class="related-finding-cell">
<ng-container *ngIf="comment.data['relatedFindings'].length > 0; else NoRelatedFindings">
<app-findig-widget [numberOfFindigs]="comment.data['relatedFindings'].length"></app-findig-widget>
<app-findig-widget [numberOfFindings]="comment.data['relatedFindings'].length"></app-findig-widget>
</ng-container>
<ng-template #NoRelatedFindings>
{{ 'comment.no.relatedFindings' | translate }}

View File

@ -9,7 +9,7 @@
<app-pentest-findings></app-pentest-findings>
</nb-tab>
<nb-tab class="pentest-tabset" tabTitle="{{ 'pentest.comments' | translate }}"
badgeText="{{currentNumberOfComments$.getValue()}}" badgeStatus="info">
badgeText="{{currentNumberOfComments$.getValue()}}" badgeStatus="control">
<app-pentest-comments></app-pentest-comments>
</nb-tab>
</nb-tabset>

View File

@ -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);

View File

@ -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));

View File

@ -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({

View File

@ -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),
}
];

View File

@ -5,13 +5,13 @@
routerLink="id"
fragment="{{project.id}}"
class="project-link project-header"
[state]="{selectedProject:project}">
(click)="onClickRouteToProject(project)">
<h4>{{project?.title}}</h4>
</nb-card-header>
<nb-card-body class="project-link"
routerLink="id"
fragment="{{project.id}}"
[state]="{selectedProject:project}">
(click)="onClickRouteToProject(project)">
<p class="project-subheader">
{{'project.client' | translate}}:
</p>

View File

@ -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<boolean> {
return this.loading$.asObservable();

View File

@ -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,

View File

@ -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),
},
];

View File

@ -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);
});
}
}

View File

@ -216,6 +216,7 @@
"title": "Titel",
"findings": "Funde",
"comments": "Kommentare",
"findings&comments": "Funde & Kommentare",
"status": "Status",
"statusText": {
"not_started": "Nicht angefangen",

View File

@ -216,6 +216,7 @@
"title": "Title",
"findings": "Findings",
"comments": "Comments",
"findings&comments": "Findings & Comments",
"status": "Status",
"statusText": {
"not_started": "Not Started",

View File

@ -0,0 +1,11 @@
import {Loading, LoadingState} from '@shared/models/loading.model';
export function calculatePercentage<T>(download: Loading<T>, event): Loading<T> {
return {
progress: event.total
? Math.round((100 * event.loaded) / event.total)
: download.progress,
state: LoadingState.IN_PROGRESS,
content: null
};
}

View File

@ -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.');
}
}

View File

@ -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<T>(event: HttpEvent<T>): event is HttpResponse<T> {
return event.type === HttpEventType.Response;
}
function isHttpProgressEvent(
event: HttpEvent<unknown>
): event is HttpProgressEvent {
return (
event.type === HttpEventType.DownloadProgress ||
event.type === HttpEventType.UploadProgress
);
}
export function loadContent<T>(
media?: (b: T) => void
): (source: Observable<HttpEvent<T>>) => Observable<Loading<T>> {
return (source: Observable<HttpEvent<T>>) =>
source.pipe(
scan(
(download: Loading<T>, event): Loading<T> => {
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
)
);
}

View File

@ -0,0 +1,11 @@
export class Loading<T = any> {
content?: T | null;
progress: number;
state: LoadingState;
}
export enum LoadingState {
PENDING = 'PENDING',
IN_PROGRESS = 'IN_PROGRESS',
DONE = 'DONE'
}

View File

@ -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

View File

@ -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'
}

View File

@ -8,11 +8,12 @@
{{ 'global.project' | translate }}:
</label>
<b class="project-title">
{{selectedProject$.getValue()?.title}}
{{dialogData.options[0].additionalData.title}}
</b>
<!--Chart Objective Component-->
<div fxLayout="column" fxLayoutGap="2rem" fxLayoutAlign="center center">
<app-objective-chart [projectPentestData]="selectedProject$.getValue().projectPentests"></app-objective-chart>
<app-objective-chart
[projectPentestData]="selectedEvaluatedProject$.getValue().projectPentests"></app-objective-chart>
<span
class="hint"> {{'popup.info' | translate}} {{ 'report.hint' | translate: this.completedProjectPentests$?.getValue() }}
</span>
@ -64,4 +65,4 @@
</nb-card-footer>
</nb-card>
<app-loading-spinner [isLoading$]="isLoading()" *ngIf="isLoading() | async"></app-loading-spinner>
<app-loading-bar *ngIf="downloadPentestReport$ | async as download" [isLoading$]="isLoading()" [loadingProgress]="download?.progress"></app-loading-bar>

View File

@ -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();
});

View File

@ -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<Project> = new BehaviorSubject<Project>(null);
selectedEvaluatedProject$: BehaviorSubject<Project> = new BehaviorSubject<Project>(null);
completedProjectPentests$: BehaviorSubject<any> = new BehaviorSubject<any>({completedObjectivesNumber: 0});
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
// Loading Bar Property
downloadPentestReport$: Observable<Loading<ArrayBuffer>>;
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);

View File

@ -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

View File

@ -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<Loading<ArrayBuffer>> {
return this.http.get(`${this.reportBaseURL}/${projectId}/pdf`,
{
// @ts-ignore
responseType: 'arraybuffer',
reportProgress: true,
observe: 'events'
}
).pipe(loadContent<ArrayBuffer>());
}
/* ToDo: Remove report function without observing report progress
public getReportPDFforProjectById(projectId: string): Observable<ArrayBuffer> {
// @ts-ignore
return this.http.get<ArrayBuffer>(`${this.reportBaseURL}/${projectId}/pdf`, {responseType: 'arraybuffer'})
}
}*/
}
export interface ReportDownloadConfiguration {
options: {
headers: HttpHeaders;
params: HttpParams;
observe: 'events';
reportProgress: true;
responseType: 'arraybuffer';
withCredentials?: boolean;
};
}

View File

@ -0,0 +1,9 @@
<div class="comment-widget">
<ng-container *ngIf="numberOfComments > 0; else noComments">
<fa-icon [icon]="fa.faComment" size="lg" class="comment-icon"></fa-icon>
<span class="comments-count">{{numberOfComments}}</span>
</ng-container>
<ng-template #noComments>
{{'-'}}
</ng-template>
</div>

View File

@ -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);
}
}

View File

@ -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<CommentWidgetComponent>;
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();
});
});

View File

@ -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 {
}
}

View File

@ -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 {
}

View File

@ -1,7 +1,7 @@
<div class="finding-widget">
<ng-container *ngIf="numberOfFindigs > 0; else noFindings">
<ng-container *ngIf="numberOfFindings > 0; else noFindings">
<fa-icon [icon]="fa.faExclamationCircle" size="lg" class="finding-icon"></fa-icon>
<span class="findings-count">{{numberOfFindigs}}</span>
<span class="findings-count">{{numberOfFindings}}</span>
</ng-container>
<ng-template #noFindings>
{{'-'}}

View File

@ -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;

View File

@ -7,13 +7,13 @@ import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
declarations: [
FindigWidgetComponent
],
exports: [
FindigWidgetComponent
],
imports: [
CommonModule,
FontAwesomeModule
]
],
exports: [
FindigWidgetComponent
],
})
export class FindigWidgetModule {
}

View File

@ -1,12 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
declarations: [],
imports: [
CommonModule
]
})
export class FindigModule { }

View File

@ -0,0 +1,7 @@
<div fxFill fxLayout="row" fxLayoutAlign="center center" *ngIf="loading" class="loading-overlay">
<nb-progress-bar class="progress-bar"
status="info"
[value]="loadingProgress"
[displayValue]="false">
</nb-progress-bar>
</div>

View File

@ -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;
}
}

View File

@ -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<LoadingBarComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ LoadingBarComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(LoadingBarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -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<boolean> = 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);
}
}

View File

@ -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 { }

View File

@ -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<PentestReport> = mutableListOf<PentestReport>(),

View File

@ -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<PentestReport>(),

View File

@ -19,7 +19,7 @@
<property name="net.sf.jasperreports.json.field.expression" value="title"/>
<fieldDescription><![CDATA[title]]></fieldDescription>
</field>
<field name="createdAt" class="java.lang.String">
<field name="createdAt" class="java.util.Date">
<property name="net.sf.jasperreports.json.field.expression" value="createdAt"/>
<fieldDescription><![CDATA[createdAt]]></fieldDescription>
</field>
@ -144,7 +144,7 @@
<textElement textAlignment="Right">
<font fontName="SansSerif" size="12" isBold="true" isItalic="true"/>
</textElement>
<textFieldExpression><![CDATA[$F{createdAt}]]></textFieldExpression>
<textFieldExpression><![CDATA[(new SimpleDateFormat("dd/MM/yyyy").format($F{createdAt}))]]></textFieldExpression>
</textField>
</band>
</detail>