Compare commits
8 Commits
main
...
mhg_c4po_3
Author | SHA1 | Date |
---|---|---|
|
4fb3d82fee | |
|
cccb3c5b3e | |
|
ffe6553990 | |
|
6c71282798 | |
|
d3f3a7ecfb | |
|
0001b67e4a | |
|
906f1a1705 | |
|
ab88a02652 |
|
@ -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!
|
||||
|
|
|
@ -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
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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.
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}));
|
||||
}
|
||||
};
|
|
@ -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));
|
||||
});
|
||||
});
|
|
@ -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>;
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
module.exports = {
|
||||
moduleNameMapper: {
|
||||
'@core/(.*)': '<rootDir>/src/app/core/$1',
|
||||
},
|
||||
preset: 'jest-preset-angular',
|
||||
setupFilesAfterEnv: ['<rootDir>/setup-jest.ts'],
|
||||
};
|
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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 { }
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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 { }
|
|
@ -0,0 +1 @@
|
|||
<p>dashboard works!</p>
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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 {
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export {DashboardModule} from './dashboard.module';
|
||||
export {DashboardRoutingModule} from './dashboard-routing.module';
|
|
@ -0,0 +1,4 @@
|
|||
<div class="c4po-header">
|
||||
<p>header works!</p>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
@import "../../assets/@theme/styles/_variables.scss";
|
||||
|
||||
.c4po-header {
|
||||
height: $header-height;
|
||||
|
||||
.toggle-dark-mode {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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 {
|
||||
}
|
||||
|
||||
}
|
|
@ -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 { }
|
|
@ -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>
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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 {
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export {LoginModule} from './login.module';
|
||||
export {LoginRoutingModule} from './login-routing.module';
|
|
@ -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 {}
|
|
@ -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>
|
||||
© {{NOVATEC_NAME}}
|
||||
</div>
|
||||
</div>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
</div>
|
|
@ -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%
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
@mixin ngx-layout() {
|
||||
@include media-breakpoint-down(is) {
|
||||
.row {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 */
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
$header-height: 4rem;
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 */
|
|
@ -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;
|
|
@ -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;
|
|
@ -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);
|
|
@ -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,
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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 |
|
@ -0,0 +1 @@
|
|||
{"version": "local"}
|
|
@ -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',
|
||||
};
|
|
@ -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 |
|
@ -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>
|
|
@ -0,0 +1,2 @@
|
|||
export * from './main';
|
||||
export * from './polyfills';
|
|
@ -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));
|
|
@ -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
|
||||
*/
|
|
@ -0,0 +1,4 @@
|
|||
export const GlobalTitlesVariables = {
|
||||
SECURITYC4PO_TITLE: 'SecurityC4PO',
|
||||
NOVATEC_NAME: 'Novatec'
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
import moment from 'moment-timezone';
|
||||
|
||||
export function getUtcMomentFromDate(date: Date): moment.Moment {
|
||||
return moment(date).tz('Etc/UTC');
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
})
|
||||
);
|
||||
}*/
|
||||
}
|
||||
|
|
@ -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},
|
||||
];
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
export enum FieldStatus {
|
||||
BASIC = 'basic',
|
||||
PRIMARY = 'primary',
|
||||
INFO = 'info',
|
||||
SUCCESS = 'success',
|
||||
WARNING = 'warning',
|
||||
DANGER = 'danger'
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import {NotificationService, PopupType} from './notification.service';
|
||||
|
||||
export class NotificationServiceMock implements Required<NotificationService> {
|
||||
public showPopup(translationKey: string, popupType?: PopupType): void {
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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'
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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}`);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
};
|
|
@ -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"
|
||||
]
|
||||
}
|
|
@ -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/*"]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
]
|
||||
}
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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/
|
|
@ -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" ]
|
|
@ -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
Loading…
Reference in New Issue