Added angular frontend #70
This commit is contained in:
parent
ee49bde961
commit
8c3cd1fae7
16
kdb-web/.browserslistrc
Normal file
16
kdb-web/.browserslistrc
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# 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
|
16
kdb-web/.editorconfig
Normal file
16
kdb-web/.editorconfig
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Editor configuration, see https://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.ts]
|
||||||
|
quote_type = single
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
max_line_length = off
|
||||||
|
trim_trailing_whitespace = false
|
43
kdb-web/.gitignore
vendored
Normal file
43
kdb-web/.gitignore
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# Compiled output
|
||||||
|
/dist
|
||||||
|
/tmp
|
||||||
|
/out-tsc
|
||||||
|
/bazel-out
|
||||||
|
|
||||||
|
# Node
|
||||||
|
/node_modules
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
.idea/
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
.vscode/*
|
||||||
|
.vscode
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.history/*
|
||||||
|
|
||||||
|
# Miscellaneous
|
||||||
|
/.angular/cache
|
||||||
|
.sass-cache/
|
||||||
|
/connect.lock
|
||||||
|
/coverage
|
||||||
|
/libpeerconnection.log
|
||||||
|
testem.log
|
||||||
|
/typings
|
||||||
|
|
||||||
|
# System files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
27
kdb-web/README.md
Normal file
27
kdb-web/README.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# KdbWeb
|
||||||
|
|
||||||
|
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.0.
|
||||||
|
|
||||||
|
## Development server
|
||||||
|
|
||||||
|
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application 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.
|
||||||
|
|
||||||
|
## 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 a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
|
||||||
|
|
||||||
|
## 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.
|
105
kdb-web/angular.json
Normal file
105
kdb-web/angular.json
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"version": 1,
|
||||||
|
"newProjectRoot": "projects",
|
||||||
|
"projects": {
|
||||||
|
"kdb-web": {
|
||||||
|
"projectType": "application",
|
||||||
|
"schematics": {
|
||||||
|
"@schematics/angular:component": {
|
||||||
|
"style": "scss"
|
||||||
|
},
|
||||||
|
"@schematics/angular:application": {
|
||||||
|
"strict": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"prefix": "app",
|
||||||
|
"architect": {
|
||||||
|
"build": {
|
||||||
|
"builder": "@angular-devkit/build-angular:browser",
|
||||||
|
"options": {
|
||||||
|
"outputPath": "dist/kdb-web",
|
||||||
|
"index": "src/index.html",
|
||||||
|
"main": "src/main.ts",
|
||||||
|
"polyfills": "src/polyfills.ts",
|
||||||
|
"tsConfig": "tsconfig.app.json",
|
||||||
|
"inlineStyleLanguage": "scss",
|
||||||
|
"assets": ["src/favicon.ico", "src/assets"],
|
||||||
|
"styles": [
|
||||||
|
"src/styles.scss",
|
||||||
|
"node_modules/primeicons/primeicons.css",
|
||||||
|
"node_modules/primeng/resources/themes/lara-light-blue/theme.css",
|
||||||
|
"node_modules/primeng/resources/primeng.min.css"
|
||||||
|
],
|
||||||
|
"scripts": []
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"budgets": [
|
||||||
|
{
|
||||||
|
"type": "initial",
|
||||||
|
"maximumWarning": "500kb",
|
||||||
|
"maximumError": "1mb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "anyComponentStyle",
|
||||||
|
"maximumWarning": "2kb",
|
||||||
|
"maximumError": "4kb"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fileReplacements": [
|
||||||
|
{
|
||||||
|
"replace": "src/environments/environment.ts",
|
||||||
|
"with": "src/environments/environment.prod.ts"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputHashing": "all"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"buildOptimizer": false,
|
||||||
|
"optimization": false,
|
||||||
|
"vendorChunk": true,
|
||||||
|
"extractLicenses": false,
|
||||||
|
"sourceMap": true,
|
||||||
|
"namedChunks": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "production"
|
||||||
|
},
|
||||||
|
"serve": {
|
||||||
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"browserTarget": "kdb-web:build:production"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"browserTarget": "kdb-web:build:development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "development"
|
||||||
|
},
|
||||||
|
"extract-i18n": {
|
||||||
|
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||||
|
"options": {
|
||||||
|
"browserTarget": "kdb-web:build"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"builder": "@angular-devkit/build-angular:karma",
|
||||||
|
"options": {
|
||||||
|
"main": "src/test.ts",
|
||||||
|
"polyfills": "src/polyfills.ts",
|
||||||
|
"tsConfig": "tsconfig.spec.json",
|
||||||
|
"karmaConfig": "karma.conf.js",
|
||||||
|
"inlineStyleLanguage": "scss",
|
||||||
|
"assets": ["src/favicon.ico", "src/assets"],
|
||||||
|
"styles": ["src/styles.scss"],
|
||||||
|
"scripts": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
kdb-web/karma.conf.js
Normal file
44
kdb-web/karma.conf.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// Karma configuration file, see link for more information
|
||||||
|
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||||
|
|
||||||
|
module.exports = function (config) {
|
||||||
|
config.set({
|
||||||
|
basePath: '',
|
||||||
|
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||||
|
plugins: [
|
||||||
|
require('karma-jasmine'),
|
||||||
|
require('karma-chrome-launcher'),
|
||||||
|
require('karma-jasmine-html-reporter'),
|
||||||
|
require('karma-coverage'),
|
||||||
|
require('@angular-devkit/build-angular/plugins/karma')
|
||||||
|
],
|
||||||
|
client: {
|
||||||
|
jasmine: {
|
||||||
|
// you can add configuration options for Jasmine here
|
||||||
|
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
||||||
|
// for example, you can disable the random execution with `random: false`
|
||||||
|
// or set a specific seed with `seed: 4321`
|
||||||
|
},
|
||||||
|
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||||
|
},
|
||||||
|
jasmineHtmlReporter: {
|
||||||
|
suppressAll: true // removes the duplicated traces
|
||||||
|
},
|
||||||
|
coverageReporter: {
|
||||||
|
dir: require('path').join(__dirname, './coverage/kdb-web'),
|
||||||
|
subdir: '.',
|
||||||
|
reporters: [
|
||||||
|
{ type: 'html' },
|
||||||
|
{ type: 'text-summary' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
reporters: ['progress', 'kjhtml'],
|
||||||
|
port: 9876,
|
||||||
|
colors: true,
|
||||||
|
logLevel: config.LOG_INFO,
|
||||||
|
autoWatch: true,
|
||||||
|
browsers: ['Chrome'],
|
||||||
|
singleRun: false,
|
||||||
|
restartOnFileChange: true
|
||||||
|
});
|
||||||
|
};
|
21484
kdb-web/package-lock.json
generated
Normal file
21484
kdb-web/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
49
kdb-web/package.json
Normal file
49
kdb-web/package.json
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"name": "kdb-web",
|
||||||
|
"version": "0.3.0",
|
||||||
|
"scripts": {
|
||||||
|
"ng": "ng",
|
||||||
|
"update-version": "ts-node -O '{\"module\": \"commonjs\"}' update-version.ts",
|
||||||
|
"prestart": "npm run update-version",
|
||||||
|
"start": "ng serve",
|
||||||
|
"prebuild": "npm run update-version",
|
||||||
|
"build": "ng build",
|
||||||
|
"watch": "ng build --watch --configuration development",
|
||||||
|
"test": "ng test"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@angular/animations": "^14.0.0",
|
||||||
|
"@angular/common": "^14.0.0",
|
||||||
|
"@angular/compiler": "^14.0.0",
|
||||||
|
"@angular/core": "^14.0.0",
|
||||||
|
"@angular/forms": "^14.0.0",
|
||||||
|
"@angular/platform-browser": "^14.0.0",
|
||||||
|
"@angular/platform-browser-dynamic": "^14.0.0",
|
||||||
|
"@angular/router": "^14.0.0",
|
||||||
|
"@auth0/angular-jwt": "^5.1.0",
|
||||||
|
"@microsoft/signalr": "^6.0.9",
|
||||||
|
"@ngx-translate/core": "^14.0.0",
|
||||||
|
"@ngx-translate/http-loader": "^7.0.0",
|
||||||
|
"primeicons": "^6.0.1",
|
||||||
|
"primeng": "^14.1.2",
|
||||||
|
"rxjs": "~7.5.0",
|
||||||
|
"tslib": "^2.3.0",
|
||||||
|
"zone.js": "~0.11.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@angular-devkit/build-angular": "^14.0.0",
|
||||||
|
"@angular/cli": "~14.0.0",
|
||||||
|
"@angular/compiler-cli": "^14.0.0",
|
||||||
|
"@types/jasmine": "~4.0.0",
|
||||||
|
"@types/node": "^18.8.3",
|
||||||
|
"jasmine-core": "~4.1.0",
|
||||||
|
"karma": "~6.3.0",
|
||||||
|
"karma-chrome-launcher": "~3.1.0",
|
||||||
|
"karma-coverage": "~2.2.0",
|
||||||
|
"karma-jasmine": "~5.0.0",
|
||||||
|
"karma-jasmine-html-reporter": "~1.7.0",
|
||||||
|
"ts-node": "~8.3.0",
|
||||||
|
"typescript": "~4.7.2"
|
||||||
|
}
|
||||||
|
}
|
22
kdb-web/src/app/app-routing.module.ts
Normal file
22
kdb-web/src/app/app-routing.module.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { NotFoundComponent } from './components/error/not-found/not-found.component';
|
||||||
|
import { AuthRoles } from './models/auth/auth-roles.enum';
|
||||||
|
import { AuthGuard } from './modules/shared/guards/auth/auth.guard';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' },
|
||||||
|
{ path: 'dashboard', loadChildren: () => import('./modules/view/dashboard/dashboard.module').then(m => m.DashboardModule), canActivate: [AuthGuard] },
|
||||||
|
{ path: 'change-password', loadChildren: () => import('./modules/view/change-password/change-password.module').then(m => m.ChangePasswordModule), canActivate: [AuthGuard] },
|
||||||
|
{ path: 'user-settings', loadChildren: () => import('./modules/view/user-settings/user-settings.module').then(m => m.UserSettingsModule), canActivate: [AuthGuard] },
|
||||||
|
{ path: 'auth', loadChildren: () => import('./modules/auth/auth.module').then(m => m.AuthModule) },
|
||||||
|
{ path: 'admin/settings', loadChildren: () => import('./modules/admin/settings/settings.module').then(m => m.SettingsModule), canActivate: [AuthGuard], data: { role: AuthRoles.Admin } },
|
||||||
|
{ path: 'admin/users', loadChildren: () => import('./modules/admin/auth-users/auth-user.module').then(m => m.AuthUserModule), canActivate: [AuthGuard], data: { role: AuthRoles.Admin } },
|
||||||
|
{ path: '404', component: NotFoundComponent}
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forRoot(routes)],
|
||||||
|
exports: [RouterModule]
|
||||||
|
})
|
||||||
|
export class AppRoutingModule { }
|
36
kdb-web/src/app/app.component.html
Normal file
36
kdb-web/src/app/app.component.html
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<main [class]="themeService.themeName">
|
||||||
|
<ng-container *ngIf="authService.isLoggedIn; else login">
|
||||||
|
<app-header (isSidebarFullWidth)="themeService.setSideWidth($event)"></app-header>
|
||||||
|
|
||||||
|
<section class="app">
|
||||||
|
<div>
|
||||||
|
<section class="sidebar" [style.width]="themeService.sidebarWidth">
|
||||||
|
<app-sidebar [isSidebarOpen]="themeService.isSidebarOpen"></app-sidebar>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div class="component-wrapper">
|
||||||
|
<section class="component">
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<app-footer></app-footer>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
|
||||||
|
<ng-template #login>
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<app-spinner></app-spinner>
|
||||||
|
<p-confirmDialog #cd key="confirmConfirmationDialog" [baseZIndex]="10000">
|
||||||
|
<ng-template pTemplate="footer">
|
||||||
|
<div class="wrapper-right btn-wrapper">
|
||||||
|
<button pButton label="{{'dialog.abort' | translate}}" class="btn icon-btn danger-icon-btn" icon="pi pi-times-circle" (click)="cd.reject()"></button>
|
||||||
|
<button pButton label="{{'dialog.confirm' | translate}}" class="btn" icon="pi pi-check-circle" (click)="cd.accept()"></button>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</p-confirmDialog>
|
||||||
|
<p-toast></p-toast>
|
||||||
|
</main>
|
0
kdb-web/src/app/app.component.scss
Normal file
0
kdb-web/src/app/app.component.scss
Normal file
16
kdb-web/src/app/app.component.spec.ts
Normal file
16
kdb-web/src/app/app.component.spec.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
|
||||||
|
describe('AppComponent', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
RouterTestingModule
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
AppComponent
|
||||||
|
],
|
||||||
|
}).compileComponents();
|
||||||
|
});
|
||||||
|
});
|
21
kdb-web/src/app/app.component.ts
Normal file
21
kdb-web/src/app/app.component.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { AuthService } from './services/auth/auth.service';
|
||||||
|
import { ThemeService } from './services/theme/theme.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss']
|
||||||
|
})
|
||||||
|
export class AppComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public authService: AuthService,
|
||||||
|
public themeService: ThemeService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.themeService.loadTheme();
|
||||||
|
this.themeService.loadMenu();
|
||||||
|
}
|
||||||
|
}
|
81
kdb-web/src/app/app.module.ts
Normal file
81
kdb-web/src/app/app.module.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { APP_INITIALIZER, ErrorHandler, NgModule } from '@angular/core';
|
||||||
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { JwtModule } from '@auth0/angular-jwt';
|
||||||
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||||
|
import { ConfirmationService, MessageService } from 'primeng/api';
|
||||||
|
import { DialogService } from 'primeng/dynamicdialog';
|
||||||
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
import { FooterComponent } from './components/footer/footer.component';
|
||||||
|
import { HeaderComponent } from './components/header/header.component';
|
||||||
|
import { SidebarComponent } from './components/sidebar/sidebar.component';
|
||||||
|
import { SpinnerComponent } from './components/spinner/spinner.component';
|
||||||
|
import { SharedModule } from './modules/shared/shared.module';
|
||||||
|
import { ErrorHandlerService } from './services/error-handler/error-handler.service';
|
||||||
|
import { SettingsService } from './services/settings/settings.service';
|
||||||
|
import { NotFoundComponent } from './components/error/not-found/not-found.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AppComponent,
|
||||||
|
HeaderComponent,
|
||||||
|
SidebarComponent,
|
||||||
|
FooterComponent,
|
||||||
|
SpinnerComponent,
|
||||||
|
NotFoundComponent,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
BrowserModule,
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
AppRoutingModule,
|
||||||
|
SharedModule,
|
||||||
|
JwtModule.forRoot({
|
||||||
|
config: {
|
||||||
|
tokenGetter,
|
||||||
|
allowedDomains: ['localhost:5000', 'localhost:5001'],
|
||||||
|
disallowedRoutes: []
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useFactory: HttpLoaderFactory,
|
||||||
|
deps: [HttpClient]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: APP_INITIALIZER,
|
||||||
|
useFactory: configurationFactory,
|
||||||
|
deps: [SettingsService],
|
||||||
|
multi: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ErrorHandler,
|
||||||
|
useClass: ErrorHandlerService
|
||||||
|
},
|
||||||
|
MessageService,
|
||||||
|
ConfirmationService,
|
||||||
|
DialogService
|
||||||
|
],
|
||||||
|
bootstrap: [AppComponent]
|
||||||
|
})
|
||||||
|
export class AppModule { }
|
||||||
|
|
||||||
|
export function configurationFactory(settingsService: SettingsService): () => Promise<unknown> {
|
||||||
|
return (): Promise<unknown> => {
|
||||||
|
return settingsService.loadSettings();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tokenGetter(): string {
|
||||||
|
return localStorage.getItem('jwt') ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
|
||||||
|
return new TranslateHttpLoader(http);
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
<div class="content-row">
|
||||||
|
<div class="content-column">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<div class="content-header">
|
||||||
|
<h2>
|
||||||
|
{{'common.error' | translate}}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
{{'common.404' | translate}}
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { NotFoundComponent } from './not-found.component';
|
||||||
|
|
||||||
|
describe('NotFoundComponent', () => {
|
||||||
|
let component: NotFoundComponent;
|
||||||
|
let fixture: ComponentFixture<NotFoundComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ NotFoundComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(NotFoundComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,18 @@
|
|||||||
|
import { Location } from '@angular/common';
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-not-found',
|
||||||
|
templateUrl: './not-found.component.html',
|
||||||
|
styleUrls: ['./not-found.component.scss']
|
||||||
|
})
|
||||||
|
export class NotFoundComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public location: Location
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
26
kdb-web/src/app/components/footer/footer.component.html
Normal file
26
kdb-web/src/app/components/footer/footer.component.html
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<footer>
|
||||||
|
<div class="left">
|
||||||
|
<div class="frontend-version">
|
||||||
|
<span>
|
||||||
|
{{'footer.frontend' | translate}}:
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{{frontendVersion.getVersionString()}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span class="version-divider">
|
||||||
|
|
|
||||||
|
</span>
|
||||||
|
<div class="backend-version">
|
||||||
|
<span>
|
||||||
|
{{'footer.backend' | translate}}:
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{{backendVersion.getVersionString()}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<a href="https://www.sh-edraft.de/Impressum" target="_blank">{{'footer.imprint' | translate}}</a>
|
||||||
|
</div>
|
||||||
|
</footer>
|
25
kdb-web/src/app/components/footer/footer.component.spec.ts
Normal file
25
kdb-web/src/app/components/footer/footer.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { FooterComponent } from './footer.component';
|
||||||
|
|
||||||
|
describe('FooterComponent', () => {
|
||||||
|
let component: FooterComponent;
|
||||||
|
let fixture: ComponentFixture<FooterComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ FooterComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(FooterComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
46
kdb-web/src/app/components/footer/footer.component.ts
Normal file
46
kdb-web/src/app/components/footer/footer.component.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { type } from 'os';
|
||||||
|
import { catchError } from 'rxjs/operators';
|
||||||
|
import { SoftwareVersion } from 'src/app/models/config/software-version';
|
||||||
|
import { GuiService } from 'src/app/services/gui/gui.service';
|
||||||
|
import { SettingsService } from 'src/app/services/settings/settings.service';
|
||||||
|
import { SpinnerService } from 'src/app/services/spinner/spinner.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-footer',
|
||||||
|
templateUrl: './footer.component.html',
|
||||||
|
styleUrls: ['./footer.component.scss']
|
||||||
|
})
|
||||||
|
export class FooterComponent implements OnInit {
|
||||||
|
|
||||||
|
|
||||||
|
frontendVersion!: SoftwareVersion;
|
||||||
|
backendVersion!: SoftwareVersion;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private settings: SettingsService,
|
||||||
|
private guiService: GuiService,
|
||||||
|
private spinnerService: SpinnerService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.frontendVersion = this.settings.getWebVersion() ?? new SoftwareVersion('0', '0', '0');
|
||||||
|
|
||||||
|
this.spinnerService.showSpinner();
|
||||||
|
this.guiService.getApiVersion()
|
||||||
|
.pipe(catchError(err => {
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
throw err;
|
||||||
|
}))
|
||||||
|
.subscribe(version => {
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
const webVersion = new SoftwareVersion(
|
||||||
|
version.major,
|
||||||
|
version.minor,
|
||||||
|
version.micro
|
||||||
|
);
|
||||||
|
this.backendVersion = webVersion;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
25
kdb-web/src/app/components/header/header.component.html
Normal file
25
kdb-web/src/app/components/header/header.component.html
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<header>
|
||||||
|
<div class="logo-button-wrapper">
|
||||||
|
<button pButton type="button" icon="pi pi-bars" class="btn p-button-text" (click)="toggleMenu()"></button>
|
||||||
|
</div>
|
||||||
|
<div class="logo">
|
||||||
|
<h1 class="app-name">{{'header.header' | translate}}</h1>
|
||||||
|
</div>
|
||||||
|
<div class="header-menu logo-button-wrapper">
|
||||||
|
<div class="logo-button-wrapper">
|
||||||
|
<button type="button" pButton icon="pi pi-globe" class="btn icon-btn p-button-text"
|
||||||
|
(click)="langMenu.toggle($event)"></button>
|
||||||
|
<p-menu #langMenu [popup]="true" [model]="langList" class="lang-menu"></p-menu>
|
||||||
|
</div>
|
||||||
|
<div class="logo-button-wrapper">
|
||||||
|
<button type="button" pButton icon="pi pi-palette" class="btn icon-btn p-button-text"
|
||||||
|
(click)="themeMenu.toggle($event)"></button>
|
||||||
|
<p-menu #themeMenu [popup]="true" [model]="themeList" class="theme-menu"></p-menu>
|
||||||
|
</div>
|
||||||
|
<div class="logo-button-wrapper">
|
||||||
|
<button type="button" pButton icon="pi pi-user" class="btn icon-btn p-button-text"
|
||||||
|
(click)="userMenu.toggle($event)"></button>
|
||||||
|
<p-menu #userMenu [popup]="true" [model]="userMenuList" class="user-menu"></p-menu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
25
kdb-web/src/app/components/header/header.component.spec.ts
Normal file
25
kdb-web/src/app/components/header/header.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { HeaderComponent } from './header.component';
|
||||||
|
|
||||||
|
describe('HeaderComponent', () => {
|
||||||
|
let component: HeaderComponent;
|
||||||
|
let fixture: ComponentFixture<HeaderComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ HeaderComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(HeaderComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
168
kdb-web/src/app/components/header/header.component.ts
Normal file
168
kdb-web/src/app/components/header/header.component.ts
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { LangChangeEvent, TranslateService } from '@ngx-translate/core';
|
||||||
|
import { MenuItem, PrimeNGConfig } from 'primeng/api';
|
||||||
|
import { catchError } from 'rxjs/operators';
|
||||||
|
import { AuthService } from 'src/app/services/auth/auth.service';
|
||||||
|
import { SettingsService } from 'src/app/services/settings/settings.service';
|
||||||
|
import { SpinnerService } from 'src/app/services/spinner/spinner.service';
|
||||||
|
import { ThemeService } from 'src/app/services/theme/theme.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-header',
|
||||||
|
templateUrl: './header.component.html',
|
||||||
|
styleUrls: ['./header.component.scss']
|
||||||
|
})
|
||||||
|
export class HeaderComponent implements OnInit {
|
||||||
|
@Output() isSidebarFullWidth: EventEmitter<boolean> = new EventEmitter<boolean>(this.themeService.isSidebarOpen);
|
||||||
|
|
||||||
|
langList: MenuItem[] = [];
|
||||||
|
themeList: MenuItem[] = [];
|
||||||
|
userMenuList!: MenuItem[];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private authService: AuthService,
|
||||||
|
private router: Router,
|
||||||
|
private themeService: ThemeService,
|
||||||
|
private spinnerService: SpinnerService,
|
||||||
|
private settings: SettingsService,
|
||||||
|
private translateService: TranslateService,
|
||||||
|
private config: PrimeNGConfig
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.translateService.setDefaultLang('en');
|
||||||
|
this.initMenuLists();
|
||||||
|
this.loadLang();
|
||||||
|
this.translateService.onLangChange.subscribe((event: LangChangeEvent) => {
|
||||||
|
this.initUserMenuList();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initUserMenuList(): void {
|
||||||
|
this.spinnerService.showSpinner();
|
||||||
|
const mail = this.authService.getEMailFromDecodedToken(this.authService.getDecodedToken());
|
||||||
|
this.authService.getUserByEMail(mail ?? '')
|
||||||
|
.pipe(catchError(err => {
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
this.authService.logout();
|
||||||
|
throw err;
|
||||||
|
}))
|
||||||
|
.subscribe(user => {
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
|
||||||
|
|
||||||
|
this.userMenuList = [
|
||||||
|
{
|
||||||
|
label: `${user.firstName} ${user.lastName}`,
|
||||||
|
disabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
separator: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: this.translateService.instant('header.change_password'), command: () => {
|
||||||
|
this.changePassword();
|
||||||
|
},
|
||||||
|
icon: 'pi pi-key'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: this.translateService.instant('header.settings'), command: () => {
|
||||||
|
this.userSettings();
|
||||||
|
},
|
||||||
|
icon: 'pi pi-cog'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: this.translateService.instant('header.logout'), command: () => {
|
||||||
|
this.logout();
|
||||||
|
},
|
||||||
|
icon: 'pi pi-sign-out'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initMenuLists(): void {
|
||||||
|
this.langList = [
|
||||||
|
{
|
||||||
|
label: 'English', command: () => {
|
||||||
|
this.translate('en');
|
||||||
|
this.setLang('en');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Deutsch', command: () => {
|
||||||
|
this.translate('de');
|
||||||
|
this.setLang('de');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
this.initUserMenuList();
|
||||||
|
|
||||||
|
this.settings.getThemes()?.forEach(theme => {
|
||||||
|
this.themeList.push({
|
||||||
|
label: theme.Label,
|
||||||
|
command: () => {
|
||||||
|
this.changeTheme(theme.Name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleMenu(): void {
|
||||||
|
this.themeService.setIsMenuOpen(!this.themeService.isSidebarOpen);
|
||||||
|
this.isSidebarFullWidth.emit(this.themeService.isSidebarOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
changeTheme(name: string): void {
|
||||||
|
this.themeService.setTheme(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
changePassword(): void {
|
||||||
|
this.router.navigate(['/change-password']);
|
||||||
|
}
|
||||||
|
|
||||||
|
userSettings(): void {
|
||||||
|
this.router.navigate(['/user-settings']);
|
||||||
|
}
|
||||||
|
|
||||||
|
logout(): void {
|
||||||
|
this.authService.logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
translate(lang: string) {
|
||||||
|
this.translateService.use(lang);
|
||||||
|
this.translateService.get('primeng').subscribe(res => this.config.setTranslation(res));
|
||||||
|
}
|
||||||
|
|
||||||
|
loadLang(): void {
|
||||||
|
const token = this.authService.getDecodedToken();
|
||||||
|
const mail = this.authService.getEMailFromDecodedToken(token);
|
||||||
|
|
||||||
|
if (!mail) {
|
||||||
|
this.translate('en');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let lang = localStorage.getItem(`${mail}_lang`);
|
||||||
|
if (!lang) {
|
||||||
|
lang = 'en';
|
||||||
|
this.setLang(lang);
|
||||||
|
}
|
||||||
|
this.translate(lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
setLang(lang: string): void {
|
||||||
|
this.authService.isUserLoggedInAsync().then(result => {
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = this.authService.getDecodedToken();
|
||||||
|
const mail = this.authService.getEMailFromDecodedToken(token);
|
||||||
|
localStorage.setItem(`${mail}_lang`, lang);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
<div class="menu">
|
||||||
|
<p-menu [model]="menuItems"></p-menu>
|
||||||
|
</div>
|
25
kdb-web/src/app/components/sidebar/sidebar.component.spec.ts
Normal file
25
kdb-web/src/app/components/sidebar/sidebar.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { SidebarComponent } from './sidebar.component';
|
||||||
|
|
||||||
|
describe('SidebarComponent', () => {
|
||||||
|
let component: SidebarComponent;
|
||||||
|
let fixture: ComponentFixture<SidebarComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ SidebarComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(SidebarComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
52
kdb-web/src/app/components/sidebar/sidebar.component.ts
Normal file
52
kdb-web/src/app/components/sidebar/sidebar.component.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
|
||||||
|
import { LangChangeEvent, TranslateService } from '@ngx-translate/core';
|
||||||
|
import { MenuItem } from 'primeng/api';
|
||||||
|
import { AuthRoles } from 'src/app/models/auth/auth-roles.enum';
|
||||||
|
import { AuthService } from 'src/app/services/auth/auth.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-sidebar',
|
||||||
|
templateUrl: './sidebar.component.html',
|
||||||
|
styleUrls: ['./sidebar.component.scss']
|
||||||
|
})
|
||||||
|
export class SidebarComponent implements OnInit, OnChanges {
|
||||||
|
|
||||||
|
@Input() isSidebarOpen!: boolean;
|
||||||
|
|
||||||
|
menuItems!: MenuItem[];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private authService: AuthService,
|
||||||
|
private translateService: TranslateService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.translateService.onLangChange.subscribe(async (event: LangChangeEvent) => {
|
||||||
|
await this.setMenu(this.isSidebarOpen);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async setMenu(isSidebarOpen: boolean) {
|
||||||
|
this.menuItems = [];
|
||||||
|
this.menuItems = [
|
||||||
|
{ label: isSidebarOpen ? this.translateService.instant('sidebar.dashboard') : '', icon: 'pi pi-th-large', routerLink: 'dashboard' },
|
||||||
|
];
|
||||||
|
|
||||||
|
if (await this.authService.hasUserPermission(AuthRoles.Admin)) {
|
||||||
|
this.menuItems.push(
|
||||||
|
{ separator: true },
|
||||||
|
{ label: isSidebarOpen ? this.translateService.instant('sidebar.config') : '', icon: 'pi pi-cog', routerLink: '/admin/settings' },
|
||||||
|
{ label: isSidebarOpen ? this.translateService.instant('sidebar.auth_user_list') : '', icon: 'pi pi-user-edit', routerLink: '/admin/users' },
|
||||||
|
);
|
||||||
|
this.menuItems = this.menuItems.slice();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async ngOnChanges(changes: SimpleChanges): Promise<void> {
|
||||||
|
if (!changes)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await this.setMenu(changes['isSidebarOpen'].currentValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
<ng-container *ngIf="spinnerService.showSpinnerState">
|
||||||
|
<div class="spinner-component-wrapper">
|
||||||
|
<div class="spinner-wrapper">
|
||||||
|
<p-progressSpinner styleClass="custom-spinner" animationDuration=".8s"></p-progressSpinner>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
25
kdb-web/src/app/components/spinner/spinner.component.spec.ts
Normal file
25
kdb-web/src/app/components/spinner/spinner.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { SpinnerComponent } from './spinner.component';
|
||||||
|
|
||||||
|
describe('SpinnerComponent', () => {
|
||||||
|
let component: SpinnerComponent;
|
||||||
|
let fixture: ComponentFixture<SpinnerComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ SpinnerComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(SpinnerComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
18
kdb-web/src/app/components/spinner/spinner.component.ts
Normal file
18
kdb-web/src/app/components/spinner/spinner.component.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { SpinnerService } from 'src/app/services/spinner/spinner.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-spinner',
|
||||||
|
templateUrl: './spinner.component.html',
|
||||||
|
styleUrls: ['./spinner.component.scss']
|
||||||
|
})
|
||||||
|
export class SpinnerComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public spinnerService: SpinnerService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
7
kdb-web/src/app/models/auth/admin-update-user.dto.ts
Normal file
7
kdb-web/src/app/models/auth/admin-update-user.dto.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { AuthUserDTO } from "./auth-user.dto";
|
||||||
|
|
||||||
|
export interface AdminUpdateUserDTO {
|
||||||
|
authUserDTO: AuthUserDTO;
|
||||||
|
newAuthUserDTO: AuthUserDTO;
|
||||||
|
changePassword: boolean;
|
||||||
|
}
|
7
kdb-web/src/app/models/auth/auth-error-messages.enum.ts
Normal file
7
kdb-web/src/app/models/auth/auth-error-messages.enum.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export enum AuthErrorMessages {
|
||||||
|
UserIsEmpty = "User is empty",
|
||||||
|
UserNotFound = "User not found",
|
||||||
|
WrongPassword = "Wrong password",
|
||||||
|
UserAlreadyExists = "User already exists",
|
||||||
|
EMailNotConfirmed = "E-Mail not confirmed"
|
||||||
|
}
|
4
kdb-web/src/app/models/auth/auth-roles.enum.ts
Normal file
4
kdb-web/src/app/models/auth/auth-roles.enum.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export enum AuthRoles {
|
||||||
|
Normal = 0,
|
||||||
|
Admin = 1
|
||||||
|
}
|
12
kdb-web/src/app/models/auth/auth-user-atr-errors.ts
Normal file
12
kdb-web/src/app/models/auth/auth-user-atr-errors.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export class AuthUserAtrErrors {
|
||||||
|
firstName: AuthUserAtrErrorType = new AuthUserAtrErrorType();
|
||||||
|
lastName: AuthUserAtrErrorType = new AuthUserAtrErrorType();
|
||||||
|
email: AuthUserAtrErrorType = new AuthUserAtrErrorType();
|
||||||
|
password: AuthUserAtrErrorType = new AuthUserAtrErrorType();
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AuthUserAtrErrorType {
|
||||||
|
wrongData: boolean = false;
|
||||||
|
required: boolean = false;
|
||||||
|
notConfirmed: boolean = false;
|
||||||
|
}
|
11
kdb-web/src/app/models/auth/auth-user.dto.ts
Normal file
11
kdb-web/src/app/models/auth/auth-user.dto.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { AuthRoles } from "./auth-roles.enum";
|
||||||
|
|
||||||
|
export interface AuthUserDTO {
|
||||||
|
id?: number;
|
||||||
|
firstName: string | null;
|
||||||
|
lastName: string | null;
|
||||||
|
email: string | null;
|
||||||
|
password: string | null;
|
||||||
|
isConfirmed?: boolean
|
||||||
|
authRole?: AuthRoles;
|
||||||
|
}
|
3
kdb-web/src/app/models/auth/email-string.dto.ts
Normal file
3
kdb-web/src/app/models/auth/email-string.dto.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export interface EMailStringDTO {
|
||||||
|
email: string;
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
export enum RegisterErrorMessages {
|
||||||
|
InvalidEMail = "Invalid E-Mail",
|
||||||
|
UserAlreadyExists = "User already exists",
|
||||||
|
}
|
6
kdb-web/src/app/models/auth/reset-password.dto.ts
Normal file
6
kdb-web/src/app/models/auth/reset-password.dto.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { AuthUserDTO } from "./auth-user.dto";
|
||||||
|
|
||||||
|
export interface ResetPasswordDTO {
|
||||||
|
id: string;
|
||||||
|
password: string;
|
||||||
|
}
|
4
kdb-web/src/app/models/auth/token.dto.ts
Normal file
4
kdb-web/src/app/models/auth/token.dto.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export interface TokenDTO {
|
||||||
|
token: string;
|
||||||
|
refreshToken: string;
|
||||||
|
}
|
6
kdb-web/src/app/models/auth/update-user.dto.ts
Normal file
6
kdb-web/src/app/models/auth/update-user.dto.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { AuthUserDTO } from "./auth-user.dto";
|
||||||
|
|
||||||
|
export interface UpdateUserDTO {
|
||||||
|
authUserDTO: AuthUserDTO;
|
||||||
|
newAuthUserDTO: AuthUserDTO;
|
||||||
|
}
|
5
kdb-web/src/app/models/config/api-version.ts
Normal file
5
kdb-web/src/app/models/config/api-version.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export interface ApiVersion {
|
||||||
|
Major: string;
|
||||||
|
Minor: string;
|
||||||
|
Micro: string;
|
||||||
|
}
|
8
kdb-web/src/app/models/config/appsettings.ts
Normal file
8
kdb-web/src/app/models/config/appsettings.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { SoftwareVersion } from "./software-version";
|
||||||
|
import { Theme } from '../view/theme';
|
||||||
|
|
||||||
|
export interface Appsettings {
|
||||||
|
ApiURL: string;
|
||||||
|
WebVersion: SoftwareVersion;
|
||||||
|
Themes: Theme[];
|
||||||
|
}
|
16
kdb-web/src/app/models/config/settings.dto.ts
Normal file
16
kdb-web/src/app/models/config/settings.dto.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export interface SettingsDTO {
|
||||||
|
webVersion: string;
|
||||||
|
apiVersion: string;
|
||||||
|
configPath: string;
|
||||||
|
webBaseURL: string;
|
||||||
|
apiBaseURL: string;
|
||||||
|
|
||||||
|
tokenExpireTime: number;
|
||||||
|
refreshTokenExpireTime: number;
|
||||||
|
|
||||||
|
mailUser: string;
|
||||||
|
mailPort: number;
|
||||||
|
mailHost: string;
|
||||||
|
mailTransceiver: string;
|
||||||
|
mailTransceiverAddress: string;
|
||||||
|
}
|
5
kdb-web/src/app/models/config/software-version.dto.ts
Normal file
5
kdb-web/src/app/models/config/software-version.dto.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export interface SoftwareVersionDTO {
|
||||||
|
major: string;
|
||||||
|
minor: string;
|
||||||
|
micro: string
|
||||||
|
}
|
19
kdb-web/src/app/models/config/software-version.ts
Normal file
19
kdb-web/src/app/models/config/software-version.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
export class SoftwareVersion {
|
||||||
|
Major: string;
|
||||||
|
Minor: string;
|
||||||
|
Micro: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
major: string,
|
||||||
|
minor: string,
|
||||||
|
micro: string
|
||||||
|
) {
|
||||||
|
this.Major = major;
|
||||||
|
this.Minor = minor;
|
||||||
|
this.Micro = micro;
|
||||||
|
}
|
||||||
|
|
||||||
|
getVersionString(): string {
|
||||||
|
return `${this.Major}.${this.Minor}.${this.Micro}`;
|
||||||
|
}
|
||||||
|
}
|
8
kdb-web/src/app/models/error/error-dto.ts
Normal file
8
kdb-web/src/app/models/error/error-dto.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { ServiceErrorCode } from "./service-error-code.enum";
|
||||||
|
|
||||||
|
|
||||||
|
export class ErrorDTO {
|
||||||
|
errorCode!: ServiceErrorCode;
|
||||||
|
message!: string;
|
||||||
|
|
||||||
|
}
|
16
kdb-web/src/app/models/error/service-error-code.enum.ts
Normal file
16
kdb-web/src/app/models/error/service-error-code.enum.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export enum ServiceErrorCode {
|
||||||
|
Unknown = 0,
|
||||||
|
|
||||||
|
InvalidDependencies = 1,
|
||||||
|
InvalidData = 2,
|
||||||
|
NotFound = 3,
|
||||||
|
DataAlreadyExists = 4,
|
||||||
|
UnableToAdd = 5,
|
||||||
|
UnableToDelete = 6,
|
||||||
|
|
||||||
|
InvalidUser = 7,
|
||||||
|
|
||||||
|
ConnectionFailed = 8,
|
||||||
|
Timeout = 9,
|
||||||
|
MailError = 10
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
import { SelectCriterion } from "../select-criterion.model";
|
||||||
|
|
||||||
|
export interface AuthUserSelectCriterion extends SelectCriterion {
|
||||||
|
firstName: string | null;
|
||||||
|
lastName: string | null;
|
||||||
|
email: string | null;
|
||||||
|
authRole?: number | null;
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
import { AuthUserDTO } from "../../auth/auth-user.dto";
|
||||||
|
|
||||||
|
export interface GetFilteredAuthUsersResultDTO {
|
||||||
|
users: AuthUserDTO[];
|
||||||
|
totalCount: number;
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
import { SelectCriterion } from "../select-criterion.model";
|
||||||
|
|
||||||
|
export interface LoginSelectCriterion extends SelectCriterion {
|
||||||
|
timeFrom: string;
|
||||||
|
timeTo: string;
|
||||||
|
userName: string;
|
||||||
|
hostName: string;
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
export interface SelectCriterion {
|
||||||
|
pageIndex: number;
|
||||||
|
pageSize: number;
|
||||||
|
sortDirection: string | null;
|
||||||
|
sortColumn: string | null;
|
||||||
|
}
|
7
kdb-web/src/app/models/utils/confirmation-dialog.ts
Normal file
7
kdb-web/src/app/models/utils/confirmation-dialog.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export interface ConfirmationDialog {
|
||||||
|
key?: string;
|
||||||
|
header: string;
|
||||||
|
message: string;
|
||||||
|
accept?: () => void;
|
||||||
|
reject?: () => void;
|
||||||
|
}
|
5
kdb-web/src/app/models/utils/toast-options.ts
Normal file
5
kdb-web/src/app/models/utils/toast-options.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export interface ToastOptions {
|
||||||
|
life?: number;
|
||||||
|
sticky?: boolean;
|
||||||
|
closable?: boolean;
|
||||||
|
}
|
4
kdb-web/src/app/models/view/theme.ts
Normal file
4
kdb-web/src/app/models/view/theme.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export interface Theme {
|
||||||
|
Label: string;
|
||||||
|
Name: string;
|
||||||
|
}
|
7
kdb-web/src/app/models/view/themes.enum.ts
Normal file
7
kdb-web/src/app/models/view/themes.enum.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export enum Themes {
|
||||||
|
Default = "sh-edraft-dark-theme",
|
||||||
|
DefaultLight = "default-light-theme",
|
||||||
|
DefaultDark = "default-dark-theme",
|
||||||
|
ShEdraftLight = "sh-edraft-light-theme",
|
||||||
|
ShEdraftDark = "sh-edraft-dark-theme",
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { AuthUserComponent } from './components/auth-user/auth-user.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{path: '', component: AuthUserComponent}
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule]
|
||||||
|
})
|
||||||
|
export class AuthUserRoutingModule { }
|
18
kdb-web/src/app/modules/admin/auth-users/auth-user.module.ts
Normal file
18
kdb-web/src/app/modules/admin/auth-users/auth-user.module.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { AuthUserRoutingModule } from './auth-user-routing.module';
|
||||||
|
import { AuthUserComponent } from './components/auth-user/auth-user.component';
|
||||||
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AuthUserComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
AuthUserRoutingModule,
|
||||||
|
SharedModule
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AuthUserModule { }
|
@ -0,0 +1,201 @@
|
|||||||
|
<h1>
|
||||||
|
{{'admin.auth_users.header' | translate}}
|
||||||
|
</h1>
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<div class="content">
|
||||||
|
<p-table #dt [value]="users" dataKey="id" editMode="row" [rowHover]="true" [rows]="10"
|
||||||
|
[rowsPerPageOptions]="[10,25,50]" [paginator]="true" [loading]="loading" [totalRecords]="totalRecords"
|
||||||
|
[lazy]="true" (onLazyLoad)="nextPage($event)">
|
||||||
|
|
||||||
|
<ng-template pTemplate="caption">
|
||||||
|
<div class="table-caption">
|
||||||
|
<div class="table-caption-text">
|
||||||
|
<ng-container *ngIf="!loading">{{users.length}} {{'admin.auth_users.of' | translate}}
|
||||||
|
{{dt.totalRecords}}
|
||||||
|
</ng-container>
|
||||||
|
{{'admin.auth_users.users' | translate}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-caption-btn-wrapper btn-wrapper">
|
||||||
|
<button pButton label="{{'admin.auth_users.add' | translate}}" class="icon-btn btn"
|
||||||
|
icon="pi pi-user-plus" (click)="addUser(dt)" [disabled]="isEditingNew">
|
||||||
|
</button>
|
||||||
|
<button pButton label="{{'admin.auth_users.reset_filters' | translate}}" icon="pi pi-undo"
|
||||||
|
class="icon-btn btn" (click)="resetFilters(dt)">
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template pTemplate="header">
|
||||||
|
<tr>
|
||||||
|
<th pSortableColumn="firstName">
|
||||||
|
<div class="table-header-label">
|
||||||
|
<div class="table-header-text">{{'admin.auth_users.headers.first_name' | translate}}</div>
|
||||||
|
<p-sortIcon icon="sort" field="firstName" class="table-header-icon"></p-sortIcon>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
|
||||||
|
<th pSortableColumn="lastName">
|
||||||
|
<div class="table-header-label">
|
||||||
|
<div class="table-header-text">{{'admin.auth_users.headers.last_name' | translate}}</div>
|
||||||
|
<p-sortIcon field="lastName" class="table-header-icon"></p-sortIcon>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
|
||||||
|
<th pSortableColumn="eMail">
|
||||||
|
<div class="table-header-label">
|
||||||
|
<div class="table-header-text">{{'admin.auth_users.headers.e_mail' | translate}}</div>
|
||||||
|
<p-sortIcon field="eMail" class="table-header-icon"></p-sortIcon>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
|
||||||
|
<th class="table-header-actions" pSortableColumn="confirmationId">
|
||||||
|
<div class="table-header-label">
|
||||||
|
<div class="table-header-text">{{'admin.auth_users.headers.active' | translate}}</div>
|
||||||
|
<p-sortIcon field="confirmationId" class="table-header-icon"></p-sortIcon>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
|
||||||
|
<th class="table-header-small-dropdown" pSortableColumn="authRole">
|
||||||
|
<div class="table-header-label">
|
||||||
|
<div class="table-header-text">{{'admin.auth_users.headers.role' | translate}}</div>
|
||||||
|
<p-sortIcon field="authRole" class="table-header-icon"></p-sortIcon>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
|
||||||
|
<th>
|
||||||
|
<div class="table-header-label">
|
||||||
|
<div class="table-header-text">{{'admin.auth_users.headers.password' | translate}}</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
|
||||||
|
<th class="table-header-actions">
|
||||||
|
<div class="table-header-label">
|
||||||
|
<div class="table-header-text">{{'admin.auth_users.headers.actions' | translate}}</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<form [formGroup]="filterForm">
|
||||||
|
<input type="text" pInputText formControlName="firstName" placeholder="{{'admin.auth_users.headers.first_name' | translate}}">
|
||||||
|
</form>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<form [formGroup]="filterForm">
|
||||||
|
<input type="text" pInputText formControlName="lastName" placeholder="{{'admin.auth_users.headers.last_name' | translate}}">
|
||||||
|
</form>
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
<form [formGroup]="filterForm">
|
||||||
|
<input type="email" pInputText formControlName="email" placeholder="{{'admin.auth_users.headers.e_mail' | translate}}">
|
||||||
|
</form>
|
||||||
|
</th>
|
||||||
|
<th></th>
|
||||||
|
<th>
|
||||||
|
<form [formGroup]="filterForm">
|
||||||
|
<p-dropdown formControlName="authRole" [options]="authRoles"></p-dropdown>
|
||||||
|
</form>
|
||||||
|
</th>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template pTemplate="body" let-user let-editing="editing" let-ri="rowIndex">
|
||||||
|
<tr [pEditableRow]="user">
|
||||||
|
<td>
|
||||||
|
<p-cellEditor>
|
||||||
|
<ng-template pTemplate="input">
|
||||||
|
<input class="table-edit-input" pInputText type="text" [(ngModel)]="user.firstName"
|
||||||
|
[ngClass]="{ 'invalid-feedback-input': isFirstNameInvalid}">
|
||||||
|
</ng-template>
|
||||||
|
<ng-template pTemplate="output">
|
||||||
|
{{user.firstName}}
|
||||||
|
</ng-template>
|
||||||
|
</p-cellEditor>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p-cellEditor>
|
||||||
|
<ng-template pTemplate="input">
|
||||||
|
<input class="table-edit-input" pInputText type="text" [(ngModel)]="user.lastName"
|
||||||
|
[ngClass]="{ 'invalid-feedback-input': isLastNameInvalid}">
|
||||||
|
</ng-template>
|
||||||
|
<ng-template pTemplate="output">
|
||||||
|
{{user.lastName}}
|
||||||
|
</ng-template>
|
||||||
|
</p-cellEditor>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p-cellEditor>
|
||||||
|
<ng-template pTemplate="input">
|
||||||
|
<input class="table-edit-input" pInputText type="email" [(ngModel)]="user.email"
|
||||||
|
[ngClass]="{ 'invalid-feedback-input': isEMailInvalid}">
|
||||||
|
</ng-template>
|
||||||
|
<ng-template pTemplate="output">
|
||||||
|
{{user.email}}
|
||||||
|
</ng-template>
|
||||||
|
</p-cellEditor>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p-cellEditor>
|
||||||
|
<ng-template pTemplate="input">
|
||||||
|
<p-checkbox [binary]="true" [(ngModel)]="user.isConfirmed"
|
||||||
|
[ngClass]="{ 'invalid-feedback-input': isEMailInvalid}" [disabled]="isEditingNew">
|
||||||
|
</p-checkbox>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template pTemplate="output">
|
||||||
|
<p-checkbox [binary]="true" [ngModel]="user.isConfirmed" [disabled]="true">
|
||||||
|
</p-checkbox>
|
||||||
|
</ng-template>
|
||||||
|
</p-cellEditor>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p-cellEditor>
|
||||||
|
<ng-template pTemplate="input">
|
||||||
|
<p-dropdown [options]="authRoles" [(ngModel)]="user.authRole"
|
||||||
|
[disabled]="user.email == loggedInUserEMail"></p-dropdown>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template pTemplate="output">
|
||||||
|
{{user.authRole | authRole}}
|
||||||
|
</ng-template>
|
||||||
|
</p-cellEditor>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p-cellEditor>
|
||||||
|
<ng-template pTemplate="input">
|
||||||
|
<input class="table-edit-input" pInputText type="password" [(ngModel)]="user.password"
|
||||||
|
[ngClass]="{ 'invalid-feedback-input': isPasswordInvalid}">
|
||||||
|
</ng-template>
|
||||||
|
<ng-template pTemplate="output">
|
||||||
|
</ng-template>
|
||||||
|
</p-cellEditor>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="btn-wrapper">
|
||||||
|
<button *ngIf="!editing" pButton pInitEditableRow class="btn icon-btn" icon="pi pi-pencil"
|
||||||
|
(click)="onRowEditInit(dt, user, ri)"></button>
|
||||||
|
<button *ngIf="!editing" pButton class="btn icon-btn danger-icon-btn" icon="pi pi-trash"
|
||||||
|
(click)="deleteUser(user)"></button>
|
||||||
|
|
||||||
|
<button *ngIf="editing" pButton pSaveEditableRow class="btn icon-btn"
|
||||||
|
icon="pi pi-check-circle" (click)="onRowEditSave(dt, user, ri)"></button>
|
||||||
|
<button *ngIf="editing" pButton pCancelEditableRow class="btn icon-btn danger-icon-btn"
|
||||||
|
icon="pi pi-times-circle" (click)="onRowEditCancel(user, ri)"></button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template pTemplate="Invalidmessage">
|
||||||
|
<tr>
|
||||||
|
<td colspan="7">{{'admin.auth_users.no_entries_found' | translate}}</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template pTemplate="paginatorleft">
|
||||||
|
</ng-template>
|
||||||
|
</p-table>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AuthUserComponent } from './auth-user.component';
|
||||||
|
|
||||||
|
describe('AuthUserComponent', () => {
|
||||||
|
let component: AuthUserComponent;
|
||||||
|
let fixture: ComponentFixture<AuthUserComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ AuthUserComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(AuthUserComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,330 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { catchError, debounceTime, last } from 'rxjs/operators';
|
||||||
|
import { AuthRoles } from 'src/app/models/auth/auth-roles.enum';
|
||||||
|
import { AuthUserDTO } from 'src/app/models/auth/auth-user.dto';
|
||||||
|
import { AuthService } from 'src/app/services/auth/auth.service';
|
||||||
|
import { ConfirmationDialogService } from 'src/app/services/confirmation-dialog/confirmation-dialog.service';
|
||||||
|
import { SpinnerService } from 'src/app/services/spinner/spinner.service';
|
||||||
|
import { ToastService } from 'src/app/services/toast/toast.service';
|
||||||
|
import { Table } from 'primeng/table';
|
||||||
|
import { ServiceErrorCode } from 'src/app/models/error/service-error-code.enum';
|
||||||
|
import { RegisterErrorMessages } from 'src/app/models/auth/register-error-messages.enum';
|
||||||
|
import { ErrorDTO } from 'src/app/models/error/error-dto';
|
||||||
|
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
|
||||||
|
import { AuthUserSelectCriterion } from 'src/app/models/selection/auth-user/auth-user-select-criterion.dto';
|
||||||
|
import { LazyLoadEvent } from 'primeng/api';
|
||||||
|
import { throwError } from 'rxjs';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-auth-user',
|
||||||
|
templateUrl: './auth-user.component.html',
|
||||||
|
styleUrls: ['./auth-user.component.scss']
|
||||||
|
})
|
||||||
|
export class AuthUserComponent implements OnInit {
|
||||||
|
|
||||||
|
users!: AuthUserDTO[];
|
||||||
|
statuses!: any[];
|
||||||
|
loading = true;
|
||||||
|
activityValues: number[] = [0, 100];
|
||||||
|
|
||||||
|
clonedUsers: { [s: string]: AuthUserDTO; } = {};
|
||||||
|
isEditingNew: boolean = false;
|
||||||
|
|
||||||
|
authRoles = [
|
||||||
|
{ label: AuthRoles[AuthRoles.Normal].toString(), value: AuthRoles.Normal },
|
||||||
|
{ label: AuthRoles[AuthRoles.Admin].toString(), value: AuthRoles.Admin }
|
||||||
|
]
|
||||||
|
|
||||||
|
newUserTemplate: AuthUserDTO = {
|
||||||
|
id: 0,
|
||||||
|
firstName: "",
|
||||||
|
lastName: "",
|
||||||
|
email: "",
|
||||||
|
password: "",
|
||||||
|
authRole: AuthRoles.Normal
|
||||||
|
};
|
||||||
|
|
||||||
|
isFirstNameInvalid: boolean = false;
|
||||||
|
isLastNameInvalid: boolean = false;
|
||||||
|
isEMailInvalid: boolean = false;
|
||||||
|
isPasswordInvalid: boolean = false;
|
||||||
|
|
||||||
|
loggedInUserEMail: string = "";
|
||||||
|
|
||||||
|
filterForm!: FormGroup<{
|
||||||
|
firstName: FormControl<string | null>,
|
||||||
|
lastName: FormControl<string | null>,
|
||||||
|
email: FormControl<string | null>,
|
||||||
|
authRole: FormControl<string | null>
|
||||||
|
}>;
|
||||||
|
searchCriterions!: AuthUserSelectCriterion;
|
||||||
|
totalRecords!: number;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private authService: AuthService,
|
||||||
|
private spinnerService: SpinnerService,
|
||||||
|
private toastService: ToastService,
|
||||||
|
private confirmDialog: ConfirmationDialogService,
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private translate: TranslateService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.loggedInUserEMail = this.authService.getEMailFromDecodedToken(this.authService.getDecodedToken()) ?? '';
|
||||||
|
this.searchCriterions = {
|
||||||
|
firstName: null,
|
||||||
|
lastName: null,
|
||||||
|
email: null,
|
||||||
|
authRole: null,
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: 10,
|
||||||
|
sortColumn: null,
|
||||||
|
sortDirection: null
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setFilterForm();
|
||||||
|
// this.loadNextPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
setFilterForm() {
|
||||||
|
this.filterForm = this.fb.group({
|
||||||
|
firstName: [''],
|
||||||
|
lastName: [''],
|
||||||
|
email: [''],
|
||||||
|
authRole: ['']
|
||||||
|
});
|
||||||
|
|
||||||
|
this.filterForm.valueChanges.pipe(
|
||||||
|
debounceTime(600)
|
||||||
|
).subscribe(changes => {
|
||||||
|
if (changes.firstName) {
|
||||||
|
this.searchCriterions.firstName = changes.firstName;
|
||||||
|
} else {
|
||||||
|
this.searchCriterions.firstName = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changes.lastName) {
|
||||||
|
this.searchCriterions.lastName = changes.lastName;
|
||||||
|
} else {
|
||||||
|
this.searchCriterions.lastName = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changes.email) {
|
||||||
|
this.searchCriterions.email = changes.email;
|
||||||
|
} else {
|
||||||
|
this.searchCriterions.email = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changes.authRole != null) {
|
||||||
|
this.searchCriterions.authRole = +changes.authRole;
|
||||||
|
} else {
|
||||||
|
this.searchCriterions.authRole = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.searchCriterions.pageSize)
|
||||||
|
this.searchCriterions.pageSize = 10;
|
||||||
|
|
||||||
|
if (this.searchCriterions.pageSize)
|
||||||
|
this.searchCriterions.pageIndex = 0;
|
||||||
|
|
||||||
|
this.loadNextPage();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadNextPage() {
|
||||||
|
this.authService.getFilteredUsers(this.searchCriterions).pipe(catchError(err => {
|
||||||
|
this.loading = false;
|
||||||
|
return throwError(err);
|
||||||
|
})).subscribe(list => {
|
||||||
|
this.totalRecords = list.totalCount;
|
||||||
|
this.users = list.users;
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPage(event: LazyLoadEvent) {
|
||||||
|
this.searchCriterions.pageSize = event.rows ?? 0;
|
||||||
|
if (event.first != null && event.rows != null)
|
||||||
|
this.searchCriterions.pageIndex = event.first / event.rows;
|
||||||
|
this.searchCriterions.sortColumn = event.sortField ?? null;
|
||||||
|
this.searchCriterions.sortDirection = event.sortOrder === 1 ? 'asc' : event.sortOrder === -1 ? 'desc' : 'asc';
|
||||||
|
|
||||||
|
if (event.filters) {
|
||||||
|
// + "" => convert to string
|
||||||
|
this.searchCriterions.firstName = event.filters['firstName'] ? event.filters['firstName'] + "" : null;
|
||||||
|
this.searchCriterions.lastName = event.filters['lastName'] ? event.filters['lastName'] + "" : null;
|
||||||
|
this.searchCriterions.email = event.filters['email'] ? event.filters['email'] + "" : null;
|
||||||
|
this.searchCriterions.authRole = event.filters['authRole'] ? +event.filters['authRole'] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadNextPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
resetFilters(table: Table) {
|
||||||
|
this.filterForm.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
initUserList(): void {
|
||||||
|
this.spinnerService.showSpinner();
|
||||||
|
this.authService.getAllUsers()
|
||||||
|
.pipe(catchError(err => {
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
throw err;
|
||||||
|
}))
|
||||||
|
.subscribe(users => {
|
||||||
|
this.users = users;
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onRowEditInit(table: Table, user: AuthUserDTO, index: number) {
|
||||||
|
this.clonedUsers[index] = { ...user };
|
||||||
|
}
|
||||||
|
|
||||||
|
onRowEditSave(table: Table, newUser: AuthUserDTO, index: number) {
|
||||||
|
const oldUser = this.clonedUsers[index];
|
||||||
|
delete this.clonedUsers[index];
|
||||||
|
|
||||||
|
if (JSON.stringify(oldUser) === JSON.stringify(newUser) && !this.isEditingNew) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isEditingNew && JSON.stringify(newUser) === JSON.stringify(this.newUserTemplate)) {
|
||||||
|
this.isEditingNew = false;
|
||||||
|
this.users.splice(index, 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isFirstNameInvalid = newUser.firstName == "";
|
||||||
|
this.isLastNameInvalid = newUser.lastName == "";
|
||||||
|
this.isEMailInvalid = newUser.email == "";
|
||||||
|
this.isPasswordInvalid = newUser.password == "";
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.isEditingNew && (
|
||||||
|
newUser.firstName == "" ||
|
||||||
|
newUser.lastName == "" ||
|
||||||
|
newUser.email == ""
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
table.initRowEdit(newUser);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isEditingNew) {
|
||||||
|
this.spinnerService.showSpinner();
|
||||||
|
this.authService.register(newUser).pipe(catchError(error => {
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
|
||||||
|
if (error.error !== null) {
|
||||||
|
const err: ErrorDTO = error.error;
|
||||||
|
|
||||||
|
if (err.errorCode === ServiceErrorCode.InvalidData && err.message === RegisterErrorMessages.InvalidEMail) {
|
||||||
|
this.isEMailInvalid = true;
|
||||||
|
this.toastService.error(this.translate.instant('admin.auth_users.message.invalid_email'), this.translate.instant('admin.auth_users.message.invalid_email_d', { email: newUser.email }));
|
||||||
|
} else if (err.errorCode === ServiceErrorCode.InvalidUser && err.message === RegisterErrorMessages.UserAlreadyExists) {
|
||||||
|
this.isEMailInvalid = true;
|
||||||
|
this.toastService.error(this.translate.instant('admin.auth_users.message.user_already_exists'), this.translate.instant('admin.auth_users.message.user_already_exists_d', { email: newUser.email }));
|
||||||
|
}
|
||||||
|
error.error = null;
|
||||||
|
table.initRowEdit(newUser);
|
||||||
|
}
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}))
|
||||||
|
.subscribe(_ => {
|
||||||
|
this.initUserList();
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
this.toastService.success(this.translate.instant('admin.auth_users.message.user_added'), this.translate.instant('admin.auth_users.message.user_added_d', { email: newUser.email }));
|
||||||
|
this.isEditingNew = false;
|
||||||
|
});
|
||||||
|
this.triggerUserChangeDetection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.spinnerService.showSpinner();
|
||||||
|
this.authService.updateUserAsAdmin({
|
||||||
|
authUserDTO: oldUser,
|
||||||
|
newAuthUserDTO: newUser,
|
||||||
|
changePassword: newUser.password != ""
|
||||||
|
}).pipe(catchError(err => {
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
this.toastService.error(this.translate.instant('admin.auth_users.message.user_change_failed'), this.translate.instant('admin.auth_users.message.user_change_failed_d', { email: newUser.email }));
|
||||||
|
this.initUserList();
|
||||||
|
throw err;
|
||||||
|
}))
|
||||||
|
.subscribe(_ => {
|
||||||
|
this.initUserList();
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
this.toastService.success(this.translate.instant('admin.auth_users.message.user_changed'), this.translate.instant('admin.auth_users.message.user_changed_d', { email: newUser.email }));
|
||||||
|
});
|
||||||
|
this.triggerUserChangeDetection();
|
||||||
|
}
|
||||||
|
|
||||||
|
onRowEditCancel(user: AuthUserDTO, index: number) {
|
||||||
|
this.isFirstNameInvalid = false;
|
||||||
|
this.isLastNameInvalid = false;
|
||||||
|
this.isEMailInvalid = false;
|
||||||
|
this.isPasswordInvalid = false;
|
||||||
|
|
||||||
|
if (this.isEditingNew) {
|
||||||
|
this.users.splice(index, 1);
|
||||||
|
this.triggerUserChangeDetection();
|
||||||
|
delete this.clonedUsers[index];
|
||||||
|
this.isEditingNew = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.users[index] = this.clonedUsers[index];
|
||||||
|
this.triggerUserChangeDetection();
|
||||||
|
delete this.clonedUsers[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteUser(user: AuthUserDTO) {
|
||||||
|
if (user.email == this.loggedInUserEMail) {
|
||||||
|
this.toastService.error(this.translate.instant('admin.auth_users.message.cannot_delete_user'), this.translate.instant('admin.auth_users.message.logon_with_another_user'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.confirmDialog.confirmDialog(
|
||||||
|
this.translate.instant('admin.auth_users.message.user_delete'), this.translate.instant('admin.auth_users.message.user_delete_q', { email: user.email }),
|
||||||
|
() => {
|
||||||
|
this.spinnerService.showSpinner();
|
||||||
|
this.authService.deleteUserByMail(user.email ?? '')
|
||||||
|
.pipe(catchError(err => {
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
throw err;
|
||||||
|
}))
|
||||||
|
.subscribe(_ => {
|
||||||
|
this.initUserList();
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
this.toastService.success(this.translate.instant('admin.auth_users.message.user_deleted'), this.translate.instant('admin.auth_users.message.user_deleted_d', { email: user.email }));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addUser(table: Table) {
|
||||||
|
const newUser = JSON.parse(JSON.stringify(this.newUserTemplate));
|
||||||
|
newUser.id = Math.max.apply(Math, this.users.map(u => { return u.id ?? 0; })) + 1;
|
||||||
|
console.log(newUser);
|
||||||
|
|
||||||
|
this.users.push(newUser);
|
||||||
|
this.triggerUserChangeDetection();
|
||||||
|
|
||||||
|
table.initRowEdit(newUser);
|
||||||
|
|
||||||
|
const index = this.users.findIndex(u => u.email == newUser.email);
|
||||||
|
this.onRowEditInit(table, newUser, index);
|
||||||
|
|
||||||
|
this.isEditingNew = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerUserChangeDetection() {
|
||||||
|
// trigger change detection (https://github.com/primefaces/primeng/issues/2219)
|
||||||
|
this.users = this.users.slice();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,125 @@
|
|||||||
|
<h1>
|
||||||
|
{{'admin.settings.header' | translate}}
|
||||||
|
</h1>
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<div class="content-header">
|
||||||
|
<h2>
|
||||||
|
{{'admin.settings.website.header' | translate}}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<div class="content-row">
|
||||||
|
<div class="content-column">
|
||||||
|
<div class="content-data-name">{{'admin.settings.website.frontend_version' | translate}}:</div>
|
||||||
|
<div class="content-data-value">{{data.webVersion}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-row">
|
||||||
|
<div class="content-column">
|
||||||
|
<div class="content-data-name">{{'admin.settings.website.backend_version' | translate}}:</div>
|
||||||
|
<div class="content-data-value">{{data.apiVersion}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-row">
|
||||||
|
<div class="content-column">
|
||||||
|
<div class="content-data-name">{{'admin.settings.website.config_path' | translate}}:</div>
|
||||||
|
<div class="content-data-value">{{data.configPath}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-row">
|
||||||
|
<div class="content-column">
|
||||||
|
<div class="content-data-name">{{'admin.settings.website.frontend_base_url' | translate}}:</div>
|
||||||
|
<div class="content-data-value">{{data.webBaseURL}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-row">
|
||||||
|
<div class="content-column">
|
||||||
|
<div class="content-data-name">{{'admin.settings.website.backend_base_url' | translate}}:</div>
|
||||||
|
<div class="content-data-value">{{data.apiBaseURL}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-divider"></div>
|
||||||
|
|
||||||
|
<div class="content-row">
|
||||||
|
<div class="content-column">
|
||||||
|
<div class="content-data-name">{{'admin.settings.website.token_expire_time' | translate}}:</div>
|
||||||
|
<div class="content-data-value">{{data.tokenExpireTime}} {{'general.minutes' | translate}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-row">
|
||||||
|
<div class="content-column">
|
||||||
|
<div class="content-data-name">{{'admin.settings.website.refresh_token_expire_time' | translate}}:</div>
|
||||||
|
<div class="content-data-value">{{data.refreshTokenExpireTime}} {{'general.days' | translate}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<div class="content-header">
|
||||||
|
<h2>
|
||||||
|
{{'admin.settings.e_mail.header' | translate}}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<div class="content-row">
|
||||||
|
<div class="content-column">
|
||||||
|
<div class="content-data-name">{{'admin.settings.e_mail.user' | translate}}:</div>
|
||||||
|
<div class="content-data-value">{{data.mailUser}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-row">
|
||||||
|
<div class="content-column">
|
||||||
|
<div class="content-data-name">{{'admin.settings.e_mail.host' | translate}}:</div>
|
||||||
|
<div class="content-data-value">{{data.mailHost}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-row">
|
||||||
|
<div class="content-column">
|
||||||
|
<div class="content-data-name">{{'admin.settings.e_mail.port' | translate}}:</div>
|
||||||
|
<div class="content-data-value">{{data.mailPort}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-row">
|
||||||
|
<div class="content-column">
|
||||||
|
<div class="content-data-name">{{'admin.settings.e_mail.transceiver' | translate}}:</div>
|
||||||
|
<div class="content-data-value">{{data.mailTransceiver}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-row">
|
||||||
|
<div class="content-column">
|
||||||
|
<div class="content-data-name">{{'admin.settings.e_mail.e_mail_address' | translate}}:</div>
|
||||||
|
<div class="content-data-value">{{data.mailTransceiverAddress}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-row">
|
||||||
|
<form [formGroup]="testMailForm" class="content-column">
|
||||||
|
<div class="content-data-name">
|
||||||
|
<div class="input-field content-input-field">
|
||||||
|
<input type="email" pInputText formControlName="mail" placeholder="{{'admin.settings.e_mail.e_mail' | translate}}" autocomplete="email">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-data-value">
|
||||||
|
<div class="login-form-submit">
|
||||||
|
<button pButton icon="pi pi-save" label="{{'admin.settings.e_mail.send_e_mail' | translate}}" class="btn login-form-submit-btn"
|
||||||
|
(click)="testMail()" [disabled]="testMailForm.invalid"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { SettingsComponent } from './settings.component';
|
||||||
|
|
||||||
|
describe('SettingsComponent', () => {
|
||||||
|
let component: SettingsComponent;
|
||||||
|
let fixture: ComponentFixture<SettingsComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ SettingsComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(SettingsComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,121 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { catchError } from 'rxjs/operators';
|
||||||
|
import { SettingsDTO } from 'src/app/models/config/settings.dto';
|
||||||
|
import { ErrorDTO } from 'src/app/models/error/error-dto';
|
||||||
|
import { ServiceErrorCode } from 'src/app/models/error/service-error-code.enum';
|
||||||
|
import { AuthService } from 'src/app/services/auth/auth.service';
|
||||||
|
import { GuiService } from 'src/app/services/gui/gui.service';
|
||||||
|
import { SettingsService } from 'src/app/services/settings/settings.service';
|
||||||
|
import { SpinnerService } from 'src/app/services/spinner/spinner.service';
|
||||||
|
import { ToastService } from 'src/app/services/toast/toast.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-settings',
|
||||||
|
templateUrl: './settings.component.html',
|
||||||
|
styleUrls: ['./settings.component.scss']
|
||||||
|
})
|
||||||
|
export class SettingsComponent implements OnInit {
|
||||||
|
|
||||||
|
testMailForm!: FormGroup;
|
||||||
|
data: SettingsDTO = {
|
||||||
|
webVersion: '',
|
||||||
|
apiVersion: '',
|
||||||
|
configPath: '',
|
||||||
|
webBaseURL: '',
|
||||||
|
apiBaseURL: '',
|
||||||
|
|
||||||
|
tokenExpireTime: 0,
|
||||||
|
refreshTokenExpireTime: 0,
|
||||||
|
|
||||||
|
mailUser: '',
|
||||||
|
mailPort: 0,
|
||||||
|
mailHost: '',
|
||||||
|
mailTransceiver: '',
|
||||||
|
mailTransceiverAddress: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private settingsService: SettingsService,
|
||||||
|
private spinnerService: SpinnerService,
|
||||||
|
private guiService: GuiService,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private toastService: ToastService,
|
||||||
|
private translate: TranslateService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.spinnerService.showSpinner();
|
||||||
|
this.initForms();
|
||||||
|
|
||||||
|
this.guiService.getSettings()
|
||||||
|
.pipe(catchError(err => {
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
throw err;
|
||||||
|
}))
|
||||||
|
.subscribe(settings => {
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
this.data = settings;
|
||||||
|
this.data.webVersion = this.settingsService.getWebVersion()?.getVersionString() ?? '0.0.0';
|
||||||
|
this.data.apiBaseURL = this.settingsService.getApiURL();
|
||||||
|
if (!this.data.apiBaseURL.endsWith('/')) {
|
||||||
|
this.data.apiBaseURL += '/';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initForms(): void {
|
||||||
|
this.testMailForm = this.formBuilder.group({
|
||||||
|
mail: [null, [Validators.required, Validators.email]],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
testMail(): void {
|
||||||
|
this.spinnerService.showSpinner();
|
||||||
|
|
||||||
|
const mail = this.testMailForm.value.mail;
|
||||||
|
if (!mail) {
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.guiService.sendTestMail(mail)
|
||||||
|
.pipe(catchError(error => {
|
||||||
|
let header = this.translate.instant('admin.settings.message.error');
|
||||||
|
let message = this.translate.instant('admin.settings.message.could_not_send_mail');
|
||||||
|
|
||||||
|
if (error.error !== null) {
|
||||||
|
const err: ErrorDTO = error.error;
|
||||||
|
|
||||||
|
if (err.errorCode === ServiceErrorCode.ConnectionFailed) {
|
||||||
|
header = this.translate.instant('admin.settings.message.connection_failed');
|
||||||
|
message = this.translate.instant('admin.settings.message.connection_to_mail_failed');
|
||||||
|
error.error = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.errorCode === ServiceErrorCode.InvalidUser) {
|
||||||
|
header = this.translate.instant('admin.settings.message.connection_failed');
|
||||||
|
message = this.translate.instant('admin.settings.message.mail_login_failed');
|
||||||
|
error.error = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.errorCode === ServiceErrorCode.MailError) {
|
||||||
|
header = this.translate.instant('admin.settings.message.send_failed');
|
||||||
|
message = this.translate.instant('admin.settings.message.test_mail_not_send');
|
||||||
|
error.error = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
this.toastService.error(header, message);
|
||||||
|
throw error;
|
||||||
|
}))
|
||||||
|
.subscribe(res => {
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
this.toastService.success(this.translate.instant('admin.settings.message.success'), this.translate.instant('admin.settings.message.send_mail'));
|
||||||
|
this.testMailForm.reset();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { SettingsComponent } from './components/settings/settings.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{path:'', component: SettingsComponent}
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule]
|
||||||
|
})
|
||||||
|
export class SettingsRoutingModule { }
|
19
kdb-web/src/app/modules/admin/settings/settings.module.ts
Normal file
19
kdb-web/src/app/modules/admin/settings/settings.module.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
|
import { SettingsRoutingModule } from './settings-routing.module';
|
||||||
|
import { SettingsComponent } from './components/settings/settings.component';
|
||||||
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
SettingsComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
SettingsRoutingModule,
|
||||||
|
SharedModule
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class SettingsModule { }
|
21
kdb-web/src/app/modules/auth/auth-routing.module.ts
Normal file
21
kdb-web/src/app/modules/auth/auth-routing.module.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { ForgetPasswordComponent } from './components/forget-password/forget-password.component';
|
||||||
|
import { LoginComponent } from './components/login/login.component';
|
||||||
|
import { RegistrationComponent } from './components/registration/registration.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{ path: 'login', component: LoginComponent },
|
||||||
|
{ path: 'register', component: RegistrationComponent },
|
||||||
|
{ path: 'register/:id', component: RegistrationComponent },
|
||||||
|
{ path: 'forgot-password', component: ForgetPasswordComponent },
|
||||||
|
{ path: 'forgot-password/:id', component: ForgetPasswordComponent },
|
||||||
|
{ path: 'forgot-password', component: ForgetPasswordComponent },
|
||||||
|
{ path: 'forgot-password/:id', component: ForgetPasswordComponent },
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule]
|
||||||
|
})
|
||||||
|
export class AuthRoutingModule { }
|
23
kdb-web/src/app/modules/auth/auth.module.ts
Normal file
23
kdb-web/src/app/modules/auth/auth.module.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
|
import { AuthRoutingModule } from './auth-routing.module';
|
||||||
|
import { ForgetPasswordComponent } from './components/forget-password/forget-password.component';
|
||||||
|
import { LoginComponent } from './components/login/login.component';
|
||||||
|
import { RegistrationComponent } from './components/registration/registration.component';
|
||||||
|
import { SharedModule } from '../shared/shared.module';
|
||||||
|
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
ForgetPasswordComponent,
|
||||||
|
LoginComponent,
|
||||||
|
RegistrationComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
AuthRoutingModule,
|
||||||
|
SharedModule
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AuthModule { }
|
@ -0,0 +1,57 @@
|
|||||||
|
<section class="login-wrapper">
|
||||||
|
<div class="login-form-wrapper">
|
||||||
|
<div class="login-form">
|
||||||
|
<ng-container *ngIf="resetPasswordId === null; else resetPasswordForm">
|
||||||
|
<form [formGroup]="emailForm">
|
||||||
|
<h1>{{'auth.header' | translate}}</h1>
|
||||||
|
<div *ngIf="!ready" class="input-field">
|
||||||
|
<input type="email" pInputText formControlName="email" placeholder="{{'auth.forgot_password.e_mail' | translate}}"
|
||||||
|
autocomplete="username email">
|
||||||
|
</div>
|
||||||
|
<div *ngIf="ready" class="input-field-info-text">
|
||||||
|
{{'auth.forgot_password.send_confirmation_url' | translate}}
|
||||||
|
</div>
|
||||||
|
<div class="login-form-submit">
|
||||||
|
<button pButton label="{{'auth.forgot_password.reset_password' | translate}}" class="btn login-form-submit-btn"
|
||||||
|
(click)="forgotPassword()" [disabled]="emailForm.invalid || ready"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="login-form-sub-button-wrapper">
|
||||||
|
<div class="login-form-sub-btn-wrapper">
|
||||||
|
<button pButton label="{{'auth.forgot_password.login' | translate}}" class="btn login-form-sub-btn" (click)="login()"></button>
|
||||||
|
</div>
|
||||||
|
<div class="login-form-sub-btn-wrapper">
|
||||||
|
<button pButton label="{{'auth.forgot_password.register' | translate}}" class="btn login-form-sub-btn"
|
||||||
|
(click)="register()"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-template #resetPasswordForm>
|
||||||
|
<form [formGroup]="passwordForm">
|
||||||
|
<h1>{{'auth.header' | translate}}</h1>
|
||||||
|
|
||||||
|
<div class="input-field">
|
||||||
|
<input type="password" pInputText formControlName="password" placeholder="{{'auth.forgot_password.password' | translate}}"
|
||||||
|
autocomplete="new-password">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-field">
|
||||||
|
<input type="password" pInputText formControlName="passwordRepeat"
|
||||||
|
placeholder="{{'auth.forgot_password.repeat_password' | translate}}"
|
||||||
|
[ngClass]="{ 'invalid-feedback-input': submitted && repeatErrors.password}">
|
||||||
|
<div *ngIf="submitted" class="invalid-feedback">
|
||||||
|
<div *ngIf="repeatErrors.password">{{'auth.forgot_password.passwords_do_not_match' | translate}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="login-form-submit">
|
||||||
|
<button pButton label="{{'auth.forgot_password.reset_password' | translate}}" class="btn login-form-submit-btn"
|
||||||
|
(click)="resetPassword()" [disabled]="passwordForm.invalid || ready"></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ForgetPasswordComponent } from './forget-password.component';
|
||||||
|
|
||||||
|
describe('ForgetPasswordComponent', () => {
|
||||||
|
let component: ForgetPasswordComponent;
|
||||||
|
let fixture: ComponentFixture<ForgetPasswordComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ ForgetPasswordComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ForgetPasswordComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,136 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { catchError } from 'rxjs/operators';
|
||||||
|
import { ResetPasswordDTO } from 'src/app/models/auth/reset-password.dto';
|
||||||
|
import { AuthService } from 'src/app/services/auth/auth.service';
|
||||||
|
import { SpinnerService } from 'src/app/services/spinner/spinner.service';
|
||||||
|
import { ToastService } from 'src/app/services/toast/toast.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-forget-password',
|
||||||
|
templateUrl: './forget-password.component.html',
|
||||||
|
styleUrls: ['./forget-password.component.scss']
|
||||||
|
})
|
||||||
|
export class ForgetPasswordComponent implements OnInit {
|
||||||
|
|
||||||
|
emailForm!: FormGroup;
|
||||||
|
passwordForm!: FormGroup;
|
||||||
|
submitted = false;
|
||||||
|
ready = false;
|
||||||
|
repeatErrors = {
|
||||||
|
email: false,
|
||||||
|
password: false
|
||||||
|
};
|
||||||
|
|
||||||
|
resetPasswordId: string | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private authService: AuthService,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private router: Router,
|
||||||
|
private spinnerService: SpinnerService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private toastService: ToastService,
|
||||||
|
private translate: TranslateService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.spinnerService.showSpinner();
|
||||||
|
this.authService.isUserLoggedInAsync().then(result => {
|
||||||
|
if (result) {
|
||||||
|
this.router.navigate(['/dashboard']);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initForms();
|
||||||
|
this.checkResetPasswordId();
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initForms(): void {
|
||||||
|
this.emailForm = this.formBuilder.group({
|
||||||
|
email: [null, [Validators.required, Validators.email]]
|
||||||
|
});
|
||||||
|
|
||||||
|
this.passwordForm = this.formBuilder.group({
|
||||||
|
password: [null, [Validators.required, Validators.minLength(8)]],
|
||||||
|
passwordRepeat: [null, [Validators.required, Validators.minLength(8)]]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
login(): void {
|
||||||
|
this.router.navigate(['/auth/login']);
|
||||||
|
}
|
||||||
|
|
||||||
|
register(): void {
|
||||||
|
this.router.navigate(['/auth/register']);
|
||||||
|
}
|
||||||
|
|
||||||
|
forgotPassword(): void {
|
||||||
|
this.submitted = true;
|
||||||
|
|
||||||
|
if (this.emailForm.invalid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.spinnerService.showSpinner();
|
||||||
|
this.authService.forgotPassword(this.emailForm.value.email)
|
||||||
|
.pipe(catchError(err => {
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
throw err;
|
||||||
|
})).subscribe(res => {
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
this.ready = true;
|
||||||
|
setTimeout(() => { this.router.navigate(['/dashboard']); }, 5000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
checkResetPasswordId(): void {
|
||||||
|
const id = this.route.snapshot.params['id'];
|
||||||
|
if (id) {
|
||||||
|
this.resetPasswordId = id;
|
||||||
|
this.spinnerService.showSpinner();
|
||||||
|
this.authService.getEMailFromforgotPasswordId(id)
|
||||||
|
.pipe(catchError(err => {
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
this.router.navigate(['/auth/forgot-password']);
|
||||||
|
throw err;
|
||||||
|
})).subscribe(email => {
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
if (email) {
|
||||||
|
this.emailForm.value.email = email;
|
||||||
|
} else {
|
||||||
|
this.router.navigate(['/auth/forgot-password']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resetPassword(): void {
|
||||||
|
const id = this.route.snapshot.params['id'];
|
||||||
|
if (this.emailForm.value.password !== this.emailForm.value.passwordRepeat) {
|
||||||
|
this.repeatErrors.password = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.spinnerService.showSpinner();
|
||||||
|
|
||||||
|
const resetPasswordDTO: ResetPasswordDTO = {
|
||||||
|
id,
|
||||||
|
password: this.passwordForm.value.password
|
||||||
|
};
|
||||||
|
|
||||||
|
this.authService.resetPassword(resetPasswordDTO)
|
||||||
|
.pipe(catchError(error => {
|
||||||
|
this.router.navigate(['/auth/login']);
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
throw error;
|
||||||
|
}))
|
||||||
|
.subscribe(resp => {
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
this.toastService.success(this.translate.instant('auth.forgot_password.message.reset_password'), this.translate.instant('auth.forgot_password.message.reset_password_d'));
|
||||||
|
this.router.navigate(['/auth/login']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
<section class="login-wrapper">
|
||||||
|
<div class="login-form-wrapper">
|
||||||
|
<div class="login-form">
|
||||||
|
<form [formGroup]="loginForm">
|
||||||
|
<h1>sh-edraft.de</h1>
|
||||||
|
<div class="input-field">
|
||||||
|
<input type="email" pInputText formControlName="email" placeholder="E-Mail" [ngClass]="{ 'invalid-feedback-input': submitted && (
|
||||||
|
(loginForm.controls.email.errors && loginForm.controls.email.errors['required'] || authUserAtrErrors.email.required) ||
|
||||||
|
(authUserAtrErrors.email.wrongData) ||
|
||||||
|
(authUserAtrErrors.email.notConfirmed)
|
||||||
|
)}" autocomplete="username email">
|
||||||
|
<div *ngIf="submitted" class="invalid-feedback">
|
||||||
|
<div
|
||||||
|
*ngIf="loginForm.controls.email.errors && loginForm.controls.email.errors['required'] || authUserAtrErrors.email.required">
|
||||||
|
E-Mail wird benötigt</div>
|
||||||
|
<div *ngIf="authUserAtrErrors.email.wrongData">Benutzer nicht gefunden</div>
|
||||||
|
<div *ngIf="authUserAtrErrors.email.notConfirmed">E-Mail wurde nicht bestätigt</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-field">
|
||||||
|
<!--
|
||||||
|
!! WARNING !!
|
||||||
|
Bugfix from https://github.com/primefaces/primeng/issues/10788
|
||||||
|
styleClass="p-password p-component p-inputwrapper p-input-icon-right"
|
||||||
|
Remove after update!
|
||||||
|
-->
|
||||||
|
<p-password type="password" formControlName="password" placeholder="Passwort" [ngClass]="{ 'invalid-feedback-input': submitted && (
|
||||||
|
(loginForm.controls.password.errors && loginForm.controls.password.errors['required'] || authUserAtrErrors.password.required) ||
|
||||||
|
(authUserAtrErrors.password.wrongData)
|
||||||
|
)}" autocomplete="current-password" [toggleMask]="true" [feedback]="false"
|
||||||
|
styleClass="p-password p-component p-inputwrapper p-input-icon-right"
|
||||||
|
></p-password>
|
||||||
|
<div *ngIf="submitted" class="invalid-feedback">
|
||||||
|
<div
|
||||||
|
*ngIf="loginForm.controls.password.errors && loginForm.controls.password.errors['required'] || authUserAtrErrors.password.required">
|
||||||
|
Password wird benötigt</div>
|
||||||
|
<div *ngIf="authUserAtrErrors.password.wrongData">Falsches passwort</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="login-form-submit">
|
||||||
|
<button pButton label="Anmelden" class="btn login-form-submit-btn" (click)="login()"
|
||||||
|
[disabled]="loginForm.invalid"></button>
|
||||||
|
</div>
|
||||||
|
<div class="login-form-sub-button-wrapper">
|
||||||
|
<div class="login-form-sub-btn-wrapper">
|
||||||
|
<button pButton label="Registrieren" class="btn login-form-sub-btn"
|
||||||
|
(click)="register()"></button>
|
||||||
|
</div>
|
||||||
|
<div class="login-form-sub-btn-wrapper">
|
||||||
|
<button pButton label="Passwort vergessen?"
|
||||||
|
class="btn login-form-sub-btn login-form-sub-login-btn p-button-text"
|
||||||
|
(click)="forgotPassword()"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { LoginComponent } from './login.component';
|
||||||
|
|
||||||
|
describe('LoginComponent', () => {
|
||||||
|
let component: LoginComponent;
|
||||||
|
let fixture: ComponentFixture<LoginComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ LoginComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(LoginComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
114
kdb-web/src/app/modules/auth/components/login/login.component.ts
Normal file
114
kdb-web/src/app/modules/auth/components/login/login.component.ts
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { AuthService } from 'src/app/services/auth/auth.service';
|
||||||
|
import { AuthUserDTO } from 'src/app/models/auth/auth-user.dto';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { catchError } from 'rxjs/operators';
|
||||||
|
import { ErrorDTO } from 'src/app/models/error/error-dto';
|
||||||
|
import { AuthErrorMessages } from 'src/app/models/auth/auth-error-messages.enum';
|
||||||
|
import { ServiceErrorCode } from 'src/app/models/error/service-error-code.enum';
|
||||||
|
import { AuthUserAtrErrors } from 'src/app/models/auth/auth-user-atr-errors';
|
||||||
|
import { SpinnerService } from 'src/app/services/spinner/spinner.service';
|
||||||
|
import { ThemeService } from 'src/app/services/theme/theme.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-login',
|
||||||
|
templateUrl: './login.component.html',
|
||||||
|
styleUrls: ['./login.component.scss']
|
||||||
|
})
|
||||||
|
export class LoginComponent implements OnInit {
|
||||||
|
|
||||||
|
loginForm!: FormGroup<{
|
||||||
|
email: FormControl<string | null>,
|
||||||
|
password: FormControl<string | null>
|
||||||
|
}>;
|
||||||
|
submitted = false;
|
||||||
|
|
||||||
|
authUserAtrErrors!: AuthUserAtrErrors;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private authService: AuthService,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private router: Router,
|
||||||
|
private spinnerService: SpinnerService,
|
||||||
|
private themeService: ThemeService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.spinnerService.showSpinner();
|
||||||
|
this.authService.isUserLoggedInAsync().then(result => {
|
||||||
|
if (result) {
|
||||||
|
this.router.navigate(['/dashboard']);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initLoginForm();
|
||||||
|
this.resetStateFlags();
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
resetStateFlags(): void {
|
||||||
|
this.authUserAtrErrors = new AuthUserAtrErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
initLoginForm(): void {
|
||||||
|
this.loginForm = this.formBuilder.group({
|
||||||
|
email: ['', [Validators.required, Validators.email]],
|
||||||
|
password: ['', [Validators.required, Validators.minLength(8)]]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
register(): void {
|
||||||
|
this.router.navigate(['/auth/register']);
|
||||||
|
}
|
||||||
|
|
||||||
|
forgotPassword(): void {
|
||||||
|
this.router.navigate(['/auth/forgot-password']);
|
||||||
|
}
|
||||||
|
|
||||||
|
login(): void {
|
||||||
|
this.submitted = true;
|
||||||
|
this.resetStateFlags();
|
||||||
|
|
||||||
|
if (this.loginForm.invalid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.spinnerService.showSpinner();
|
||||||
|
const user: AuthUserDTO = {
|
||||||
|
firstName: '',
|
||||||
|
lastName: '',
|
||||||
|
email: this.loginForm.value.email ?? null,
|
||||||
|
password: this.loginForm.value.password ?? null
|
||||||
|
};
|
||||||
|
|
||||||
|
this.authService.login(user)
|
||||||
|
.pipe(catchError(error => {
|
||||||
|
if (error.error !== null) {
|
||||||
|
const err: ErrorDTO = error.error;
|
||||||
|
|
||||||
|
if (err.errorCode === ServiceErrorCode.InvalidData && err.message === AuthErrorMessages.UserIsEmpty) {
|
||||||
|
this.authUserAtrErrors.email.required = true;
|
||||||
|
this.authUserAtrErrors.password.required = true;
|
||||||
|
} else if (err.errorCode === ServiceErrorCode.InvalidUser && err.message === AuthErrorMessages.UserNotFound) {
|
||||||
|
this.authUserAtrErrors.email.wrongData = true;
|
||||||
|
} else if (err.errorCode === ServiceErrorCode.InvalidUser && err.message === AuthErrorMessages.WrongPassword) {
|
||||||
|
this.authUserAtrErrors.password.wrongData = true;
|
||||||
|
} else if (err.errorCode === ServiceErrorCode.InvalidUser && err.message === AuthErrorMessages.EMailNotConfirmed) {
|
||||||
|
this.authUserAtrErrors.email.notConfirmed = true;
|
||||||
|
}
|
||||||
|
error.error = null;
|
||||||
|
}
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
throw error;
|
||||||
|
}))
|
||||||
|
.subscribe(token => {
|
||||||
|
this.authService.saveToken(token);
|
||||||
|
this.themeService.loadTheme();
|
||||||
|
this.themeService.loadMenu();
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
this.router.navigate(['/dashboard']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
<section class="login-wrapper">
|
||||||
|
<div class="login-form-wrapper register-form-wrapper">
|
||||||
|
<div class="login-form">
|
||||||
|
<form [formGroup]="loginForm">
|
||||||
|
<h1>sh-edraft.de</h1>
|
||||||
|
<div class="input-field">
|
||||||
|
<input type="text" pInputText formControlName="firstName" placeholder="Vorname"
|
||||||
|
autocomplete="given-name">
|
||||||
|
<div *ngIf="submitted" class="invalid-feedback">
|
||||||
|
<div
|
||||||
|
*ngIf="loginForm.controls.firstName.errors && loginForm.controls.firstName.errors['required'] || authUserAtrErrors.firstName.required">
|
||||||
|
Vorname wird benötigt</div>
|
||||||
|
<div *ngIf="authUserAtrErrors.firstName.wrongData">Vorname ist ungültig</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-field">
|
||||||
|
<input type="text" pInputText formControlName="lastName" placeholder="Nachname"
|
||||||
|
autocomplete="family-name">
|
||||||
|
<div *ngIf="submitted" class="invalid-feedback">
|
||||||
|
<div
|
||||||
|
*ngIf="loginForm.controls.lastName.errors && loginForm.controls.lastName.errors['required'] || authUserAtrErrors.lastName.required">
|
||||||
|
Nachname wird benötigt</div>
|
||||||
|
<div *ngIf="authUserAtrErrors.lastName.wrongData">Nachname ist ungültig</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-field">
|
||||||
|
<input type="email" pInputText formControlName="email" placeholder="E-Mail"
|
||||||
|
[ngClass]="{ 'invalid-feedback-input': submitted && (authUserAtrErrors.email.wrongData || loginForm.controls.email.errors && loginForm.controls.email.errors['required'] || authUserAtrErrors.email.required)}"
|
||||||
|
autocomplete="username email">
|
||||||
|
<div *ngIf="submitted" class="invalid-feedback">
|
||||||
|
<div
|
||||||
|
*ngIf="loginForm.controls.email.errors && loginForm.controls.email.errors['required'] || authUserAtrErrors.email.required">
|
||||||
|
E-Mail wird benötigt</div>
|
||||||
|
<div *ngIf="authUserAtrErrors.email.wrongData">Benutzer existiert bereits</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-field">
|
||||||
|
<input type="email" pInputText formControlName="emailRepeat" placeholder="E-Mail wiederholen"
|
||||||
|
[ngClass]="{ 'invalid-feedback-input': submitted && repeatErrors.email}">
|
||||||
|
<div *ngIf="submitted" class="invalid-feedback">
|
||||||
|
<div *ngIf="repeatErrors.email">Die E-Mails stimmen nicht überein</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-field">
|
||||||
|
<p-password type="password" formControlName="password" placeholder="Passwort"
|
||||||
|
ngClass="{ 'invalid-feedback': submitted && loginForm.controls.password.errors && loginForm.controls.password.errors['required'] || authUserAtrErrors.password.required}"
|
||||||
|
autocomplete="new-password" [toggleMask]="true" [feedback]="false"
|
||||||
|
styleClass="p-password p-component p-inputwrapper p-input-icon-right"></p-password>
|
||||||
|
<div *ngIf="submitted" class="invalid-feedback">
|
||||||
|
<div
|
||||||
|
*ngIf="loginForm.controls.password.errors && loginForm.controls.password.errors['required'] || authUserAtrErrors.password.required">
|
||||||
|
Password wird benötigt</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-field">
|
||||||
|
<p-password type="password" formControlName="passwordRepeat" placeholder="Passwort wiederholen"
|
||||||
|
[ngClass]="{ 'invalid-feedback-input': submitted && repeatErrors.password}" [toggleMask]="true"
|
||||||
|
[feedback]="false" styleClass="p-password p-component p-inputwrapper p-input-icon-right">
|
||||||
|
</p-password>
|
||||||
|
<div *ngIf="submitted" class="invalid-feedback">
|
||||||
|
<div *ngIf="repeatErrors.password">Die Passwörter stimmen nicht überein</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="login-form-submit">
|
||||||
|
<button pButton label="Registrieren" class="btn login-form-submit-btn" (click)="register()"
|
||||||
|
[disabled]="loginForm.invalid"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="login-form-sub-button-wrapper">
|
||||||
|
<div class="login-form-sub-btn-wrapper">
|
||||||
|
<button pButton label="Einloggen" class="btn login-form-sub-btn" (click)="login()"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { RegistrationComponent } from './registration.component';
|
||||||
|
|
||||||
|
describe('RegistrationComponent', () => {
|
||||||
|
let component: RegistrationComponent;
|
||||||
|
let fixture: ComponentFixture<RegistrationComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ RegistrationComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(RegistrationComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,144 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { catchError } from 'rxjs/operators';
|
||||||
|
import { AuthErrorMessages } from 'src/app/models/auth/auth-error-messages.enum';
|
||||||
|
import { AuthUserDTO } from 'src/app/models/auth/auth-user.dto';
|
||||||
|
import { AuthUserAtrErrors } from 'src/app/models/auth/auth-user-atr-errors';
|
||||||
|
import { ErrorDTO } from 'src/app/models/error/error-dto';
|
||||||
|
import { ServiceErrorCode } from 'src/app/models/error/service-error-code.enum';
|
||||||
|
import { AuthService } from 'src/app/services/auth/auth.service';
|
||||||
|
import { SpinnerService } from 'src/app/services/spinner/spinner.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-registration',
|
||||||
|
templateUrl: './registration.component.html',
|
||||||
|
styleUrls: ['./registration.component.scss']
|
||||||
|
})
|
||||||
|
export class RegistrationComponent implements OnInit {
|
||||||
|
|
||||||
|
loginForm!: FormGroup<{
|
||||||
|
firstName: FormControl<string | null>,
|
||||||
|
lastName: FormControl<string | null>,
|
||||||
|
email: FormControl<string | null>,
|
||||||
|
emailRepeat: FormControl<string | null>,
|
||||||
|
password: FormControl<string | null>,
|
||||||
|
passwordRepeat: FormControl<string | null>
|
||||||
|
}>;
|
||||||
|
submitted = false;
|
||||||
|
authUserAtrErrors!: AuthUserAtrErrors;
|
||||||
|
repeatErrors = {
|
||||||
|
email: false,
|
||||||
|
password: false
|
||||||
|
};
|
||||||
|
|
||||||
|
showEMailConfirmation = false;
|
||||||
|
showEMailConfirmationError = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private authService: AuthService,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private router: Router,
|
||||||
|
private spinnerService: SpinnerService,
|
||||||
|
private route: ActivatedRoute
|
||||||
|
) {
|
||||||
|
this.spinnerService.showSpinner();
|
||||||
|
this.authService.isUserLoggedInAsync().then(res => {
|
||||||
|
if (res) {
|
||||||
|
this.router.navigate(['/dashboard']);
|
||||||
|
}
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.initLoginForm();
|
||||||
|
this.resetStateFlags();
|
||||||
|
this.confirmEMail();
|
||||||
|
}
|
||||||
|
|
||||||
|
resetStateFlags(): void {
|
||||||
|
this.authUserAtrErrors = new AuthUserAtrErrors();
|
||||||
|
this.repeatErrors = {
|
||||||
|
email: false,
|
||||||
|
password: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
login(): void {
|
||||||
|
this.router.navigate(['/auth/login']);
|
||||||
|
}
|
||||||
|
|
||||||
|
initLoginForm(): void {
|
||||||
|
this.loginForm = this.formBuilder.group({
|
||||||
|
firstName: ['', Validators.required],
|
||||||
|
lastName: ['', Validators.required],
|
||||||
|
email: ['', [Validators.required, Validators.email]],
|
||||||
|
emailRepeat: ['', [Validators.required, Validators.email]],
|
||||||
|
password: ['', [Validators.required, Validators.minLength(8)]],
|
||||||
|
passwordRepeat: ['', [Validators.required, Validators.minLength(8)]]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
register(): void {
|
||||||
|
this.submitted = true;
|
||||||
|
this.resetStateFlags();
|
||||||
|
|
||||||
|
// stop here if form is invalid
|
||||||
|
if (this.loginForm.invalid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.loginForm.value.email !== this.loginForm.value.emailRepeat) {
|
||||||
|
this.repeatErrors.email = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.loginForm.value.password !== this.loginForm.value.passwordRepeat) {
|
||||||
|
this.repeatErrors.password = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.spinnerService.showSpinner();
|
||||||
|
const user: AuthUserDTO = {
|
||||||
|
firstName: this.loginForm.value.firstName ?? null,
|
||||||
|
lastName: this.loginForm.value.lastName ?? null,
|
||||||
|
email: this.loginForm.value.email ?? null,
|
||||||
|
password: this.loginForm.value.password ?? null
|
||||||
|
};
|
||||||
|
|
||||||
|
this.authService.register(user)
|
||||||
|
.pipe(catchError(error => {
|
||||||
|
if (error.error !== null) {
|
||||||
|
const err: ErrorDTO = error.error;
|
||||||
|
|
||||||
|
if (err.errorCode === ServiceErrorCode.InvalidUser && err.message === AuthErrorMessages.UserAlreadyExists) {
|
||||||
|
this.authUserAtrErrors.email.wrongData = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
throw error;
|
||||||
|
}))
|
||||||
|
.subscribe(resp => {
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
this.router.navigate(['/auth/login']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmEMail(): void {
|
||||||
|
const id = this.route.snapshot.params['id'];
|
||||||
|
if (id) {
|
||||||
|
this.spinnerService.showSpinner();
|
||||||
|
this.authService.confirmEMail(id)
|
||||||
|
.pipe(catchError(error => {
|
||||||
|
this.router.navigate(['/auth/login']);
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
throw error;
|
||||||
|
}))
|
||||||
|
.subscribe(resp => {
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
this.router.navigate(['/auth/login']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AuthGuard } from './auth.guard';
|
||||||
|
|
||||||
|
describe('AuthGuard', () => {
|
||||||
|
let guard: AuthGuard;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
guard = TestBed.inject(AuthGuard);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(guard).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
38
kdb-web/src/app/modules/shared/guards/auth/auth.guard.ts
Normal file
38
kdb-web/src/app/modules/shared/guards/auth/auth.guard.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
|
||||||
|
import { catchError } from 'rxjs/operators';
|
||||||
|
import { AuthService } from 'src/app/services/auth/auth.service';
|
||||||
|
import { ThemeService } from 'src/app/services/theme/theme.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class AuthGuard implements CanActivate {
|
||||||
|
constructor(
|
||||||
|
private router: Router,
|
||||||
|
private authService: AuthService,
|
||||||
|
private themeService: ThemeService
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
|
||||||
|
if (!this.authService.getToken().token) {
|
||||||
|
this.authService.logout();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!await this.authService.isUserLoggedInAsync()) {
|
||||||
|
this.authService.logout();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const role = route.data['role'];
|
||||||
|
if (role) {
|
||||||
|
if (!await this.authService.hasUserPermission(role)) {
|
||||||
|
this.router.navigate(['/dashboard']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
13
kdb-web/src/app/modules/shared/pipes/auth-role.pipe.ts
Normal file
13
kdb-web/src/app/modules/shared/pipes/auth-role.pipe.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
import { AuthRoles } from 'src/app/models/auth/auth-roles.enum';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'authRole'
|
||||||
|
})
|
||||||
|
export class AuthRolePipe implements PipeTransform {
|
||||||
|
|
||||||
|
transform(value: AuthRoles): string {
|
||||||
|
return AuthRoles[value].toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
8
kdb-web/src/app/modules/shared/pipes/bool.pipe.spec.ts
Normal file
8
kdb-web/src/app/modules/shared/pipes/bool.pipe.spec.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { BoolPipe } from './bool.pipe';
|
||||||
|
|
||||||
|
describe('BoolPipe', () => {
|
||||||
|
it('create an instance', () => {
|
||||||
|
const pipe = new BoolPipe();
|
||||||
|
expect(pipe).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
21
kdb-web/src/app/modules/shared/pipes/bool.pipe.ts
Normal file
21
kdb-web/src/app/modules/shared/pipes/bool.pipe.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'bool'
|
||||||
|
})
|
||||||
|
export class BoolPipe implements PipeTransform {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private translate: TranslateService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
transform(value: boolean): string {
|
||||||
|
if (value === true) {
|
||||||
|
return this.translate.instant('common.bool_as_string.true');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.translate.instant('common.bool_as_string.false');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
27
kdb-web/src/app/modules/shared/pipes/ip-address.pipe.ts
Normal file
27
kdb-web/src/app/modules/shared/pipes/ip-address.pipe.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'ipAddress'
|
||||||
|
})
|
||||||
|
export class IpAddressPipe implements PipeTransform {
|
||||||
|
|
||||||
|
transform(ipAsArray: number[]): string {
|
||||||
|
let ipAsString = "";
|
||||||
|
|
||||||
|
if (ipAsArray.length != 4){
|
||||||
|
throw new Error("Invalid IP")
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < ipAsArray.length; i++) {
|
||||||
|
const byte = ipAsArray[i];
|
||||||
|
if (i == ipAsArray.length - 1) {
|
||||||
|
ipAsString += `${byte}`;
|
||||||
|
} else {
|
||||||
|
ipAsString += `${byte}.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ipAsString;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
71
kdb-web/src/app/modules/shared/shared.module.ts
Normal file
71
kdb-web/src/app/modules/shared/shared.module.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { CheckboxModule } from 'primeng/checkbox';
|
||||||
|
import { ConfirmDialogModule } from 'primeng/confirmdialog';
|
||||||
|
import { DialogModule } from 'primeng/dialog';
|
||||||
|
import { DropdownModule } from 'primeng/dropdown';
|
||||||
|
import { DynamicDialogModule } from 'primeng/dynamicdialog';
|
||||||
|
import { InputTextModule } from 'primeng/inputtext';
|
||||||
|
import { MenuModule } from 'primeng/menu';
|
||||||
|
import { PasswordModule } from 'primeng/password';
|
||||||
|
import { ProgressSpinnerModule } from 'primeng/progressspinner';
|
||||||
|
import { TableModule } from 'primeng/table';
|
||||||
|
import { ToastModule } from 'primeng/toast';
|
||||||
|
import { AuthRolePipe } from './pipes/auth-role.pipe';
|
||||||
|
import { IpAddressPipe } from './pipes/ip-address.pipe';
|
||||||
|
import { BoolPipe } from './pipes/bool.pipe';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AuthRolePipe,
|
||||||
|
IpAddressPipe,
|
||||||
|
BoolPipe,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
ButtonModule,
|
||||||
|
PasswordModule,
|
||||||
|
MenuModule,
|
||||||
|
DialogModule,
|
||||||
|
ProgressSpinnerModule,
|
||||||
|
HttpClientModule,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
ToastModule,
|
||||||
|
ConfirmDialogModule,
|
||||||
|
TableModule,
|
||||||
|
InputTextModule,
|
||||||
|
CheckboxModule,
|
||||||
|
DropdownModule,
|
||||||
|
TranslateModule,
|
||||||
|
DynamicDialogModule
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
ButtonModule,
|
||||||
|
PasswordModule,
|
||||||
|
MenuModule,
|
||||||
|
DialogModule,
|
||||||
|
ProgressSpinnerModule,
|
||||||
|
HttpClientModule,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
ToastModule,
|
||||||
|
ConfirmDialogModule,
|
||||||
|
TableModule,
|
||||||
|
InputTextModule,
|
||||||
|
CheckboxModule,
|
||||||
|
DropdownModule,
|
||||||
|
TranslateModule,
|
||||||
|
DynamicDialogModule,
|
||||||
|
AuthRolePipe,
|
||||||
|
IpAddressPipe,
|
||||||
|
BoolPipe,
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class SharedModule { }
|
@ -0,0 +1,13 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { ChangePasswordComponent } from './components/change-password/change-password.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{path: '', component:ChangePasswordComponent}
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule]
|
||||||
|
})
|
||||||
|
export class ChangePasswordRoutingModule { }
|
@ -0,0 +1,19 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
|
import { ChangePasswordRoutingModule } from './change-password-routing.module';
|
||||||
|
import { ChangePasswordComponent } from './components/change-password/change-password.component';
|
||||||
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
ChangePasswordComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
ChangePasswordRoutingModule,
|
||||||
|
SharedModule
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class ChangePasswordModule { }
|
@ -0,0 +1,35 @@
|
|||||||
|
<h1>
|
||||||
|
{{'view.change-password.header' | translate}}
|
||||||
|
</h1>
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<div class="content">
|
||||||
|
<form [formGroup]="passwordForm">
|
||||||
|
<div class="input-field">
|
||||||
|
<input type="password" pInputText formControlName="oldPassword" placeholder="{{'view.change-password.active_password' | translate}}"
|
||||||
|
autocomplete="password" [ngClass]="{ 'invalid-feedback-input': submitted && errors.oldPassword}">
|
||||||
|
<div *ngIf="submitted" class="invalid-feedback">
|
||||||
|
<div *ngIf="errors.oldPassword">{{'view.change-password.wrong_password' | translate}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-field">
|
||||||
|
<input type="password" pInputText formControlName="password" placeholder="{{'view.change-password.new_password' | translate}}"
|
||||||
|
autocomplete="new-password">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-field">
|
||||||
|
<input type="password" pInputText formControlName="passwordRepeat"
|
||||||
|
placeholder="{{'view.change-password.repeat_new_password' | translate}}"
|
||||||
|
[ngClass]="{ 'invalid-feedback-input': submitted && errors.repeatPassword}">
|
||||||
|
<div *ngIf="submitted" class="invalid-feedback">
|
||||||
|
<div *ngIf="errors.repeatPassword">{{'view.change-password.passwords_do_not_match' | translate}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="login-form-submit">
|
||||||
|
<button pButton icon="pi pi-save" label="{{'view.change-password.save' | translate}}" class="btn login-form-submit-btn"
|
||||||
|
(click)="changePassword()" [disabled]="passwordForm.invalid || ready"></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ChangePasswordComponent } from './change-password.component';
|
||||||
|
|
||||||
|
describe('ChangePasswordComponent', () => {
|
||||||
|
let component: ChangePasswordComponent;
|
||||||
|
let fixture: ComponentFixture<ChangePasswordComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ ChangePasswordComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ChangePasswordComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,107 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { catchError } from 'rxjs/operators';
|
||||||
|
import { AuthErrorMessages } from 'src/app/models/auth/auth-error-messages.enum';
|
||||||
|
import { UpdateUserDTO } from 'src/app/models/auth/update-user.dto';
|
||||||
|
import { ErrorDTO } from 'src/app/models/error/error-dto';
|
||||||
|
import { ServiceErrorCode } from 'src/app/models/error/service-error-code.enum';
|
||||||
|
import { AuthService } from 'src/app/services/auth/auth.service';
|
||||||
|
import { SpinnerService } from 'src/app/services/spinner/spinner.service';
|
||||||
|
import { ToastService } from 'src/app/services/toast/toast.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-change-password',
|
||||||
|
templateUrl: './change-password.component.html',
|
||||||
|
styleUrls: ['./change-password.component.scss']
|
||||||
|
})
|
||||||
|
export class ChangePasswordComponent implements OnInit {
|
||||||
|
|
||||||
|
passwordForm!: FormGroup;
|
||||||
|
submitted = false;
|
||||||
|
ready = false;
|
||||||
|
errors = {
|
||||||
|
email: false,
|
||||||
|
repeatPassword: false,
|
||||||
|
oldPassword: false
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private authService: AuthService,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private router: Router,
|
||||||
|
private spinnerService: SpinnerService,
|
||||||
|
private toastService: ToastService,
|
||||||
|
private translate: TranslateService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.initForms();
|
||||||
|
}
|
||||||
|
|
||||||
|
initForms(): void {
|
||||||
|
this.passwordForm = this.formBuilder.group({
|
||||||
|
oldPassword: [null, [Validators.required, Validators.minLength(8)]],
|
||||||
|
password: [null, [Validators.required, Validators.minLength(8)]],
|
||||||
|
passwordRepeat: [null, [Validators.required, Validators.minLength(8)]]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
resetErrorFlags(): void {
|
||||||
|
this.errors = {
|
||||||
|
email: false,
|
||||||
|
repeatPassword: false,
|
||||||
|
oldPassword: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
changePassword(): void {
|
||||||
|
this.submitted = true;
|
||||||
|
this.resetErrorFlags();
|
||||||
|
|
||||||
|
if (this.passwordForm.value.password !== this.passwordForm.value.passwordRepeat) {
|
||||||
|
this.errors.repeatPassword = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.spinnerService.showSpinner();
|
||||||
|
|
||||||
|
const decodedToken = this.authService.getDecodedToken();
|
||||||
|
const changePasswordDTO: UpdateUserDTO = {
|
||||||
|
authUserDTO: {
|
||||||
|
firstName: null,
|
||||||
|
lastName: null,
|
||||||
|
password: this.passwordForm.value.oldPassword,
|
||||||
|
email: this.authService.getEMailFromDecodedToken(decodedToken)
|
||||||
|
},
|
||||||
|
newAuthUserDTO: {
|
||||||
|
firstName: null,
|
||||||
|
lastName: null,
|
||||||
|
password: this.passwordForm.value.password,
|
||||||
|
email: this.authService.getEMailFromDecodedToken(decodedToken)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.authService.updateUser(changePasswordDTO)
|
||||||
|
.pipe(catchError(error => {
|
||||||
|
if (error.error !== null) {
|
||||||
|
const err: ErrorDTO = error.error;
|
||||||
|
|
||||||
|
if (err.errorCode === ServiceErrorCode.InvalidUser && err.message === AuthErrorMessages.WrongPassword) {
|
||||||
|
this.errors.oldPassword = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
error.error = null;
|
||||||
|
}
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
this.toastService.error(this.translate.instant('view.change_password.message.error'), this.translate.instant('view.change_password.message.password_cannot_be_changed'));
|
||||||
|
throw error;
|
||||||
|
}))
|
||||||
|
.subscribe(resp => {
|
||||||
|
this.spinnerService.hideSpinner();
|
||||||
|
this.toastService.success(this.translate.instant('view.change_password.message.change_password'), this.translate.instant('view.change_password.message.changed_password'));
|
||||||
|
this.router.navigate(["/dashboard"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
<p>dashboard works!</p>
|
||||||
|
|
||||||
|
<div class="content"></div>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user