Merge pull request 'Ausgewählter Server besser darstellen #131' (#196) from #131 into 1.0.0

Reviewed-on: sh-edraft.de/kd_discord_bot#196
Reviewed-by: edraft-dev <dev.sven.heidemann@sh-edraft.de>
Closes #131
This commit is contained in:
Sven Heidemann 2023-02-11 10:42:03 +01:00
commit 84937dde0a
14 changed files with 19932 additions and 19901 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "kdb-web", "name": "kdb-web",
"version": "0.3.dev162-2", "version": "0.3.dev162-3",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"update-version": "ts-node-esm update-version.ts", "update-version": "ts-node-esm update-version.ts",

View File

@ -7,7 +7,7 @@ import { AuthGuard } from './modules/shared/guards/auth/auth.guard';
const routes: Routes = [ const routes: Routes = [
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' }, { path: '', redirectTo: 'dashboard', pathMatch: 'full' },
{ path: 'dashboard', loadChildren: () => import('./modules/view/dashboard/dashboard.module').then(m => m.DashboardModule), canActivate: [AuthGuard] }, { path: 'dashboard', loadChildren: () => import('./modules/view/dashboard/dashboard.module').then(m => m.DashboardModule), canActivate: [AuthGuard] },
{ path: 'server', loadChildren: () => import('./modules/view/server/server.module').then(m => m.ServerModule), 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: '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: '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: 'auth', loadChildren: () => import('./modules/auth/auth.module').then(m => m.AuthModule) },

View File

@ -1,47 +1,37 @@
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from '@angular/router'; import { LangChangeEvent, TranslateService } from "@ngx-translate/core";
import { LangChangeEvent, TranslateService } from '@ngx-translate/core'; import { MenuItem } from "primeng/api";
import { MenuItem } from 'primeng/api'; import { AuthService } from "src/app/services/auth/auth.service";
import { AuthRoles } from 'src/app/models/auth/auth-roles.enum'; import { ThemeService } from "src/app/services/theme/theme.service";
import { AuthService } from 'src/app/services/auth/auth.service'; import { SidebarService } from "../../services/sidebar/sidebar.service";
import { ServerService } from 'src/app/services/data/server.service';
import { ThemeService } from 'src/app/services/theme/theme.service';
@Component({ @Component({
selector: 'app-sidebar', selector: "app-sidebar",
templateUrl: './sidebar.component.html', templateUrl: "./sidebar.component.html",
styleUrls: ['./sidebar.component.scss'] styleUrls: ["./sidebar.component.scss"]
}) })
export class SidebarComponent implements OnInit { export class SidebarComponent implements OnInit {
isSidebarOpen: boolean = true; isSidebarOpen!: boolean;
menuItems!: MenuItem[]; menuItems!: MenuItem[];
private serverId?: number;
constructor( constructor(
private authService: AuthService, private authService: AuthService,
private translateService: TranslateService, private translateService: TranslateService,
private themeService: ThemeService, private themeService: ThemeService,
private route: ActivatedRoute, private sidebar: SidebarService
private serverService: ServerService
) { ) {
this.translateService.onLangChange.subscribe((event: LangChangeEvent) => {
this.sidebar.setMenu();
});
this.themeService.isSidebarOpen$.subscribe(value => { this.themeService.isSidebarOpen$.subscribe(value => {
this.isSidebarOpen = value; this.isSidebarOpen = value;
this.setMenu(); this.sidebar.setMenu();
}); });
this.translateService.onLangChange.subscribe((event: LangChangeEvent) => { this.sidebar.menuItems$.subscribe(value => {
this.setMenu(); this.menuItems = value;
});
this.serverService.server$.subscribe(server => {
if (!server) {
return;
}
this.serverId = server.id;
this.setMenu();
}); });
} }
@ -49,44 +39,4 @@ export class SidebarComponent implements OnInit {
this.themeService.loadMenu(); this.themeService.loadMenu();
} }
setMenu() {
this.authService.hasUserPermission(AuthRoles.Admin).then(hasPermission => {
this.menuItems = [];
this.menuItems = [
{ label: this.isSidebarOpen ? this.translateService.instant('sidebar.dashboard') : '', icon: 'pi pi-th-large', routerLink: 'dashboard' }
];
if (this.serverId) {
this.addServerMenu();
}
if (hasPermission) {
this.addAdminMenu();
}
this.menuItems = this.menuItems.slice();
});
}
addServerMenu() {
this.menuItems.push(
{
label: this.isSidebarOpen ? this.translateService.instant('sidebar.server') : '', icon: 'pi pi-server', items: [
{ label: this.isSidebarOpen ? this.translateService.instant('sidebar.settings') : '', icon: 'pi pi-cog', routerLink: 'server/settings' },
{ label: this.isSidebarOpen ? this.translateService.instant('sidebar.members') : '', icon: 'pi pi-users', routerLink: 'server/members' },
]
}
);
}
addAdminMenu() {
this.menuItems.push(
{
label: this.isSidebarOpen ? this.translateService.instant('sidebar.administration') : '', icon: 'pi pi-cog', items: [
{ label: this.isSidebarOpen ? this.translateService.instant('sidebar.config') : '', icon: 'pi pi-cog', routerLink: '/admin/settings' },
{ label: this.isSidebarOpen ? this.translateService.instant('sidebar.auth_user_list') : '', icon: 'pi pi-user-edit', routerLink: '/admin/users' },
]
},
);
}
} }

View File

@ -1,5 +1,5 @@
export class Queries { export class Queries {
static serverInfoQuery = ` static serversListQuery = `
query ServerInfo($filter: ServerFilter, $page: Page, $sort: Sort) { query ServerInfo($filter: ServerFilter, $page: Page, $sort: Sort) {
serverCount serverCount
servers(filter: $filter, page: $page, sort: $sort) { servers(filter: $filter, page: $page, sort: $sort) {
@ -10,4 +10,15 @@ export class Queries {
} }
} }
`; `;
static serversQuery = `
query ServerInfo($filter: ServerFilter, $page: Page, $sort: Sort) {
servers(filter: $filter, page: $page, sort: $sort) {
id
name
iconURL
userCount
}
}
`;
} }

View File

@ -6,7 +6,6 @@ import { LazyLoadEvent } from "primeng/api";
import { debounceTime, throwError } from "rxjs"; import { debounceTime, throwError } from "rxjs";
import { ConfirmationDialogService } from "src/app/services/confirmation-dialog/confirmation-dialog.service"; import { ConfirmationDialogService } from "src/app/services/confirmation-dialog/confirmation-dialog.service";
import { DataService } from "src/app/services/data/data.service"; import { DataService } from "src/app/services/data/data.service";
import { ServerService } from "src/app/services/data/server.service";
import { SpinnerService } from "src/app/services/spinner/spinner.service"; import { SpinnerService } from "src/app/services/spinner/spinner.service";
import { ToastService } from "src/app/services/toast/toast.service"; import { ToastService } from "src/app/services/toast/toast.service";
import { Server } from "../../../../../models/data/server.model"; import { Server } from "../../../../../models/data/server.model";
@ -15,6 +14,7 @@ import { Queries } from "../../../../../models/graphql/queries.model";
import { Page } from "../../../../../models/graphql/filter/page.model"; import { Page } from "../../../../../models/graphql/filter/page.model";
import { Sort } from "../../../../../models/graphql/filter/sort.model"; import { Sort } from "../../../../../models/graphql/filter/sort.model";
import { Query } from "../../../../../models/graphql/query.model"; import { Query } from "../../../../../models/graphql/query.model";
import { SidebarService } from "../../../../../services/sidebar/sidebar.service";
@Component({ @Component({
selector: "app-dashboard", selector: "app-dashboard",
@ -50,7 +50,7 @@ export class DashboardComponent implements OnInit {
private fb: FormBuilder, private fb: FormBuilder,
private translate: TranslateService, private translate: TranslateService,
private router: Router, private router: Router,
private serverService: ServerService private sidebar: SidebarService,
) { ) {
} }
@ -85,7 +85,7 @@ export class DashboardComponent implements OnInit {
loadNextPage() { loadNextPage() {
this.spinnerService.showSpinner(); this.spinnerService.showSpinner();
this.data.query<Query>(Queries.serverInfoQuery,{ this.data.query<Query>(Queries.serversListQuery,{
filter: this.filter, filter: this.filter,
page: this.page, page: this.page,
sort: this.sort, sort: this.sort,
@ -122,8 +122,8 @@ export class DashboardComponent implements OnInit {
} }
selectServer(server: Server) { selectServer(server: Server) {
this.serverService.server$.next(server); this.sidebar.serverName$.next(server.name ?? "");
this.router.navigate(["/server"]); this.router.navigate(["/server", server.id]);
} }
} }

View File

@ -1,5 +1,5 @@
<h1> <h1>
{{'view.dashboard.header' | translate}} {{'view.server.dashboard.header' | translate}}
</h1> </h1>
<div class="content-wrapper"> <div class="content-wrapper">
<div class="content-header"> <div class="content-header">

View File

@ -2,13 +2,15 @@ import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { Server } from "src/app/models/data/server.model"; import { Server } from "src/app/models/data/server.model";
import { DataService } from "src/app/services/data/data.service"; import { DataService } from "src/app/services/data/data.service";
import { ServerService } from "src/app/services/data/server.service";
import { SpinnerService } from "src/app/services/spinner/spinner.service"; import { SpinnerService } from "src/app/services/spinner/spinner.service";
import { Queries } from "../../../../models/graphql/queries.model";
import { Query } from "../../../../models/graphql/query.model";
import { SidebarService } from "../../../../services/sidebar/sidebar.service";
@Component({ @Component({
selector: 'app-server-dashboard', selector: "app-server-dashboard",
templateUrl: './server-dashboard.component.html', templateUrl: "./server-dashboard.component.html",
styleUrls: ['./server-dashboard.component.scss'] styleUrls: ["./server-dashboard.component.scss"]
}) })
export class ServerDashboardComponent implements OnInit { export class ServerDashboardComponent implements OnInit {
@ -20,19 +22,29 @@ export class ServerDashboardComponent implements OnInit {
private router: Router, private router: Router,
private data: DataService, private data: DataService,
private spinner: SpinnerService, private spinner: SpinnerService,
private serverService: ServerService private sidebar: SidebarService
) { } ) {
}
ngOnInit(): void { ngOnInit(): void {
this.spinner.showSpinner(); this.spinner.showSpinner();
if (!this.serverService.server$.value) { if (!this.route.snapshot.params["serverId"]) {
this.spinner.hideSpinner(); this.spinner.hideSpinner();
this.router.navigate(['/dashboard']); this.router.navigate(['/dashboard']);
return; return;
} }
this.server = this.serverService.server$.value; this.data.query<Server>(Queries.serversQuery, {
filter: { id: this.route.snapshot.params["serverId"] }
},
function(data: Query) {
return data.servers.length > 0 ? data.servers[0] : null;
}
).subscribe(server => {
this.server = server;
this.sidebar.serverName$.next(server.name ?? "");
this.spinner.hideSpinner(); this.spinner.hideSpinner();
});
} }
} }

View File

@ -27,13 +27,14 @@ export class DataService {
// ); // );
// } // }
public query<T>(query: string, variables?: Variables): Observable<T> { public query<T>(query: string, variables?: Variables, f?: Function): Observable<T> {
return this.http return this.http
.post<{ data: T }>(`${this.appsettings.getApiURL()}/api/graphql`, { .post<{ data: T }>(`${this.appsettings.getApiURL()}/api/graphql`, {
query: query, query: query,
variables: variables variables: variables
}) })
.pipe(map((d) => d.data)); .pipe(map((d) => d.data))
.pipe(map((d) => f ? f(d) : d));
} }
} }

View File

@ -1,23 +0,0 @@
import { Injectable } from "@angular/core";
import { BehaviorSubject } from "rxjs";
import { Server } from "src/app/models/data/server.model";
@Injectable({
providedIn: 'root'
})
export class ServerService {
private server!: Server;
server$ = new BehaviorSubject<Server | null>(null);
constructor() {
this.server$.subscribe(server => {
if (!server) {
return;
}
this.server = server;
});
}
}

View File

@ -1,13 +1,13 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { ServerService } from './server.service'; import { SidebarService } from './sidebar.service';
describe('ServerService', () => { describe('SidebarService', () => {
let service: ServerService; let service: SidebarService;
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({}); TestBed.configureTestingModule({});
service = TestBed.inject(ServerService); service = TestBed.inject(SidebarService);
}); });
it('should be created', () => { it('should be created', () => {

View File

@ -0,0 +1,73 @@
import { Injectable } from "@angular/core";
import { MenuItem } from "primeng/api";
import { BehaviorSubject } from "rxjs";
import { AuthRoles } from "../../models/auth/auth-roles.enum";
import { AuthService } from "../auth/auth.service";
import { TranslateService } from "@ngx-translate/core";
import { ActivatedRoute, NavigationEnd, Router } from "@angular/router";
import { ThemeService } from "../theme/theme.service";
@Injectable({
providedIn: "root"
})
export class SidebarService {
isSidebarOpen: boolean = true;
menuItems$: BehaviorSubject<MenuItem[]> = new BehaviorSubject(new Array<MenuItem>());
serverName$: BehaviorSubject<string> = new BehaviorSubject("");
constructor(
private themeService: ThemeService,
private authService: AuthService,
private translateService: TranslateService,
private router: Router,
) {
this.themeService.isSidebarOpen$.subscribe(value => {
this.isSidebarOpen = value;
this.setMenu();
});
this.serverName$.subscribe(value => {
this.setMenu();
});
this.router.events.subscribe(event => {
if (!(event instanceof NavigationEnd)) {
return;
}
});
}
setMenu() {
this.authService.hasUserPermission(AuthRoles.Admin).then(hasPermission => {
let menuItems: MenuItem[] = [
{ label: this.isSidebarOpen ? this.translateService.instant("sidebar.dashboard") : "", icon: "pi pi-th-large", routerLink: "dashboard" }
];
const serverMenu = {
label: this.isSidebarOpen ? this.serverName$.value : "", icon: "pi pi-server", items: [
{ label: this.isSidebarOpen ? this.translateService.instant("sidebar.settings") : "", icon: "pi pi-cog", routerLink: "server/settings" },
{ label: this.isSidebarOpen ? this.translateService.instant("sidebar.members") : "", icon: "pi pi-users", routerLink: "server/members" }
]
};
if (this.serverName$.value != "") {
menuItems.push(serverMenu);
} else if (menuItems.find(x => x.icon == "pi pi-server")) {
menuItems.splice(menuItems.indexOf(serverMenu), 1);
}
if (hasPermission) {
menuItems.push(
{
label: this.isSidebarOpen ? this.translateService.instant("sidebar.administration") : "", icon: "pi pi-cog", items: [
{ label: this.isSidebarOpen ? this.translateService.instant("sidebar.config") : "", icon: "pi pi-cog", routerLink: "/admin/settings" },
{ label: this.isSidebarOpen ? this.translateService.instant("sidebar.auth_user_list") : "", icon: "pi pi-user-edit", routerLink: "/admin/users" }
]
}
);
}
this.menuItems$.next(menuItems);
});
}
}

View File

@ -151,7 +151,10 @@
} }
}, },
"server": { "server": {
"header": "Server" "header": "Server",
"dashboard": {
"header": "Server dashboard"
}
}, },
"user-list": {}, "user-list": {},
"change-password": { "change-password": {

View File

@ -34,8 +34,12 @@ header,
.p-panelmenu-icon { .p-panelmenu-icon {
order: 1; // to be the first item on right side. order: 1; // to be the first item on right side.
} }
.p-menuitem-text { .p-menuitem-text {
flex-grow: 1; // to fill the whole space and push the icon to the end flex-grow: 1; // to fill the whole space and push the icon to the end
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
.p-panelmenu-header > a { .p-panelmenu-header > a {