Compare commits

...

8 Commits

Author SHA1 Message Date
Marcel Haag 4fb3d82fee Added Keycloak and WebsecurityConfig and improved script management 2021-05-14 14:34:15 +02:00
mhg cccb3c5b3e
Merge pull request #2 from Marcel-Haag/mhg_c4po_2
Added Header, Project Model, Service, Controller and Testdata
2021-03-19 13:32:49 +01:00
Marcel Haag ffe6553990 Added Header, Project Model, Service, Controller and Testdata 2021-02-19 16:48:13 +01:00
mhg 6c71282798
Merge pull request #1 from Marcel-Haag/mhg_c4po_1
Added SessionStore, Login, Internationalization, Guards and LazyLoading
2021-02-05 20:15:57 +01:00
Marcel Haag d3f3a7ecfb Added SessionStore, Login, Internationalization, Guards and LazyLoading 2020-12-11 14:58:00 +01:00
Marcel Haag 0001b67e4a Added Dockerfiles for virtualization 2020-10-24 20:28:16 +02:00
Marcel Haag 906f1a1705 Created Initial SpringBoot App with OWASP Check and SpotBug 2020-10-24 18:40:55 +02:00
Marcel Haag ab88a02652 Created Initial Angular App with Nebular and Jest 2020-10-24 14:53:29 +02:00
142 changed files with 23215 additions and 0 deletions

View File

@ -3,3 +3,11 @@
## Project Members
* Daniel Mader
* Marcel Haag
## Development server
Execute 'c4po.sh' and all services will run on a dev server.
## Credentials:
* Username: ttt
* Password: Test1234!

38
c4po.sh Executable file
View File

@ -0,0 +1,38 @@
#!/bin/bash
docker_reg="c4po.io"
baseDir=$(pwd)
composeKeycloak=$baseDir"/security-c4po-cfg/kc/docker-compose.keycloak.yml"
composeFrontend=$baseDir"/security-c4po-cfg/frontend/docker-compose.frontend.yml"
composeBackend=$baseDir"/security-c4po-cfg/backend/docker-compose.backend.yml"
echo -e "
_______ _______ _______ _ _ ______ _____ _______ __ __
|______ |______ | | | |_____/ | | \_/
______| |______ |_____ |_____| | \_ __|__ | | _/_/_/ _/ _/ _/_/_/ _/_/
_/ _/ _/ _/ _/ _/ _/
_/ _/_/_/_/ _/_/_/ _/ _/
_/ _/ _/ _/ _/
_/_/_/ _/ _/ _/_/
\n"
echo "-------------CLEAN UP Container---------------"
echo -e "\n"
#docker rm -f security-c4po-keycloak
#docker rm -f security-c4po-postgres-keycloak
docker rm -f security-c4po-api
docker rm -f security-c4po-angular
echo -e "\n"
echo "-----------------Start Build------------------"
echo -e "\n"
echo " - Backend: "
docker-compose -f ${composeBackend} build
echo -e "\n"
echo " - Frontend: "
docker-compose -f ${composeFrontend} build
echo -e "\n"
echo "------------Start Docker Container------------"
echo -e "\n"
docker-compose -f ${composeKeycloak} -f ${composeBackend} -f ${composeFrontend} up

View File

@ -0,0 +1,18 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support
# You can see what browsers were selected by your queries by running:
# npx browserslist
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR
not IE 9-10 # Angular support for IE 9-10 has been deprecated and will be removed as of Angular v11. To opt-in, remove the 'not' prefix on this line.
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.

View File

@ -0,0 +1,16 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false

46
security-c4po-angular/.gitignore vendored Normal file
View File

@ -0,0 +1,46 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out
# dependencies
/node_modules
# profiling files
chrome-profiler-events*.json
speed-measure-plugin*.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db

View File

@ -0,0 +1,19 @@
# base image
FROM node:12.13.1
# set working directory
WORKDIR /app
# add `/app/node_modules/.bin` to $PATH
ENV PATH /app/node_modules/.bin:$PATH
# install and cache app dependencies
COPY package.json /app/package.json
RUN npm install
RUN npm install -g @angular/cli@10.2.0
# add app
COPY . /app
# start app
CMD ng serve --host 0.0.0.0

View File

@ -0,0 +1,27 @@
# SecurityC4poAngular
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.2.0.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.

View File

@ -0,0 +1,141 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"security-c4po-angular": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/security-c4po-angular",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"aot": true,
"assets": [
"src/favicon.ico",
"src/favicon-c4po.ico",
"src/assets"
],
"styles": [
"src/assets/@theme/styles/styles.scss"
],
"scripts": [],
"allowedCommonJsDependencies": [
"buffer",
"crypto-js/hmac-sha256",
"crypto-js/lib-typedarrays",
"js-cookie",
"deep-equal",
"moment-timezone",
"uuid"
]
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "3mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "security-c4po-angular:build"
},
"configurations": {
"production": {
"browserTarget": "security-c4po-angular:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "security-c4po-angular:build"
}
},
"test": {
"builder": "@angular-builders/jest:run",
"options": {
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"assets": [
"src/favicon.ico",
"src/favicon-c4po.ico",
"src/assets"
],
"styles": [
"src/assets/@theme/styles/styles.scss"
],
"scripts": []
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"e2e/tsconfig.json"
],
"exclude": [
"**/node_modules/**"
]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "security-c4po-angular:serve"
},
"configurations": {
"production": {
"devServerTarget": "security-c4po-angular:serve:production"
}
}
}
}
}
},
"defaultProject": "security-c4po-angular",
"cli": {
"analytics": false
}
}

View File

@ -0,0 +1,36 @@
// @ts-check
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter');
/**
* @type { import("protractor").Config }
*/
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
browserName: 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json')
});
jasmine.getEnv().addReporter(new SpecReporter({
spec: {
displayStacktrace: StacktraceOption.PRETTY
}
}));
}
};

View File

@ -0,0 +1,23 @@
import { AppPage } from './app.po';
import { browser, logging } from 'protractor';
describe('workspace-project App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getTitleText()).toEqual('Welcome');
});
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry));
});
});

View File

@ -0,0 +1,11 @@
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo(): Promise<unknown> {
return browser.get(browser.baseUrl) as Promise<unknown>;
}
getTitleText(): Promise<string> {
return element(by.css('app-root .content span')).getText() as Promise<string>;
}
}

View File

@ -0,0 +1,14 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es2018",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}

View File

@ -0,0 +1,7 @@
module.exports = {
moduleNameMapper: {
'@core/(.*)': '<rootDir>/src/app/core/$1',
},
preset: 'jest-preset-angular',
setupFilesAfterEnv: ['<rootDir>/setup-jest.ts'],
};

16224
security-c4po-angular/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,72 @@
{
"name": "security-c4po-angular",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "^11.0.3",
"@angular/cdk": "^10.2.7",
"@angular/common": "^10.2.3",
"@angular/compiler": "~10.2.0",
"@angular/core": "~10.2.0",
"@angular/flex-layout": "^11.0.0-beta.33",
"@angular/forms": "~10.2.0",
"@angular/localize": "^11.0.2",
"@angular/platform-browser": "~10.2.0",
"@angular/platform-browser-dynamic": "~10.2.0",
"@angular/router": "~10.2.0",
"@briebug/jest-schematic": "^3.0.0",
"@fortawesome/angular-fontawesome": "^0.8.0",
"@fortawesome/fontawesome-common-types": "^0.2.32",
"@fortawesome/fontawesome-svg-core": "^1.2.32",
"@fortawesome/free-regular-svg-icons": "^5.15.1",
"@fortawesome/free-solid-svg-icons": "^5.15.1",
"@nebular/eva-icons": "^6.2.1",
"@nebular/theme": "^6.2.1",
"@ngx-translate/core": "^13.0.0",
"@ngx-translate/http-loader": "^6.0.0",
"@ngxs/store": "^3.7.0",
"eva-icons": "^1.1.3",
"i18n-iso-countries": "^6.2.2",
"jwt-decode": "^3.1.2",
"keycloak-angular": "^8.1.0",
"keycloak-js": "^13.0.0",
"moment": "^2.29.1",
"moment-timezone": "latest",
"ngx-moment": "^5.0.0",
"ngx-take-until-destroy": "^5.4.0",
"ngx-translate-testing": "^5.0.0",
"roboto-fontface": "^0.10.0",
"rxjs": "~6.6.0",
"tslib": "^2.0.0",
"uuid": "^8.3.1",
"zone.js": "~0.10.2"
},
"devDependencies": {
"@angular-builders/jest": "10.0.1",
"@angular-devkit/build-angular": "~0.1002.0",
"@angular/cli": "~10.2.0",
"@angular/compiler-cli": "~10.2.0",
"@schematics/angular": "~10.2.0",
"@types/jasmine": "~3.5.0",
"@types/jasminewd2": "~2.0.3",
"@types/jest": "26.0.15",
"@types/node": "^12.19.8",
"codelyzer": "^6.0.0",
"font-awesome": "^4.7.0",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",
"jest": "26.6.1",
"protractor": "~7.0.0",
"ts-node": "~8.3.0",
"tslint": "~6.1.0",
"typescript": "~4.0.2"
}
}

View File

@ -0,0 +1,34 @@
import 'jest-preset-angular';
/* global mocks for jsdom */
const mock = () => {
let storage: { [key: string]: string } = {};
return {
getItem: (key: string) => (key in storage ? storage[key] : null),
setItem: (key: string, value: string) => (storage[key] = value || ''),
removeItem: (key: string) => delete storage[key],
clear: () => (storage = {}),
};
};
Object.defineProperty(window, 'localStorage', { value: mock() });
Object.defineProperty(window, 'sessionStorage', { value: mock() });
Object.defineProperty(window, 'getComputedStyle', {
value: () => ({
getPropertyValue: (prop) => {
return '';
}
})
});
Object.defineProperty(document.body.style, 'transform', {
value: () => {
return {
enumerable: true,
configurable: true,
};
},
});
/* output shorter and more meaningful Zone error stack traces */
// Error.stackTraceLimit = 2;

View File

@ -0,0 +1,33 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import {HomeComponent} from './home/home.component';
import {AuthGuardService} from '../shared/guards/auth-guard.service';
export const START_PAGE = 'home';
const routes: Routes = [
{
path: 'home',
component: HomeComponent,
canActivate: [AuthGuardService]
},
{
path: 'dashboard',
loadChildren: () => import('./dashboard').then(mod => mod.DashboardModule),
canActivate: [AuthGuardService]
},
// ToDo: Exchange default Keycloak login with self made login
/*{
path: 'login',
loadChildren: () => import('./login').then(mod => mod.LoginModule),
canActivate: [LoginGuardService]
},*/
{path: '**', redirectTo: START_PAGE},
{path: '', redirectTo: START_PAGE, pathMatch: 'full'},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

View File

@ -0,0 +1,14 @@
<nb-layout>
<nb-layout-header *ngIf="$authState.getValue()">
<app-header></app-header>
</nb-layout-header>
<nb-layout-column>
<div class="content-container">
<div class="scrollable-content">
<router-outlet></router-outlet>
</div>
</div>
</nb-layout-column>
</nb-layout>

View File

@ -0,0 +1,15 @@
@import "../assets/@theme/styles/_variables.scss";
.content-container {
width: 90vw;
height: calc(90vh - #{$header-height});
.scrollable-content {
width: 100%;
max-width: 100vw;
height: 100%;
max-height: calc(100vh - #{$header-height});
overflow: auto;
}
}

View File

@ -0,0 +1,56 @@
import {TestBed} from '@angular/core/testing';
import {RouterTestingModule} from '@angular/router/testing';
import {AppComponent} from './app.component';
import {NbLayoutModule, NbThemeModule} from '@nebular/theme';
import {NbEvaIconsModule} from '@nebular/eva-icons';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {HttpLoaderFactory} from './common-app.module';
import {HttpClient} from '@angular/common/http';
import {ThemeModule} from '../assets/@theme/theme.module';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {SessionState} from '../shared/stores/session-state/session-state';
import {NgxsModule} from '@ngxs/store';
import {HeaderModule} from './header/header.module';
import {KeycloakService} from 'keycloak-angular';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
RouterTestingModule,
NbThemeModule.forRoot(),
NbLayoutModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
}),
NbEvaIconsModule,
ThemeModule,
HeaderModule,
NgxsModule.forRoot([SessionState]),
HttpClientTestingModule
],
providers: [
KeycloakService
],
declarations: [
AppComponent
],
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'security-c4po-angular'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('security-c4po-angular');
});
});

