feat: As a user I want to add the project version
This commit is contained in:
parent
9e4fa27b92
commit
07c6871294
|
@ -36,6 +36,9 @@
|
|||
.logo-container {
|
||||
font-style: oblique;
|
||||
color: #e74c3c;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
nb-action {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
@import '../../../assets/@theme/styles/themes';
|
||||
|
||||
.pentest-categories {
|
||||
width: 22vw;
|
||||
width: 20rem;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 {
|
||||
}
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
},
|
||||
|
|
|
@ -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'
|
||||
},
|
||||
|
|
|
@ -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'
|
||||
},
|
||||
|
|
|
@ -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'
|
||||
},
|
||||
|
|
|
@ -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 }}"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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'
|
||||
},
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
.table-wrapper{
|
||||
padding-right: 0 !important;
|
||||
border-style: none;
|
||||
|
||||
.table-column {
|
||||
overflow: auto !important;
|
||||
|
|
|
@ -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'
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 |
|
@ -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 |
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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'
|
||||
},
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
};
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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 { }
|
|
@ -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>
|
|
@ -0,0 +1,3 @@
|
|||
.version-tag {
|
||||
font-family: Courier, serif !important;
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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 {
|
||||
}
|
||||
|
||||
}
|
|
@ -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 { }
|
|
@ -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()
|
||||
)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -258,6 +258,7 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() {
|
|||
tester = "Novatester",
|
||||
projectPentests = emptyList(),
|
||||
state = PentestState.NEW,
|
||||
version = "1.0",
|
||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||
)
|
||||
// Pentests
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -284,6 +284,7 @@ class CommentControllerDocumentationTest : BaseDocumentationIntTest() {
|
|||
tester = "Novatester",
|
||||
projectPentests = emptyList(),
|
||||
state = PentestState.NEW,
|
||||
version = "1.0",
|
||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||
)
|
||||
// Pentests
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -342,6 +342,7 @@ class FindingControllerDocumentationTest: BaseDocumentationIntTest() {
|
|||
tester = "Novatester",
|
||||
projectPentests = emptyList(),
|
||||
state = PentestState.NEW,
|
||||
version = "1.0",
|
||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||
)
|
||||
// Pentests
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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

" 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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue