Moved folders #405
This commit is contained in:
16
web/.browserslistrc
Normal file
16
web/.browserslistrc
Normal file
@@ -0,0 +1,16 @@
|
||||
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
|
||||
# For additional information regarding the format and rule options, please see:
|
||||
# https://github.com/browserslist/browserslist#queries
|
||||
|
||||
# For the full list of supported browsers by the Angular framework, please see:
|
||||
# https://angular.io/guide/browser-support
|
||||
|
||||
# You can see what browsers were selected by your queries by running:
|
||||
# npx browserslist
|
||||
|
||||
last 1 Chrome version
|
||||
last 1 Firefox version
|
||||
last 2 Edge major versions
|
||||
last 2 Safari major versions
|
||||
last 2 iOS major versions
|
||||
Firefox ESR
|
16
web/.editorconfig
Normal file
16
web/.editorconfig
Normal file
@@ -0,0 +1,16 @@
|
||||
# Editor configuration, see https://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.ts]
|
||||
quote_type = single
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
46
web/.gitignore
vendored
Normal file
46
web/.gitignore
vendored
Normal 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
4
web/.prettierrc
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"tabWidth": 4,
|
||||
"useTabs": false
|
||||
}
|
27
web/README.md
Normal file
27
web/README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# KdbWeb
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.0.0.
|
||||
|
||||
## Development server
|
||||
|
||||
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Running end-to-end tests
|
||||
|
||||
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
|
122
web/angular.json
Normal file
122
web/angular.json
Normal 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
7
web/dockerfile
Normal 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
44
web/karma.conf.js
Normal file
@@ -0,0 +1,44 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage'),
|
||||
require('@angular-devkit/build-angular/plugins/karma')
|
||||
],
|
||||
client: {
|
||||
jasmine: {
|
||||
// you can add configuration options for Jasmine here
|
||||
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
||||
// for example, you can disable the random execution with `random: false`
|
||||
// or set a specific seed with `seed: 4321`
|
||||
},
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
jasmineHtmlReporter: {
|
||||
suppressAll: true // removes the duplicated traces
|
||||
},
|
||||
coverageReporter: {
|
||||
dir: require('path').join(__dirname, './coverage/kdb-web'),
|
||||
subdir: '.',
|
||||
reporters: [
|
||||
{ type: 'html' },
|
||||
{ type: 'text-summary' }
|
||||
]
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false,
|
||||
restartOnFileChange: true
|
||||
});
|
||||
};
|
30
web/nginx.conf.template
Normal file
30
web/nginx.conf.template
Normal 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 [1–6]\.”;
|
||||
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
11734
web/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
55
web/package.json
Normal file
55
web/package.json
Normal 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"
|
||||
}
|
||||
}
|
23
web/src/app/app-routing.module.ts
Normal file
23
web/src/app/app-routing.module.ts
Normal 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 { }
|
37
web/src/app/app.component.html
Normal file
37
web/src/app/app.component.html
Normal 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>
|
0
web/src/app/app.component.scss
Normal file
0
web/src/app/app.component.scss
Normal file
16
web/src/app/app.component.spec.ts
Normal file
16
web/src/app/app.component.spec.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
RouterTestingModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
});
|
83
web/src/app/app.component.ts
Normal file
83
web/src/app/app.component.ts
Normal 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
83
web/src/app/app.module.ts
Normal 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);
|
||||
}
|
7
web/src/app/base/component-with-table.spec.ts
Normal file
7
web/src/app/base/component-with-table.spec.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { ComponentWithTable } from './component-with-table';
|
||||
|
||||
describe('ComponentWithTable', () => {
|
||||
it('should create an instance', () => {
|
||||
expect(new ComponentWithTable()).toBeTruthy();
|
||||
});
|
||||
});
|
44
web/src/app/base/component-with-table.ts
Normal file
44
web/src/app/base/component-with-table.ts
Normal 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));
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
<div class="content-row">
|
||||
<div class="content-column">
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-wrapper">
|
||||
<div class="content-header">
|
||||
<h2>
|
||||
{{'common.error' | translate}}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
{{'common.404' | translate}}
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { NotFoundComponent } from './not-found.component';
|
||||
|
||||
describe('NotFoundComponent', () => {
|
||||
let component: NotFoundComponent;
|
||||
let fixture: ComponentFixture<NotFoundComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ NotFoundComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(NotFoundComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@@ -0,0 +1,18 @@
|
||||
import { Location } from '@angular/common';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-not-found',
|
||||
templateUrl: './not-found.component.html',
|
||||
styleUrls: ['./not-found.component.scss']
|
||||
})
|
||||
export class NotFoundComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
public location: Location
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
28
web/src/app/components/footer/footer.component.html
Normal file
28
web/src/app/components/footer/footer.component.html
Normal 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>
|
0
web/src/app/components/footer/footer.component.scss
Normal file
0
web/src/app/components/footer/footer.component.scss
Normal file
25
web/src/app/components/footer/footer.component.spec.ts
Normal file
25
web/src/app/components/footer/footer.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { FooterComponent } from './footer.component';
|
||||
|
||||
describe('FooterComponent', () => {
|
||||
let component: FooterComponent;
|
||||
let fixture: ComponentFixture<FooterComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ FooterComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FooterComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
56
web/src/app/components/footer/footer.component.ts
Normal file
56
web/src/app/components/footer/footer.component.ts
Normal 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");
|
||||
}
|
||||
|
||||
}
|
25
web/src/app/components/header/header.component.html
Normal file
25
web/src/app/components/header/header.component.html
Normal file
@@ -0,0 +1,25 @@
|
||||
<header>
|
||||
<div class="logo-button-wrapper">
|
||||
<button pButton type="button" icon="pi pi-bars" class="btn p-button-text" (click)="toggleMenu()"></button>
|
||||
</div>
|
||||
<div class="logo">
|
||||
<h1 class="app-name">{{'header.header' | translate}}</h1>
|
||||
</div>
|
||||
<div class="header-menu logo-button-wrapper">
|
||||
<div class="logo-button-wrapper">
|
||||
<button type="button" pButton icon="pi pi-globe" class="btn icon-btn p-button-text"
|
||||
(click)="langMenu.toggle($event)"></button>
|
||||
<p-menu #langMenu [popup]="true" [model]="langList" class="lang-menu"></p-menu>
|
||||
</div>
|
||||
<div class="logo-button-wrapper">
|
||||
<button type="button" pButton icon="pi pi-palette" class="btn icon-btn p-button-text"
|
||||
(click)="themeMenu.toggle($event)"></button>
|
||||
<p-menu #themeMenu [popup]="true" [model]="themeList" class="theme-menu"></p-menu>
|
||||
</div>
|
||||
<div class="logo-button-wrapper">
|
||||
<button type="button" pButton icon="pi pi-user" class="btn icon-btn p-button-text"
|
||||
(click)="userMenu.toggle($event)"></button>
|
||||
<p-menu #userMenu [popup]="true" [model]="userMenuList" class="user-menu"></p-menu>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
0
web/src/app/components/header/header.component.scss
Normal file
0
web/src/app/components/header/header.component.scss
Normal file
25
web/src/app/components/header/header.component.spec.ts
Normal file
25
web/src/app/components/header/header.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { HeaderComponent } from './header.component';
|
||||
|
||||
describe('HeaderComponent', () => {
|
||||
let component: HeaderComponent;
|
||||
let fixture: ComponentFixture<HeaderComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ HeaderComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(HeaderComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
169
web/src/app/components/header/header.component.ts
Normal file
169
web/src/app/components/header/header.component.ts
Normal 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);
|
||||
}
|
||||
|
||||
}
|
3
web/src/app/components/sidebar/sidebar.component.html
Normal file
3
web/src/app/components/sidebar/sidebar.component.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<div class="menu">
|
||||
<p-panelMenu [model]="menuItems"></p-panelMenu>
|
||||
</div>
|
25
web/src/app/components/sidebar/sidebar.component.spec.ts
Normal file
25
web/src/app/components/sidebar/sidebar.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SidebarComponent } from './sidebar.component';
|
||||
|
||||
describe('SidebarComponent', () => {
|
||||
let component: SidebarComponent;
|
||||
let fixture: ComponentFixture<SidebarComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ SidebarComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SidebarComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
42
web/src/app/components/sidebar/sidebar.component.ts
Normal file
42
web/src/app/components/sidebar/sidebar.component.ts
Normal 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();
|
||||
}
|
||||
|
||||
}
|
7
web/src/app/components/spinner/spinner.component.html
Normal file
7
web/src/app/components/spinner/spinner.component.html
Normal 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>
|
25
web/src/app/components/spinner/spinner.component.spec.ts
Normal file
25
web/src/app/components/spinner/spinner.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SpinnerComponent } from './spinner.component';
|
||||
|
||||
describe('SpinnerComponent', () => {
|
||||
let component: SpinnerComponent;
|
||||
let fixture: ComponentFixture<SpinnerComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ SpinnerComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SpinnerComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
24
web/src/app/components/spinner/spinner.component.ts
Normal file
24
web/src/app/components/spinner/spinner.component.ts
Normal 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 {
|
||||
}
|
||||
|
||||
}
|
7
web/src/app/models/auth/admin-update-user.dto.ts
Normal file
7
web/src/app/models/auth/admin-update-user.dto.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { AuthUserDTO } from "./auth-user.dto";
|
||||
|
||||
export interface AdminUpdateUserDTO {
|
||||
authUserDTO: AuthUserDTO;
|
||||
newAuthUserDTO: AuthUserDTO;
|
||||
changePassword: boolean;
|
||||
}
|
7
web/src/app/models/auth/auth-error-messages.enum.ts
Normal file
7
web/src/app/models/auth/auth-error-messages.enum.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export enum AuthErrorMessages {
|
||||
UserIsEmpty = "User is empty",
|
||||
UserNotFound = "User not found",
|
||||
WrongPassword = "Wrong password",
|
||||
UserAlreadyExists = "User already exists",
|
||||
EMailNotConfirmed = "E-Mail not confirmed"
|
||||
}
|
4
web/src/app/models/auth/auth-roles.enum.ts
Normal file
4
web/src/app/models/auth/auth-roles.enum.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum AuthRoles {
|
||||
Normal = 0,
|
||||
Admin = 1
|
||||
}
|
12
web/src/app/models/auth/auth-user-atr-errors.ts
Normal file
12
web/src/app/models/auth/auth-user-atr-errors.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export class AuthUserAtrErrors {
|
||||
firstName: AuthUserAtrErrorType = new AuthUserAtrErrorType();
|
||||
lastName: AuthUserAtrErrorType = new AuthUserAtrErrorType();
|
||||
email: AuthUserAtrErrorType = new AuthUserAtrErrorType();
|
||||
password: AuthUserAtrErrorType = new AuthUserAtrErrorType();
|
||||
}
|
||||
|
||||
export class AuthUserAtrErrorType {
|
||||
wrongData: boolean = false;
|
||||
required: boolean = false;
|
||||
notConfirmed: boolean = false;
|
||||
}
|
33
web/src/app/models/auth/auth-user.dto.ts
Normal file
33
web/src/app/models/auth/auth-user.dto.ts
Normal 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,
|
||||
}
|
3
web/src/app/models/auth/discord-auth-url.dto.ts
Normal file
3
web/src/app/models/auth/discord-auth-url.dto.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface DiscordAuthURL {
|
||||
loginUrl: string;
|
||||
}
|
3
web/src/app/models/auth/email-string.dto.ts
Normal file
3
web/src/app/models/auth/email-string.dto.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface EMailStringDTO {
|
||||
email: string;
|
||||
}
|
6
web/src/app/models/auth/oauth.dto.ts
Normal file
6
web/src/app/models/auth/oauth.dto.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { AuthUserDTO } from "./auth-user.dto";
|
||||
|
||||
export interface OAuthDTO {
|
||||
user: AuthUserDTO;
|
||||
oAuthId: string;
|
||||
}
|
4
web/src/app/models/auth/register-error-messages.enum.ts
Normal file
4
web/src/app/models/auth/register-error-messages.enum.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum RegisterErrorMessages {
|
||||
InvalidEMail = "Invalid E-Mail",
|
||||
UserAlreadyExists = "User already exists",
|
||||
}
|
6
web/src/app/models/auth/reset-password.dto.ts
Normal file
6
web/src/app/models/auth/reset-password.dto.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { AuthUserDTO } from "./auth-user.dto";
|
||||
|
||||
export interface ResetPasswordDTO {
|
||||
id: string;
|
||||
password: string;
|
||||
}
|
5
web/src/app/models/auth/token.dto.ts
Normal file
5
web/src/app/models/auth/token.dto.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface TokenDTO {
|
||||
token: string;
|
||||
refreshToken: string;
|
||||
firstLogin?: boolean;
|
||||
}
|
6
web/src/app/models/auth/update-user.dto.ts
Normal file
6
web/src/app/models/auth/update-user.dto.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { AuthUserDTO } from "./auth-user.dto";
|
||||
|
||||
export interface UpdateUserDTO {
|
||||
authUserDTO: AuthUserDTO;
|
||||
newAuthUserDTO: AuthUserDTO;
|
||||
}
|
5
web/src/app/models/config/api-version.ts
Normal file
5
web/src/app/models/config/api-version.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface ApiVersion {
|
||||
Major: string;
|
||||
Minor: string;
|
||||
Micro: string;
|
||||
}
|
21
web/src/app/models/config/app-settings.ts
Normal file
21
web/src/app/models/config/app-settings.ts
Normal 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;
|
||||
}
|
4
web/src/app/models/config/feature-flags.model.ts
Normal file
4
web/src/app/models/config/feature-flags.model.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface FeatureFlag {
|
||||
key: string;
|
||||
value: boolean;
|
||||
}
|
27
web/src/app/models/config/server-config.model.ts
Normal file
27
web/src/app/models/config/server-config.model.ts
Normal 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[];
|
||||
}
|
16
web/src/app/models/config/settings.dto.ts
Normal file
16
web/src/app/models/config/settings.dto.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export interface SettingsDTO {
|
||||
webVersion: string;
|
||||
apiVersion: string;
|
||||
configPath: string;
|
||||
webBaseURL: string;
|
||||
apiBaseURL: string;
|
||||
|
||||
tokenExpireTime: number;
|
||||
refreshTokenExpireTime: number;
|
||||
|
||||
mailUser: string;
|
||||
mailPort: number;
|
||||
mailHost: string;
|
||||
mailTransceiver: string;
|
||||
mailTransceiverAddress: string;
|
||||
}
|
5
web/src/app/models/config/software-version.dto.ts
Normal file
5
web/src/app/models/config/software-version.dto.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface SoftwareVersionDTO {
|
||||
major: string;
|
||||
minor: string;
|
||||
micro: string
|
||||
}
|
19
web/src/app/models/config/software-version.ts
Normal file
19
web/src/app/models/config/software-version.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export class SoftwareVersion {
|
||||
Major: string;
|
||||
Minor: string;
|
||||
Micro: string;
|
||||
|
||||
constructor(
|
||||
major: string,
|
||||
minor: string,
|
||||
micro: string
|
||||
) {
|
||||
this.Major = major;
|
||||
this.Minor = minor;
|
||||
this.Micro = micro;
|
||||
}
|
||||
|
||||
getVersionString(): string {
|
||||
return `${this.Major}.${this.Minor}.${this.Micro}`;
|
||||
}
|
||||
}
|
13
web/src/app/models/config/technician-config.model.ts
Normal file
13
web/src/app/models/config/technician-config.model.ts
Normal 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[];
|
||||
}
|
29
web/src/app/models/data/achievement.model.ts
Normal file
29
web/src/app/models/data/achievement.model.ts
Normal 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;
|
||||
}
|
36
web/src/app/models/data/auto_role.model.ts
Normal file
36
web/src/app/models/data/auto_role.model.ts
Normal 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;
|
||||
}
|
16
web/src/app/models/data/client.model.ts
Normal file
16
web/src/app/models/data/client.model.ts
Normal 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;
|
||||
|
||||
}
|
17
web/src/app/models/data/data.model.ts
Normal file
17
web/src/app/models/data/data.model.ts
Normal 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;
|
||||
}
|
42
web/src/app/models/data/discord.model.ts
Normal file
42
web/src/app/models/data/discord.model.ts
Normal 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;
|
||||
}
|
17
web/src/app/models/data/level.model.ts
Normal file
17
web/src/app/models/data/level.model.ts
Normal 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;
|
||||
}
|
36
web/src/app/models/data/server.model.ts
Normal file
36
web/src/app/models/data/server.model.ts
Normal 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;
|
||||
}
|
20
web/src/app/models/data/short_role_name.model.ts
Normal file
20
web/src/app/models/data/short_role_name.model.ts
Normal 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;
|
||||
}
|
48
web/src/app/models/data/user.model.ts
Normal file
48
web/src/app/models/data/user.model.ts
Normal 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;
|
||||
}
|
11
web/src/app/models/data/user_joined_game_server.model.ts
Normal file
11
web/src/app/models/data/user_joined_game_server.model.ts
Normal 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;
|
||||
}
|
9
web/src/app/models/data/user_joined_server.model.ts
Normal file
9
web/src/app/models/data/user_joined_server.model.ts
Normal 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;
|
||||
}
|
12
web/src/app/models/data/user_joined_voice_channel.model.ts
Normal file
12
web/src/app/models/data/user_joined_voice_channel.model.ts
Normal 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;
|
||||
}
|
16
web/src/app/models/data/user_warning.model.ts
Normal file
16
web/src/app/models/data/user_warning.model.ts
Normal 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;
|
||||
}
|
8
web/src/app/models/error/error-dto.ts
Normal file
8
web/src/app/models/error/error-dto.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { ServiceErrorCode } from "./service-error-code.enum";
|
||||
|
||||
|
||||
export class ErrorDTO {
|
||||
errorCode!: ServiceErrorCode;
|
||||
message!: string;
|
||||
|
||||
}
|
17
web/src/app/models/error/service-error-code.enum.ts
Normal file
17
web/src/app/models/error/service-error-code.enum.ts
Normal 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
|
||||
}
|
4
web/src/app/models/graphql/filter/page.model.ts
Normal file
4
web/src/app/models/graphql/filter/page.model.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface Page {
|
||||
pageIndex?: number;
|
||||
pageSize?: number;
|
||||
}
|
9
web/src/app/models/graphql/filter/sort.model.ts
Normal file
9
web/src/app/models/graphql/filter/sort.model.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export interface Sort {
|
||||
sortColumn?: string;
|
||||
sortDirection?: SortDirection;
|
||||
}
|
||||
|
||||
export enum SortDirection {
|
||||
ASC = "ASC",
|
||||
DESC = "DESC",
|
||||
}
|
376
web/src/app/models/graphql/mutations.model.ts
Normal file
376
web/src/app/models/graphql/mutations.model.ts
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
550
web/src/app/models/graphql/queries.model.ts
Normal file
550
web/src/app/models/graphql/queries.model.ts
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
87
web/src/app/models/graphql/query.model.ts
Normal file
87
web/src/app/models/graphql/query.model.ts
Normal 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[];
|
||||
}
|
||||
|
88
web/src/app/models/graphql/result.model.ts
Normal file
88
web/src/app/models/graphql/result.model.ts
Normal 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
|
||||
};
|
||||
}
|
9
web/src/app/models/graphql/variables.model.ts
Normal file
9
web/src/app/models/graphql/variables.model.ts
Normal 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;
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
import { SelectCriterion } from "../select-criterion.model";
|
||||
|
||||
export interface AuthUserSelectCriterion extends SelectCriterion {
|
||||
firstName: string | null;
|
||||
lastName: string | null;
|
||||
email: string | null;
|
||||
authRole?: number | null;
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
import { AuthUserDTO } from "../../auth/auth-user.dto";
|
||||
|
||||
export interface GetFilteredAuthUsersResultDTO {
|
||||
users: AuthUserDTO[];
|
||||
totalCount: number;
|
||||
}
|
6
web/src/app/models/selection/select-criterion.model.ts
Normal file
6
web/src/app/models/selection/select-criterion.model.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface SelectCriterion {
|
||||
pageIndex: number;
|
||||
pageSize: number;
|
||||
sortDirection: string | null;
|
||||
sortColumn: string | null;
|
||||
}
|
7
web/src/app/models/utils/confirmation-dialog.ts
Normal file
7
web/src/app/models/utils/confirmation-dialog.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface ConfirmationDialog {
|
||||
key?: string;
|
||||
header: string;
|
||||
message: string;
|
||||
accept?: () => void;
|
||||
reject?: () => void;
|
||||
}
|
5
web/src/app/models/utils/toast-options.ts
Normal file
5
web/src/app/models/utils/toast-options.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface ToastOptions {
|
||||
life?: number;
|
||||
sticky?: boolean;
|
||||
closable?: boolean;
|
||||
}
|
4
web/src/app/models/view/theme.ts
Normal file
4
web/src/app/models/view/theme.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface Theme {
|
||||
Label: string;
|
||||
Name: string;
|
||||
}
|
7
web/src/app/models/view/themes.enum.ts
Normal file
7
web/src/app/models/view/themes.enum.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export enum Themes {
|
||||
Default = "sh-edraft-dark-theme",
|
||||
DefaultLight = "default-light-theme",
|
||||
DefaultDark = "default-dark-theme",
|
||||
ShEdraftLight = "sh-edraft-light-theme",
|
||||
ShEdraftDark = "sh-edraft-dark-theme",
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { AuthUserComponent } from './components/auth-user/auth-user.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{path: '', component: AuthUserComponent}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AuthUserRoutingModule { }
|
18
web/src/app/modules/admin/auth-users/auth-user.module.ts
Normal file
18
web/src/app/modules/admin/auth-users/auth-user.module.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { AuthUserRoutingModule } from './auth-user-routing.module';
|
||||
import { AuthUserComponent } from './components/auth-user/auth-user.component';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AuthUserComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
AuthUserRoutingModule,
|
||||
SharedModule
|
||||
]
|
||||
})
|
||||
export class AuthUserModule { }
|
@@ -0,0 +1,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>
|
||||
|
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AuthUserComponent } from './auth-user.component';
|
||||
|
||||
describe('AuthUserComponent', () => {
|
||||
let component: AuthUserComponent;
|
||||
let fixture: ComponentFixture<AuthUserComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ AuthUserComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AuthUserComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@@ -0,0 +1,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();
|
||||
}
|
||||
|
||||
}
|
@@ -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>
|
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SettingsComponent } from './settings.component';
|
||||
|
||||
describe('SettingsComponent', () => {
|
||||
let component: SettingsComponent;
|
||||
let fixture: ComponentFixture<SettingsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ SettingsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SettingsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@@ -0,0 +1,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"));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { SettingsComponent } from './components/settings/settings.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{path:'', component: SettingsComponent}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class SettingsRoutingModule { }
|
19
web/src/app/modules/admin/settings/settings.module.ts
Normal file
19
web/src/app/modules/admin/settings/settings.module.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { SettingsRoutingModule } from './settings-routing.module';
|
||||
import { SettingsComponent } from './components/settings/settings.component';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
SettingsComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
SettingsRoutingModule,
|
||||
SharedModule
|
||||
]
|
||||
})
|
||||
export class SettingsModule { }
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user