View File

@ -0,0 +1,73 @@
import {Component, Inject, LOCALE_ID, OnDestroy, OnInit} from '@angular/core';
import {TranslateService} from '@ngx-translate/core';
import localeDe from '@angular/common/locales/de';
import {registerLocale} from 'i18n-iso-countries';
import {registerLocaleData} from '@angular/common';
import {Store} from '@ngxs/store';
import {BehaviorSubject, Subscription} from 'rxjs';
import {SessionState, SessionStateModel} from '../shared/stores/session-state/session-state';
import {untilDestroyed} from 'ngx-take-until-destroy';
import {isNotNullOrUndefined} from 'codelyzer/util/isNotNullOrUndefined';
import {filter} from 'rxjs/operators';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit, OnDestroy {
title = 'security-c4po-angular';
$authState: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
private authStateSubscription: Subscription;
constructor(private translateService: TranslateService,
private store: Store,
@Inject(LOCALE_ID) private localeId: string) {
this.initApp();
}
ngOnInit(): void {
// authState change handling
this.authStateSubscription = this.store.select(SessionState).pipe(
filter(isNotNullOrUndefined),
untilDestroyed(this)
).subscribe({
next: (state: SessionStateModel) => {
this.$authState.next(state.isAuthenticated);
},
error: (err) => console.error('auth error:', err)
});
}
initApp(): void {
// for global language
this.translateService.use(this.localeId);
// for number, date and time
registerLocaleData(localeDe, 'de-DE');
this.setupCountryCode();
}
/**
* i18n-iso-countries
* TODO: can be also lazy loaded if selected language exist
*/
setupCountryCode(): void {
// Support german languages.
// @ts-ignore
registerLocale(require('i18n-iso-countries/langs/en.json'));
// @ts-ignore
registerLocale(require('i18n-iso-countries/langs/de.json'));
}
ngOnDestroy(): void {
// This method must be present when using ngx-take-until-destroy
// even when empty
if (this.authStateSubscription) {
this.authStateSubscription.unsubscribe();
this.authStateSubscription = undefined;
}
}
}

View File

@ -0,0 +1,106 @@
import {APP_INITIALIZER, NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {AppRoutingModule} from './app-routing.module';
import {AppComponent} from './app.component';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {
NbLayoutModule,
NbToastrModule,
NbIconModule, NbCardModule, NbButtonModule,
} from '@nebular/theme';
import {NbEvaIconsModule} from '@nebular/eva-icons';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {HttpClient, HttpClientModule} from '@angular/common/http';
import {HttpLoaderFactory} from './common-app.module';
import {RouterModule} from '@angular/router';
import {FaConfig, FaIconLibrary, FontAwesomeModule} from '@fortawesome/angular-fontawesome';
import {fas} from '@fortawesome/free-solid-svg-icons';
import {far} from '@fortawesome/free-regular-svg-icons';
import {NgxsModule} from '@ngxs/store';
import {SessionState} from '../shared/stores/session-state/session-state';
import {environment} from '../environments/environment';
import {NotificationService} from '../shared/services/notification.service';
import {ThemeModule} from '@assets/@theme/theme.module';
import {HeaderModule} from './header/header.module';
import {HomeModule} from './home/home.module';
import {KeycloakService} from 'keycloak-angular';
import {httpInterceptorProviders} from '../shared/interceptors';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule,
RouterModule,
FontAwesomeModule,
NbToastrModule.forRoot(), // used for notification service
BrowserAnimationsModule,
ThemeModule.forRoot(),
NbLayoutModule,
NbCardModule,
NbIconModule,
NbButtonModule,
NbEvaIconsModule,
NgxsModule.forRoot([SessionState], {developmentMode: !environment.production}),
HttpClientModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
}),
HeaderModule,
HomeModule
],
providers: [
HttpClient,
{
provide: APP_INITIALIZER,
useFactory: initializer,
multi: true,
deps: [KeycloakService]
},
KeycloakService,
httpInterceptorProviders,
NotificationService
],
bootstrap: [
AppComponent
]
})
export class AppModule {
constructor(library: FaIconLibrary, faConfig: FaConfig) {
library.addIconPacks(fas, far);
faConfig.defaultPrefix = 'fas';
}
}
export function initializer(keycloak: KeycloakService): () => Promise<any> {
return async (): Promise<any> => {
try {
await keycloak.init({
config: {
url: environment.keycloakURL,
realm: environment.keycloakrealm,
clientId: environment.keycloakclientId
},
initOptions: {
onLoad: 'login-required',
checkLoginIframe: false,
// flow: 'implicit'
},
loadUserProfileAtStartUp: false,
enableBearerInterceptor: true,
bearerExcludedUrls: [
'/assets',
'/clients/public'
]
});
} catch (error) {
// console.error(error);
}
};
}

View File

@ -0,0 +1,45 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {TranslateHttpLoader} from '@ngx-translate/http-loader';
import {HttpClient, HttpClientModule} from '@angular/common/http';
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
import {FlexLayoutModule, FlexModule} from '@angular/flex-layout';
import {MomentModule} from 'ngx-moment';
import {NotificationService} from '../shared/services/notification.service';
import {NbToastrModule} from '@nebular/theme';
import {ThemeModule} from '../assets/@theme/theme.module';
export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
return new TranslateHttpLoader(http);
}
@NgModule({
declarations: [],
imports: [
CommonModule,
NbToastrModule, // used for notification service
FontAwesomeModule,
FlexLayoutModule,
ThemeModule.forRoot(),
FlexModule,
HttpClientModule,
TranslateModule.forChild({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
})
],
providers: [
HttpClient,
NotificationService
],
exports: [
// modules
MomentModule
]
})
export class CommonAppModule {
}

View File

@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {DashboardComponent} from './dashboard.component';
const routes: Routes = [
{
path: '',
component: DashboardComponent
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class DashboardRoutingModule { }

View File

@ -0,0 +1 @@
<p>dashboard works!</p>

View File

@ -0,0 +1,27 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DashboardComponent } from './dashboard.component';
describe('DashboardComponent', () => {
let component: DashboardComponent;
let fixture: ComponentFixture<DashboardComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
DashboardComponent
]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DashboardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,17 @@
import {Component, OnDestroy, OnInit} from '@angular/core';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss']
})
export class DashboardComponent implements OnInit, OnDestroy {
constructor() { }
ngOnInit(): void {
}
ngOnDestroy(): void {
}
}

View File

@ -0,0 +1,17 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {DashboardComponent} from './dashboard.component';
@NgModule({
declarations: [
DashboardComponent,
],
exports: [
DashboardComponent
],
imports: [
CommonModule
]
})
export class DashboardModule {
}

View File

@ -0,0 +1,2 @@
export {DashboardModule} from './dashboard.module';
export {DashboardRoutingModule} from './dashboard-routing.module';

View File

@ -0,0 +1,4 @@
<div class="c4po-header">
<p>header works!</p>
</div>

View File

