feat: As a user I want to add the project version
This commit is contained in:
parent
9e4fa27b92
commit
07c6871294
|
@ -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">
|
||||
|
|
|
@ -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,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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,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 {
|
||||
|
|
|
@ -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
|
||||
<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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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[]) => {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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">
|
||||
{{ 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
|
||||
|
|
|
@ -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>
|
||||
|
@ -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"/>
|
||||
|
|
Loading…
Reference in New Issue