Added angular frontend #70
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
<div class="content-row">
|
||||
<div class="content-column">
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-wrapper">
|
||||
<div class="content-header">
|
||||
<h2>
|
||||
{{'common.error' | translate}}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
{{'common.404' | translate}}
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { NotFoundComponent } from './not-found.component';
|
||||
|
||||
describe('NotFoundComponent', () => {
|
||||
let component: NotFoundComponent;
|
||||
let fixture: ComponentFixture<NotFoundComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ NotFoundComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(NotFoundComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@@ -0,0 +1,18 @@
|
||||
import { Location } from '@angular/common';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-not-found',
|
||||
templateUrl: './not-found.component.html',
|
||||
styleUrls: ['./not-found.component.scss']
|
||||
})
|
||||
export class NotFoundComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
public location: Location
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
26
kdb-web/src/app/components/footer/footer.component.html
Normal file
26
kdb-web/src/app/components/footer/footer.component.html
Normal file
@@ -0,0 +1,26 @@
|
||||
<footer>
|
||||
<div class="left">
|
||||
<div class="frontend-version">
|
||||
<span>
|
||||
{{'footer.frontend' | translate}}:
|
||||
</span>
|
||||
<span>
|
||||
{{frontendVersion.getVersionString()}}
|
||||
</span>
|
||||
</div>
|
||||
<span class="version-divider">
|
||||
|
|
||||
</span>
|
||||
<div class="backend-version">
|
||||
<span>
|
||||
{{'footer.backend' | translate}}:
|
||||
</span>
|
||||
<span>
|
||||
{{backendVersion.getVersionString()}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<a href="https://www.sh-edraft.de/Impressum" target="_blank">{{'footer.imprint' | translate}}</a>
|
||||
</div>
|
||||
</footer>
|
25
kdb-web/src/app/components/footer/footer.component.spec.ts
Normal file
25
kdb-web/src/app/components/footer/footer.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { FooterComponent } from './footer.component';
|
||||
|
||||
describe('FooterComponent', () => {
|
||||
let component: FooterComponent;
|
||||
let fixture: ComponentFixture<FooterComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ FooterComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FooterComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
46
kdb-web/src/app/components/footer/footer.component.ts
Normal file
46
kdb-web/src/app/components/footer/footer.component.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { type } from 'os';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { SoftwareVersion } from 'src/app/models/config/software-version';
|
||||
import { GuiService } from 'src/app/services/gui/gui.service';
|
||||
import { SettingsService } from 'src/app/services/settings/settings.service';
|
||||
import { SpinnerService } from 'src/app/services/spinner/spinner.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-footer',
|
||||
templateUrl: './footer.component.html',
|
||||
styleUrls: ['./footer.component.scss']
|
||||
})
|
||||
export class FooterComponent implements OnInit {
|
||||
|
||||
|
||||
frontendVersion!: SoftwareVersion;
|
||||
backendVersion!: SoftwareVersion;
|
||||
|
||||
constructor(
|
||||
private settings: SettingsService,
|
||||
private guiService: GuiService,
|
||||
private spinnerService: SpinnerService
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.frontendVersion = this.settings.getWebVersion() ?? new SoftwareVersion('0', '0', '0');
|
||||
|
||||
this.spinnerService.showSpinner();
|
||||
this.guiService.getApiVersion()
|
||||
.pipe(catchError(err => {
|
||||
this.spinnerService.hideSpinner();
|
||||
throw err;
|
||||
}))
|
||||
.subscribe(version => {
|
||||
this.spinnerService.hideSpinner();
|
||||
const webVersion = new SoftwareVersion(
|
||||
version.major,
|
||||
version.minor,
|
||||
version.micro
|
||||
);
|
||||
this.backendVersion = webVersion;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
25
kdb-web/src/app/components/header/header.component.html
Normal file
25
kdb-web/src/app/components/header/header.component.html
Normal file
@@ -0,0 +1,25 @@
|
||||
<header>
|
||||
<div class="logo-button-wrapper">
|
||||
<button pButton type="button" icon="pi pi-bars" class="btn p-button-text" (click)="toggleMenu()"></button>
|
||||
</div>
|
||||
<div class="logo">
|
||||
<h1 class="app-name">{{'header.header' | translate}}</h1>
|
||||
</div>
|
||||
<div class="header-menu logo-button-wrapper">
|
||||
<div class="logo-button-wrapper">
|
||||
<button type="button" pButton icon="pi pi-globe" class="btn icon-btn p-button-text"
|
||||
(click)="langMenu.toggle($event)"></button>
|
||||
<p-menu #langMenu [popup]="true" [model]="langList" class="lang-menu"></p-menu>
|
||||
</div>
|
||||
<div class="logo-button-wrapper">
|
||||
<button type="button" pButton icon="pi pi-palette" class="btn icon-btn p-button-text"
|
||||
(click)="themeMenu.toggle($event)"></button>
|
||||
<p-menu #themeMenu [popup]="true" [model]="themeList" class="theme-menu"></p-menu>
|
||||
</div>
|
||||
<div class="logo-button-wrapper">
|
||||
<button type="button" pButton icon="pi pi-user" class="btn icon-btn p-button-text"
|
||||
(click)="userMenu.toggle($event)"></button>
|
||||
<p-menu #userMenu [popup]="true" [model]="userMenuList" class="user-menu"></p-menu>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
25
kdb-web/src/app/components/header/header.component.spec.ts
Normal file
25
kdb-web/src/app/components/header/header.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { HeaderComponent } from './header.component';
|
||||
|
||||
describe('HeaderComponent', () => {
|
||||
let component: HeaderComponent;
|
||||
let fixture: ComponentFixture<HeaderComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ HeaderComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(HeaderComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
168
kdb-web/src/app/components/header/header.component.ts
Normal file
168
kdb-web/src/app/components/header/header.component.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { LangChangeEvent, TranslateService } from '@ngx-translate/core';
|
||||
import { MenuItem, PrimeNGConfig } from 'primeng/api';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { AuthService } from 'src/app/services/auth/auth.service';
|
||||
import { SettingsService } from 'src/app/services/settings/settings.service';
|
||||
import { SpinnerService } from 'src/app/services/spinner/spinner.service';
|
||||
import { ThemeService } from 'src/app/services/theme/theme.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-header',
|
||||
templateUrl: './header.component.html',
|
||||
styleUrls: ['./header.component.scss']
|
||||
})
|
||||
export class HeaderComponent implements OnInit {
|
||||
@Output() isSidebarFullWidth: EventEmitter<boolean> = new EventEmitter<boolean>(this.themeService.isSidebarOpen);
|
||||
|
||||
langList: MenuItem[] = [];
|
||||
themeList: MenuItem[] = [];
|
||||
userMenuList!: MenuItem[];
|
||||
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
private router: Router,
|
||||
private themeService: ThemeService,
|
||||
private spinnerService: SpinnerService,
|
||||
private settings: SettingsService,
|
||||
private translateService: TranslateService,
|
||||
private config: PrimeNGConfig
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.translateService.setDefaultLang('en');
|
||||
this.initMenuLists();
|
||||
this.loadLang();
|
||||
this.translateService.onLangChange.subscribe((event: LangChangeEvent) => {
|
||||
this.initUserMenuList();
|
||||
});
|
||||
}
|
||||
|
||||
initUserMenuList(): void {
|
||||
this.spinnerService.showSpinner();
|
||||
const mail = this.authService.getEMailFromDecodedToken(this.authService.getDecodedToken());
|
||||
this.authService.getUserByEMail(mail ?? '')
|
||||
.pipe(catchError(err => {
|
||||
this.spinnerService.hideSpinner();
|
||||
this.authService.logout();
|
||||
throw err;
|
||||
}))
|
||||
.subscribe(user => {
|
||||
this.spinnerService.hideSpinner();
|
||||
|
||||
|
||||
this.userMenuList = [
|
||||
{
|
||||
label: `${user.firstName} ${user.lastName}`,
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
separator: true
|
||||
},
|
||||
{
|
||||
label: this.translateService.instant('header.change_password'), command: () => {
|
||||
this.changePassword();
|
||||
},
|
||||
icon: 'pi pi-key'
|
||||
},
|
||||
{
|
||||
label: this.translateService.instant('header.settings'), command: () => {
|
||||
this.userSettings();
|
||||
},
|
||||
icon: 'pi pi-cog'
|
||||
},
|
||||
{
|
||||
label: this.translateService.instant('header.logout'), command: () => {
|
||||
this.logout();
|
||||
},
|
||||
icon: 'pi pi-sign-out'
|
||||
}
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
initMenuLists(): void {
|
||||
this.langList = [
|
||||
{
|
||||
label: 'English', command: () => {
|
||||
this.translate('en');
|
||||
this.setLang('en');
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Deutsch', command: () => {
|
||||
this.translate('de');
|
||||
this.setLang('de');
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
this.initUserMenuList();
|
||||
|
||||
this.settings.getThemes()?.forEach(theme => {
|
||||
this.themeList.push({
|
||||
label: theme.Label,
|
||||
command: () => {
|
||||
this.changeTheme(theme.Name);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
toggleMenu(): void {
|
||||
this.themeService.setIsMenuOpen(!this.themeService.isSidebarOpen);
|
||||
this.isSidebarFullWidth.emit(this.themeService.isSidebarOpen);
|
||||
}
|
||||
|
||||
changeTheme(name: string): void {
|
||||
this.themeService.setTheme(name);
|
||||
}
|
||||
|
||||
changePassword(): void {
|
||||
this.router.navigate(['/change-password']);
|
||||
}
|
||||
|
||||
userSettings(): void {
|
||||
this.router.navigate(['/user-settings']);
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
this.authService.logout();
|
||||
}
|
||||
|
||||
translate(lang: string) {
|
||||
this.translateService.use(lang);
|
||||
this.translateService.get('primeng').subscribe(res => this.config.setTranslation(res));
|
||||
}
|
||||
|
||||
loadLang(): void {
|
||||
const token = this.authService.getDecodedToken();
|
||||
const mail = this.authService.getEMailFromDecodedToken(token);
|
||||
|
||||
if (!mail) {
|
||||
this.translate('en');
|
||||
return;
|
||||
}
|
||||
|
||||
let lang = localStorage.getItem(`${mail}_lang`);
|
||||
if (!lang) {
|
||||
lang = 'en';
|
||||
this.setLang(lang);
|
||||
}
|
||||
this.translate(lang);
|
||||
}
|
||||
|
||||
setLang(lang: string): void {
|
||||
this.authService.isUserLoggedInAsync().then(result => {
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
const token = this.authService.getDecodedToken();
|
||||
const mail = this.authService.getEMailFromDecodedToken(token);
|
||||
localStorage.setItem(`${mail}_lang`, lang);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
<div class="menu">
|
||||
<p-menu [model]="menuItems"></p-menu>
|
||||
</div>
|
25
kdb-web/src/app/components/sidebar/sidebar.component.spec.ts
Normal file
25
kdb-web/src/app/components/sidebar/sidebar.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SidebarComponent } from './sidebar.component';
|
||||
|
||||
describe('SidebarComponent', () => {
|
||||
let component: SidebarComponent;
|
||||
let fixture: ComponentFixture<SidebarComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ SidebarComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SidebarComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
52
kdb-web/src/app/components/sidebar/sidebar.component.ts
Normal file
52
kdb-web/src/app/components/sidebar/sidebar.component.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
|
||||
import { LangChangeEvent, TranslateService } from '@ngx-translate/core';
|
||||
import { MenuItem } from 'primeng/api';
|
||||
import { AuthRoles } from 'src/app/models/auth/auth-roles.enum';
|
||||
import { AuthService } from 'src/app/services/auth/auth.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-sidebar',
|
||||
templateUrl: './sidebar.component.html',
|
||||
styleUrls: ['./sidebar.component.scss']
|
||||
})
|
||||
export class SidebarComponent implements OnInit, OnChanges {
|
||||
|
||||
@Input() isSidebarOpen!: boolean;
|
||||
|
||||
menuItems!: MenuItem[];
|
||||
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
private translateService: TranslateService
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.translateService.onLangChange.subscribe(async (event: LangChangeEvent) => {
|
||||
await this.setMenu(this.isSidebarOpen);
|
||||
});
|
||||
}
|
||||
|
||||
async setMenu(isSidebarOpen: boolean) {
|
||||
this.menuItems = [];
|
||||
this.menuItems = [
|
||||
{ label: isSidebarOpen ? this.translateService.instant('sidebar.dashboard') : '', icon: 'pi pi-th-large', routerLink: 'dashboard' },
|
||||
];
|
||||
|
||||
if (await this.authService.hasUserPermission(AuthRoles.Admin)) {
|
||||
this.menuItems.push(
|
||||
{ separator: true },
|
||||
{ label: isSidebarOpen ? this.translateService.instant('sidebar.config') : '', icon: 'pi pi-cog', routerLink: '/admin/settings' },
|
||||
{ label: isSidebarOpen ? this.translateService.instant('sidebar.auth_user_list') : '', icon: 'pi pi-user-edit', routerLink: '/admin/users' },
|
||||
);
|
||||
this.menuItems = this.menuItems.slice();
|
||||
}
|
||||
}
|
||||
|
||||
async ngOnChanges(changes: SimpleChanges): Promise<void> {
|
||||
if (!changes)
|
||||
return;
|
||||
|
||||
await this.setMenu(changes['isSidebarOpen'].currentValue);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
<ng-container *ngIf="spinnerService.showSpinnerState">
|
||||
<div class="spinner-component-wrapper">
|
||||
<div class="spinner-wrapper">
|
||||
<p-progressSpinner styleClass="custom-spinner" animationDuration=".8s"></p-progressSpinner>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
25
kdb-web/src/app/components/spinner/spinner.component.spec.ts
Normal file
25
kdb-web/src/app/components/spinner/spinner.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SpinnerComponent } from './spinner.component';
|
||||
|
||||
describe('SpinnerComponent', () => {
|
||||
let component: SpinnerComponent;
|
||||
let fixture: ComponentFixture<SpinnerComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ SpinnerComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SpinnerComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
18
kdb-web/src/app/components/spinner/spinner.component.ts
Normal file
18
kdb-web/src/app/components/spinner/spinner.component.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { SpinnerService } from 'src/app/services/spinner/spinner.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-spinner',
|
||||
templateUrl: './spinner.component.html',
|
||||
styleUrls: ['./spinner.component.scss']
|
||||
})
|
||||
export class SpinnerComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
public spinnerService: SpinnerService
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user