@ -0,0 +1,9 @@
@import "../../assets/@theme/styles/_variables.scss";
.c4po-header {
height: $header-height;
.toggle-dark-mode {
text-align: right;
}
}

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HeaderComponent } from './header.component';
describe('HeaderComponent', () => {
let component: HeaderComponent;
let fixture: ComponentFixture<HeaderComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ HeaderComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(HeaderComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,14 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss']
})
export class HeaderComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {HeaderComponent} from './header.component';
@NgModule({
declarations: [
HeaderComponent
],
exports: [
HeaderComponent
],
imports: [
CommonModule
]
})
export class HeaderModule { }

View File

@ -0,0 +1,16 @@
<nb-card>
<nb-card-header>
<button (click)="onClickGetProjects()" nbButton>get Projects</button>
</nb-card-header>
<nb-card-body>
<div *ngIf="projects.getValue().length > 0, else noProjects">
{{projects.getValue() | json}}
</div>
<ng-template #noProjects>
{{'No Projects available!'}}
</ng-template>
</nb-card-body>
</nb-card>

View File

@ -0,0 +1,38 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HomeComponent } from './home.component';
import {NbButtonModule, NbCardModule} from '@nebular/theme';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {KeycloakService} from 'keycloak-angular';
describe('HomeComponent', () => {
let component: HomeComponent;
let fixture: ComponentFixture<HomeComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
HomeComponent
],
imports: [
HttpClientTestingModule,
NbCardModule,
NbButtonModule
],
providers : [
KeycloakService
]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,36 @@
import {Component, OnDestroy, OnInit} from '@angular/core';
import {BehaviorSubject} from 'rxjs';
import {Project} from '../../shared/models/project.model';
import {ProjectService} from '../../shared/services/project.service';
import {untilDestroyed} from 'ngx-take-until-destroy';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit, OnDestroy {
projects: BehaviorSubject<Project[]> = new BehaviorSubject<Project[]>([]);
constructor(private projectService: ProjectService) { }
ngOnInit(): void {
}
onClickGetProjects(): void {
this.getProjects();
}
getProjects(): void {
this.projectService.getProjects()
.pipe(untilDestroyed(this))
.subscribe((projects) => {
this.projects.next(projects);
});
}
ngOnDestroy(): void {
}
}

View File

@ -0,0 +1,20 @@
import {NgModule} from '@angular/core';
import {HomeComponent} from './home.component';
import {CommonModule} from '@angular/common';
import {NbButtonModule, NbCardModule} from '@nebular/theme';
@NgModule({
declarations: [
HomeComponent
],
exports: [
HomeComponent
],
imports: [
CommonModule,
NbCardModule,
NbButtonModule
]
})
export class HomeModule {
}

View File

@ -0,0 +1,2 @@
export {LoginModule} from './login.module';
export {LoginRoutingModule} from './login-routing.module';

View File

@ -0,0 +1,16 @@
import {RouterModule, Routes} from '@angular/router';
import {NgModule} from '@angular/core';
import {LoginComponent} from './login.component';
const routes: Routes = [
{
path: '',
component: LoginComponent
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class LoginRoutingModule {}

View File

@ -0,0 +1,53 @@
<div class="login-background"></div>
<div class="login-container">
<nb-card class="login-card">
<nb-card-body fxLayout="column"
fxLayoutGap="0.5rem">
<form [formGroup]="loginFormGroup">
<div fxLayout="column"
fxLayoutGap="0.5rem">
<h2 class="login-label">
{{SECURITYC4PO_TITLE}}
</h2>
<nb-form-field class="username">
<input formControlName="username" required
type="text" nbInput
status="{{formCtrlStatus}}"
placeholder="{{'global.username' | translate}}"
[min]="MIN_LENGTH"
(keydown.enter)="onEnterPressed()">
</nb-form-field>
<nb-form-field class="password">
<input formControlName="password" required
type="password" nbInput
status="{{formCtrlStatus}}"
placeholder="{{'global.password' | translate}}"
(keydown.enter)="onEnterPressed()">
</nb-form-field>
<div id="login-failed-error"
class="alert"
*ngIf="loginFailedWithCurrentCredentials">
{{'login.failed' | translate}}
</div>
<button nbButton class="login-button" status="primary"
[disabled]="formIsEmptyOrInvalid()"
(click)="login()">
{{'global.action.login' | translate}}
</button>
</div>
</form>
<div class="bottom-text" fxLayout="row" fxLayoutAlign="space-around center">
<div class="version-text">
{{version}}
</div>
<div>
&copy; {{NOVATEC_NAME}}
</div>
</div>
</nb-card-body>
</nb-card>
</div>

View File

@ -0,0 +1,87 @@
@import '~@nebular/theme/styles/theming';
$login-width: 24em;
$input-width: 16rem;
.login-container {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
display: flex;
flex-direction: row;
justify-content: center;
align-content: center;
.login-card {
z-index: 10;
align-self: center;
text-align: center;
border-radius: 0.5rem;
max-width: $login-width;
max-height: $login-width;
.login-label {
margin-top: 1rem;
margin-bottom: 2rem !important;
color: #8f9bb3;
}
.username {
width: $input-width;
margin-bottom: 1.5rem !important;
}
.password {
width: $input-width;
margin-bottom: 1.25rem !important;
}
.login-button {
max-width: $input-width;
margin-bottom: 0.2rem;
}
.alert {
padding-bottom: 0.5rem;
color: #e74c3c;
}
}
.bottom-text {
display: flex;
text-align: center;
margin-top: auto;
margin-bottom: 1rem;
justify-content: space-around;
padding: 0.625rem;
.version-text{
max-width: 50%;
text-overflow: ellipsis;
overflow: hidden;
}
}
}
.login-background {
background-image: url("../../assets/images/login/login.jpg");
background-position: center;
background-repeat: no-repeat;
background-size: cover;
min-width: 100vw;
min-height: 100vh;
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
filter: grayscale(100%) blur(2.5px) brightness(1);
z-index: 1;
margin: -2%
}

View File

@ -0,0 +1,95 @@
import {ComponentFixture, fakeAsync, inject, TestBed} from '@angular/core/testing';
import {LoginComponent} from './login.component';
import {RouterTestingModule} from '@angular/router/testing';
import {
NbButtonModule,
NbCardModule,
NbFormFieldModule,
NbInputModule,
NbLayoutModule,
} from '@nebular/theme';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {HttpLoaderFactory} from '../common-app.module';
import {HttpClient, HttpClientModule} from '@angular/common/http';
import {ThemeModule} from '../../assets/@theme/theme.module';
import {NgxsModule, Store} from '@ngxs/store';
import {SESSION_STATE_NAME, SessionState, SessionStateModel} from '../../shared/stores/session-state/session-state';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import {FlexLayoutModule} from '@angular/flex-layout';
import {ReactiveFormsModule} from '@angular/forms';
import {User} from '../../shared/models/user.model';
import {CommonModule} from '@angular/common';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {NotificationService} from '../../shared/services/notification.service';
import {NotificationServiceMock} from '../../shared/services/notification.service.mock';
import {KeycloakService} from 'keycloak-angular';
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('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
let httpMock: HttpTestingController;
let store: Store;
beforeEach(fakeAsync(() => {
TestBed.configureTestingModule({
imports: [
CommonModule,
NbCardModule,
FlexLayoutModule,
BrowserAnimationsModule,
ReactiveFormsModule,
NbInputModule,
NbCardModule,
NbButtonModule,
NbLayoutModule,
ThemeModule.forRoot(),
NbFormFieldModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
}),
RouterTestingModule.withRoutes([]),
NgxsModule.forRoot([SessionState]),
HttpClientModule,
HttpClientTestingModule
],
declarations: [
LoginComponent
],
providers: [
KeycloakService,
{provide: NotificationService, useValue: new NotificationServiceMock()}
]
})
.compileComponents();
}));
beforeEach(inject([Store], (inStore: Store) => {
store = inStore;
store.reset({
...store.snapshot(),
[SESSION_STATE_NAME]: DESIRED_STORE_STATE_SESSION
});
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
httpMock = TestBed.inject(HttpTestingController);
fixture.detectChanges();
}));
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,126 @@
import {Component, OnDestroy, OnInit} from '@angular/core';
import {AbstractControl, FormBuilder, FormGroup, Validators} from '@angular/forms';
import {Router} from '@angular/router';
import {Store} from '@ngxs/store';
import {NotificationService, PopupType} from '../../shared/services/notification.service';
import {untilDestroyed} from 'ngx-take-until-destroy';
import {User} from '../../shared/models/user.model';
import {throwError} from 'rxjs';
import {UpdateIsAuthenticated, UpdateUser} from '../../shared/stores/session-state/session-state.actions';
import {GlobalTitlesVariables} from '../../shared/config/global-variables';
import {HttpClient} from '@angular/common/http';
import {FieldStatus} from '../../shared/models/form-field-status.model';
import {KeycloakService} from 'keycloak-angular';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
// ToDo: Exchange default Keycloak login with self made login
export class LoginComponent implements OnInit, OnDestroy {
readonly MIN_LENGTH: number = 2;
readonly SECURITYC4PO_TITLE = GlobalTitlesVariables.SECURITYC4PO_TITLE;
readonly NOVATEC_NAME = GlobalTitlesVariables.NOVATEC_NAME;
// ToDo: Remove after adding real authentication
private readonly user = new User('ttt', 'test', 'user', 'default.user@test.de', 'en-US');
version: string;
// form control elements
loginFormGroup: FormGroup;
loginUsernameCtrl: AbstractControl;
loginPasswordCtrl: AbstractControl;
loginFailedWithCurrentCredentials = false;
invalidUsername: string;
invalidPassword: string;
formCtrlStatus = FieldStatus.BASIC;
constructor(private fb: FormBuilder,
private router: Router,
private store: Store,
private readonly httpClient: HttpClient,
private notificationService: NotificationService,
protected keycloakService: KeycloakService) {
}
ngOnInit(): void {
this.loginFormGroup = this.fb.group({
username: ['', [Validators.required, Validators.minLength(this.MIN_LENGTH)]],
password: ['', Validators.required]
});
this.loginUsernameCtrl = this.loginFormGroup.get('username');
this.loginPasswordCtrl = this.loginFormGroup.get('password');
this.loginFormGroup.valueChanges
.pipe(untilDestroyed(this))
.subscribe(() => {
this.formCtrlStatus = FieldStatus.BASIC;
this.loginFailedWithCurrentCredentials = false;
});
this.readAppVersion();
}
login(): void {
const username = this.loginUsernameCtrl.value;
const password = this.loginPasswordCtrl.value;
// ToDo: Should be handled in Guards
this.keycloakService.login({});
if (true) {
// ToDo: Should be handled in Guards
this.store.dispatch(new UpdateIsAuthenticated(true));
this.store.dispatch(new UpdateUser(this.user, true));
this.router.navigate(['/home']).then(() => {
this.notificationService.showPopup('popup.login.successful', PopupType.SUCCESS);
}, err => {
console.error(err);
});
} else {
this.invalidUsername = username;
this.invalidPassword = password;
this.loginFailedWithCurrentCredentials = true;
this.formCtrlStatus = FieldStatus.DANGER;
throwError('Invalid Credentials');
console.error('Invalid Credentials');
}
}
onEnterPressed(): void {
this.login();
}
formIsEmptyOrInvalid(): boolean {
return this.isEmpty(this.loginUsernameCtrl.value)
|| this.isEmpty(this.loginPasswordCtrl.value)
|| this.loginPasswordCtrl.invalid
|| this.loginFailedWithCurrentCredentials === true;
}
/**
* @param ctrlValue of type string
* @return if ctrlValue is empty or not
*/
isEmpty(ctrlValue: string): boolean {
return ctrlValue === '';
}
ngOnDestroy(): void {
// This method must be present when using ngx-take-until-destroy
// even when empty
}
readAppVersion(): void {
this.httpClient.get<Version>('assets/version.json', {responseType: 'json'})
.subscribe((data: Version) => {
this.version = data.version;
});
}
}
export interface Version {
version: string;
}

View File

@ -0,0 +1,42 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {LoginComponent} from './login.component';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {HttpLoaderFactory} from '../common-app.module';
import {HttpClient} from '@angular/common/http';
import {NotificationService} from '../../shared/services/notification.service';
import {LoginRoutingModule} from './login-routing.module';
import {NbButtonModule, NbCardModule, NbFormFieldModule, NbInputModule, NbLayoutModule} from '@nebular/theme';
import {ReactiveFormsModule} from '@angular/forms';
import {FlexLayoutModule} from '@angular/flex-layout';
import {ThemeModule} from '@assets/@theme/theme.module';
@NgModule({
declarations: [
LoginComponent
],
imports: [
CommonModule,
LoginRoutingModule,
TranslateModule.forChild({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
}),
NbCardModule,
FlexLayoutModule,
ReactiveFormsModule,
NbInputModule,
ThemeModule,
NbCardModule,
NbButtonModule,
NbLayoutModule,
NbFormFieldModule
], providers: [
NotificationService
]
})
export class LoginModule {
}

View File

@ -0,0 +1,6 @@
@mixin ngx-layout() {
@include media-breakpoint-down(is) {
.row {
}
}
}

View File

@ -0,0 +1,11 @@
@import './themes';
@mixin nb-overrides() {
nb-select.size-medium button {
padding: 0.4375rem 2.2rem 0.4375rem 1.125rem !important;
nb-icon {
right: 0.41rem !important;
}
}
}

View File

@ -0,0 +1,61 @@
.chromeframe {
margin: 0.2em 0;
background: #ccc;
color: #000;
padding: 0.2em 0;
}
#loader-wrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
}
#loader {
display: block;
position: relative;
left: 50%;
top: 50%;
width: 150px;
height: 150px;
margin: -75px 0 0 -75px;
border-radius: 50%;
border: 3px solid transparent;
border-top-color: #3498db;
-webkit-animation: spin 2s linear infinite; /* Chrome, Opera 15+, Safari 5+ */
animation: spin 2s linear infinite; /* Chrome, Firefox 16+, IE 10+, Opera */
}
#loader:before {
content: "";
position: absolute;
top: 5px;
left: 5px;
right: 5px;
bottom: 5px;
border-radius: 50%;
border: 3px solid transparent;
border-top-color: #e74c3c;
-webkit-animation: spin 3s linear infinite; /* Chrome, Opera 15+, Safari 5+ */
animation: spin 3s linear infinite; /* Chrome, Firefox 16+, IE 10+, Opera */
}
#loader:after {
content: "";
position: absolute;
top: 15px;
left: 15px;
right: 15px;
bottom: 15px;
border-radius: 50%;
border: 3px solid transparent;
border-top-color: #f9c922;
-webkit-animation: spin 1.5s linear infinite; /* Chrome, Opera 15+, Safari 5+ */
animation: spin 1.5s linear infinite; /* Chrome, Firefox 16+, IE 10+, Opera */
}

View File

@ -0,0 +1 @@
$header-height: 4rem;

View File

