feat: As a user I want to add the project version

This commit is contained in:
Marcel Haag 2023-04-06 11:43:32 +02:00 committed by Cel
parent 9e4fa27b92
commit 07c6871294
70 changed files with 1031 additions and 387 deletions

View File

@ -36,6 +36,9 @@
.logo-container {
font-style: oblique;
color: #e74c3c;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
nb-action {

View File

@ -34,7 +34,12 @@ export class HeaderComponent implements OnInit {
// User Menu Properties
userPictureOnly = false;
user: BehaviorSubject<User> = new BehaviorSubject<User>(null);
userMenu: NbMenuItem[] = [{title: '', pathMatch: 'prefix'}];
userMenu: NbMenuItem[] = [
{
title: '',
pathMatch: 'prefix'
}
];
readonly FALLBACK_IMG = 'assets/images/demo/anon-user-icon.png';
constructor(
@ -67,7 +72,7 @@ export class HeaderComponent implements OnInit {
console.error(err);
}
});
// Handle user profile manu selection
// Handle user profile menu selection
this.menuService.onItemClick()
.pipe(
untilDestroyed(this)

View File

@ -1,5 +1,5 @@
@import '../../../assets/@theme/styles/themes';
.pentest-categories {
width: 22vw;
width: 20rem;
}

View File

@ -9,18 +9,18 @@ import {TranslateService} from '@ngx-translate/core';
import {ProjectState} from '@shared/stores/project-state/project-state';
import {catchError, switchMap, tap} from 'rxjs/operators';
import {Pentest, transformPentestsToObjectiveEntries} from '@shared/models/pentest.model';
import {UntilDestroy} from '@ngneat/until-destroy';
@Component({
selector: 'app-objective-categories',
templateUrl: './objective-categories.component.html',
styleUrls: ['./objective-categories.component.scss']
})
export class ObjectiveCategoriesComponent implements OnInit, OnDestroy {
@UntilDestroy()
export class ObjectiveCategoriesComponent implements OnInit {
categories: NbMenuItem[] = [];
selectedCategory: Category = 0;
private destroy$ = new Subject<void>();
constructor(private store: Store,
private menuService: NbMenuService,
private translateService: TranslateService) {
@ -49,6 +49,7 @@ export class ObjectiveCategoriesComponent implements OnInit, OnDestroy {
untilDestroyed(this)
)
.subscribe((menuBag) => {
if (menuBag.tag === 'menu') {
this.selectedCategory = menuBag.item.data;
this.categories.forEach(category => {
category.selected = false;
@ -57,6 +58,7 @@ export class ObjectiveCategoriesComponent implements OnInit, OnDestroy {
menuBag.item.selected = true;
this.store.dispatch(new ChangeCategory(this.selectedCategory));
}
}
});
}
@ -86,9 +88,4 @@ export class ObjectiveCategoriesComponent implements OnInit, OnDestroy {
}
}
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}

View File

@ -9,20 +9,23 @@
</button>
</div>
<app-report-state-tag class="state-tag" [currentReportState]="selectedProject$.getValue()?.state"></app-report-state-tag>
<h4>{{selectedProject$.getValue().title}}</h4>
<div class="header-info" fxLayout="row" fxLayoutGap="4rem" fxLayoutAlign="space-between center">
<app-report-state-tag class="state-tag"
[currentReportState]="selectedProject$.getValue()?.state"></app-report-state-tag>
<h4 class="project-title">{{selectedProject$.getValue().title}}</h4>
<app-version-tag [version]="selectedProject$.getValue().version"></app-version-tag>
</div>
<div class="button-container">
<nb-actions size="medium">
<!--Actions for normal view-->
<nb-actions size="medium" fxHide.lt-lg>
<nb-action>
<!--status="button-outline-basic-text-color"-->
<button nbButton
status="primary"
shape="round"
(click)="onClickEditPentestProject()">
<fa-icon [icon]="fa.faEdit"
class="element-icon fa-lg"></fa-icon>
<!--<span class="element-text">{{ 'global.action.edit' | translate }}</span>-->
</button>
</nb-action>
<nb-action>
@ -36,5 +39,11 @@
</button>
</nb-action>
</nb-actions>
<!--Actions for mobile devices-->
<nb-actions size="medium" fxHide fxShow.lt-lg>
<nb-action>
<nb-user [nbContextMenu]="objectiveActionItems" shape="rectangle" [picture]="BARS_IMG" name="" [onlyPicture]></nb-user> <!--[picture]="fa.faRedoAlt"-->
</nb-action>
</nb-actions>
</div>
</div>

View File

@ -1,16 +1,30 @@
@import '../../../assets/@theme/styles/_text-overflow.scss';
.pentest-header {
width: calc(100vw - 14%);
width: 100vw;
.back-button-container {
.back-element-icon {
}
}
.header-info {
position: absolute;
margin-left: 10rem;
margin-right: 10rem;
text-align: center;
.state-tag {
}
.project-title {
@include multiLineEllipsis($font-size: 1.5rem, $font-weight: bold, $line-height: 2rem, $lines-to-show: 1, $max-width: 36rem);
}
}
.button-container {
display: flex;
align-content: flex-end;
// ToDo: Fix so that longer / shorter name won't change needed margin
// margin-right: 2.25rem;
position: absolute;
right: 2rem;
.element-icon {
}

View File

@ -11,7 +11,7 @@ import {RouterTestingModule} from '@angular/router/testing';
import {NgxsModule, Store} from '@ngxs/store';
import {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/stores/project-state/project-state';
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
import {NbActionsModule, NbIconModule} from '@nebular/theme';
import {NbActionsModule, NbIconModule, NbMenuService} from '@nebular/theme';
import {ProjectService} from '@shared/services/api/project.service';
import {ProjectServiceMock} from '@shared/services/api/project.service.mock';
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
@ -27,6 +27,7 @@ import {ExportReportDialogServiceMock} from '@shared/modules/export-report-dialo
import {ReportState} from '@shared/models/state.enum';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
allProjects: [],
selectedProject: {
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
client: 'E Corp',
@ -35,6 +36,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
tester: 'Novatester',
summary: '',
state: ReportState.NEW,
version: '1.0',
testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
},
@ -80,6 +82,7 @@ describe('ObjectiveHeaderComponent', () => {
NgxsModule.forRoot([ProjectState])
],
providers: [
NbMenuService,
{provide: ProjectService, useValue: new ProjectServiceMock()},
{provide: ProjectDialogService, useClass: ProjectDialogServiceMock},
{provide: ExportReportDialogService, useClass: ExportReportDialogServiceMock},

View File

@ -16,6 +16,9 @@ import {ProjectDialogService} from '@shared/modules/project-dialog/service/proje
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';
import {NbMenuItem} from '@nebular/theme/components/menu/menu.service';
import {NbMenuService} from '@nebular/theme';
import {TranslateService} from '@ngx-translate/core';
@UntilDestroy()
@Component({
@ -27,6 +30,23 @@ export class ObjectiveHeaderComponent implements OnInit {
readonly fa = FA;
selectedProject$: BehaviorSubject<Project> = new BehaviorSubject<Project>(null);
// Mobile menu properties
objectiveActionItems: NbMenuItem[] = [
{
title: 'global.action.edit',
badge: {
status: 'warning'
}
},
{
title: 'global.action.report',
badge: {
status: 'info'
}
},
];
readonly BARS_IMG = 'assets/images/icons/bars.svg';
readonly ELLIPSIS_IMG = 'assets/images/icons/ellipsis.svg';
constructor(private store: Store,
private readonly notificationService: NotificationService,
@ -34,7 +54,10 @@ export class ObjectiveHeaderComponent implements OnInit {
private projectDialogService: ProjectDialogService,
private projectService: ProjectService,
private exportReportDialogService: ExportReportDialogService,
private readonly router: Router) {
private readonly router: Router,
private translateService: TranslateService,
private menuService: NbMenuService
) {
}
ngOnInit(): void {
@ -52,6 +75,33 @@ export class ObjectiveHeaderComponent implements OnInit {
console.error(err);
}
});
// Handle user profile menu action selection
this.menuService.onItemClick()
.pipe(
untilDestroyed(this)
)
.subscribe((menuBag) => {
if (menuBag.item.badge && menuBag.item.badge.status === 'warning') {
this.onClickEditPentestProject();
} else if (menuBag.item.badge && menuBag.item.badge.status === 'info') {
this.onClickGeneratePentestReport();
}
});
// Setup stream to translate menu action item
this.translateService.stream('global.action.edit')
.pipe(
untilDestroyed(this)
).subscribe((text: string) => {
this.objectiveActionItems[0].title = text;
});
// Setup stream to translate menu action item
this.translateService.stream('global.action.report')
.pipe(
untilDestroyed(this)
).subscribe((text: string) => {
this.objectiveActionItems[1].title = text;
});
}
onClickRouteBack(): void {
@ -78,6 +128,7 @@ export class ObjectiveHeaderComponent implements OnInit {
untilDestroyed(this)
).subscribe({
next: (project) => {
if (project) {
this.store.dispatch(new InitProjectState(
project,
[],
@ -86,6 +137,7 @@ export class ObjectiveHeaderComponent implements OnInit {
untilDestroyed(this)
).subscribe();
}
}
});
}

View File

@ -11,7 +11,7 @@ import {
NbListModule,
NbButtonModule,
NbTooltipModule,
NbActionsModule
NbActionsModule, NbUserModule, NbContextMenuModule
} from '@nebular/theme';
import {TranslateModule} from '@ngx-translate/core';
import {StatusTagModule} from '@shared/widgets/status-tag/status-tag.module';
@ -26,6 +26,7 @@ import {ExportReportDialogModule} from '@shared/modules/export-report-dialog/exp
import {ProjectDialogModule} from '@shared/modules/project-dialog/project-dialog.module';
import {CommentWidgetModule} from '@shared/widgets/comment-widget/comment-widget.module';
import {ReportStateTagModule} from '@shared/widgets/report-state-tag/report-state-tag.module';
import {VersionTagModule} from '@shared/widgets/version-tag/version-tag.module';
@NgModule({
declarations: [
@ -58,7 +59,10 @@ import {ReportStateTagModule} from '@shared/widgets/report-state-tag/report-stat
FindigWidgetModule,
CommentWidgetModule,
NbMenuModule,
ReportStateTagModule
ReportStateTagModule,
VersionTagModule,
NbUserModule,
NbContextMenuModule
],
exports: [
ObjectiveHeaderComponent,

View File

@ -1,4 +1,4 @@
<nb-card class="pentest-table">
<div class="pentest-table">
<table [nbTreeGrid]="dataSource">
<tr nbTreeGridHeaderRow *nbTreeGridHeaderRowDef="columns"></tr>
<tr nbTreeGridRow *nbTreeGridRowDef="let pentest; columns: columns"
@ -52,6 +52,6 @@
</td>
</ng-container>
</table>
</nb-card>
</div>
<app-loading-spinner [isLoading$]="isLoading()" *ngIf="isLoading() | async"></app-loading-spinner>

View File

@ -1,7 +1,11 @@
@import '../../../assets/@theme/styles/themes';
.pentest-table {
width: calc(78vw - 18%);
// width: calc(78vw - 18%);
// width: 100%;
// width: calc(100% - 20rem);
margin-right: 2rem;
padding-right: 2rem;
.pentest-cell {
// Add style here

View File

@ -25,6 +25,7 @@ import {CommentDialogServiceMock} from '@shared/modules/comment-dialog/service/c
import {ReportState} from '@shared/models/state.enum';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
allProjects: [],
selectedProject: {
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
client: 'E Corp',
@ -33,6 +34,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
tester: 'Novatester',
summary: '',
state: ReportState.NEW,
version: '1.0',
testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
},

View File

@ -16,6 +16,7 @@ import {NotificationServiceMock} from '@shared/services/toaster-service/notifica
import {ReportState} from '@shared/models/state.enum';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
allProjects: [],
selectedProject: {
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
client: 'E Corp',
@ -24,6 +25,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
tester: 'Novatester',
summary: '',
state: ReportState.NEW,
version: '1.0',
testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
},

View File

@ -25,6 +25,7 @@ import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.
import {ReportState} from '@shared/models/state.enum';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
allProjects: [],
selectedProject: {
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
client: 'E Corp',
@ -33,6 +34,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
tester: 'Novatester',
summary: '',
state: ReportState.NEW,
version: '1.0',
testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
},

View File

@ -16,6 +16,7 @@ import {PentestStatus} from '@shared/models/pentest-status.model';
import {ReportState} from '@shared/models/state.enum';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
allProjects: [],
selectedProject: {
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
client: 'E Corp',
@ -24,6 +25,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
tester: 'Novatester',
summary: '',
state: ReportState.NEW,
version: '1.0',
testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
},

View File

@ -1,4 +1,4 @@
<div class="pentest-stepper" fxLayout="row" fxLayoutGap="2rem" fxLayoutAlign="space-between center">
<div class="pentest-header" fxLayout="row" fxLayoutGap="2rem" fxLayoutAlign="space-between center">
<div class="exit-button-container">
<button nbButton
shape="round"
@ -11,7 +11,13 @@
</button>
</div>
<h4>{{selectedProjectTitle$.getValue()}} / {{pentest$.getValue().refNumber}}</h4>
<div class="header-info" fxLayout="row" fxHide.lt-lg>
<span class="project-title">{{selectedProjectTitle$.getValue()}}</span>
<span class="pentest-ref">{{" / " + pentest$.getValue().refNumber}}</span>
</div>
<div class="header-info-mobile" fxHide fxShow.lt-lg>
<span class="pentest-ref">{{pentest$.getValue().refNumber}}</span>
</div>
<div class="pentest-status-container" fxLayout="row" fxLayoutGap="2.5rem" fxLayoutAlign="end center">
<!-- Pentest Timer-->
@ -21,7 +27,7 @@
<!-- Complete Pentest -->
<div>
<button nbButton
class="save-pentest-button"
class="complete-pentest-button"
status="success"
[disabled]="!pentestStatusChanged() || !pentestHasFindingsOrComments()"
title="{{ 'global.action.save' | translate }}"

View File

@ -1,5 +1,37 @@
.pentest-stepper {
width: calc(100vw - 14%);
@import '../../../assets/@theme/styles/_text-overflow.scss';
.pentest-header {
width: 100vw;
.header-info {
position: absolute;
margin-left: 10rem;
margin-right: 10rem;
text-align: center;
.project-title {
@include multiLineEllipsis($font-size: 1.5rem, $font-weight: bold, $line-height: 2rem, $lines-to-show: 1, $max-width: 32rem);
}
.pentest-ref {
font-size: 1.5rem;
font-weight: bold;
line-height: 2rem;
}
}
.header-info-mobile{
position: absolute;
margin-left: 10rem;
margin-right: 10rem;
text-align: center;
.pentest-ref {
font-size: 1.5rem;
font-weight: bold;
line-height: 2rem;
}
}
.exit-button-container {
.exit-element-icon {
@ -11,6 +43,8 @@
}
.pentest-status-container {
position: fixed;
right: 4rem;
// display: flex;
// align-content: flex-end;
@ -20,9 +54,13 @@
margin: 0.5rem 2.25rem 1rem 0;
}
.complete-pentest-button {
// position: absolute;
.action-element-text {
padding-left: 0.5rem;
}
}
.pentest-status-dialog {
margin: 1rem 2.25rem 1rem 0;

View File

@ -16,6 +16,7 @@ import {NotificationServiceMock} from '@shared/services/toaster-service/notifica
import {ReportState} from '@shared/models/state.enum';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
allProjects: [],
selectedProject: {
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
client: 'E Corp',
@ -24,6 +25,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
tester: 'Novatester',
summary: '',
state: ReportState.NEW,
version: '1.0',
testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
},

View File

@ -6,25 +6,27 @@
<!--Filter-->
<div fxLayout="row" fxLayoutGap="1rem" class="header-filer">
<!--Actions-->
<!--ToDo: Add searchbar that filters for title, client, tester-->
<form class="project-filter-input">
<nb-form-field>
<fa-icon nbPrefix class="search-prefix-icon" [icon]="fa.faSearch"></fa-icon>
<input type="text" required
<input type="text"
fullWidth nbInput
class="search-field"
[formControl]="projectSearch"
placeholder="{{ 'project.filter.placeholder' | translate }}"
placeholder="{{ 'project.filter.placeholder' | translate: this.allProjectsCount$?.getValue() }}"
status="basic"
shape="semi-round"
fieldSize="medium"
status="basic">
fieldSize="medium">
</nb-form-field>
</form>
<!--ToDo: Add dropdown to filter for specific state-->
<button nbButton
status="danger"
outline
size="medium"
shape="semi-round"
class="reset-filter-btn"
[disabled]="projectSearch.value === ''"
(click)="onClickResetFilter()">
<fa-icon [icon]="fa.faFilterCircleXmark" class="btn-icon"></fa-icon>
{{'global.action.reset' | translate}}
@ -45,75 +47,11 @@
</div>
</nb-layout-header>
<!--Column-->
<!--ToDo: Fix the column style for multiple projects in css-->
<nb-layout-column class="pentest-overview-column">
<div fxLayout="row" fxLayoutGap="2rem">
<div *ngFor="let project of projects$ | async">
<nb-card class="project-card" accent="{{getProjectAccentFillStatus(project?.state)}}">
<nb-card-header fxLayoutAlign="start center"
routerLink="id"
fragment="{{project.id}}"
class="project-link project-header"
(click)="onClickRouteToProject(project)">
<div fxLayout="row" fxLayoutAlign="space-between center">
<h4 class="header-title">{{project?.title}}</h4>
<app-report-state-tag class="state-tag" [currentReportState]="project?.state"></app-report-state-tag>
</div>
</nb-card-header>
<nb-card-body class="project-link"
routerLink="id"
fragment="{{project.id}}"
(click)="onClickRouteToProject(project)">
<p class="project-subheader">
{{'project.client' | translate}}:
</p>
<span class="project-paragraph">
{{project?.client}}
</span>
<p class="project-subheader">
{{'project.tester' | translate}}:
</p>
<span class="project-paragraph">
{{project?.tester}}
</span>
<p class="project-subheader">
{{'project.createdAt' | translate}}:
</p>
<span class="project-paragraph">
{{project?.createdAt | dateTimeFormat}}
</span>
</nb-card-body>
<nb-card-footer>
<div fxLayout="row" fxLayoutGap="1rem" fxLayoutAlign="start end">
<div class="project-progress">
<nb-progress-bar *ngIf="project.testingProgress > 0; else altProgressBar"
status="warning"
[value]="project.testingProgress"
[displayValue]="true">
</nb-progress-bar>
<ng-template #altProgressBar>
{{'popup.info' | translate}} {{'global.no.progress' | translate}}
</ng-template>
</div>
<button nbButton
status="primary"
size="small"
class="project-button"
(click)="onClickEditProject(project)">
<fa-icon [icon]="fa.faPencilAlt"></fa-icon>
</button>
<button nbButton
status="danger"
size="small"
class="project-button"
(click)="onClickDeleteProject(project)">
<fa-icon [icon]="fa.faTrash"></fa-icon>
</button>
</div>
</nb-card-footer>
</nb-card>
<div class="project-grid">
<div class="project" *ngFor="let project of projects$ | async">
<app-project-widget [project]="project"></app-project-widget>
</div>
</div>
<!--Error Text-->
@ -125,7 +63,6 @@
</div>
<!--Loading Spinner-->
<app-loading-spinner [isLoading$]="isLoading()" *ngIf="isLoading() | async"></app-loading-spinner>
</nb-layout-column>
</nb-layout>
</div>

View File

@ -1,21 +1,28 @@
@import '../../assets/@theme/styles/themes';
@import '../../assets/@theme/styles/variables';
@import '../../assets/@theme/styles/_text-overflow.scss';
.pentest-overview {
width: 100vw;
height: 80vh;
// ToDo: Disable and fix scrolling
overflow: hidden;
height: 85vh;
overflow: hidden !important;
.pentest-overview-header {
width: 100vw;
height: 5rem;
.header-filer {
.project-filter-input {
width: 24rem;
border-color: nb-theme(color-control-default);
.search-prefix-icon {
.search-field:active {
color: nb-theme(color-info-default);
// opacity: initial;
}
.search-prefix-icon:active {
color: nb-theme(color-info-default);
}
}
@ -53,63 +60,26 @@
.pentest-overview-column {
width: 100vw;
// ToDo: Adjust this property when adding footer
height: calc(100% - 15rem) !important;
max-height: 100vh !important;
margin-top: 1.25rem;
// Scrollbar
overflow-y: scroll !important;
overflow-x: hidden;
scroll-behavior: smooth;
.project-card {
max-width: 22rem;
width: 22rem;
min-width: 20rem;
max-height: 100%;
height: 100%;
min-height: 100%;
.project-grid {
display: grid;
/* define the number of grid columns */
grid-template-columns: repeat( auto-fill, minmax(24rem, 1fr) );
.project-header {
max-height: 8rem;
height: 8rem;
min-height: 6rem;
.header-title {
width: 12.5rem;
}
.state-tag {
width: 1rem;
.project {
padding-bottom: 1.25rem;
height: max-content;
}
}
.project-subheader {
font-size: 1.25rem;
font-weight: bold;
}
.project-paragraph {
font-size: 1.15rem;
font-style: italic;
}
.project-progress {
max-width: 65%;
width: 65%;
min-width: 65%;
}
.project-button {
height: 1.425rem;
}
}
.project-card:hover {
background-color: nb-theme(color-basic-transparent-focus);
// Increases element size on hover
// Decreases usability which is why it is commented out
/*
margin-top: +0.625rem;
transform: scale(1.025);
*/
}
.project-link:hover {
cursor: pointer !important;
}
.error-text {
font-size: 1.25rem;
font-weight: bold;

View File

@ -26,7 +26,7 @@ import {DialogService} from '@shared/services/dialog-service/dialog.service';
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
import {ProjectDialogServiceMock} from '@shared/modules/project-dialog/service/project-dialog.service.mock';
import {MockComponent, MockPipe} from 'ng-mocks';
import {MockComponent} from 'ng-mocks';
describe('ProjectOverviewComponent', () => {
let component: ProjectOverviewComponent;
@ -36,19 +36,17 @@ describe('ProjectOverviewComponent', () => {
await TestBed.configureTestingModule({
declarations: [
ProjectOverviewComponent,
MockComponent(LoadingSpinnerComponent),
MockPipe(DateTimeFormatPipe)
MockComponent(LoadingSpinnerComponent)
],
imports: [
CommonModule,
ProjectOverviewRoutingModule,
NbCardModule,
NbButtonModule,
FlexLayoutModule,
BrowserAnimationsModule,
FontAwesomeModule,
TranslateModule,
NbProgressBarModule,
ProjectOverviewRoutingModule,
NbSpinnerModule,
HttpClientTestingModule,
ThemeModule.forRoot(),

View File

@ -5,16 +5,17 @@ import {BehaviorSubject, Observable} from 'rxjs';
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
import {ProjectService} from '@shared/services/api/project.service';
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service';
import {filter, startWith, tap} from 'rxjs/operators';
import {startWith, tap} from 'rxjs/operators';
import {DialogService} from '@shared/services/dialog-service/dialog.service';
import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
import {Router} from '@angular/router';
import {Route} from '@shared/models/route.enum';
import {InitProjectState} from '@shared/stores/project-state/project-state.actions';
import {Store} from '@ngxs/store';
import {ReportState} from '@shared/models/state.enum';
import {FormControl} from '@angular/forms';
import {ProjectState} from '@shared/stores/project-state/project-state';
import {Pentest} from '@shared/models/pentest.model';
import {Route} from '@shared/models/route.enum';
import {SetAvailableProjects} from '@shared/stores/project-state/project-state.actions';
@UntilDestroy()
@Component({
@ -33,6 +34,7 @@ export class ProjectOverviewComponent implements OnInit {
// Search
projectSearch: FormControl;
protected filter$: Observable<string>;
allProjectsCount$: BehaviorSubject<any> = new BehaviorSubject<any>({allProjectsCount: 0});
constructor(
private readonly notificationService: NotificationService,
@ -44,9 +46,24 @@ export class ProjectOverviewComponent implements OnInit {
}
ngOnInit(): void {
// Load all available projects
this.loadProjects();
// Subscribe to project store
this.store.select(ProjectState.allProjects).pipe(
untilDestroyed(this)
).subscribe({
next: (projects: Project[]) => {
if (projects.length === 0) {
this.loadProjects();
} else {
}
},
error: err => {
console.error(err);
}
});
// Setup Search
this.projectSearch = new FormControl({value: '', disabled: !this.allProjects$.getValue()});
this.projectSearch = new FormControl({value: '', disabled: this.allProjects$.getValue() === []});
this.setFilterObserverForProjects();
}
@ -58,8 +75,16 @@ export class ProjectOverviewComponent implements OnInit {
)
.subscribe({
next: (projects: Project[]) => {
if (projects) {
this.projects$.next(projects);
this.allProjects$.next(projects);
this.allProjectsCount$.next({allProjectsCount: projects.length});
this.store.dispatch(new SetAvailableProjects(projects));
} else {
this.projects$.next([]);
this.allProjects$.next([]);
this.allProjectsCount$.next({allProjectsCount: 0});
}
this.loading$.next(false);
},
error: err => {
@ -89,122 +114,11 @@ export class ProjectOverviewComponent implements OnInit {
});
}
onClickEditProject(project: Project): void {
this.projectDialogService.openProjectDialog(
ProjectDialogComponent,
project,
{
closeOnEsc: false,
hasScroll: false,
autoFocus: true,
closeOnBackdropClick: false
}
).pipe(
untilDestroyed(this)
).subscribe({
next: () => {
this.loadProjects();
}
});
}
onClickDeleteProject(project: Project): void {
// Set dialog message
const message = {
title: 'project.delete.title',
key: 'project.delete.key',
data: {name: project.title},
} as any;
// Check if project is empty
if (project.testingProgress === 0) {
this.dialogService.openConfirmDialog(
message
).onClose.pipe(
filter((confirm) => !!confirm),
untilDestroyed(this)
).subscribe({
next: () => {
this.deleteProject(project);
}
});
} else {
const secMessage = {
title: 'project.delete.title',
key: 'project.delete.sec.key',
confirmString: project.title.toString(),
inputPlaceholderKey: 'project.delete.confirmStringPlaceholder',
data: {name: project.title, confirmString: project.title.toString()},
} as any;
// Set confirm string
// message.data.confirmString = project.title;
this.dialogService.openSecurityConfirmDialog(
secMessage
).onClose.pipe(
filter((confirm) => !!confirm),
untilDestroyed(this)
).subscribe({
next: () => {
this.deleteProject(project);
}
});
}
}
onClickRouteToProject(project): void {
this.router.navigate([Route.OBJECTIVE_OVERVIEW]).then(() => {
this.store.dispatch(new InitProjectState(
project,
[],
[]
)).pipe(untilDestroyed(this)).subscribe();
}, err => {
console.error(err);
});
}
// HTML only
isLoading(): Observable<boolean> {
return this.loading$.asObservable();
}
/**
* HTML only
* @return the correct nb-accent for current report state of the project
*/
getProjectAccentFillStatus(value: any): string {
let reportStateFillStatus;
const statusValue = typeof value !== 'number' ? ReportState[value] : value;
// Check for correct accent color of status
switch (statusValue) {
case 6:
case 7: {
reportStateFillStatus = 'success';
break;
}
case 0: {
reportStateFillStatus = 'info';
break;
}
case 8:
case 9:
case 11:
case 12: {
reportStateFillStatus = 'warning';
break;
}
case 1:
case 10: {
reportStateFillStatus = 'danger';
break;
}
default: {
reportStateFillStatus = 'control';
break;
}
}
return reportStateFillStatus;
}
onClickResetFilter(): void {
this.projectSearch.reset('');
this.projects$.next(this.allProjects$.getValue());
@ -234,31 +148,4 @@ export class ProjectOverviewComponent implements OnInit {
}
);
}
private deleteProject(project: Project): void {
this.projectService.deleteProjectById(project.id).pipe(
untilDestroyed(this)
).subscribe({
next: () => {
this.loadProjects();
this.notificationService.showPopup('project.popup.delete.success', PopupType.SUCCESS);
}, error: error => {
this.notificationService.showPopup('project.popup.delete.failed', PopupType.FAILURE);
this.onRequestFailed(project);
console.error(error);
}
});
}
private onRequestFailed(retryParameter: any): void {
this.dialogService.openRetryDialog({key: 'global.retry.dialog', data: null}).onClose
.pipe(
untilDestroyed(this)
)
.subscribe((ref) => {
if (ref.retry) {
this.deleteProject(retryParameter);
}
});
}
}

View File

@ -14,7 +14,6 @@ import {
import {FlexLayoutModule} from '@angular/flex-layout';
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
import {TranslateModule} from '@ngx-translate/core';
import {DateTimeFormatPipe} from '@shared/pipes/date-time-format.pipe';
import {ProjectDialogModule} from '@shared/modules/project-dialog/project-dialog.module';
import {CommonAppModule} from '../common-app.module';
import {ConfirmDialogModule} from '@shared/modules/confirm-dialog/confirm-dialog.module';
@ -22,11 +21,11 @@ import {SecurityConfirmDialogModule} from '@shared/modules/security-confirm-dial
import {RouterModule} from '@angular/router';
import {ReportStateTagModule} from '@shared/widgets/report-state-tag/report-state-tag.module';
import {ReactiveFormsModule} from '@angular/forms';
import {ProjectWidgetModule} from '@shared/widgets/project-widget/project-widget.module';
@NgModule({
declarations: [
ProjectOverviewComponent,
DateTimeFormatPipe
ProjectOverviewComponent
],
imports: [
CommonModule,
@ -50,7 +49,8 @@ import {ReactiveFormsModule} from '@angular/forms';
NbInputModule,
NbFormFieldModule,
ReactiveFormsModule,
NbSelectModule
NbSelectModule,
ProjectWidgetModule
]
})
export class ProjectOverviewModule {

View File

@ -11,7 +11,7 @@
</nb-card>
</nb-layout-column>
<nb-layout-column fxFlex="0 1 max-content" class="table-wrapper" >
<nb-layout-column fxFlex=" max-content" class="table-wrapper" >
<nb-card class="table-column">
<nb-card-body>
<app-objective-table></app-objective-table>

View File

@ -21,6 +21,7 @@
.table-wrapper{
padding-right: 0 !important;
border-style: none;
.table-column {
overflow: auto !important;

View File

@ -29,6 +29,7 @@ import {ExportReportDialogServiceMock} from '@shared/modules/export-report-dialo
import {ReportState} from '@shared/models/state.enum';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
allProjects: [],
selectedProject: {
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
client: 'E Corp',
@ -37,6 +38,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
tester: 'Novatester',
summary: '',
state: ReportState.NEW,
version: '1.0',
testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
},

View File

@ -0,0 +1,16 @@
/* mixin for multiline */
@mixin multiLineEllipsis($font-size, $font-weight, $line-height, $lines-to-show, $max-width) {
// Fallback for non-webkit
display: block;
display: -webkit-box;
max-width: $max-width;
// Fallback for non-webkit
height: calc(#{$font-size} * #{$line-height} * #{$lines-to-show});
font-size: $font-size;
font-weight: $font-weight;
line-height: $line-height;
-webkit-line-clamp: $lines-to-show;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}

View File

@ -22,6 +22,7 @@
"password": "Passwort",
"no.progress": "Kein Fortschritt",
"project": "Projekt",
"version": "Version",
"validationMessage": {
"inputNotMatching": "Eingabe stimmt nicht überein!"
},
@ -98,7 +99,7 @@
"no.projects": "Keine Projekte verfügbar"
},
"filter": {
"placeholder": "Projekt suchen"
"placeholder": "Alle {{allProjectsCount}} Projekt(e) durchsuchen.."
},
"create": {
"header": "Neues Projekt erstellen"

View File

@ -22,6 +22,7 @@
"password": "Password",
"no.progress": "No progress",
"project": "Project",
"version": "Version",
"validationMessage": {
"inputNotMatching": "Input does not match!"
},
@ -98,7 +99,7 @@
"no.projects": "No projects available"
},
"filter": {
"placeholder": "Search for project"
"placeholder": "Search through all {{allProjectsCount}} project(s).."
},
"create": {
"header": "Create New Project"

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1080" height="1080" viewBox="0 0 1080 1080" xml:space="preserve">
<desc>Created with Fabric.js 5.2.4</desc>
<defs>
</defs>
<rect x="0" y="0" width="100%" height="100%" fill="transparent"></rect>
<g transform="matrix(1 0 0 1 540 540)" id="339ec410-843e-4d32-9d44-a3214d1855a2" >
<rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,255,255); fill-rule: nonzero; opacity: 1; visibility: hidden;" vector-effect="non-scaling-stroke" x="-540" y="-540" rx="0" ry="0" width="1080" height="1080" />
</g>
<g transform="matrix(1 0 0 1 540 540)" id="c5b836d2-f11c-4f7c-a1e7-feb062409888" >
</g>
<g transform="matrix(1.69 0 0 1.95 550.4 533.5)" id="f6e0b7ed-b6ac-4653-944b-d371601fdf97" >
<path style="stroke: rgb(254,254,255); stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(52,164,254); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-224, -256)" d="M 0 96 C 0 78.3 14.3 64 32 64 L 416 64 C 433.7 64 448 78.3 448 96 C 448 113.7 433.7 128 416 128 L 32 128 C 14.3 128 0 113.7 0 96 z M 0 256 C 0 238.3 14.3 224 32 224 L 416 224 C 433.7 224 448 238.3 448 256 C 448 273.7 433.7 288 416 288 L 32 288 C 14.3 288 0 273.7 0 256 z M 448 416 C 448 433.7 433.7 448 416 448 L 32 448 C 14.3 448 0 433.7 0 416 C 0 398.3 14.3 384 32 384 L 416 384 C 433.7 384 448 398.3 448 416 z" stroke-linecap="round" />
</g>
<g transform="matrix(NaN NaN NaN NaN 0 0)" >
<g style="" >
</g>
</g>
<g transform="matrix(NaN NaN NaN NaN 0 0)" >
<g style="" >
</g>
</g>
<g transform="matrix(NaN NaN NaN NaN 0 0)" >
<g style="" >
</g>
</g>
<g transform="matrix(NaN NaN NaN NaN 0 0)" >
<g style="" >
</g>
</g>
<g transform="matrix(NaN NaN NaN NaN 0 0)" >
<g style="" >
</g>
</g>
<g transform="matrix(NaN NaN NaN NaN 0 0)" >
<g style="" >
</g>
</g>
<g transform="matrix(NaN NaN NaN NaN 0 0)" >
<g style="" >
</g>
</g>
<g transform="matrix(NaN NaN NaN NaN 0 0)" >
<g style="" >
</g>
</g>
<g transform="matrix(NaN NaN NaN NaN 0 0)" >
<g style="" >
</g>
</g>
<g transform="matrix(NaN NaN NaN NaN 0 0)" >
<g style="" >
</g>
</g>
<g transform="matrix(NaN NaN NaN NaN 0 0)" >
<g style="" >
</g>
</g>
<g transform="matrix(NaN NaN NaN NaN 0 0)" >
<g style="" >
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1080" height="1080" viewBox="0 0 1080 1080" xml:space="preserve">
<desc>Created with Fabric.js 5.2.4</desc>
<defs>
</defs>
<rect x="0" y="0" width="100%" height="100%" fill="transparent"></rect>
<g transform="matrix(1 0 0 1 540 540)" id="38dd971f-78c6-4561-8eaa-43300a653918" >
<rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,255,255); fill-rule: nonzero; opacity: 1; visibility: hidden;" vector-effect="non-scaling-stroke" x="-540" y="-540" rx="0" ry="0" width="1080" height="1080" />
</g>
<g transform="matrix(1 0 0 1 540 540)" id="849561dd-eb53-48c2-ba7a-68c6bdd55e5b" >
</g>
<g transform="matrix(1.83 0 0 1.83 540 540)" >
<g style="" vector-effect="non-scaling-stroke" >
<g transform="matrix(1 0 0 1 -490 -490)" >
<rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,255,255); fill-opacity: 0; fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" x="-50" y="-50" rx="0" ry="0" width="100" height="100" />
</g>
<g transform="matrix(1 0 0 1 0 0)" >
<rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,255,255); fill-rule: nonzero; opacity: 1; visibility: hidden;" vector-effect="non-scaling-stroke" x="-540" y="-540" rx="0" ry="0" width="1080" height="1080" />
</g>
<g transform="matrix(1.01 0 0 1.01 0 0)" >
<path style="stroke: rgb(0,0,0); stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(52,164,254); fill-rule: nonzero; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-64, -256)" d="M 64 360 C 33.072054009475565 360 8 385.07205400947555 8 416 C 8 446.92794599052445 33.072054009475565 472 64 472 C 94.92794599052444 472 120 446.92794599052445 120 416 C 120 385.07205400947555 94.92794599052444 360 64 360 z M 64 200 C 33.072054009475565 200 8 225.07205400947555 8 256 C 8 286.92794599052445 33.072054009475565 312 64 312 C 94.92794599052444 312 120 286.92794599052445 120 256 C 120 225.07205400947555 94.92794599052444 200 64 200 z M 120 96 C 120 65.07205400947556 94.92794599052444 40 64 40 C 33.072054009475565 40 8 65.07205400947555 8 96 C 8 126.92794599052444 33.072054009475565 152 64 152 C 94.92794599052444 152 120 126.92794599052444 120 96 z" stroke-linecap="round" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -9,6 +9,7 @@ export class Project {
tester: string;
summary: string;
state: ReportState;
version: string;
projectPentests?: Array<ProjectPentests>;
testingProgress?: number;
createdBy: string;
@ -19,6 +20,7 @@ export class Project {
createdAt: Date,
tester: string,
state: ReportState,
version: string,
projectPentests?: Array<ProjectPentests>,
testingProgress?: number,
summary?: string,
@ -32,6 +34,7 @@ export class Project {
this.testingProgress = testingProgress;
this.summary = summary;
this.state = state;
this.version = version;
this.createdBy = createdBy;
}
}

View File

@ -35,6 +35,7 @@ import {Comment} from '@shared/models/comment.model';
import {ReportState} from '@shared/models/state.enum';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
allProjects: [],
selectedProject: {
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
client: 'E Corp',
@ -43,6 +44,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
tester: 'Novatester',
summary: '',
state: ReportState.NEW,
version: '1.0',
testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
},

View File

@ -1,15 +1,17 @@
<nb-card #dialog accent="{{dialogData?.options[0].accentColor}}" class="export-report-dialog">
<nb-card-header fxLayoutAlign="start center" class="export-report-header">
<nb-card-header fxLayout="row" fxLayoutAlign="start center" class="export-report-header">
{{ dialogData?.options[0].headerLabelKey | translate }}
</nb-card-header>
<nb-card-body>
<!--ToDo: fix ngIf to avoid rendering issue-->
<nb-card-body *ngIf="selectedEvaluatedProject$.getValue()">
<div fxLayout="column" fxLayoutGap="1rem" fxLayoutAlign="start start">
<label class="export-format-label">
{{ 'global.project' | translate }}:
</label>
<b class="project-title">
{{dialogData.options[0].additionalData.title}}
</b>
<div fxLayout="row" fxLayoutAlign="space-between center" class="project-title">
<span class="title">{{dialogData.options[0].additionalData.title}}</span>
<app-version-tag [version]="dialogData.options[0].additionalData?.version"></app-version-tag>
</div>
<!--Chart Objective Component-->
<div fxLayout="column" fxLayoutGap="2rem" fxLayoutAlign="center center">
<app-objective-chart

View File

@ -1,5 +1,6 @@
@import "../../../assets/@theme/styles/_dialog.scss";
@import '../../../assets/@theme/styles/themes';
@import '../../../assets/@theme/styles/_text-overflow.scss';
.export-report-dialog {
width: 45.25rem !important;
@ -44,8 +45,13 @@
}
.project-title {
font-size: 1.5rem;
padding: 0.25rem 0.5rem 0.5rem;
width: 100%;
.title {
@include multiLineEllipsis($font-size: 1.5rem, $font-weight: bold, $line-height: 1.5rem, $lines-to-show: 2, $max-width: 32rem);
}
}
.form-field {

View File

@ -104,6 +104,7 @@ const mockProject: Project = {
tester: 'Novatester',
summary: '',
state: ReportState.NEW,
version: '1.0',
projectPentests: mockedPentests,
testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'

View File

@ -13,7 +13,6 @@ import {PentestStatus} from '@shared/models/pentest-status.model';
import {shareReplay, tap} from 'rxjs/operators';
import {downloadFile} from '@shared/functions/download-file.function';
import {Loading, LoadingState} from '@shared/models/loading.model';
import {HttpEvent, HttpEventType} from '@angular/common/http';
import {DialogService} from '@shared/services/dialog-service/dialog.service';
@Component({

View File

@ -11,6 +11,7 @@ import {ExportReportDialogService} from '@shared/modules/export-report-dialog/se
import {ReportingService} from '@shared/services/reporting/reporting.service';
import {ObjectiveChartModule} from '@shared/modules/objective-chart/objective-chart.module';
import {LoadingBarModule} from '@shared/widgets/loading-bar/loading-bar.module';
import {VersionTagModule} from '@shared/widgets/version-tag/version-tag.module';
@NgModule({
declarations: [
@ -29,7 +30,8 @@ import {LoadingBarModule} from '@shared/widgets/loading-bar/loading-bar.module';
ReactiveFormsModule,
NbRadioModule,
ObjectiveChartModule,
LoadingBarModule
LoadingBarModule,
VersionTagModule
],
providers: [
ExportReportDialogService,

View File

@ -33,6 +33,7 @@ import {NgxsModule, Store} from '@ngxs/store';
import {ReportState} from '@shared/models/state.enum';
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
allProjects: [],
selectedProject: {
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
client: 'E Corp',
@ -41,6 +42,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
tester: 'Novatester',
summary: '',
state: ReportState.NEW,
version: '1.0',
testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
},

View File

@ -97,6 +97,7 @@ export const mockProject: Project = {
tester: 'Testpentester',
summary: 'Test',
state: ReportState.NEW,
version: '1.0',
createdAt: new Date(),
testingProgress: 0,
createdBy: 'UID-11-12-13'

View File

@ -44,6 +44,7 @@ describe('ProjectService', () => {
tester: 'Novatester',
summary: '',
state: ReportState.NEW,
version: '1.0',
testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
};
@ -97,6 +98,7 @@ describe('ProjectService', () => {
tester: 'Novatester',
summary: '',
state: ReportState.NEW,
version: '1.0',
testingProgress: 0,
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
};

View File

@ -14,6 +14,13 @@ export class InitProjectState {
}
}
export class SetAvailableProjects {
static readonly type = '[ProjectState] SetAvailableProjects';
constructor(public projects: Project[]) {
}
}
export class ChangeProject {
static readonly type = '[ProjectState] ChangeProject';

View File

@ -8,6 +8,7 @@ import {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/store
import {Category} from '@shared/models/category.model';
const INITIAL_PROJECT_STATE_SESSION: ProjectStateModel = {
allProjects: [],
selectedProject: null,
disabledCategories: [],
selectedCategory: Category.INFORMATION_GATHERING,
@ -16,6 +17,7 @@ const INITIAL_PROJECT_STATE_SESSION: ProjectStateModel = {
};
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
allProjects: [],
selectedProject: null,
disabledCategories: [],
selectedCategory: Category.INFORMATION_GATHERING,

View File

@ -5,7 +5,7 @@ import {
ChangeCategory,
ChangePentest,
ChangeProject,
InitProjectState,
InitProjectState, SetAvailableProjects,
UpdatePentestComments,
UpdatePentestFindings, UpdatePentestStatus,
UpdatePentestTime
@ -17,6 +17,7 @@ import {PentestStatus} from '@shared/models/pentest-status.model';
export const PROJECT_STATE_NAME = 'project';
export interface ProjectStateModel {
allProjects: Project[];
selectedProject: Project;
// Manages Categories
disabledCategories: Array<string>;
@ -33,6 +34,7 @@ export interface FindingMap {
@State<ProjectStateModel>({
name: PROJECT_STATE_NAME,
defaults: {
allProjects: [],
selectedProject: null,
disabledCategories: [],
selectedCategory: Category.INFORMATION_GATHERING,
@ -42,6 +44,11 @@ export interface FindingMap {
})
@Injectable()
export class ProjectState {
@Selector()
static allProjects(state: ProjectStateModel): Project[] {
return state.allProjects;
}
@Selector()
static project(state: ProjectStateModel): Project {
return state.selectedProject;
@ -60,6 +67,7 @@ export class ProjectState {
@Action(InitProjectState)
initProjectState(ctx: StateContext<ProjectStateModel>, action: InitProjectState): void {
ctx.setState({
allProjects: ctx.getState().allProjects,
selectedProject: action.project,
disabledCategories: action.disabledCategories,
selectedCategory: Category.INFORMATION_GATHERING,
@ -68,6 +76,15 @@ export class ProjectState {
});
}
@Action(SetAvailableProjects)
setAllAvailableProjects(ctx: StateContext<ProjectStateModel>, {projects}: SetAvailableProjects): void {
const state = ctx.getState();
// ToDo: Add logic to change selectedCategory if disabled
ctx.patchState({
allProjects: projects
});
}
@Action(ChangeProject)
changeProject(ctx: StateContext<ProjectStateModel>, {project}: ChangeProject): void {
const state = ctx.getState();

View File

@ -0,0 +1,64 @@
<nb-card class="project-card" accent="{{getProjectAccentFillStatus(project?.state)}}">
<nb-card-header fxLayoutAlign="start center"
class="project-link project-header"
(click)="onClickRouteToProject(project)">
<div fxLayout="row" fxLayoutAlign="space-between center">
<h4 class="header-title">{{project?.title}}</h4>
<span>
<app-report-state-tag class="state-tag" [currentReportState]="project?.state"></app-report-state-tag>
</span>
</div>
</nb-card-header>
<nb-card-body class="project-link"
(click)="onClickRouteToProject(project)">
<p class="project-subheader">
{{'project.client' | translate}}:
</p>
<span class="project-paragraph">
{{project?.client}}
</span>
<p class="project-subheader">
{{'project.tester' | translate}}:
</p>
<span class="project-paragraph">
{{project?.tester}}
</span>
<p class="project-subheader">
{{'project.createdAt' | translate}}:
</p>
<span class="project-paragraph">
{{project?.createdAt | dateTimeFormat}}
</span>
</nb-card-body>
<nb-card-footer>
<div fxLayout="row" fxLayoutGap="1rem" fxLayoutAlign="start end">
<div class="project-progress">
<nb-progress-bar *ngIf="project.testingProgress > 0; else altProgressBar"
status="warning"
[value]="project.testingProgress"
[displayValue]="true">
</nb-progress-bar>
<ng-template #altProgressBar>
{{'popup.info' | translate}} {{'global.no.progress' | translate}}
</ng-template>
</div>
<button nbButton
status="primary"
size="small"
class="project-button"
(click)="onClickEditProject(project)">
<fa-icon [icon]="fa.faPencilAlt"></fa-icon>
</button>
<button nbButton
status="danger"
size="small"
class="project-button"
(click)="onClickDeleteProject(project)">
<fa-icon [icon]="fa.faTrash"></fa-icon>
</button>
</div>
</nb-card-footer>
</nb-card>

View File

@ -0,0 +1,68 @@
@import '../../../assets/@theme/styles/themes';
@import '../../../assets/@theme/styles/variables';
@import '../../../assets/@theme/styles/_text-overflow.scss';
.project-card {
max-width: 24rem;
width: 24rem;
min-width: 24rem;
max-height: 100%;
height: 100%;
min-height: 100%;
.project-header {
max-height: 8rem;
height: 8rem;
min-height: 6rem;
.header-title {
@include multiLineEllipsis($font-size: 1.5rem, $font-weight: bold, $line-height: 1.5rem, $lines-to-show: 2, $max-width: 14rem);
}
.state-tag {
width: 1rem;
// Align status
margin-left: 1rem;
margin-right: 1rem;
}
}
.project-subheader {
font-size: 1.25rem;
font-weight: bold;
}
.project-paragraph {
font-size: 1.15rem;
font-style: italic;
}
.project-progress {
max-width: 65%;
width: 65%;
min-width: 65%;
}
.project-button {
height: 1.425rem;
}
&.accent {
// Changes accent width only
// border-width: 0.5rem 0 0 0rem;
border-width: 0.5rem 0 0 0.25rem;
}
}
.project-card:hover {
background-color: nb-theme(color-basic-transparent-focus);
// Increases element size on hover
// Decreases usability which is why it is commented out
margin-top: +0.625rem;
transform: scale(1.025);
border-width: 0.5rem 0 0 0.45rem;
}
.project-link:hover {
cursor: pointer !important;
}

View File

@ -0,0 +1,78 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ProjectWidgetComponent } from './project-widget.component';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {HttpLoaderFactory} from '../../../app/common-app.module';
import {HttpClient} from '@angular/common/http';
import {RouterTestingModule} from '@angular/router/testing';
import {NbButtonModule, NbCardModule, NbProgressBarModule} from '@nebular/theme';
import {MockPipe} from 'ng-mocks';
import {DateTimeFormatPipe} from '@shared/pipes/date-time-format.pipe';
import {ProjectService} from '@shared/services/api/project.service';
import {ProjectServiceMock} from '@shared/services/api/project.service.mock';
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
import {ProjectDialogServiceMock} from '@shared/modules/project-dialog/service/project-dialog.service.mock';
import {DialogService} from '@shared/services/dialog-service/dialog.service';
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
import {NotificationService} from '@shared/services/toaster-service/notification.service';
import {NotificationServiceMock} from '@shared/services/toaster-service/notification.service.mock';
import {FlexLayoutModule} from '@angular/flex-layout';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {NgxsModule} from '@ngxs/store';
import {SessionState} from '@shared/stores/session-state/session-state';
import {KeycloakService} from 'keycloak-angular';
import {ThemeModule} from '@assets/@theme/theme.module';
describe('ProjectWidgetComponent', () => {
let component: ProjectWidgetComponent;
let fixture: ComponentFixture<ProjectWidgetComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
ProjectWidgetComponent,
MockPipe(DateTimeFormatPipe)
],
imports: [
ThemeModule.forRoot(),
NbProgressBarModule,
NbCardModule,
NbButtonModule,
FlexLayoutModule,
BrowserAnimationsModule,
FontAwesomeModule,
HttpClientTestingModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
}),
RouterTestingModule.withRoutes([]),
NgxsModule.forRoot([SessionState])
],
providers: [
KeycloakService,
{provide: ProjectService, useValue: new ProjectServiceMock()},
{provide: ProjectDialogService, useClass: ProjectDialogServiceMock},
{provide: DialogService, useClass: DialogServiceMock},
{provide: NotificationService, useClass: NotificationServiceMock}
]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ProjectWidgetComponent);
component = fixture.componentInstance;
// ToDo: fix detectChanges() when project input is defined
// fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,182 @@
import {Component, Input, OnInit} from '@angular/core';
import {Project} from '@shared/models/project.model';
import * as FA from '@fortawesome/free-solid-svg-icons';
import {ReportState} from '@shared/models/state.enum';
import {Route} from '@shared/models/route.enum';
import {ChangePentest, InitProjectState, SetAvailableProjects} from '@shared/stores/project-state/project-state.actions';
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
import {filter, tap} from 'rxjs/operators';
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.service';
import {Store} from '@ngxs/store';
import {Router} from '@angular/router';
import {ProjectService} from '@shared/services/api/project.service';
import {DialogService} from '@shared/services/dialog-service/dialog.service';
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
@Component({
selector: 'app-project-widget',
templateUrl: './project-widget.component.html',
styleUrls: ['./project-widget.component.scss']
})
@UntilDestroy()
export class ProjectWidgetComponent implements OnInit {
@Input() project: Project;
// HTML only
readonly fa = FA;
constructor(
private readonly notificationService: NotificationService,
private store: Store,
private router: Router,
private projectService: ProjectService,
private dialogService: DialogService,
private projectDialogService: ProjectDialogService
) {
}
ngOnInit(): void {
}
onClickRouteToProject(project): void {
this.router.navigate([Route.OBJECTIVE_OVERVIEW]).then(() => {
this.store.dispatch(new InitProjectState(
project,
[],
[]
)).pipe(untilDestroyed(this)).subscribe();
}, err => {
console.error(err);
});
}
/**
* HTML only
* @return the correct nb-accent for current report state of the project
*/
getProjectAccentFillStatus(value: any): string {
let reportStateFillStatus;
const statusValue = typeof value !== 'number' ? ReportState[value] : value;
// Check for correct accent color of status
switch (statusValue) {
case 6:
case 7: {
reportStateFillStatus = 'success';
break;
}
case 0: {
reportStateFillStatus = 'info';
break;
}
case 8:
case 9:
case 11:
case 12: {
reportStateFillStatus = 'warning';
break;
}
case 1:
case 10: {
reportStateFillStatus = 'danger';
break;
}
default: {
reportStateFillStatus = 'control';
break;
}
}
return reportStateFillStatus;
}
onClickEditProject(project: Project): void {
this.projectDialogService.openProjectDialog(
ProjectDialogComponent,
project,
{
closeOnEsc: false,
hasScroll: false,
autoFocus: true,
closeOnBackdropClick: false
}
).pipe(
untilDestroyed(this)
).subscribe({
next: () => {
// ToDo: Find way to edit / delete the single project from project store instead
this.store.dispatch(new SetAvailableProjects([]));
}
});
}
onClickDeleteProject(project: Project): void {
// Set dialog message
const message = {
title: 'project.delete.title',
key: 'project.delete.key',
data: {name: project.title},
} as any;
// Check if project is empty
if (project.testingProgress === 0) {
this.dialogService.openConfirmDialog(
message
).onClose.pipe(
filter((confirm) => !!confirm),
untilDestroyed(this)
).subscribe({
next: () => {
this.deleteProject(project);
}
});
} else {
const secMessage = {
title: 'project.delete.title',
key: 'project.delete.sec.key',
confirmString: project.title.toString(),
inputPlaceholderKey: 'project.delete.confirmStringPlaceholder',
data: {name: project.title, confirmString: project.title.toString()},
} as any;
// Set confirm string
// message.data.confirmString = project.title;
this.dialogService.openSecurityConfirmDialog(
secMessage
).onClose.pipe(
filter((confirm) => !!confirm),
untilDestroyed(this)
).subscribe({
next: () => {
this.deleteProject(project);
}
});
}
}
private deleteProject(project: Project): void {
this.projectService.deleteProjectById(project.id).pipe(
untilDestroyed(this)
).subscribe({
next: () => {
// ToDo: Find way to edit / delete the single project from project store instead
this.store.dispatch(new SetAvailableProjects([]));
this.notificationService.showPopup('project.popup.delete.success', PopupType.SUCCESS);
}, error: error => {
this.notificationService.showPopup('project.popup.delete.failed', PopupType.FAILURE);
this.onRequestFailed(project);
console.error(error);
}
});
}
private onRequestFailed(retryParameter: any): void {
this.dialogService.openRetryDialog({key: 'global.retry.dialog', data: null}).onClose
.pipe(
untilDestroyed(this)
)
.subscribe((ref) => {
if (ref.retry) {
this.deleteProject(retryParameter);
}
});
}
}

View File

@ -0,0 +1,31 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {ProjectWidgetComponent} from '@shared/widgets/project-widget/project-widget.component';
import {ReportStateTagModule} from '@shared/widgets/report-state-tag/report-state-tag.module';
import {NbButtonModule, NbCardModule, NbProgressBarModule} from '@nebular/theme';
import {TranslateModule} from '@ngx-translate/core';
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
import {DateTimeFormatPipe} from '@shared/pipes/date-time-format.pipe';
import {FlexLayoutModule} from '@angular/flex-layout';
@NgModule({
declarations: [
ProjectWidgetComponent,
DateTimeFormatPipe
],
imports: [
CommonModule,
ReportStateTagModule,
NbCardModule,
NbButtonModule,
NbProgressBarModule,
TranslateModule,
FlexLayoutModule,
FontAwesomeModule
],
exports: [
DateTimeFormatPipe,
ProjectWidgetComponent
]
})
export class ProjectWidgetModule { }

View File

@ -0,0 +1,3 @@
<span fxFlex="max-content">
<nb-tag status="basic" appearance="outline" class="version-tag" text="{{'global.version' | translate}} {{version}}"></nb-tag>
</span>

View File

@ -0,0 +1,3 @@
.version-tag {
font-family: Courier, serif !important;
}

View File

@ -0,0 +1,44 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {VersionTagComponent} from './version-tag.component';
import {MockModule} from 'ng-mocks';
import {NbTagModule} from '@nebular/theme';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {HttpLoaderFactory} from '../../../app/common-app.module';
import {HttpClient} from '@angular/common/http';
import {HttpClientTestingModule} from '@angular/common/http/testing';
describe('VersionTagComponent', () => {
let component: VersionTagComponent;
let fixture: ComponentFixture<VersionTagComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
VersionTagComponent
],
imports: [
HttpClientTestingModule,
MockModule(NbTagModule),
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
})
]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(VersionTagComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,17 @@
import {Component, Input, OnInit} from '@angular/core';
@Component({
selector: 'app-version-tag',
templateUrl: './version-tag.component.html',
styleUrls: ['./version-tag.component.scss']
})
export class VersionTagComponent implements OnInit {
@Input() version = '';
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,22 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {VersionTagComponent} from '@shared/widgets/version-tag/version-tag.component';
import {NbTagModule} from '@nebular/theme';
import {TranslateModule} from '@ngx-translate/core';
import {FlexLayoutModule} from '@angular/flex-layout';
@NgModule({
declarations: [
VersionTagComponent
],
imports: [
CommonModule,
NbTagModule,
TranslateModule,
FlexLayoutModule
],
exports: [
VersionTagComponent
]
})
export class VersionTagModule { }

View File

@ -22,6 +22,7 @@ data class Project(
val tester: String,
val summary: String? = null,
val state: PentestState,
val version: String,
var projectPentests: List<ProjectPentest> = emptyList(),
val createdBy: String
)
@ -35,6 +36,7 @@ fun buildProject(body: ProjectRequestBody, projectEntity: ProjectEntity): Projec
tester = body.tester,
summary = body.summary,
state = body.state,
version = projectEntity.data.version,
projectPentests = projectEntity.data.projectPentests,
createdBy = projectEntity.data.createdBy
)
@ -49,6 +51,7 @@ fun Project.toProjectResponseBody(): ResponseBody {
"tester" to tester,
"summary" to summary,
"state" to state,
"version" to version,
/* ToDo: Calculate percentage in BE type: float */
"testingProgress" to calculateProgress(),
"createdBy" to createdBy
@ -64,6 +67,7 @@ fun Project.toProjectCompletedPentestResponseBody(): ResponseBody {
"createdAt" to createdAt,
"tester" to tester,
"summary" to summary,
"version" to version,
"projectPentests" to projectPentests.filter { pentest -> pentest.status == PentestStatus.COMPLETED },
"createdBy" to createdBy
)
@ -78,6 +82,7 @@ fun Project.toProjectEvaluatedPentestResponseBody(): ResponseBody {
"createdAt" to createdAt,
"tester" to tester,
"summary" to summary,
"version" to version,
"projectPentests" to projectPentests,
"createdBy" to createdBy
)
@ -150,6 +155,8 @@ fun ProjectRequestBody.toProject(): Project {
tester = this.tester,
summary = this.summary,
state = this.state,
// ToDo: Update version in backend automatically
version = "1.0",
// ToDo: Should be changed to SUB from Token after adding AUTH Header
createdBy = UUID.randomUUID().toString()
)

View File

@ -20,6 +20,7 @@ fun ProjectEntity.toProject() : Project {
this.data.tester,
this.data.summary,
this.data.state,
this.data.version,
this.data.projectPentests,
this.data.createdBy
)

View File

@ -258,6 +258,7 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() {
tester = "Novatester",
projectPentests = emptyList(),
state = PentestState.NEW,
version = "1.0",
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
)
// Pentests

View File

@ -173,6 +173,7 @@ class PentestControllerIntTest : BaseIntTest() {
createdAt = "2021-01-10T18:05:00Z",
tester = "Novatester",
state = PentestState.NEW,
version = "1.0",
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
)
// pentests

View File

@ -284,6 +284,7 @@ class CommentControllerDocumentationTest : BaseDocumentationIntTest() {
tester = "Novatester",
projectPentests = emptyList(),
state = PentestState.NEW,
version = "1.0",
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
)
// Pentests

View File

@ -181,6 +181,7 @@ class CommentControllerIntTest : BaseIntTest() {
createdAt = "2021-01-10T18:05:00Z",
tester = "Novatester",
state = PentestState.NEW,
version = "1.0",
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
)
// pentests

View File

@ -342,6 +342,7 @@ class FindingControllerDocumentationTest: BaseDocumentationIntTest() {
tester = "Novatester",
projectPentests = emptyList(),
state = PentestState.NEW,
version = "1.0",
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
)
// Pentests

View File

@ -209,6 +209,7 @@ class FindingControllerIntTest: BaseIntTest() {
createdAt = "2021-01-10T18:05:00Z",
tester = "Novatester",
state = PentestState.NEW,
version = "1.0",
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
)
// pentests

View File

@ -77,6 +77,8 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
.description("The summary of the requested project"),
PayloadDocumentation.fieldWithPath("[].state").type(JsonFieldType.STRING)
.description("The state of the requested project pentest"),
PayloadDocumentation.fieldWithPath("[].version").type(JsonFieldType.STRING)
.description("The version of the requested project"),
PayloadDocumentation.fieldWithPath("[].createdBy").type(JsonFieldType.STRING)
.description("The id of the user that created the project"),
PayloadDocumentation.fieldWithPath("[].testingProgress").type(JsonFieldType.NUMBER)
@ -95,6 +97,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
summary = "Lorem Ipsum",
projectPentests = emptyList<ProjectPentest>(),
state = PentestState.NEW,
version = "1.0",
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
)
val projectTwo = Project(
@ -106,6 +109,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
summary = "Lorem Ipsum",
projectPentests = emptyList<ProjectPentest>(),
state = PentestState.NEW,
version = "1.0",
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
)
@ -150,6 +154,8 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
.description("The user that is assigned as a tester in the project"),
PayloadDocumentation.fieldWithPath("state").type(JsonFieldType.STRING)
.description("The state of the requested project pentest"),
PayloadDocumentation.fieldWithPath("version").type(JsonFieldType.STRING)
.description("The version of the requested project"),
PayloadDocumentation.fieldWithPath("createdBy").type(JsonFieldType.STRING)
.description("The id of the user that created the project"),
PayloadDocumentation.fieldWithPath("testingProgress").type(JsonFieldType.NUMBER)
@ -238,6 +244,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
tester = "Novatester",
projectPentests = emptyList<ProjectPentest>(),
state = PentestState.NEW,
version = "1.0",
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
)
}
@ -279,6 +286,8 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
.description("The summary of the requested project"),
PayloadDocumentation.fieldWithPath("state").type(JsonFieldType.STRING)
.description("The state of the requested project pentest"),
PayloadDocumentation.fieldWithPath("version").type(JsonFieldType.STRING)
.description("The version of the requested project"),
PayloadDocumentation.fieldWithPath("createdBy").type(JsonFieldType.STRING)
.description("The id of the user that created the project"),
PayloadDocumentation.fieldWithPath("testingProgress").type(JsonFieldType.NUMBER)
@ -304,6 +313,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
tester = "Stipe_updated",
summary = "",
state = PentestState.NEW,
version = "1.0",
projectPentests = emptyList<ProjectPentest>(),
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
)
@ -319,6 +329,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
tester = "Novatester",
summary = "Lorem Ipsum",
state = PentestState.NEW,
version = "1.0",
projectPentests = emptyList<ProjectPentest>(),
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
)
@ -330,6 +341,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
tester = "Elliot",
summary = "Lorem Ipsum",
state = PentestState.NEW,
version = "1.0",
projectPentests = emptyList<ProjectPentest>(),
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
)

View File

@ -73,6 +73,7 @@ class ProjectControllerIntTest : BaseIntTest() {
tester = "Novatester",
summary = "Lorem Ipsum",
state = PentestState.NEW,
version = "1.0",
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
)
val projectTwo = Project(
@ -83,6 +84,7 @@ class ProjectControllerIntTest : BaseIntTest() {
tester = "Elliot",
summary = "Lorem Ipsum",
state = PentestState.NEW,
version = "1.0",
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
)
@ -119,6 +121,7 @@ class ProjectControllerIntTest : BaseIntTest() {
tester = "Stipe",
summary = "",
state = PentestState.NEW,
version = "1.0",
createdBy = "a8891ad2-5cf5-4519-a89e-9ef8eec9e10c"
)
}
@ -153,6 +156,7 @@ class ProjectControllerIntTest : BaseIntTest() {
createdAt = "2021-01-10T18:05:00Z",
tester = "Elliot",
state = PentestState.NEW,
version = "1.0",
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
)
}
@ -180,6 +184,7 @@ class ProjectControllerIntTest : BaseIntTest() {
createdAt = "2021-04-10T18:05:00Z",
tester = "Stipe_updated",
state = PentestState.NEW,
version = "1.0",
createdBy = "a8891ad2-5cf5-4519-a89e-9ef8eec9e10c"
)
}
@ -194,6 +199,7 @@ class ProjectControllerIntTest : BaseIntTest() {
tester = "Novatester",
summary = "Lorem Ipsum",
state = PentestState.NEW,
version = "1.0",
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
)
val projectTwo = Project(
@ -204,6 +210,7 @@ class ProjectControllerIntTest : BaseIntTest() {
tester = "Elliot",
summary = "Lorem Ipsum",
state = PentestState.NEW,
version = "1.0",
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
)
// persist test data in database

View File

@ -9,6 +9,7 @@ data class ProjectReport(
val createdAt: Date,
val tester: String,
val summary: String? = null,
val version: String,
var projectPentestReport: MutableList<PentestReport> = mutableListOf<PentestReport>(),
val createdBy: String
)

View File

@ -5,7 +5,6 @@ import com.securityc4po.reporting.remote.model.ProjectReport
import java.time.Instant
import java.util.Date
data class Project(
val id: String,
val client: String,
@ -13,6 +12,7 @@ data class Project(
val createdAt: String,
val tester: String,
val summary: String? = "",
val version: String,
var projectPentests: List<ProjectPentest>? = emptyList(),
val createdBy: String
)
@ -26,6 +26,7 @@ fun Project.toProjectReport(): ProjectReport {
createdAt = Date.from(Instant.now()),
tester = this.tester,
summary = this.summary,
version = this.version,
projectPentestReport = mutableListOf<PentestReport>(),
createdBy = this.createdBy
)

View File

@ -39,6 +39,7 @@
<property name="net.sf.jasperreports.json.field.expression" value="createdBy"/>
<fieldDescription><![CDATA[createdBy]]></fieldDescription>
</field>
<field name="version" class="java.lang.String"/>
<background>
<band splitType="Stretch"/>
</background>
@ -149,23 +150,23 @@
</band>
</detail>
<columnFooter>
<band height="70" splitType="Stretch">
<band height="76" splitType="Stretch">
<rectangle>
<reportElement x="-20" y="30" width="595" height="30" forecolor="#232B44" backcolor="#232B44" uuid="1ed47e2d-9d46-44b9-bad7-8eeb1143c83c"/>
<graphicElement>
<pen lineWidth="1.0"/>
</graphicElement>
</rectangle>
<staticText>
<reportElement mode="Opaque" x="0" y="35" width="551" height="20" forecolor="#FEFEFF" backcolor="#232B44" uuid="fc82cf8c-f284-413c-9c49-ad924c765c48"/>
<textElement textAlignment="Right" verticalAlignment="Middle">
<font fontName="SansSerif&#xA;&#xA;" size="12" isBold="false" isItalic="true"/>
</textElement>
<text><![CDATA[Version 1.0]]></text>
</staticText>
<rectangle>
<reportElement x="-20" y="20" width="595" height="10" forecolor="#151B2E" backcolor="#151B2E" uuid="b9a5cd43-3460-4177-97f5-a46eac874e7d"/>
</rectangle>
<textField>
<reportElement x="380" y="35" width="174" height="20" forecolor="#FFFFFF" uuid="0aa9c401-c73c-4a7e-b5a2-b650d333093f"/>
<textElement textAlignment="Right" verticalAlignment="Middle">
<font size="12" isItalic="true"/>
</textElement>
<textFieldExpression><![CDATA["Version " + $F{version}]]></textFieldExpression>
</textField>
</band>
</columnFooter>
<pageFooter>

View File

@ -105,6 +105,7 @@
<property name="net.sf.jasperreports.json.field.expression" value="createdBy"/>
<fieldDescription><![CDATA[createdBy]]></fieldDescription>
</field>
<field name="version" class="java.lang.String"/>
<group name="id">
<groupExpression><![CDATA[$F{id}]]></groupExpression>
</group>

View File

@ -33,6 +33,7 @@
<property name="net.sf.jasperreports.json.field.expression" value="createdBy"/>
<fieldDescription><![CDATA[createdBy]]></fieldDescription>
</field>
<field name="version" class="java.lang.String"/>
<background>
<band splitType="Stretch"/>
</background>