feat: As a user I want to see my profile in the header and log myself out manually
This commit is contained in:
parent
3be43fa96e
commit
a37e06f8ca
|
@ -12,7 +12,7 @@ import {
|
||||||
NbSelectModule,
|
NbSelectModule,
|
||||||
NbThemeModule,
|
NbThemeModule,
|
||||||
NbOverlayContainerAdapter,
|
NbOverlayContainerAdapter,
|
||||||
NbDialogModule,
|
NbDialogModule, NbMenuModule,
|
||||||
} from '@nebular/theme';
|
} from '@nebular/theme';
|
||||||
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
|
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
|
||||||
import {HttpClient, HttpClientModule} from '@angular/common/http';
|
import {HttpClient, HttpClientModule} from '@angular/common/http';
|
||||||
|
@ -58,6 +58,7 @@ import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||||
FontAwesomeModule,
|
FontAwesomeModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
ThemeModule.forRoot(),
|
ThemeModule.forRoot(),
|
||||||
|
NbMenuModule.forRoot(),
|
||||||
NbSelectModule,
|
NbSelectModule,
|
||||||
NgxsModule.forRoot([SessionState, ProjectState], {developmentMode: !environment.production}),
|
NgxsModule.forRoot([SessionState, ProjectState], {developmentMode: !environment.production}),
|
||||||
NgxsLoggerPluginModule.forRoot({developmentMode: !environment.production}),
|
NgxsLoggerPluginModule.forRoot({developmentMode: !environment.production}),
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
||||||
import {FlexLayoutModule, FlexModule} from '@angular/flex-layout';
|
import {FlexLayoutModule, FlexModule} from '@angular/flex-layout';
|
||||||
import {MomentModule} from 'ngx-moment';
|
import {MomentModule} from 'ngx-moment';
|
||||||
import {NotificationService} from '@shared/services/toaster-service/notification.service';
|
import {NotificationService} from '@shared/services/toaster-service/notification.service';
|
||||||
import {NbOverlayContainerAdapter, NbSpinnerModule, NbToastrModule} from '@nebular/theme';
|
import {NbMenuModule, NbOverlayContainerAdapter, NbSpinnerModule, NbToastrModule} from '@nebular/theme';
|
||||||
import {ThemeModule} from '@assets/@theme/theme.module';
|
import {ThemeModule} from '@assets/@theme/theme.module';
|
||||||
import {LoadingSpinnerComponent} from '@shared/widgets/loading-spinner/loading-spinner.component';
|
import {LoadingSpinnerComponent} from '@shared/widgets/loading-spinner/loading-spinner.component';
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
|
||||||
FontAwesomeModule,
|
FontAwesomeModule,
|
||||||
FlexLayoutModule,
|
FlexLayoutModule,
|
||||||
ThemeModule.forRoot(),
|
ThemeModule.forRoot(),
|
||||||
|
NbMenuModule.forRoot(),
|
||||||
FlexModule,
|
FlexModule,
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
TranslateModule.forChild({
|
TranslateModule.forChild({
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
<div class="filler"></div>
|
<div class="filler"></div>
|
||||||
<div fxLayoutGap="4rem">
|
<div fxLayoutGap="4rem">
|
||||||
<nb-actions size="medium">
|
<nb-actions size="medium">
|
||||||
|
<!--Theme Action-->
|
||||||
<nb-action class="toggle-theme">
|
<nb-action class="toggle-theme">
|
||||||
<button nbButton
|
<button nbButton
|
||||||
(click)="onClickGoToLink('https://owasp.org/www-project-web-security-testing-guide/v42/')">
|
(click)="onClickGoToLink('https://owasp.org/www-project-web-security-testing-guide/v42/')">
|
||||||
|
@ -19,7 +20,6 @@
|
||||||
<span class="owasp-redirect-button">OWASP</span>
|
<span class="owasp-redirect-button">OWASP</span>
|
||||||
</button>
|
</button>
|
||||||
</nb-action>
|
</nb-action>
|
||||||
|
|
||||||
<nb-action class="toggle-theme">
|
<nb-action class="toggle-theme">
|
||||||
<button nbButton
|
<button nbButton
|
||||||
(click)="onClickSwitchTheme()">
|
(click)="onClickSwitchTheme()">
|
||||||
|
@ -30,16 +30,29 @@
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</button>
|
</button>
|
||||||
</nb-action>
|
</nb-action>
|
||||||
|
<!--Language Action-->
|
||||||
|
<nb-action class="language-menu">
|
||||||
|
<div *ngIf="selectedLanguage && languages" class="languageContainer">
|
||||||
|
<nb-select selected="{{selectedLanguage}}" fullWidth>
|
||||||
|
<nb-option *ngFor="let language of languages"
|
||||||
|
value="{{language}}" (click)="onClickLanguage(language)" fxLayoutAlign="start center">
|
||||||
|
<img src="../../assets/images/flags/{{language}}.svg" class="flag" width="25rem" height="16rem" alt="">
|
||||||
|
<span fxFlexOffset="0.5rem"> {{'languageKeys.' + language | translate}} </span>
|
||||||
|
</nb-option>
|
||||||
|
</nb-select>
|
||||||
|
</div>
|
||||||
|
</nb-action>
|
||||||
|
<!--User Action-->
|
||||||
|
<nb-action class="user">
|
||||||
|
<!--<fa-icon [icon]="fa.faUser" class="user-icon">
|
||||||
|
</fa-icon>-->
|
||||||
|
<nb-user [nbContextMenu]="userMenu"
|
||||||
|
[picture]="FALLBACK_IMG"
|
||||||
|
name="{{user?.getValue()?.username}}"
|
||||||
|
title="Pentester">
|
||||||
|
</nb-user>
|
||||||
|
</nb-action>
|
||||||
</nb-actions>
|
</nb-actions>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="selectedLanguage && languages" class="languageContainer">
|
|
||||||
<nb-select selected="{{selectedLanguage}}" fullWidth>
|
|
||||||
<nb-option *ngFor="let language of languages"
|
|
||||||
value="{{language}}" (click)="onClickLanguage(language)" fxLayoutAlign="start center">
|
|
||||||
<img src="../../assets/images/flags/{{language}}.svg" class="flag" width="25rem" height="16rem" alt="">
|
|
||||||
<span fxFlexOffset="0.5rem"> {{'languageKeys.' + language | translate}} </span>
|
|
||||||
</nb-option>
|
|
||||||
</nb-select>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -3,17 +3,30 @@ import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||||
import {HeaderComponent} from './header.component';
|
import {HeaderComponent} from './header.component';
|
||||||
import {CommonModule} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
import {FontAwesomeTestingModule} from '@fortawesome/angular-fontawesome/testing';
|
import {FontAwesomeTestingModule} from '@fortawesome/angular-fontawesome/testing';
|
||||||
import {NbActionsModule, NbSelectModule} from '@nebular/theme';
|
import {NbActionsModule, NbMenuModule, NbMenuService, NbSelectModule} from '@nebular/theme';
|
||||||
import {ThemeModule} from '@assets/@theme/theme.module';
|
import {ThemeModule} from '@assets/@theme/theme.module';
|
||||||
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
|
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
|
||||||
import {HttpLoaderFactory} from '../common-app.module';
|
import {HttpLoaderFactory} from '../common-app.module';
|
||||||
import {HttpClient} from '@angular/common/http';
|
import {HttpClient} from '@angular/common/http';
|
||||||
import {RouterTestingModule} from '@angular/router/testing';
|
import {RouterTestingModule} from '@angular/router/testing';
|
||||||
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
import {HttpClientTestingModule} from '@angular/common/http/testing';
|
||||||
|
import {NgxsModule, Store} from '@ngxs/store';
|
||||||
|
import {KeycloakService} from 'keycloak-angular';
|
||||||
|
import {SESSION_STATE_NAME, SessionState, SessionStateModel} from '@shared/stores/session-state/session-state';
|
||||||
|
import {User} from '@shared/models/user.model';
|
||||||
|
|
||||||
|
const DESIRED_STORE_STATE_SESSION: SessionStateModel = {
|
||||||
|
userAccount: {
|
||||||
|
...new User('ttt', 'test', 'user', 'default.user@test.de', 'en-US'),
|
||||||
|
id: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
|
||||||
|
},
|
||||||
|
isAuthenticated: true
|
||||||
|
};
|
||||||
|
|
||||||
describe('HeaderComponent', () => {
|
describe('HeaderComponent', () => {
|
||||||
let component: HeaderComponent;
|
let component: HeaderComponent;
|
||||||
let fixture: ComponentFixture<HeaderComponent>;
|
let fixture: ComponentFixture<HeaderComponent>;
|
||||||
|
let store: Store;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
|
@ -26,6 +39,7 @@ describe('HeaderComponent', () => {
|
||||||
NbSelectModule,
|
NbSelectModule,
|
||||||
FontAwesomeTestingModule,
|
FontAwesomeTestingModule,
|
||||||
HttpClientTestingModule,
|
HttpClientTestingModule,
|
||||||
|
NbMenuModule,
|
||||||
ThemeModule.forRoot(),
|
ThemeModule.forRoot(),
|
||||||
TranslateModule.forRoot({
|
TranslateModule.forRoot({
|
||||||
loader: {
|
loader: {
|
||||||
|
@ -34,14 +48,23 @@ describe('HeaderComponent', () => {
|
||||||
deps: [HttpClient]
|
deps: [HttpClient]
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
RouterTestingModule.withRoutes([])
|
RouterTestingModule.withRoutes([]),
|
||||||
|
NgxsModule.forRoot([SessionState])
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
NbMenuService,
|
||||||
|
KeycloakService
|
||||||
]
|
]
|
||||||
})
|
}).compileComponents();
|
||||||
.compileComponents();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(HeaderComponent);
|
fixture = TestBed.createComponent(HeaderComponent);
|
||||||
|
store = TestBed.inject(Store);
|
||||||
|
store.reset({
|
||||||
|
...store.snapshot(),
|
||||||
|
[SESSION_STATE_NAME]: DESIRED_STORE_STATE_SESSION
|
||||||
|
});
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,19 +1,29 @@
|
||||||
import {Component, OnDestroy, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import * as FA from '@fortawesome/free-solid-svg-icons';
|
import * as FA from '@fortawesome/free-solid-svg-icons';
|
||||||
import {NbThemeService} from '@nebular/theme';
|
import {NbMenuItem, NbMenuService, NbThemeService} from '@nebular/theme';
|
||||||
import {map} from 'rxjs/operators';
|
import {map} from 'rxjs/operators';
|
||||||
import {GlobalTitlesVariables} from '@shared/config/global-variables';
|
import {GlobalTitlesVariables} from '@shared/config/global-variables';
|
||||||
import {TranslateService} from '@ngx-translate/core';
|
import {TranslateService} from '@ngx-translate/core';
|
||||||
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
||||||
|
import {KeycloakService} from 'keycloak-angular';
|
||||||
|
import {Store} from '@ngxs/store';
|
||||||
|
import {ResetSession} from '@shared/stores/session-state/session-state.actions';
|
||||||
|
import {UserService} from '@shared/services/user-service/user.service';
|
||||||
|
import {User} from '@shared/models/user.model';
|
||||||
|
import {BehaviorSubject} from 'rxjs';
|
||||||
|
import {Route} from '@shared/models/route.enum';
|
||||||
|
import {environment} from '../../environments/environment';
|
||||||
|
import {Router} from '@angular/router';
|
||||||
|
|
||||||
@UntilDestroy()
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-header',
|
selector: 'app-header',
|
||||||
templateUrl: './header.component.html',
|
templateUrl: './header.component.html',
|
||||||
styleUrls: ['./header.component.scss']
|
styleUrls: ['./header.component.scss']
|
||||||
})
|
})
|
||||||
export class HeaderComponent implements OnInit{
|
@UntilDestroy()
|
||||||
|
export class HeaderComponent implements OnInit {
|
||||||
|
|
||||||
|
// HTML only
|
||||||
readonly fa = FA;
|
readonly fa = FA;
|
||||||
readonly SECURITYC4PO_TITLE: string = GlobalTitlesVariables.SECURITYC4PO_TITLE;
|
readonly SECURITYC4PO_TITLE: string = GlobalTitlesVariables.SECURITYC4PO_TITLE;
|
||||||
|
|
||||||
|
@ -21,16 +31,59 @@ export class HeaderComponent implements OnInit{
|
||||||
languages = ['en-US', 'de-DE'];
|
languages = ['en-US', 'de-DE'];
|
||||||
selectedLanguage = '';
|
selectedLanguage = '';
|
||||||
|
|
||||||
constructor(private themeService: NbThemeService, private translateService: TranslateService) { }
|
// User Menu Properties
|
||||||
|
userPictureOnly = false;
|
||||||
|
user: BehaviorSubject<User> = new BehaviorSubject<User>(null);
|
||||||
|
userMenu: NbMenuItem[] = [{title: '', pathMatch: 'prefix'}];
|
||||||
|
readonly FALLBACK_IMG = 'assets/images/demo/anon-user-icon.png';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private store: Store,
|
||||||
|
private router: Router,
|
||||||
|
private themeService: NbThemeService,
|
||||||
|
private translateService: TranslateService,
|
||||||
|
private menuService: NbMenuService,
|
||||||
|
private userService: UserService,
|
||||||
|
protected keycloakService: KeycloakService) {
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
// Handle theme selection
|
||||||
this.themeService.onThemeChange()
|
this.themeService.onThemeChange()
|
||||||
.pipe(
|
.pipe(
|
||||||
map(({ name }) => name),
|
map(({name}) => name),
|
||||||
untilDestroyed(this),
|
untilDestroyed(this),
|
||||||
)
|
).subscribe(themeName => this.currentTheme = themeName);
|
||||||
.subscribe(themeName => this.currentTheme = themeName);
|
|
||||||
this.selectedLanguage = this.translateService.currentLang;
|
this.selectedLanguage = this.translateService.currentLang;
|
||||||
|
// Load user profile
|
||||||
|
this.userService.loadUserProfile().pipe(
|
||||||
|
untilDestroyed(this)
|
||||||
|
).subscribe({
|
||||||
|
next: (user: User) => {
|
||||||
|
this.user.next(user);
|
||||||
|
},
|
||||||
|
error: err => {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Handle user profile manu selection
|
||||||
|
this.menuService.onItemClick()
|
||||||
|
.pipe(
|
||||||
|
untilDestroyed(this)
|
||||||
|
)
|
||||||
|
.subscribe((menuBag) => {
|
||||||
|
if (menuBag.item.pathMatch === 'prefix') {
|
||||||
|
this.onClickLogOut();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Setup stream to translate menu item
|
||||||
|
this.translateService.stream('global.action.logout')
|
||||||
|
.pipe(
|
||||||
|
untilDestroyed(this)
|
||||||
|
).subscribe((text: string) => {
|
||||||
|
this.userMenu[0].title = text;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTML only
|
// HTML only
|
||||||
|
@ -46,6 +99,22 @@ export class HeaderComponent implements OnInit{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onClickLogOut(): void {
|
||||||
|
// ToDo: Redirect user to Landing page from Issue #142 https://github.com/Marcel-Haag/security-c4po/issues/143
|
||||||
|
// ToDo: Fix Redirect URI in Keycloak Setting
|
||||||
|
this.keycloakService.logout(`http://auth-server/realms/${environment.keycloakclientId}/protocol/openid-connect/logout`).then(() => {
|
||||||
|
// Route user back to default page
|
||||||
|
this.router.navigate([Route.HOME]).then(() => {
|
||||||
|
// Reset User props from store
|
||||||
|
this.store.dispatch(new ResetSession());
|
||||||
|
}, err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}, err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onClickLanguage(language: string): void {
|
onClickLanguage(language: string): void {
|
||||||
this.translateService.use(language);
|
this.translateService.use(language);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
import {NgModule} from '@angular/core';
|
import {NgModule} from '@angular/core';
|
||||||
import {CommonModule} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
import {HeaderComponent} from './header.component';
|
import {HeaderComponent} from './header.component';
|
||||||
import {NbActionsModule, NbButtonModule, NbCardModule, NbSelectModule} from '@nebular/theme';
|
import {
|
||||||
|
NbActionsModule,
|
||||||
|
NbButtonModule,
|
||||||
|
NbCardModule,
|
||||||
|
NbContextMenuModule, NbMenuModule,
|
||||||
|
NbSelectModule,
|
||||||
|
NbUserModule
|
||||||
|
} from '@nebular/theme';
|
||||||
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
|
||||||
import {FlexLayoutModule} from '@angular/flex-layout';
|
import {FlexLayoutModule} from '@angular/flex-layout';
|
||||||
import {TranslateModule} from '@ngx-translate/core';
|
import {TranslateModule} from '@ngx-translate/core';
|
||||||
|
@ -13,16 +20,20 @@ import {TranslateModule} from '@ngx-translate/core';
|
||||||
exports: [
|
exports: [
|
||||||
HeaderComponent
|
HeaderComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
NbButtonModule,
|
NbButtonModule,
|
||||||
FontAwesomeModule,
|
FontAwesomeModule,
|
||||||
NbCardModule,
|
NbCardModule,
|
||||||
NbActionsModule,
|
NbActionsModule,
|
||||||
FlexLayoutModule,
|
FlexLayoutModule,
|
||||||
NbSelectModule,
|
NbSelectModule,
|
||||||
TranslateModule
|
TranslateModule,
|
||||||
]
|
NbUserModule,
|
||||||
|
NbContextMenuModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
]
|
||||||
})
|
})
|
||||||
export class HeaderModule {
|
export class HeaderModule {
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,7 +81,6 @@ describe('LoginComponent', () => {
|
||||||
...store.snapshot(),
|
...store.snapshot(),
|
||||||
[SESSION_STATE_NAME]: DESIRED_STORE_STATE_SESSION
|
[SESSION_STATE_NAME]: DESIRED_STORE_STATE_SESSION
|
||||||
});
|
});
|
||||||
|
|
||||||
fixture = TestBed.createComponent(LoginComponent);
|
fixture = TestBed.createComponent(LoginComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
httpMock = TestBed.inject(HttpTestingController);
|
httpMock = TestBed.inject(HttpTestingController);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="pentest-categories">
|
<div class="pentest-categories">
|
||||||
<nb-menu class="menu-style" tag="menu" [items]="categories"></nb-menu>
|
<nb-menu id="category-menu" class="menu-style" tag="menu" [items]="categories"></nb-menu>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -54,10 +54,10 @@ export class ObjectiveCategoriesComponent implements OnInit, OnDestroy {
|
||||||
category.selected = false;
|
category.selected = false;
|
||||||
});
|
});
|
||||||
menuBag.item.selected = true;
|
menuBag.item.selected = true;
|
||||||
this.store.dispatch(new ChangeCategory(this.selectedCategory));
|
if (this.selectedCategory) {
|
||||||
|
this.store.dispatch(new ChangeCategory(this.selectedCategory));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private initTranslation(): void {
|
private initTranslation(): void {
|
||||||
|
|
|
@ -37,7 +37,6 @@ import {CommentWidgetModule} from '@shared/widgets/comment-widget/comment-widget
|
||||||
CommonAppModule,
|
CommonAppModule,
|
||||||
NbLayoutModule,
|
NbLayoutModule,
|
||||||
NbCardModule,
|
NbCardModule,
|
||||||
NbMenuModule.forRoot(),
|
|
||||||
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
|
||||||
|
@ -46,7 +45,6 @@ import {CommentWidgetModule} from '@shared/widgets/comment-widget/comment-widget
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
StatusTagModule,
|
StatusTagModule,
|
||||||
RouterModule,
|
RouterModule,
|
||||||
NbMenuModule,
|
|
||||||
FormsModule,
|
FormsModule,
|
||||||
NbListModule,
|
NbListModule,
|
||||||
FontAwesomeModule,
|
FontAwesomeModule,
|
||||||
|
@ -57,7 +55,8 @@ import {CommentWidgetModule} from '@shared/widgets/comment-widget/comment-widget
|
||||||
ObjectiveOverviewRoutingModule,
|
ObjectiveOverviewRoutingModule,
|
||||||
// Table Widgets
|
// Table Widgets
|
||||||
FindigWidgetModule,
|
FindigWidgetModule,
|
||||||
CommentWidgetModule
|
CommentWidgetModule,
|
||||||
|
NbMenuModule
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
ObjectiveHeaderComponent,
|
ObjectiveHeaderComponent,
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
status="success"
|
status="success"
|
||||||
[disabled]="!pentestStatusChanged() || !pentestHasFindingsOrComments()"
|
[disabled]="!pentestStatusChanged() || !pentestHasFindingsOrComments()"
|
||||||
title="{{ 'global.action.save' | translate }}"
|
title="{{ 'global.action.save' | translate }}"
|
||||||
(click)="onClickCompletePentestAndRouteBack()">
|
(click)="onClickCompletePentest()">
|
||||||
<fa-icon [icon]="fa.faSquare"></fa-icon>
|
<fa-icon [icon]="fa.faSquare"></fa-icon>
|
||||||
<span class="action-element-text"> {{ 'global.action.complete' | translate }} </span>
|
<span class="action-element-text"> {{ 'global.action.complete' | translate }} </span>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -95,7 +95,7 @@ export class PentestHeaderComponent implements OnInit, OnDestroy {
|
||||||
).finally();
|
).finally();
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickCompletePentestAndRouteBack(): void {
|
onClickCompletePentest(): void {
|
||||||
// Update existing Pentest
|
// Update existing Pentest
|
||||||
this.pentest$.next({...this.pentest$.getValue(), status: PentestStatus.COMPLETED, timeSpent: this.currentTimeSpent});
|
this.pentest$.next({...this.pentest$.getValue(), status: PentestStatus.COMPLETED, timeSpent: this.currentTimeSpent});
|
||||||
this.updatePentest();
|
this.updatePentest();
|
||||||
|
@ -107,11 +107,11 @@ export class PentestHeaderComponent implements OnInit, OnDestroy {
|
||||||
next: (pentest: Pentest) => {
|
next: (pentest: Pentest) => {
|
||||||
this.store.dispatch(new ChangePentest(pentest));
|
this.store.dispatch(new ChangePentest(pentest));
|
||||||
this.initialTimeSpent = pentest.timeSpent;
|
this.initialTimeSpent = pentest.timeSpent;
|
||||||
this.notificationService.showPopup('pentest.popup.update.success', PopupType.SUCCESS);
|
this.notificationService.showPopup('pentest.popup.complete.success', PopupType.SUCCESS);
|
||||||
},
|
},
|
||||||
error: err => {
|
error: err => {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
this.notificationService.showPopup('pentest.popup.update.failed', PopupType.FAILURE);
|
this.notificationService.showPopup('pentest.popup.complete.failed', PopupType.FAILURE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"global": {
|
"global": {
|
||||||
"action.login": "Einloggen",
|
"action.login": "Einloggen",
|
||||||
|
"action.logout": "Ausloggen",
|
||||||
"action.retry": "Erneut Versuchen",
|
"action.retry": "Erneut Versuchen",
|
||||||
"action.info": "Info",
|
"action.info": "Info",
|
||||||
"action.save": "Speichern",
|
"action.save": "Speichern",
|
||||||
|
@ -233,6 +234,8 @@
|
||||||
"initial.save.failed": "Initialer Pentest konnte nicht aufgesetzt werden",
|
"initial.save.failed": "Initialer Pentest konnte nicht aufgesetzt werden",
|
||||||
"save.success": "Pentest erfolgreich gespeichert",
|
"save.success": "Pentest erfolgreich gespeichert",
|
||||||
"save.failed": "Pentest konnte nicht gespeichert werden",
|
"save.failed": "Pentest konnte nicht gespeichert werden",
|
||||||
|
"complete.success": "Pentest erfolgreich vervollständigt",
|
||||||
|
"complete.failed": "Pentest konnte nicht vervollständigt werden",
|
||||||
"update.success": "Pentest erfolgreich aktualisiert",
|
"update.success": "Pentest erfolgreich aktualisiert",
|
||||||
"update.failed": "Pentest konnte nicht aktualisiert werden",
|
"update.failed": "Pentest konnte nicht aktualisiert werden",
|
||||||
"delete.success": "Pentest erfolgreich gelöscht",
|
"delete.success": "Pentest erfolgreich gelöscht",
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"global": {
|
"global": {
|
||||||
"action.login": "Login",
|
"action.login": "Login",
|
||||||
|
"action.logout": "Logout",
|
||||||
"action.retry": "Try again",
|
"action.retry": "Try again",
|
||||||
"action.info": "Info",
|
"action.info": "Info",
|
||||||
"action.confirm": "Confirm",
|
"action.confirm": "Confirm",
|
||||||
|
@ -233,6 +234,8 @@
|
||||||
"initial.save.failed": "Initial Pentest could not be setup",
|
"initial.save.failed": "Initial Pentest could not be setup",
|
||||||
"save.success": "Pentest saved successfully",
|
"save.success": "Pentest saved successfully",
|
||||||
"save.failed": "Pentest could not be saved",
|
"save.failed": "Pentest could not be saved",
|
||||||
|
"complete.success": "Pentest completed successfully",
|
||||||
|
"complete.failed": "Pentest could not be completed",
|
||||||
"update.success": "Pentest updated successfully",
|
"update.success": "Pentest updated successfully",
|
||||||
"update.failed": "Pentest could not be updated",
|
"update.failed": "Pentest could not be updated",
|
||||||
"delete.success": "Pentest deleted successfully",
|
"delete.success": "Pentest deleted successfully",
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
|
@ -133,8 +133,7 @@ export const createSpyObj = (baseName, methodNames): { [key: string]: Mock<any>
|
||||||
export const mockComment: Comment = {
|
export const mockComment: Comment = {
|
||||||
id: '11-22-33',
|
id: '11-22-33',
|
||||||
title: 'Test Finding',
|
title: 'Test Finding',
|
||||||
description: 'Test Description',
|
description: 'Test Description'
|
||||||
relatedFindings: ['68c47c56-3bcd-45f1-a05b-c197dbd33224']
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mockedCommentDialogData = {
|
export const mockedCommentDialogData = {
|
||||||
|
@ -164,19 +163,6 @@ export const mockedCommentDialogData = {
|
||||||
errors: [
|
errors: [
|
||||||
{errorCode: 'required', translationKey: 'comment.validationMessage.descriptionRequired'}
|
{errorCode: 'required', translationKey: 'comment.validationMessage.descriptionRequired'}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
commentRelatedFindings: {
|
|
||||||
fieldName: 'commentRelatedFindings',
|
|
||||||
type: 'text',
|
|
||||||
labelKey: 'comment.relatedFindings.label',
|
|
||||||
placeholder: 'comment.relatedFindingsPlaceholder',
|
|
||||||
controlsConfig: [
|
|
||||||
{value: mockComment ? mockComment.relatedFindings : [], disabled: false},
|
|
||||||
[]
|
|
||||||
],
|
|
||||||
errors: [
|
|
||||||
{errorCode: 'required', translationKey: 'finding.validationMessage.relatedFindings'}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
options: [
|
options: [
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {CommentDialogService} from '@shared/modules/comment-dialog/service/comme
|
||||||
import {ComponentType} from '@angular/cdk/overlay';
|
import {ComponentType} from '@angular/cdk/overlay';
|
||||||
import {NbDialogConfig} from '@nebular/theme';
|
import {NbDialogConfig} from '@nebular/theme';
|
||||||
import {Observable, of} from 'rxjs';
|
import {Observable, of} from 'rxjs';
|
||||||
import {Comment, RelatedFindingOption} from '@shared/models/comment.model';
|
import {Comment} from '@shared/models/comment.model';
|
||||||
|
|
||||||
export class CommentDialogServiceMock implements Required<CommentDialogService> {
|
export class CommentDialogServiceMock implements Required<CommentDialogService> {
|
||||||
|
|
||||||
|
@ -11,7 +11,6 @@ export class CommentDialogServiceMock implements Required<CommentDialogService>
|
||||||
openCommentDialog(
|
openCommentDialog(
|
||||||
componentOrTemplateRef: ComponentType<any>,
|
componentOrTemplateRef: ComponentType<any>,
|
||||||
findingIds: [],
|
findingIds: [],
|
||||||
relatedFindings: RelatedFindingOption[],
|
|
||||||
comment: Comment | undefined,
|
comment: Comment | undefined,
|
||||||
config: Partial<NbDialogConfig<Partial<any> | string>> | undefined): Observable<any> {
|
config: Partial<NbDialogConfig<Partial<any> | string>> | undefined): Observable<any> {
|
||||||
return of(undefined);
|
return of(undefined);
|
||||||
|
|
|
@ -18,7 +18,7 @@ export class TimerDurationPipe implements PipeTransform {
|
||||||
let seconds: string | number = 0;
|
let seconds: string | number = 0;
|
||||||
if (time) {
|
if (time) {
|
||||||
// tslint:disable-next-line:variable-name
|
// tslint:disable-next-line:variable-name
|
||||||
const sec_num = parseInt(time, 10); // don't forget the second param
|
const sec_num = parseInt(time, 10);
|
||||||
hours = Math.floor(sec_num / 3600);
|
hours = Math.floor(sec_num / 3600);
|
||||||
minutes = Math.floor((sec_num - (hours * 3600)) / 60);
|
minutes = Math.floor((sec_num - (hours * 3600)) / 60);
|
||||||
seconds = sec_num - (hours * 3600) - (minutes * 60);
|
seconds = sec_num - (hours * 3600) - (minutes * 60);
|
||||||
|
|
|
@ -81,9 +81,7 @@ class CommentControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
PayloadDocumentation.fieldWithPath("[].title").type(JsonFieldType.STRING)
|
PayloadDocumentation.fieldWithPath("[].title").type(JsonFieldType.STRING)
|
||||||
.description("The title of the requested comment"),
|
.description("The title of the requested comment"),
|
||||||
PayloadDocumentation.fieldWithPath("[].description").type(JsonFieldType.STRING)
|
PayloadDocumentation.fieldWithPath("[].description").type(JsonFieldType.STRING)
|
||||||
.description("The description number of the comment"),
|
.description("The description number of the comment")
|
||||||
PayloadDocumentation.fieldWithPath("[].relatedFindings").type(JsonFieldType.ARRAY)
|
|
||||||
.description("List of related Findings of the comment")
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -93,7 +91,7 @@ class CommentControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
id = "ab62d365-1b1d-4da1-89bc-5496616e220f",
|
id = "ab62d365-1b1d-4da1-89bc-5496616e220f",
|
||||||
title = "Found Bug",
|
title = "Found Bug",
|
||||||
description = "OTG-INFO-002 Bug",
|
description = "OTG-INFO-002 Bug",
|
||||||
relatedFindings = emptyList()
|
attachments = emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun getCommentsResponse() = listOf(
|
private fun getCommentsResponse() = listOf(
|
||||||
|
@ -133,9 +131,7 @@ class CommentControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
PayloadDocumentation.fieldWithPath("title").type(JsonFieldType.STRING)
|
PayloadDocumentation.fieldWithPath("title").type(JsonFieldType.STRING)
|
||||||
.description("The title of the requested comment"),
|
.description("The title of the requested comment"),
|
||||||
PayloadDocumentation.fieldWithPath("description").type(JsonFieldType.STRING)
|
PayloadDocumentation.fieldWithPath("description").type(JsonFieldType.STRING)
|
||||||
.description("The description number of the comment"),
|
.description("The description number of the comment")
|
||||||
PayloadDocumentation.fieldWithPath("relatedFindings").type(JsonFieldType.ARRAY)
|
|
||||||
.description("List of related findings of the comment")
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -145,7 +141,7 @@ class CommentControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
id = "ab62d365-1b1d-4da1-89bc-5496616e220f",
|
id = "ab62d365-1b1d-4da1-89bc-5496616e220f",
|
||||||
title = "Found Bug",
|
title = "Found Bug",
|
||||||
description = "OTG-INFO-002 Bug",
|
description = "OTG-INFO-002 Bug",
|
||||||
relatedFindings = emptyList()
|
attachments = emptyList()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,9 +178,7 @@ class CommentControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
PayloadDocumentation.fieldWithPath("title").type(JsonFieldType.STRING)
|
PayloadDocumentation.fieldWithPath("title").type(JsonFieldType.STRING)
|
||||||
.description("The title of the comment"),
|
.description("The title of the comment"),
|
||||||
PayloadDocumentation.fieldWithPath("description").type(JsonFieldType.STRING)
|
PayloadDocumentation.fieldWithPath("description").type(JsonFieldType.STRING)
|
||||||
.description("The description of the comment"),
|
.description("The description of the comment")
|
||||||
PayloadDocumentation.fieldWithPath("relatedFindings").type(JsonFieldType.ARRAY)
|
|
||||||
.description("List of related findings of the comment")
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -192,8 +186,7 @@ class CommentControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
|
|
||||||
private val commentBody = CommentRequestBody(
|
private val commentBody = CommentRequestBody(
|
||||||
title = "Found another Bug",
|
title = "Found another Bug",
|
||||||
description = "Another OTG-INFO-002 Bug",
|
description = "Another OTG-INFO-002 Bug"
|
||||||
relatedFindings = emptyList()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,9 +223,7 @@ class CommentControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
PayloadDocumentation.fieldWithPath("title").type(JsonFieldType.STRING)
|
PayloadDocumentation.fieldWithPath("title").type(JsonFieldType.STRING)
|
||||||
.description("The title of the requested comment"),
|
.description("The title of the requested comment"),
|
||||||
PayloadDocumentation.fieldWithPath("description").type(JsonFieldType.STRING)
|
PayloadDocumentation.fieldWithPath("description").type(JsonFieldType.STRING)
|
||||||
.description("The description number of the comment"),
|
.description("The description number of the comment")
|
||||||
PayloadDocumentation.fieldWithPath("relatedFindings").type(JsonFieldType.ARRAY)
|
|
||||||
.description("List of related findings of the comment")
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -240,8 +231,7 @@ class CommentControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
|
|
||||||
private val commentBody = CommentRequestBody(
|
private val commentBody = CommentRequestBody(
|
||||||
title = "Updated Comment",
|
title = "Updated Comment",
|
||||||
description = "Updated Description",
|
description = "Updated Description"
|
||||||
relatedFindings = emptyList()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -330,7 +320,7 @@ class CommentControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
id = "ab62d365-1b1d-4da1-89bc-5496616e220f",
|
id = "ab62d365-1b1d-4da1-89bc-5496616e220f",
|
||||||
title = "Found Bug",
|
title = "Found Bug",
|
||||||
description = "OTG-INFO-002 Bug",
|
description = "OTG-INFO-002 Bug",
|
||||||
relatedFindings = emptyList()
|
attachments = emptyList()
|
||||||
)
|
)
|
||||||
// persist test data in database
|
// persist test data in database
|
||||||
mongoTemplate.save(ProjectEntity(projectOne))
|
mongoTemplate.save(ProjectEntity(projectOne))
|
||||||
|
|
|
@ -77,7 +77,7 @@ class CommentControllerIntTest : BaseIntTest() {
|
||||||
id = "ab62d365-1b1d-4da1-89bc-5496616e220f",
|
id = "ab62d365-1b1d-4da1-89bc-5496616e220f",
|
||||||
title = "Found Bug",
|
title = "Found Bug",
|
||||||
description = "OTG-INFO-002 Bug",
|
description = "OTG-INFO-002 Bug",
|
||||||
relatedFindings = emptyList()
|
attachments = emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun getComments() = listOf(
|
private fun getComments() = listOf(
|
||||||
|
@ -103,7 +103,7 @@ class CommentControllerIntTest : BaseIntTest() {
|
||||||
id = "ab62d365-1b1d-4da1-89bc-5496616e220f",
|
id = "ab62d365-1b1d-4da1-89bc-5496616e220f",
|
||||||
title = "Found Bug",
|
title = "Found Bug",
|
||||||
description = "OTG-INFO-002 Bug",
|
description = "OTG-INFO-002 Bug",
|
||||||
relatedFindings = emptyList()
|
attachments = emptyList()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,13 +122,11 @@ class CommentControllerIntTest : BaseIntTest() {
|
||||||
.expectBody()
|
.expectBody()
|
||||||
.jsonPath("$.title").isEqualTo("Found another Bug")
|
.jsonPath("$.title").isEqualTo("Found another Bug")
|
||||||
.jsonPath("$.description").isEqualTo("Another OTG-INFO-002 Bug")
|
.jsonPath("$.description").isEqualTo("Another OTG-INFO-002 Bug")
|
||||||
.jsonPath("$.relatedFindings").isEmpty
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val commentBody = CommentRequestBody(
|
private val commentBody = CommentRequestBody(
|
||||||
title = "Found another Bug",
|
title = "Found another Bug",
|
||||||
description = "Another OTG-INFO-002 Bug",
|
description = "Another OTG-INFO-002 Bug"
|
||||||
relatedFindings = emptyList()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,13 +145,11 @@ class CommentControllerIntTest : BaseIntTest() {
|
||||||
.expectBody()
|
.expectBody()
|
||||||
.jsonPath("$.title").isEqualTo("Updated Comment")
|
.jsonPath("$.title").isEqualTo("Updated Comment")
|
||||||
.jsonPath("$.description").isEqualTo("Updated Description")
|
.jsonPath("$.description").isEqualTo("Updated Description")
|
||||||
.jsonPath("$.relatedFindings").isEmpty
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val commentBody = CommentRequestBody(
|
private val commentBody = CommentRequestBody(
|
||||||
title = "Updated Comment",
|
title = "Updated Comment",
|
||||||
description = "Updated Description",
|
description = "Updated Description"
|
||||||
relatedFindings = emptyList()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,7 +217,7 @@ class CommentControllerIntTest : BaseIntTest() {
|
||||||
id = "ab62d365-1b1d-4da1-89bc-5496616e220f",
|
id = "ab62d365-1b1d-4da1-89bc-5496616e220f",
|
||||||
title = "Found Bug",
|
title = "Found Bug",
|
||||||
description = "OTG-INFO-002 Bug",
|
description = "OTG-INFO-002 Bug",
|
||||||
relatedFindings = emptyList()
|
attachments = emptyList()
|
||||||
)
|
)
|
||||||
// persist test data in database
|
// persist test data in database
|
||||||
mongoTemplate.save(ProjectEntity(projectOne))
|
mongoTemplate.save(ProjectEntity(projectOne))
|
||||||
|
|
Loading…
Reference in New Issue