@ -0,0 +1,20 @@
/**
* @license
* Copyright Akveo. All Rights Reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*/
@mixin ngx-pace-theme() {
.pace .pace-progress {
background: nb-theme(color-primary-default);
}
.pace .pace-progress-inner {
box-shadow: 0 0 10px nb-theme(color-primary-default), 0 0 5px nb-theme(color-primary-default);
}
.pace .pace-activity {
display: none;
}
}

View File

@ -0,0 +1,30 @@
// themes - our custom or/and out of the box themes
@import 'themes';
// framework component themes (styles tied to theme variables)
@import '~@nebular/theme/styles/globals';
// loading progress bar theme
@import './pace.theme';
@import './layout';
@import './overrides';
@import './variables';
* {
font-family: Roboto, "Helvetica Neue", sans-serif;
}
body {
margin: 0;
}
@include nb-install() {
// framework global styles
@include nb-theme-global();
@include ngx-layout();
@include nb-overrides();
};
/* You can add global styles to this file, and also import other style files */

View File

@ -0,0 +1,308 @@
import { NbJSThemeOptions, CORPORATE_THEME as baseTheme } from '@nebular/theme';
const baseThemeVariables = baseTheme.variables;
export const CORPORATE_THEME = {
name: 'corporate',
base: 'corporate',
variables: {
temperature: {
arcFill: [ '#ffa36b', '#ffa36b', '#ff9e7a', '#ff9888', '#ff8ea0' ],
arcEmpty: baseThemeVariables.bg2,
thumbBg: baseThemeVariables.bg2,
thumbBorder: '#ffa36b',
},
solar: {
gradientLeft: baseThemeVariables.primary,
gradientRight: baseThemeVariables.primary,
shadowColor: 'rgba(0, 0, 0, 0)',
secondSeriesFill: baseThemeVariables.bg2,
radius: ['80%', '90%'],
},
traffic: {
tooltipBg: baseThemeVariables.bg,
tooltipBorderColor: baseThemeVariables.border2,
tooltipExtraCss: 'border-radius: 10px; padding: 4px 16px;',
tooltipTextColor: baseThemeVariables.fgText,
tooltipFontWeight: 'normal',
yAxisSplitLine: 'rgba(0, 0, 0, 0)',
lineBg: baseThemeVariables.primary,
lineShadowBlur: '0',
itemColor: baseThemeVariables.border4,
itemBorderColor: baseThemeVariables.border4,
itemEmphasisBorderColor: baseThemeVariables.primaryLight,
shadowLineDarkBg: 'rgba(0, 0, 0, 0)',
shadowLineShadow: 'rgba(0, 0, 0, 0)',
gradFrom: baseThemeVariables.bg,
gradTo: baseThemeVariables.bg,
},
electricity: {
tooltipBg: baseThemeVariables.bg,
tooltipLineColor: baseThemeVariables.fgText,
tooltipLineWidth: '0',
tooltipBorderColor: baseThemeVariables.border2,
tooltipExtraCss: 'border-radius: 10px; padding: 8px 24px;',
tooltipTextColor: baseThemeVariables.fgText,
tooltipFontWeight: 'normal',
axisLineColor: baseThemeVariables.border3,
xAxisTextColor: baseThemeVariables.fg,
yAxisSplitLine: baseThemeVariables.separator,
itemBorderColor: baseThemeVariables.primary,
lineStyle: 'solid',
lineWidth: '4',
lineGradFrom: baseThemeVariables.primary,
lineGradTo: baseThemeVariables.primary,
lineShadow: 'rgba(0, 0, 0, 0)',
areaGradFrom: 'rgba(0, 0, 0, 0)',
areaGradTo: 'rgba(0, 0, 0, 0)',
shadowLineDarkBg: 'rgba(0, 0, 0, 0)',
},
bubbleMap: {
titleColor: baseThemeVariables.fgText,
areaColor: baseThemeVariables.bg4,
areaHoverColor: baseThemeVariables.fgHighlight,
areaBorderColor: baseThemeVariables.border5,
},
profitBarAnimationEchart: {
textColor: baseThemeVariables.fgText,
firstAnimationBarColor: baseThemeVariables.primary,
secondAnimationBarColor: baseThemeVariables.success,
splitLineStyleOpacity: '1',
splitLineStyleWidth: '1',
splitLineStyleColor: baseThemeVariables.separator,
tooltipTextColor: baseThemeVariables.fgText,
tooltipFontWeight: 'normal',
tooltipFontSize: '16',
tooltipBg: baseThemeVariables.bg,
tooltipBorderColor: baseThemeVariables.border2,
tooltipBorderWidth: '1',
tooltipExtraCss: 'border-radius: 10px; padding: 4px 16px;',
},
trafficBarEchart: {
gradientFrom: baseThemeVariables.warningLight,
gradientTo: baseThemeVariables.warning,
shadow: baseThemeVariables.warningLight,
shadowBlur: '0',
axisTextColor: baseThemeVariables.fgText,
axisFontSize: '12',
tooltipBg: baseThemeVariables.bg,
tooltipBorderColor: baseThemeVariables.border2,
tooltipExtraCss: 'border-radius: 10px; padding: 8px 24px;',
tooltipTextColor: baseThemeVariables.fgText,
tooltipFontWeight: 'normal',
},
countryOrders: {
countryBorderColor: baseThemeVariables.border4,
countryFillColor: baseThemeVariables.bg4,
countryBorderWidth: '1',
hoveredCountryBorderColor: baseThemeVariables.primary,
hoveredCountryFillColor: baseThemeVariables.primaryLight,
hoveredCountryBorderWidth: '1',
chartAxisLineColor: baseThemeVariables.border4,
chartAxisTextColor: baseThemeVariables.fg,
chartAxisFontSize: '16',
chartGradientTo: baseThemeVariables.primary,
chartGradientFrom: baseThemeVariables.primaryLight,
chartAxisSplitLine: baseThemeVariables.separator,
chartShadowLineColor: baseThemeVariables.primaryLight,
chartLineBottomShadowColor: baseThemeVariables.primary,
chartInnerLineColor: baseThemeVariables.bg2,
},
echarts: {
bg: baseThemeVariables.bg,
textColor: baseThemeVariables.fgText,
axisLineColor: baseThemeVariables.fgText,
splitLineColor: baseThemeVariables.separator,
itemHoverShadowColor: 'rgba(0, 0, 0, 0.5)',
tooltipBackgroundColor: baseThemeVariables.primary,
areaOpacity: '0.7',
},
chartjs: {
axisLineColor: baseThemeVariables.separator,
textColor: baseThemeVariables.fgText,
},
orders: {
tooltipBg: baseThemeVariables.bg,
tooltipLineColor: 'rgba(0, 0, 0, 0)',
tooltipLineWidth: '0',
tooltipBorderColor: baseThemeVariables.border2,
tooltipExtraCss: 'border-radius: 10px; padding: 8px 24px;',
tooltipTextColor: baseThemeVariables.fgText,
tooltipFontWeight: 'normal',
tooltipFontSize: '20',
axisLineColor: baseThemeVariables.border4,
axisFontSize: '16',
axisTextColor: baseThemeVariables.fg,
yAxisSplitLine: baseThemeVariables.separator,
itemBorderColor: baseThemeVariables.primary,
lineStyle: 'solid',
lineWidth: '4',
// first line
firstAreaGradFrom: baseThemeVariables.bg3,
firstAreaGradTo: baseThemeVariables.bg3,
firstShadowLineDarkBg: 'rgba(0, 0, 0, 0)',
// second line
secondLineGradFrom: baseThemeVariables.primary,
secondLineGradTo: baseThemeVariables.primary,
secondAreaGradFrom: 'rgba(0, 0, 0, 0)',
secondAreaGradTo: 'rgba(0, 0, 0, 0)',
secondShadowLineDarkBg: 'rgba(0, 0, 0, 0)',
// third line
thirdLineGradFrom: baseThemeVariables.success,
thirdLineGradTo: baseThemeVariables.successLight,
thirdAreaGradFrom: 'rgba(0, 0, 0, 0)',
thirdAreaGradTo: 'rgba(0, 0, 0, 0)',
thirdShadowLineDarkBg: 'rgba(0, 0, 0, 0)',
},
profit: {
bg: baseThemeVariables.bg,
textColor: baseThemeVariables.fgText,
axisLineColor: baseThemeVariables.border4,
splitLineColor: baseThemeVariables.separator,
areaOpacity: '1',
axisFontSize: '16',
axisTextColor: baseThemeVariables.fg,
// first bar
firstLineGradFrom: baseThemeVariables.bg3,
firstLineGradTo: baseThemeVariables.bg3,
firstLineShadow: 'rgba(0, 0, 0, 0)',
// second bar
secondLineGradFrom: baseThemeVariables.primary,
secondLineGradTo: baseThemeVariables.primary,
secondLineShadow: 'rgba(0, 0, 0, 0)',
// third bar
thirdLineGradFrom: baseThemeVariables.success,
thirdLineGradTo: baseThemeVariables.success,
thirdLineShadow: 'rgba(0, 0, 0, 0)',
},
orderProfitLegend: {
firstItem: baseThemeVariables.success,
secondItem: baseThemeVariables.primary,
thirdItem: baseThemeVariables.bg3,
},
visitors: {
tooltipBg: baseThemeVariables.bg,
tooltipLineColor: 'rgba(0, 0, 0, 0)',
tooltipLineWidth: '1',
tooltipBorderColor: baseThemeVariables.border2,
tooltipExtraCss: 'border-radius: 10px; padding: 8px 24px;',
tooltipTextColor: baseThemeVariables.fgText,
tooltipFontWeight: 'normal',
tooltipFontSize: '20',
axisLineColor: baseThemeVariables.border4,
axisFontSize: '16',
axisTextColor: baseThemeVariables.fg,
yAxisSplitLine: baseThemeVariables.separator,
itemBorderColor: baseThemeVariables.primary,
lineStyle: 'dotted',
lineWidth: '6',
lineGradFrom: '#ffffff',
lineGradTo: '#ffffff',
lineShadow: 'rgba(0, 0, 0, 0)',
areaGradFrom: baseThemeVariables.primary,
areaGradTo: baseThemeVariables.primaryLight,
innerLineStyle: 'solid',
innerLineWidth: '1',
innerAreaGradFrom: baseThemeVariables.success,
innerAreaGradTo: baseThemeVariables.success,
},
visitorsLegend: {
firstIcon: baseThemeVariables.success,
secondIcon: baseThemeVariables.primary,
},
visitorsPie: {
firstPieGradientLeft: baseThemeVariables.success,
firstPieGradientRight: baseThemeVariables.success,
firstPieShadowColor: 'rgba(0, 0, 0, 0)',
firstPieRadius: ['65%', '90%'],
secondPieGradientLeft: baseThemeVariables.warning,
secondPieGradientRight: baseThemeVariables.warningLight,
secondPieShadowColor: 'rgba(0, 0, 0, 0)',
secondPieRadius: ['63%', '92%'],
shadowOffsetX: '-4',
shadowOffsetY: '-4',
},
visitorsPieLegend: {
firstSection: baseThemeVariables.warning,
secondSection: baseThemeVariables.success,
},
earningPie: {
radius: ['65%', '100%'],
center: ['50%', '50%'],
fontSize: '22',
firstPieGradientLeft: baseThemeVariables.success,
firstPieGradientRight: baseThemeVariables.success,
firstPieShadowColor: 'rgba(0, 0, 0, 0)',
secondPieGradientLeft: baseThemeVariables.primary,
secondPieGradientRight: baseThemeVariables.primary,
secondPieShadowColor: 'rgba(0, 0, 0, 0)',
thirdPieGradientLeft: baseThemeVariables.warning,
thirdPieGradientRight: baseThemeVariables.warning,
thirdPieShadowColor: 'rgba(0, 0, 0, 0)',
},
earningLine: {
gradFrom: baseThemeVariables.primary,
gradTo: baseThemeVariables.primary,
tooltipTextColor: baseThemeVariables.fgText,
tooltipFontWeight: 'normal',
tooltipFontSize: '16',
tooltipBg: baseThemeVariables.bg,
tooltipBorderColor: baseThemeVariables.border2,
tooltipBorderWidth: '1',
tooltipExtraCss: 'border-radius: 10px; padding: 4px 16px;',
},
},
} as NbJSThemeOptions;

