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

@ -6,7 +6,7 @@
</ng-template>
<div class="logo-container">
<h1 >{{SECURITYC4PO_TITLE}} </h1>
<h1>{{SECURITYC4PO_TITLE}} </h1>
</div>
<div class="filler"></div>
<div fxLayoutGap="4rem">

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,13 +49,15 @@ export class ObjectiveCategoriesComponent implements OnInit, OnDestroy {
untilDestroyed(this)
)
.subscribe((menuBag) => {
this.selectedCategory = menuBag.item.data;
this.categories.forEach(category => {
category.selected = false;
});
if (this.selectedCategory >= 0) {
menuBag.item.selected = true;
this.store.dispatch(new ChangeCategory(this.selectedCategory));
if (menuBag.tag === 'menu') {
this.selectedCategory = menuBag.item.data;
this.categories.forEach(category => {
category.selected = false;
});
if (this.selectedCategory >= 0) {
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,13 +128,15 @@ export class ObjectiveHeaderComponent implements OnInit {
untilDestroyed(this)
).subscribe({
next: (project) => {
this.store.dispatch(new InitProjectState(
project,
[],
[]
)).pipe(
untilDestroyed(this)
).subscribe();
if (project) {
this.store.dispatch(new InitProjectState(
project,
[],
[]
)).pipe(
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: [
@ -33,33 +34,36 @@ import {ReportStateTagModule} from '@shared/widgets/report-state-tag/report-stat
ObjectiveCategoriesComponent,
ObjectiveTableComponent,
],
imports: [
CommonModule,
CommonAppModule,
NbLayoutModule,
NbCardModule,
NbButtonModule,
// nbTooltip crashes app right now if used in component,
// workaround: use title in html for now
NbTooltipModule,
NbTreeGridModule,
TranslateModule,
StatusTagModule,
RouterModule,
FormsModule,
NbListModule,
FontAwesomeModule,
FlexLayoutModule,
NbActionsModule,
ExportReportDialogModule,
ProjectDialogModule,
ObjectiveOverviewRoutingModule,
// Table Widgets
FindigWidgetModule,
CommentWidgetModule,
NbMenuModule,
ReportStateTagModule
],
imports: [
CommonModule,
CommonAppModule,
NbLayoutModule,
NbCardModule,
NbButtonModule,
// nbTooltip crashes app right now if used in component,
// workaround: use title in html for now
NbTooltipModule,
NbTreeGridModule,
TranslateModule,
StatusTagModule,
RouterModule,
FormsModule,
NbListModule,
FontAwesomeModule,
FlexLayoutModule,
NbActionsModule,
ExportReportDialogModule,
ProjectDialogModule,
ObjectiveOverviewRoutingModule,
// Table Widgets
FindigWidgetModule,
CommentWidgetModule,
NbMenuModule,
ReportStateTagModule,
VersionTagModule,
NbUserModule,
NbContextMenuModule
],
exports: [
ObjectiveHeaderComponent,
ObjectiveCategoriesComponent,

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,8 +54,12 @@
margin: 0.5rem 2.25rem 1rem 0;
}
.action-element-text {
padding-left: 0.5rem;
.complete-pentest-button {
// position: absolute;
.action-element-text {
padding-left: 0.5rem;
}
}
.pentest-status-dialog {

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
<fa-icon nbPrefix class="search-prefix-icon" [icon]="fa.faSearch"></fa-icon>
<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,22 +1,29 @@
@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 {
color:nb-theme(color-info-default);
.search-field:active {
color: nb-theme(color-info-default);
// opacity: initial;
}
.search-prefix-icon:active {
color: nb-theme(color-info-default);
}
}
@ -40,7 +47,7 @@
position: fixed;
right: 1.5rem;
.add-project-button {
.add-project-button {
// align-content: flex-end;
margin: 6rem 2rem 6rem 0;
@ -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[]) => {
this.projects$.next(projects);
this.allProjects$.next(projects);
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">
{{ dialogData?.options[0].headerLabelKey | translate }}
<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>
@ -70,7 +71,7 @@
<textElement>
<font size="12"/>
</textElement>
<textFieldExpression><![CDATA["The contents of this document have been developed by " + $F{tester} + ". " + $F{tester} + " considers the contents of this document to be proprietary and business confidential information. This information is to be used only in the performance of its intended use. This document may not be released to another vendor, business partner or contractor without prior written consent from " + $F{tester} + ". Additionally, no portion of this document may be communicated, reproduced, copied or distributed without the prior consent of " + $F{tester} + "." ]]></textFieldExpression>
<textFieldExpression><![CDATA["The contents of this document have been developed by " + $F{tester} + ". " + $F{tester} + " considers the contents of this document to be proprietary and business confidential information. This information is to be used only in the performance of its intended use. This document may not be released to another vendor, business partner or contractor without prior written consent from " + $F{tester} + ". Additionally, no portion of this document may be communicated, reproduced, copied or distributed without the prior consent of " + $F{tester} + "."]]></textFieldExpression>
</textField>
<textField textAdjust="StretchHeight">
<reportElement positionType="Float" x="0" y="100" width="551" height="100" uuid="6b5626a8-ff7b-450b-8c53-93c2115bd56c"/>