renamed folders

This commit is contained in:
2022-02-20 13:57:55 +01:00
parent 0c3a31513f
commit 2095c51ec2
232 changed files with 0 additions and 0 deletions

View File

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

View File

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

27
frontend/app/README.md Normal file
View File

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

140
frontend/app/angular.json Normal file
View File

@@ -0,0 +1,140 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"app": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/app",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.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": "app:build"
},
"configurations": {
"production": {
"browserTarget": "app:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "app: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": "app:serve"
},
"configurations": {
"production": {
"devServerTarget": "app:serve:production"
}
}
}
}
}
},
"defaultProject": "app",
"cli": {
"analytics": "dcc5d68a-d438-4579-bac1-5bc2d574bd1a"
}
}

View File

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

View File

@@ -0,0 +1,23 @@
import { AppPage } from './app.po';
import { browser, logging } from 'protractor';
describe('workspace-project App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getTitleText()).toEqual('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));
});
});

View File

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

View File

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

View File

@@ -0,0 +1,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
frontend/app/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

58
frontend/app/package.json Normal file
View File

@@ -0,0 +1,58 @@
{
"name": "app",
"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"
}
}

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 { }

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

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!');
});
});

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();
}
}

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);
}

View File

@@ -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>

View File

@@ -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();
});
});

View File

@@ -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 {
}
}

View 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>

View 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();
});
});

View 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();
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;
});
}
}

View 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">App</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>

View File

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

View File

@@ -0,0 +1,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);
});
}
}

View File

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

View File

@@ -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();
});
});

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 {
}
}

View File

@@ -0,0 +1,3 @@
<div class="menu">
<p-menu [model]="menuItems"></p-menu>
</div>

View 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();
});
});

View 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.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);
}
}

View File

@@ -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>

View 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();
});
});

View 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 {
}
}

View File

@@ -0,0 +1,7 @@
import { AuthUserDTO } from "./auth-user.dto";
export interface AdminUpdateUserDTO {
authUserDTO: AuthUserDTO;
newAuthUserDTO: AuthUserDTO;
changePassword: boolean;
}

View 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"
}

View File

@@ -0,0 +1,4 @@
export enum AuthRoles {
Normal = 0,
Admin = 1
}

View 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;
}

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;
}

View File

@@ -0,0 +1,3 @@
export interface EMailStringDTO {
email: string;
}

View File

@@ -0,0 +1,4 @@
export enum RegisterErrorMessages {
InvalidEMail = "Invalid E-Mail",
UserAlreadyExists = "User already exists",
}

View File

@@ -0,0 +1,6 @@
import { AuthUserDTO } from "./auth-user.dto";
export interface ResetPasswordDTO {
id: string;
password: string;
}

View File

@@ -0,0 +1,4 @@
export interface TokenDTO {
token: string;
refreshToken: string;
}

View File

@@ -0,0 +1,6 @@
import { AuthUserDTO } from "./auth-user.dto";
export interface UpdateUserDTO {
authUserDTO: AuthUserDTO;
newAuthUserDTO: AuthUserDTO;
}

View File

@@ -0,0 +1,5 @@
export interface ApiVersion {
Major: string;
Minor: string;
Micro: string;
}

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[];
}

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;
}

View File

@@ -0,0 +1,5 @@
export interface SoftwareVersionDTO {
major: string;
minor: string;
micro: string
}

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}`;
}
}

View File

@@ -0,0 +1,8 @@
import { ServiceErrorCode } from "./service-error-code.enum";
export class ErrorDTO {
errorCode: ServiceErrorCode;
message: string;
}

View 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
}

View File

@@ -0,0 +1,8 @@
import { SelectCriterion } from "../select-criterion.model";
export interface AuthUserSelectCriterion extends SelectCriterion {
firstName: string;
lastName: string;
eMail: string;
authRole?: number;
}

View File

@@ -0,0 +1,6 @@
import { AuthUserDTO } from "../../auth/auth-user.dto";
export interface GetFilteredAuthUsersResultDTO {
users: AuthUserDTO[];
totalCount: number;
}

View File

@@ -0,0 +1,6 @@
export interface SelectCriterion {
pageIndex: number;
pageSize: number;
sortDirection: string;
sortColumn: string;
}

View File

@@ -0,0 +1,7 @@
export interface ConfirmationDialog {
key?: string;
header: string;
message: string;
accept?: () => void;
reject?: () => void;
}

View File

@@ -0,0 +1,5 @@
export interface ToastOptions {
life?: number;
sticky?: boolean;
closable?: boolean;
}

View File

@@ -0,0 +1,4 @@
export interface Theme {
Label: string;
Name: string;
}

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",
}

View File

@@ -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 { }

View 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 { }

View File

@@ -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>

View File

@@ -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();
});
});

View File

@@ -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();
}
}

View File

@@ -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>

View File

@@ -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();
});
});

View File

@@ -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();
});
}
}

View File

@@ -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 { }

View 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 { }

View 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 { }

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 { }

View File

@@ -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>

View File

@@ -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();
});
});

View File

@@ -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']);
});
}
}

View File

@@ -0,0 +1,50 @@
<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">
<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"></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>

View File

@@ -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();
});
});

View File

@@ -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']);
});
}
}

View File

@@ -0,0 +1,81 @@
<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"></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"></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>

View File

@@ -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();
});
});

View File

@@ -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']);
});
}
}
}

View File

@@ -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();
});
});

View 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(['/home']);
return false;
}
}
return true;
}
}

View 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();
}
}

View File

@@ -0,0 +1,8 @@
import { BoolPipe } from './bool.pipe';
describe('BoolPipe', () => {
it('create an instance', () => {
const pipe = new BoolPipe();
expect(pipe).toBeTruthy();
});
});

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');
}
}

View 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;
}
}

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 { }

View File

@@ -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 { }

View File

@@ -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 { }

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