View File

@ -0,0 +1,314 @@
import { NbJSThemeOptions, DARK_THEME as baseTheme } from '@nebular/theme';
const baseThemeVariables = baseTheme.variables;
export const DARK_THEME = {
name: 'dark',
base: 'dark',
variables: {
temperature: {
arcFill: [
baseThemeVariables.primary,
baseThemeVariables.primary,
baseThemeVariables.primary,
baseThemeVariables.primary,
baseThemeVariables.primary,
],
arcEmpty: baseThemeVariables.bg2,
thumbBg: baseThemeVariables.bg2,
thumbBorder: baseThemeVariables.primary,
},
solar: {
gradientLeft: baseThemeVariables.primary,
gradientRight: baseThemeVariables.primary,
shadowColor: 'rgba(0, 0, 0, 0)',
secondSeriesFill: baseThemeVariables.bg2,
radius: ['80%', '90%'],
},
traffic: {
tooltipBg: baseThemeVariables.bg,
tooltipBorderColor: baseThemeVariables.border2,
tooltipExtraCss: 'border-radius: 10px; padding: 4px 16px;',
tooltipTextColor: baseThemeVariables.fgText,
tooltipFontWeight: 'normal',
yAxisSplitLine: baseThemeVariables.separator,
lineBg: baseThemeVariables.border4,
lineShadowBlur: '1',
itemColor: baseThemeVariables.border4,
itemBorderColor: baseThemeVariables.border4,
itemEmphasisBorderColor: baseThemeVariables.primary,
shadowLineDarkBg: 'rgba(0, 0, 0, 0)',
shadowLineShadow: 'rgba(0, 0, 0, 0)',
gradFrom: baseThemeVariables.bg2,
gradTo: baseThemeVariables.bg2,
},
electricity: {
tooltipBg: baseThemeVariables.bg,
tooltipLineColor: baseThemeVariables.fgText,
tooltipLineWidth: '0',
tooltipBorderColor: baseThemeVariables.border2,
tooltipExtraCss: 'border-radius: 10px; padding: 8px 24px;',
tooltipTextColor: baseThemeVariables.fgText,
tooltipFontWeight: 'normal',
axisLineColor: baseThemeVariables.border3,
xAxisTextColor: baseThemeVariables.fg,
yAxisSplitLine: baseThemeVariables.separator,
itemBorderColor: baseThemeVariables.primary,
lineStyle: 'solid',
lineWidth: '4',
lineGradFrom: baseThemeVariables.primary,
lineGradTo: baseThemeVariables.primary,
lineShadow: 'rgba(0, 0, 0, 0)',
areaGradFrom: baseThemeVariables.bg2,
areaGradTo: baseThemeVariables.bg2,
shadowLineDarkBg: 'rgba(0, 0, 0, 0)',
},
bubbleMap: {
titleColor: baseThemeVariables.fgText,
areaColor: baseThemeVariables.bg4,
areaHoverColor: baseThemeVariables.fgHighlight,
areaBorderColor: baseThemeVariables.border5,
},
profitBarAnimationEchart: {
textColor: baseThemeVariables.fgText,
firstAnimationBarColor: baseThemeVariables.primary,
secondAnimationBarColor: baseThemeVariables.success,
splitLineStyleOpacity: '1',
splitLineStyleWidth: '1',
splitLineStyleColor: baseThemeVariables.separator,
tooltipTextColor: baseThemeVariables.fgText,
tooltipFontWeight: 'normal',
tooltipFontSize: '16',
tooltipBg: baseThemeVariables.bg,
tooltipBorderColor: baseThemeVariables.border2,
tooltipBorderWidth: '1',
tooltipExtraCss: 'border-radius: 10px; padding: 4px 16px;',
},
trafficBarEchart: {
gradientFrom: baseThemeVariables.warningLight,
gradientTo: baseThemeVariables.warning,
shadow: baseThemeVariables.warningLight,
shadowBlur: '0',
axisTextColor: baseThemeVariables.fgText,
axisFontSize: '12',
tooltipBg: baseThemeVariables.bg,
tooltipBorderColor: baseThemeVariables.border2,
tooltipExtraCss: 'border-radius: 10px; padding: 4px 16px;',
tooltipTextColor: baseThemeVariables.fgText,
tooltipFontWeight: 'normal',
},
countryOrders: {
countryBorderColor: baseThemeVariables.border4,
countryFillColor: baseThemeVariables.bg3,
countryBorderWidth: '1',
hoveredCountryBorderColor: baseThemeVariables.primary,
hoveredCountryFillColor: baseThemeVariables.primaryLight,
hoveredCountryBorderWidth: '1',
chartAxisLineColor: baseThemeVariables.border4,
chartAxisTextColor: baseThemeVariables.fg,
chartAxisFontSize: '16',
chartGradientTo: baseThemeVariables.primary,
chartGradientFrom: baseThemeVariables.primaryLight,
chartAxisSplitLine: baseThemeVariables.separator,
chartShadowLineColor: baseThemeVariables.primaryLight,
chartLineBottomShadowColor: baseThemeVariables.primary,
chartInnerLineColor: baseThemeVariables.bg2,
},
echarts: {
bg: baseThemeVariables.bg,
textColor: baseThemeVariables.fgText,
axisLineColor: baseThemeVariables.fgText,
splitLineColor: baseThemeVariables.separator,
itemHoverShadowColor: 'rgba(0, 0, 0, 0.5)',
tooltipBackgroundColor: baseThemeVariables.primary,
areaOpacity: '0.7',
},
chartjs: {
axisLineColor: baseThemeVariables.separator,
textColor: baseThemeVariables.fgText,
},
orders: {
tooltipBg: baseThemeVariables.bg,
tooltipLineColor: 'rgba(0, 0, 0, 0)',
tooltipLineWidth: '0',
tooltipBorderColor: baseThemeVariables.border2,
tooltipExtraCss: 'border-radius: 10px; padding: 8px 24px;',
tooltipTextColor: baseThemeVariables.fgText,
tooltipFontWeight: 'normal',
tooltipFontSize: '20',
axisLineColor: baseThemeVariables.border4,
axisFontSize: '16',
axisTextColor: baseThemeVariables.fg,
yAxisSplitLine: baseThemeVariables.separator,
itemBorderColor: baseThemeVariables.primary,
lineStyle: 'solid',
lineWidth: '4',
// first line
firstAreaGradFrom: baseThemeVariables.bg3,
firstAreaGradTo: baseThemeVariables.bg3,
firstShadowLineDarkBg: 'rgba(0, 0, 0, 0)',
// second line
secondLineGradFrom: baseThemeVariables.primary,
secondLineGradTo: baseThemeVariables.primary,
secondAreaGradFrom: 'rgba(51, 102, 255, 0.2)',
secondAreaGradTo: 'rgba(51, 102, 255, 0)',
secondShadowLineDarkBg: 'rgba(0, 0, 0, 0)',
// third line
thirdLineGradFrom: baseThemeVariables.success,
thirdLineGradTo: baseThemeVariables.successLight,
thirdAreaGradFrom: 'rgba(0, 214, 143, 0.2)',
thirdAreaGradTo: 'rgba(0, 214, 143, 0)',
thirdShadowLineDarkBg: 'rgba(0, 0, 0, 0)',
},
profit: {
bg: baseThemeVariables.bg,
textColor: baseThemeVariables.fgText,
axisLineColor: baseThemeVariables.border4,
splitLineColor: baseThemeVariables.separator,
areaOpacity: '1',
axisFontSize: '16',
axisTextColor: baseThemeVariables.fg,
// first bar
firstLineGradFrom: baseThemeVariables.bg3,
firstLineGradTo: baseThemeVariables.bg3,
firstLineShadow: 'rgba(0, 0, 0, 0)',
// second bar
secondLineGradFrom: baseThemeVariables.primary,
secondLineGradTo: baseThemeVariables.primary,
secondLineShadow: 'rgba(0, 0, 0, 0)',
// third bar
thirdLineGradFrom: baseThemeVariables.success,
thirdLineGradTo: baseThemeVariables.successLight,
thirdLineShadow: 'rgba(0, 0, 0, 0)',
},
orderProfitLegend: {
firstItem: baseThemeVariables.success,
secondItem: baseThemeVariables.primary,
thirdItem: baseThemeVariables.bg3,
},
visitors: {
tooltipBg: baseThemeVariables.bg,
tooltipLineColor: 'rgba(0, 0, 0, 0)',
tooltipLineWidth: '0',
tooltipBorderColor: baseThemeVariables.border2,
tooltipExtraCss: 'border-radius: 10px; padding: 8px 24px;',
tooltipTextColor: baseThemeVariables.fgText,
tooltipFontWeight: 'normal',
tooltipFontSize: '20',
axisLineColor: baseThemeVariables.border4,
axisFontSize: '16',
axisTextColor: baseThemeVariables.fg,
yAxisSplitLine: baseThemeVariables.separator,
itemBorderColor: baseThemeVariables.primary,
lineStyle: 'dotted',
lineWidth: '6',
lineGradFrom: '#ffffff',
lineGradTo: '#ffffff',
lineShadow: 'rgba(0, 0, 0, 0)',
areaGradFrom: baseThemeVariables.primary,
areaGradTo: baseThemeVariables.primaryLight,
innerLineStyle: 'solid',
innerLineWidth: '1',
innerAreaGradFrom: baseThemeVariables.success,
innerAreaGradTo: baseThemeVariables.success,
},
visitorsLegend: {
firstIcon: baseThemeVariables.success,
secondIcon: baseThemeVariables.primary,
},
visitorsPie: {
firstPieGradientLeft: baseThemeVariables.success,
firstPieGradientRight: baseThemeVariables.success,
firstPieShadowColor: 'rgba(0, 0, 0, 0)',
firstPieRadius: ['70%', '90%'],
secondPieGradientLeft: baseThemeVariables.warning,
secondPieGradientRight: baseThemeVariables.warningLight,
secondPieShadowColor: 'rgba(0, 0, 0, 0)',
secondPieRadius: ['60%', '97%'],
shadowOffsetX: '0',
shadowOffsetY: '0',
},
visitorsPieLegend: {
firstSection: baseThemeVariables.warning,
secondSection: baseThemeVariables.success,
},
earningPie: {
radius: ['65%', '100%'],
center: ['50%', '50%'],
fontSize: '22',
firstPieGradientLeft: baseThemeVariables.success,
firstPieGradientRight: baseThemeVariables.success,
firstPieShadowColor: 'rgba(0, 0, 0, 0)',
secondPieGradientLeft: baseThemeVariables.primary,
secondPieGradientRight: baseThemeVariables.primary,
secondPieShadowColor: 'rgba(0, 0, 0, 0)',
thirdPieGradientLeft: baseThemeVariables.warning,
thirdPieGradientRight: baseThemeVariables.warning,
thirdPieShadowColor: 'rgba(0, 0, 0, 0)',
},
earningLine: {
gradFrom: baseThemeVariables.primary,
gradTo: baseThemeVariables.primary,
tooltipTextColor: baseThemeVariables.fgText,
tooltipFontWeight: 'normal',
tooltipFontSize: '16',
tooltipBg: baseThemeVariables.bg,
tooltipBorderColor: baseThemeVariables.border2,
tooltipBorderWidth: '1',
tooltipExtraCss: 'border-radius: 10px; padding: 4px 16px;',
},
},
} as NbJSThemeOptions;

