Added frontend

This commit is contained in:
2022-02-20 19:07:41 +01:00
commit 4c21fa631a
155 changed files with 31276 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support
# You can see what browsers were selected by your queries by running:
# npx browserslist
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR
not IE 9-10 # Angular support for IE 9-10 has been deprecated and will be removed as of Angular v11. To opt-in, remove the 'not' prefix on this line.
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.
+16
View File
@@ -0,0 +1,16 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false
+66
View File
@@ -0,0 +1,66 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
*/dist
*/tmp
*/out-tsc
# Only exists if Bazel was run
*/bazel-out
# dependencies
*node_modules*
# profiling files
*chrome-profiler-events*.json
*speed-measure-plugin*.json
# IDEs and editors
*/.idea
*.project
*.classpath
*.c9/
*.launch
*.settings/
*.sublime-workspace
# IDE - VSCode
*.vscode/*
!*.vscode/settings.json
!*.vscode/tasks.json
!*.vscode/launch.json
!*.vscode/extensions.json
*.history/*
# misc
*/.sass-cache
*/connect.lock
*/coverage
*/libpeerconnection.log
*npm-debug.log
*yarn-error.log
*testem.log
*/typings
# System Files
*.DS_Store
*Thumbs.db
# .Net Env
*Debug*
obj
logs
-nlog.txt
.vs
.vscode
# Python Env
*__pycache__*
*.pyc
*.idea*
*.log
# angular & .net
dist
bin
.angular
appsettings.*.json
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 sh-edraft.de
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+2
View File
@@ -0,0 +1,2 @@
# Web-App template
+140
View File
@@ -0,0 +1,140 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"gswi": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "gswi",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/gswi",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.gswi.json",
"assets": [
"src/assets/images/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss",
"node_modules/primeicons/primeicons.css",
"node_modules/primeng/resources/themes/saga-blue/theme.css",
"node_modules/primeng/resources/primeng.min.css"
],
"scripts": [],
"vendorChunk": true,
"extractLicenses": false,
"buildOptimizer": false,
"sourceMap": true,
"optimization": false,
"namedChunks": true
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
}
},
"defaultConfiguration": ""
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "gswi:build"
},
"configurations": {
"production": {
"browserTarget": "gswi:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "gswi: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",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": []
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"e2e/tsconfig.json"
],
"exclude": [
"**/node_modules/**"
]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "gswi:serve"
},
"configurations": {
"production": {
"devServerTarget": "gswi:serve:production"
}
}
}
}
}
},
"defaultProject": "app",
"cli": {
"analytics": "dcc5d68a-d438-4579-bac1-5bc2d574bd1a"
}
}
+36
View File
@@ -0,0 +1,36 @@
// @ts-check
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter');
/**
* @type { import("protractor").Config }
*/
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
browserName: 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json')
});
jasmine.getEnv().addReporter(new SpecReporter({
spec: {
displayStacktrace: StacktraceOption.PRETTY
}
}));
}
};
+23
View File
@@ -0,0 +1,23 @@
import { AppPage } from './app.po';
import { browser, logging } from 'protractor';
describe('workspace-project App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getTitleText()).toEqual('app app is running!');
});
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry));
});
});
+11
View File
@@ -0,0 +1,11 @@
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo(): Promise<unknown> {
return browser.get(browser.baseUrl) as Promise<unknown>;
}
getTitleText(): Promise<string> {
return element(by.css('app-root .content span')).getText() as Promise<string>;
}
}
+14
View File
@@ -0,0 +1,14 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es2018",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}
+32
View File
@@ -0,0 +1,32 @@
// 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-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, './coverage/app'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};
+23382
View File
File diff suppressed because it is too large Load Diff
+58
View File
@@ -0,0 +1,58 @@
{
"name": "gswi",
"version": "1.0.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",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "~13.2.3",
"@angular/cdk": "^13.2.3",
"@angular/common": "~13.2.3",
"@angular/compiler": "~13.2.3",
"@angular/core": "~13.2.3",
"@angular/forms": "~13.2.3",
"@angular/platform-browser": "~13.2.3",
"@angular/platform-browser-dynamic": "~13.2.3",
"@angular/router": "~13.2.3",
"@aspnet/signalr": "^3.0.0-preview6.19307.2",
"@auth0/angular-jwt": "^5.0.2",
"@ngx-translate/core": "^14.0.0",
"@ngx-translate/http-loader": "^7.0.0",
"primeicons": "^5.0.0",
"primeng": "^13.2.0",
"rxjs": "^7.5.4",
"ts-lint": "^4.5.1",
"tslib": "^2.3.1",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "~13.2.4",
"@angular/cli": "~13.2.4",
"@angular/compiler-cli": "~13.2.3",
"@angular/localize": "^13.2.3",
"@types/jasmine": "^3.10.3",
"@types/jasminewd2": "^2.0.10",
"@types/node": "^17.0.18",
"codelyzer": "^6.0.2",
"jasmine-core": "~4.0.0",
"jasmine-spec-reporter": "~7.0.0",
"karma": "~6.3.16",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~3.0.3",
"karma-jasmine": "~4.0.1",
"karma-jasmine-html-reporter": "^1.7.0",
"protractor": "~7.0.0",
"ts-node": "~10.5.0",
"tslint": "~6.1.0",
"typescript": "~4.5.5"
}
}
+23
View File
@@ -0,0 +1,23 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { NotFoundComponent } from './components/error/not-found/not-found.component';
import { HomeComponent } from './components/home/home.component';
import { AuthRoles } from './models/auth/auth-roles.enum';
import { AuthGuard } from './modules/shared/guards/auth/auth.guard';
const routes: Routes = [
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent, pathMatch: 'full' },
{ 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, { relativeLinkResolution: 'legacy' })],
exports: [RouterModule]
})
export class AppRoutingModule { }
+36
View 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>
View File
+35
View File
@@ -0,0 +1,35 @@
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();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'app'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('app');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement;
expect(compiled.querySelector('.content span').textContent).toContain('app app is running!');
});
});
+24
View File
@@ -0,0 +1,24 @@
import { Component, OnInit } from '@angular/core';
import { AuthService } from './services/auth/auth.service';
import { SignalRService } from './services/signalr/signalr.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,
private signalr: SignalRService,
) { }
ngOnInit(): void {
this.signalr.startSignalR();
this.themeService.loadTheme();
this.themeService.loadMenu();
}
}
+83
View File
@@ -0,0 +1,83 @@
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';
import { HomeComponent } from './components/home/home.component';
@NgModule({
declarations: [
AppComponent,
HeaderComponent,
SidebarComponent,
FooterComponent,
SpinnerComponent,
NotFoundComponent,
HomeComponent,
],
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 {
}
}
@@ -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>
@@ -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();
});
});
@@ -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();
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;
});
}
}
@@ -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">GSWI</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>
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HeaderComponent } from './header.component';
describe('HeaderComponent', () => {
let component: HeaderComponent;
let fixture: ComponentFixture<HeaderComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ HeaderComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(HeaderComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,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 @@
<p>home works!</p>
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HomeComponent } from './home.component';
describe('HomeComponent', () => {
let component: HomeComponent;
let fixture: ComponentFixture<HomeComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ HomeComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
+15
View File
@@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}
@@ -0,0 +1,3 @@
<div class="menu">
<p-menu [model]="menuItems"></p-menu>
</div>
@@ -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();
});
});
@@ -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.home') : '', icon: 'pi pi-th-large', routerLink: 'home' },
];
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>
@@ -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();
});
});
@@ -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 {
}
}
@@ -0,0 +1,7 @@
import { AuthUserDTO } from "./auth-user.dto";
export interface AdminUpdateUserDTO {
authUserDTO: AuthUserDTO;
newAuthUserDTO: AuthUserDTO;
changePassword: boolean;
}
@@ -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
View File
@@ -0,0 +1,4 @@
export enum AuthRoles {
Normal = 0,
Admin = 1
}
@@ -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
View File
@@ -0,0 +1,11 @@
import { AuthRoles } from "./auth-roles.enum";
export interface AuthUserDTO {
id?: number;
firstName: string;
lastName: string;
eMail: string;
password: string;
isConfirmed?: boolean
authRole?: AuthRoles;
}
+3
View 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",
}
@@ -0,0 +1,6 @@
import { AuthUserDTO } from "./auth-user.dto";
export interface ResetPasswordDTO {
id: string;
password: string;
}
+4
View File
@@ -0,0 +1,4 @@
export interface TokenDTO {
token: string;
refreshToken: string;
}
+6
View File
@@ -0,0 +1,6 @@
import { AuthUserDTO } from "./auth-user.dto";
export interface UpdateUserDTO {
authUserDTO: AuthUserDTO;
newAuthUserDTO: AuthUserDTO;
}
+5
View File
@@ -0,0 +1,5 @@
export interface ApiVersion {
Major: string;
Minor: string;
Micro: string;
}
+8
View 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
View 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;
}
@@ -0,0 +1,5 @@
export interface SoftwareVersionDTO {
major: string;
minor: string;
micro: string
}
+19
View 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
View File
@@ -0,0 +1,8 @@
import { ServiceErrorCode } from "./service-error-code.enum";
export class ErrorDTO {
errorCode: ServiceErrorCode;
message: string;
}
@@ -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;
lastName: string;
eMail: string;
authRole?: number;
}
@@ -0,0 +1,6 @@
import { AuthUserDTO } from "../../auth/auth-user.dto";
export interface GetFilteredAuthUsersResultDTO {
users: AuthUserDTO[];
totalCount: number;
}
@@ -0,0 +1,6 @@
export interface SelectCriterion {
pageIndex: number;
pageSize: number;
sortDirection: string;
sortColumn: string;
}
@@ -0,0 +1,7 @@
export interface ConfirmationDialog {
key?: string;
header: string;
message: string;
accept?: () => void;
reject?: () => void;
}
+5
View File
@@ -0,0 +1,5 @@
export interface ToastOptions {
life?: number;
sticky?: boolean;
closable?: boolean;
}
+4
View File
@@ -0,0 +1,4 @@
export interface Theme {
Label: string;
Name: string;
}
+6
View File
@@ -0,0 +1,6 @@
export enum Themes {
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 { }
@@ -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,325 @@
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, 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: null,
firstName: "",
lastName: "",
eMail: "",
password: "",
authRole: AuthRoles.Normal
};
isFirstNameInvalid: boolean = false;
isLastNameInvalid: boolean = false;
isEMailInvalid: boolean = false;
isPasswordInvalid: boolean = false;
loggedInUserEMail: string = "";
filterForm: FormGroup;
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: [null],
lastName: [null],
eMail: [null],
authRole: [null]
});
this.filterForm.valueChanges.pipe(
debounceTime(600)
).subscribe(changes => {
if (changes.firstName) {
this.searchCriterions.firstName = changes.firstName;
} else {
this.searchCriterions.firstName = undefined;
}
if (changes.lastName) {
this.searchCriterions.lastName = changes.lastName;
} else {
this.searchCriterions.lastName = undefined;
}
if (changes.eMail) {
this.searchCriterions.eMail = changes.eMail;
} else {
this.searchCriterions.eMail = undefined;
}
if (changes.authRole != null) {
this.searchCriterions.authRole = changes.authRole;
} else {
this.searchCriterions.authRole = undefined;
}
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;
if (event.first != null && event.rows != null)
this.searchCriterions.pageIndex = event.first / event.rows;
this.searchCriterions.sortColumn = event.sortField;
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 + "" : undefined;
this.searchCriterions.lastName = event.filters.lastName ? event.filters.lastName + "" : undefined;
this.searchCriterions.eMail = event.filters.eMail ? event.filters.eMail + "" : undefined;
this.searchCriterions.authRole = event.filters.authRole ? +event.filters.authRole : undefined;
}
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(function (u) { return u.id; })) + 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: null,
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();
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 { }
@@ -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 { }
@@ -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
View 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;
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(['/home']);
}
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(['/home']); }, 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();
});
});
@@ -0,0 +1,111 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder, 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;
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(['/home']);
}
this.initLoginForm();
this.resetStateFlags();
this.spinnerService.hideSpinner();
});
}
resetStateFlags(): void {
this.authUserAtrErrors = new AuthUserAtrErrors();
}
initLoginForm(): void {
this.loginForm = this.formBuilder.group({
email: [null, [Validators.required, Validators.email]],
password: [null, [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,
password: this.loginForm.value.password
};
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(['/home']);
});
}
}
@@ -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,137 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder, 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;
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(['/home']);
}
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: [null, Validators.required],
lastName: [null, Validators.required],
email: [null, [Validators.required, Validators.email]],
emailRepeat: [null, [Validators.required, Validators.email]],
password: [null, [Validators.required, Validators.minLength(8)]],
passwordRepeat: [null, [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,
lastName: this.loginForm.value.lastName,
eMail: this.loginForm.value.email,
password: this.loginForm.value.password
};
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();
});
});
@@ -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(['/home']);
return false;
}
}
return true;
}
}
@@ -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();
}
}
@@ -0,0 +1,8 @@
import { BoolPipe } from './bool.pipe';
describe('BoolPipe', () => {
it('create an instance', () => {
const pipe = new BoolPipe();
expect(pipe).toBeTruthy();
});
});
+21
View 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');
}
}
@@ -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
View 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 { }

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