feat: As a user I want to have an export dialog to download my pentest report
This commit is contained in:
parent
bb544c71a0
commit
79a2493c37
|
@ -36,6 +36,8 @@
|
||||||
"crypto-js/hmac-sha256",
|
"crypto-js/hmac-sha256",
|
||||||
"crypto-js/lib-typedarrays",
|
"crypto-js/lib-typedarrays",
|
||||||
"js-cookie",
|
"js-cookie",
|
||||||
|
"chartjs-plugin-annotation",
|
||||||
|
"chart.js",
|
||||||
"deep-equal",
|
"deep-equal",
|
||||||
"moment-timezone",
|
"moment-timezone",
|
||||||
"uuid"
|
"uuid"
|
||||||
|
|
|
@ -2951,6 +2951,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@kurkle/color": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw=="
|
||||||
|
},
|
||||||
"@nebular/eva-icons": {
|
"@nebular/eva-icons": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@nebular/eva-icons/-/eva-icons-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@nebular/eva-icons/-/eva-icons-8.0.0.tgz",
|
||||||
|
@ -5097,6 +5102,14 @@
|
||||||
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
|
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"chart.js": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-6YbpQ0nt3NovAgOzbkSSeeAQu/3za1319dPUQTXn9WcOpywM8rGKxJHrhS8V8xEkAlk8YhEfjbuAPfUyp6jIsw==",
|
||||||
|
"requires": {
|
||||||
|
"@kurkle/color": "^0.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"chokidar": {
|
"chokidar": {
|
||||||
"version": "3.5.3",
|
"version": "3.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
"@ngx-translate/http-loader": "^6.0.0",
|
"@ngx-translate/http-loader": "^6.0.0",
|
||||||
"@ngxs/storage-plugin": "^3.7.3",
|
"@ngxs/storage-plugin": "^3.7.3",
|
||||||
"@ngxs/store": "^3.7.3",
|
"@ngxs/store": "^3.7.3",
|
||||||
|
"chart.js": "^4.2.1",
|
||||||
"eva-icons": "^1.1.3",
|
"eva-icons": "^1.1.3",
|
||||||
"i18n-iso-countries": "^6.8.0",
|
"i18n-iso-countries": "^6.8.0",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
|
|
|
@ -24,7 +24,7 @@ import {far} from '@fortawesome/free-regular-svg-icons';
|
||||||
import {NgxsModule} from '@ngxs/store';
|
import {NgxsModule} from '@ngxs/store';
|
||||||
import {SessionState} from '@shared/stores/session-state/session-state';
|
import {SessionState} from '@shared/stores/session-state/session-state';
|
||||||
import {environment} from '../environments/environment';
|
import {environment} from '../environments/environment';
|
||||||
import {NotificationService} from '@shared/services/notification.service';
|
import {NotificationService} from '@shared/services/toaster-service/notification.service';
|
||||||
import {ThemeModule} from '@assets/@theme/theme.module';
|
import {ThemeModule} from '@assets/@theme/theme.module';
|
||||||
import {HeaderModule} from './header/header.module';
|
import {HeaderModule} from './header/header.module';
|
||||||
import {HomeModule} from './home/home.module';
|
import {HomeModule} from './home/home.module';
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {HttpClient, HttpClientModule} from '@angular/common/http';
|
||||||
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
||||||
import {FlexLayoutModule, FlexModule} from '@angular/flex-layout';
|
import {FlexLayoutModule, FlexModule} from '@angular/flex-layout';
|
||||||
import {MomentModule} from 'ngx-moment';
|
import {MomentModule} from 'ngx-moment';
|
||||||
import {NotificationService} from '@shared/services/notification.service';
|
import {NotificationService} from '@shared/services/toaster-service/notification.service';
|
||||||
import {NbOverlayContainerAdapter, NbSpinnerModule, NbToastrModule} from '@nebular/theme';
|
import {NbOverlayContainerAdapter, NbSpinnerModule, NbToastrModule} from '@nebular/theme';
|
||||||
import {ThemeModule} from '@assets/@theme/theme.module';
|
import {ThemeModule} from '@assets/@theme/theme.module';
|
||||||
import {LoadingSpinnerComponent} from '@shared/widgets/loading-spinner/loading-spinner.component';
|
import {LoadingSpinnerComponent} from '@shared/widgets/loading-spinner/loading-spinner.component';
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||||
export class HeaderComponent implements OnInit{
|
export class HeaderComponent implements OnInit{
|
||||||
|
|
||||||
readonly fa = FA;
|
readonly fa = FA;
|
||||||
readonly SECURITYC4PO_TITLE = GlobalTitlesVariables.SECURITYC4PO_TITLE;
|
readonly SECURITYC4PO_TITLE: string = GlobalTitlesVariables.SECURITYC4PO_TITLE;
|
||||||
|
|
||||||
currentTheme = '';
|
currentTheme = '';
|
||||||
languages = ['en-US', 'de-DE'];
|
languages = ['en-US', 'de-DE'];
|
||||||
|
|
|
@ -21,8 +21,8 @@ import {ReactiveFormsModule} from '@angular/forms';
|
||||||
import {User} from '../../shared/models/user.model';
|
import {User} from '../../shared/models/user.model';
|
||||||
import {CommonModule} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||||
import {NotificationService} from '../../shared/services/notification.service';
|
import {NotificationService} from '@shared/services/toaster-service/notification.service';
|
||||||
import {NotificationServiceMock} from '../../shared/services/notification.service.mock';
|
import {NotificationServiceMock} from '@shared/services/toaster-service/notification.service.mock';
|
||||||
import {KeycloakService} from 'keycloak-angular';
|
import {KeycloakService} from 'keycloak-angular';
|
||||||
|
|
||||||
const DESIRED_STORE_STATE_SESSION: SessionStateModel = {
|
const DESIRED_STORE_STATE_SESSION: SessionStateModel = {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {Component, OnInit} from '@angular/core';
|
||||||
import {AbstractControl, FormBuilder, FormGroup, Validators} from '@angular/forms';
|
import {AbstractControl, FormBuilder, FormGroup, Validators} from '@angular/forms';
|
||||||
import {Router} from '@angular/router';
|
import {Router} from '@angular/router';
|
||||||
import {Store} from '@ngxs/store';
|
import {Store} from '@ngxs/store';
|
||||||
import {NotificationService, PopupType} from '../../shared/services/notification.service';
|
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service';
|
||||||
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||||
import {User} from '../../shared/models/user.model';
|
import {User} from '../../shared/models/user.model';
|
||||||
import {throwError} from 'rxjs';
|
import {throwError} from 'rxjs';
|
||||||
|
@ -22,7 +22,6 @@ import {KeycloakService} from 'keycloak-angular';
|
||||||
export class LoginComponent implements OnInit {
|
export class LoginComponent implements OnInit {
|
||||||
readonly MIN_LENGTH: number = 2;
|
readonly MIN_LENGTH: number = 2;
|
||||||
readonly SECURITYC4PO_TITLE = GlobalTitlesVariables.SECURITYC4PO_TITLE;
|
readonly SECURITYC4PO_TITLE = GlobalTitlesVariables.SECURITYC4PO_TITLE;
|
||||||
readonly NOVATEC_NAME = GlobalTitlesVariables.NOVATEC_NAME;
|
|
||||||
|
|
||||||
// ToDo: Remove after adding real authentication
|
// ToDo: Remove after adding real authentication
|
||||||
private readonly user = new User('ttt', 'test', 'user', 'default.user@test.de', 'en-US');
|
private readonly user = new User('ttt', 'test', 'user', 'default.user@test.de', 'en-US');
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {LoginComponent} from './login.component';
|
||||||
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
|
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
|
||||||
import {HttpLoaderFactory} from '../common-app.module';
|
import {HttpLoaderFactory} from '../common-app.module';
|
||||||
import {HttpClient} from '@angular/common/http';
|
import {HttpClient} from '@angular/common/http';
|
||||||
import {NotificationService} from '../../shared/services/notification.service';
|
import {NotificationService} from '@shared/services/toaster-service/notification.service';
|
||||||
import {LoginRoutingModule} from './login-routing.module';
|
import {LoginRoutingModule} from './login-routing.module';
|
||||||
import {NbButtonModule, NbCardModule, NbFormFieldModule, NbInputModule, NbLayoutModule} from '@nebular/theme';
|
import {NbButtonModule, NbCardModule, NbFormFieldModule, NbInputModule, NbLayoutModule} from '@nebular/theme';
|
||||||
import {ReactiveFormsModule} from '@angular/forms';
|
import {ReactiveFormsModule} from '@angular/forms';
|
||||||
|
|
|
@ -14,8 +14,9 @@
|
||||||
<div class="button-container">
|
<div class="button-container">
|
||||||
<nb-actions size="medium">
|
<nb-actions size="medium">
|
||||||
<nb-action>
|
<nb-action>
|
||||||
|
<!--status="button-outline-basic-text-color"-->
|
||||||
<button nbButton
|
<button nbButton
|
||||||
status="button-outline-basic-text-color"
|
status="primary"
|
||||||
shape="round"
|
shape="round"
|
||||||
(click)="onClickEditPentestProject()">
|
(click)="onClickEditPentestProject()">
|
||||||
<fa-icon [icon]="fa.faEdit"
|
<fa-icon [icon]="fa.faEdit"
|
||||||
|
@ -27,7 +28,7 @@
|
||||||
<button nbButton hero
|
<button nbButton hero
|
||||||
status="info"
|
status="info"
|
||||||
shape="round"
|
shape="round"
|
||||||
(click)="onClickExportPentest()">
|
(click)="onClickExportPentestReport()">
|
||||||
<fa-icon [icon]="fa.faFileExport"
|
<fa-icon [icon]="fa.faFileExport"
|
||||||
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.export' | translate }}</span>
|
||||||
|
|
|
@ -12,16 +12,18 @@ import {NgxsModule, Store} from '@ngxs/store';
|
||||||
import {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/stores/project-state/project-state';
|
import {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/stores/project-state/project-state';
|
||||||
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
||||||
import {NbActionsModule, NbIconModule} from '@nebular/theme';
|
import {NbActionsModule, NbIconModule} from '@nebular/theme';
|
||||||
import {ProjectService} from '@shared/services/project.service';
|
import {ProjectService} from '@shared/services/api/project.service';
|
||||||
import {ProjectServiceMock} from '@shared/services/project.service.mock';
|
import {ProjectServiceMock} from '@shared/services/api/project.service.mock';
|
||||||
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
|
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
|
||||||
import {ProjectDialogServiceMock} from '@shared/modules/project-dialog/service/project-dialog.service.mock';
|
import {ProjectDialogServiceMock} from '@shared/modules/project-dialog/service/project-dialog.service.mock';
|
||||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||||
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
|
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
|
||||||
import {NotificationService} from '@shared/services/notification.service';
|
import {NotificationService} from '@shared/services/toaster-service/notification.service';
|
||||||
import {NotificationServiceMock} from '@shared/services/notification.service.mock';
|
import {NotificationServiceMock} from '@shared/services/toaster-service/notification.service.mock';
|
||||||
import {Category} from '@shared/models/category.model';
|
import {Category} from '@shared/models/category.model';
|
||||||
import {PentestStatus} from '@shared/models/pentest-status.model';
|
import {PentestStatus} from '@shared/models/pentest-status.model';
|
||||||
|
import {ExportReportDialogService} from '@shared/modules/export-report-dialog/service/export-report-dialog.service';
|
||||||
|
import {ExportReportDialogServiceMock} from '@shared/modules/export-report-dialog/service/export-report-dialog.service.mock';
|
||||||
|
|
||||||
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||||
selectedProject: {
|
selectedProject: {
|
||||||
|
@ -78,6 +80,7 @@ describe('ObjectiveHeaderComponent', () => {
|
||||||
providers: [
|
providers: [
|
||||||
{provide: ProjectService, useValue: new ProjectServiceMock()},
|
{provide: ProjectService, useValue: new ProjectServiceMock()},
|
||||||
{provide: ProjectDialogService, useClass: ProjectDialogServiceMock},
|
{provide: ProjectDialogService, useClass: ProjectDialogServiceMock},
|
||||||
|
{provide: ExportReportDialogService, useClass: ExportReportDialogServiceMock},
|
||||||
{provide: DialogService, useClass: DialogServiceMock},
|
{provide: DialogService, useClass: DialogServiceMock},
|
||||||
{provide: NotificationService, useValue: new NotificationServiceMock()}
|
{provide: NotificationService, useValue: new NotificationServiceMock()}
|
||||||
]
|
]
|
||||||
|
|
|
@ -9,11 +9,13 @@ import {BehaviorSubject} from 'rxjs';
|
||||||
import {Project, ProjectDialogBody} from '@shared/models/project.model';
|
import {Project, ProjectDialogBody} from '@shared/models/project.model';
|
||||||
import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
|
import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
|
||||||
import {filter, mergeMap} from 'rxjs/operators';
|
import {filter, mergeMap} from 'rxjs/operators';
|
||||||
import {NotificationService, PopupType} from '@shared/services/notification.service';
|
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service';
|
||||||
import {ProjectService} from '@shared/services/project.service';
|
import {ProjectService} from '@shared/services/api/project.service';
|
||||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||||
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
|
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
|
||||||
import {InitProjectState} from '@shared/stores/project-state/project-state.actions';
|
import {InitProjectState} from '@shared/stores/project-state/project-state.actions';
|
||||||
|
import {ExportReportDialogService} from '@shared/modules/export-report-dialog/service/export-report-dialog.service';
|
||||||
|
import {ExportReportDialogComponent} from '@shared/modules/export-report-dialog/export-report-dialog.component';
|
||||||
|
|
||||||
@UntilDestroy()
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -28,9 +30,10 @@ export class ObjectiveHeaderComponent implements OnInit {
|
||||||
|
|
||||||
constructor(private store: Store,
|
constructor(private store: Store,
|
||||||
private readonly notificationService: NotificationService,
|
private readonly notificationService: NotificationService,
|
||||||
private projectService: ProjectService,
|
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
private projectDialogService: ProjectDialogService,
|
private projectDialogService: ProjectDialogService,
|
||||||
|
private projectService: ProjectService,
|
||||||
|
private exportReportDialogService: ExportReportDialogService,
|
||||||
private readonly router: Router) {
|
private readonly router: Router) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,8 +90,30 @@ export class ObjectiveHeaderComponent implements OnInit {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickExportPentest(): void {
|
onClickExportPentestReport(): void {
|
||||||
// tslint:disable-next-line:no-console
|
this.exportReportDialogService.openExportReportDialog(
|
||||||
console.info('To be implemented..');
|
ExportReportDialogComponent,
|
||||||
|
this.selectedProject$.getValue(),
|
||||||
|
{
|
||||||
|
closeOnEsc: true,
|
||||||
|
hasScroll: false,
|
||||||
|
autoFocus: true,
|
||||||
|
closeOnBackdropClick: true
|
||||||
|
}
|
||||||
|
).pipe(
|
||||||
|
filter(value => !!value),
|
||||||
|
/*ToDo: Needed?*/
|
||||||
|
/*mergeMap((value: ProjectDialogBody) => this.projectService.updateProject(this.selectedProject$.getValue().id, value)),*/
|
||||||
|
untilDestroyed(this)
|
||||||
|
).subscribe({
|
||||||
|
next: () => {
|
||||||
|
// ToDo: Open report in new Tab or just download it?
|
||||||
|
// this.notificationService.showPopup('project.popup.update.success', PopupType.SUCCESS);
|
||||||
|
},
|
||||||
|
error: error => {
|
||||||
|
console.error(error);
|
||||||
|
// this.notificationService.showPopup('project.popup.update.failed', PopupType.FAILURE);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
||||||
import {FlexLayoutModule} from '@angular/flex-layout';
|
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';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@ -50,6 +51,7 @@ import {ObjectiveOverviewRoutingModule} from './objective-overview-routing.modul
|
||||||
FontAwesomeModule,
|
FontAwesomeModule,
|
||||||
FlexLayoutModule,
|
FlexLayoutModule,
|
||||||
NbActionsModule,
|
NbActionsModule,
|
||||||
|
ExportReportDialogModule,
|
||||||
ObjectiveOverviewRoutingModule
|
ObjectiveOverviewRoutingModule
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {NbGetters, NbTreeGridDataSource, NbTreeGridDataSourceBuilder} from '@nebular/theme';
|
import {NbGetters, NbTreeGridDataSource, NbTreeGridDataSourceBuilder} from '@nebular/theme';
|
||||||
import {Pentest, ObjectiveEntry, transformPentestsToObjectiveEntries} from '@shared/models/pentest.model';
|
import {Pentest, ObjectiveEntry, transformPentestsToObjectiveEntries} from '@shared/models/pentest.model';
|
||||||
import {PentestService} from '@shared/services/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 {ProjectState} from '@shared/stores/project-state/project-state';
|
||||||
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||||
|
|
|
@ -14,8 +14,8 @@ import {ThemeModule} from '@assets/@theme/theme.module';
|
||||||
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
|
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
|
||||||
import {HttpLoaderFactory} from '../../../common-app.module';
|
import {HttpLoaderFactory} from '../../../common-app.module';
|
||||||
import {HttpClient} from '@angular/common/http';
|
import {HttpClient} from '@angular/common/http';
|
||||||
import {NotificationService} from '@shared/services/notification.service';
|
import {NotificationService} from '@shared/services/toaster-service/notification.service';
|
||||||
import {NotificationServiceMock} from '@shared/services/notification.service.mock';
|
import {NotificationServiceMock} from '@shared/services/toaster-service/notification.service.mock';
|
||||||
import {MockComponent} from 'ng-mocks';
|
import {MockComponent} from 'ng-mocks';
|
||||||
import {LoadingSpinnerComponent} from '@shared/widgets/loading-spinner/loading-spinner.component';
|
import {LoadingSpinnerComponent} from '@shared/widgets/loading-spinner/loading-spinner.component';
|
||||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||||
|
|
|
@ -3,7 +3,7 @@ import {BehaviorSubject, Observable} from 'rxjs';
|
||||||
import {Pentest} from '@shared/models/pentest.model';
|
import {Pentest} from '@shared/models/pentest.model';
|
||||||
import * as FA from '@fortawesome/free-solid-svg-icons';
|
import * as FA from '@fortawesome/free-solid-svg-icons';
|
||||||
import {NbGetters, NbTreeGridDataSource, NbTreeGridDataSourceBuilder} from '@nebular/theme';
|
import {NbGetters, NbTreeGridDataSource, NbTreeGridDataSourceBuilder} from '@nebular/theme';
|
||||||
import {NotificationService, PopupType} from '@shared/services/notification.service';
|
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service';
|
||||||
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||||
import {catchError, filter, mergeMap, switchMap, tap} from 'rxjs/operators';
|
import {catchError, filter, mergeMap, switchMap, tap} from 'rxjs/operators';
|
||||||
import {
|
import {
|
||||||
|
@ -19,11 +19,11 @@ import {Store} from '@ngxs/store';
|
||||||
import {PentestStatus} from '@shared/models/pentest-status.model';
|
import {PentestStatus} from '@shared/models/pentest-status.model';
|
||||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||||
import {CommentDialogService} from '@shared/modules/comment-dialog/service/comment-dialog.service';
|
import {CommentDialogService} from '@shared/modules/comment-dialog/service/comment-dialog.service';
|
||||||
import {CommentService} from '@shared/services/comment.service';
|
import {CommentService} from '@shared/services/api/comment.service';
|
||||||
import {UpdatePentestComments} from '@shared/stores/project-state/project-state.actions';
|
import {UpdatePentestComments} from '@shared/stores/project-state/project-state.actions';
|
||||||
import {CommentDialogComponent} from '@shared/modules/comment-dialog/comment-dialog.component';
|
import {CommentDialogComponent} from '@shared/modules/comment-dialog/comment-dialog.component';
|
||||||
import {Finding} from '@shared/models/finding.model';
|
import {Finding} from '@shared/models/finding.model';
|
||||||
import {FindingService} from '@shared/services/finding.service';
|
import {FindingService} from '@shared/services/api/finding.service';
|
||||||
|
|
||||||
@UntilDestroy()
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
|
|
|
@ -11,8 +11,8 @@ import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||||
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||||
import {Category} from '@shared/models/category.model';
|
import {Category} from '@shared/models/category.model';
|
||||||
import {PentestStatus} from '@shared/models/pentest-status.model';
|
import {PentestStatus} from '@shared/models/pentest-status.model';
|
||||||
import {NotificationService} from '@shared/services/notification.service';
|
import {NotificationService} from '@shared/services/toaster-service/notification.service';
|
||||||
import {NotificationServiceMock} from '@shared/services/notification.service.mock';
|
import {NotificationServiceMock} from '@shared/services/toaster-service/notification.service.mock';
|
||||||
|
|
||||||
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||||
selectedProject: {
|
selectedProject: {
|
||||||
|
|
|
@ -5,8 +5,8 @@ import {Store} from '@ngxs/store';
|
||||||
import {ProjectState} from '@shared/stores/project-state/project-state';
|
import {ProjectState} from '@shared/stores/project-state/project-state';
|
||||||
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
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/pentest.service';
|
import {PentestService} from '@shared/services/api/pentest.service';
|
||||||
import {NotificationService} from '@shared/services/notification.service';
|
import {NotificationService} from '@shared/services/toaster-service/notification.service';
|
||||||
|
|
||||||
@UntilDestroy()
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
|
|
|
@ -9,8 +9,8 @@ import {HttpClient} from '@angular/common/http';
|
||||||
import {NgxsModule, Store} from '@ngxs/store';
|
import {NgxsModule, Store} from '@ngxs/store';
|
||||||
import {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/stores/project-state/project-state';
|
import {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/stores/project-state/project-state';
|
||||||
import {NbButtonModule, NbTreeGridModule} from '@nebular/theme';
|
import {NbButtonModule, NbTreeGridModule} from '@nebular/theme';
|
||||||
import {NotificationService} from '@shared/services/notification.service';
|
import {NotificationService} from '@shared/services/toaster-service/notification.service';
|
||||||
import {NotificationServiceMock} from '@shared/services/notification.service.mock';
|
import {NotificationServiceMock} from '@shared/services/toaster-service/notification.service.mock';
|
||||||
import {CommonModule} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
import {MockComponent} from 'ng-mocks';
|
import {MockComponent} from 'ng-mocks';
|
||||||
import {LoadingSpinnerComponent} from '@shared/widgets/loading-spinner/loading-spinner.component';
|
import {LoadingSpinnerComponent} from '@shared/widgets/loading-spinner/loading-spinner.component';
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {PentestService} from '@shared/services/pentest.service';
|
import {PentestService} from '@shared/services/api/pentest.service';
|
||||||
import {BehaviorSubject, Observable} from 'rxjs';
|
import {BehaviorSubject, Observable} from 'rxjs';
|
||||||
import {Pentest} from '@shared/models/pentest.model';
|
import {Pentest} from '@shared/models/pentest.model';
|
||||||
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||||
import {catchError, filter, mergeMap, switchMap, tap} from 'rxjs/operators';
|
import {catchError, filter, mergeMap, switchMap, tap} from 'rxjs/operators';
|
||||||
import {NotificationService, PopupType} from '@shared/services/notification.service';
|
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service';
|
||||||
import {
|
import {
|
||||||
Finding,
|
Finding,
|
||||||
FindingDialogBody,
|
FindingDialogBody,
|
||||||
|
@ -22,7 +22,7 @@ import {Store} from '@ngxs/store';
|
||||||
import {UpdatePentestFindings} from '@shared/stores/project-state/project-state.actions';
|
import {UpdatePentestFindings} from '@shared/stores/project-state/project-state.actions';
|
||||||
import {ProjectState} from '@shared/stores/project-state/project-state';
|
import {ProjectState} from '@shared/stores/project-state/project-state';
|
||||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||||
import {FindingService} from '@shared/services/finding.service';
|
import {FindingService} from '@shared/services/api/finding.service';
|
||||||
|
|
||||||
@UntilDestroy()
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
|
|
|
@ -11,8 +11,8 @@ import {NgxsModule, Store} from '@ngxs/store';
|
||||||
import {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/stores/project-state/project-state';
|
import {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/stores/project-state/project-state';
|
||||||
import {Category} from '@shared/models/category.model';
|
import {Category} from '@shared/models/category.model';
|
||||||
import {PentestStatus} from '@shared/models/pentest-status.model';
|
import {PentestStatus} from '@shared/models/pentest-status.model';
|
||||||
import {NotificationService} from '@shared/services/notification.service';
|
import {NotificationService} from '@shared/services/toaster-service/notification.service';
|
||||||
import {NotificationServiceMock} from '@shared/services/notification.service.mock';
|
import {NotificationServiceMock} from '@shared/services/toaster-service/notification.service.mock';
|
||||||
|
|
||||||
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||||
selectedProject: {
|
selectedProject: {
|
||||||
|
|
|
@ -9,9 +9,9 @@ import {BehaviorSubject} from 'rxjs';
|
||||||
import {ProjectState} from '@shared/stores/project-state/project-state';
|
import {ProjectState} from '@shared/stores/project-state/project-state';
|
||||||
import {Project} from '@shared/models/project.model';
|
import {Project} from '@shared/models/project.model';
|
||||||
import {Pentest, transformPentestToRequestBody} from '@shared/models/pentest.model';
|
import {Pentest, transformPentestToRequestBody} from '@shared/models/pentest.model';
|
||||||
import {NotificationService, PopupType} from '@shared/services/notification.service';
|
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service';
|
||||||
import {PentestStatus} from '@shared/models/pentest-status.model';
|
import {PentestStatus} from '@shared/models/pentest-status.model';
|
||||||
import {PentestService} from '@shared/services/pentest.service';
|
import {PentestService} from '@shared/services/api/pentest.service';
|
||||||
import {StatusText} from '@shared/widgets/status-tag/status-tag.component';
|
import {StatusText} from '@shared/widgets/status-tag/status-tag.component';
|
||||||
|
|
||||||
@UntilDestroy()
|
@UntilDestroy()
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {NbButtonModule, NbCardModule, NbProgressBarModule, NbSpinnerModule} from
|
||||||
import {FlexLayoutModule} from '@angular/flex-layout';
|
import {FlexLayoutModule} from '@angular/flex-layout';
|
||||||
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
||||||
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
|
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
|
||||||
import {ProjectService} from '@shared/services/project.service';
|
import {ProjectService} from '@shared/services/api/project.service';
|
||||||
import {HttpLoaderFactory} from '../common-app.module';
|
import {HttpLoaderFactory} from '../common-app.module';
|
||||||
import {HttpClient} from '@angular/common/http';
|
import {HttpClient} from '@angular/common/http';
|
||||||
import {RouterTestingModule} from '@angular/router/testing';
|
import {RouterTestingModule} from '@angular/router/testing';
|
||||||
|
@ -16,9 +16,9 @@ import {NgxsModule} from '@ngxs/store';
|
||||||
import {SessionState} from '@shared/stores/session-state/session-state';
|
import {SessionState} from '@shared/stores/session-state/session-state';
|
||||||
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||||
import {NotificationService} from '@shared/services/notification.service';
|
import {NotificationService} from '@shared/services/toaster-service/notification.service';
|
||||||
import {NotificationServiceMock} from '@shared/services/notification.service.mock';
|
import {NotificationServiceMock} from '@shared/services/toaster-service/notification.service.mock';
|
||||||
import {ProjectServiceMock} from '@shared/services/project.service.mock';
|
import {ProjectServiceMock} from '@shared/services/api/project.service.mock';
|
||||||
import {ThemeModule} from '@assets/@theme/theme.module';
|
import {ThemeModule} from '@assets/@theme/theme.module';
|
||||||
import {LoadingSpinnerComponent} from '@shared/widgets/loading-spinner/loading-spinner.component';
|
import {LoadingSpinnerComponent} from '@shared/widgets/loading-spinner/loading-spinner.component';
|
||||||
import {KeycloakService} from 'keycloak-angular';
|
import {KeycloakService} from 'keycloak-angular';
|
||||||
|
|
|
@ -3,8 +3,8 @@ import * as FA from '@fortawesome/free-solid-svg-icons';
|
||||||
import {Project, ProjectDialogBody} from '@shared/models/project.model';
|
import {Project, ProjectDialogBody} from '@shared/models/project.model';
|
||||||
import {BehaviorSubject, Observable} from 'rxjs';
|
import {BehaviorSubject, Observable} from 'rxjs';
|
||||||
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||||
import {ProjectService} from '@shared/services/project.service';
|
import {ProjectService} from '@shared/services/api/project.service';
|
||||||
import {NotificationService, PopupType} from '@shared/services/notification.service';
|
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service';
|
||||||
import {catchError, filter, mergeMap, switchMap, tap} from 'rxjs/operators';
|
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';
|
||||||
|
@ -17,7 +17,7 @@ import {ProjectDialogService} from '@shared/modules/project-dialog/service/proje
|
||||||
styleUrls: ['./project-overview.component.scss']
|
styleUrls: ['./project-overview.component.scss']
|
||||||
})
|
})
|
||||||
export class ProjectOverviewComponent implements OnInit {
|
export class ProjectOverviewComponent implements OnInit {
|
||||||
|
// HTML only
|
||||||
readonly fa = FA;
|
readonly fa = FA;
|
||||||
|
|
||||||
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
||||||
|
|
|
@ -10,20 +10,23 @@ import {RouterTestingModule} from '@angular/router/testing';
|
||||||
import {NgxsModule, Store} from '@ngxs/store';
|
import {NgxsModule, Store} from '@ngxs/store';
|
||||||
import {SessionState} from '@shared/stores/session-state/session-state';
|
import {SessionState} from '@shared/stores/session-state/session-state';
|
||||||
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||||
import {NbCardModule, NbLayoutModule} from '@nebular/theme';
|
import {NbCardModule, NbDialogRef, NbLayoutModule} from '@nebular/theme';
|
||||||
import {KeycloakService} from 'keycloak-angular';
|
import {KeycloakService} from 'keycloak-angular';
|
||||||
import {ObjectiveOverviewModule} from '../../objective-overview';
|
import {ObjectiveOverviewModule} from '../../objective-overview';
|
||||||
import {NotificationService} from '@shared/services/notification.service';
|
import {NotificationService} from '@shared/services/toaster-service/notification.service';
|
||||||
import {NotificationServiceMock} from '@shared/services/notification.service.mock';
|
import {NotificationServiceMock} from '@shared/services/toaster-service/notification.service.mock';
|
||||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||||
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
|
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
|
||||||
import {ProjectService} from '@shared/services/project.service';
|
import {ProjectService} from '@shared/services/api/project.service';
|
||||||
import {ProjectServiceMock} from '@shared/services/project.service.mock';
|
import {ProjectServiceMock} from '@shared/services/api/project.service.mock';
|
||||||
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
|
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
|
||||||
import {ProjectDialogServiceMock} from '@shared/modules/project-dialog/service/project-dialog.service.mock';
|
import {ProjectDialogServiceMock} from '@shared/modules/project-dialog/service/project-dialog.service.mock';
|
||||||
import {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/stores/project-state/project-state';
|
import {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/stores/project-state/project-state';
|
||||||
import {Category} from '@shared/models/category.model';
|
import {Category} from '@shared/models/category.model';
|
||||||
import {PentestStatus} from '@shared/models/pentest-status.model';
|
import {PentestStatus} from '@shared/models/pentest-status.model';
|
||||||
|
import {createSpyObj} from '@shared/modules/finding-dialog/finding-dialog.component.spec';
|
||||||
|
import {ExportReportDialogService} from '@shared/modules/export-report-dialog/service/export-report-dialog.service';
|
||||||
|
import {ExportReportDialogServiceMock} from '@shared/modules/export-report-dialog/service/export-report-dialog.service.mock';
|
||||||
|
|
||||||
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||||
selectedProject: {
|
selectedProject: {
|
||||||
|
@ -58,6 +61,8 @@ describe('ProjectComponent', () => {
|
||||||
let store: Store;
|
let store: Store;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
const dialogSpy = createSpyObj('NbDialogRef', ['close']);
|
||||||
|
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
ProjectComponent
|
ProjectComponent
|
||||||
|
@ -83,8 +88,10 @@ describe('ProjectComponent', () => {
|
||||||
providers: [
|
providers: [
|
||||||
KeycloakService,
|
KeycloakService,
|
||||||
{provide: ProjectService, useValue: new ProjectServiceMock()},
|
{provide: ProjectService, useValue: new ProjectServiceMock()},
|
||||||
{provide: ProjectDialogService, useClass: ProjectDialogServiceMock},
|
|
||||||
{provide: DialogService, useClass: DialogServiceMock},
|
{provide: DialogService, useClass: DialogServiceMock},
|
||||||
|
{provide: NbDialogRef, useValue: dialogSpy},
|
||||||
|
{provide: ExportReportDialogService, useClass: ExportReportDialogServiceMock},
|
||||||
|
{provide: ProjectDialogService, useClass: ProjectDialogServiceMock},
|
||||||
{provide: NotificationService, useClass: NotificationServiceMock}
|
{provide: NotificationService, useClass: NotificationServiceMock}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
"action.update": "Speichern",
|
"action.update": "Speichern",
|
||||||
"action.edit": "Editieren",
|
"action.edit": "Editieren",
|
||||||
"action.export": "Exportieren",
|
"action.export": "Exportieren",
|
||||||
|
"action.download": "Herunterladen",
|
||||||
|
"action.report": "Bericht",
|
||||||
"action.reset": "Zurücksetzen",
|
"action.reset": "Zurücksetzen",
|
||||||
"action.yes": "Ja",
|
"action.yes": "Ja",
|
||||||
"action.no": "Nein",
|
"action.no": "Nein",
|
||||||
|
@ -19,7 +21,8 @@
|
||||||
"no.progress": "Kein Fortschritt",
|
"no.progress": "Kein Fortschritt",
|
||||||
"validationMessage": {
|
"validationMessage": {
|
||||||
"inputNotMatching": "Eingabe stimmt nicht überein!"
|
"inputNotMatching": "Eingabe stimmt nicht überein!"
|
||||||
}
|
},
|
||||||
|
"project": "Projekt"
|
||||||
},
|
},
|
||||||
"languageKeys":{
|
"languageKeys":{
|
||||||
"de-DE": "Deutsch",
|
"de-DE": "Deutsch",
|
||||||
|
@ -43,6 +46,19 @@
|
||||||
"failed": "Benutzername oder Passwort falsch",
|
"failed": "Benutzername oder Passwort falsch",
|
||||||
"unauthorized": "Benutzer nicht gefunden. Bitte registrieren und erneut versuchen"
|
"unauthorized": "Benutzer nicht gefunden. Bitte registrieren und erneut versuchen"
|
||||||
},
|
},
|
||||||
|
"report": {
|
||||||
|
"dialog": {
|
||||||
|
"header": "Penetrationstestbereicht exportieren",
|
||||||
|
"formatLabel": "Wählen Sie das Exportformat für den Bericht:",
|
||||||
|
"languageLabel": "Wählen Sie die Berichtssprache:"
|
||||||
|
},
|
||||||
|
"popup": {
|
||||||
|
"generation.success": "Bericht erfolgreich generiert",
|
||||||
|
"generation.failed": "Bericht konnte nicht generiert werden"
|
||||||
|
},
|
||||||
|
"generate": "Bericht generieren",
|
||||||
|
"hint": "{{completedObjectivesNumber}} Ihrer abgeschlossenen Ziele wird in den Pentestbericht aufgenommen."
|
||||||
|
},
|
||||||
"project": {
|
"project": {
|
||||||
"title.label": "Projekt Titel",
|
"title.label": "Projekt Titel",
|
||||||
"client.label": "Name des Auftraggebers",
|
"client.label": "Name des Auftraggebers",
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
"action.update": "Update",
|
"action.update": "Update",
|
||||||
"action.edit": "Edit",
|
"action.edit": "Edit",
|
||||||
"action.export": "Export",
|
"action.export": "Export",
|
||||||
|
"action.download": "Download",
|
||||||
|
"action.report": "Report",
|
||||||
"action.reset": "Reset",
|
"action.reset": "Reset",
|
||||||
"action.yes": "Yes",
|
"action.yes": "Yes",
|
||||||
"action.no": "No",
|
"action.no": "No",
|
||||||
|
@ -19,7 +21,8 @@
|
||||||
"no.progress": "No progress",
|
"no.progress": "No progress",
|
||||||
"validationMessage": {
|
"validationMessage": {
|
||||||
"inputNotMatching": "Input does not match!"
|
"inputNotMatching": "Input does not match!"
|
||||||
}
|
},
|
||||||
|
"project": "Project"
|
||||||
},
|
},
|
||||||
"languageKeys":{
|
"languageKeys":{
|
||||||
"de-DE": "German",
|
"de-DE": "German",
|
||||||
|
@ -43,6 +46,19 @@
|
||||||
"failed": "Wrong username or password",
|
"failed": "Wrong username or password",
|
||||||
"unauthorized": "User not found. Please register and try again"
|
"unauthorized": "User not found. Please register and try again"
|
||||||
},
|
},
|
||||||
|
"report": {
|
||||||
|
"dialog": {
|
||||||
|
"header": "Export Penetrationtest Report",
|
||||||
|
"formatLabel": "Pick export format for report:",
|
||||||
|
"languageLabel": "Pick report language:"
|
||||||
|
},
|
||||||
|
"popup": {
|
||||||
|
"generation.success": "Report generation successful",
|
||||||
|
"generation.failed": "Report could not be generated"
|
||||||
|
},
|
||||||
|
"generate": "Generate Report",
|
||||||
|
"hint": "{{completedObjectivesNumber}} of your completed objective(s) will be included in the pentest report."
|
||||||
|
},
|
||||||
"project": {
|
"project": {
|
||||||
"title.label": "Project Title",
|
"title.label": "Project Title",
|
||||||
"client.label": "Name of Client",
|
"client.label": "Name of Client",
|
||||||
|
|
|
@ -8,4 +8,5 @@ export const environment = {
|
||||||
|
|
||||||
// backend service
|
// backend service
|
||||||
apiEndpoint: 'http://localhost:8443',
|
apiEndpoint: 'http://localhost:8443',
|
||||||
|
reportEndpoint: 'http://localhost:8444'
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,6 +13,7 @@ export const environment = {
|
||||||
|
|
||||||
// backend service
|
// backend service
|
||||||
apiEndpoint: 'http://localhost:8443',
|
apiEndpoint: 'http://localhost:8443',
|
||||||
|
reportEndpoint: 'http://localhost:8444'
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export const GlobalTitlesVariables = {
|
export const GlobalTitlesVariables = {
|
||||||
SECURITYC4PO_TITLE: 'Security C4PO',
|
SECURITYC4PO_TITLE: 'Security C4PO',
|
||||||
NOVATEC_NAME: 'Novatec'
|
TOTAL_OWASP_OBJECTIVES: 95
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
/**
|
||||||
|
* Method is used to download file.
|
||||||
|
* @param data - Array Buffer data
|
||||||
|
* @param type - type of the document.
|
||||||
|
*/
|
||||||
|
export function downloadFile(data: any, type: string): void {
|
||||||
|
const blob = new Blob([data], {type});
|
||||||
|
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.');
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ export class TokenInterceptor implements HttpInterceptor {
|
||||||
private static listOfKeycloakRelevantHosts(): { origin: string }[] {
|
private static listOfKeycloakRelevantHosts(): { origin: string }[] {
|
||||||
const relevantList = new Array<{ origin: string }>();
|
const relevantList = new Array<{ origin: string }>();
|
||||||
relevantList.push({origin: getOriginByUrl(environment.apiEndpoint)});
|
relevantList.push({origin: getOriginByUrl(environment.apiEndpoint)});
|
||||||
|
relevantList.push({origin: getOriginByUrl(environment.reportEndpoint)});
|
||||||
relevantList.push({origin: getOriginByUrl(environment.keycloakURL)});
|
relevantList.push({origin: getOriginByUrl(environment.keycloakURL)});
|
||||||
return relevantList;
|
return relevantList;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import {PentestStatus} from '@shared/models/pentest-status.model';
|
||||||
|
|
||||||
export class Project {
|
export class Project {
|
||||||
id: string;
|
id: string;
|
||||||
client: string;
|
client: string;
|
||||||
|
@ -5,7 +7,8 @@ export class Project {
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
tester: string;
|
tester: string;
|
||||||
summary: string;
|
summary: string;
|
||||||
testingProgress: number;
|
projectPentests?: Array<ProjectPentests>;
|
||||||
|
testingProgress?: number;
|
||||||
createdBy: string;
|
createdBy: string;
|
||||||
|
|
||||||
constructor(id: string,
|
constructor(id: string,
|
||||||
|
@ -13,7 +16,8 @@ export class Project {
|
||||||
title: string,
|
title: string,
|
||||||
createdAt: Date,
|
createdAt: Date,
|
||||||
tester: string,
|
tester: string,
|
||||||
testingProgress: number,
|
projectPentests?: Array<ProjectPentests>,
|
||||||
|
testingProgress?: number,
|
||||||
summary?: string,
|
summary?: string,
|
||||||
createdBy?: string) {
|
createdBy?: string) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
@ -21,6 +25,7 @@ export class Project {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.createdAt = createdAt;
|
this.createdAt = createdAt;
|
||||||
this.tester = tester;
|
this.tester = tester;
|
||||||
|
this.projectPentests = projectPentests;
|
||||||
this.testingProgress = testingProgress;
|
this.testingProgress = testingProgress;
|
||||||
this.summary = summary;
|
this.summary = summary;
|
||||||
this.createdBy = createdBy;
|
this.createdBy = createdBy;
|
||||||
|
@ -31,5 +36,10 @@ export interface ProjectDialogBody {
|
||||||
title: string;
|
title: string;
|
||||||
client: string;
|
client: string;
|
||||||
tester: string;
|
tester: string;
|
||||||
// ToDo: summary: string;
|
summary: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProjectPentests {
|
||||||
|
pentestId: string;
|
||||||
|
status: PentestStatus;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,8 @@ import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
|
||||||
import {HttpLoaderFactory} from '../../../app/common-app.module';
|
import {HttpLoaderFactory} from '../../../app/common-app.module';
|
||||||
import {HttpClient, HttpClientModule} from '@angular/common/http';
|
import {HttpClient, HttpClientModule} from '@angular/common/http';
|
||||||
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||||
import {NotificationService} from '@shared/services/notification.service';
|
import {NotificationService} from '@shared/services/toaster-service/notification.service';
|
||||||
import {NotificationServiceMock} from '@shared/services/notification.service.mock';
|
import {NotificationServiceMock} from '@shared/services/toaster-service/notification.service.mock';
|
||||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||||
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
|
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
|
||||||
import Mock = jest.Mock;
|
import Mock = jest.Mock;
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
<nb-card #dialog accent="{{dialogData?.options[0].accentColor}}" class="export-report-dialog">
|
||||||
|
<nb-card-header fxLayoutAlign="start center" class="export-report-header">
|
||||||
|
{{ dialogData?.options[0].headerLabelKey | translate }}
|
||||||
|
</nb-card-header>
|
||||||
|
<nb-card-body>
|
||||||
|
<div fxLayout="column" fxLayoutGap="1rem" fxLayoutAlign="start start">
|
||||||
|
<label class="export-format-label">
|
||||||
|
{{ 'global.project' | translate }}:
|
||||||
|
</label>
|
||||||
|
<b class="project-title">
|
||||||
|
{{selectedProject$.getValue()?.title}}
|
||||||
|
</b>
|
||||||
|
<!--Chart Objective Component-->
|
||||||
|
<div fxLayout="column" fxLayoutGap="2rem" fxLayoutAlign="center center">
|
||||||
|
<app-objective-chart [projectPentestData]="selectedProject$.getValue().projectPentests"></app-objective-chart>
|
||||||
|
<span
|
||||||
|
class="hint"> {{'popup.info' | translate}} {{ 'report.hint' | translate: this.completedProjectPentests$?.getValue() }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<!--Export Language Radio Selection-->
|
||||||
|
<label class="export-language-label">
|
||||||
|
{{ 'report.dialog.languageLabel' | translate }}
|
||||||
|
</label>
|
||||||
|
<nb-radio-group name="language" [formControl]="exportReportLanguageControl"
|
||||||
|
class="export-radio-buttons languageContainer" status="info">
|
||||||
|
<nb-radio value="{{exportLanguages.ENGLISH}}">
|
||||||
|
<img src="../../assets/images/flags/{{exportLanguages.ENGLISH}}.svg" class="flag" width="25rem" height="16rem"
|
||||||
|
alt="">
|
||||||
|
</nb-radio>
|
||||||
|
<nb-radio disabled value="{{exportLanguages.GERMAN}}">
|
||||||
|
<img src="../../assets/images/flags/{{exportLanguages.GERMAN}}.svg" class="flag" width="25rem" height="16rem"
|
||||||
|
alt="">
|
||||||
|
</nb-radio>
|
||||||
|
</nb-radio-group>
|
||||||
|
<!--Export Format Radio Selection-->
|
||||||
|
<label class="export-format-label">
|
||||||
|
{{ 'report.dialog.formatLabel' | translate }}
|
||||||
|
</label>
|
||||||
|
<nb-radio-group name="format" [formControl]="exportReportFormatControl" class="export-radio-buttons"
|
||||||
|
status="info">
|
||||||
|
<nb-radio value="{{exportFormats.PDF}}">
|
||||||
|
{{exportFormats.PDF}}
|
||||||
|
</nb-radio>
|
||||||
|
<nb-radio disabled value="{{exportFormats.CSV}}">
|
||||||
|
{{exportFormats.CSV}}
|
||||||
|
</nb-radio>
|
||||||
|
<nb-radio disabled value="{{exportFormats.HTML}}">
|
||||||
|
{{exportFormats.HTML}}
|
||||||
|
</nb-radio>
|
||||||
|
</nb-radio-group>
|
||||||
|
</div>
|
||||||
|
</nb-card-body>
|
||||||
|
<nb-card-footer fxLayout="row" fxLayoutGap="1.5rem" fxLayoutAlign="end end">
|
||||||
|
<button nbButton status="info" size="small" class="dialog-button generate-report-button"
|
||||||
|
[disabled]="(loading$.getValue() === true || completedProjectPentests$?.getValue().completedObjectivesNumber < 1)"
|
||||||
|
(click)="onClickExport(exportReportFormatControl.value, exportReportLanguageControl.value)">
|
||||||
|
<fa-icon [icon]="fa.faFileExport"
|
||||||
|
class="element-icon fa-lg"></fa-icon>
|
||||||
|
<span class="element-text"> {{ dialogData?.options[0].buttonKey | translate}} </span>
|
||||||
|
</button>
|
||||||
|
<button nbButton status="danger" size="small" class="dialog-button" (click)="onClickClose()">
|
||||||
|
{{ 'global.action.cancel' | translate }}
|
||||||
|
</button>
|
||||||
|
</nb-card-footer>
|
||||||
|
</nb-card>
|
||||||
|
|
||||||
|
<app-loading-spinner [isLoading$]="isLoading()" *ngIf="isLoading() | async"></app-loading-spinner>
|
|
@ -0,0 +1,84 @@
|
||||||
|
@import "../../../assets/@theme/styles/_dialog.scss";
|
||||||
|
@import '../../../assets/@theme/styles/themes';
|
||||||
|
|
||||||
|
.export-report-dialog {
|
||||||
|
width: 45.25rem !important;
|
||||||
|
height: 54.25rem;
|
||||||
|
|
||||||
|
.export-report-header {
|
||||||
|
height: 8vh;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.languageContainer {
|
||||||
|
display: flex;
|
||||||
|
max-width: 8rem;
|
||||||
|
min-width: 8rem;
|
||||||
|
|
||||||
|
.flag {
|
||||||
|
// object-fit: contain;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint {
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
color: nb-theme(color-danger-default);
|
||||||
|
}
|
||||||
|
|
||||||
|
.export-radio-buttons {
|
||||||
|
float: left;
|
||||||
|
clear: none;
|
||||||
|
margin-left: 1rem;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
nb-form-field {
|
||||||
|
padding: 0.5rem 0 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
padding: 0.25rem 0.5rem 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field {
|
||||||
|
width: 26.75rem;
|
||||||
|
// width: 30rem !important;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-textarea {
|
||||||
|
width: 26.75rem !important;
|
||||||
|
// width: 30rem !important;
|
||||||
|
height: 8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-textarea:disabled {
|
||||||
|
width: 26.75rem !important;
|
||||||
|
// width: 30rem !important;
|
||||||
|
background-color: nb-theme(color-basic-transparent-focus);
|
||||||
|
height: 8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.generate-report-button {
|
||||||
|
width: calc(45.25rem - 5.75rem) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.element-text {
|
||||||
|
font-size: 1rem !important;
|
||||||
|
text-transform: uppercase !important;
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-text {
|
||||||
|
float: left;
|
||||||
|
color: nb-theme(color-danger-default);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||||
|
|
||||||
|
import {ExportReportDialogComponent} from './export-report-dialog.component';
|
||||||
|
import {CommonModule} from '@angular/common';
|
||||||
|
import {
|
||||||
|
NB_DIALOG_CONFIG,
|
||||||
|
NbButtonModule,
|
||||||
|
NbCardModule,
|
||||||
|
NbDialogRef,
|
||||||
|
NbFormFieldModule,
|
||||||
|
NbInputModule,
|
||||||
|
NbLayoutModule, NbRadioModule,
|
||||||
|
NbTagModule
|
||||||
|
} from '@nebular/theme';
|
||||||
|
import {FlexLayoutModule} from '@angular/flex-layout';
|
||||||
|
import {NG_VALUE_ACCESSOR, ReactiveFormsModule} from '@angular/forms';
|
||||||
|
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||||
|
import {ThemeModule} from '@assets/@theme/theme.module';
|
||||||
|
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
|
||||||
|
import {HttpLoaderFactory} from '../../../app/common-app.module';
|
||||||
|
import {HttpClient, HttpClientModule} from '@angular/common/http';
|
||||||
|
import {NgxsModule} from '@ngxs/store';
|
||||||
|
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||||
|
import {NotificationService} from '@shared/services/toaster-service/notification.service';
|
||||||
|
import {NotificationServiceMock} from '@shared/services/toaster-service/notification.service.mock';
|
||||||
|
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||||
|
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
|
||||||
|
import {createSpyObj} from '@shared/modules/finding-dialog/finding-dialog.component.spec';
|
||||||
|
import {Project, ProjectPentests} from '@shared/models/project.model';
|
||||||
|
import {PentestStatus} from '@shared/models/pentest-status.model';
|
||||||
|
import {ObjectiveChartModule} from '@shared/modules/objective-chart/objective-chart.module';
|
||||||
|
import {forwardRef} from '@angular/core';
|
||||||
|
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
||||||
|
|
||||||
|
describe('ExportReportDialogComponent', () => {
|
||||||
|
let component: ExportReportDialogComponent;
|
||||||
|
let fixture: ComponentFixture<ExportReportDialogComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const dialogSpy = createSpyObj('NbDialogRef', ['close']);
|
||||||
|
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [
|
||||||
|
ExportReportDialogComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
NbCardModule,
|
||||||
|
NbButtonModule,
|
||||||
|
NbFormFieldModule,
|
||||||
|
NbInputModule,
|
||||||
|
FlexLayoutModule,
|
||||||
|
FontAwesomeModule,
|
||||||
|
TranslateModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
NbRadioModule,
|
||||||
|
ObjectiveChartModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
ThemeModule.forRoot(),
|
||||||
|
TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useFactory: HttpLoaderFactory,
|
||||||
|
deps: [HttpClient]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
NgxsModule.forRoot([]),
|
||||||
|
HttpClientModule,
|
||||||
|
HttpClientTestingModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{provide: NotificationService, useValue: new NotificationServiceMock()},
|
||||||
|
{provide: DialogService, useClass: DialogServiceMock},
|
||||||
|
{provide: NbDialogRef, useValue: dialogSpy},
|
||||||
|
{provide: NB_DIALOG_CONFIG, useValue: mockedExportPentestDialogData}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.overrideProvider(NB_DIALOG_CONFIG, {useValue: mockedExportPentestDialogData});
|
||||||
|
fixture = TestBed.createComponent(ExportReportDialogComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
component.selectedProject$.next(mockProject);
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockedPentests: ProjectPentests[] = [
|
||||||
|
{
|
||||||
|
pentestId: '122',
|
||||||
|
status: PentestStatus.COMPLETED
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockProject: Project = {
|
||||||
|
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
||||||
|
client: 'E Corp',
|
||||||
|
title: 'Some Mock API (v1.0) Scanning',
|
||||||
|
createdAt: new Date('2019-01-10T09:00:00'),
|
||||||
|
tester: 'Novatester',
|
||||||
|
summary: '',
|
||||||
|
projectPentests: mockedPentests,
|
||||||
|
testingProgress: 0,
|
||||||
|
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockedExportPentestDialogData = {
|
||||||
|
form: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
headerLabelKey: 'finding.create.header',
|
||||||
|
buttonKey: 'global.action.save',
|
||||||
|
accentColor: 'info',
|
||||||
|
additionalData: mockProject
|
||||||
|
},
|
||||||
|
]
|
||||||
|
};
|
|
@ -0,0 +1,136 @@
|
||||||
|
import {Component, Inject, OnInit} from '@angular/core';
|
||||||
|
import {FormControl} from '@angular/forms';
|
||||||
|
import {GenericDialogData} from '@shared/models/generic-dialog-data';
|
||||||
|
import {NB_DIALOG_CONFIG, NbDialogRef} from '@nebular/theme';
|
||||||
|
import {ReportingService} from '@shared/services/reporting/reporting.service';
|
||||||
|
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||||
|
import {Project} from '@shared/models/project.model';
|
||||||
|
import * as FA from '@fortawesome/free-solid-svg-icons';
|
||||||
|
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 {downloadFile} from '@shared/functions/download-file.function';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-export-report-dialog',
|
||||||
|
templateUrl: './export-report-dialog.component.html',
|
||||||
|
styleUrls: ['./export-report-dialog.component.scss']
|
||||||
|
})
|
||||||
|
@UntilDestroy()
|
||||||
|
export class ExportReportDialogComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(NB_DIALOG_CONFIG) private data: GenericDialogData,
|
||||||
|
private projectService: ProjectService,
|
||||||
|
private reportingService: ReportingService,
|
||||||
|
private readonly notificationService: NotificationService,
|
||||||
|
protected dialogRef: NbDialogRef<ExportReportDialogComponent>
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML
|
||||||
|
readonly fa = FA;
|
||||||
|
// form control elements
|
||||||
|
exportReportFormatControl = new FormControl(ExportFormatOptions.PDF);
|
||||||
|
exportReportLanguageControl = new FormControl(ExportLanguageOptions.ENGLISH);
|
||||||
|
// exports
|
||||||
|
exportFormats = ExportFormatOptions;
|
||||||
|
exportLanguages = ExportLanguageOptions;
|
||||||
|
|
||||||
|
dialogData: GenericDialogData;
|
||||||
|
|
||||||
|
selectedProject$: BehaviorSubject<Project> = new BehaviorSubject<Project>(null);
|
||||||
|
completedProjectPentests$: BehaviorSubject<any> = new BehaviorSubject<any>({completedObjectivesNumber: 0});
|
||||||
|
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.dialogData = this.data;
|
||||||
|
this.loadEvaluatedProject();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadEvaluatedProject(): void {
|
||||||
|
// Get project id from dialog data
|
||||||
|
const projectId = this.dialogData.options[0].additionalData.id;
|
||||||
|
// Request project information by id
|
||||||
|
this.projectService.getEvaluatedProjectById(projectId)
|
||||||
|
.pipe(
|
||||||
|
tap(() => this.loading$.next(true)),
|
||||||
|
untilDestroyed(this)
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next: (project: Project) => {
|
||||||
|
this.selectedProject$.next(project);
|
||||||
|
const completedPentestObjectives = project.projectPentests.filter(pentest => pentest.status === PentestStatus.COMPLETED);
|
||||||
|
this.completedProjectPentests$.next({completedObjectivesNumber: completedPentestObjectives.length});
|
||||||
|
this.loading$.next(false);
|
||||||
|
},
|
||||||
|
error: err => {
|
||||||
|
console.log(err);
|
||||||
|
this.notificationService.showPopup('project.popup.not.found', PopupType.FAILURE);
|
||||||
|
this.loading$.next(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickExport(reportFormat: string, reportLanguage: string): void {
|
||||||
|
// 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({
|
||||||
|
next: (response) => {
|
||||||
|
downloadFile(response, 'application/pdf');
|
||||||
|
this.loading$.next(false);
|
||||||
|
this.notificationService.showPopup('report.popup.generation.success', PopupType.SUCCESS);
|
||||||
|
},
|
||||||
|
error: error => {
|
||||||
|
console.error(error);
|
||||||
|
this.loading$.next(false);
|
||||||
|
this.notificationService.showPopup('report.popup.generation.failed', PopupType.FAILURE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ExportFormatOptions.CSV: {
|
||||||
|
this.loading$.next(false);
|
||||||
|
this.notificationService.showPopup('report.popup.generation.failed', PopupType.FAILURE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ExportFormatOptions.HTML: {
|
||||||
|
this.loading$.next(false);
|
||||||
|
this.notificationService.showPopup('report.popup.generation.failed', PopupType.FAILURE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
this.loading$.next(false);
|
||||||
|
this.notificationService.showPopup('report.popup.generation.failed', PopupType.FAILURE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClickClose(): void {
|
||||||
|
this.dialogRef.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML only
|
||||||
|
isLoading(): Observable<boolean> {
|
||||||
|
return this.loading$.asObservable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ExportFormatOptions {
|
||||||
|
PDF = 'PDF',
|
||||||
|
CSV = 'CSV',
|
||||||
|
HTML = 'HTML'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ExportLanguageOptions {
|
||||||
|
ENGLISH = 'en-US',
|
||||||
|
GERMAN = 'de-DE'
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import {CommonAppModule} from '../../../app/common-app.module';
|
||||||
|
import {NbButtonModule, NbCardModule, NbFormFieldModule, NbInputModule, NbRadioModule} from '@nebular/theme';
|
||||||
|
import {FlexLayoutModule} from '@angular/flex-layout';
|
||||||
|
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
||||||
|
import {TranslateModule} from '@ngx-translate/core';
|
||||||
|
import {ReactiveFormsModule} from '@angular/forms';
|
||||||
|
import {ExportReportDialogComponent} from '@shared/modules/export-report-dialog/export-report-dialog.component';
|
||||||
|
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';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
ExportReportDialogComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
CommonAppModule,
|
||||||
|
NbCardModule,
|
||||||
|
NbButtonModule,
|
||||||
|
NbFormFieldModule,
|
||||||
|
NbInputModule,
|
||||||
|
FlexLayoutModule,
|
||||||
|
FontAwesomeModule,
|
||||||
|
TranslateModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
NbRadioModule,
|
||||||
|
ObjectiveChartModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
ExportReportDialogService,
|
||||||
|
ReportingService
|
||||||
|
],
|
||||||
|
entryComponents: [
|
||||||
|
ExportReportDialogComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class ExportReportDialogModule { }
|
|
@ -0,0 +1,17 @@
|
||||||
|
import {ComponentType} from '@angular/cdk/overlay';
|
||||||
|
import {NbDialogConfig} from '@nebular/theme';
|
||||||
|
import {Observable, of} from 'rxjs';
|
||||||
|
import {ExportReportDialogService} from '@shared/modules/export-report-dialog/service/export-report-dialog.service';
|
||||||
|
import {Project} from '@shared/models/project.model';
|
||||||
|
|
||||||
|
export class ExportReportDialogServiceMock implements Required<ExportReportDialogService> {
|
||||||
|
|
||||||
|
dialog: any;
|
||||||
|
|
||||||
|
openExportReportDialog(
|
||||||
|
componentOrTemplateRef: ComponentType<any>,
|
||||||
|
project: Project | undefined,
|
||||||
|
config: Partial<NbDialogConfig<Partial<any> | string>> | undefined): Observable<any> {
|
||||||
|
return of(undefined);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ExportReportDialogService } from './export-report-dialog.service';
|
||||||
|
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||||
|
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||||
|
import {NbDialogModule, NbDialogRef} from '@nebular/theme';
|
||||||
|
import {ExportReportDialogServiceMock} from '@shared/modules/export-report-dialog/service/export-report-dialog.service.mock';
|
||||||
|
|
||||||
|
describe('ExportReportDialogService', () => {
|
||||||
|
let service: ExportReportDialogService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
HttpClientTestingModule,
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
NbDialogModule.forChild()
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{provide: ExportReportDialogService, useClass: ExportReportDialogServiceMock},
|
||||||
|
{provide: NbDialogRef, useValue: {}},
|
||||||
|
]
|
||||||
|
});
|
||||||
|
service = TestBed.inject(ExportReportDialogService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import {NbDialogConfig, NbDialogService} from '@nebular/theme';
|
||||||
|
import {GenericDialogData} from '@shared/models/generic-dialog-data';
|
||||||
|
import {ComponentType} from '@angular/cdk/overlay';
|
||||||
|
import {Observable} from 'rxjs';
|
||||||
|
import {ExportReportDialogComponent} from '@shared/modules/export-report-dialog/export-report-dialog.component';
|
||||||
|
import {Project} from '@shared/models/project.model';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ExportReportDialogService {
|
||||||
|
constructor(private readonly dialog: NbDialogService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly MIN_LENGTH: number = 4;
|
||||||
|
|
||||||
|
static addDataToDialogConfig(
|
||||||
|
dialogOptions?: Partial<NbDialogConfig<Partial<any> | string>>,
|
||||||
|
reportData?: GenericDialogData
|
||||||
|
): Partial<NbDialogConfig<Partial<any> | string>> {
|
||||||
|
return {
|
||||||
|
context: {data: reportData},
|
||||||
|
closeOnEsc: dialogOptions?.closeOnEsc || false,
|
||||||
|
hasScroll: dialogOptions?.hasScroll || false,
|
||||||
|
autoFocus: dialogOptions?.autoFocus || false,
|
||||||
|
closeOnBackdropClick: dialogOptions?.closeOnBackdropClick || false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public openExportReportDialog(componentOrTemplateRef: ComponentType<any>,
|
||||||
|
project?: Project,
|
||||||
|
config?: Partial<NbDialogConfig<Partial<any> | string>>): Observable<any> {
|
||||||
|
let dialogOptions: Partial<NbDialogConfig<Partial<any> | string>>;
|
||||||
|
let dialogData: GenericDialogData;
|
||||||
|
// Setup ExportReportDialogBody
|
||||||
|
dialogData = {
|
||||||
|
form: {
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
headerLabelKey: 'report.dialog.header',
|
||||||
|
buttonKey: 'report.generate',
|
||||||
|
accentColor: 'info',
|
||||||
|
additionalData: project
|
||||||
|
},
|
||||||
|
]
|
||||||
|
};
|
||||||
|
// Merge dialog config with finding data
|
||||||
|
dialogOptions = ExportReportDialogService.addDataToDialogConfig(config, dialogData);
|
||||||
|
return this.dialog.open(ExportReportDialogComponent, dialogOptions).onClose;
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,8 +19,8 @@ import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
|
||||||
import {HttpLoaderFactory} from '../../../app/common-app.module';
|
import {HttpLoaderFactory} from '../../../app/common-app.module';
|
||||||
import {HttpClient, HttpClientModule} from '@angular/common/http';
|
import {HttpClient, HttpClientModule} from '@angular/common/http';
|
||||||
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||||
import {NotificationService} from '@shared/services/notification.service';
|
import {NotificationService} from '@shared/services/toaster-service/notification.service';
|
||||||
import {NotificationServiceMock} from '@shared/services/notification.service.mock';
|
import {NotificationServiceMock} from '@shared/services/toaster-service/notification.service.mock';
|
||||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||||
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
|
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
|
||||||
import {Severity} from '@shared/models/severity.enum';
|
import {Severity} from '@shared/models/severity.enum';
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
<div class="objective-chart" fxLayout="column" fxLayoutGap="1rem" fxLayoutAlign="start start">
|
||||||
|
<canvas id="PentestObjectiveChart" class="chart" >{{ chart }}</canvas>
|
||||||
|
</div>
|
|
@ -0,0 +1,9 @@
|
||||||
|
.objective-chart {
|
||||||
|
width: 40rem !important;
|
||||||
|
height: 16rem;
|
||||||
|
|
||||||
|
.chart {
|
||||||
|
// height: 100%;
|
||||||
|
// width: 100%;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||||
|
|
||||||
|
import {ObjectiveChartComponent} from './objective-chart.component';
|
||||||
|
import {CommonModule} from '@angular/common';
|
||||||
|
import {NbButtonModule, NbCardModule, NbFormFieldModule, NbInputModule, NbLayoutModule, NbTagModule} from '@nebular/theme';
|
||||||
|
import {FlexLayoutModule} from '@angular/flex-layout';
|
||||||
|
import {ReactiveFormsModule} from '@angular/forms';
|
||||||
|
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||||
|
import {ThemeModule} from '@assets/@theme/theme.module';
|
||||||
|
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
|
||||||
|
import {HttpLoaderFactory} from '../../../app/common-app.module';
|
||||||
|
import {HttpClient, HttpClientModule} from '@angular/common/http';
|
||||||
|
import {NgxsModule} from '@ngxs/store';
|
||||||
|
import {ProjectState} from '@shared/stores/project-state/project-state';
|
||||||
|
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||||
|
|
||||||
|
describe('ObjectiveChartComponent', () => {
|
||||||
|
let component: ObjectiveChartComponent;
|
||||||
|
let fixture: ComponentFixture<ObjectiveChartComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [
|
||||||
|
ObjectiveChartComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
NbLayoutModule,
|
||||||
|
NbCardModule,
|
||||||
|
FlexLayoutModule,
|
||||||
|
NbInputModule,
|
||||||
|
NbTagModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
ThemeModule.forRoot(),
|
||||||
|
TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useFactory: HttpLoaderFactory,
|
||||||
|
deps: [HttpClient]
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
NgxsModule.forRoot([ProjectState]),
|
||||||
|
HttpClientModule,
|
||||||
|
HttpClientTestingModule
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ObjectiveChartComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,128 @@
|
||||||
|
import {Component, Input, OnInit} from '@angular/core';
|
||||||
|
import * as FA from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import {GlobalTitlesVariables} from '@shared/config/global-variables';
|
||||||
|
import {ProjectPentests} from '@shared/models/project.model';
|
||||||
|
import Chart from 'chart.js/auto';
|
||||||
|
import {PentestStatus} from '@shared/models/pentest-status.model';
|
||||||
|
import {TranslateService} from '@ngx-translate/core';
|
||||||
|
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-objective-chart',
|
||||||
|
templateUrl: './objective-chart.component.html',
|
||||||
|
styleUrls: ['./objective-chart.component.scss']
|
||||||
|
})
|
||||||
|
@UntilDestroy()
|
||||||
|
export class ObjectiveChartComponent implements OnInit {
|
||||||
|
|
||||||
|
readonly fa = FA;
|
||||||
|
readonly TOTAL_OWASP_OBJECTIVES: number = GlobalTitlesVariables.TOTAL_OWASP_OBJECTIVES;
|
||||||
|
|
||||||
|
@Input() projectPentestData: ProjectPentests[] = [];
|
||||||
|
|
||||||
|
chart: any;
|
||||||
|
|
||||||
|
data: any;
|
||||||
|
options: any;
|
||||||
|
|
||||||
|
pentestStatusColors = {
|
||||||
|
disabledDefault: '#000',
|
||||||
|
notStartedDefault: '#8f9bb3',
|
||||||
|
info: '#34a4fe',
|
||||||
|
warning: '#ffab00',
|
||||||
|
success: '#01d68f'
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(private translateService: TranslateService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML only
|
||||||
|
status = PentestStatus;
|
||||||
|
readonly pentestStatusLabels: Array<string> = [
|
||||||
|
'pentest.statusText.disabled',
|
||||||
|
'pentest.statusText.not_started',
|
||||||
|
'pentest.statusText.open',
|
||||||
|
'pentest.statusText.in_progress',
|
||||||
|
'pentest.statusText.completed'
|
||||||
|
];
|
||||||
|
|
||||||
|
translatedPentestStatusLabels: Array<string> = [];
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.translateLabels();
|
||||||
|
this.createChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
createChart(): void {
|
||||||
|
// Sort objectives by status
|
||||||
|
const disabledPentests: ProjectPentests[]
|
||||||
|
= this.projectPentestData.filter(projectPentest => projectPentest.status === PentestStatus.DISABLED);
|
||||||
|
const openPentests: ProjectPentests[]
|
||||||
|
= this.projectPentestData.filter(projectPentest => projectPentest.status === PentestStatus.OPEN);
|
||||||
|
const inProgressPentests: ProjectPentests[]
|
||||||
|
= this.projectPentestData.filter(projectPentest => projectPentest.status === PentestStatus.IN_PROGRESS);
|
||||||
|
const completedPentests: ProjectPentests[]
|
||||||
|
= this.projectPentestData.filter(projectPentest => projectPentest.status === PentestStatus.COMPLETED);
|
||||||
|
// Find not started pentest by removing other pentests from total objective count
|
||||||
|
const notStartedPentests: number
|
||||||
|
= this.TOTAL_OWASP_OBJECTIVES - this.projectPentestData.length;
|
||||||
|
|
||||||
|
// Setup data for chart
|
||||||
|
const pentestData = [
|
||||||
|
disabledPentests.length,
|
||||||
|
notStartedPentests,
|
||||||
|
openPentests.length,
|
||||||
|
inProgressPentests.length,
|
||||||
|
completedPentests.length
|
||||||
|
];
|
||||||
|
// increase-legend-spacing
|
||||||
|
const increseLegenStylePlugin = {
|
||||||
|
id: 'increase-legend-spacing',
|
||||||
|
beforeInit(chart: any): void {
|
||||||
|
// Get reference to the original fit function
|
||||||
|
const originalFit = chart.legend.fit;
|
||||||
|
// Override the fit function
|
||||||
|
chart.legend.fit = function fit(): void {
|
||||||
|
// Call original function and bind scope in order to use `this` correctly inside it
|
||||||
|
originalFit.bind(chart.legend)();
|
||||||
|
// Change the height as suggested in another answers
|
||||||
|
this.height += 25;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Build Chart
|
||||||
|
this.chart = new Chart('PentestObjectiveChart', {
|
||||||
|
type: 'pie', // this denotes tha type of chart
|
||||||
|
|
||||||
|
data: {// values on X-Axis
|
||||||
|
labels: this.translatedPentestStatusLabels.length ? this.translatedPentestStatusLabels : this.pentestStatusLabels,
|
||||||
|
datasets: [{
|
||||||
|
label: ' ',
|
||||||
|
data: pentestData,
|
||||||
|
backgroundColor: [
|
||||||
|
this.pentestStatusColors.disabledDefault,
|
||||||
|
this.pentestStatusColors.notStartedDefault,
|
||||||
|
this.pentestStatusColors.info,
|
||||||
|
this.pentestStatusColors.warning,
|
||||||
|
this.pentestStatusColors.success,
|
||||||
|
],
|
||||||
|
hoverOffset: 1
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
aspectRatio: 2.5,
|
||||||
|
},
|
||||||
|
plugins: [increseLegenStylePlugin]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private translateLabels(): void {
|
||||||
|
this.pentestStatusLabels.forEach((label, index) => {
|
||||||
|
this.translateService.get(label)
|
||||||
|
.pipe(untilDestroyed(this))
|
||||||
|
.subscribe((translated: string): void => {
|
||||||
|
this.translatedPentestStatusLabels[index] = translated;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import {ObjectiveChartComponent} from '@shared/modules/objective-chart/objective-chart.component';
|
||||||
|
import {FlexLayoutModule} from '@angular/flex-layout';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
ObjectiveChartComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FlexLayoutModule
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
ObjectiveChartComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class ObjectiveChartModule { }
|
|
@ -2,7 +2,7 @@
|
||||||
@import '../../../assets/@theme/styles/themes';
|
@import '../../../assets/@theme/styles/themes';
|
||||||
|
|
||||||
.project-dialog {
|
.project-dialog {
|
||||||
width: 40rem !important;
|
width: 34rem !important;
|
||||||
height: 42.5rem;
|
height: 42.5rem;
|
||||||
|
|
||||||
.project-dialog-header {
|
.project-dialog-header {
|
||||||
|
|
|
@ -17,8 +17,8 @@ import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
|
||||||
import {HttpLoaderFactory} from '../../../app/common-app.module';
|
import {HttpLoaderFactory} from '../../../app/common-app.module';
|
||||||
import {HttpClient, HttpClientModule} from '@angular/common/http';
|
import {HttpClient, HttpClientModule} from '@angular/common/http';
|
||||||
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||||
import {NotificationService} from '@shared/services/notification.service';
|
import {NotificationService} from '@shared/services/toaster-service/notification.service';
|
||||||
import {NotificationServiceMock} from '@shared/services/notification.service.mock';
|
import {NotificationServiceMock} from '@shared/services/toaster-service/notification.service.mock';
|
||||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||||
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
|
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
|
||||||
import {ReactiveFormsModule, Validators} from '@angular/forms';
|
import {ReactiveFormsModule, Validators} from '@angular/forms';
|
||||||
|
|
|
@ -3,7 +3,7 @@ import {User} from '@shared/models/user.model';
|
||||||
import {NumberAndDateFormatSystem} from '@shared/models/number-and-date-time-format.model';
|
import {NumberAndDateFormatSystem} from '@shared/models/number-and-date-time-format.model';
|
||||||
import {SESSION_STATE_NAME, SessionState, SessionStateModel} from '@shared/stores/session-state/session-state';
|
import {SESSION_STATE_NAME, SessionState, SessionStateModel} from '@shared/stores/session-state/session-state';
|
||||||
import {NgxsModule, Store} from '@ngxs/store';
|
import {NgxsModule, Store} from '@ngxs/store';
|
||||||
import {UserService} from '@shared/services/user.service';
|
import {UserService} from '@shared/services/user-service/user.service';
|
||||||
import {inject, TestBed} from '@angular/core/testing';
|
import {inject, TestBed} from '@angular/core/testing';
|
||||||
import {HttpClient} from '@angular/common/http';
|
import {HttpClient} from '@angular/common/http';
|
||||||
import {HttpLoaderFactory} from '../../app/common-app.module';
|
import {HttpLoaderFactory} from '../../app/common-app.module';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
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} from '@angular/common/http';
|
||||||
import {Store} from '@ngxs/store';
|
import {Store} from '@ngxs/store';
|
||||||
import {Observable} from 'rxjs';
|
import {Observable} from 'rxjs';
|
|
@ -1,5 +1,5 @@
|
||||||
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} from '@angular/common/http';
|
||||||
import {Store} from '@ngxs/store';
|
import {Store} from '@ngxs/store';
|
||||||
import {Observable} from 'rxjs';
|
import {Observable} from 'rxjs';
|
|
@ -1,5 +1,5 @@
|
||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {environment} from '../../environments/environment';
|
import {environment} from '../../../environments/environment';
|
||||||
import {HttpClient, HttpParams} from '@angular/common/http';
|
import {HttpClient, HttpParams} from '@angular/common/http';
|
||||||
import {Observable, of} from 'rxjs';
|
import {Observable, of} from 'rxjs';
|
||||||
import {Category} from '@shared/models/category.model';
|
import {Category} from '@shared/models/category.model';
|
|
@ -1,4 +1,4 @@
|
||||||
import {ProjectService} from '@shared/services/project.service';
|
import {ProjectService} from '@shared/services/api/project.service';
|
||||||
import {HttpClient} from '@angular/common/http';
|
import {HttpClient} from '@angular/common/http';
|
||||||
import {Observable, of} from 'rxjs';
|
import {Observable, of} from 'rxjs';
|
||||||
import {Project, ProjectDialogBody} from '@shared/models/project.model';
|
import {Project, ProjectDialogBody} from '@shared/models/project.model';
|
||||||
|
@ -12,6 +12,14 @@ export class ProjectServiceMock implements Required<ProjectService> {
|
||||||
return of([]);
|
return of([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCompletedProjectById(projectId: string): Observable<Project> {
|
||||||
|
return of();
|
||||||
|
}
|
||||||
|
|
||||||
|
getEvaluatedProjectById(projectId: string): Observable<Project> {
|
||||||
|
return of();
|
||||||
|
}
|
||||||
|
|
||||||
saveProject(saveProject: ProjectDialogBody): Observable<Project> {
|
saveProject(saveProject: ProjectDialogBody): Observable<Project> {
|
||||||
return of();
|
return of();
|
||||||
}
|
}
|
|
@ -5,7 +5,7 @@ import {HttpClientTestingModule, HttpTestingController} from '@angular/common/ht
|
||||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||||
import {KeycloakService} from 'keycloak-angular';
|
import {KeycloakService} from 'keycloak-angular';
|
||||||
import {Project, ProjectDialogBody} from '@shared/models/project.model';
|
import {Project, ProjectDialogBody} from '@shared/models/project.model';
|
||||||
import {environment} from '../../environments/environment';
|
import {environment} from '../../../environments/environment';
|
||||||
import {throwError} from 'rxjs';
|
import {throwError} from 'rxjs';
|
||||||
|
|
||||||
describe('ProjectService', () => {
|
describe('ProjectService', () => {
|
||||||
|
@ -83,6 +83,7 @@ describe('ProjectService', () => {
|
||||||
client: 'E Corp',
|
client: 'E Corp',
|
||||||
title: 'Some Mock API (v1.0) Scanning',
|
title: 'Some Mock API (v1.0) Scanning',
|
||||||
tester: 'Novatester',
|
tester: 'Novatester',
|
||||||
|
summary: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockProject: Project = {
|
const mockProject: Project = {
|
|
@ -1,7 +1,7 @@
|
||||||
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} from '@angular/common/http';
|
||||||
import {Project, ProjectDialogBody} from '../models/project.model';
|
import {Project, ProjectDialogBody} from '../../models/project.model';
|
||||||
import {Observable} from 'rxjs';
|
import {Observable} from 'rxjs';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
|
@ -21,6 +21,20 @@ export class ProjectService {
|
||||||
return this.http.get<Project[]>(`${this.apiBaseURL}`);
|
return this.http.get<Project[]>(`${this.apiBaseURL}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get completed project by id
|
||||||
|
*/
|
||||||
|
public getCompletedProjectById(projectId: string): Observable<Project> {
|
||||||
|
return this.http.get<Project>(`${this.apiBaseURL}/${projectId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get evaluated project by id
|
||||||
|
*/
|
||||||
|
public getEvaluatedProjectById(projectId: string): Observable<Project> {
|
||||||
|
return this.http.get<Project>(`${this.apiBaseURL}/evaluation/${projectId}`);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save Project
|
* Save Project
|
||||||
* @param project the information of the project
|
* @param project the information of the project
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ReportingService } from './reporting.service';
|
||||||
|
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
|
||||||
|
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||||
|
import {environment} from '../../../environments/environment';
|
||||||
|
|
||||||
|
describe('ReportingService', () => {
|
||||||
|
let service: ReportingService;
|
||||||
|
let httpMock: HttpTestingController;
|
||||||
|
|
||||||
|
const reportBaseURL = `${environment.reportEndpoint}/reports`;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
HttpClientTestingModule,
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
]
|
||||||
|
});
|
||||||
|
service = TestBed.inject(ReportingService);
|
||||||
|
httpMock = TestBed.inject(HttpTestingController);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,23 @@
|
||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
import {environment} from '../../../environments/environment';
|
||||||
|
import {HttpClient} from '@angular/common/http';
|
||||||
|
import {Observable} from 'rxjs';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ReportingService {
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private reportBaseURL = `${environment.reportEndpoint}/reports`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get PDF Report by project id
|
||||||
|
*/
|
||||||
|
public getReportPDFforProjectById(projectId: string): Observable<ArrayBuffer> {
|
||||||
|
// @ts-ignore
|
||||||
|
return this.http.get<ArrayBuffer>(`${this.reportBaseURL}/${projectId}/pdf`, {responseType: 'arraybuffer'})
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,10 +5,10 @@ import {TranslateLoader, TranslateModule, TranslateService} from '@ngx-translate
|
||||||
import {Observable, of} from 'rxjs';
|
import {Observable, of} from 'rxjs';
|
||||||
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||||
import {HttpLoaderFactory} from '../../app/common-app.module';
|
import {HttpLoaderFactory} from '../../../app/common-app.module';
|
||||||
import {HttpClient} from '@angular/common/http';
|
import {HttpClient} from '@angular/common/http';
|
||||||
import {NgxsModule} from '@ngxs/store';
|
import {NgxsModule} from '@ngxs/store';
|
||||||
import {SessionState} from '../stores/session-state/session-state';
|
import {SessionState} from '../../stores/session-state/session-state';
|
||||||
import {KeycloakService} from 'keycloak-angular';
|
import {KeycloakService} from 'keycloak-angular';
|
||||||
import {NbToastrModule, NbToastrService} from '@nebular/theme';
|
import {NbToastrModule, NbToastrService} from '@nebular/theme';
|
||||||
|
|
|
@ -4,10 +4,10 @@ import { UserService } from './user.service';
|
||||||
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||||
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
|
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
|
||||||
import {HttpLoaderFactory} from '../../app/common-app.module';
|
import {HttpLoaderFactory} from '../../../app/common-app.module';
|
||||||
import {HttpClient} from '@angular/common/http';
|
import {HttpClient} from '@angular/common/http';
|
||||||
import {NgxsModule} from '@ngxs/store';
|
import {NgxsModule} from '@ngxs/store';
|
||||||
import {SessionState} from '../stores/session-state/session-state';
|
import {SessionState} from '../../stores/session-state/session-state';
|
||||||
import {KeycloakService} from 'keycloak-angular';
|
import {KeycloakService} from 'keycloak-angular';
|
||||||
|
|
||||||
describe('UserService', () => {
|
describe('UserService', () => {
|
|
@ -1,6 +1,6 @@
|
||||||
import {Injectable} from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {HttpClient, HttpHeaders} from '@angular/common/http';
|
import {HttpClient, HttpHeaders} from '@angular/common/http';
|
||||||
import {User} from '../models/user.model';
|
import {User} from '../../models/user.model';
|
||||||
import {from, Observable, Subscriber} from 'rxjs';
|
import {from, Observable, Subscriber} from 'rxjs';
|
||||||
import {Store} from '@ngxs/store';
|
import {Store} from '@ngxs/store';
|
||||||
import {KeycloakService} from 'keycloak-angular';
|
import {KeycloakService} from 'keycloak-angular';
|
|
@ -5,7 +5,7 @@ import {TranslateService} from '@ngx-translate/core';
|
||||||
import {FetchUser, InitSession, ResetSession, UpdateIsAuthenticated, UpdateUser, UpdateUserSettings} from './session-state.actions';
|
import {FetchUser, InitSession, ResetSession, UpdateIsAuthenticated, UpdateUser, UpdateUserSettings} from './session-state.actions';
|
||||||
import deepEqual from 'deep-equal';
|
import deepEqual from 'deep-equal';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import {UserService} from '../../services/user.service';
|
import {UserService} from '../../services/user-service/user.service';
|
||||||
|
|
||||||
export interface SessionStateModel {
|
export interface SessionStateModel {
|
||||||
userAccount: User;
|
userAccount: User;
|
||||||
|
|
|
@ -54,14 +54,14 @@
|
||||||
"response": []
|
"response": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "getProjectById",
|
"name": "getCompletedProjectById",
|
||||||
"request": {
|
"request": {
|
||||||
"auth": {
|
"auth": {
|
||||||
"type": "bearer",
|
"type": "bearer",
|
||||||
"bearer": [
|
"bearer": [
|
||||||
{
|
{
|
||||||
"key": "token",
|
"key": "token",
|
||||||
"value": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItdG1lbEV0ZHhGTnRSMW9aNXlRdE5jaFFpX0RVN2VNeV9YcU44aXY0S3hzIn0.eyJleHAiOjE2NzY5NzMxMTAsImlhdCI6MTY3Njk3MjgxMCwianRpIjoiNDFkMDAwNzEtNjAyYy00NmEzLThjYjctMTJlZTExYWYyZDBhIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2M0cG9fcmVhbG1fbG9jYWwiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMTBlMDZkN2EtOGRkMC00ZWNkLTg5NjMtMDU2YjQ1MDc5YzRmIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYzRwb19sb2NhbCIsInNlc3Npb25fc3RhdGUiOiI5MTA5ZWU0Ni03OGEzLTRmMDUtODdhYi03NzIxNGJmNzNlZWMiLCJhbGxvd2VkLW9yaWdpbnMiOlsiKiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiYzRwb191c2VyIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImM0cG9fbG9jYWwiOnsicm9sZXMiOlsidXNlciJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJwcm9maWxlIGVtYWlsIiwic2lkIjoiOTEwOWVlNDYtNzhhMy00ZjA1LTg3YWItNzcyMTRiZjczZWVjIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoidGVzdCB1c2VyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoidHR0IiwiZ2l2ZW5fbmFtZSI6InRlc3QiLCJmYW1pbHlfbmFtZSI6InVzZXIifQ.hZhUBi4cGQdn3lZ1Xm1Kz2WpboiBBJCFrtODD_c4N0ZiymB0MWVc1jXzU1fQ25mZ_I9VJXqg97x_gnCM7mKJrncFxs6cj75zIeH3so1BhlcDf7q2pjIkCH1yCerPWSLtrK2pWyxTr1GyO1Cp_wqQms_7_rmpzajLzmqBGKF8vd4yAk8kHmBoGBJhRU_gVCsDnIe74in3a032---IgCJ2XA0E5yxP9oBe6_9xPuCsk82YDihbfK1ZEO-9YZt0g1Iv3y30-hG10eflftWJEMSi8Bso4H_2WSJLqy4YRuGQR0EKDiomM0deVCK9IkuaoIsdIZ8kd65YuxSnj-_ue17QTA",
|
"value": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItdG1lbEV0ZHhGTnRSMW9aNXlRdE5jaFFpX0RVN2VNeV9YcU44aXY0S3hzIn0.eyJleHAiOjE2NzcwNjI4MTAsImlhdCI6MTY3NzA2MjUxMCwianRpIjoiM2NlNWIyNjEtZGZkNy00NzIwLThlODMtOTczYjg0NTIxZWU4IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2M0cG9fcmVhbG1fbG9jYWwiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMTBlMDZkN2EtOGRkMC00ZWNkLTg5NjMtMDU2YjQ1MDc5YzRmIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYzRwb19sb2NhbCIsInNlc3Npb25fc3RhdGUiOiJlYWU4MTBkOS04OWU4LTQxNmEtOGExNS1kYzI5MWY5YmY0NGQiLCJhbGxvd2VkLW9yaWdpbnMiOlsiKiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiYzRwb191c2VyIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImM0cG9fbG9jYWwiOnsicm9sZXMiOlsidXNlciJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJwcm9maWxlIGVtYWlsIiwic2lkIjoiZWFlODEwZDktODllOC00MTZhLThhMTUtZGMyOTFmOWJmNDRkIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoidGVzdCB1c2VyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoidHR0IiwiZ2l2ZW5fbmFtZSI6InRlc3QiLCJmYW1pbHlfbmFtZSI6InVzZXIifQ.u2NP-PV9uNUtE8IyqOEjwFs_tEF55yU4F0KbdIb8P_1yl_ExiIGFBzEm_n5uUA-3AhXid56RAlrysEGjsTVKWm9c37MGc4oyzZzHoXoBFOquA-mb7K0bazmyFCSLCYszOM6oLcSrKelStu_aoga0MAqL1yrl8qYpZ902O32hG_5BJwEJo0uN-60dcDXwQMed8GqkraFoMCPBQF5vGZrGeNAMGwHXN1__E9JJU8ehPMKV_vnv11HrzK6OJfx4esWkF_5aNi-MASF2vaREbGVM0d0PGfgWDWphtJRK8acc4oHfqzWBW8M2qudZ7FDgMDJgk1mgFfUcQ_TEl-gdO_5PYQ",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -87,6 +87,41 @@
|
||||||
},
|
},
|
||||||
"response": []
|
"response": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "getEvaluatedProjectById",
|
||||||
|
"request": {
|
||||||
|
"auth": {
|
||||||
|
"type": "bearer",
|
||||||
|
"bearer": [
|
||||||
|
{
|
||||||
|
"key": "token",
|
||||||
|
"value": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItdG1lbEV0ZHhGTnRSMW9aNXlRdE5jaFFpX0RVN2VNeV9YcU44aXY0S3hzIn0.eyJleHAiOjE2NzcwNjI4MTAsImlhdCI6MTY3NzA2MjUxMCwianRpIjoiM2NlNWIyNjEtZGZkNy00NzIwLThlODMtOTczYjg0NTIxZWU4IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2M0cG9fcmVhbG1fbG9jYWwiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMTBlMDZkN2EtOGRkMC00ZWNkLTg5NjMtMDU2YjQ1MDc5YzRmIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYzRwb19sb2NhbCIsInNlc3Npb25fc3RhdGUiOiJlYWU4MTBkOS04OWU4LTQxNmEtOGExNS1kYzI5MWY5YmY0NGQiLCJhbGxvd2VkLW9yaWdpbnMiOlsiKiJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiYzRwb191c2VyIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImM0cG9fbG9jYWwiOnsicm9sZXMiOlsidXNlciJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJwcm9maWxlIGVtYWlsIiwic2lkIjoiZWFlODEwZDktODllOC00MTZhLThhMTUtZGMyOTFmOWJmNDRkIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoidGVzdCB1c2VyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoidHR0IiwiZ2l2ZW5fbmFtZSI6InRlc3QiLCJmYW1pbHlfbmFtZSI6InVzZXIifQ.u2NP-PV9uNUtE8IyqOEjwFs_tEF55yU4F0KbdIb8P_1yl_ExiIGFBzEm_n5uUA-3AhXid56RAlrysEGjsTVKWm9c37MGc4oyzZzHoXoBFOquA-mb7K0bazmyFCSLCYszOM6oLcSrKelStu_aoga0MAqL1yrl8qYpZ902O32hG_5BJwEJo0uN-60dcDXwQMed8GqkraFoMCPBQF5vGZrGeNAMGwHXN1__E9JJU8ehPMKV_vnv11HrzK6OJfx4esWkF_5aNi-MASF2vaREbGVM0d0PGfgWDWphtJRK8acc4oHfqzWBW8M2qudZ7FDgMDJgk1mgFfUcQ_TEl-gdO_5PYQ",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "undefined",
|
||||||
|
"type": "any"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "http://localhost:8443/projects/evaluation/5a4f126c-9471-43b8-80b9-6eb02b7c35d0",
|
||||||
|
"protocol": "http",
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
],
|
||||||
|
"port": "8443",
|
||||||
|
"path": [
|
||||||
|
"projects",
|
||||||
|
"evaluation",
|
||||||
|
"5a4f126c-9471-43b8-80b9-6eb02b7c35d0"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "saveProject",
|
"name": "saveProject",
|
||||||
"request": {
|
"request": {
|
||||||
|
|
|
@ -66,6 +66,20 @@ fun Project.toProjectCompletedPentestResponseBody(): ResponseBody {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressFBWarnings(BC_BAD_CAST_TO_ABSTRACT_COLLECTION, MESSAGE_BAD_CAST_TO_ABSTRACT_COLLECTION)
|
||||||
|
fun Project.toProjectEvaluatedPentestResponseBody(): ResponseBody {
|
||||||
|
return mapOf(
|
||||||
|
"id" to id,
|
||||||
|
"client" to client,
|
||||||
|
"title" to title,
|
||||||
|
"createdAt" to createdAt,
|
||||||
|
"tester" to tester,
|
||||||
|
"summary" to summary,
|
||||||
|
"projectPentests" to projectPentests,
|
||||||
|
"createdBy" to createdBy
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun Project.toProjectDeleteResponseBody(): ResponseBody {
|
fun Project.toProjectDeleteResponseBody(): ResponseBody {
|
||||||
return mapOf(
|
return mapOf(
|
||||||
"id" to id
|
"id" to id
|
||||||
|
@ -76,9 +90,9 @@ fun Project.toProjectDeleteResponseBody(): ResponseBody {
|
||||||
fun Project.calculateProgress(): BigDecimal {
|
fun Project.calculateProgress(): BigDecimal {
|
||||||
// Total number of pentests listet in the OWASP testing guide
|
// Total number of pentests listet in the OWASP testing guide
|
||||||
// https://owasp.org/www-project-web-security-testing-guide/assets/archive/OWASP_Testing_Guide_v4.pdf
|
// https://owasp.org/www-project-web-security-testing-guide/assets/archive/OWASP_Testing_Guide_v4.pdf
|
||||||
// @Value("\${owasp.web.pentests}")
|
// @Value("\${owasp.web.objectives}")
|
||||||
// lateinit var TOTALPENTESTS: Int
|
// lateinit var TOTALPENTESTS: Int
|
||||||
val TOTALPENTESTS = 95.0
|
val TOTAL_OWASP_OBJECTIVES = 95.0
|
||||||
|
|
||||||
return if (projectPentests.isEmpty())
|
return if (projectPentests.isEmpty())
|
||||||
BigDecimal.ZERO
|
BigDecimal.ZERO
|
||||||
|
@ -92,7 +106,7 @@ fun Project.calculateProgress(): BigDecimal {
|
||||||
completedPentests += 0.5
|
completedPentests += 0.5
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val progress = (completedPentests * 100) / TOTALPENTESTS
|
val progress = (completedPentests * 100) / TOTAL_OWASP_OBJECTIVES
|
||||||
BigDecimal(progress).setScale(2, RoundingMode.HALF_UP)
|
BigDecimal(progress).setScale(2, RoundingMode.HALF_UP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ class ProjectController(private val projectService: ProjectService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{projectId}")
|
@GetMapping("/{projectId}")
|
||||||
fun getProjectById(
|
fun getCompletedProjectById(
|
||||||
@PathVariable(value = "projectId") projectId: String
|
@PathVariable(value = "projectId") projectId: String
|
||||||
): Mono<ResponseEntity<ResponseBody>> {
|
): Mono<ResponseEntity<ResponseBody>> {
|
||||||
return projectService.getProjectById(projectId).map {
|
return projectService.getProjectById(projectId).map {
|
||||||
|
@ -46,6 +46,18 @@ class ProjectController(private val projectService: ProjectService) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/evaluation/{projectId}")
|
||||||
|
fun getProjectById(
|
||||||
|
@PathVariable(value = "projectId") projectId: String
|
||||||
|
): Mono<ResponseEntity<ResponseBody>> {
|
||||||
|
return projectService.getProjectById(projectId).map {
|
||||||
|
it.toProjectEvaluatedPentestResponseBody()
|
||||||
|
}.map {
|
||||||
|
if (it.isEmpty()) ResponseEntity.noContent().build()
|
||||||
|
else ResponseEntity.ok(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
fun saveProject(
|
fun saveProject(
|
||||||
@RequestBody body: ProjectRequestBody
|
@RequestBody body: ProjectRequestBody
|
||||||
|
|
|
@ -22,6 +22,6 @@ keycloakhost=localhost
|
||||||
keycloak.client.url=http://localhost:8080
|
keycloak.client.url=http://localhost:8080
|
||||||
keycloak.client.realm.path=auth/realms/c4po_realm_local/
|
keycloak.client.realm.path=auth/realms/c4po_realm_local/
|
||||||
|
|
||||||
## Total number of pentests listet in the OWASP testing guide
|
## Total number of pentests / objectives listet in the OWASP testing guide
|
||||||
## https://owasp.org/www-project-web-security-testing-guide/assets/archive/OWASP_Testing_Guide_v4.pdf
|
## https://owasp.org/www-project-web-security-testing-guide/assets/archive/OWASP_Testing_Guide_v4.pdf
|
||||||
owasp.web.pentests=95
|
owasp.web.objectives=95
|
||||||
|
|
Loading…
Reference in New Issue