View File

@ -0,0 +1,46 @@
// @nebular theming framework
@import '~@nebular/theme/styles/theming';
// @nebular out of the box themes
@import '~@nebular/theme/styles/themes';
$nb-themes: nb-register-theme((
layout-padding-top: 2.25rem,
menu-item-icon-margin: 0 0.5rem 0 0,
card-height-tiny: 13.5rem,
card-height-small: 21.1875rem,
card-height-medium: 28.875rem,
card-height-large: 36.5625rem,
card-height-giant: 44.25rem,
card-margin-bottom: 1.875rem,
card-header-with-select-padding-top: 0.5rem,
card-header-with-select-padding-bottom: 0.5rem,
select-min-width: 6rem,
slide-out-background: linear-gradient(270deg, #edf1f7 0%, #e4e9f2 100%),
slide-out-shadow-color: 0 4px 14px 0 #8f9bb3,
slide-out-shadow-color-rtl: 0 4px 14px 0 #8f9bb3,
), corporate, corporate);
$nb-themes: nb-register-theme((
layout-padding-top: 2.25rem,
menu-item-icon-margin: 0 0.5rem 0 0,
card-height-tiny: 13.5rem,
card-height-small: 21.1875rem,
card-height-medium: 28.875rem,
card-height-large: 36.5625rem,
card-height-giant: 44.25rem,
card-margin-bottom: 1.875rem,
card-header-with-select-padding-top: 0.5rem,
card-header-with-select-padding-bottom: 0.5rem,
select-min-width: 6rem,
slide-out-background: linear-gradient(270deg, #222b45 0%, #151a30 100%),
slide-out-shadow-color: 0 4px 14px 0 #8f9bb3,
slide-out-shadow-color-rtl: 0 4px 14px 0 #8f9bb3,
), dark, dark);

View File

@ -0,0 +1,57 @@
import { ModuleWithProviders, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
NbActionsModule,
NbLayoutModule,
NbMenuModule,
NbSearchModule,
NbSidebarModule,
NbUserModule,
NbContextMenuModule,
NbButtonModule,
NbSelectModule,
NbIconModule,
NbThemeModule,
} from '@nebular/theme';
import { NbEvaIconsModule } from '@nebular/eva-icons';
import { CORPORATE_THEME } from './styles/theme.corporate';
import { DARK_THEME } from './styles/theme.dark';
const NB_MODULES = [
NbLayoutModule,
NbMenuModule,
NbUserModule,
NbActionsModule,
NbSearchModule,
NbSidebarModule,
NbContextMenuModule,
NbButtonModule,
NbSelectModule,
NbIconModule,
NbEvaIconsModule,
];
const COMPONENTS = [
];
const PIPES = [
];
@NgModule({
imports: [CommonModule, ...NB_MODULES],
exports: [CommonModule, ...PIPES, ...COMPONENTS],
declarations: [...COMPONENTS, ...PIPES],
})
export class ThemeModule {
static forRoot(): ModuleWithProviders<ThemeModule> {
return {
ngModule: ThemeModule,
providers: [
...NbThemeModule.forRoot(
{
name: 'corporate',
},
[CORPORATE_THEME, DARK_THEME],
).providers,
],
};
}
}

View File

@ -0,0 +1,26 @@
{
"global": {
"action.login": "Einloggen",
"action.retry": "Erneut Versuchen",
"username": "Nutzername",
"password": "Passwort"
},
"popup": {
"success": "✔",
"failure": "✘",
"warning": "!",
"info": "",
"error.position": {
"permissionDenied": "Berechtigung verweigert",
"timeout": "Zeitüberschreitung"
},
"login": {
"successful": "Einloggen erfolgreich"
}
},
"login": {
"title": "Einloggen",
"failed": "Benutzername oder Passwort falsch",
"unauthorized": "Benutzer nicht gefunden. Bitte registrieren und erneut versuchen"
}
}

View File

@ -0,0 +1,26 @@
{
"global": {
"action.login": "Login",
"action.retry": "Try again",
"username": "Username",
"password": "Password"
},
"popup": {
"success": "✔",
"failure": "✘",
"warning": "!",
"info": "",
"error.position": {
"permissionDenied": "Permission Denied",
"timeout": "Timeout"
},
"login": {
"successful": "Login successful"
}
},
"login": {
"title": "Login",
"failed": "Wrong username or password",
"unauthorized": "User not found. Please register and try again"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

View File

@ -0,0 +1 @@
{"version": "local"}

View File

@ -0,0 +1,11 @@
export const environment = {
production: true,
// keycloak
keycloakURL: 'http://localhost:8888/auth',
keycloakrealm: 'c4po_realm_local',
keycloakclientId: 'c4po_local',
// backend service
apiEndpoint: 'http://localhost:8443',
};

View File

@ -0,0 +1,25 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
stage: 'n/a',
production: false,
// keycloak
keycloakURL: 'http://localhost:8888/auth',
keycloakrealm: 'c4po_realm_local',
keycloakclientId: 'c4po_local',
// backend service
apiEndpoint: 'http://localhost:8443',
};
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 KiB

View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>SecurityC4POAngular</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--<link rel="icon" type="image/x-icon" href="src/favicon.ico">-->
</head>
<body id="loader-wrapper">
<app-root id="loader"></app-root>
</body>
</html>

View File

@ -0,0 +1,2 @@
export * from './main';
export * from './polyfills';

View File

@ -0,0 +1,12 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));

View File

@ -0,0 +1,69 @@
/***************************************************************************************************
* Load `$localize` onto the global scope - used if i18n tags appear in Angular templates.
*/
import '@angular/localize/init';
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/**
* Web Animations `@angular/platform-browser/animations`
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
*/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
(window as any).global = window;
(window as any).process = {
env: {DEBUG: undefined},
};
/***************************************************************************************************
* APPLICATION IMPORTS
*/

View File

@ -0,0 +1,4 @@
export const GlobalTitlesVariables = {
SECURITYC4PO_TITLE: 'SecurityC4PO',
NOVATEC_NAME: 'Novatec'
};

View File

@ -0,0 +1,5 @@
import moment from 'moment-timezone';
export function getUtcMomentFromDate(date: Date): moment.Moment {
return moment(date).tz('Etc/UTC');
}

View File

@ -0,0 +1,38 @@
import { TestBed } from '@angular/core/testing';
import { AuthGuardService } from './auth-guard.service';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {RouterTestingModule} from '@angular/router/testing';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {HttpLoaderFactory} from '../../app/common-app.module';
import {HttpClient} from '@angular/common/http';
import {NgxsModule} from '@ngxs/store';
import {SessionState} from '../stores/session-state/session-state';
describe('AuthGuardService', () => {
let service: AuthGuardService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule,
RouterTestingModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
}),
NgxsModule.forRoot([SessionState])
],
providers: [
]
});
service = TestBed.inject(AuthGuardService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,45 @@
import {Injectable} from '@angular/core';
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router';
import {Store} from '@ngxs/store';
import {Observable, of} from 'rxjs';
import {SessionState} from '../stores/session-state/session-state';
import {catchError, map} from 'rxjs/operators';
import {KeycloakAuthGuard, KeycloakService} from 'keycloak-angular';
import {UpdateIsAuthenticated, UpdateUser} from '../stores/session-state/session-state.actions';
import {User} from '../models/user.model';
@Injectable({
providedIn: 'root'
})
export class AuthGuardService extends KeycloakAuthGuard implements CanActivate {
constructor(
public readonly router: Router,
protected keycloakService: KeycloakService,
private readonly store: Store) {
super(router, keycloakService);
}
isAccessAllowed(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
return new Promise((resolve, reject) => {
if (!this.authenticated) {
this.keycloakAngular.login()
.catch(e => console.error(e));
return reject(false);
}
const requiredRoles: string[] = route.data.roles;
if (!requiredRoles || requiredRoles.length === 0) {
this.store.dispatch(new UpdateIsAuthenticated(true));
this.store.dispatch(new UpdateUser(route.data.user, true));
return resolve(true);
} else {
if (!this.roles || this.roles.length === 0) {
this.store.dispatch(new UpdateIsAuthenticated(false));
this.store.dispatch(new UpdateUser(null, true));
resolve(false);
}
resolve(requiredRoles.every(role => this.roles.indexOf(role) > -1));
}
});
}
}

View File

@ -0,0 +1,40 @@
import { TestBed } from '@angular/core/testing';
import { LoginGuardService } from './login-guard.service';
import {RouterTestingModule} from '@angular/router/testing';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {NgxsModule} from '@ngxs/store';
import {SessionState} from '../stores/session-state/session-state';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {HttpLoaderFactory} from '../../app/common-app.module';
import {HttpClient} from '@angular/common/http';
import {KeycloakService} from 'keycloak-angular';
describe('LoginGuardService', () => {
let service: LoginGuardService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule,
RouterTestingModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
}),
NgxsModule.forRoot([SessionState])
],
providers: [
KeycloakService
]
});
service = TestBed.inject(LoginGuardService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,73 @@
import { Injectable } from '@angular/core';
import {Store} from '@ngxs/store';
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router';
import {Observable, of} from 'rxjs';
import {catchError, map, tap} from 'rxjs/operators';
import {SessionState} from '../stores/session-state/session-state';
import {KeycloakAuthGuard, KeycloakService} from 'keycloak-angular';
import {UpdateIsAuthenticated, UpdateUser} from '../stores/session-state/session-state.actions';
@Injectable({
providedIn: 'root'
})
export class LoginGuardService extends KeycloakAuthGuard implements CanActivate {
constructor(
public readonly router: Router,
protected keycloakAngular: KeycloakService,
private readonly store: Store) {
super(router, keycloakAngular);
}
isAccessAllowed(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
return new Promise((resolve, reject) => {
if (!this.authenticated) {
this.keycloakAngular.login()
.catch(e => console.error(e));
return reject(false);
}
const requiredRoles: string[] = route.data.roles;
if (!requiredRoles || requiredRoles.length === 0) {
this.store.dispatch(new UpdateIsAuthenticated(true));
this.store.dispatch(new UpdateUser(route.data.user, true));
return resolve(true);
} else {
if (!this.roles || this.roles.length === 0) {
this.store.dispatch(new UpdateIsAuthenticated(false));
this.store.dispatch(new UpdateUser(null, true));
resolve(false);
}
resolve(requiredRoles.every(role => this.roles.indexOf(role) > -1));
}
});
}
/* canActivate(routeSnapshot: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return this.isAuthenticated().pipe(
tap((canAccess: boolean) => {
if (canAccess) {
this.router.navigate(['']).then();
}
}),
// return true if login should be loaded (=> invert)
map((canAccess: boolean) => !canAccess)
);
}
/!**
* @return state of authentication
*!/
private isAuthenticated(): Observable<boolean> {
// ToDo: Should check from Authentication Provider
return of(this.store.selectSnapshot(SessionState.isAuthenticated))
.pipe(
map((isLoggedIn: boolean) => {
return isLoggedIn;
}),
catchError(() => {
return of(false);
})
);
}*/
}

View File

@ -0,0 +1,6 @@
import {HTTP_INTERCEPTORS} from '@angular/common/http';
import {TokenInterceptor} from './token.interceptor';
export const httpInterceptorProviders = [
{provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true},
];

View File

@ -0,0 +1,72 @@
import {Injectable} from '@angular/core';
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {KeycloakService} from 'keycloak-angular';
import {environment} from '../../environments/environment';
import {Observable, Subscriber} from 'rxjs';
import {mergeMap} from 'rxjs/operators';
@Injectable()
export class TokenInterceptor implements HttpInterceptor {
constructor(private keycloakService: KeycloakService) {
}
/**
* TODO: has to be edit every time a service is added and requires the keycloak token on HEADER
*/
private static listOfKeycloakRelevantHosts(): { origin: string }[] {
const relevantList = new Array<{ origin: string }>();
relevantList.push({origin: getOriginByUrl(environment.apiEndpoint)});
relevantList.push({origin: getOriginByUrl(environment.keycloakURL)});
return relevantList;
function getOriginByUrl(inputUrl: string): string {
return (new URL(inputUrl)).origin;
}
}
private static requestTargetIsWhitelisted(requestTargetOrigin: string): boolean {
if (requestTargetOrigin) {
try {
const targetUrl: URL = new URL(requestTargetOrigin);
if (targetUrl && targetUrl.origin) {
const matchList = TokenInterceptor.listOfKeycloakRelevantHosts()
.map(value => value.origin)
.filter(value => (value === targetUrl.origin));
return !!matchList.length;
}
} catch (e) {
// ignore e.g. local calls
}
}
return false;
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const requestTargetHost = request.url || '';
if (TokenInterceptor.requestTargetIsWhitelisted(requestTargetHost)) {
const tokenObserver: Observable<string> = new Observable((observer: Subscriber<any>): void => {
this.keycloakService.getToken().then(token => {
observer.next(token);
observer.complete();
}).catch(error => {
observer.error(error);
observer.complete();
});
});
return tokenObserver.pipe(
mergeMap((authToken: string) => {
request = request.clone({
headers: request.headers.append('Authorization', `Bearer ${authToken}`)
});
return next.handle(request);
}
));
} else {
// Do nothing
return next.handle(request);
}
}
}

View File

@ -0,0 +1,8 @@
export enum FieldStatus {
BASIC = 'basic',
PRIMARY = 'primary',
INFO = 'info',
SUCCESS = 'success',
WARNING = 'warning',
DANGER = 'danger'
}

View File

@ -0,0 +1,25 @@
import { v4 as UUID } from 'uuid';
export class Project {
id: string;
client: string;
title: string;
/* Change to Date after database integration */
createdAt: string;
tester: string;
logo: string;
constructor(id: string,
client: string,
title: string,
createdAt: string,
tester?: string,
logo?: string) {
this.id = id;
this.client = client;
this.title = title;
this.createdAt = createdAt;
this.tester = tester;
this.logo = logo;
}
}

View File

@ -0,0 +1,31 @@
import { v4 as UUID } from 'uuid';
export class User {
id?: string;
username?: string;
firstName?: string;
lastName?: string;
mailAddress?: string;
interfaceLang?: string;
constructor(username?: string,
firstName?: string,
lastName?: string,
email?: string,
interfaceLang?: string) {
this.id = UUID();
this.username = username;
this.firstName = firstName;
this.lastName = lastName;
if (email) {
this.mailAddress = email;
} else {
this.mailAddress = null;
}
if (interfaceLang) {
this.interfaceLang = interfaceLang;
} else {
this.interfaceLang = 'en-US';
}
}
}

View File

@ -0,0 +1,6 @@
import {NotificationService, PopupType} from './notification.service';
export class NotificationServiceMock implements Required<NotificationService> {
public showPopup(translationKey: string, popupType?: PopupType): void {
}
}

View File

@ -0,0 +1,59 @@
import { TestBed } from '@angular/core/testing';
import { NotificationService } from './notification.service';
import {NbToastrModule, NbToastrService} from '@nebular/theme';
import {TranslateLoader, TranslateModule, TranslateService} from '@ngx-translate/core';
import {Observable, of} from 'rxjs';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {HttpLoaderFactory} from '../../app/common-app.module';
import {HttpClient} from '@angular/common/http';
import {NgxsModule} from '@ngxs/store';
import {SessionState} from '../stores/session-state/session-state';
import {KeycloakService} from 'keycloak-angular';
describe('NotificationService', () => {
let toastrServiceStub: Partial<NbToastrService>;
let translateServiceStub: Partial<TranslateService>;
let service: NotificationService;
toastrServiceStub = {
show(): any {
return {};
}
};
translateServiceStub = {
get(): Observable<string | any> {
return of(new Observable<string>());
}
};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule,
BrowserAnimationsModule,
NbToastrModule,
NgxsModule.forRoot([SessionState]),
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
}),
],
providers: [
NotificationService,
KeycloakService,
{provide: NbToastrService, useValue: toastrServiceStub},
{provide: TranslateService, useValue: translateServiceStub}]
});
service = TestBed.inject(NotificationService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,39 @@
import {Injectable} from '@angular/core';
import {TranslateService} from '@ngx-translate/core';
import {NbGlobalPhysicalPosition, NbToastrService} from '@nebular/theme';
@Injectable({
providedIn: 'root'
})
export class NotificationService {
constructor(private toastrService: NbToastrService,
private translateService: TranslateService) {
}
public showPopup(translationKey: string, popupType: PopupType): void {
// TODO: Eliminate subscriptions by using flattening operators
this.translateService.get([popupType, translationKey])
.subscribe((translationContainer) => {
this.toastrService.show(
'',
translationContainer[popupType] + ' ' + translationContainer[translationKey], {
position: NbGlobalPhysicalPosition.BOTTOM_RIGHT,
duration: 5000,
toastClass: createCssClassName(popupType)
});
});
function createCssClassName(type: PopupType): string {
const currentType = type ? type : PopupType.INFO;
return currentType.toString().replace('.', '-');
}
}
}
export enum PopupType {
SUCCESS = 'popup.success',
FAILURE = 'popup.failure',
WARNING = 'popup.warning',
INFO = 'popup.info'
}

View File

@ -0,0 +1,27 @@
import { TestBed } from '@angular/core/testing';
import { ProjectService } from './project.service';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {KeycloakService} from 'keycloak-angular';
describe('ProjectService', () => {
let service: ProjectService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule,
BrowserAnimationsModule,
],
providers: [
KeycloakService
]
});
service = TestBed.inject(ProjectService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,20 @@
import { Injectable } from '@angular/core';
import {environment} from '../../environments/environment';
import {HttpClient} from '@angular/common/http';
import {Project} from '../models/project.model';
import {Observable} from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class ProjectService {
private apiBaseURL = `${environment.apiEndpoint}/projects`;
constructor(private http: HttpClient) {
}
public getProjects(): Observable<Project[]> {
return this.http.get<Project[]>(`${this.apiBaseURL}`);
}
}

View File

@ -0,0 +1,40 @@
import { TestBed } from '@angular/core/testing';
import { UserService } from './user.service';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {HttpLoaderFactory} from '../../app/common-app.module';
import {HttpClient} from '@angular/common/http';
import {NgxsModule} from '@ngxs/store';
import {SessionState} from '../stores/session-state/session-state';
import {KeycloakService} from 'keycloak-angular';
describe('UserService', () => {
let service: UserService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule,
BrowserAnimationsModule,
NgxsModule.forRoot([SessionState]),
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
}),
],
providers: [
KeycloakService
]
});
service = TestBed.inject(UserService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,54 @@
import {Injectable} from '@angular/core';
import {HttpClient, HttpHeaders} from '@angular/common/http';
import {User} from '../models/user.model';
import {from, Observable, Subscriber} from 'rxjs';
import {Store} from '@ngxs/store';
import {KeycloakService} from 'keycloak-angular';
import {map} from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class UserService {
constructor(private http: HttpClient,
private keycloakService: KeycloakService,
private store: Store) {
}
private static createHttpHeadersWithContentType(token: string): HttpHeaders {
return new HttpHeaders({
'Content-Type': 'application/json',
});
}
private createHttpOptions(): Observable<any> {
return this.getToken().pipe(
// create HttpHeaders
map((token: string): HttpHeaders => {
return UserService.createHttpHeadersWithContentType(token);
}),
// createHttpOptions
map((httpHeaders: HttpHeaders): { headers } => {
return {headers: httpHeaders};
})
);
}
public loadUserProfile(): Observable<User> {
return from(this.keycloakService.loadUserProfile()) as Observable<User>;
}
private getToken(): Observable<string> {
return new Observable((observer: Subscriber<any>): void => {
this.keycloakService.getToken().then(token => {
console.warn(token);
observer.next(token);
observer.complete();
}).catch(error => {
observer.error(error);
observer.complete();
});
});
}
}

View File

@ -0,0 +1,41 @@
import {User} from '../../models/user.model';
export class InitSession {
static readonly type = '[Session] InitSession';
}
// resetLocalUser
export class ResetSession {
static readonly type = '[Session] ResetSession';
}
export class FetchUser {
static readonly type = '[Session] FetchUser';
}
export class UpdateUser {
static readonly type = '[Session] UpdateUser';
/**
* Updates the current user-account, if user isn't equal!
* @param user of type User (user can be null if no user is logged in)
* @param force if update should be forced!
*/
constructor(public user: User, public force = false) {
}
}
// updateUserSpecificSettings
export class UpdateUserSettings {
static readonly type = '[Session] UpdateUserSettings';
constructor(public user: User) {
}
}
export class UpdateIsAuthenticated {
static readonly type = '[Session] UpdateIsAuthenticated';
constructor(public authenticated: boolean) {
}
}

View File

@ -0,0 +1,108 @@
import {NgxsModule, Store} from '@ngxs/store';
import {fakeAsync, TestBed, tick} from '@angular/core/testing';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {HttpLoaderFactory} from '../../../app/common-app.module';
import {HttpClient} from '@angular/common/http';
import {SESSION_STATE_NAME, SessionState, SessionStateModel} from './session-state';
import {User} from '../../models/user.model';
import {InitSession, UpdateUser} from './session-state.actions';
import {KeycloakService} from 'keycloak-angular';
const INITIAL_STORE_STATE_SESSION: SessionStateModel = {
userAccount: {
...new User(),
id: ''
},
isAuthenticated: false
};
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('SessionState', () => {
let store: Store;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
}),
NgxsModule.forRoot([SessionState]),
],
providers: [
KeycloakService
]
});
store = TestBed.inject(Store);
store.reset({
...store.snapshot(),
[SESSION_STATE_NAME]: DESIRED_STORE_STATE_SESSION
});
});
it('should contain store for SESSION_STATE_NAME', (done) => {
store.selectSnapshot(state => {
expect(state[SESSION_STATE_NAME]).toBeTruthy();
done();
});
});
describe('SelectorTests', () => {
describe('userAccount', () => {
it('should return userAccount', () => {
const user = store.selectSnapshot(SessionState.userAccount);
expect(user).toBeTruthy();
});
});
describe('isAuthenticated', () => {
it('should return isAuthenticated', () => {
const isAuthenticated = store.selectSnapshot(SessionState.isAuthenticated);
expect(isAuthenticated).toBeTruthy();
});
});
});
describe('ActionTests', () => {
describe('InitSessionState', () => {
it('should initialize default state', () => {
store.dispatch(new InitSession());
const currentState = store.selectSnapshot(SessionState);
expect(currentState).toBeTruthy();
});
});
describe('UpdateUser', () => {
it('should update user', fakeAsync(() => {
const newUser = {
...new User('ttt', 'test', 'user', 'default.user@test.de', 'en-US'),
id: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
};
store.dispatch(new UpdateUser(newUser));
tick();
const currentUser = store.selectSnapshot(SessionState.userAccount);
expect(currentUser).toEqual(newUser);
}));
it('should force update user', fakeAsync(() => {
const newUser = {
...new User('ttt', 'test', 'user', 'default.user@test.de', 'en-US'),
id: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
};
store.dispatch(new UpdateUser(newUser, true));
tick();
const currentUser = store.selectSnapshot(SessionState.userAccount);
expect(currentUser).toEqual(newUser);
}));
});
});
});

