{{'project.overview.no.projects' | translate}}
diff --git a/security-c4po-angular/src/app/project-overview/project-overview.component.scss b/security-c4po-angular/src/app/project-overview/project-overview.component.scss
index 20f6bb7..0fae7c5 100644
--- a/security-c4po-angular/src/app/project-overview/project-overview.component.scss
+++ b/security-c4po-angular/src/app/project-overview/project-overview.component.scss
@@ -36,9 +36,13 @@
}
.project-card:hover {
- background-color: nb-theme(color-info-transparent-default);
+ 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)
+ transform: scale(1.025);
+ */
}
.project-link:hover {
diff --git a/security-c4po-angular/src/app/project-overview/project-overview.component.spec.ts b/security-c4po-angular/src/app/project-overview/project-overview.component.spec.ts
index a800ff4..df9f274 100644
--- a/security-c4po-angular/src/app/project-overview/project-overview.component.spec.ts
+++ b/security-c4po-angular/src/app/project-overview/project-overview.component.spec.ts
@@ -24,6 +24,8 @@ import {LoadingSpinnerComponent} from '@shared/widgets/loading-spinner/loading-s
import {KeycloakService} from 'keycloak-angular';
import {DialogService} from '@shared/services/dialog-service/dialog.service';
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
+import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
+import {ProjectDialogServiceMock} from '@shared/modules/project-dialog/service/project-dialog.service.mock';
describe('ProjectOverviewComponent', () => {
let component: ProjectOverviewComponent;
@@ -63,6 +65,7 @@ describe('ProjectOverviewComponent', () => {
providers: [
KeycloakService,
{provide: ProjectService, useValue: new ProjectServiceMock()},
+ {provide: ProjectDialogService, useClass: ProjectDialogServiceMock},
{provide: DialogService, useClass: DialogServiceMock},
{provide: NotificationService, useValue: new NotificationServiceMock()}
]
diff --git a/security-c4po-angular/src/app/project-overview/project-overview.component.ts b/security-c4po-angular/src/app/project-overview/project-overview.component.ts
index 200a6ae..d6419b1 100644
--- a/security-c4po-angular/src/app/project-overview/project-overview.component.ts
+++ b/security-c4po-angular/src/app/project-overview/project-overview.component.ts
@@ -1,6 +1,6 @@
import {Component, OnDestroy, OnInit} from '@angular/core';
import * as FA from '@fortawesome/free-solid-svg-icons';
-import {Project, SaveProjectDialogBody} from '@shared/models/project.model';
+import {Project, ProjectDialogBody} from '@shared/models/project.model';
import {BehaviorSubject, Observable} from 'rxjs';
import {untilDestroyed} from 'ngx-take-until-destroy';
import {ProjectService} from '@shared/services/project.service';
@@ -8,6 +8,7 @@ import {NotificationService, PopupType} from '@shared/services/notification.serv
import {catchError, filter, mergeMap, switchMap, tap} from 'rxjs/operators';
import {DialogService} from '@shared/services/dialog-service/dialog.service';
import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
+import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
@Component({
selector: 'app-project-overview',
@@ -24,6 +25,7 @@ export class ProjectOverviewComponent implements OnInit, OnDestroy {
constructor(
private readonly projectService: ProjectService,
private readonly dialogService: DialogService,
+ private readonly projectDialogService: ProjectDialogService,
private readonly notificationService: NotificationService) {
}
@@ -51,17 +53,18 @@ export class ProjectOverviewComponent implements OnInit, OnDestroy {
}
onClickAddProject(): void {
- this.dialogService.openCustomDialog(
+ this.projectDialogService.openProjectDialog(
ProjectDialogComponent,
+ null,
{
closeOnEsc: false,
hasScroll: false,
autoFocus: false,
closeOnBackdropClick: false
}
- ).onClose.pipe(
+ ).pipe(
filter(value => !!value),
- mergeMap((value: SaveProjectDialogBody) => this.projectService.saveProject(value)),
+ mergeMap((value: ProjectDialogBody) => this.projectService.saveProject(value)),
untilDestroyed(this)
).subscribe({
next: () => {
@@ -75,8 +78,30 @@ export class ProjectOverviewComponent implements OnInit, OnDestroy {
});
}
- onClickEditProject(): void {
- console.log('to be implemented...');
+ onClickEditProject(project: Project): void {
+ this.projectDialogService.openProjectDialog(
+ ProjectDialogComponent,
+ project,
+ {
+ closeOnEsc: false,
+ hasScroll: false,
+ autoFocus: false,
+ closeOnBackdropClick: false
+ }
+ ).pipe(
+ filter(value => !!value),
+ mergeMap((value: ProjectDialogBody) => this.projectService.updateProject(project.id, value)),
+ untilDestroyed(this)
+ ).subscribe({
+ next: () => {
+ this.loadProjects();
+ this.notificationService.showPopup('project.popup.update.success', PopupType.SUCCESS);
+ },
+ error: error => {
+ console.error(error);
+ this.notificationService.showPopup('project.popup.update.failed', PopupType.FAILURE);
+ }
+ });
}
onClickDeleteProject(project: Project): void {
diff --git a/security-c4po-angular/src/assets/i18n/de-DE.json b/security-c4po-angular/src/assets/i18n/de-DE.json
index c81a822..09241a3 100644
--- a/security-c4po-angular/src/assets/i18n/de-DE.json
+++ b/security-c4po-angular/src/assets/i18n/de-DE.json
@@ -3,6 +3,7 @@
"action.login": "Einloggen",
"action.retry": "Erneut Versuchen",
"action.save": "Speichern",
+ "action.update": "Aktualisieren",
"action.confirm": "Bestätigen",
"action.cancel": "Abbrechen",
"action.yes": "Ja",
@@ -14,7 +15,7 @@
"success": "✔",
"failure": "✘",
"warning": "!",
- "info": "",
+ "info": "ⓘ",
"error.position": {
"permissionDenied": "Berechtigung verweigert",
"timeout": "Zeitüberschreitung"
@@ -29,6 +30,13 @@
"unauthorized": "Benutzer nicht gefunden. Bitte registrieren und erneut versuchen"
},
"project": {
+ "title.label": "Projekt Titel",
+ "client.label": "Name des Auftraggebers",
+ "tester.label": "Name des Pentester",
+ "title": "Titel",
+ "client": "Klient",
+ "tester": "Tester",
+ "createdAt": "Erstellt am",
"overview": {
"add.project": "Projekt hinzufügen",
"no.projects": "Keine Projekte verfügbar"
@@ -36,23 +44,26 @@
"create": {
"header": "Neues Projekt erstellen"
},
+ "edit": {
+ "header": "Projekt editieren"
+ },
"delete": {
"title": "Projekt löschen",
"key": "Möchten Sie das Projekt \"{{name}}\" unwiderruflich löschen?"
},
+ "validationMessage": {
+ "titleRequired": "Titel ist erforderlich.",
+ "clientRequired": "Klient ist erforderlich.",
+ "testerRequired": "Tester ist erforderlich."
+ },
"popup": {
"not.found": "Keine Projekte gefunden",
"save.success": "Projekt erfolgreich gespeichert",
"save.failed": "Projekt konnte nicht gespeichert werden",
+ "update.success": "Projekt erfolgreich aktualisiert",
+ "update.failed": "Projekt konnte nicht aktualisiert werden",
"delete.success": "Projekt erfolgreich gelöscht",
"delete.failed": "Projekt konnte nicht gelöscht werden"
- },
- "title.label": "Projekt Titel",
- "client.label": "Name des Auftraggebers",
- "tester.label": "Name des Pentester",
- "title": "Titel",
- "client": "Klient",
- "tester": "Tester",
- "createdAt": "Erstellt am"
+ }
}
}
diff --git a/security-c4po-angular/src/assets/i18n/en-US.json b/security-c4po-angular/src/assets/i18n/en-US.json
index a1cb888..a1511d5 100644
--- a/security-c4po-angular/src/assets/i18n/en-US.json
+++ b/security-c4po-angular/src/assets/i18n/en-US.json
@@ -4,6 +4,7 @@
"action.retry": "Try again",
"action.confirm": "Confirm",
"action.save": "Save",
+ "action.update": "Update",
"action.cancel": "Cancel",
"action.yes": "Yes",
"action.no": "No",
@@ -14,7 +15,7 @@
"success": "✔",
"failure": "✘",
"warning": "!",
- "info": "",
+ "info": "ⓘ",
"error.position": {
"permissionDenied": "Permission denied",
"timeout": "Timeout"
@@ -29,30 +30,40 @@
"unauthorized": "User not found. Please register and try again"
},
"project": {
- "overview": {
- "add.project": "Add project",
- "no.projects": "No projects available"
- },
- "create": {
- "header": "Create New Project"
- },
- "delete": {
- "title": "Delete Project",
- "key": "Do you want to permanently delete the project \"{{name}}\"?"
- },
- "popup": {
- "not.found": "No projects found",
- "save.success": "Project saved successfully",
- "save.failed": "Project could not be saved",
- "delete.success": "Project deleted successfully",
- "delete.failed": "Project could not be deleted"
- },
"title.label": "Project Title",
"client.label": "Name of Client",
"tester.label": "Name of Pentester",
"title": "Title",
"client": "Client",
"tester": "Tester",
- "createdAt": "Created at"
+ "createdAt": "Created at",
+ "overview": {
+ "add.project": "Add Project",
+ "no.projects": "No projects available"
+ },
+ "create": {
+ "header": "Create New Project"
+ },
+ "edit": {
+ "header": "Edit Project"
+ },
+ "delete": {
+ "title": "Delete Project",
+ "key": "Do you want to permanently delete the project \"{{name}}\"?"
+ },
+ "validationMessage": {
+ "titleRequired": "Title is required.",
+ "clientRequired": "Client is required.",
+ "testerRequired": "Tester is required."
+ },
+ "popup": {
+ "not.found": "No projects found",
+ "save.success": "Project saved successfully",
+ "save.failed": "Project could not be saved",
+ "update.success": "Project updated successfully",
+ "update.failed": "Project could not be updated",
+ "delete.success": "Project deleted successfully",
+ "delete.failed": "Project could not be deleted"
+ }
}
}
diff --git a/security-c4po-angular/src/index.html b/security-c4po-angular/src/index.html
index d4d461d..690c807 100644
--- a/security-c4po-angular/src/index.html
+++ b/security-c4po-angular/src/index.html
@@ -2,10 +2,10 @@
-
SecurityC4POAngular
+
Security C4PO
-
+
diff --git a/security-c4po-angular/src/shared/config/global-variables.ts b/security-c4po-angular/src/shared/config/global-variables.ts
index 89e54d7..a41fd07 100644
--- a/security-c4po-angular/src/shared/config/global-variables.ts
+++ b/security-c4po-angular/src/shared/config/global-variables.ts
@@ -1,4 +1,4 @@
export const GlobalTitlesVariables = {
- SECURITYC4PO_TITLE: 'SecurityC4PO',
+ SECURITYC4PO_TITLE: 'Security C4PO',
NOVATEC_NAME: 'Novatec'
};
diff --git a/security-c4po-angular/src/shared/models/project-dialog-data.ts b/security-c4po-angular/src/shared/models/project-dialog-data.ts
new file mode 100644
index 0000000..37c2ed0
--- /dev/null
+++ b/security-c4po-angular/src/shared/models/project-dialog-data.ts
@@ -0,0 +1,26 @@
+export interface ProjectDialogData {
+ form: {
+ [key: string]: GenericFormFieldConfig // key is property name, e.g. title
+ };
+ options?: GenericFormFieldOption[];
+}
+
+export interface GenericFormFieldConfig {
+ fieldName: string;
+ type: string; // text, password, email
+ labelKey: string; // translation key of field label
+ placeholder: string; // translation key of placeholder text
+ controlsConfig: { [key: string]: any };
+ errors: GenericFormFieldError[];
+}
+
+export interface GenericFormFieldError {
+ errorCode: string;
+ translationKey: string;
+}
+
+export interface GenericFormFieldOption {
+ headerLabelKey: string;
+ buttonKey: string;
+ accentColor: string;
+}
diff --git a/security-c4po-angular/src/shared/models/project.model.ts b/security-c4po-angular/src/shared/models/project.model.ts
index 8741e42..0c8532c 100644
--- a/security-c4po-angular/src/shared/models/project.model.ts
+++ b/security-c4po-angular/src/shared/models/project.model.ts
@@ -21,7 +21,7 @@ export class Project {
}
}
-export interface SaveProjectDialogBody {
+export interface ProjectDialogBody {
title: string;
client: string;
tester: string;
diff --git a/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.html b/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.html
index 0b84d7b..157d43e 100644
--- a/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.html
+++ b/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.html
@@ -1,48 +1,43 @@
-
+
-
-
diff --git a/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.scss b/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.scss
index a8fa406..7fc74d7 100644
--- a/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.scss
+++ b/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.scss
@@ -1,8 +1,9 @@
@import "../../../assets/@theme/styles/_dialog.scss";
+@import '../../../assets/@theme/styles/themes';
.project-dialog {
- width: 24rem;
- height: 31rem;
+ width: 25.25rem;
+ height: 35rem;
.project-dialog-header {
height: 8vh;
@@ -20,6 +21,12 @@
}
.input {
- width: 15rem;
+ width: 18rem;
+ margin-bottom: 0.5rem;
+ }
+
+ .error-text {
+ float: left;
+ color: nb-theme(color-danger-default);;
}
}
diff --git a/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.spec.ts b/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.spec.ts
index 4f0802e..d099b3b 100644
--- a/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.spec.ts
+++ b/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.spec.ts
@@ -1,8 +1,15 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
-
import {ProjectDialogComponent} from './project-dialog.component';
import {CommonModule} from '@angular/common';
-import {NbButtonModule, NbCardModule, NbDialogRef, NbFormFieldModule, NbInputModule, NbLayoutModule} from '@nebular/theme';
+import {
+ NB_DIALOG_CONFIG,
+ NbButtonModule,
+ NbCardModule,
+ NbDialogRef,
+ NbFormFieldModule,
+ NbInputModule,
+ NbLayoutModule
+} from '@nebular/theme';
import {FlexLayoutModule} from '@angular/flex-layout';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {ThemeModule} from '@assets/@theme/theme.module';
@@ -14,13 +21,18 @@ import {NotificationService} from '@shared/services/notification.service';
import {NotificationServiceMock} from '@shared/services/notification.service.mock';
import {DialogService} from '@shared/services/dialog-service/dialog.service';
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
-import {ReactiveFormsModule} from '@angular/forms';
+import {ReactiveFormsModule, Validators} from '@angular/forms';
+import {Project} from '@shared/models/project.model';
+import Mock = jest.Mock;
+import deepEqual from 'deep-equal';
describe('ProjectDialogComponent', () => {
let component: ProjectDialogComponent;
let fixture: ComponentFixture;
beforeEach(async () => {
+ const dialogSpy = createSpyObj('NbDialogRef', ['close']);
+
await TestBed.configureTestingModule({
declarations: [
ProjectDialogComponent
@@ -49,12 +61,14 @@ describe('ProjectDialogComponent', () => {
providers: [
{provide: NotificationService, useValue: new NotificationServiceMock()},
{provide: DialogService, useClass: DialogServiceMock},
- {provide: NbDialogRef, useValue: {}}
+ {provide: NbDialogRef, useValue: dialogSpy},
+ {provide: NB_DIALOG_CONFIG, useValue: mockedDialogData}
]
}).compileComponents();
});
beforeEach(() => {
+ TestBed.overrideProvider(NB_DIALOG_CONFIG, {useValue: mockedDialogData});
fixture = TestBed.createComponent(ProjectDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
@@ -64,3 +78,71 @@ describe('ProjectDialogComponent', () => {
expect(component).toBeTruthy();
});
});
+
+export const createSpyObj = (baseName, methodNames): { [key: string]: Mock } => {
+ const obj: any = {};
+ for (const i of methodNames) {
+ obj[i] = jest.fn();
+ }
+ return obj;
+};
+
+export const mockProject: Project = {
+ id: '11-22-33',
+ title: 'Test Project',
+ client: 'Testclient',
+ tester: 'Testpentester',
+ createdAt: new Date(),
+ createdBy: 'UID-11-12-13'
+};
+
+export const mockedDialogData = {
+ form: {
+ projectTitle: {
+ fieldName: 'projectTitle',
+ type: 'text',
+ labelKey: 'project.title.label',
+ placeholder: 'project.title',
+ controlsConfig: [
+ {value: mockProject ? mockProject.title : '', disabled: false},
+ [Validators.required]
+ ],
+ errors: [
+ {errorCode: 'required', translationKey: 'project.validationMessage.titleRequired'}
+ ]
+ },
+ projectClient: {
+ fieldName: 'projectClient',
+ type: 'text',
+ labelKey: 'project.client.label',
+ placeholder: 'project.client',
+ controlsConfig: [
+ {value: mockProject ? mockProject.client : '', disabled: false},
+ [Validators.required]
+ ],
+ errors: [
+ {errorCode: 'required', translationKey: 'project.validationMessage.clientRequired'}
+ ]
+ },
+ projectTester: {
+ fieldName: 'projectTester',
+ type: 'text',
+ labelKey: 'project.tester.label',
+ placeholder: 'project.tester',
+ controlsConfig: [
+ {value: mockProject ? mockProject.tester : '', disabled: false},
+ [Validators.required]
+ ],
+ errors: [
+ {errorCode: 'required', translationKey: 'project.validationMessage.testerRequired'}
+ ]
+ }
+ },
+ options: [
+ {
+ headerLabelKey: 'project.edit.header',
+ buttonKey: 'global.action.update',
+ accentColor: 'warning'
+ },
+ ]
+};
diff --git a/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.ts b/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.ts
index 2d7a718..1e9ebd3 100644
--- a/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.ts
+++ b/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.component.ts
@@ -1,8 +1,8 @@
-import {Component, OnDestroy, OnInit} from '@angular/core';
-import {NbDialogRef} from '@nebular/theme';
-import {AbstractControl, FormBuilder, FormGroup, Validators} from '@angular/forms';
-import {FieldStatus} from '@shared/models/form-field-status.model';
-import {untilDestroyed} from 'ngx-take-until-destroy';
+import {Component, Inject, OnDestroy, OnInit} from '@angular/core';
+import {NB_DIALOG_CONFIG, NbDialogRef} from '@nebular/theme';
+import {FormBuilder, FormGroup} from '@angular/forms';
+import {GenericFormFieldConfig, ProjectDialogData} from '@shared/models/project-dialog-data';
+import deepEqual from 'deep-equal';
@Component({
selector: 'app-project-dialog',
@@ -12,40 +12,29 @@ import {untilDestroyed} from 'ngx-take-until-destroy';
export class ProjectDialogComponent implements OnInit, OnDestroy {
// form control elements
projectFormGroup: FormGroup;
- projectTitleCtrl: AbstractControl;
- projectClientCtrl: AbstractControl;
- projectTesterCtrl: AbstractControl;
+ formArray: GenericFormFieldConfig[];
- formCtrlStatus = FieldStatus.BASIC;
-
- invalidProjectTitle: string;
- invalidProjectClient: string;
- invalidProjectTester: string;
-
- readonly MIN_LENGTH: number = 2;
+ dialogData: ProjectDialogData;
constructor(
+ @Inject(NB_DIALOG_CONFIG) private data: ProjectDialogData,
private fb: FormBuilder,
protected dialogRef: NbDialogRef
) {
}
ngOnInit(): void {
- this.projectFormGroup = this.fb.group({
- projectTitle: ['', [Validators.required, Validators.minLength(this.MIN_LENGTH)]],
- projectClient: ['', [Validators.required, Validators.minLength(this.MIN_LENGTH)]],
- projectTester: ['', [Validators.required, Validators.minLength(this.MIN_LENGTH)]]
- });
+ this.projectFormGroup = this.generateFormCreationFieldArray();
+ this.dialogData = this.data;
+ }
- this.projectTitleCtrl = this.projectFormGroup.get('projectTitle');
- this.projectClientCtrl = this.projectFormGroup.get('projectClient');
- this.projectTesterCtrl = this.projectFormGroup.get('projectTester');
-
- this.projectFormGroup.valueChanges
- .pipe(untilDestroyed(this))
- .subscribe(() => {
- this.formCtrlStatus = FieldStatus.BASIC;
- });
+ generateFormCreationFieldArray(): FormGroup {
+ this.formArray = Object.values(this.data.form);
+ const config = this.formArray?.reduce((accumulator: {}, currentValue: GenericFormFieldConfig) => ({
+ ...accumulator,
+ [currentValue?.fieldName]: currentValue?.controlsConfig
+ }), {});
+ return this.fb.group(config);
}
onClickSave(value): void {
@@ -60,23 +49,41 @@ export class ProjectDialogComponent implements OnInit, OnDestroy {
this.dialogRef.close();
}
- formIsEmptyOrInvalid(): boolean {
- return this.isEmpty(this.projectTitleCtrl.value)
- || this.isEmpty(this.projectClientCtrl.value)
- || this.isEmpty(this.projectTesterCtrl.value)
- || this.projectTitleCtrl.invalid
- || this.projectClientCtrl.invalid
- || this.projectTesterCtrl.invalid;
+ allowSave(): boolean {
+ return this.projectFormGroup.valid && this.projectDataChanged();
}
/**
- * @param ctrlValue of type string
- * @return if ctrlValue is empty or not
+ * @return true if project data is different from initial value
*/
- isEmpty(ctrlValue: string): boolean {
- return ctrlValue === '';
+ private projectDataChanged(): boolean {
+ const oldProjectData = this.parseInitializedProjectDialogData(this.dialogData);
+ const newProjectData = this.projectFormGroup.getRawValue();
+ Object.entries(newProjectData).forEach(entry => {
+ const [key, value] = entry;
+ if (value === null) {
+ newProjectData[key] = '';
+ }
+ });
+ const didChange = !deepEqual(oldProjectData, newProjectData);
+ return didChange;
+ }
+
+ /**
+ * @param dialogData of type ProjectDialogData
+ * @return parsed projectData
+ */
+ private parseInitializedProjectDialogData(dialogData: ProjectDialogData): any {
+ const projectData = {};
+ Object.entries(dialogData.form).forEach(entry => {
+ const [key, value] = entry;
+ projectData[key] = value.controlsConfig[0] ?
+ (value.controlsConfig[0].value ? value.controlsConfig[0].value : value.controlsConfig[0]) : '';
+ });
+ return projectData;
}
ngOnDestroy(): void {
+ // ToDo: Remove this after Angular upgrade and use @UnitDestroy() instead
}
}
diff --git a/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.module.ts b/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.module.ts
index 6e46c61..0b06202 100644
--- a/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.module.ts
+++ b/security-c4po-angular/src/shared/modules/project-dialog/project-dialog.module.ts
@@ -7,6 +7,7 @@ import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
import {TranslateModule} from '@ngx-translate/core';
import {DialogService} from '@shared/services/dialog-service/dialog.service';
import {ReactiveFormsModule} from '@angular/forms';
+import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
@NgModule({
declarations: [
@@ -25,6 +26,7 @@ import {ReactiveFormsModule} from '@angular/forms';
],
providers: [
DialogService,
+ ProjectDialogService,
NbDialogService
],
entryComponents: [
diff --git a/security-c4po-angular/src/shared/modules/project-dialog/service/project-dialog.service.mock.ts b/security-c4po-angular/src/shared/modules/project-dialog/service/project-dialog.service.mock.ts
new file mode 100644
index 0000000..685e209
--- /dev/null
+++ b/security-c4po-angular/src/shared/modules/project-dialog/service/project-dialog.service.mock.ts
@@ -0,0 +1,17 @@
+import {ComponentType} from '@angular/cdk/overlay';
+import {NbDialogConfig} from '@nebular/theme';
+import {ProjectDialogService} from '@shared/modules/project-dialog/service/project-dialog.service';
+import {Project} from '@shared/models/project.model';
+import {Observable, of} from 'rxjs';
+
+export class ProjectDialogServiceMock implements Required {
+
+ dialog: any;
+
+ openProjectDialog(
+ componentOrTemplateRef: ComponentType,
+ project: Project | undefined,
+ config: Partial | string>> | undefined): Observable {
+ return of(undefined);
+ }
+}
diff --git a/security-c4po-angular/src/shared/modules/project-dialog/service/project-dialog.service.spec.ts b/security-c4po-angular/src/shared/modules/project-dialog/service/project-dialog.service.spec.ts
new file mode 100644
index 0000000..bfbc628
--- /dev/null
+++ b/security-c4po-angular/src/shared/modules/project-dialog/service/project-dialog.service.spec.ts
@@ -0,0 +1,30 @@
+import { TestBed } from '@angular/core/testing';
+
+import { ProjectDialogService } from './project-dialog.service';
+import {HttpClientTestingModule} from '@angular/common/http/testing';
+import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
+import {NbDialogModule, NbDialogRef} from '@nebular/theme';
+import {ProjectDialogServiceMock} from '@shared/modules/project-dialog/service/project-dialog.service.mock';
+
+describe('ProjectDialogService', () => {
+ let service: ProjectDialogService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ HttpClientTestingModule,
+ BrowserAnimationsModule,
+ NbDialogModule.forRoot()
+ ],
+ providers: [
+ {provide: ProjectDialogService, useClass: ProjectDialogServiceMock},
+ {provide: NbDialogRef, useValue: {}},
+ ]
+ });
+ service = TestBed.inject(ProjectDialogService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/security-c4po-angular/src/shared/modules/project-dialog/service/project-dialog.service.ts b/security-c4po-angular/src/shared/modules/project-dialog/service/project-dialog.service.ts
new file mode 100644
index 0000000..1e89fba
--- /dev/null
+++ b/security-c4po-angular/src/shared/modules/project-dialog/service/project-dialog.service.ts
@@ -0,0 +1,102 @@
+import {Injectable} from '@angular/core';
+import {NbDialogConfig, NbDialogService} from '@nebular/theme';
+import {ComponentType} from '@angular/cdk/overlay';
+import {Observable} from 'rxjs';
+import {Project} from '@shared/models/project.model';
+import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
+import {Validators} from '@angular/forms';
+import {ProjectDialogData} from '@shared/models/project-dialog-data';
+
+@Injectable()
+export class ProjectDialogService {
+
+ constructor(private readonly dialog: NbDialogService) {
+ }
+
+ private readonly MIN_LENGTH: number = 4;
+
+ static addDataToDialogConfig(
+ dialogOptions?: Partial | string>>,
+ projectData?: ProjectDialogData
+ ): Partial | string>> {
+ return {
+ context: {data: projectData},
+ closeOnEsc: dialogOptions?.closeOnEsc || false,
+ hasScroll: dialogOptions?.hasScroll || false,
+ autoFocus: dialogOptions?.autoFocus || false,
+ closeOnBackdropClick: dialogOptions?.closeOnBackdropClick || false
+ };
+ }
+
+ public openProjectDialog(componentOrTemplateRef: ComponentType,
+ project?: Project,
+ config?: Partial | string>>): Observable {
+ let dialogOptions: Partial | string>>;
+ let dialogData: ProjectDialogData;
+ // Setup ProjectDialogData
+ dialogData = {
+ form: {
+ projectTitle: {
+ fieldName: 'projectTitle',
+ type: 'text',
+ labelKey: 'project.title.label',
+ placeholder: 'project.title',
+ controlsConfig: [
+ {value: project ? project.title : '', disabled: false},
+ [Validators.required]
+ ],
+ errors: [
+ {errorCode: 'required', translationKey: 'project.validationMessage.titleRequired'}
+ ]
+ },
+ projectClient: {
+ fieldName: 'projectClient',
+ type: 'text',
+ labelKey: 'project.client.label',
+ placeholder: 'project.client',
+ controlsConfig: [
+ {value: project ? project.client : '', disabled: false},
+ [Validators.required]
+ ],
+ errors: [
+ {errorCode: 'required', translationKey: 'project.validationMessage.clientRequired'}
+ ]
+ },
+ projectTester: {
+ fieldName: 'projectTester',
+ type: 'text',
+ labelKey: 'project.tester.label',
+ placeholder: 'project.tester',
+ controlsConfig: [
+ {value: project ? project.tester : '', disabled: false},
+ [Validators.required]
+ ],
+ errors: [
+ {errorCode: 'required', translationKey: 'project.validationMessage.testerRequired'}
+ ]
+ }
+ },
+ options: []
+ };
+ if (project) {
+ dialogData.options = [
+ {
+ headerLabelKey: 'project.edit.header',
+ buttonKey: 'global.action.update',
+ accentColor: 'warning'
+ },
+ ];
+ } else {
+ dialogData.options = [
+ {
+ headerLabelKey: 'project.create.header',
+ buttonKey: 'global.action.save',
+ accentColor: 'primary'
+ },
+ ];
+ }
+ // Merge dialog config with project data
+ dialogOptions = ProjectDialogService.addDataToDialogConfig(config, dialogData);
+ return this.dialog.open(ProjectDialogComponent, dialogOptions).onClose;
+ }
+}
diff --git a/security-c4po-angular/src/shared/services/dialog-service/dialog.service.ts b/security-c4po-angular/src/shared/services/dialog-service/dialog.service.ts
index 1aa701c..1a52226 100644
--- a/security-c4po-angular/src/shared/services/dialog-service/dialog.service.ts
+++ b/security-c4po-angular/src/shared/services/dialog-service/dialog.service.ts
@@ -24,7 +24,7 @@ export class DialogService {
closeOnEsc: config?.closeOnEsc || false,
hasScroll: config?.hasScroll || false,
autoFocus: config?.autoFocus || false,
- closeOnBackdropClick: config?.closeOnBackdropClick || false,
+ closeOnBackdropClick: config?.closeOnBackdropClick || false
});
}
@@ -35,7 +35,7 @@ export class DialogService {
*/
openConfirmDialog(message: DialogMessage): NbDialogRef {
return this.dialog.open(ConfirmDialogComponent, {
- closeOnEsc: false,
+ closeOnEsc: true,
hasScroll: false,
autoFocus: false,
closeOnBackdropClick: false,
diff --git a/security-c4po-angular/src/shared/services/project.service.mock.ts b/security-c4po-angular/src/shared/services/project.service.mock.ts
index c4e198e..4ce3c1f 100644
--- a/security-c4po-angular/src/shared/services/project.service.mock.ts
+++ b/security-c4po-angular/src/shared/services/project.service.mock.ts
@@ -1,7 +1,7 @@
import {ProjectService} from '@shared/services/project.service';
import {HttpClient} from '@angular/common/http';
import {Observable, of} from 'rxjs';
-import {Project, SaveProjectDialogBody} from '@shared/models/project.model';
+import {Project, ProjectDialogBody} from '@shared/models/project.model';
export class ProjectServiceMock implements Required {
@@ -12,7 +12,11 @@ export class ProjectServiceMock implements Required {
return of([]);
}
- saveProject(saveProject: SaveProjectDialogBody): Observable {
+ saveProject(saveProject: ProjectDialogBody): Observable {
+ return of();
+ }
+
+ updateProject(projectId: string, project: ProjectDialogBody): Observable {
return of();
}
diff --git a/security-c4po-angular/src/shared/services/project.service.spec.ts b/security-c4po-angular/src/shared/services/project.service.spec.ts
index ca8e4c6..e655285 100644
--- a/security-c4po-angular/src/shared/services/project.service.spec.ts
+++ b/security-c4po-angular/src/shared/services/project.service.spec.ts
@@ -4,7 +4,7 @@ import {ProjectService} from './project.service';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {KeycloakService} from 'keycloak-angular';
-import {Project, SaveProjectDialogBody} from '@shared/models/project.model';
+import {Project, ProjectDialogBody} from '@shared/models/project.model';
import {environment} from '../../environments/environment';
describe('ProjectService', () => {
@@ -76,7 +76,7 @@ describe('ProjectService', () => {
describe('saveProject', () => {
// arrange
- const mockSaveProjectDialogBody: SaveProjectDialogBody = {
+ const mockSaveProjectDialogBody: ProjectDialogBody = {
client: 'E Corp',
title: 'Some Mock API (v1.0) Scanning',
tester: 'Novatester',
diff --git a/security-c4po-angular/src/shared/services/project.service.ts b/security-c4po-angular/src/shared/services/project.service.ts
index beb0678..11a9826 100644
--- a/security-c4po-angular/src/shared/services/project.service.ts
+++ b/security-c4po-angular/src/shared/services/project.service.ts
@@ -1,7 +1,7 @@
import {Injectable} from '@angular/core';
import {environment} from '../../environments/environment';
import {HttpClient} from '@angular/common/http';
-import {Project, SaveProjectDialogBody} from '../models/project.model';
+import {Project, ProjectDialogBody} from '../models/project.model';
import {Observable} from 'rxjs';
@Injectable({
@@ -25,10 +25,20 @@ export class ProjectService {
* Save Project
* @param project the information of the project
*/
- public saveProject(project: SaveProjectDialogBody): Observable {
+ public saveProject(project: ProjectDialogBody): Observable {
return this.http.post(`${this.apiBaseURL}`, project);
}
+ /**
+ * Update Project
+ * @param projectId the id of the project
+ * @param project the information of the project
+ */
+ public updateProject(projectId: string, project: ProjectDialogBody): Observable {
+ console.log('update Project');
+ return this.http.patch(`${this.apiBaseURL}/${projectId}`, project);
+ }
+
/**
* Delete Project
* @param projectId the id of the project
diff --git a/security-c4po-angular/tsconfig.json b/security-c4po-angular/tsconfig.json
index bf60e0f..d9d85c4 100644
--- a/security-c4po-angular/tsconfig.json
+++ b/security-c4po-angular/tsconfig.json
@@ -7,6 +7,7 @@
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
+ "esModuleInterop": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"moduleResolution": "node",
diff --git a/security-c4po-angular/tsconfig.spec.json b/security-c4po-angular/tsconfig.spec.json
index 2b6775c..d18c4fa 100644
--- a/security-c4po-angular/tsconfig.spec.json
+++ b/security-c4po-angular/tsconfig.spec.json
@@ -6,6 +6,7 @@
"jest"
],
"module": "commonjs",
+ "esModuleInterop": true,
"emitDecoratorMetadata": true,
"allowJs": true,
},
diff --git a/security-c4po-cfg/kc/docker-compose.keycloak.yml b/security-c4po-cfg/kc/docker-compose.keycloak.yml
index 050428c..51204a1 100644
--- a/security-c4po-cfg/kc/docker-compose.keycloak.yml
+++ b/security-c4po-cfg/kc/docker-compose.keycloak.yml
@@ -12,7 +12,7 @@ services:
- ../cfg/keycloak.env
c4po-keycloak-postgress:
container_name: c4po-keycloak-postgres
- image: postgres:latest
+ image: postgres:10.16-alpine
env_file:
- ../cfg/keycloakdb.env
ports: