fix: As a user I want to refactor the current routing logic
This commit is contained in:
parent
79a2493c37
commit
4ca2828e0f
|
@ -17,6 +17,16 @@ const routes: Routes = [
|
||||||
loadChildren: () => import('./project-overview').then(mod => mod.ProjectOverviewModule),
|
loadChildren: () => import('./project-overview').then(mod => mod.ProjectOverviewModule),
|
||||||
canActivate: [AuthGuardService]
|
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
|
// ToDo: Remove after default Keycloak login mask got reworked
|
||||||
/*{
|
/*{
|
||||||
path: 'login',
|
path: 'login',
|
||||||
|
|
|
@ -28,10 +28,10 @@
|
||||||
<button nbButton hero
|
<button nbButton hero
|
||||||
status="info"
|
status="info"
|
||||||
shape="round"
|
shape="round"
|
||||||
(click)="onClickExportPentestReport()">
|
(click)="onClickGeneratePentestReport()">
|
||||||
<fa-icon [icon]="fa.faFileExport"
|
<fa-icon [icon]="fa.faFileAlt"
|
||||||
class="element-icon fa-lg"></fa-icon>
|
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>
|
</button>
|
||||||
</nb-action>
|
</nb-action>
|
||||||
</nb-actions>
|
</nb-actions>
|
||||||
|
|
|
@ -42,7 +42,11 @@ export class ObjectiveHeaderComponent implements OnInit {
|
||||||
untilDestroyed(this)
|
untilDestroyed(this)
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: (selectedProject: Project) => {
|
next: (selectedProject: Project) => {
|
||||||
this.selectedProject$.next(selectedProject);
|
if (selectedProject) {
|
||||||
|
this.selectedProject$.next(selectedProject);
|
||||||
|
} else {
|
||||||
|
this.router.navigate([Route.PROJECT_OVERVIEW]);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
error: err => {
|
error: err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
@ -90,7 +94,7 @@ export class ObjectiveHeaderComponent implements OnInit {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickExportPentestReport(): void {
|
onClickGeneratePentestReport(): void {
|
||||||
this.exportReportDialogService.openExportReportDialog(
|
this.exportReportDialogService.openExportReportDialog(
|
||||||
ExportReportDialogComponent,
|
ExportReportDialogComponent,
|
||||||
this.selectedProject$.getValue(),
|
this.selectedProject$.getValue(),
|
||||||
|
|
|
@ -23,6 +23,8 @@ import {FlexLayoutModule} from '@angular/flex-layout';
|
||||||
import {CommonAppModule} from '../common-app.module';
|
import {CommonAppModule} from '../common-app.module';
|
||||||
import {ObjectiveOverviewRoutingModule} from './objective-overview-routing.module';
|
import {ObjectiveOverviewRoutingModule} from './objective-overview-routing.module';
|
||||||
import {ExportReportDialogModule} from '@shared/modules/export-report-dialog/export-report-dialog.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({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@ -43,7 +45,6 @@ import {ExportReportDialogModule} from '@shared/modules/export-report-dialog/exp
|
||||||
NbTreeGridModule,
|
NbTreeGridModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
StatusTagModule,
|
StatusTagModule,
|
||||||
FindigWidgetModule,
|
|
||||||
RouterModule,
|
RouterModule,
|
||||||
NbMenuModule,
|
NbMenuModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
|
@ -52,7 +53,11 @@ import {ExportReportDialogModule} from '@shared/modules/export-report-dialog/exp
|
||||||
FlexLayoutModule,
|
FlexLayoutModule,
|
||||||
NbActionsModule,
|
NbActionsModule,
|
||||||
ExportReportDialogModule,
|
ExportReportDialogModule,
|
||||||
ObjectiveOverviewRoutingModule
|
ProjectDialogModule,
|
||||||
|
ObjectiveOverviewRoutingModule,
|
||||||
|
// Table Widgets
|
||||||
|
FindigWidgetModule,
|
||||||
|
CommentWidgetModule
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
ObjectiveHeaderComponent,
|
ObjectiveHeaderComponent,
|
||||||
|
|
|
@ -3,10 +3,7 @@
|
||||||
<tr nbTreeGridHeaderRow *nbTreeGridHeaderRowDef="columns"></tr>
|
<tr nbTreeGridHeaderRow *nbTreeGridHeaderRowDef="columns"></tr>
|
||||||
<tr nbTreeGridRow *nbTreeGridRowDef="let pentest; columns: columns"
|
<tr nbTreeGridRow *nbTreeGridRowDef="let pentest; columns: columns"
|
||||||
class="pentest-cell"
|
class="pentest-cell"
|
||||||
routerLink="pentest"
|
(click)="onClickRouteToObjectivePentest(pentest.data)">
|
||||||
fragment="{{pentest.data['refNumber']}}"
|
|
||||||
(click)="selectPentest(pentest.data)"
|
|
||||||
[skipLocationChange]="true">
|
|
||||||
</tr>
|
</tr>
|
||||||
<!-- Test ID -->
|
<!-- Test ID -->
|
||||||
<ng-container [nbTreeGridColumnDef]="columns[0]">
|
<ng-container [nbTreeGridColumnDef]="columns[0]">
|
||||||
|
@ -44,11 +41,14 @@
|
||||||
<!-- Findings -->
|
<!-- Findings -->
|
||||||
<ng-container [nbTreeGridColumnDef]="columns[3]">
|
<ng-container [nbTreeGridColumnDef]="columns[3]">
|
||||||
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
|
<th nbTreeGridHeaderCell *nbTreeGridHeaderCellDef>
|
||||||
{{ 'pentest.findings' | translate }}
|
{{ 'pentest.findings&comments' | translate }}
|
||||||
</th>
|
</th>
|
||||||
<td nbTreeGridCell *nbTreeGridCellDef="let pentest">
|
<td nbTreeGridCell *nbTreeGridCellDef="let pentest">
|
||||||
<app-findig-widget [numberOfFindigs]="pentest.data['findings']"></app-findig-widget>
|
<div fxLayout="row" fxLayoutGap="0.5rem" fxLayoutAlign="start start">
|
||||||
<!--ToDo: Add comments {{pentest.data['comments'] || '-'}}-->
|
<app-findig-widget [numberOfFindings]="pentest.data['findings']"></app-findig-widget>
|
||||||
|
<span> / </span>
|
||||||
|
<app-comment-widget [numberOfComments]="pentest.data['comments']"></app-comment-widget>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -3,13 +3,14 @@ import {NbGetters, NbTreeGridDataSource, NbTreeGridDataSourceBuilder} from '@neb
|
||||||
import {Pentest, ObjectiveEntry, transformPentestsToObjectiveEntries} from '@shared/models/pentest.model';
|
import {Pentest, ObjectiveEntry, transformPentestsToObjectiveEntries} from '@shared/models/pentest.model';
|
||||||
import {PentestService} from '@shared/services/api/pentest.service';
|
import {PentestService} from '@shared/services/api/pentest.service';
|
||||||
import {Store} from '@ngxs/store';
|
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 {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||||
import {catchError, switchMap, tap} from 'rxjs/operators';
|
import {catchError, switchMap, tap} from 'rxjs/operators';
|
||||||
import {BehaviorSubject, Observable, of} from 'rxjs';
|
import {BehaviorSubject, Observable, of} from 'rxjs';
|
||||||
import {getTitleKeyForRefNumber} from '@shared/functions/categories/get-title-key-for-ref-number.function';
|
import {getTitleKeyForRefNumber} from '@shared/functions/categories/get-title-key-for-ref-number.function';
|
||||||
import {Router} from '@angular/router';
|
import {Router} from '@angular/router';
|
||||||
import {ChangePentest} from '@shared/stores/project-state/project-state.actions';
|
import {ChangePentest} from '@shared/stores/project-state/project-state.actions';
|
||||||
|
import {Route} from '@shared/models/route.enum';
|
||||||
|
|
||||||
@UntilDestroy()
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -20,7 +21,7 @@ import {ChangePentest} from '@shared/stores/project-state/project-state.actions'
|
||||||
export class ObjectiveTableComponent implements OnInit {
|
export class ObjectiveTableComponent implements OnInit {
|
||||||
|
|
||||||
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
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>;
|
dataSource: NbTreeGridDataSource<ObjectiveEntry>;
|
||||||
|
|
||||||
private data: ObjectiveEntry[] = [];
|
private data: ObjectiveEntry[] = [];
|
||||||
|
@ -36,7 +37,7 @@ export class ObjectiveTableComponent implements OnInit {
|
||||||
private store: Store,
|
private store: Store,
|
||||||
private pentestService: PentestService,
|
private pentestService: PentestService,
|
||||||
private dataSourceBuilder: NbTreeGridDataSourceBuilder<ObjectiveEntry>,
|
private dataSourceBuilder: NbTreeGridDataSourceBuilder<ObjectiveEntry>,
|
||||||
private readonly router: Router
|
private router: Router
|
||||||
) {
|
) {
|
||||||
this.dataSource = dataSourceBuilder.create(this.data, this.getters);
|
this.dataSource = dataSourceBuilder.create(this.data, this.getters);
|
||||||
}
|
}
|
||||||
|
@ -53,7 +54,6 @@ export class ObjectiveTableComponent implements OnInit {
|
||||||
untilDestroyed(this)
|
untilDestroyed(this)
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: (pentests: Pentest[]) => {
|
next: (pentests: Pentest[]) => {
|
||||||
// ToDo: Change assignement here
|
|
||||||
this.pentests$.next(pentests);
|
this.pentests$.next(pentests);
|
||||||
this.data = transformPentestsToObjectiveEntries(pentests);
|
this.data = transformPentestsToObjectiveEntries(pentests);
|
||||||
this.dataSource.setData(this.data, this.getters);
|
this.dataSource.setData(this.data, this.getters);
|
||||||
|
@ -66,16 +66,14 @@ export class ObjectiveTableComponent implements OnInit {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
selectPentest(selectedPentest: Pentest): void {
|
onClickRouteToObjectivePentest(selectedPentest: Pentest): void {
|
||||||
/* ToDo: Include again after fixing pentest route
|
this.router.navigate([Route.PENTEST_OBJECTIVE])
|
||||||
this.router.navigate([Route.PENTEST])
|
|
||||||
.then(
|
.then(
|
||||||
() => this.store.reset({
|
() => this.store.reset({
|
||||||
...this.store.snapshot(),
|
...this.store.snapshot(),
|
||||||
// [PROJECT_STATE_NAME]: pentest
|
|
||||||
})
|
})
|
||||||
).finally();
|
).finally();
|
||||||
*/
|
// Change Pentest State
|
||||||
const statePentest: Pentest = this.pentests$.getValue().find(pentest => pentest.refNumber === selectedPentest.refNumber);
|
const statePentest: Pentest = this.pentests$.getValue().find(pentest => pentest.refNumber === selectedPentest.refNumber);
|
||||||
if (statePentest) {
|
if (statePentest) {
|
||||||
this.store.dispatch(new ChangePentest(statePentest));
|
this.store.dispatch(new ChangePentest(statePentest));
|
||||||
|
@ -111,5 +109,5 @@ enum ObjectiveColumns {
|
||||||
TEST_ID = 'testId',
|
TEST_ID = 'testId',
|
||||||
TITLE = 'title',
|
TITLE = 'title',
|
||||||
STATUS = 'status',
|
STATUS = 'status',
|
||||||
FINDINGS = 'findings'
|
FINDINGS_AND_COMMENTS = 'findings&comments'
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
</th>
|
</th>
|
||||||
<td nbTreeGridCell *nbTreeGridCellDef="let comment" class="related-finding-cell">
|
<td nbTreeGridCell *nbTreeGridCellDef="let comment" class="related-finding-cell">
|
||||||
<ng-container *ngIf="comment.data['relatedFindings'].length > 0; else NoRelatedFindings">
|
<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-container>
|
||||||
<ng-template #NoRelatedFindings>
|
<ng-template #NoRelatedFindings>
|
||||||
{{ 'comment.no.relatedFindings' | translate }}
|
{{ 'comment.no.relatedFindings' | translate }}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<app-pentest-findings></app-pentest-findings>
|
<app-pentest-findings></app-pentest-findings>
|
||||||
</nb-tab>
|
</nb-tab>
|
||||||
<nb-tab class="pentest-tabset" tabTitle="{{ 'pentest.comments' | translate }}"
|
<nb-tab class="pentest-tabset" tabTitle="{{ 'pentest.comments' | translate }}"
|
||||||
badgeText="{{currentNumberOfComments$.getValue()}}" badgeStatus="info">
|
badgeText="{{currentNumberOfComments$.getValue()}}" badgeStatus="control">
|
||||||
<app-pentest-comments></app-pentest-comments>
|
<app-pentest-comments></app-pentest-comments>
|
||||||
</nb-tab>
|
</nb-tab>
|
||||||
</nb-tabset>
|
</nb-tabset>
|
||||||
|
|
|
@ -7,6 +7,8 @@ import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||||
import {Pentest} from '@shared/models/pentest.model';
|
import {Pentest} from '@shared/models/pentest.model';
|
||||||
import {PentestService} from '@shared/services/api/pentest.service';
|
import {PentestService} from '@shared/services/api/pentest.service';
|
||||||
import {NotificationService} from '@shared/services/toaster-service/notification.service';
|
import {NotificationService} from '@shared/services/toaster-service/notification.service';
|
||||||
|
import {Router} from '@angular/router';
|
||||||
|
import {Route} from '@shared/models/route.enum';
|
||||||
|
|
||||||
@UntilDestroy()
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -25,6 +27,7 @@ export class PentestContentComponent implements OnInit {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly pentestService: PentestService,
|
private readonly pentestService: PentestService,
|
||||||
private notificationService: NotificationService,
|
private notificationService: NotificationService,
|
||||||
|
private router: Router,
|
||||||
private store: Store) {
|
private store: Store) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,11 +36,15 @@ export class PentestContentComponent implements OnInit {
|
||||||
untilDestroyed(this)
|
untilDestroyed(this)
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: (selectedPentest: Pentest) => {
|
next: (selectedPentest: Pentest) => {
|
||||||
this.pentest$.next(selectedPentest);
|
if (selectedPentest) {
|
||||||
const findings = selectedPentest.findingIds ? selectedPentest.findingIds.length : 0;
|
this.pentest$.next(selectedPentest);
|
||||||
this.currentNumberOfFindings$.next(findings);
|
const findings = selectedPentest.findingIds ? selectedPentest.findingIds.length : 0;
|
||||||
const comments = selectedPentest.commentIds ? selectedPentest.commentIds.length : 0;
|
this.currentNumberOfFindings$.next(findings);
|
||||||
this.currentNumberOfComments$.next(comments);
|
const comments = selectedPentest.commentIds ? selectedPentest.commentIds.length : 0;
|
||||||
|
this.currentNumberOfComments$.next(comments);
|
||||||
|
} else {
|
||||||
|
this.router.navigate([Route.PROJECT_OVERVIEW]);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
error: err => {
|
error: err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
|
@ -79,7 +79,7 @@ export class PentestHeaderComponent implements OnInit {
|
||||||
|
|
||||||
onClickRouteBack(): void {
|
onClickRouteBack(): void {
|
||||||
// ToDo: Change to Objective Overview after routing is fixed
|
// ToDo: Change to Objective Overview after routing is fixed
|
||||||
this.router.navigate([Route.PROJECT_OVERVIEW])
|
this.router.navigate([Route.OBJECTIVE_OVERVIEW])
|
||||||
.then(
|
.then(
|
||||||
() => {
|
() => {
|
||||||
this.store.dispatch(new ChangePentest(null));
|
this.store.dispatch(new ChangePentest(null));
|
||||||
|
|
|
@ -1,12 +1,7 @@
|
||||||
import {NgModule} from '@angular/core';
|
import {NgModule} from '@angular/core';
|
||||||
import {RouterModule, Routes} from '@angular/router';
|
import {RouterModule, Routes} from '@angular/router';
|
||||||
import {PentestComponent} from './pentest.component';
|
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
component: PentestComponent
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import {RouterModule, Routes} from '@angular/router';
|
import {RouterModule, Routes} from '@angular/router';
|
||||||
import {ProjectOverviewComponent} from './project-overview.component';
|
import {Route} from '@shared/models/route.enum';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: Route.OBJECTIVE_OVERVIEW,
|
||||||
component: ProjectOverviewComponent,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'id',
|
|
||||||
loadChildren: () => import('./project').then(mod => mod.ProjectModule),
|
loadChildren: () => import('./project').then(mod => mod.ProjectModule),
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
|
@ -5,13 +5,13 @@
|
||||||
routerLink="id"
|
routerLink="id"
|
||||||
fragment="{{project.id}}"
|
fragment="{{project.id}}"
|
||||||
class="project-link project-header"
|
class="project-link project-header"
|
||||||
[state]="{selectedProject:project}">
|
(click)="onClickRouteToProject(project)">
|
||||||
<h4>{{project?.title}}</h4>
|
<h4>{{project?.title}}</h4>
|
||||||
</nb-card-header>
|
</nb-card-header>
|
||||||
<nb-card-body class="project-link"
|
<nb-card-body class="project-link"
|
||||||
routerLink="id"
|
routerLink="id"
|
||||||
fragment="{{project.id}}"
|
fragment="{{project.id}}"
|
||||||
[state]="{selectedProject:project}">
|
(click)="onClickRouteToProject(project)">
|
||||||
<p class="project-subheader">
|
<p class="project-subheader">
|
||||||
{{'project.client' | translate}}:
|
{{'project.client' | translate}}:
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -9,6 +9,10 @@ import {catchError, filter, mergeMap, switchMap, tap} from 'rxjs/operators';
|
||||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||||
import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
|
import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
|
||||||
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
|
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()
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -25,6 +29,8 @@ export class ProjectOverviewComponent implements OnInit {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly notificationService: NotificationService,
|
private readonly notificationService: NotificationService,
|
||||||
|
private store: Store,
|
||||||
|
private router: Router,
|
||||||
private projectService: ProjectService,
|
private projectService: ProjectService,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
private projectDialogService: ProjectDialogService) {
|
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
|
// HTML only
|
||||||
isLoading(): Observable<boolean> {
|
isLoading(): Observable<boolean> {
|
||||||
return this.loading$.asObservable();
|
return this.loading$.asObservable();
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {ProjectDialogModule} from '@shared/modules/project-dialog/project-dialog
|
||||||
import {CommonAppModule} from '../common-app.module';
|
import {CommonAppModule} from '../common-app.module';
|
||||||
import {ConfirmDialogModule} from '@shared/modules/confirm-dialog/confirm-dialog.module';
|
import {ConfirmDialogModule} from '@shared/modules/confirm-dialog/confirm-dialog.module';
|
||||||
import {SecurityConfirmDialogModule} from '@shared/modules/security-confirm-dialog/security-confirm-dialog.module';
|
import {SecurityConfirmDialogModule} from '@shared/modules/security-confirm-dialog/security-confirm-dialog.module';
|
||||||
|
import {RouterModule} from '@angular/router';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@ -20,6 +21,10 @@ import {SecurityConfirmDialogModule} from '@shared/modules/security-confirm-dial
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
CommonAppModule,
|
CommonAppModule,
|
||||||
|
RouterModule.forChild([{
|
||||||
|
path: '',
|
||||||
|
component: ProjectOverviewComponent
|
||||||
|
}]),
|
||||||
NbCardModule,
|
NbCardModule,
|
||||||
NbButtonModule,
|
NbButtonModule,
|
||||||
NbProgressBarModule,
|
NbProgressBarModule,
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
import {NgModule} from '@angular/core';
|
import {NgModule} from '@angular/core';
|
||||||
import {RouterModule, Routes} from '@angular/router';
|
import {RouterModule, Routes} from '@angular/router';
|
||||||
import {ProjectComponent} from './project.component';
|
import {Route} from '@shared/models/route.enum';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: Route.PENTEST_OBJECTIVE,
|
||||||
component: ProjectComponent
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'pentest',
|
|
||||||
loadChildren: () => import('../../pentest').then(mod => mod.PentestModule),
|
loadChildren: () => import('../../pentest').then(mod => mod.PentestModule),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {Store} from '@ngxs/store';
|
import {Store} from '@ngxs/store';
|
||||||
import {InitProjectState} from '@shared/stores/project-state/project-state.actions';
|
|
||||||
import {Router} from '@angular/router';
|
import {Router} from '@angular/router';
|
||||||
import {Route} from '@shared/models/route.enum';
|
|
||||||
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
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';
|
import {Project} from '@shared/models/project.model';
|
||||||
|
|
||||||
@UntilDestroy()
|
@UntilDestroy()
|
||||||
|
@ -20,19 +20,23 @@ export class ProjectComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
if (history?.state && 'selectedProject' in history?.state) {
|
this.store.select(ProjectState.project).pipe(
|
||||||
this.initProjectStore();
|
untilDestroyed(this)
|
||||||
} else {
|
).subscribe({
|
||||||
this.router.navigate([Route.PROJECT_OVERVIEW]).finally();
|
next: (selectedProject: Project) => {
|
||||||
}
|
this.initProjectStore();
|
||||||
|
},
|
||||||
|
error: err => {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private initProjectStore(): void {
|
private initProjectStore(): void {
|
||||||
const project: Project = history?.state?.selectedProject ? history?.state?.selectedProject : null;
|
this.router.navigate([Route.OBJECTIVE_OVERVIEW]).then(() => {
|
||||||
this.store.dispatch(new InitProjectState(
|
}, err => {
|
||||||
project,
|
this.router.navigate([Route.PROJECT_OVERVIEW]);
|
||||||
[],
|
console.error(err);
|
||||||
[]
|
});
|
||||||
)).pipe(untilDestroyed(this)).subscribe();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -216,6 +216,7 @@
|
||||||
"title": "Titel",
|
"title": "Titel",
|
||||||
"findings": "Funde",
|
"findings": "Funde",
|
||||||
"comments": "Kommentare",
|
"comments": "Kommentare",
|
||||||
|
"findings&comments": "Funde & Kommentare",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"statusText": {
|
"statusText": {
|
||||||
"not_started": "Nicht angefangen",
|
"not_started": "Nicht angefangen",
|
||||||
|
|
|
@ -216,6 +216,7 @@
|
||||||
"title": "Title",
|
"title": "Title",
|
||||||
"findings": "Findings",
|
"findings": "Findings",
|
||||||
"comments": "Comments",
|
"comments": "Comments",
|
||||||
|
"findings&comments": "Findings & Comments",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"statusText": {
|
"statusText": {
|
||||||
"not_started": "Not Started",
|
"not_started": "Not Started",
|
||||||
|
|
|
@ -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
|
||||||
|
};
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ export function downloadFile(data: any, type: string): void {
|
||||||
const url = window.URL.createObjectURL(blob);
|
const url = window.URL.createObjectURL(blob);
|
||||||
const pwa = window.open(url);
|
const pwa = window.open(url);
|
||||||
if (!pwa || pwa.closed || typeof pwa.closed === 'undefined') {
|
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.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
|
@ -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'
|
||||||
|
}
|
|
@ -63,6 +63,7 @@ export function transformPentestsToObjectiveEntries(pentests: Pentest[]): Object
|
||||||
refNumber: value.refNumber,
|
refNumber: value.refNumber,
|
||||||
status: value.status,
|
status: value.status,
|
||||||
findings: value.findingIds ? value.findingIds.length : 0,
|
findings: value.findingIds ? value.findingIds.length : 0,
|
||||||
|
comments: value.commentIds ? value.commentIds.length : 0,
|
||||||
kind: value.childEntries ? 'dir' : 'cell',
|
kind: value.childEntries ? 'dir' : 'cell',
|
||||||
childEntries: value.childEntries ? value.childEntries : null,
|
childEntries: value.childEntries ? value.childEntries : null,
|
||||||
expanded: !!value.childEntries
|
expanded: !!value.childEntries
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
export enum Route {
|
export enum Route {
|
||||||
|
LOGIN = 'login',
|
||||||
HOME = 'home',
|
HOME = 'home',
|
||||||
PROJECT_OVERVIEW = 'projects',
|
PROJECT_OVERVIEW = 'projects',
|
||||||
|
OBJECTIVE_OVERVIEW = 'project/objectives',
|
||||||
|
PENTEST_OBJECTIVE = 'project/objectives/pentest',
|
||||||
PENTEST_OVERVIEW = 'pentest'
|
PENTEST_OVERVIEW = 'pentest'
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,12 @@
|
||||||
{{ 'global.project' | translate }}:
|
{{ 'global.project' | translate }}:
|
||||||
</label>
|
</label>
|
||||||
<b class="project-title">
|
<b class="project-title">
|
||||||
{{selectedProject$.getValue()?.title}}
|
{{dialogData.options[0].additionalData.title}}
|
||||||
</b>
|
</b>
|
||||||
<!--Chart Objective Component-->
|
<!--Chart Objective Component-->
|
||||||
<div fxLayout="column" fxLayoutGap="2rem" fxLayoutAlign="center center">
|
<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
|
<span
|
||||||
class="hint"> {{'popup.info' | translate}} {{ 'report.hint' | translate: this.completedProjectPentests$?.getValue() }}
|
class="hint"> {{'popup.info' | translate}} {{ 'report.hint' | translate: this.completedProjectPentests$?.getValue() }}
|
||||||
</span>
|
</span>
|
||||||
|
@ -64,4 +65,4 @@
|
||||||
</nb-card-footer>
|
</nb-card-footer>
|
||||||
</nb-card>
|
</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>
|
||||||
|
|
|
@ -82,7 +82,7 @@ describe('ExportReportDialogComponent', () => {
|
||||||
TestBed.overrideProvider(NB_DIALOG_CONFIG, {useValue: mockedExportPentestDialogData});
|
TestBed.overrideProvider(NB_DIALOG_CONFIG, {useValue: mockedExportPentestDialogData});
|
||||||
fixture = TestBed.createComponent(ExportReportDialogComponent);
|
fixture = TestBed.createComponent(ExportReportDialogComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
component.selectedProject$.next(mockProject);
|
component.selectedEvaluatedProject$.next(mockProject);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,10 @@ import {BehaviorSubject, Observable} from 'rxjs';
|
||||||
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service';
|
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service';
|
||||||
import {ProjectService} from '@shared/services/api/project.service';
|
import {ProjectService} from '@shared/services/api/project.service';
|
||||||
import {PentestStatus} from '@shared/models/pentest-status.model';
|
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 {downloadFile} from '@shared/functions/download-file.function';
|
||||||
|
import {Loading, LoadingState} from '@shared/models/loading.model';
|
||||||
|
import {HttpEvent, HttpEventType} from '@angular/common/http';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-export-report-dialog',
|
selector: 'app-export-report-dialog',
|
||||||
|
@ -41,10 +43,14 @@ export class ExportReportDialogComponent implements OnInit {
|
||||||
|
|
||||||
dialogData: GenericDialogData;
|
dialogData: GenericDialogData;
|
||||||
|
|
||||||
selectedProject$: BehaviorSubject<Project> = new BehaviorSubject<Project>(null);
|
selectedEvaluatedProject$: BehaviorSubject<Project> = new BehaviorSubject<Project>(null);
|
||||||
completedProjectPentests$: BehaviorSubject<any> = new BehaviorSubject<any>({completedObjectivesNumber: 0});
|
completedProjectPentests$: BehaviorSubject<any> = new BehaviorSubject<any>({completedObjectivesNumber: 0});
|
||||||
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
||||||
|
|
||||||
|
// Loading Bar Property
|
||||||
|
downloadPentestReport$: Observable<Loading<ArrayBuffer>>;
|
||||||
|
progress = 0;
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.dialogData = this.data;
|
this.dialogData = this.data;
|
||||||
this.loadEvaluatedProject();
|
this.loadEvaluatedProject();
|
||||||
|
@ -61,7 +67,7 @@ export class ExportReportDialogComponent implements OnInit {
|
||||||
)
|
)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (project: Project) => {
|
next: (project: Project) => {
|
||||||
this.selectedProject$.next(project);
|
this.selectedEvaluatedProject$.next(project);
|
||||||
const completedPentestObjectives = project.projectPentests.filter(pentest => pentest.status === PentestStatus.COMPLETED);
|
const completedPentestObjectives = project.projectPentests.filter(pentest => pentest.status === PentestStatus.COMPLETED);
|
||||||
this.completedProjectPentests$.next({completedObjectivesNumber: completedPentestObjectives.length});
|
this.completedProjectPentests$.next({completedObjectivesNumber: completedPentestObjectives.length});
|
||||||
this.loading$.next(false);
|
this.loading$.next(false);
|
||||||
|
@ -75,18 +81,26 @@ export class ExportReportDialogComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickExport(reportFormat: string, reportLanguage: string): void {
|
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
|
// Loading is true as long as there is a response from the reporting service
|
||||||
this.loading$.next(true);
|
this.loading$.next(true);
|
||||||
// Export pentest in choosen format
|
// Export pentest in choosen format
|
||||||
switch (reportFormat) {
|
switch (reportFormat) {
|
||||||
case ExportFormatOptions.PDF: {
|
case ExportFormatOptions.PDF: {
|
||||||
this.reportingService.getReportPDFforProjectById(this.selectedProject$.getValue().id).pipe(
|
// @ts-ignore
|
||||||
untilDestroyed(this)
|
this.downloadPentestReport$ = this.reportingService.getReportPDFforProjectById(projectId)
|
||||||
).subscribe({
|
.pipe(
|
||||||
|
shareReplay(),
|
||||||
|
untilDestroyed(this)
|
||||||
|
);
|
||||||
|
this.downloadPentestReport$.subscribe({
|
||||||
next: (response) => {
|
next: (response) => {
|
||||||
downloadFile(response, 'application/pdf');
|
if (response.state === LoadingState.DONE) {
|
||||||
this.loading$.next(false);
|
downloadFile(response.content, 'application/pdf');
|
||||||
this.notificationService.showPopup('report.popup.generation.success', PopupType.SUCCESS);
|
this.loading$.next(false);
|
||||||
|
this.notificationService.showPopup('report.popup.generation.success', PopupType.SUCCESS);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
error: error => {
|
error: error => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
|
@ -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 {ExportReportDialogService} from '@shared/modules/export-report-dialog/service/export-report-dialog.service';
|
||||||
import {ReportingService} from '@shared/services/reporting/reporting.service';
|
import {ReportingService} from '@shared/services/reporting/reporting.service';
|
||||||
import {ObjectiveChartModule} from '@shared/modules/objective-chart/objective-chart.module';
|
import {ObjectiveChartModule} from '@shared/modules/objective-chart/objective-chart.module';
|
||||||
|
import {LoadingBarModule} from '@shared/widgets/loading-bar/loading-bar.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
ExportReportDialogComponent
|
ExportReportDialogComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
CommonAppModule,
|
CommonAppModule,
|
||||||
NbCardModule,
|
NbCardModule,
|
||||||
NbButtonModule,
|
NbButtonModule,
|
||||||
NbFormFieldModule,
|
NbFormFieldModule,
|
||||||
NbInputModule,
|
NbInputModule,
|
||||||
FlexLayoutModule,
|
FlexLayoutModule,
|
||||||
FontAwesomeModule,
|
FontAwesomeModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
NbRadioModule,
|
NbRadioModule,
|
||||||
ObjectiveChartModule
|
ObjectiveChartModule,
|
||||||
],
|
LoadingBarModule
|
||||||
|
],
|
||||||
providers: [
|
providers: [
|
||||||
ExportReportDialogService,
|
ExportReportDialogService,
|
||||||
ReportingService
|
ReportingService
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {environment} from '../../../environments/environment';
|
import {environment} from '../../../environments/environment';
|
||||||
import {HttpClient} from '@angular/common/http';
|
import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http';
|
||||||
import {Observable} from 'rxjs';
|
import {BehaviorSubject, Observable} from 'rxjs';
|
||||||
|
import {Loading} from '@shared/models/loading.model';
|
||||||
|
import { loadContent } from '@shared/functions/load-content';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
|
@ -16,8 +18,31 @@ export class ReportingService {
|
||||||
/**
|
/**
|
||||||
* Get PDF Report by project id
|
* 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> {
|
public getReportPDFforProjectById(projectId: string): Observable<ArrayBuffer> {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return this.http.get<ArrayBuffer>(`${this.reportBaseURL}/${projectId}/pdf`, {responseType: 'arraybuffer'})
|
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;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
|
@ -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 {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
<div class="finding-widget">
|
<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>
|
<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-container>
|
||||||
<ng-template #noFindings>
|
<ng-template #noFindings>
|
||||||
{{'-'}}
|
{{'-'}}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import * as FA from '@fortawesome/free-solid-svg-icons';
|
||||||
styleUrls: ['./findig-widget.component.scss']
|
styleUrls: ['./findig-widget.component.scss']
|
||||||
})
|
})
|
||||||
export class FindigWidgetComponent implements OnInit {
|
export class FindigWidgetComponent implements OnInit {
|
||||||
@Input() numberOfFindigs: number;
|
@Input() numberOfFindings: number;
|
||||||
|
|
||||||
// HTML only
|
// HTML only
|
||||||
readonly fa = FA;
|
readonly fa = FA;
|
||||||
|
|
|
@ -7,13 +7,13 @@ import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
||||||
declarations: [
|
declarations: [
|
||||||
FindigWidgetComponent
|
FindigWidgetComponent
|
||||||
],
|
],
|
||||||
exports: [
|
|
||||||
FindigWidgetComponent
|
|
||||||
],
|
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
FontAwesomeModule
|
FontAwesomeModule
|
||||||
]
|
],
|
||||||
|
exports: [
|
||||||
|
FindigWidgetComponent
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class FindigWidgetModule {
|
export class FindigWidgetModule {
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [],
|
|
||||||
imports: [
|
|
||||||
CommonModule
|
|
||||||
]
|
|
||||||
})
|
|
||||||
export class FindigModule { }
|
|
|
@ -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>
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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 { }
|
|
@ -1,10 +1,12 @@
|
||||||
package com.securityc4po.reporting.remote.model
|
package com.securityc4po.reporting.remote.model
|
||||||
|
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
data class ProjectReport(
|
data class ProjectReport(
|
||||||
val id: String,
|
val id: String,
|
||||||
val client: String,
|
val client: String,
|
||||||
val title: String,
|
val title: String,
|
||||||
val createdAt: String,
|
val createdAt: Date,
|
||||||
val tester: String,
|
val tester: String,
|
||||||
val summary: String? = null,
|
val summary: String? = null,
|
||||||
var projectPentestReport: MutableList<PentestReport> = mutableListOf<PentestReport>(),
|
var projectPentestReport: MutableList<PentestReport> = mutableListOf<PentestReport>(),
|
||||||
|
|
|
@ -2,6 +2,9 @@ package com.securityc4po.reporting.remote.model.api
|
||||||
|
|
||||||
import com.securityc4po.reporting.remote.model.PentestReport
|
import com.securityc4po.reporting.remote.model.PentestReport
|
||||||
import com.securityc4po.reporting.remote.model.ProjectReport
|
import com.securityc4po.reporting.remote.model.ProjectReport
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
|
||||||
data class Project(
|
data class Project(
|
||||||
val id: String,
|
val id: String,
|
||||||
|
@ -19,7 +22,8 @@ fun Project.toProjectReport(): ProjectReport {
|
||||||
id = this.id,
|
id = this.id,
|
||||||
client = this.client,
|
client = this.client,
|
||||||
title = this.title,
|
title = this.title,
|
||||||
createdAt = this.createdAt,
|
/* Use the time of report creation */
|
||||||
|
createdAt = Date.from(Instant.now()),
|
||||||
tester = this.tester,
|
tester = this.tester,
|
||||||
summary = this.summary,
|
summary = this.summary,
|
||||||
projectPentestReport = mutableListOf<PentestReport>(),
|
projectPentestReport = mutableListOf<PentestReport>(),
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<property name="net.sf.jasperreports.json.field.expression" value="title"/>
|
<property name="net.sf.jasperreports.json.field.expression" value="title"/>
|
||||||
<fieldDescription><![CDATA[title]]></fieldDescription>
|
<fieldDescription><![CDATA[title]]></fieldDescription>
|
||||||
</field>
|
</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"/>
|
<property name="net.sf.jasperreports.json.field.expression" value="createdAt"/>
|
||||||
<fieldDescription><![CDATA[createdAt]]></fieldDescription>
|
<fieldDescription><![CDATA[createdAt]]></fieldDescription>
|
||||||
</field>
|
</field>
|
||||||
|
@ -144,7 +144,7 @@
|
||||||
<textElement textAlignment="Right">
|
<textElement textAlignment="Right">
|
||||||
<font fontName="SansSerif" size="12" isBold="true" isItalic="true"/>
|
<font fontName="SansSerif" size="12" isBold="true" isItalic="true"/>
|
||||||
</textElement>
|
</textElement>
|
||||||
<textFieldExpression><![CDATA[$F{createdAt}]]></textFieldExpression>
|
<textFieldExpression><![CDATA[(new SimpleDateFormat("dd/MM/yyyy").format($F{createdAt}))]]></textFieldExpression>
|
||||||
</textField>
|
</textField>
|
||||||
</band>
|
</band>
|
||||||
</detail>
|
</detail>
|
||||||
|
|
Loading…
Reference in New Issue