Moved folders #405

This commit is contained in:
2023-10-13 17:10:00 +02:00
parent eb32bec43c
commit f435d3dd48
807 changed files with 3801 additions and 1297 deletions

16
web/.browserslistrc Normal file
View File

@@ -0,0 +1,16 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support
# You can see what browsers were selected by your queries by running:
# npx browserslist
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR

16
web/.editorconfig Normal file
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

46
web/.gitignore vendored Normal file
View File

@@ -0,0 +1,46 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
.vscode
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db
ignore
ignore/*

4
web/.prettierrc Normal file
View File

@@ -0,0 +1,4 @@
{
"tabWidth": 4,
"useTabs": false
}

27
web/README.md Normal file
View File

@@ -0,0 +1,27 @@
# KdbWeb
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.0.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.

122
web/angular.json Normal file
View File

@@ -0,0 +1,122 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"kdb-web": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
},
"@schematics/angular:application": {
"strict": true
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/kdb-web",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": ["src/favicon.ico", "src/assets"],
"styles": [
"src/styles.scss",
"node_modules/primeicons/primeicons.css",
"node_modules/primeng/resources/themes/lara-light-blue/theme.css",
"node_modules/primeng/resources/primeng.min.css"
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "8mb",
"maximumError": "16mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputHashing": "all"
},
"staging": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.staging.ts"
}
]
},
"development": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.development.ts"
}
],
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "kdb-web:build:production"
},
"development": {
"browserTarget": "kdb-web:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "kdb-web:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"inlineStyleLanguage": "scss",
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.scss"],
"scripts": []
}
}
}
}
},
"cli": {
"analytics": false
}
}

7
web/dockerfile Normal file
View File

@@ -0,0 +1,7 @@
FROM nginx:alpine
COPY nginx.conf.template /etc/nginx/conf.d/nginx.conf.template
RUN rm -rf /usr/share/nginx/html/*
COPY ./dist/kdb-web/ /usr/share/nginx/html
RUN apk update
RUN apk add bash
CMD /bin/bash -c "envsubst '\$BOT_CONTAINER_NAME' < /etc/nginx/conf.d/nginx.conf.template > /etc/nginx/nginx.conf; nginx -g 'daemon off;'"

44
web/karma.conf.js Normal file
View File

@@ -0,0 +1,44 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true // removes the duplicated traces
},
coverageReporter: {
dir: require('path').join(__dirname, './coverage/kdb-web'),
subdir: '.',
reporters: [
{ type: 'html' },
{ type: 'text-summary' }
]
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

30
web/nginx.conf.template Normal file
View File

@@ -0,0 +1,30 @@
events{}
http {
include /etc/nginx/mime.types;
server {
listen 80;
sendfile on;
default_type application/octet-stream;
gzip on;
gzip_http_version 1.1;
gzip_disable “MSIE [16]\.”;
gzip_min_length 256;
gzip_vary on;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
gzip_comp_level 9;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html =404;
}
location /api {
proxy_pass http://${BOT_CONTAINER_NAME};
}
}
}

11734
web/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

55
web/package.json Normal file
View File

@@ -0,0 +1,55 @@
{
"name": "kdb-web",
"version": "1.2.0",
"scripts": {
"ng": "ng",
"update-version": "ts-node update-version.ts",
"prestart": "npm run update-version",
"start": "ng serve",
"prebuild": "npm run update-version",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"predocker-build": "npm run update-version",
"docker-build": "export VERSION=$npm_package_version; ng build; docker build -t sh-edraft.de/kdb-web:$VERSION .",
"docker-build-dev": "export VERSION=$npm_package_version; ng build --configuration development; docker build -t sh-edraft.de/kdb-web:$VERSION .",
"docker-build-stage": "export VERSION=$npm_package_version; ng build --configuration staging; docker build -t sh-edraft.de/kdb-web:$VERSION ."
},
"private": true,
"dependencies": {
"@angular/animations": "^15.1.4",
"@angular/common": "^15.1.4",
"@angular/compiler": "^15.1.4",
"@angular/core": "^15.1.4",
"@angular/forms": "^15.1.4",
"@angular/platform-browser": "^15.1.4",
"@angular/platform-browser-dynamic": "^15.1.4",
"@angular/router": "^15.1.4",
"@auth0/angular-jwt": "^5.1.0",
"@microsoft/signalr": "^6.0.9",
"@ngx-translate/core": "^14.0.0",
"@ngx-translate/http-loader": "^7.0.0",
"@types/socket.io-client": "^3.0.0",
"moment": "^2.29.4",
"primeicons": "^6.0.1",
"primeng": "^15.2.0",
"rxjs": "~7.5.0",
"socket.io-client": "^4.5.3",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "^15.1.5",
"@angular/cli": "~15.1.5",
"@angular/compiler-cli": "^15.1.4",
"@types/jasmine": "~4.0.0",
"@types/node": "^18.11.9",
"jasmine-core": "~4.1.0",
"karma": "~6.3.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.0.0",
"karma-jasmine-html-reporter": "~1.7.0",
"tslib": "^2.4.1",
"typescript": "~4.9.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 { AuthRoles } from './models/auth/auth-roles.enum';
import { AuthGuard } from './modules/shared/guards/auth/auth.guard';
const routes: Routes = [
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' },
{ path: 'dashboard', loadChildren: () => import('./modules/view/dashboard/dashboard.module').then(m => m.DashboardModule), canActivate: [AuthGuard] },
{ path: 'server/:serverId', loadChildren: () => import('./modules/view/server/server.module').then(m => m.ServerModule), canActivate: [AuthGuard] },
{ path: 'change-password', loadChildren: () => import('./modules/view/change-password/change-password.module').then(m => m.ChangePasswordModule), canActivate: [AuthGuard] },
{ path: 'user-settings', loadChildren: () => import('./modules/view/user-settings/user-settings.module').then(m => m.UserSettingsModule), canActivate: [AuthGuard] },
{ path: 'auth', loadChildren: () => import('./modules/auth/auth.module').then(m => m.AuthModule) },
{ path: 'admin/settings', loadChildren: () => import('./modules/admin/settings/settings.module').then(m => m.SettingsModule), canActivate: [AuthGuard], data: { role: AuthRoles.Admin } },
{ path: 'admin/users', loadChildren: () => import('./modules/admin/auth-users/auth-user.module').then(m => m.AuthUserModule), canActivate: [AuthGuard], data: { role: AuthRoles.Admin } },
{ path: '404', component: NotFoundComponent}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

View File

@@ -0,0 +1,37 @@
<main [class]="themeName">
<ng-container *ngIf="isLoggedIn; else login">
<app-header></app-header>
<section class="app">
<div>
<section [ngClass]="{'sidebar-open': isSidebarOpen, 'sidebar-closed': !isSidebarOpen}">
<app-sidebar></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,16 @@
import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
AppComponent
],
}).compileComponents();
});
});

View File

@@ -0,0 +1,83 @@
import { Component, OnDestroy, OnInit } from "@angular/core";
import { TranslateService } from "@ngx-translate/core";
import { PrimeNGConfig } from "primeng/api";
import { AuthService } from "./services/auth/auth.service";
import { SocketService } from "./services/socket/socket.service";
import { ThemeService } from "./services/theme/theme.service";
import { Subject } from "rxjs";
import { Themes } from "./models/view/themes.enum";
import { takeUntil } from "rxjs/operators";
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.scss"]
})
export class AppComponent implements OnInit, OnDestroy {
themeName: string = Themes.Default;
isSidebarOpen: boolean = true;
isLoggedIn: boolean = false;
private unsubscriber = new Subject<void>();
constructor(
private authService: AuthService,
private themeService: ThemeService,
private socket: SocketService,
private translateService: TranslateService,
private config: PrimeNGConfig,
) {
this.themeService.isSidebarOpen$.pipe(
takeUntil(this.unsubscriber)
).subscribe(value => {
this.isSidebarOpen = value;
});
this.themeService.themeName$.pipe(
takeUntil(this.unsubscriber)
).subscribe(value => {
this.themeName = value;
});
this.authService.isLoggedIn$.pipe(
takeUntil(this.unsubscriber)
).subscribe(value => {
this.isLoggedIn = value;
});
}
ngOnInit(): void {
this.translateService.setDefaultLang("en");
this.themeService.loadTheme();
this.socket.startSocket();
}
public ngOnDestroy(): void {
this.unsubscriber.next();
this.unsubscriber.complete();
}
loadLang(): void {
let lang = localStorage.getItem(`default_lang`);
if (!lang) {
lang = "en";
this.setLang(lang);
}
this.translate(lang);
}
setLang(lang: string): void {
localStorage.setItem(`default_lang`, lang);
}
translate(lang: string) {
this.translateService.use(lang);
this.translateService.get("primeng").subscribe(res => this.config.setTranslation(res));
}
setSideWidth($event: any): void {
this.themeService.setSideWidth($event);
}
}

83
web/src/app/app.module.ts Normal file
View File

@@ -0,0 +1,83 @@
import { HttpClient, HttpClientModule } 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 { NotFoundComponent } from "./components/error/not-found/not-found.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";
@NgModule({
declarations: [
AppComponent,
HeaderComponent,
SidebarComponent,
FooterComponent,
SpinnerComponent,
NotFoundComponent,
],
imports: [
BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
SharedModule,
JwtModule.forRoot({
config: {
tokenGetter,
allowedDomains: ['localhost:8044', 'localhost:8041', 'localhost:5000'],
disallowedRoutes: []
}
}),
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
}),
HttpClientModule
],
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,7 @@
import { ComponentWithTable } from './component-with-table';
describe('ComponentWithTable', () => {
it('should create an instance', () => {
expect(new ComponentWithTable()).toBeTruthy();
});
});

View File

@@ -0,0 +1,44 @@
export interface Column {
key: string;
name: string;
}
export class ComponentWithTable {
private _hiddenColumns: Column[] = [];
set hiddenColumns(value: Column[]) {
this._hiddenColumns = value;
localStorage.setItem("hiddenColumns", JSON.stringify(value));
}
get hiddenColumns(): Column[] {
return this._hiddenColumns;
}
public name: string = "";
public columns: Column[] = [];
constructor(
name: string,
columns: string[]
) {
this.name = name;
this.columns = columns.map(column => {
return { key: this.getKey(column), name: column };
});
let hiddenColumns = localStorage.getItem("hiddenColumns");
if (!hiddenColumns) {
localStorage.setItem("hiddenColumns", JSON.stringify([{}]));
hiddenColumns = localStorage.getItem("hiddenColumns") ?? JSON.stringify([{}]);
}
this._hiddenColumns = JSON.parse(hiddenColumns);
}
private getKey(column: string): string {
return `${this.name}_${column}`;
}
public isColumnVisible(column: string): boolean {
return !this._hiddenColumns.map(column => column.key).includes(this.getKey(column));
}
}

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,28 @@
<footer>
<div class="left">
<div class="frontend-version">
<span>
{{'footer.frontend' | translate}}:
</span>
<span>
{{frontendVersion.getVersionString()}}
</span>
</div>
<span class="divider">
|
</span>
<div class="backend-version">
<span>
{{'footer.backend' | translate}}:
</span>
<span>
{{backendVersion.getVersionString()}}
</span>
</div>
</div>
<div class="right">
<button pButton label="{{'footer.privacy' | translate}}" class="btn text-btn" (click)="navigateToPrivacy()"></button>
<span class="divider"> | </span>
<button pButton label="{{'footer.imprint' | translate}}" class="btn text-btn" (click)="navigateToImprint()"></button>
</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,56 @@
import { Component, OnInit } from "@angular/core";
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";
import { throwError } from "rxjs";
@Component({
selector: 'app-footer',
templateUrl: './footer.component.html',
styleUrls: ['./footer.component.scss']
})
export class FooterComponent implements OnInit {
public frontendVersion: SoftwareVersion = new SoftwareVersion("0", "0", "0");
public backendVersion: SoftwareVersion = new SoftwareVersion("0", "0", "0");
public privacy: string = "";
public imprint: string = "";
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(error => {
this.spinnerService.hideSpinner();
return throwError(() => error);
}))
.subscribe(version => {
this.spinnerService.hideSpinner();
this.backendVersion = new SoftwareVersion(
version.major,
version.minor,
version.micro
);
});
}
public navigateToPrivacy(): void {
window.open(this.settings.getPrivacyURL(), "_blank");
}
public navigateToImprint(): void {
window.open(this.settings.getImprintURL(), "_blank");
}
}

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">{{'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>

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,169 @@
import { Component, OnInit } 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";
import { throwError } from "rxjs";
import { SidebarService } from "../../services/sidebar/sidebar.service";
@Component({
selector: "app-header",
templateUrl: "./header.component.html",
styleUrls: ["./header.component.scss"]
})
export class HeaderComponent implements OnInit {
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,
private sidebarService: SidebarService,
) {
}
ngOnInit(): void {
this.initMenuLists();
this.loadLang();
this.translateService.onLangChange.subscribe((event: LangChangeEvent) => {
this.initUserMenuList();
});
}
initUserMenuList(): void {
if (!this.authService.isLoggedIn$.value) {
return;
}
this.spinnerService.showSpinner();
const mail = this.authService.getEMailFromDecodedToken(this.authService.getDecodedToken());
this.authService.getUserByEMail(mail ?? "")
.pipe(catchError(error => {
this.spinnerService.hideSpinner();
this.authService.logout();
return throwError(() => error);
}))
.subscribe(user => {
this.spinnerService.hideSpinner();
this.sidebarService.setMenu(true);
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);
}
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 {
if (!this.authService.isLoggedIn$.value) {
return;
}
const token = this.authService.getDecodedToken();
const mail = this.authService.getEMailFromDecodedToken(token);
localStorage.setItem(`${mail}_lang`, lang);
}
}

View File

@@ -0,0 +1,3 @@
<div class="menu">
<p-panelMenu [model]="menuItems"></p-panelMenu>
</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,42 @@
import { Component, OnDestroy, OnInit } from "@angular/core";
import { TranslateService } from "@ngx-translate/core";
import { MenuItem } from "primeng/api";
import { AuthService } from "src/app/services/auth/auth.service";
import { ThemeService } from "src/app/services/theme/theme.service";
import { SidebarService } from "../../services/sidebar/sidebar.service";
import { Subject } from "rxjs";
import { takeUntil } from "rxjs/operators";
@Component({
selector: "app-sidebar",
templateUrl: "./sidebar.component.html",
styleUrls: ["./sidebar.component.scss"]
})
export class SidebarComponent implements OnInit, OnDestroy {
menuItems: MenuItem[]= [];
private unsubscriber = new Subject<void>();
constructor(
private authService: AuthService,
private translateService: TranslateService,
private themeService: ThemeService,
private sidebar: SidebarService
) {
this.sidebar.menuItems$.pipe(
takeUntil(this.unsubscriber)
).subscribe(value => {
this.menuItems = value;
});
}
ngOnInit(): void {
this.themeService.loadMenu();
}
ngOnDestroy() {
this.unsubscriber.next();
this.unsubscriber.complete();
}
}

View File

@@ -0,0 +1,7 @@
<ng-container *ngIf="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,24 @@
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 {
showSpinnerState: boolean = false;
constructor(
public spinnerService: SpinnerService
) {
this.spinnerService.showSpinnerState$.subscribe(value => {
this.showSpinnerState = value;
});
}
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,33 @@
import {AuthRoles} from "./auth-roles.enum";
export interface AuthUserDTO {
id?: number;
firstName: string | null;
lastName: string | null;
email: string | null;
password: string | null;
isConfirmed?: boolean;
authRole?: AuthRoles;
users?: UserDTO[];
createdAt?: string;
modifiedAt?: string;
}
export interface UserDTO {
id: number;
discordId: number;
xp: number;
server: number;
createdAt: string;
modifiedAt: string;
isTechnician: boolean;
isAdmin: boolean;
isModerator: boolean;
}
export enum MemberRoles {
Moderator = 0,
Admin = 1,
Technician = 2,
}

View File

@@ -0,0 +1,3 @@
export interface DiscordAuthURL {
loginUrl: string;
}

View File

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

View File

@@ -0,0 +1,6 @@
import { AuthUserDTO } from "./auth-user.dto";
export interface OAuthDTO {
user: AuthUserDTO;
oAuthId: 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,5 @@
export interface TokenDTO {
token: string;
refreshToken: string;
firstLogin?: boolean;
}

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,21 @@
import { SoftwareVersion } from "./software-version";
import { Theme } from '../view/theme';
export interface AppAndVersionSettings {
ApiURL: string;
PrivacyURL: string;
ImprintURL: string;
Themes: Theme[];
WebVersion: SoftwareVersion;
}
export interface AppSettings {
ApiURL: string;
PrivacyURL: string;
ImprintURL: string;
Themes: Theme[];
}
export interface VersionSettings {
WebVersion: SoftwareVersion;
}

View File

@@ -0,0 +1,4 @@
export interface FeatureFlag {
key: string;
value: boolean;
}

View File

@@ -0,0 +1,27 @@
import { DataWithHistory } from "../data/data.model";
import { FeatureFlag } from "./feature-flags.model";
export interface ServerConfig extends DataWithHistory {
id?: number;
messageDeleteTimer?: number;
notificationChatId?: string;
maxVoiceStateHours?: number;
xpPerMessage?: number;
xpPerReaction?: number;
maxMessageXpPerHour?: number;
xpPerOntimeHour?: number;
xpPerEventParticipation?: number;
xpPerAchievement?: number;
xpForBirthday?: number;
afkCommandChannelId?: string;
helpVoiceChannelId?: string;
teamChannelId?: string;
loginMessageChannelId?: string;
defaultRoleId?: string;
shortRoleNameOnlySetHighestRole?: boolean;
gameOfferNotificationChatId?: string;
featureFlags: FeatureFlag[];
afkChannelIds: string[];
moderatorRoleIds: string[];
adminRoleIds: string[];
}

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,13 @@
import { DataWithHistory } from "../data/data.model";
import { FeatureFlag } from "./feature-flags.model";
export interface TechnicianConfig extends DataWithHistory {
id?: number;
helpCommandReferenceUrl?: string;
waitForRestart?: number;
waitForShutdown?: number;
cacheMaxMessages?: number;
featureFlags: FeatureFlag[];
pingURLs: string[];
technicianIds: string[];
}

View File

@@ -0,0 +1,29 @@
import { DataWithHistory } from "./data.model";
import { Server, ServerFilter } from "./server.model";
export interface AchievementAttribute {
name?: string;
type?: string;
}
export interface Achievement extends DataWithHistory {
id?: number;
name?: string;
description?: string;
attribute?: string | AchievementAttribute;
operator?: string;
value?: string;
server?: Server;
createdAt?: string;
}
export interface AchievementFilter {
id?: number;
name?: string;
description?: string;
attribute?: string;
operator?: string;
value?: string;
server?: ServerFilter;
}

View File

@@ -0,0 +1,36 @@
import { Data } from "./data.model";
import { Server, ServerFilter } from "./server.model";
export interface AutoRole extends Data {
id?: number;
channelId?: string;
channelName?: string;
messageId?: string;
server?: Server;
autoRoleRuleCount?: number;
autoRoleRules?: AutoRoleRule[];
}
export interface AutoRoleFilter {
id?: number;
channelId?: string;
channelName?: string;
messageId?: string;
server?: ServerFilter;
}
export interface AutoRoleRule extends Data {
id?: number;
emojiName?: string;
roleId?: string;
roleName?: string;
autoRole?: AutoRole;
}
export interface AutoRoleRuleFilter {
id?: number;
emojiName?: string;
roleId?: string;
autoRole?: AutoRoleFilter;
}

View File

@@ -0,0 +1,16 @@
import {Server} from "./server.model";
import {Data} from "./data.model";
export interface Client extends Data {
id?: number;
discordId?: string;
name?: string;
sentMessageCount?: number;
receivedMessageCount?: number;
deletedMessageCount?: number;
receivedCommandCount?: number;
movedUsersCount?: number;
server?: Server;
}

View File

@@ -0,0 +1,17 @@
export interface Data {
createdAt?: string;
modifiedAt?: string;
}
export interface DataWithHistory {
createdAt?: string;
modifiedAt?: string;
history?: History[];
}
export interface History {
deleted?: boolean;
dateFrom?: string;
dateTo?: string;
[x: string | number | symbol]: unknown;
}

View File

@@ -0,0 +1,42 @@
export interface Discord {
guilds?: Guild[];
users?: DiscordUser[];
}
export interface Guild {
id?: string;
name?: string;
channels: Channel[];
roles: Role[];
emojis: Emoji[];
}
export interface Channel {
id?: string;
name?: string;
type?: ChannelType;
}
export enum ChannelType {
category = "CategoryChannel",
text = "TextChannel",
voice = "VoiceChannel"
}
export interface Role {
id?: string;
name?: string;
}
export interface Emoji {
id?: string;
name?: string;
url?: string;
}
export interface DiscordUser {
id?: string;
name?: string;
bot?: boolean;
}

View File

@@ -0,0 +1,17 @@
import {Data} from "./data.model";
import {Server, ServerFilter} from "./server.model";
export interface Level extends Data {
id?: number;
name?: string;
color?: string;
minXp?: number;
permissions?: string;
server?: Server;
}
export interface LevelFilter {
id?: number;
name?: string;
server?: ServerFilter;
}

View File

@@ -0,0 +1,36 @@
import {Data} from "./data.model";
import {User} from "./user.model";
import {Level} from "./level.model";
import {Client} from "./client.model";
import { AutoRole } from "./auto_role.model";
import { ServerConfig } from "../config/server-config.model";
import { FeatureFlag } from "../config/feature-flags.model";
export interface GameServer {
id?: number;
name?: string;
}
export interface Server extends Data {
id?: number;
discordId?: String;
name?: string;
iconURL?: string;
autoRoleCount?: number;
autoRoles?: AutoRole[];
clientCount?: number;
clients?: Client[];
levelCount?: number;
levels?: Level[];
userCount?: number;
users?: User[];
config?: ServerConfig;
hasFeatureFlag?: FeatureFlag;
activeFeatureFlags?: FeatureFlag[];
}
export interface ServerFilter {
id?: number;
discordId?: String;
name?: String;
}

View File

@@ -0,0 +1,20 @@
import { Data } from "./data.model";
import { Server, ServerFilter } from "./server.model";
export interface ShortRoleName extends Data {
id?: number;
shortName?: string;
roleId?: string;
roleName?: string;
position?: string;
server?: Server;
}
export interface ShortRoleNameFilter {
id?: number;
shortName?: string;
roleId?: string;
roleName?: string;
position?: string;
server?: ServerFilter;
}

View File

@@ -0,0 +1,48 @@
import { DataWithHistory } from "./data.model";
import { Level, LevelFilter } from "./level.model";
import { Server, ServerFilter } from "./server.model";
import { UserJoinedServer } from "./user_joined_server.model";
import { UserJoinedVoiceChannel } from "./user_joined_voice_channel.model";
import { UserJoinedGameServer } from "./user_joined_game_server.model";
import { Achievement } from "./achievement.model";
import { UserWarning } from "./user_warning.model";
export interface User extends DataWithHistory {
id?: number;
discordId?: number;
name?: string;
xp?: number;
messageCount?: number;
reactionCount?: number;
birthday?: string;
ontime?: number;
level?: Level;
server?: Server;
leftServer?: boolean;
joinedServerCount?: number;
joinedServers?: UserJoinedServer[];
joinedVoiceChannelCount?: number;
joinedVoiceChannels?: UserJoinedVoiceChannel[];
userJoinedGameServerCount?: number;
userJoinedGameServers?: UserJoinedGameServer[];
achievementCount?: number;
achievements?: Achievement[];
userWarningCount?: number;
userWarnings?: UserWarning[];
}
export interface UserFilter {
id?: number;
discordId?: number;
name?: string;
xp?: number;
ontime?: number;
level?: LevelFilter;
server?: ServerFilter;
leftServer?: boolean;
}

View File

@@ -0,0 +1,11 @@
import { Data } from "./data.model";
import { User } from "./user.model";
export interface UserJoinedGameServer extends Data {
id: number;
gameServer: string;
user: User;
time: number;
joinedOn: string;
leavedOn: string;
}

View File

@@ -0,0 +1,9 @@
import { Data } from "./data.model";
import { User } from "./user.model";
export interface UserJoinedServer extends Data {
id: number;
user: User;
joinedOn: string;
leavedOn: string;
}

View File

@@ -0,0 +1,12 @@
import { Data } from "./data.model";
import { User } from "./user.model";
export interface UserJoinedVoiceChannel extends Data {
id: number;
channelId: string;
channelName: string;
user: User;
time: number;
joinedOn: string;
leavedOn: string;
}

View File

@@ -0,0 +1,16 @@
import { DataWithHistory } from "./data.model";
import { User, UserFilter } from "./user.model";
export interface UserWarning extends DataWithHistory {
id?: number;
user?: User;
description?: string;
author?: User;
}
export interface UserWarningFilter {
id?: number;
user?: UserFilter;
description?: string;
author?: UserFilter;
}

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,17 @@
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,
Unauthorized = 11
}

View File

@@ -0,0 +1,4 @@
export interface Page {
pageIndex?: number;
pageSize?: number;
}

View File

@@ -0,0 +1,9 @@
export interface Sort {
sortColumn?: string;
sortDirection?: SortDirection;
}
export enum SortDirection {
ASC = "ASC",
DESC = "DESC",
}

View File

@@ -0,0 +1,376 @@
export class Mutations {
static updateUser = `
mutation updateUser($id: ID, $xp: Int $birthday: String, $levelId: ID, $userWarnings: [UserWarningInput]) {
user {
updateUser(input: { id: $id, xp: $xp, birthday: $birthday, levelId: $levelId, userWarnings: $userWarnings }) {
id
name
xp
messageCount
reactionCount
birthday
level {
id
name
}
userWarnings {
id
description
}
}
}
}
`;
static createAutoRole = `
mutation createAutoRole($serverId: ID, $channelId: String, $messageId: String) {
autoRole {
createAutoRole(input: { serverId: $serverId, channelId: $channelId, messageId: $messageId }) {
id
channelId
channelName
messageId
}
}
}
`;
static deleteAutoRole = `
mutation deleteAutoRole($id: ID) {
autoRole {
deleteAutoRole(id: $id) {
id
channelId
channelName
messageId
}
}
}
`;
static createAutoRoleRule = `
mutation createAutoRoleRule($autoRoleId: ID, $emojiName: String, $roleId: String) {
autoRoleRule {
createAutoRoleRule(input: { autoRoleId: $autoRoleId, emojiName: $emojiName, roleId: $roleId }) {
id
emojiName
roleId
roleName
}
}
}
`;
static updateAutoRoleRule = `
mutation updateAutoRoleRule($id: ID, $emojiName: String, $roleId: String) {
autoRoleRule {
updateAutoRoleRule(input: { id: $id, emojiName: $emojiName, roleId: $roleId }) {
id
emojiName
roleId
roleName
}
}
}
`;
static deleteAutoRoleRule = `
mutation deleteAutoRoleRule($id: ID) {
autoRoleRule {
deleteAutoRoleRule(id: $id) {
id
emojiName
roleId
roleName
}
}
}
`;
static createLevel = `
mutation createLevel($name: String, $color: String, $minXp: Int, $permissions: String, $serverId: ID) {
level {
createLevel(input: { name: $name, color: $color, minXp: $minXp, permissions: $permissions, serverId: $serverId}) {
id
name
color
minXp
permissions
server {
id
}
}
}
}
`;
static updateLevel = `
mutation updateLevel($id: ID, $name: String, $color: String, $minXp: Int, $permissions: String) {
level {
updateLevel(input: { id: $id, name: $name, color: $color, minXp: $minXp, permissions: $permissions }) {
id
name
color
minXp
permissions
}
}
}
`;
static deleteLevel = `
mutation deleteLevel($id: ID) {
level {
deleteLevel(id: $id) {
id
name
}
}
}
`;
static createAchievement = `
mutation createAchievement($name: String, $description: String, $attribute: String, $operator: String, $value: String, $serverId: ID) {
achievement {
createAchievement(input: { name: $name, description: $description, attribute: $attribute, operator: $operator, value: $value, serverId: $serverId}) {
id
name
description
attribute
operator
value
server {
id
}
}
}
}
`;
static updateAchievement = `
mutation updateAchievement($id: ID, $name: String, $description: String, $attribute: String, $operator: String, $value: String) {
achievement {
updateAchievement(input: { id: $id, name: $name, description: $description, attribute: $attribute, operator: $operator, value: $value}) {
id
name
description
attribute
operator
value
}
}
}
`;
static deleteAchievement = `
mutation deleteAchievement($id: ID) {
achievement {
deleteAchievement(id: $id) {
id
name
}
}
}
`;
static createShortRoleName = `
mutation createShortRoleName($shortName: String, $roleId: String, $position: String, $serverId: ID) {
shortRoleName {
createShortRoleName(input: { shortName: $shortName, roleId: $roleId, position: $position, serverId: $serverId}) {
id
shortName
roleId
roleName
position
server {
id
}
}
}
}
`;
static updateShortRoleName = `
mutation updateShortRoleName($id: ID, $shortName: String, $roleId: String, $position: String, $serverId: ID) {
shortRoleName {
updateShortRoleName(input: { id: $id, shortName: $shortName, roleId: $roleId, position: $position, serverId: $serverId }) {
id
shortName
roleId
roleName
position
}
}
}
`;
static deleteShortRoleName = `
mutation deleteShortRoleName($id: ID) {
shortRoleName {
deleteShortRoleName(id: $id) {
id
shortName
}
}
}
`;
static updateTechnicianConfig = `
mutation updateTechnicianConfig($id: ID, $helpCommandReferenceUrl: String, $waitForRestart: Int, $waitForShutdown: Int, $cacheMaxMessages: Int, $featureFlags: [FeatureFlagInput], $pingURLs: [String], $technicianIds: [String]) {
technicianConfig {
updateTechnicianConfig(input: {
id: $id,
helpCommandReferenceUrl: $helpCommandReferenceUrl,
waitForRestart: $waitForRestart,
waitForShutdown: $waitForShutdown,
cacheMaxMessages: $cacheMaxMessages,
featureFlags: $featureFlags,
pingURLs: $pingURLs,
technicianIds: $technicianIds
}) {
id
helpCommandReferenceUrl
waitForRestart
waitForShutdown
cacheMaxMessages
featureFlags {
key
value
}
pingURLs
technicianIds
}
}
}
`;
static updateServerConfig = `
mutation updateServerConfig(
$id: ID,
$messageDeleteTimer: Int,
$notificationChatId: String,
$maxVoiceStateHours: Int,
$xpPerMessage: Int,
$xpPerReaction: Int,
$maxMessageXpPerHour: Int,
$xpPerOntimeHour: Int,
$xpPerEventParticipation: Int,
$xpPerAchievement: Int,
$xpForBirthday: Int,
$afkCommandChannelId: String,
$helpVoiceChannelId: String,
$teamChannelId: String,
$loginMessageChannelId: String,
$defaultRoleId: String,
$shortRoleNameOnlySetHighestRole: Boolean,
$gameOfferNotificationChatId: String,
$featureFlags: [FeatureFlagInput],
$afkChannelIds: [String],
$moderatorRoleIds: [String],
$adminRoleIds: [String]
) {
serverConfig {
updateServerConfig(input: {
id: $id,
messageDeleteTimer: $messageDeleteTimer,
notificationChatId: $notificationChatId,
maxVoiceStateHours: $maxVoiceStateHours,
xpPerMessage: $xpPerMessage,
xpPerReaction: $xpPerReaction,
maxMessageXpPerHour: $maxMessageXpPerHour,
xpPerOntimeHour: $xpPerOntimeHour,
xpPerEventParticipation: $xpPerEventParticipation,
xpPerAchievement: $xpPerAchievement,
xpForBirthday: $xpForBirthday,
afkCommandChannelId: $afkCommandChannelId,
helpVoiceChannelId: $helpVoiceChannelId,
teamChannelId: $teamChannelId,
loginMessageChannelId: $loginMessageChannelId,
defaultRoleId: $defaultRoleId,
shortRoleNameOnlySetHighestRole: $shortRoleNameOnlySetHighestRole,
gameOfferNotificationChatId: $gameOfferNotificationChatId,
featureFlags: $featureFlags,
afkChannelIds: $afkChannelIds,
moderatorRoleIds: $moderatorRoleIds,
adminRoleIds: $adminRoleIds
}) {
id
messageDeleteTimer
notificationChatId
maxVoiceStateHours
xpPerMessage
xpPerReaction
maxMessageXpPerHour
xpPerOntimeHour
xpPerEventParticipation
xpPerAchievement
xpForBirthday
afkCommandChannelId
helpVoiceChannelId
teamChannelId
loginMessageChannelId
defaultRoleId
shortRoleNameOnlySetHighestRole
gameOfferNotificationChatId
featureFlags {
key
value
}
afkChannelIds
moderatorRoleIds
adminRoleIds
server {
id
}
}
}
}
`;
static createUserWarning = `
mutation createUserWarning($name: String, $description: String, $attribute: String, $operator: String, $value: String, $serverId: ID) {
userWarning {
createUserWarning(input: { name: $name, description: $description, attribute: $attribute, operator: $operator, value: $value, serverId: $serverId}) {
id
name
description
attribute
operator
value
server {
id
}
}
}
}
`;
static updateUserWarning = `
mutation updateUserWarning($id: ID, $name: String, $description: String, $attribute: String, $operator: String, $value: String) {
userWarning {
updateUserWarning(input: { id: $id, name: $name, description: $description, attribute: $attribute, operator: $operator, value: $value}) {
id
name
description
attribute
operator
value
}
}
}
`;
static deleteUserWarning = `
mutation deleteUserWarning($id: ID) {
userWarning {
deleteUserWarning(id: $id) {
id
name
}
}
}
`;
}

View File

@@ -0,0 +1,550 @@
export class Queries {
static guildsQuery = `
query GuildsQuery($id: ID, $filter: ChannelFilter) {
discord {
guilds(filter: {id: $id}) {
id
name
channels(filter: $filter) {
id
name
type
}
roles {
id
name
}
emojis {
id
name
url
}
}
}
}
`;
static discordUsersQuery = `
query DiscordUsersQuery {
discord {
users {
id
name
}
}
}
`;
static serverConfigDiscordQuery = `
query ServerConfigDiscordQuery($id: ID) {
discord {
guilds(filter: {id: $id}) {
id
name
roles {
id
name
}
channels {
id
name
type
}
}
}
}
`;
static serversQuery = `
query ServerInfo($filter: ServerFilter, $page: Page, $sort: Sort) {
serverCount
servers(filter: $filter, page: $page, sort: $sort) {
id
discordId
name
iconURL
userCount
clients {
id
discordId
name
sentMessageCount
receivedMessageCount
deletedMessageCount
receivedCommandCount
movedUsersCount
}
}
}
`;
static hasServerFeatureFlag = `
query HasServerFeatureFlag($filter: ServerFilter, $flag: String) {
servers(filter: $filter) {
hasFeatureFlag(flag: $flag) {
key
value
}
}
}
`;
static gameServerQuery = `
query GameServersList($serverId: ID) {
servers(filter: {id: $serverId}) {
gameServers {
name
}
}
}
`;
static levelQuery = `
query LevelsList($serverId: ID, $filter: LevelFilter, $page: Page, $sort: Sort) {
servers(filter: {id: $serverId}) {
levelCount
levels(filter: $filter, page: $page, sort: $sort) {
id
name
color
minXp
permissions
server {
id
name
}
createdAt
modifiedAt
}
}
}
`;
static levelWithHistoryQuery = `
query LevelHistory($serverId: ID, $id: ID) {
servers(filter: {id: $serverId}) {
levelCount
levels(filter: {id: $id}) {
id
history {
id
name
color
minXp
permissions
server
deleted
dateFrom
dateTo
}
}
}
}
`;
static achievementTypeQuery = `
query AchievementType {
achievementOperators
achievementAttributes {
name
type
}
}
`;
static achievementQuery = `
query AchievementList($serverId: ID, $filter: AchievementFilter, $page: Page, $sort: Sort) {
servers(filter: {id: $serverId}) {
achievementCount
achievements(filter: $filter, page: $page, sort: $sort) {
id
name
description
attribute
operator
value
server {
id
name
}
createdAt
modifiedAt
}
}
}
`;
static achievementWithHistoryQuery = `
query AchievementHistory($serverId: ID, $id: ID) {
servers(filter: {id: $serverId}) {
achievementCount
achievements(filter: {id: $id}) {
id
history {
id
name
description
attribute
operator
value
server
deleted
dateFrom
dateTo
}
}
}
}
`;
static shortRoleNamePositionsQuery = `
query {
shortRoleNamePositions
}
`;
static shortRoleNameQuery = `
query ShortRoleNameList($serverId: ID, $filter: ShortRoleNameFilter, $page: Page, $sort: Sort) {
servers(filter: {id: $serverId}) {
shortRoleNameCount
shortRoleNames(filter: $filter, page: $page, sort: $sort) {
id
shortName
roleId
roleName
position
server {
id
name
}
createdAt
modifiedAt
}
}
}
`;
static shortRoleNameWithHistoryQuery = `
query ShortRoleNameListHistory($serverId: ID, $id: ID) {
servers(filter: {id: $serverId}) {
shortRoleNameCount
shortRoleNames(filter: {id: $id}) {
id
history {
id
shortName
roleId
roleName
position
server
deleted
dateFrom
dateTo
}
}
}
}
`;
static usersQuery = `
query UsersList($serverId: ID, $filter: UserFilter, $page: Page, $sort: Sort) {
servers(filter: {id: $serverId}) {
userCount
users(filter: $filter, page: $page, sort: $sort) {
id
discordId
name
xp
ontime
level {
id
name
}
leftServer
createdAt
modifiedAt
}
}
}
`;
static userProfile = `
query UserProfile($serverId: ID, $userId: ID, $page: Page, $sort: Sort) {
userCount
users(filter: {server: {id: $serverId}, id: $userId}, page: $page, sort: $sort) {
id
discordId
name
xp
messageCount
reactionCount
birthday
ontime
level {
id
name
}
leftServer
server {
id
name
}
joinedServerCount
joinedServers {
id
joinedOn
leavedOn
}
createdAt
modifiedAt
}
}
`;
static userProfileAchievements = `
query UserProfile($serverId: ID, $userId: ID, $page: Page, $sort: Sort) {
users(filter: {server: {id: $serverId}, id: $userId}, page: $page, sort: $sort) {
achievementCount
achievements {
id
name
description
createdAt
}
}
}
`;
static userProfileVoiceChannelJoins = `
query UserProfile($serverId: ID, $userId: ID, $page: Page, $sort: Sort) {
users(filter: {server: {id: $serverId}, id: $userId}, page: $page, sort: $sort) {
joinedVoiceChannelCount
joinedVoiceChannels {
id
channelId
channelName
time
joinedOn
leavedOn
}
}
}
`;
static userProfileGameserverJoins = `
query UserProfile($serverId: ID, $userId: ID, $page: Page, $sort: Sort) {
users(filter: {server: {id: $serverId}, id: $userId}, page: $page, sort: $sort) {
userJoinedGameServerCount
userJoinedGameServers {
id
gameServer
time
joinedOn
leavedOn
}
}
}
`;
static userProfileWarnings = `
query UserProfile($serverId: ID, $userId: ID, $page: Page, $sort: Sort) {
users(filter: {server: {id: $serverId}, id: $userId}, page: $page, sort: $sort) {
userWarningCount
userWarnings {
id
user {
id
name
}
description
author {
id
name
}
createdAt
}
}
}
`;
static userQueryWithHistory = `
query UsersWithHistory($serverId: ID, $id: ID) {
servers(filter: {id: $serverId}) {
userCount
users(filter: {id: $id}) {
id
history {
id
discordId
xp
server
deleted
dateFrom
dateTo
}
}
}
}
`;
static autoRolesQuery = `
query AutoRoleQuery($serverId: ID, $filter: AutoRoleFilter, $page: Page, $sort: Sort) {
servers(filter: {id: $serverId}) {
autoRoleCount
autoRoles(filter: $filter, page: $page, sort: $sort) {
id
channelId
channelName
messageId
autoRoleRuleCount
server {
id
}
createdAt
modifiedAt
}
}
}
`;
static autoRolesWithHistoryQuery = `
query AutoRoleWithHistoryQuery($serverId: ID, $id: ID) {
servers(filter: {id: $serverId}) {
autoRoleCount
autoRoles(filter: {id: $id}) {
id
history {
id
channelId
messageId
server
deleted
dateFrom
dateTo
}
}
}
}
`;
static autoRoleRulesQuery = `
query AutoRoleRuleQuery($serverId: ID, $autoRoleId: ID, $filter: AutoRoleRuleFilter, $page: Page, $sort: Sort) {
servers(filter: {id: $serverId}) {
autoRoles(filter: {id: $autoRoleId}) {
autoRoleRuleCount
autoRoleRules(filter: $filter, page: $page, sort: $sort) {
id
emojiName
roleId
roleName
autoRole {
id
}
createdAt
modifiedAt
}
}
}
}
`;
static autoRoleRulesHistoryQuery = `
query AutoRoleRuleHistoryQuery($serverId: ID, $autoRoleId: ID, $id: ID) {
servers(filter: {id: $serverId}) {
autoRoles(filter: {id: $autoRoleId}) {
autoRoleRuleCount
autoRoleRules(filter: {id: $id}) {
id
history {
id
emojiName
roleId
autoRole
deleted
dateFrom
dateTo
}
}
}
}
}
`;
static technicianConfigQuery = `
query technicianConfigQuery {
technicianConfig {
id
helpCommandReferenceUrl
waitForRestart
waitForShutdown
cacheMaxMessages
featureFlags {
key
value
}
pingURLs
technicianIds
createdAt
modifiedAt
}
}
`;
static serverConfigQuery = `
query serverConfigQuery($serverId: ID) {
servers(filter: { id: $serverId }) {
name
config {
id
messageDeleteTimer
notificationChatId
maxVoiceStateHours
xpPerMessage
xpPerReaction
maxMessageXpPerHour
xpPerOntimeHour
xpPerEventParticipation
xpPerAchievement
xpForBirthday
afkCommandChannelId
helpVoiceChannelId
teamChannelId
loginMessageChannelId
defaultRoleId
shortRoleNameOnlySetHighestRole
featureFlags {
key
value
}
afkChannelIds
moderatorRoleIds
adminRoleIds
server {
id
}
}
}
}
`;
}

View File

@@ -0,0 +1,87 @@
import { GameServer, Server } from "../data/server.model";
import { User } from "../data/user.model";
import { AutoRole, AutoRoleRule } from "../data/auto_role.model";
import { Discord, Guild } from "../data/discord.model";
import { Level } from "../data/level.model";
import { Achievement, AchievementAttribute } from "../data/achievement.model";
import { TechnicianConfig } from "../config/technician-config.model";
import { ServerConfig } from "../config/server-config.model";
import { ShortRoleName } from "../data/short_role_name.model";
import { FeatureFlag } from "../config/feature-flags.model";
import { UserWarning } from "../data/user_warning.model";
export interface Query {
serverCount: number;
servers: Server[];
}
export interface TechnicianConfigQuery {
technicianConfig: TechnicianConfig;
}
export interface ServerConfigQuery {
config: ServerConfig;
}
export interface SingleDiscordQuery {
discord: Discord;
}
export interface UserListQuery {
userCount: number;
users: User[];
}
export interface UserWarningQuery {
userWarningCount: number;
userWarnings: UserWarning[];
}
export interface GameServerListQuery {
gameServerCount: number;
gameServers: GameServer[];
}
export interface LevelListQuery {
levelCount: number;
levels: Level[];
}
export interface AchievementTypeQuery {
achievementAttributes: AchievementAttribute[];
achievementOperators: string[];
}
export interface AchievementListQuery {
achievementCount: number;
achievements: Achievement[];
}
export interface AutoRoleQuery {
autoRoleCount: number;
autoRoles: AutoRole[];
}
export interface AutoRoleRuleQuery {
autoRoleRuleCount: number;
autoRoleRules: AutoRoleRule[];
}
export interface PossibleFeatureFlagsQuery {
possibleFeatureFlags: string[];
}
export interface HasServerFeatureFlagQuery {
hasFeatureFlag: FeatureFlag;
}
export interface ShortRoleNameListQuery {
shortRoleNameCount: number;
shortRoleNames: ShortRoleName[];
}
export interface ShortRoleNamePositionsQuery {
shortRoleNamePositions: string[];
}

View File

@@ -0,0 +1,88 @@
import { User } from "../data/user.model";
import { AutoRole, AutoRoleRule } from "../data/auto_role.model";
import { Level } from "../data/level.model";
import { Server } from "../data/server.model";
import { Achievement } from "../data/achievement.model";
import { TechnicianConfig } from "../config/technician-config.model";
import { ServerConfig } from "../config/server-config.model";
import { ShortRoleName } from "../data/short_role_name.model";
import { UserWarning } from "../data/user_warning.model";
export interface GraphQLResult {
data: {
servers?: Server[];
};
errors?: [];
}
export interface QueryResult {
data: {
servers?: Server[];
};
}
export interface UpdateUserMutationResult {
user: {
updateUser: User
};
}
export interface AutoRoleMutationResult {
autoRole: {
createAutoRole?: AutoRole
updateAutoRole?: AutoRole
deleteAutoRole?: AutoRole
};
}
export interface AutoRoleRuleMutationResult {
autoRoleRule: {
createAutoRoleRule?: AutoRoleRule
updateAutoRoleRule?: AutoRoleRule
deleteAutoRoleRule?: AutoRoleRule
};
}
export interface LevelMutationResult {
level: {
createLevel?: Level
updateLevel?: Level
deleteLevel?: Level
};
}
export interface TechnicianConfigMutationResult {
technicianConfig: {
updateTechnicianConfig?: TechnicianConfig
};
}
export interface ServerConfigMutationResult {
serverConfig: {
updateServerConfig?: ServerConfig
};
}
export interface AchievementMutationResult {
achievement: {
createAchievement?: Achievement
updateAchievement?: Achievement
deleteAchievement?: Achievement
};
}
export interface ShortRoleNameMutationResult {
shortRoleName: {
createShortRoleName?: ShortRoleName
updateShortRoleName?: ShortRoleName
deleteShortRoleName?: ShortRoleName
};
}
export interface UserWarningMutationResult {
userWarning: {
createUserWarning?: UserWarning
updateUserWarning?: UserWarning
deleteUserWarning?: UserWarning
};
}

View File

@@ -0,0 +1,9 @@
import { Page } from "./filter/page.model";
import { Sort } from "./filter/sort.model";
export interface Variables {
filter?: object;
page?: Page;
sort?: Sort;
[x: string | number | symbol]: unknown;
}

View File

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

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 | null;
sortColumn: string | null;
}

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,7 @@
export enum Themes {
Default = "sh-edraft-dark-theme",
DefaultLight = "default-light-theme",
DefaultDark = "default-dark-theme",
ShEdraftLight = "sh-edraft-light-theme",
ShEdraftDark = "sh-edraft-dark-theme",
}

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,247 @@
<h1>
{{'admin.auth_users.header' | translate}}
</h1>
<div class="content-wrapper">
<div class="content">
<p-table #dt [value]="users" [responsive]="true" responsiveLayout="stack" [breakpoint]="'720px'" 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-table-info">
<div class="table-caption-text">
<ng-container *ngIf="!loading">{{users.length}} {{'common.of' | translate}}
{{dt.totalRecords}}
</ng-container>
{{'admin.auth_users.users' | translate}}
</div>
<app-multi-select-columns [table]="name" [columns]="columns" [(hiddenColumns)]="hiddenColumns"></app-multi-select-columns>
</div>
<div class="table-caption-btn-wrapper btn-wrapper">
<button pButton label="{{'common.add' | translate}}" class="icon-btn btn"
icon="pi pi-user-plus" (click)="addUser(dt)" [disabled]="isEditingNew">
</button>
<button pButton label="{{'common.reset_filters' | translate}}" icon="pi pi-undo"
class="icon-btn btn" (click)="resetFilters()">
</button>
</div>
</div>
</ng-template>
<ng-template pTemplate="header">
<tr>
<th hideable-th="first_name" [parent]="this" [sortable]="true">
<div class="table-header-label">
<div class="table-header-text">{{'common.first_name' | translate}}</div>
<p-sortIcon field="firstName" class="table-header-icon"></p-sortIcon>
</div>
</th>
<th hideable-th="last_name" [parent]="this" [sortable]="true">
<div class="table-header-label">
<div class="table-header-text">{{'common.last_name' | translate}}</div>
<p-sortIcon field="lastName" class="table-header-icon"></p-sortIcon>
</div>
</th>
<th hideable-th="email" [parent]="this" [sortable]="true">
<div class="table-header-label">
<div class="table-header-text">{{'common.email' | translate}}</div>
<p-sortIcon field="email" class="table-header-icon"></p-sortIcon>
</div>
</th>
<th class="table-header-actions" hideable-th="active" [parent]="this" [sortable]="true">
<div class="table-header-label">
<div class="table-header-text">{{'common.active' | translate}}</div>
<p-sortIcon field="confirmationId" class="table-header-icon"></p-sortIcon>
</div>
</th>
<th class="table-header-small-dropdown" hideable-th="auth_role" [parent]="this" [sortable]="true">
<div class="table-header-label">
<div class="table-header-text">{{'common.role' | translate}}</div>
<p-sortIcon field="authRole" class="table-header-icon"></p-sortIcon>
</div>
</th>
<th hideable-th="password" [parent]="this">
<div class="table-header-label">
<div class="table-header-text">{{'common.password' | translate}}</div>
</div>
</th>
<th class="table-header-small-dropdown">
<div class="table-header-label">
<div class="table-header-text">{{'common.created_at' | translate}}</div>
</div>
</th>
<th class="table-header-small-dropdown">
<div class="table-header-label">
<div class="table-header-text">{{'common.modified_at' | translate}}</div>
</div>
</th>
<th class="table-header-actions">
<div class="table-header-label">
<div class="table-header-text">{{'common.actions' | translate}}</div>
</div>
</th>
</tr>
<tr>
<th hideable-th="first_name" [parent]="this">
<form [formGroup]="filterForm">
<input type="text" pInputText formControlName="firstName" placeholder="{{'common.first_name' | translate}}">
</form>
</th>
<th hideable-th="last_name" [parent]="this">
<form [formGroup]="filterForm">
<input type="text" pInputText formControlName="lastName" placeholder="{{'common.last_name' | translate}}">
</form>
</th>
<th hideable-th="email" [parent]="this">
<form [formGroup]="filterForm">
<input type="email" pInputText formControlName="email" placeholder="{{'common.email' | translate}}">
</form>
</th>
<th hideable-th="active" [parent]="this"></th>
<th hideable-th="auth_role" [parent]="this">
<form [formGroup]="filterForm">
<p-dropdown formControlName="authRole" [options]="authRoles" placeholder="{{'common.auth_role' | translate}}"></p-dropdown>
</form>
</th>
<th hideable-th="password" [parent]="this"></th>
<th></th>
<th></th>
<th></th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-user let-editing="editing" let-ri="rowIndex">
<tr [pEditableRow]="user">
<td hideable-td="first_name" [parent]="this">
<span class="p-column-title">{{'common.first_name' | translate}}:</span>
<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 hideable-td="last_name" [parent]="this">
<span class="p-column-title">{{'common.last_name' | translate}}:</span>
<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 hideable-td="email" [parent]="this">
<span class="p-column-title">{{'common.email' | translate}}:</span>
<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 hideable-td="active" [parent]="this">
<span class="p-column-title">{{'common.active' | translate}}:</span>
<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 hideable-td="auth_role" [parent]="this">
<span class="p-column-title">{{'common.auth_role' | translate}}:</span>
<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 hideable-td="password" [parent]="this">
<span class="p-column-title">{{'common.password' | translate}}:</span>
<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>
<span class="p-column-title">{{'common.created_at' | translate}}:</span>
<p-cellEditor>
<ng-template pTemplate="input">
{{user.createdAt | date:'dd.MM.yy HH:mm'}}
</ng-template>
<ng-template pTemplate="output">
{{user.createdAt | date:'dd.MM.yy HH:mm'}}
</ng-template>
</p-cellEditor>
</td>
<td>
<span class="p-column-title">{{'common.modified_at' | translate}}:</span>
<p-cellEditor>
<ng-template pTemplate="input">
{{user.modifiedAt | date:'dd.MM.yy HH:mm'}}
</ng-template>
<ng-template pTemplate="output">
{{user.modifiedAt | date:'dd.MM.yy HH:mm'}}
</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="emptymessage">
<tr>
<td colspan="9">{{'common.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,341 @@
import { Component, OnDestroy, OnInit } from "@angular/core";
import { catchError, debounceTime, takeUntil } from "rxjs/operators";
import { AuthRoles } from "src/app/models/auth/auth-roles.enum";
import { AuthUserDTO } from "src/app/models/auth/auth-user.dto";
import { AuthService } from "src/app/services/auth/auth.service";
import { ConfirmationDialogService } from "src/app/services/confirmation-dialog/confirmation-dialog.service";
import { SpinnerService } from "src/app/services/spinner/spinner.service";
import { ToastService } from "src/app/services/toast/toast.service";
import { Table } from "primeng/table";
import { ServiceErrorCode } from "src/app/models/error/service-error-code.enum";
import { RegisterErrorMessages } from "src/app/models/auth/register-error-messages.enum";
import { ErrorDTO } from "src/app/models/error/error-dto";
import { FormBuilder, FormControl, FormGroup } from "@angular/forms";
import { AuthUserSelectCriterion } from "src/app/models/selection/auth-user/auth-user-select-criterion.dto";
import { LazyLoadEvent } from "primeng/api";
import { Subject, throwError } from "rxjs";
import { TranslateService } from "@ngx-translate/core";
import { ComponentWithTable } from "../../../../../base/component-with-table";
@Component({
selector: "app-auth-user",
templateUrl: "./auth-user.component.html",
styleUrls: ["./auth-user.component.scss"]
})
export class AuthUserComponent extends ComponentWithTable implements OnInit, OnDestroy {
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 = {
firstName: "",
lastName: "",
email: "",
password: "",
authRole: AuthRoles.Normal
};
isFirstNameInvalid: boolean = false;
isLastNameInvalid: boolean = false;
isEMailInvalid: boolean = false;
isPasswordInvalid: boolean = false;
loggedInUserEMail: string = "";
filterForm!: FormGroup<{
firstName: FormControl<string | null>,
lastName: FormControl<string | null>,
email: FormControl<string | null>,
authRole: FormControl<string | null>
}>;
searchCriterions!: AuthUserSelectCriterion;
totalRecords!: number;
private unsubscriber = new Subject<void>();
constructor(
private authService: AuthService,
private spinnerService: SpinnerService,
private toastService: ToastService,
private confirmDialog: ConfirmationDialogService,
private fb: FormBuilder,
private translate: TranslateService
) {
super("auth-users", ["first_name", "last_name", "email", "active", "auth_role", "password"]);
}
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();
}
public ngOnDestroy(): void {
this.unsubscriber.next();
this.unsubscriber.complete();
}
setFilterForm() {
this.filterForm = this.fb.group({
firstName: [""],
lastName: [""],
email: [""],
authRole: [""]
});
this.filterForm.valueChanges.pipe(
takeUntil(this.unsubscriber),
debounceTime(600)
).subscribe(changes => {
if (changes.firstName) {
this.searchCriterions.firstName = changes.firstName;
} else {
this.searchCriterions.firstName = null;
}
if (changes.lastName) {
this.searchCriterions.lastName = changes.lastName;
} else {
this.searchCriterions.lastName = null;
}
if (changes.email) {
this.searchCriterions.email = changes.email;
} else {
this.searchCriterions.email = null;
}
if (changes.authRole != null) {
this.searchCriterions.authRole = +changes.authRole;
} else {
this.searchCriterions.authRole = null;
}
if (this.searchCriterions.pageSize)
this.searchCriterions.pageSize = 10;
if (this.searchCriterions.pageSize)
this.searchCriterions.pageIndex = 0;
this.loadNextPage();
});
}
loadNextPage() {
this.authService.getFilteredUsers(this.searchCriterions).pipe(catchError(err => {
this.loading = false;
return throwError(() => err);
})).subscribe(list => {
this.totalRecords = list.totalCount;
this.users = list.users;
this.loading = false;
});
}
nextPage(event: LazyLoadEvent) {
this.searchCriterions.pageSize = event.rows ?? 0;
if (event.first != null && event.rows != null)
this.searchCriterions.pageIndex = event.first / event.rows;
this.searchCriterions.sortColumn = event.sortField ?? null;
this.searchCriterions.sortDirection = event.sortOrder === 1 ? "asc" : event.sortOrder === -1 ? "desc" : "asc";
if (event.filters) {
// + "" => convert to string
this.searchCriterions.firstName = event.filters["firstName"] ? event.filters["firstName"] + "" : null;
this.searchCriterions.lastName = event.filters["lastName"] ? event.filters["lastName"] + "" : null;
this.searchCriterions.email = event.filters["email"] ? event.filters["email"] + "" : null;
this.searchCriterions.authRole = event.filters["authRole"] ? +event.filters["authRole"] : null;
}
this.loadNextPage();
}
resetFilters() {
this.filterForm.reset();
}
initUserList(): void {
this.spinnerService.showSpinner();
this.authService.getAllUsers()
.pipe(catchError(error => {
this.spinnerService.hideSpinner();
return throwError(() => error);
}))
.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();
return throwError(() => 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(error => {
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();
return throwError(() => error);
}))
.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(error => {
this.spinnerService.hideSpinner();
return throwError(() => error);
}))
.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 = this.users.length == 0 ? 1 : Math.max.apply(Math, this.users.map(u => {
return u.id ?? 0;
})) + 1;
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,182 @@
<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.email.header' | translate}}
</h2>
</div>
<div class="content">
<div class="content-row">
<div class="content-column">
<div class="content-data-name">{{'admin.settings.email.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.email.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.email.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.email.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.email.email_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="{{'common.email' | translate}}" autocomplete="email">
</div>
</div>
<div class="content-data-value">
<div class="login-form-submit">
<button pButton icon="pi pi-send" label="{{'common.email' | translate}}" class="btn login-form-submit-btn"
(click)="testMail()" [disabled]="testMailForm.invalid"></button>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="content-wrapper">
<div class="content-header">
<h2>
{{'admin.settings.bot.header' | translate}}
</h2>
</div>
<div class="content">
<div class="content-row">
<div class="content-column">
<div class="content-data-name">{{'admin.settings.bot.help_url' | translate}}:</div>
<div class="content-data-value">
<input type="text" pInputText [(ngModel)]="config.helpCommandReferenceUrl" placeholder="{{'admin.settings.bot.help_url' | translate}}">
</div>
</div>
</div>
<div class="content-row">
<div class="content-column">
<div class="content-data-name">{{'admin.settings.bot.wait_for_restart' | translate}}:</div>
<div class="content-data-value">
<input type="number" pInputText [(ngModel)]="config.waitForRestart" placeholder="{{'admin.settings.bot.wait_for_restart' | translate}}">
</div>
</div>
</div>
<div class="content-row">
<div class="content-column">
<div class="content-data-name">{{'admin.settings.bot.wait_for_shutdown' | translate}}:</div>
<div class="content-data-value">
<input type="number" pInputText [(ngModel)]="config.waitForShutdown" placeholder="{{'admin.settings.bot.wait_for_shutdown' | translate}}">
</div>
</div>
</div>
<div class="content-row">
<div class="content-column">
<div class="content-data-name">{{'admin.settings.bot.cache_max_messages' | translate}}:</div>
<div class="content-data-value">
<input type="number" pInputText [(ngModel)]="config.cacheMaxMessages" placeholder="{{'admin.settings.bot.cache_max_messages' | translate}}">
</div>
</div>
</div>
<div class="content-divider"></div>
<app-config-list translationKey="admin.settings.bot.ping_urls" [(data)]="config.pingURLs"></app-config-list>
<app-config-list translationKey="admin.settings.bot.technician_ids" [options]="possibleTechnicians" optionLabel="name" optionValue="id"
[(data)]="config.technicianIds"></app-config-list>
<app-feature-flag-list [(data)]="config.featureFlags"></app-feature-flag-list>
<div class="content-row">
<button pButton icon="pi pi-save" label="{{'common.save' | translate}}" class="btn login-form-submit-btn"
(click)="saveTechnicianConfig()"></button>
</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,173 @@
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 { 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";
import { forkJoin, throwError } from "rxjs";
import { TechnicianConfig } from "../../../../../models/config/technician-config.model";
import { SingleDiscordQuery, TechnicianConfigQuery } from "../../../../../models/graphql/query.model";
import { Queries } from "../../../../../models/graphql/queries.model";
import { DataService } from "../../../../../services/data/data.service";
import { TechnicianConfigMutationResult } from "../../../../../models/graphql/result.model";
import { Mutations } from "../../../../../models/graphql/mutations.model";
import { AuthService } from "../../../../../services/auth/auth.service";
import { ChannelType, DiscordUser } from "../../../../../models/data/discord.model";
@Component({
selector: "app-settings",
templateUrl: "./settings.component.html",
styleUrls: ["./settings.component.scss"]
})
export class SettingsComponent implements OnInit {
testMailForm!: FormGroup;
data: SettingsDTO = {
webVersion: "",
apiVersion: "",
configPath: "",
webBaseURL: "",
apiBaseURL: "",
tokenExpireTime: 0,
refreshTokenExpireTime: 0,
mailUser: "",
mailPort: 0,
mailHost: "",
mailTransceiver: "",
mailTransceiverAddress: ""
};
config: TechnicianConfig = {
helpCommandReferenceUrl: "",
waitForRestart: 0,
waitForShutdown: 0,
cacheMaxMessages: 0,
featureFlags: [],
pingURLs: [],
technicianIds: []
};
possibleTechnicians?: DiscordUser[];
constructor(
private dataService: DataService,
private settingsService: SettingsService,
private spinnerService: SpinnerService,
private guiService: GuiService,
private formBuilder: FormBuilder,
private toastService: ToastService,
private translate: TranslateService,
private authService: AuthService,
private spinner: SpinnerService
) {
}
ngOnInit(): void {
this.spinnerService.showSpinner();
this.initForms();
forkJoin([
this.guiService.getSettings().pipe(catchError(error => {
this.spinnerService.hideSpinner();
return throwError(() => error);
})),
this.dataService.query<TechnicianConfigQuery>(Queries.technicianConfigQuery),
this.dataService.query<SingleDiscordQuery>(Queries.discordUsersQuery)
]).subscribe(data => {
this.data = data[0];
this.data.webVersion = this.settingsService.getWebVersion()?.getVersionString() ?? "0.0.0";
this.data.apiBaseURL = this.settingsService.getApiURL();
if (!this.data.apiBaseURL.endsWith("/")) {
this.data.apiBaseURL += "/";
}
this.config = data[1].technicianConfig;
this.possibleTechnicians = data[2].discord.users ?? undefined;
this.initForms();
this.spinnerService.hideSpinner();
});
}
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);
return throwError(() => 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();
});
}
saveTechnicianConfig() {
this.spinner.showSpinner();
this.dataService.mutation<TechnicianConfigMutationResult>(Mutations.updateTechnicianConfig, {
helpCommandReferenceUrl: this.config.helpCommandReferenceUrl,
waitForRestart: this.config.waitForRestart,
waitForShutdown: this.config.waitForShutdown,
cacheMaxMessages: this.config.cacheMaxMessages,
featureFlags: this.config.featureFlags,
pingURLs: this.config.pingURLs,
technicianIds: this.config.technicianIds
}
).pipe(catchError(err => {
this.spinner.hideSpinner();
return throwError(err);
})).subscribe(result => {
this.spinner.hideSpinner();
this.toastService.success(this.translate.instant("admin.settings.message.technician_config_create"), this.translate.instant("admin.settings.message.technician_config_create_d"));
});
}
}

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

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