View File

@ -0,0 +1,108 @@
import {User} from '../../models/user.model';
import {Inject, Injectable, LOCALE_ID} from '@angular/core';
import {Action, Selector, State, StateContext} from '@ngxs/store';
import {TranslateService} from '@ngx-translate/core';
import {FetchUser, InitSession, ResetSession, UpdateIsAuthenticated, UpdateUser, UpdateUserSettings} from './session-state.actions';
import deepEqual from 'deep-equal';
import moment from 'moment';
import {UserService} from '../../services/user.service';
export interface SessionStateModel {
userAccount: User;
isAuthenticated: boolean;
}
export const SESSION_STATE_NAME = 'session';
export const SESSION_STORAGE_KEY_USER = 'user';
@State<SessionStateModel>({
name: SESSION_STATE_NAME,
defaults: {
userAccount: null,
isAuthenticated: false
}
})
@Injectable()
export class SessionState {
constructor(@Inject(LOCALE_ID) private readonly localeId: string,
private readonly userService: UserService,
private readonly translateService: TranslateService) {
}
@Selector()
static userAccount(state: SessionStateModel): User {
return state.userAccount;
}
@Selector()
static isAuthenticated(state: SessionStateModel): boolean {
return state.isAuthenticated;
}
@Action(InitSession)
initSessionState(ctx: StateContext<SessionStateModel>): void {
ctx.setState({
userAccount: null,
isAuthenticated: false
});
}
@Action(ResetSession)
resetSession(ctx: StateContext<SessionStateModel>): void {
this.deleteSessionStorage();
ctx.dispatch(new InitSession());
}
@Action(FetchUser)
fetchUser(ctx: StateContext<SessionStateModel>): void {
this.userService.loadUserProfile().subscribe({
next: (user: User): void => {
ctx.dispatch(new UpdateUser(user, true));
},
// TODO: add better error handling
error: (err) => console.error('Failed to load UserProfile', err)
});
}
@Action(UpdateUser)
updateUser(ctx: StateContext<SessionStateModel>, {user, force}: UpdateUser): void {
const state = ctx.getState();
if (force || !deepEqual(user, state.userAccount)) {
ctx.patchState({
userAccount: user
});
// write to sessionStorage
this.setSessionStorage(user);
ctx.dispatch(new UpdateUserSettings(user));
}
}
// TODO: Should be connected KeyCloak
@Action(UpdateIsAuthenticated)
updateIsAuthenticated(ctx: StateContext<SessionStateModel>, {authenticated}: UpdateIsAuthenticated): void {
ctx.patchState({isAuthenticated: authenticated});
}
@Action(UpdateUserSettings)
updateUserSettings(ctx: StateContext<SessionStateModel>, {user}: UpdateUserSettings): void {
if (user) {
if (user.interfaceLang) {
const newLanguage = user.interfaceLang;
this.translateService.use(newLanguage);
moment.locale(newLanguage);
}
}
}
private setSessionStorage(user: User): void {
// TODO: https://www.ngxs.io/plugins/storage
if (user) {
sessionStorage.setItem(SESSION_STORAGE_KEY_USER, JSON.stringify(user));
}
}
private deleteSessionStorage(): void {
// TODO: https://www.ngxs.io/plugins/storage
sessionStorage.removeItem(SESSION_STORAGE_KEY_USER);
}
}

