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>
|
</ng-template>
|
||||||
|
|
||||||
<div class="logo-container">
|
<div class="logo-container">
|
||||||
<h1 >{{SECURITYC4PO_TITLE}} </h1>
|
<h1>{{SECURITYC4PO_TITLE}} </h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="filler"></div>
|
<div class="filler"></div>
|
||||||
<div fxLayoutGap="4rem">
|
<div fxLayoutGap="4rem">
|
||||||
|
|
|
@ -36,6 +36,9 @@
|
||||||
.logo-container {
|
.logo-container {
|
||||||
font-style: oblique;
|
font-style: oblique;
|
||||||
color: #e74c3c;
|
color: #e74c3c;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
nb-action {
|
nb-action {
|
||||||
|
|
|
@ -34,7 +34,12 @@ export class HeaderComponent implements OnInit {
|
||||||
// User Menu Properties
|
// User Menu Properties
|
||||||
userPictureOnly = false;
|
userPictureOnly = false;
|
||||||
user: BehaviorSubject<User> = new BehaviorSubject<User>(null);
|
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';
|
readonly FALLBACK_IMG = 'assets/images/demo/anon-user-icon.png';
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -67,7 +72,7 @@ export class HeaderComponent implements OnInit {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Handle user profile manu selection
|
// Handle user profile menu selection
|
||||||
this.menuService.onItemClick()
|
this.menuService.onItemClick()
|
||||||
.pipe(
|
.pipe(
|
||||||
untilDestroyed(this)
|
untilDestroyed(this)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
@import '../../../assets/@theme/styles/themes';
|
@import '../../../assets/@theme/styles/themes';
|
||||||
|
|
||||||
.pentest-categories {
|
.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 {ProjectState} from '@shared/stores/project-state/project-state';
|
||||||
import {catchError, switchMap, tap} from 'rxjs/operators';
|
import {catchError, switchMap, tap} from 'rxjs/operators';
|
||||||
import {Pentest, transformPentestsToObjectiveEntries} from '@shared/models/pentest.model';
|
import {Pentest, transformPentestsToObjectiveEntries} from '@shared/models/pentest.model';
|
||||||
|
import {UntilDestroy} from '@ngneat/until-destroy';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-objective-categories',
|
selector: 'app-objective-categories',
|
||||||
templateUrl: './objective-categories.component.html',
|
templateUrl: './objective-categories.component.html',
|
||||||
styleUrls: ['./objective-categories.component.scss']
|
styleUrls: ['./objective-categories.component.scss']
|
||||||
})
|
})
|
||||||
export class ObjectiveCategoriesComponent implements OnInit, OnDestroy {
|
@UntilDestroy()
|
||||||
|
export class ObjectiveCategoriesComponent implements OnInit {
|
||||||
categories: NbMenuItem[] = [];
|
categories: NbMenuItem[] = [];
|
||||||
selectedCategory: Category = 0;
|
selectedCategory: Category = 0;
|
||||||
|
|
||||||
private destroy$ = new Subject<void>();
|
|
||||||
|
|
||||||
constructor(private store: Store,
|
constructor(private store: Store,
|
||||||
private menuService: NbMenuService,
|
private menuService: NbMenuService,
|
||||||
private translateService: TranslateService) {
|
private translateService: TranslateService) {
|
||||||
|
@ -49,13 +49,15 @@ export class ObjectiveCategoriesComponent implements OnInit, OnDestroy {
|
||||||
untilDestroyed(this)
|
untilDestroyed(this)
|
||||||
)
|
)
|
||||||
.subscribe((menuBag) => {
|
.subscribe((menuBag) => {
|
||||||
this.selectedCategory = menuBag.item.data;
|
if (menuBag.tag === 'menu') {
|
||||||
this.categories.forEach(category => {
|
this.selectedCategory = menuBag.item.data;
|
||||||
category.selected = false;
|
this.categories.forEach(category => {
|
||||||
});
|
category.selected = false;
|
||||||
if (this.selectedCategory >= 0) {
|
});
|
||||||
menuBag.item.selected = true;
|
if (this.selectedCategory >= 0) {
|
||||||
this.store.dispatch(new ChangeCategory(this.selectedCategory));
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<app-report-state-tag class="state-tag" [currentReportState]="selectedProject$.getValue()?.state"></app-report-state-tag>
|
<div class="header-info" fxLayout="row" fxLayoutGap="4rem" fxLayoutAlign="space-between center">
|
||||||
<h4>{{selectedProject$.getValue().title}}</h4>
|
<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">
|
<div class="button-container">
|
||||||
<nb-actions size="medium">
|
<!--Actions for normal view-->
|
||||||
|
<nb-actions size="medium" fxHide.lt-lg>
|
||||||
<nb-action>
|
<nb-action>
|
||||||
<!--status="button-outline-basic-text-color"-->
|
|
||||||
<button nbButton
|
<button nbButton
|
||||||
status="primary"
|
status="primary"
|
||||||
shape="round"
|
shape="round"
|
||||||
(click)="onClickEditPentestProject()">
|
(click)="onClickEditPentestProject()">
|
||||||
<fa-icon [icon]="fa.faEdit"
|
<fa-icon [icon]="fa.faEdit"
|
||||||
class="element-icon fa-lg"></fa-icon>
|
class="element-icon fa-lg"></fa-icon>
|
||||||
<!--<span class="element-text">{{ 'global.action.edit' | translate }}</span>-->
|
|
||||||
</button>
|
</button>
|
||||||
</nb-action>
|
</nb-action>
|
||||||
<nb-action>
|
<nb-action>
|
||||||
|
@ -36,5 +39,11 @@
|
||||||
</button>
|
</button>
|
||||||
</nb-action>
|
</nb-action>
|
||||||
</nb-actions>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,16 +1,30 @@
|
||||||
|
@import '../../../assets/@theme/styles/_text-overflow.scss';
|
||||||
|
|
||||||
.pentest-header {
|
.pentest-header {
|
||||||
width: calc(100vw - 14%);
|
width: 100vw;
|
||||||
|
|
||||||
.back-button-container {
|
.back-button-container {
|
||||||
.back-element-icon {
|
.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 {
|
.button-container {
|
||||||
display: flex;
|
position: absolute;
|
||||||
align-content: flex-end;
|
right: 2rem;
|
||||||
// ToDo: Fix so that longer / shorter name won't change needed margin
|
|
||||||
// margin-right: 2.25rem;
|
|
||||||
|
|
||||||
.element-icon {
|
.element-icon {
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {RouterTestingModule} from '@angular/router/testing';
|
||||||
import {NgxsModule, Store} from '@ngxs/store';
|
import {NgxsModule, Store} from '@ngxs/store';
|
||||||
import {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/stores/project-state/project-state';
|
import {PROJECT_STATE_NAME, ProjectState, ProjectStateModel} from '@shared/stores/project-state/project-state';
|
||||||
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
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 {ProjectService} from '@shared/services/api/project.service';
|
||||||
import {ProjectServiceMock} from '@shared/services/api/project.service.mock';
|
import {ProjectServiceMock} from '@shared/services/api/project.service.mock';
|
||||||
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
|
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
|
||||||
|
@ -27,6 +27,7 @@ import {ExportReportDialogServiceMock} from '@shared/modules/export-report-dialo
|
||||||
import {ReportState} from '@shared/models/state.enum';
|
import {ReportState} from '@shared/models/state.enum';
|
||||||
|
|
||||||
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||||
|
allProjects: [],
|
||||||
selectedProject: {
|
selectedProject: {
|
||||||
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
||||||
client: 'E Corp',
|
client: 'E Corp',
|
||||||
|
@ -35,6 +36,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||||
tester: 'Novatester',
|
tester: 'Novatester',
|
||||||
summary: '',
|
summary: '',
|
||||||
state: ReportState.NEW,
|
state: ReportState.NEW,
|
||||||
|
version: '1.0',
|
||||||
testingProgress: 0,
|
testingProgress: 0,
|
||||||
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
|
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
|
||||||
},
|
},
|
||||||
|
@ -80,6 +82,7 @@ describe('ObjectiveHeaderComponent', () => {
|
||||||
NgxsModule.forRoot([ProjectState])
|
NgxsModule.forRoot([ProjectState])
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
NbMenuService,
|
||||||
{provide: ProjectService, useValue: new ProjectServiceMock()},
|
{provide: ProjectService, useValue: new ProjectServiceMock()},
|
||||||
{provide: ProjectDialogService, useClass: ProjectDialogServiceMock},
|
{provide: ProjectDialogService, useClass: ProjectDialogServiceMock},
|
||||||
{provide: ExportReportDialogService, useClass: ExportReportDialogServiceMock},
|
{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 {InitProjectState} from '@shared/stores/project-state/project-state.actions';
|
||||||
import {ExportReportDialogService} from '@shared/modules/export-report-dialog/service/export-report-dialog.service';
|
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 {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()
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -27,6 +30,23 @@ export class ObjectiveHeaderComponent implements OnInit {
|
||||||
|
|
||||||
readonly fa = FA;
|
readonly fa = FA;
|
||||||
selectedProject$: BehaviorSubject<Project> = new BehaviorSubject<Project>(null);
|
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,
|
constructor(private store: Store,
|
||||||
private readonly notificationService: NotificationService,
|
private readonly notificationService: NotificationService,
|
||||||
|
@ -34,7 +54,10 @@ export class ObjectiveHeaderComponent implements OnInit {
|
||||||
private projectDialogService: ProjectDialogService,
|
private projectDialogService: ProjectDialogService,
|
||||||
private projectService: ProjectService,
|
private projectService: ProjectService,
|
||||||
private exportReportDialogService: ExportReportDialogService,
|
private exportReportDialogService: ExportReportDialogService,
|
||||||
private readonly router: Router) {
|
private readonly router: Router,
|
||||||
|
private translateService: TranslateService,
|
||||||
|
private menuService: NbMenuService
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
@ -52,6 +75,33 @@ export class ObjectiveHeaderComponent implements OnInit {
|
||||||
console.error(err);
|
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 {
|
onClickRouteBack(): void {
|
||||||
|
@ -78,13 +128,15 @@ export class ObjectiveHeaderComponent implements OnInit {
|
||||||
untilDestroyed(this)
|
untilDestroyed(this)
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: (project) => {
|
next: (project) => {
|
||||||
this.store.dispatch(new InitProjectState(
|
if (project) {
|
||||||
project,
|
this.store.dispatch(new InitProjectState(
|
||||||
[],
|
project,
|
||||||
[]
|
[],
|
||||||
)).pipe(
|
[]
|
||||||
untilDestroyed(this)
|
)).pipe(
|
||||||
).subscribe();
|
untilDestroyed(this)
|
||||||
|
).subscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
NbListModule,
|
NbListModule,
|
||||||
NbButtonModule,
|
NbButtonModule,
|
||||||
NbTooltipModule,
|
NbTooltipModule,
|
||||||
NbActionsModule
|
NbActionsModule, NbUserModule, NbContextMenuModule
|
||||||
} from '@nebular/theme';
|
} from '@nebular/theme';
|
||||||
import {TranslateModule} from '@ngx-translate/core';
|
import {TranslateModule} from '@ngx-translate/core';
|
||||||
import {StatusTagModule} from '@shared/widgets/status-tag/status-tag.module';
|
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 {ProjectDialogModule} from '@shared/modules/project-dialog/project-dialog.module';
|
||||||
import {CommentWidgetModule} from '@shared/widgets/comment-widget/comment-widget.module';
|
import {CommentWidgetModule} from '@shared/widgets/comment-widget/comment-widget.module';
|
||||||
import {ReportStateTagModule} from '@shared/widgets/report-state-tag/report-state-tag.module';
|
import {ReportStateTagModule} from '@shared/widgets/report-state-tag/report-state-tag.module';
|
||||||
|
import {VersionTagModule} from '@shared/widgets/version-tag/version-tag.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@ -33,33 +34,36 @@ import {ReportStateTagModule} from '@shared/widgets/report-state-tag/report-stat
|
||||||
ObjectiveCategoriesComponent,
|
ObjectiveCategoriesComponent,
|
||||||
ObjectiveTableComponent,
|
ObjectiveTableComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
CommonAppModule,
|
CommonAppModule,
|
||||||
NbLayoutModule,
|
NbLayoutModule,
|
||||||
NbCardModule,
|
NbCardModule,
|
||||||
NbButtonModule,
|
NbButtonModule,
|
||||||
// nbTooltip crashes app right now if used in component,
|
// nbTooltip crashes app right now if used in component,
|
||||||
// workaround: use title in html for now
|
// workaround: use title in html for now
|
||||||
NbTooltipModule,
|
NbTooltipModule,
|
||||||
NbTreeGridModule,
|
NbTreeGridModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
StatusTagModule,
|
StatusTagModule,
|
||||||
RouterModule,
|
RouterModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
NbListModule,
|
NbListModule,
|
||||||
FontAwesomeModule,
|
FontAwesomeModule,
|
||||||
FlexLayoutModule,
|
FlexLayoutModule,
|
||||||
NbActionsModule,
|
NbActionsModule,
|
||||||
ExportReportDialogModule,
|
ExportReportDialogModule,
|
||||||
ProjectDialogModule,
|
ProjectDialogModule,
|
||||||
ObjectiveOverviewRoutingModule,
|
ObjectiveOverviewRoutingModule,
|
||||||
// Table Widgets
|
// Table Widgets
|
||||||
FindigWidgetModule,
|
FindigWidgetModule,
|
||||||
CommentWidgetModule,
|
CommentWidgetModule,
|
||||||
NbMenuModule,
|
NbMenuModule,
|
||||||
ReportStateTagModule
|
ReportStateTagModule,
|
||||||
],
|
VersionTagModule,
|
||||||
|
NbUserModule,
|
||||||
|
NbContextMenuModule
|
||||||
|
],
|
||||||
exports: [
|
exports: [
|
||||||
ObjectiveHeaderComponent,
|
ObjectiveHeaderComponent,
|
||||||
ObjectiveCategoriesComponent,
|
ObjectiveCategoriesComponent,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<nb-card class="pentest-table">
|
<div class="pentest-table">
|
||||||
<table [nbTreeGrid]="dataSource">
|
<table [nbTreeGrid]="dataSource">
|
||||||
<tr nbTreeGridHeaderRow *nbTreeGridHeaderRowDef="columns"></tr>
|
<tr nbTreeGridHeaderRow *nbTreeGridHeaderRowDef="columns"></tr>
|
||||||
<tr nbTreeGridRow *nbTreeGridRowDef="let pentest; columns: columns"
|
<tr nbTreeGridRow *nbTreeGridRowDef="let pentest; columns: columns"
|
||||||
|
@ -52,6 +52,6 @@
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</table>
|
</table>
|
||||||
</nb-card>
|
</div>
|
||||||
|
|
||||||
<app-loading-spinner [isLoading$]="isLoading()" *ngIf="isLoading() | async"></app-loading-spinner>
|
<app-loading-spinner [isLoading$]="isLoading()" *ngIf="isLoading() | async"></app-loading-spinner>
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
@import '../../../assets/@theme/styles/themes';
|
@import '../../../assets/@theme/styles/themes';
|
||||||
|
|
||||||
.pentest-table {
|
.pentest-table {
|
||||||
width: calc(78vw - 18%);
|
// width: calc(78vw - 18%);
|
||||||
|
// width: 100%;
|
||||||
|
// width: calc(100% - 20rem);
|
||||||
|
margin-right: 2rem;
|
||||||
|
padding-right: 2rem;
|
||||||
|
|
||||||
.pentest-cell {
|
.pentest-cell {
|
||||||
// Add style here
|
// Add style here
|
||||||
|
|
|
@ -25,6 +25,7 @@ import {CommentDialogServiceMock} from '@shared/modules/comment-dialog/service/c
|
||||||
import {ReportState} from '@shared/models/state.enum';
|
import {ReportState} from '@shared/models/state.enum';
|
||||||
|
|
||||||
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||||
|
allProjects: [],
|
||||||
selectedProject: {
|
selectedProject: {
|
||||||
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
||||||
client: 'E Corp',
|
client: 'E Corp',
|
||||||
|
@ -33,6 +34,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||||
tester: 'Novatester',
|
tester: 'Novatester',
|
||||||
summary: '',
|
summary: '',
|
||||||
state: ReportState.NEW,
|
state: ReportState.NEW,
|
||||||
|
version: '1.0',
|
||||||
testingProgress: 0,
|
testingProgress: 0,
|
||||||
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
|
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';
|
import {ReportState} from '@shared/models/state.enum';
|
||||||
|
|
||||||
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||||
|
allProjects: [],
|
||||||
selectedProject: {
|
selectedProject: {
|
||||||
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
||||||
client: 'E Corp',
|
client: 'E Corp',
|
||||||
|
@ -24,6 +25,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||||
tester: 'Novatester',
|
tester: 'Novatester',
|
||||||
summary: '',
|
summary: '',
|
||||||
state: ReportState.NEW,
|
state: ReportState.NEW,
|
||||||
|
version: '1.0',
|
||||||
testingProgress: 0,
|
testingProgress: 0,
|
||||||
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
|
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';
|
import {ReportState} from '@shared/models/state.enum';
|
||||||
|
|
||||||
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||||
|
allProjects: [],
|
||||||
selectedProject: {
|
selectedProject: {
|
||||||
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
||||||
client: 'E Corp',
|
client: 'E Corp',
|
||||||
|
@ -33,6 +34,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||||
tester: 'Novatester',
|
tester: 'Novatester',
|
||||||
summary: '',
|
summary: '',
|
||||||
state: ReportState.NEW,
|
state: ReportState.NEW,
|
||||||
|
version: '1.0',
|
||||||
testingProgress: 0,
|
testingProgress: 0,
|
||||||
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
|
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';
|
import {ReportState} from '@shared/models/state.enum';
|
||||||
|
|
||||||
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||||
|
allProjects: [],
|
||||||
selectedProject: {
|
selectedProject: {
|
||||||
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
||||||
client: 'E Corp',
|
client: 'E Corp',
|
||||||
|
@ -24,6 +25,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||||
tester: 'Novatester',
|
tester: 'Novatester',
|
||||||
summary: '',
|
summary: '',
|
||||||
state: ReportState.NEW,
|
state: ReportState.NEW,
|
||||||
|
version: '1.0',
|
||||||
testingProgress: 0,
|
testingProgress: 0,
|
||||||
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
|
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">
|
<div class="exit-button-container">
|
||||||
<button nbButton
|
<button nbButton
|
||||||
shape="round"
|
shape="round"
|
||||||
|
@ -11,7 +11,13 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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">
|
<div class="pentest-status-container" fxLayout="row" fxLayoutGap="2.5rem" fxLayoutAlign="end center">
|
||||||
<!-- Pentest Timer-->
|
<!-- Pentest Timer-->
|
||||||
|
@ -21,7 +27,7 @@
|
||||||
<!-- Complete Pentest -->
|
<!-- Complete Pentest -->
|
||||||
<div>
|
<div>
|
||||||
<button nbButton
|
<button nbButton
|
||||||
class="save-pentest-button"
|
class="complete-pentest-button"
|
||||||
status="success"
|
status="success"
|
||||||
[disabled]="!pentestStatusChanged() || !pentestHasFindingsOrComments()"
|
[disabled]="!pentestStatusChanged() || !pentestHasFindingsOrComments()"
|
||||||
title="{{ 'global.action.save' | translate }}"
|
title="{{ 'global.action.save' | translate }}"
|
||||||
|
|
|
@ -1,5 +1,37 @@
|
||||||
.pentest-stepper {
|
@import '../../../assets/@theme/styles/_text-overflow.scss';
|
||||||
width: calc(100vw - 14%);
|
|
||||||
|
.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-button-container {
|
||||||
.exit-element-icon {
|
.exit-element-icon {
|
||||||
|
@ -11,6 +43,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.pentest-status-container {
|
.pentest-status-container {
|
||||||
|
position: fixed;
|
||||||
|
right: 4rem;
|
||||||
// display: flex;
|
// display: flex;
|
||||||
// align-content: flex-end;
|
// align-content: flex-end;
|
||||||
|
|
||||||
|
@ -20,8 +54,12 @@
|
||||||
margin: 0.5rem 2.25rem 1rem 0;
|
margin: 0.5rem 2.25rem 1rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-element-text {
|
.complete-pentest-button {
|
||||||
padding-left: 0.5rem;
|
// position: absolute;
|
||||||
|
|
||||||
|
.action-element-text {
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pentest-status-dialog {
|
.pentest-status-dialog {
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {NotificationServiceMock} from '@shared/services/toaster-service/notifica
|
||||||
import {ReportState} from '@shared/models/state.enum';
|
import {ReportState} from '@shared/models/state.enum';
|
||||||
|
|
||||||
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||||
|
allProjects: [],
|
||||||
selectedProject: {
|
selectedProject: {
|
||||||
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
||||||
client: 'E Corp',
|
client: 'E Corp',
|
||||||
|
@ -24,6 +25,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||||
tester: 'Novatester',
|
tester: 'Novatester',
|
||||||
summary: '',
|
summary: '',
|
||||||
state: ReportState.NEW,
|
state: ReportState.NEW,
|
||||||
|
version: '1.0',
|
||||||
testingProgress: 0,
|
testingProgress: 0,
|
||||||
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
|
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,25 +6,27 @@
|
||||||
<!--Filter-->
|
<!--Filter-->
|
||||||
<div fxLayout="row" fxLayoutGap="1rem" class="header-filer">
|
<div fxLayout="row" fxLayoutGap="1rem" class="header-filer">
|
||||||
<!--Actions-->
|
<!--Actions-->
|
||||||
<!--ToDo: Add searchbar that filters for title, client, tester-->
|
|
||||||
<form class="project-filter-input">
|
<form class="project-filter-input">
|
||||||
<nb-form-field>
|
<nb-form-field>
|
||||||
<fa-icon nbPrefix class ="search-prefix-icon" [icon]="fa.faSearch"></fa-icon>
|
<fa-icon nbPrefix class="search-prefix-icon" [icon]="fa.faSearch"></fa-icon>
|
||||||
<input type="text" required
|
<input type="text"
|
||||||
fullWidth nbInput
|
fullWidth nbInput
|
||||||
|
class="search-field"
|
||||||
[formControl]="projectSearch"
|
[formControl]="projectSearch"
|
||||||
placeholder="{{ 'project.filter.placeholder' | translate }}"
|
placeholder="{{ 'project.filter.placeholder' | translate: this.allProjectsCount$?.getValue() }}"
|
||||||
|
status="basic"
|
||||||
shape="semi-round"
|
shape="semi-round"
|
||||||
fieldSize="medium"
|
fieldSize="medium">
|
||||||
status="basic">
|
|
||||||
</nb-form-field>
|
</nb-form-field>
|
||||||
</form>
|
</form>
|
||||||
<!--ToDo: Add dropdown to filter for specific state-->
|
<!--ToDo: Add dropdown to filter for specific state-->
|
||||||
<button nbButton
|
<button nbButton
|
||||||
status="danger"
|
status="danger"
|
||||||
|
outline
|
||||||
size="medium"
|
size="medium"
|
||||||
shape="semi-round"
|
shape="semi-round"
|
||||||
class="reset-filter-btn"
|
class="reset-filter-btn"
|
||||||
|
[disabled]="projectSearch.value === ''"
|
||||||
(click)="onClickResetFilter()">
|
(click)="onClickResetFilter()">
|
||||||
<fa-icon [icon]="fa.faFilterCircleXmark" class="btn-icon"></fa-icon>
|
<fa-icon [icon]="fa.faFilterCircleXmark" class="btn-icon"></fa-icon>
|
||||||
{{'global.action.reset' | translate}}
|
{{'global.action.reset' | translate}}
|
||||||
|
@ -45,75 +47,11 @@
|
||||||
</div>
|
</div>
|
||||||
</nb-layout-header>
|
</nb-layout-header>
|
||||||
<!--Column-->
|
<!--Column-->
|
||||||
|
<!--ToDo: Fix the column style for multiple projects in css-->
|
||||||
<nb-layout-column class="pentest-overview-column">
|
<nb-layout-column class="pentest-overview-column">
|
||||||
<div fxLayout="row" fxLayoutGap="2rem">
|
<div class="project-grid">
|
||||||
<div *ngFor="let project of projects$ | async">
|
<div class="project" *ngFor="let project of projects$ | async">
|
||||||
<nb-card class="project-card" accent="{{getProjectAccentFillStatus(project?.state)}}">
|
<app-project-widget [project]="project"></app-project-widget>
|
||||||
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!--Error Text-->
|
<!--Error Text-->
|
||||||
|
@ -125,7 +63,6 @@
|
||||||
</div>
|
</div>
|
||||||
<!--Loading Spinner-->
|
<!--Loading Spinner-->
|
||||||
<app-loading-spinner [isLoading$]="isLoading()" *ngIf="isLoading() | async"></app-loading-spinner>
|
<app-loading-spinner [isLoading$]="isLoading()" *ngIf="isLoading() | async"></app-loading-spinner>
|
||||||
|
|
||||||
</nb-layout-column>
|
</nb-layout-column>
|
||||||
</nb-layout>
|
</nb-layout>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,22 +1,29 @@
|
||||||
@import '../../assets/@theme/styles/themes';
|
@import '../../assets/@theme/styles/themes';
|
||||||
@import '../../assets/@theme/styles/variables';
|
@import '../../assets/@theme/styles/variables';
|
||||||
|
@import '../../assets/@theme/styles/_text-overflow.scss';
|
||||||
|
|
||||||
.pentest-overview {
|
.pentest-overview {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 80vh;
|
height: 85vh;
|
||||||
// ToDo: Disable and fix scrolling
|
overflow: hidden !important;
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.pentest-overview-header {
|
.pentest-overview-header {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
|
height: 5rem;
|
||||||
|
|
||||||
.header-filer {
|
.header-filer {
|
||||||
|
|
||||||
.project-filter-input {
|
.project-filter-input {
|
||||||
width: 24rem;
|
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;
|
position: fixed;
|
||||||
right: 1.5rem;
|
right: 1.5rem;
|
||||||
|
|
||||||
.add-project-button {
|
.add-project-button {
|
||||||
// align-content: flex-end;
|
// align-content: flex-end;
|
||||||
margin: 6rem 2rem 6rem 0;
|
margin: 6rem 2rem 6rem 0;
|
||||||
|
|
||||||
|
@ -53,63 +60,26 @@
|
||||||
|
|
||||||
.pentest-overview-column {
|
.pentest-overview-column {
|
||||||
width: 100vw;
|
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 {
|
.project-grid {
|
||||||
max-width: 22rem;
|
display: grid;
|
||||||
width: 22rem;
|
/* define the number of grid columns */
|
||||||
min-width: 20rem;
|
grid-template-columns: repeat( auto-fill, minmax(24rem, 1fr) );
|
||||||
max-height: 100%;
|
|
||||||
height: 100%;
|
|
||||||
min-height: 100%;
|
|
||||||
|
|
||||||
.project-header {
|
.project {
|
||||||
max-height: 8rem;
|
padding-bottom: 1.25rem;
|
||||||
height: 8rem;
|
height: max-content;
|
||||||
min-height: 6rem;
|
|
||||||
|
|
||||||
.header-title {
|
|
||||||
width: 12.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.state-tag {
|
|
||||||
width: 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.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 {
|
.error-text {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
font-weight: bold;
|
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 {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
|
||||||
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
|
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
|
||||||
import {ProjectDialogServiceMock} from '@shared/modules/project-dialog/service/project-dialog.service.mock';
|
import {ProjectDialogServiceMock} from '@shared/modules/project-dialog/service/project-dialog.service.mock';
|
||||||
import {MockComponent, MockPipe} from 'ng-mocks';
|
import {MockComponent} from 'ng-mocks';
|
||||||
|
|
||||||
describe('ProjectOverviewComponent', () => {
|
describe('ProjectOverviewComponent', () => {
|
||||||
let component: ProjectOverviewComponent;
|
let component: ProjectOverviewComponent;
|
||||||
|
@ -36,19 +36,17 @@ describe('ProjectOverviewComponent', () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
ProjectOverviewComponent,
|
ProjectOverviewComponent,
|
||||||
MockComponent(LoadingSpinnerComponent),
|
MockComponent(LoadingSpinnerComponent)
|
||||||
MockPipe(DateTimeFormatPipe)
|
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
ProjectOverviewRoutingModule,
|
|
||||||
NbCardModule,
|
NbCardModule,
|
||||||
NbButtonModule,
|
NbButtonModule,
|
||||||
FlexLayoutModule,
|
FlexLayoutModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
FontAwesomeModule,
|
FontAwesomeModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
NbProgressBarModule,
|
ProjectOverviewRoutingModule,
|
||||||
NbSpinnerModule,
|
NbSpinnerModule,
|
||||||
HttpClientTestingModule,
|
HttpClientTestingModule,
|
||||||
ThemeModule.forRoot(),
|
ThemeModule.forRoot(),
|
||||||
|
|
|
@ -5,16 +5,17 @@ import {BehaviorSubject, Observable} from 'rxjs';
|
||||||
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||||
import {ProjectService} from '@shared/services/api/project.service';
|
import {ProjectService} from '@shared/services/api/project.service';
|
||||||
import {NotificationService, PopupType} from '@shared/services/toaster-service/notification.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 {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||||
import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
|
import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
|
||||||
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
|
import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
|
||||||
import {Router} from '@angular/router';
|
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 {Store} from '@ngxs/store';
|
||||||
import {ReportState} from '@shared/models/state.enum';
|
|
||||||
import {FormControl} from '@angular/forms';
|
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()
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -33,6 +34,7 @@ export class ProjectOverviewComponent implements OnInit {
|
||||||
// Search
|
// Search
|
||||||
projectSearch: FormControl;
|
projectSearch: FormControl;
|
||||||
protected filter$: Observable<string>;
|
protected filter$: Observable<string>;
|
||||||
|
allProjectsCount$: BehaviorSubject<any> = new BehaviorSubject<any>({allProjectsCount: 0});
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly notificationService: NotificationService,
|
private readonly notificationService: NotificationService,
|
||||||
|
@ -44,9 +46,24 @@ export class ProjectOverviewComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
// Load all available projects
|
||||||
this.loadProjects();
|
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
|
// Setup Search
|
||||||
this.projectSearch = new FormControl({value: '', disabled: !this.allProjects$.getValue()});
|
this.projectSearch = new FormControl({value: '', disabled: this.allProjects$.getValue() === []});
|
||||||
this.setFilterObserverForProjects();
|
this.setFilterObserverForProjects();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,8 +75,16 @@ export class ProjectOverviewComponent implements OnInit {
|
||||||
)
|
)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (projects: Project[]) => {
|
next: (projects: Project[]) => {
|
||||||
this.projects$.next(projects);
|
if (projects) {
|
||||||
this.allProjects$.next(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);
|
this.loading$.next(false);
|
||||||
},
|
},
|
||||||
error: err => {
|
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
|
// HTML only
|
||||||
isLoading(): Observable<boolean> {
|
isLoading(): Observable<boolean> {
|
||||||
return this.loading$.asObservable();
|
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 {
|
onClickResetFilter(): void {
|
||||||
this.projectSearch.reset('');
|
this.projectSearch.reset('');
|
||||||
this.projects$.next(this.allProjects$.getValue());
|
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 {FlexLayoutModule} from '@angular/flex-layout';
|
||||||
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
||||||
import {TranslateModule} from '@ngx-translate/core';
|
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 {ProjectDialogModule} from '@shared/modules/project-dialog/project-dialog.module';
|
||||||
import {CommonAppModule} from '../common-app.module';
|
import {CommonAppModule} from '../common-app.module';
|
||||||
import {ConfirmDialogModule} from '@shared/modules/confirm-dialog/confirm-dialog.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 {RouterModule} from '@angular/router';
|
||||||
import {ReportStateTagModule} from '@shared/widgets/report-state-tag/report-state-tag.module';
|
import {ReportStateTagModule} from '@shared/widgets/report-state-tag/report-state-tag.module';
|
||||||
import {ReactiveFormsModule} from '@angular/forms';
|
import {ReactiveFormsModule} from '@angular/forms';
|
||||||
|
import {ProjectWidgetModule} from '@shared/widgets/project-widget/project-widget.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
ProjectOverviewComponent,
|
ProjectOverviewComponent
|
||||||
DateTimeFormatPipe
|
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
@ -50,7 +49,8 @@ import {ReactiveFormsModule} from '@angular/forms';
|
||||||
NbInputModule,
|
NbInputModule,
|
||||||
NbFormFieldModule,
|
NbFormFieldModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
NbSelectModule
|
NbSelectModule,
|
||||||
|
ProjectWidgetModule
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class ProjectOverviewModule {
|
export class ProjectOverviewModule {
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
</nb-card>
|
</nb-card>
|
||||||
</nb-layout-column>
|
</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 class="table-column">
|
||||||
<nb-card-body>
|
<nb-card-body>
|
||||||
<app-objective-table></app-objective-table>
|
<app-objective-table></app-objective-table>
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
.table-wrapper{
|
.table-wrapper{
|
||||||
padding-right: 0 !important;
|
padding-right: 0 !important;
|
||||||
|
border-style: none;
|
||||||
|
|
||||||
.table-column {
|
.table-column {
|
||||||
overflow: auto !important;
|
overflow: auto !important;
|
||||||
|
|
|
@ -29,6 +29,7 @@ import {ExportReportDialogServiceMock} from '@shared/modules/export-report-dialo
|
||||||
import {ReportState} from '@shared/models/state.enum';
|
import {ReportState} from '@shared/models/state.enum';
|
||||||
|
|
||||||
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||||
|
allProjects: [],
|
||||||
selectedProject: {
|
selectedProject: {
|
||||||
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
||||||
client: 'E Corp',
|
client: 'E Corp',
|
||||||
|
@ -37,6 +38,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||||
tester: 'Novatester',
|
tester: 'Novatester',
|
||||||
summary: '',
|
summary: '',
|
||||||
state: ReportState.NEW,
|
state: ReportState.NEW,
|
||||||
|
version: '1.0',
|
||||||
testingProgress: 0,
|
testingProgress: 0,
|
||||||
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
|
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",
|
"password": "Passwort",
|
||||||
"no.progress": "Kein Fortschritt",
|
"no.progress": "Kein Fortschritt",
|
||||||
"project": "Projekt",
|
"project": "Projekt",
|
||||||
|
"version": "Version",
|
||||||
"validationMessage": {
|
"validationMessage": {
|
||||||
"inputNotMatching": "Eingabe stimmt nicht überein!"
|
"inputNotMatching": "Eingabe stimmt nicht überein!"
|
||||||
},
|
},
|
||||||
|
@ -98,7 +99,7 @@
|
||||||
"no.projects": "Keine Projekte verfügbar"
|
"no.projects": "Keine Projekte verfügbar"
|
||||||
},
|
},
|
||||||
"filter": {
|
"filter": {
|
||||||
"placeholder": "Projekt suchen"
|
"placeholder": "Alle {{allProjectsCount}} Projekt(e) durchsuchen.."
|
||||||
},
|
},
|
||||||
"create": {
|
"create": {
|
||||||
"header": "Neues Projekt erstellen"
|
"header": "Neues Projekt erstellen"
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"no.progress": "No progress",
|
"no.progress": "No progress",
|
||||||
"project": "Project",
|
"project": "Project",
|
||||||
|
"version": "Version",
|
||||||
"validationMessage": {
|
"validationMessage": {
|
||||||
"inputNotMatching": "Input does not match!"
|
"inputNotMatching": "Input does not match!"
|
||||||
},
|
},
|
||||||
|
@ -98,7 +99,7 @@
|
||||||
"no.projects": "No projects available"
|
"no.projects": "No projects available"
|
||||||
},
|
},
|
||||||
"filter": {
|
"filter": {
|
||||||
"placeholder": "Search for project"
|
"placeholder": "Search through all {{allProjectsCount}} project(s).."
|
||||||
},
|
},
|
||||||
"create": {
|
"create": {
|
||||||
"header": "Create New Project"
|
"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;
|
tester: string;
|
||||||
summary: string;
|
summary: string;
|
||||||
state: ReportState;
|
state: ReportState;
|
||||||
|
version: string;
|
||||||
projectPentests?: Array<ProjectPentests>;
|
projectPentests?: Array<ProjectPentests>;
|
||||||
testingProgress?: number;
|
testingProgress?: number;
|
||||||
createdBy: string;
|
createdBy: string;
|
||||||
|
@ -19,6 +20,7 @@ export class Project {
|
||||||
createdAt: Date,
|
createdAt: Date,
|
||||||
tester: string,
|
tester: string,
|
||||||
state: ReportState,
|
state: ReportState,
|
||||||
|
version: string,
|
||||||
projectPentests?: Array<ProjectPentests>,
|
projectPentests?: Array<ProjectPentests>,
|
||||||
testingProgress?: number,
|
testingProgress?: number,
|
||||||
summary?: string,
|
summary?: string,
|
||||||
|
@ -32,6 +34,7 @@ export class Project {
|
||||||
this.testingProgress = testingProgress;
|
this.testingProgress = testingProgress;
|
||||||
this.summary = summary;
|
this.summary = summary;
|
||||||
this.state = state;
|
this.state = state;
|
||||||
|
this.version = version;
|
||||||
this.createdBy = createdBy;
|
this.createdBy = createdBy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ import {Comment} from '@shared/models/comment.model';
|
||||||
import {ReportState} from '@shared/models/state.enum';
|
import {ReportState} from '@shared/models/state.enum';
|
||||||
|
|
||||||
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||||
|
allProjects: [],
|
||||||
selectedProject: {
|
selectedProject: {
|
||||||
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
||||||
client: 'E Corp',
|
client: 'E Corp',
|
||||||
|
@ -43,6 +44,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||||
tester: 'Novatester',
|
tester: 'Novatester',
|
||||||
summary: '',
|
summary: '',
|
||||||
state: ReportState.NEW,
|
state: ReportState.NEW,
|
||||||
|
version: '1.0',
|
||||||
testingProgress: 0,
|
testingProgress: 0,
|
||||||
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
|
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
<nb-card #dialog accent="{{dialogData?.options[0].accentColor}}" class="export-report-dialog">
|
<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 }}
|
{{ dialogData?.options[0].headerLabelKey | translate }}
|
||||||
</nb-card-header>
|
</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">
|
<div fxLayout="column" fxLayoutGap="1rem" fxLayoutAlign="start start">
|
||||||
<label class="export-format-label">
|
<label class="export-format-label">
|
||||||
{{ 'global.project' | translate }}:
|
{{ 'global.project' | translate }}:
|
||||||
</label>
|
</label>
|
||||||
<b class="project-title">
|
<div fxLayout="row" fxLayoutAlign="space-between center" class="project-title">
|
||||||
{{dialogData.options[0].additionalData.title}}
|
<span class="title">{{dialogData.options[0].additionalData.title}}</span>
|
||||||
</b>
|
<app-version-tag [version]="dialogData.options[0].additionalData?.version"></app-version-tag>
|
||||||
|
</div>
|
||||||
<!--Chart Objective Component-->
|
<!--Chart Objective Component-->
|
||||||
<div fxLayout="column" fxLayoutGap="2rem" fxLayoutAlign="center center">
|
<div fxLayout="column" fxLayoutGap="2rem" fxLayoutAlign="center center">
|
||||||
<app-objective-chart
|
<app-objective-chart
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
@import "../../../assets/@theme/styles/_dialog.scss";
|
@import "../../../assets/@theme/styles/_dialog.scss";
|
||||||
@import '../../../assets/@theme/styles/themes';
|
@import '../../../assets/@theme/styles/themes';
|
||||||
|
@import '../../../assets/@theme/styles/_text-overflow.scss';
|
||||||
|
|
||||||
.export-report-dialog {
|
.export-report-dialog {
|
||||||
width: 45.25rem !important;
|
width: 45.25rem !important;
|
||||||
|
@ -44,8 +45,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-title {
|
.project-title {
|
||||||
font-size: 1.5rem;
|
|
||||||
padding: 0.25rem 0.5rem 0.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 {
|
.form-field {
|
||||||
|
|
|
@ -104,6 +104,7 @@ const mockProject: Project = {
|
||||||
tester: 'Novatester',
|
tester: 'Novatester',
|
||||||
summary: '',
|
summary: '',
|
||||||
state: ReportState.NEW,
|
state: ReportState.NEW,
|
||||||
|
version: '1.0',
|
||||||
projectPentests: mockedPentests,
|
projectPentests: mockedPentests,
|
||||||
testingProgress: 0,
|
testingProgress: 0,
|
||||||
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
|
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 {shareReplay, tap} from 'rxjs/operators';
|
||||||
import {downloadFile} from '@shared/functions/download-file.function';
|
import {downloadFile} from '@shared/functions/download-file.function';
|
||||||
import {Loading, LoadingState} from '@shared/models/loading.model';
|
import {Loading, LoadingState} from '@shared/models/loading.model';
|
||||||
import {HttpEvent, HttpEventType} from '@angular/common/http';
|
|
||||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {ExportReportDialogService} from '@shared/modules/export-report-dialog/se
|
||||||
import {ReportingService} from '@shared/services/reporting/reporting.service';
|
import {ReportingService} from '@shared/services/reporting/reporting.service';
|
||||||
import {ObjectiveChartModule} from '@shared/modules/objective-chart/objective-chart.module';
|
import {ObjectiveChartModule} from '@shared/modules/objective-chart/objective-chart.module';
|
||||||
import {LoadingBarModule} from '@shared/widgets/loading-bar/loading-bar.module';
|
import {LoadingBarModule} from '@shared/widgets/loading-bar/loading-bar.module';
|
||||||
|
import {VersionTagModule} from '@shared/widgets/version-tag/version-tag.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@ -29,7 +30,8 @@ import {LoadingBarModule} from '@shared/widgets/loading-bar/loading-bar.module';
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
NbRadioModule,
|
NbRadioModule,
|
||||||
ObjectiveChartModule,
|
ObjectiveChartModule,
|
||||||
LoadingBarModule
|
LoadingBarModule,
|
||||||
|
VersionTagModule
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
ExportReportDialogService,
|
ExportReportDialogService,
|
||||||
|
|
|
@ -33,6 +33,7 @@ import {NgxsModule, Store} from '@ngxs/store';
|
||||||
import {ReportState} from '@shared/models/state.enum';
|
import {ReportState} from '@shared/models/state.enum';
|
||||||
|
|
||||||
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||||
|
allProjects: [],
|
||||||
selectedProject: {
|
selectedProject: {
|
||||||
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
|
||||||
client: 'E Corp',
|
client: 'E Corp',
|
||||||
|
@ -41,6 +42,7 @@ const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||||
tester: 'Novatester',
|
tester: 'Novatester',
|
||||||
summary: '',
|
summary: '',
|
||||||
state: ReportState.NEW,
|
state: ReportState.NEW,
|
||||||
|
version: '1.0',
|
||||||
testingProgress: 0,
|
testingProgress: 0,
|
||||||
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
|
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
|
||||||
},
|
},
|
||||||
|
|
|
@ -97,6 +97,7 @@ export const mockProject: Project = {
|
||||||
tester: 'Testpentester',
|
tester: 'Testpentester',
|
||||||
summary: 'Test',
|
summary: 'Test',
|
||||||
state: ReportState.NEW,
|
state: ReportState.NEW,
|
||||||
|
version: '1.0',
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
testingProgress: 0,
|
testingProgress: 0,
|
||||||
createdBy: 'UID-11-12-13'
|
createdBy: 'UID-11-12-13'
|
||||||
|
|
|
@ -44,6 +44,7 @@ describe('ProjectService', () => {
|
||||||
tester: 'Novatester',
|
tester: 'Novatester',
|
||||||
summary: '',
|
summary: '',
|
||||||
state: ReportState.NEW,
|
state: ReportState.NEW,
|
||||||
|
version: '1.0',
|
||||||
testingProgress: 0,
|
testingProgress: 0,
|
||||||
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
|
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
|
||||||
};
|
};
|
||||||
|
@ -97,6 +98,7 @@ describe('ProjectService', () => {
|
||||||
tester: 'Novatester',
|
tester: 'Novatester',
|
||||||
summary: '',
|
summary: '',
|
||||||
state: ReportState.NEW,
|
state: ReportState.NEW,
|
||||||
|
version: '1.0',
|
||||||
testingProgress: 0,
|
testingProgress: 0,
|
||||||
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
|
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 {
|
export class ChangeProject {
|
||||||
static readonly type = '[ProjectState] 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';
|
import {Category} from '@shared/models/category.model';
|
||||||
|
|
||||||
const INITIAL_PROJECT_STATE_SESSION: ProjectStateModel = {
|
const INITIAL_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||||
|
allProjects: [],
|
||||||
selectedProject: null,
|
selectedProject: null,
|
||||||
disabledCategories: [],
|
disabledCategories: [],
|
||||||
selectedCategory: Category.INFORMATION_GATHERING,
|
selectedCategory: Category.INFORMATION_GATHERING,
|
||||||
|
@ -16,6 +17,7 @@ const INITIAL_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
const DESIRED_PROJECT_STATE_SESSION: ProjectStateModel = {
|
||||||
|
allProjects: [],
|
||||||
selectedProject: null,
|
selectedProject: null,
|
||||||
disabledCategories: [],
|
disabledCategories: [],
|
||||||
selectedCategory: Category.INFORMATION_GATHERING,
|
selectedCategory: Category.INFORMATION_GATHERING,
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
ChangeCategory,
|
ChangeCategory,
|
||||||
ChangePentest,
|
ChangePentest,
|
||||||
ChangeProject,
|
ChangeProject,
|
||||||
InitProjectState,
|
InitProjectState, SetAvailableProjects,
|
||||||
UpdatePentestComments,
|
UpdatePentestComments,
|
||||||
UpdatePentestFindings, UpdatePentestStatus,
|
UpdatePentestFindings, UpdatePentestStatus,
|
||||||
UpdatePentestTime
|
UpdatePentestTime
|
||||||
|
@ -17,6 +17,7 @@ import {PentestStatus} from '@shared/models/pentest-status.model';
|
||||||
export const PROJECT_STATE_NAME = 'project';
|
export const PROJECT_STATE_NAME = 'project';
|
||||||
|
|
||||||
export interface ProjectStateModel {
|
export interface ProjectStateModel {
|
||||||
|
allProjects: Project[];
|
||||||
selectedProject: Project;
|
selectedProject: Project;
|
||||||
// Manages Categories
|
// Manages Categories
|
||||||
disabledCategories: Array<string>;
|
disabledCategories: Array<string>;
|
||||||
|
@ -33,6 +34,7 @@ export interface FindingMap {
|
||||||
@State<ProjectStateModel>({
|
@State<ProjectStateModel>({
|
||||||
name: PROJECT_STATE_NAME,
|
name: PROJECT_STATE_NAME,
|
||||||
defaults: {
|
defaults: {
|
||||||
|
allProjects: [],
|
||||||
selectedProject: null,
|
selectedProject: null,
|
||||||
disabledCategories: [],
|
disabledCategories: [],
|
||||||
selectedCategory: Category.INFORMATION_GATHERING,
|
selectedCategory: Category.INFORMATION_GATHERING,
|
||||||
|
@ -42,6 +44,11 @@ export interface FindingMap {
|
||||||
})
|
})
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ProjectState {
|
export class ProjectState {
|
||||||
|
@Selector()
|
||||||
|
static allProjects(state: ProjectStateModel): Project[] {
|
||||||
|
return state.allProjects;
|
||||||
|
}
|
||||||
|
|
||||||
@Selector()
|
@Selector()
|
||||||
static project(state: ProjectStateModel): Project {
|
static project(state: ProjectStateModel): Project {
|
||||||
return state.selectedProject;
|
return state.selectedProject;
|
||||||
|
@ -60,6 +67,7 @@ export class ProjectState {
|
||||||
@Action(InitProjectState)
|
@Action(InitProjectState)
|
||||||
initProjectState(ctx: StateContext<ProjectStateModel>, action: InitProjectState): void {
|
initProjectState(ctx: StateContext<ProjectStateModel>, action: InitProjectState): void {
|
||||||
ctx.setState({
|
ctx.setState({
|
||||||
|
allProjects: ctx.getState().allProjects,
|
||||||
selectedProject: action.project,
|
selectedProject: action.project,
|
||||||
disabledCategories: action.disabledCategories,
|
disabledCategories: action.disabledCategories,
|
||||||
selectedCategory: Category.INFORMATION_GATHERING,
|
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)
|
@Action(ChangeProject)
|
||||||
changeProject(ctx: StateContext<ProjectStateModel>, {project}: ChangeProject): void {
|
changeProject(ctx: StateContext<ProjectStateModel>, {project}: ChangeProject): void {
|
||||||
const state = ctx.getState();
|
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 tester: String,
|
||||||
val summary: String? = null,
|
val summary: String? = null,
|
||||||
val state: PentestState,
|
val state: PentestState,
|
||||||
|
val version: String,
|
||||||
var projectPentests: List<ProjectPentest> = emptyList(),
|
var projectPentests: List<ProjectPentest> = emptyList(),
|
||||||
val createdBy: String
|
val createdBy: String
|
||||||
)
|
)
|
||||||
|
@ -35,6 +36,7 @@ fun buildProject(body: ProjectRequestBody, projectEntity: ProjectEntity): Projec
|
||||||
tester = body.tester,
|
tester = body.tester,
|
||||||
summary = body.summary,
|
summary = body.summary,
|
||||||
state = body.state,
|
state = body.state,
|
||||||
|
version = projectEntity.data.version,
|
||||||
projectPentests = projectEntity.data.projectPentests,
|
projectPentests = projectEntity.data.projectPentests,
|
||||||
createdBy = projectEntity.data.createdBy
|
createdBy = projectEntity.data.createdBy
|
||||||
)
|
)
|
||||||
|
@ -49,6 +51,7 @@ fun Project.toProjectResponseBody(): ResponseBody {
|
||||||
"tester" to tester,
|
"tester" to tester,
|
||||||
"summary" to summary,
|
"summary" to summary,
|
||||||
"state" to state,
|
"state" to state,
|
||||||
|
"version" to version,
|
||||||
/* ToDo: Calculate percentage in BE type: float */
|
/* ToDo: Calculate percentage in BE type: float */
|
||||||
"testingProgress" to calculateProgress(),
|
"testingProgress" to calculateProgress(),
|
||||||
"createdBy" to createdBy
|
"createdBy" to createdBy
|
||||||
|
@ -64,6 +67,7 @@ fun Project.toProjectCompletedPentestResponseBody(): ResponseBody {
|
||||||
"createdAt" to createdAt,
|
"createdAt" to createdAt,
|
||||||
"tester" to tester,
|
"tester" to tester,
|
||||||
"summary" to summary,
|
"summary" to summary,
|
||||||
|
"version" to version,
|
||||||
"projectPentests" to projectPentests.filter { pentest -> pentest.status == PentestStatus.COMPLETED },
|
"projectPentests" to projectPentests.filter { pentest -> pentest.status == PentestStatus.COMPLETED },
|
||||||
"createdBy" to createdBy
|
"createdBy" to createdBy
|
||||||
)
|
)
|
||||||
|
@ -78,6 +82,7 @@ fun Project.toProjectEvaluatedPentestResponseBody(): ResponseBody {
|
||||||
"createdAt" to createdAt,
|
"createdAt" to createdAt,
|
||||||
"tester" to tester,
|
"tester" to tester,
|
||||||
"summary" to summary,
|
"summary" to summary,
|
||||||
|
"version" to version,
|
||||||
"projectPentests" to projectPentests,
|
"projectPentests" to projectPentests,
|
||||||
"createdBy" to createdBy
|
"createdBy" to createdBy
|
||||||
)
|
)
|
||||||
|
@ -150,6 +155,8 @@ fun ProjectRequestBody.toProject(): Project {
|
||||||
tester = this.tester,
|
tester = this.tester,
|
||||||
summary = this.summary,
|
summary = this.summary,
|
||||||
state = this.state,
|
state = this.state,
|
||||||
|
// ToDo: Update version in backend automatically
|
||||||
|
version = "1.0",
|
||||||
// ToDo: Should be changed to SUB from Token after adding AUTH Header
|
// ToDo: Should be changed to SUB from Token after adding AUTH Header
|
||||||
createdBy = UUID.randomUUID().toString()
|
createdBy = UUID.randomUUID().toString()
|
||||||
)
|
)
|
||||||
|
|
|
@ -20,6 +20,7 @@ fun ProjectEntity.toProject() : Project {
|
||||||
this.data.tester,
|
this.data.tester,
|
||||||
this.data.summary,
|
this.data.summary,
|
||||||
this.data.state,
|
this.data.state,
|
||||||
|
this.data.version,
|
||||||
this.data.projectPentests,
|
this.data.projectPentests,
|
||||||
this.data.createdBy
|
this.data.createdBy
|
||||||
)
|
)
|
||||||
|
|
|
@ -258,6 +258,7 @@ class PentestControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
tester = "Novatester",
|
tester = "Novatester",
|
||||||
projectPentests = emptyList(),
|
projectPentests = emptyList(),
|
||||||
state = PentestState.NEW,
|
state = PentestState.NEW,
|
||||||
|
version = "1.0",
|
||||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||||
)
|
)
|
||||||
// Pentests
|
// Pentests
|
||||||
|
|
|
@ -173,6 +173,7 @@ class PentestControllerIntTest : BaseIntTest() {
|
||||||
createdAt = "2021-01-10T18:05:00Z",
|
createdAt = "2021-01-10T18:05:00Z",
|
||||||
tester = "Novatester",
|
tester = "Novatester",
|
||||||
state = PentestState.NEW,
|
state = PentestState.NEW,
|
||||||
|
version = "1.0",
|
||||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||||
)
|
)
|
||||||
// pentests
|
// pentests
|
||||||
|
|
|
@ -284,6 +284,7 @@ class CommentControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
tester = "Novatester",
|
tester = "Novatester",
|
||||||
projectPentests = emptyList(),
|
projectPentests = emptyList(),
|
||||||
state = PentestState.NEW,
|
state = PentestState.NEW,
|
||||||
|
version = "1.0",
|
||||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||||
)
|
)
|
||||||
// Pentests
|
// Pentests
|
||||||
|
|
|
@ -181,6 +181,7 @@ class CommentControllerIntTest : BaseIntTest() {
|
||||||
createdAt = "2021-01-10T18:05:00Z",
|
createdAt = "2021-01-10T18:05:00Z",
|
||||||
tester = "Novatester",
|
tester = "Novatester",
|
||||||
state = PentestState.NEW,
|
state = PentestState.NEW,
|
||||||
|
version = "1.0",
|
||||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||||
)
|
)
|
||||||
// pentests
|
// pentests
|
||||||
|
|
|
@ -342,6 +342,7 @@ class FindingControllerDocumentationTest: BaseDocumentationIntTest() {
|
||||||
tester = "Novatester",
|
tester = "Novatester",
|
||||||
projectPentests = emptyList(),
|
projectPentests = emptyList(),
|
||||||
state = PentestState.NEW,
|
state = PentestState.NEW,
|
||||||
|
version = "1.0",
|
||||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||||
)
|
)
|
||||||
// Pentests
|
// Pentests
|
||||||
|
|
|
@ -209,6 +209,7 @@ class FindingControllerIntTest: BaseIntTest() {
|
||||||
createdAt = "2021-01-10T18:05:00Z",
|
createdAt = "2021-01-10T18:05:00Z",
|
||||||
tester = "Novatester",
|
tester = "Novatester",
|
||||||
state = PentestState.NEW,
|
state = PentestState.NEW,
|
||||||
|
version = "1.0",
|
||||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||||
)
|
)
|
||||||
// pentests
|
// pentests
|
||||||
|
|
|
@ -77,6 +77,8 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
.description("The summary of the requested project"),
|
.description("The summary of the requested project"),
|
||||||
PayloadDocumentation.fieldWithPath("[].state").type(JsonFieldType.STRING)
|
PayloadDocumentation.fieldWithPath("[].state").type(JsonFieldType.STRING)
|
||||||
.description("The state of the requested project pentest"),
|
.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)
|
PayloadDocumentation.fieldWithPath("[].createdBy").type(JsonFieldType.STRING)
|
||||||
.description("The id of the user that created the project"),
|
.description("The id of the user that created the project"),
|
||||||
PayloadDocumentation.fieldWithPath("[].testingProgress").type(JsonFieldType.NUMBER)
|
PayloadDocumentation.fieldWithPath("[].testingProgress").type(JsonFieldType.NUMBER)
|
||||||
|
@ -95,6 +97,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
summary = "Lorem Ipsum",
|
summary = "Lorem Ipsum",
|
||||||
projectPentests = emptyList<ProjectPentest>(),
|
projectPentests = emptyList<ProjectPentest>(),
|
||||||
state = PentestState.NEW,
|
state = PentestState.NEW,
|
||||||
|
version = "1.0",
|
||||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||||
)
|
)
|
||||||
val projectTwo = Project(
|
val projectTwo = Project(
|
||||||
|
@ -106,6 +109,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
summary = "Lorem Ipsum",
|
summary = "Lorem Ipsum",
|
||||||
projectPentests = emptyList<ProjectPentest>(),
|
projectPentests = emptyList<ProjectPentest>(),
|
||||||
state = PentestState.NEW,
|
state = PentestState.NEW,
|
||||||
|
version = "1.0",
|
||||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
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"),
|
.description("The user that is assigned as a tester in the project"),
|
||||||
PayloadDocumentation.fieldWithPath("state").type(JsonFieldType.STRING)
|
PayloadDocumentation.fieldWithPath("state").type(JsonFieldType.STRING)
|
||||||
.description("The state of the requested project pentest"),
|
.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)
|
PayloadDocumentation.fieldWithPath("createdBy").type(JsonFieldType.STRING)
|
||||||
.description("The id of the user that created the project"),
|
.description("The id of the user that created the project"),
|
||||||
PayloadDocumentation.fieldWithPath("testingProgress").type(JsonFieldType.NUMBER)
|
PayloadDocumentation.fieldWithPath("testingProgress").type(JsonFieldType.NUMBER)
|
||||||
|
@ -238,6 +244,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
tester = "Novatester",
|
tester = "Novatester",
|
||||||
projectPentests = emptyList<ProjectPentest>(),
|
projectPentests = emptyList<ProjectPentest>(),
|
||||||
state = PentestState.NEW,
|
state = PentestState.NEW,
|
||||||
|
version = "1.0",
|
||||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -279,6 +286,8 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
.description("The summary of the requested project"),
|
.description("The summary of the requested project"),
|
||||||
PayloadDocumentation.fieldWithPath("state").type(JsonFieldType.STRING)
|
PayloadDocumentation.fieldWithPath("state").type(JsonFieldType.STRING)
|
||||||
.description("The state of the requested project pentest"),
|
.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)
|
PayloadDocumentation.fieldWithPath("createdBy").type(JsonFieldType.STRING)
|
||||||
.description("The id of the user that created the project"),
|
.description("The id of the user that created the project"),
|
||||||
PayloadDocumentation.fieldWithPath("testingProgress").type(JsonFieldType.NUMBER)
|
PayloadDocumentation.fieldWithPath("testingProgress").type(JsonFieldType.NUMBER)
|
||||||
|
@ -304,6 +313,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
tester = "Stipe_updated",
|
tester = "Stipe_updated",
|
||||||
summary = "",
|
summary = "",
|
||||||
state = PentestState.NEW,
|
state = PentestState.NEW,
|
||||||
|
version = "1.0",
|
||||||
projectPentests = emptyList<ProjectPentest>(),
|
projectPentests = emptyList<ProjectPentest>(),
|
||||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||||
)
|
)
|
||||||
|
@ -319,6 +329,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
tester = "Novatester",
|
tester = "Novatester",
|
||||||
summary = "Lorem Ipsum",
|
summary = "Lorem Ipsum",
|
||||||
state = PentestState.NEW,
|
state = PentestState.NEW,
|
||||||
|
version = "1.0",
|
||||||
projectPentests = emptyList<ProjectPentest>(),
|
projectPentests = emptyList<ProjectPentest>(),
|
||||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||||
)
|
)
|
||||||
|
@ -330,6 +341,7 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
tester = "Elliot",
|
tester = "Elliot",
|
||||||
summary = "Lorem Ipsum",
|
summary = "Lorem Ipsum",
|
||||||
state = PentestState.NEW,
|
state = PentestState.NEW,
|
||||||
|
version = "1.0",
|
||||||
projectPentests = emptyList<ProjectPentest>(),
|
projectPentests = emptyList<ProjectPentest>(),
|
||||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||||
)
|
)
|
||||||
|
|
|
@ -73,6 +73,7 @@ class ProjectControllerIntTest : BaseIntTest() {
|
||||||
tester = "Novatester",
|
tester = "Novatester",
|
||||||
summary = "Lorem Ipsum",
|
summary = "Lorem Ipsum",
|
||||||
state = PentestState.NEW,
|
state = PentestState.NEW,
|
||||||
|
version = "1.0",
|
||||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||||
)
|
)
|
||||||
val projectTwo = Project(
|
val projectTwo = Project(
|
||||||
|
@ -83,6 +84,7 @@ class ProjectControllerIntTest : BaseIntTest() {
|
||||||
tester = "Elliot",
|
tester = "Elliot",
|
||||||
summary = "Lorem Ipsum",
|
summary = "Lorem Ipsum",
|
||||||
state = PentestState.NEW,
|
state = PentestState.NEW,
|
||||||
|
version = "1.0",
|
||||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -119,6 +121,7 @@ class ProjectControllerIntTest : BaseIntTest() {
|
||||||
tester = "Stipe",
|
tester = "Stipe",
|
||||||
summary = "",
|
summary = "",
|
||||||
state = PentestState.NEW,
|
state = PentestState.NEW,
|
||||||
|
version = "1.0",
|
||||||
createdBy = "a8891ad2-5cf5-4519-a89e-9ef8eec9e10c"
|
createdBy = "a8891ad2-5cf5-4519-a89e-9ef8eec9e10c"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -153,6 +156,7 @@ class ProjectControllerIntTest : BaseIntTest() {
|
||||||
createdAt = "2021-01-10T18:05:00Z",
|
createdAt = "2021-01-10T18:05:00Z",
|
||||||
tester = "Elliot",
|
tester = "Elliot",
|
||||||
state = PentestState.NEW,
|
state = PentestState.NEW,
|
||||||
|
version = "1.0",
|
||||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -180,6 +184,7 @@ class ProjectControllerIntTest : BaseIntTest() {
|
||||||
createdAt = "2021-04-10T18:05:00Z",
|
createdAt = "2021-04-10T18:05:00Z",
|
||||||
tester = "Stipe_updated",
|
tester = "Stipe_updated",
|
||||||
state = PentestState.NEW,
|
state = PentestState.NEW,
|
||||||
|
version = "1.0",
|
||||||
createdBy = "a8891ad2-5cf5-4519-a89e-9ef8eec9e10c"
|
createdBy = "a8891ad2-5cf5-4519-a89e-9ef8eec9e10c"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -194,6 +199,7 @@ class ProjectControllerIntTest : BaseIntTest() {
|
||||||
tester = "Novatester",
|
tester = "Novatester",
|
||||||
summary = "Lorem Ipsum",
|
summary = "Lorem Ipsum",
|
||||||
state = PentestState.NEW,
|
state = PentestState.NEW,
|
||||||
|
version = "1.0",
|
||||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||||
)
|
)
|
||||||
val projectTwo = Project(
|
val projectTwo = Project(
|
||||||
|
@ -204,6 +210,7 @@ class ProjectControllerIntTest : BaseIntTest() {
|
||||||
tester = "Elliot",
|
tester = "Elliot",
|
||||||
summary = "Lorem Ipsum",
|
summary = "Lorem Ipsum",
|
||||||
state = PentestState.NEW,
|
state = PentestState.NEW,
|
||||||
|
version = "1.0",
|
||||||
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||||
)
|
)
|
||||||
// persist test data in database
|
// persist test data in database
|
||||||
|
|
|
@ -9,6 +9,7 @@ data class ProjectReport(
|
||||||
val createdAt: Date,
|
val createdAt: Date,
|
||||||
val tester: String,
|
val tester: String,
|
||||||
val summary: String? = null,
|
val summary: String? = null,
|
||||||
|
val version: String,
|
||||||
var projectPentestReport: MutableList<PentestReport> = mutableListOf<PentestReport>(),
|
var projectPentestReport: MutableList<PentestReport> = mutableListOf<PentestReport>(),
|
||||||
val createdBy: String
|
val createdBy: String
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,7 +5,6 @@ import com.securityc4po.reporting.remote.model.ProjectReport
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
|
|
||||||
data class Project(
|
data class Project(
|
||||||
val id: String,
|
val id: String,
|
||||||
val client: String,
|
val client: String,
|
||||||
|
@ -13,6 +12,7 @@ data class Project(
|
||||||
val createdAt: String,
|
val createdAt: String,
|
||||||
val tester: String,
|
val tester: String,
|
||||||
val summary: String? = "",
|
val summary: String? = "",
|
||||||
|
val version: String,
|
||||||
var projectPentests: List<ProjectPentest>? = emptyList(),
|
var projectPentests: List<ProjectPentest>? = emptyList(),
|
||||||
val createdBy: String
|
val createdBy: String
|
||||||
)
|
)
|
||||||
|
@ -26,6 +26,7 @@ fun Project.toProjectReport(): ProjectReport {
|
||||||
createdAt = Date.from(Instant.now()),
|
createdAt = Date.from(Instant.now()),
|
||||||
tester = this.tester,
|
tester = this.tester,
|
||||||
summary = this.summary,
|
summary = this.summary,
|
||||||
|
version = this.version,
|
||||||
projectPentestReport = mutableListOf<PentestReport>(),
|
projectPentestReport = mutableListOf<PentestReport>(),
|
||||||
createdBy = this.createdBy
|
createdBy = this.createdBy
|
||||||
)
|
)
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
<property name="net.sf.jasperreports.json.field.expression" value="createdBy"/>
|
<property name="net.sf.jasperreports.json.field.expression" value="createdBy"/>
|
||||||
<fieldDescription><![CDATA[createdBy]]></fieldDescription>
|
<fieldDescription><![CDATA[createdBy]]></fieldDescription>
|
||||||
</field>
|
</field>
|
||||||
|
<field name="version" class="java.lang.String"/>
|
||||||
<background>
|
<background>
|
||||||
<band splitType="Stretch"/>
|
<band splitType="Stretch"/>
|
||||||
</background>
|
</background>
|
||||||
|
@ -149,23 +150,23 @@
|
||||||
</band>
|
</band>
|
||||||
</detail>
|
</detail>
|
||||||
<columnFooter>
|
<columnFooter>
|
||||||
<band height="70" splitType="Stretch">
|
<band height="76" splitType="Stretch">
|
||||||
<rectangle>
|
<rectangle>
|
||||||
<reportElement x="-20" y="30" width="595" height="30" forecolor="#232B44" backcolor="#232B44" uuid="1ed47e2d-9d46-44b9-bad7-8eeb1143c83c"/>
|
<reportElement x="-20" y="30" width="595" height="30" forecolor="#232B44" backcolor="#232B44" uuid="1ed47e2d-9d46-44b9-bad7-8eeb1143c83c"/>
|
||||||
<graphicElement>
|
<graphicElement>
|
||||||
<pen lineWidth="1.0"/>
|
<pen lineWidth="1.0"/>
|
||||||
</graphicElement>
|
</graphicElement>
|
||||||
</rectangle>
|
</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>
|
<rectangle>
|
||||||
<reportElement x="-20" y="20" width="595" height="10" forecolor="#151B2E" backcolor="#151B2E" uuid="b9a5cd43-3460-4177-97f5-a46eac874e7d"/>
|
<reportElement x="-20" y="20" width="595" height="10" forecolor="#151B2E" backcolor="#151B2E" uuid="b9a5cd43-3460-4177-97f5-a46eac874e7d"/>
|
||||||
</rectangle>
|
</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>
|
</band>
|
||||||
</columnFooter>
|
</columnFooter>
|
||||||
<pageFooter>
|
<pageFooter>
|
||||||
|
|
|
@ -105,6 +105,7 @@
|
||||||
<property name="net.sf.jasperreports.json.field.expression" value="createdBy"/>
|
<property name="net.sf.jasperreports.json.field.expression" value="createdBy"/>
|
||||||
<fieldDescription><![CDATA[createdBy]]></fieldDescription>
|
<fieldDescription><![CDATA[createdBy]]></fieldDescription>
|
||||||
</field>
|
</field>
|
||||||
|
<field name="version" class="java.lang.String"/>
|
||||||
<group name="id">
|
<group name="id">
|
||||||
<groupExpression><![CDATA[$F{id}]]></groupExpression>
|
<groupExpression><![CDATA[$F{id}]]></groupExpression>
|
||||||
</group>
|
</group>
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
<property name="net.sf.jasperreports.json.field.expression" value="createdBy"/>
|
<property name="net.sf.jasperreports.json.field.expression" value="createdBy"/>
|
||||||
<fieldDescription><![CDATA[createdBy]]></fieldDescription>
|
<fieldDescription><![CDATA[createdBy]]></fieldDescription>
|
||||||
</field>
|
</field>
|
||||||
|
<field name="version" class="java.lang.String"/>
|
||||||
<background>
|
<background>
|
||||||
<band splitType="Stretch"/>
|
<band splitType="Stretch"/>
|
||||||
</background>
|
</background>
|
||||||
|
@ -70,7 +71,7 @@
|
||||||
<textElement>
|
<textElement>
|
||||||
<font size="12"/>
|
<font size="12"/>
|
||||||
</textElement>
|
</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>
|
||||||
<textField textAdjust="StretchHeight">
|
<textField textAdjust="StretchHeight">
|
||||||
<reportElement positionType="Float" x="0" y="100" width="551" height="100" uuid="6b5626a8-ff7b-450b-8c53-93c2115bd56c"/>
|
<reportElement positionType="Float" x="0" y="100" width="551" height="100" uuid="6b5626a8-ff7b-450b-8c53-93c2115bd56c"/>
|
||||||
|
|
Loading…
Reference in New Issue