View File

@ -0,0 +1,21 @@
import { TestBed } from '@angular/core/testing';
type CompilerOptions = Partial<{
providers: any[];
useJit: boolean;
preserveWhitespaces: boolean;
}>;
export type ConfigureFn = (testBed: typeof TestBed) => void;
export const configureTests = (configure: ConfigureFn, compilerOptions: CompilerOptions = {}) => {
const compilerConfig: CompilerOptions = {
preserveWhitespaces: false,
...compilerOptions,
};
const configuredTestBed = TestBed.configureCompiler(compilerConfig);
configure(configuredTestBed);
return configuredTestBed.compileComponents().then(() => configuredTestBed);
};

View File

@ -0,0 +1,15 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.d.ts"
]
}

View File

@ -0,0 +1,25 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "es2015",
"module": "es2020",
"lib": [
"es2018",
"dom"
],
"paths": {
"@shared/*": ["./src/app/shared/*"],
"@assets/*": ["./src/assets/*"]
}
}
}

View File

@ -0,0 +1,19 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jest"
],
"module": "commonjs",
"emitDecoratorMetadata": true,
"allowJs": true
},
"files": [
"src/polyfills.ts"
],
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}

View File

@ -0,0 +1,152 @@
{
"extends": "tslint:recommended",
"rulesDirectory": [
"codelyzer"
],
"rules": {
"align": {
"options": [
"parameters",
"statements"
]
},
"array-type": false,
"arrow-return-shorthand": true,
"curly": true,
"deprecation": {
"severity": "warning"
},
"eofline": true,
"import-blacklist": [
true,
"rxjs/Rx"
],
"import-spacing": true,
"indent": {
"options": [
"spaces"
]
},
"max-classes-per-file": false,
"max-line-length": [
true,
140
],
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-empty": false,
"no-inferrable-types": [
true,
"ignore-params"
],
"no-non-null-assertion": true,
"no-redundant-jsdoc": true,
"no-switch-case-fall-through": true,
"no-var-requires": false,
"object-literal-key-quotes": [
true,
"as-needed"
],
"quotemark": [
true,
"single"
],
"semicolon": {
"options": [
"always"
]
},
"space-before-function-paren": {
"options": {
"anonymous": "never",
"asyncArrow": "always",
"constructor": "never",
"method": "never",
"named": "never"
}
},
"typedef": [
true,
"call-signature"
],
"typedef-whitespace": {
"options": [
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
},
{
"call-signature": "onespace",
"index-signature": "onespace",
"parameter": "onespace",
"property-declaration": "onespace",
"variable-declaration": "onespace"
}
]
},
"variable-name": {
"options": [
"ban-keywords",
"check-format",
"allow-pascal-case"
]
},
"whitespace": {
"options": [
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type",
"check-typecast"
]
},
"component-class-suffix": true,
"contextual-lifecycle": true,
"directive-class-suffix": true,
"no-conflicting-lifecycle": true,
"no-host-metadata-property": true,
"no-input-rename": true,
"no-inputs-metadata-property": true,
"no-output-native": true,
"no-output-on-prefix": true,
"no-output-rename": true,
"no-outputs-metadata-property": true,
"template-banana-in-box": true,
"template-no-negated-async": true,
"use-lifecycle-interface": true,
"use-pipe-transform-interface": true,
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
]
}
}

37
security-c4po-api/.gitignore vendored Normal file
View File

@ -0,0 +1,37 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/

View File

@ -0,0 +1,7 @@
FROM openjdk:11-jre
# COPY PACKAGE INTO IMAGE
COPY ./build/libs/security-c4po-api-0.0.1-SNAPSHOT.jar .
# RUN JAVA
CMD [ "java", "-jar", "security-c4po-api-0.0.1-SNAPSHOT.jar" ]

View File

@ -0,0 +1,142 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
buildscript {
repositories {
mavenCentral()
jcenter()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:2.3.3.RELEASE")
classpath("org.owasp:dependency-check-gradle:6.0.0")
}
}
plugins {
id("org.springframework.boot") version "2.3.4.RELEASE"
id("io.spring.dependency-management") version "1.0.10.RELEASE"
id("com.github.spotbugs") version "4.5.0"
id("org.owasp.dependencycheck") version "6.0.0"
id("org.asciidoctor.jvm.convert") version "2.4.0"
kotlin("jvm") version "1.3.72"
kotlin("plugin.spring") version "1.3.72"
jacoco
}
group = "com.security-c4po.api"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11
configurations {
compileOnly {
extendsFrom(configurations.annotationProcessor.get())
}
}
repositories {
mavenCentral()
}
dependencyCheck {
autoUpdate = true
cveValidForHours = 1
}
spotbugs {
showProgress.set(true)
tasks.spotbugsMain {
reports.create("html") {
isEnabled = true
}
}
tasks.spotbugsTest {
reports.create("html") {
isEnabled = true
}
}
}
val snippetsDir = file("build/generated-snippets")
dependencies {
implementation("com.fasterxml.jackson.datatype:jackson-datatype-joda:2.11.3")
implementation("io.projectreactor.kotlin:reactor-kotlin-extensions:1.1.1")
implementation("javax.websocket:javax.websocket-api:1.1")
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
/*implementation("org.springframework.boot:spring-boot-starter-data-mongodb-reactive")
implementation("org.springframework.boot:spring-boot-starter-data-mongodb")*/
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("com.github.spotbugs:spotbugs-annotations:4.1.2")
implementation("org.modelmapper:modelmapper:2.3.2")
api("org.springframework.boot:spring-boot-starter-test")
api("org.springframework.security:spring-security-jwt:1.1.1.RELEASE")
testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0")
testImplementation("io.projectreactor:reactor-test")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.3.1")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.3.1")
testImplementation("org.springframework.cloud:spring-cloud-contract-wiremock:2.1.0.RELEASE")
testImplementation("org.springframework.restdocs:spring-restdocs-webtestclient")
}
jacoco {
toolVersion = "0.8.3"
reportsDir = file("$buildDir/reports/coverage")
}
tasks.withType<Test> {
useJUnitPlatform()
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "11"
}
}
tasks.bootJar {
dependsOn(tasks.test, tasks.asciidoctor, tasks.jacocoTestReport, tasks.dependencyCheckAnalyze)
}
tasks.test {
outputs.dir(snippetsDir)
}
tasks.dependencyCheckAnalyze {
dependsOn(tasks.test, tasks.asciidoctor, tasks.jacocoTestReport)
}
//Issue with Kotlin assignment of sourceDir and outputDir: https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues/458
tasks.asciidoctor {
inputs.dir(snippetsDir)
setSourceDir(file("src/main/asciidoc"))
setOutputDir(file("$buildDir/asciidoc"))
sources(delegateClosureOf<PatternSet> {
include("SecurityC4PO.adoc")
})
attributes(
mapOf(
"snippets" to snippetsDir,
"source-highlighter" to "coderay",
"toc" to "left",
"toclevels" to 3,
"sectlinks" to true
)
)
dependsOn(tasks.test)
}
tasks.jacocoTestReport {
reports {
xml.isEnabled = true
csv.isEnabled = false
html.isEnabled = true
html.destination = file("$buildDir/reports/coverage")
}
}

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More