Added members view #130

This commit is contained in:
Sven Heidemann 2023-02-17 17:53:54 +01:00
parent f44f77d7e5
commit 85452c9a74
24 changed files with 724 additions and 49 deletions

View File

@ -180,12 +180,12 @@ class QueryABC(ObjectType):
# @FilterABC.resolve_filter_annotation
def _resolve_collection(self, collection: List, *_, filter: FilterABC = None, page: Page = None, sort: Sort = None):
if filter is not None:
return filter.filter(collection)
collection = filter.filter(collection)
if page is not None:
return page.filter(collection)
collection = page.filter(collection)
if sort is not None:
return sort.filter(collection)
collection = sort.filter(collection)
return collection

View File

@ -25,7 +25,7 @@ class LevelFilter(FilterABC):
if "server" in values:
from bot_graphql.filter.server_filter import ServerFilter
self._server: ServerFilter = self._services.get_service(LevelFilter)
self._server: ServerFilter = self._services.get_service(ServerFilter)
self._server.from_dict(values["server"])
def filter(self, query: List[Level]) -> List[Level]:

View File

@ -5,6 +5,7 @@ from cpl_discord.service import DiscordBotServiceABC
from cpl_query.extension import List
from bot_core.abc.client_utils_abc import ClientUtilsABC
from bot_data.abc.user_joined_server_repository_abc import UserJoinedServerRepositoryABC
from bot_data.model.user import User
from bot_graphql.abc.filter_abc import FilterABC
from bot_graphql.filter.level_filter import LevelFilter
@ -18,6 +19,7 @@ class UserFilter(FilterABC):
bot: DiscordBotServiceABC,
client_utils: ClientUtilsABC,
levels: LevelService,
user_joined_servers: UserJoinedServerRepositoryABC,
):
FilterABC.__init__(self)
@ -25,6 +27,7 @@ class UserFilter(FilterABC):
self._bot = bot
self._client_utils = client_utils
self._levels = levels
self._user_joined_servers = user_joined_servers
self._id = None
self._discord_id = None
@ -34,6 +37,7 @@ class UserFilter(FilterABC):
self._ontime = None
self._level: Optional[LevelFilter] = None
self._server = None
self._left_server = None
def from_dict(self, values: dict):
if "id" in values:
@ -64,18 +68,26 @@ class UserFilter(FilterABC):
self._server: ServerFilter = self._services.get_service(ServerFilter)
self._server.from_dict(values["server"])
if "leftServer" in values:
self._left_server = values["leftServer"]
def filter(self, query: List[User]) -> List[User]:
if self._id is not None:
query = query.where(lambda x: x.user_id == self._id)
query = query.where(lambda x: x.user_id == self._id or str(self._id) in str(x.user_id))
if self._discord_id is not None:
query = query.where(lambda x: x.discord_id == self._discord_id)
query = query.where(
lambda x: x.discord_id == self._discord_id or str(self._discord_id) in str(x.discord_id)
)
if self._name is not None:
query = query.where(
lambda x: self._bot.get_user(x.discord_id).name == self._name
or self._name in self._bot.get_user(x.discord_id).name
)
def _get_member(user: User):
guild = self._bot.get_guild(user.server.discord_server_id)
member = guild.get_member(user.discord_id)
return member is not None and (member.name == self._name or self._name in member.name)
query = query.where(_get_member)
if self._xp is not None:
query = query.where(lambda x: x.xp == self._xp)
@ -94,4 +106,12 @@ class UserFilter(FilterABC):
servers = self._server.filter(query.select(lambda x: x.server)).select(lambda x: x.server_id)
query = query.where(lambda x: x.server.server_id in servers)
if self._left_server is not None:
def _has_user_left_server(user: User):
active_join = self._user_joined_servers.find_active_user_joined_server_by_user_id(user.user_id)
return (active_join is None) == self._left_server
query = query.where(_has_user_left_server)
return query

View File

@ -17,6 +17,7 @@ type User implements TableQuery {
userJoinedGameServers(filter: UserJoinedGameServerFilter, page: Page, sort: Sort): [UserJoinedGameServer]
server: Server
leftServer: Boolean
createdAt: String
modifiedAt: String
@ -31,6 +32,7 @@ input UserFilter {
ontime: Float
level: LevelFilter
server: ServerFilter
leftServer: Boolean
}
type UserMutation {
@ -40,4 +42,5 @@ type UserMutation {
input UserInput {
id: ID
xp: Int
levelId: ID
}

View File

@ -1,10 +1,12 @@
from cpl_core.database.context import DatabaseContextABC
from cpl_discord.service import DiscordBotServiceABC
from bot_data.abc.level_repository_abc import LevelRepositoryABC
from bot_data.abc.server_repository_abc import ServerRepositoryABC
from bot_data.abc.user_repository_abc import UserRepositoryABC
from bot_data.model.user_role_enum import UserRoleEnum
from bot_graphql.abc.query_abc import QueryABC
from modules.level.service.level_service import LevelService
from modules.permission.service.permission_service import PermissionService
@ -16,6 +18,8 @@ class UserMutation(QueryABC):
bot: DiscordBotServiceABC,
db: DatabaseContextABC,
permissions: PermissionService,
levels: LevelRepositoryABC,
level_service: LevelService,
):
QueryABC.__init__(self, "UserMutation")
@ -24,6 +28,8 @@ class UserMutation(QueryABC):
self._bot = bot
self._db = db
self._permissions = permissions
self._levels = levels
self._level_service = level_service
self.set_field("updateUser", self.resolve_update_user)
@ -33,8 +39,13 @@ class UserMutation(QueryABC):
user.xp = input["xp"] if "xp" in input else user.xp
if "levelId" in input:
level = self._levels.get_level_by_id(input["levelId"])
user.xp = level.min_xp
self._users.update_user(user)
self._db.save_changes()
self._level_service.set_level(user)
user = self._users.get_user_by_id(input["id"])
return user

View File

@ -57,6 +57,7 @@ class UserQuery(DataQueryABC):
UserJoinedGameServerFilter,
)
self.set_field("server", self.resolve_server)
self.set_field("leftServer", self.resolve_left_server)
@staticmethod
def resolve_id(user: User, *_):
@ -88,3 +89,6 @@ class UserQuery(DataQueryABC):
@staticmethod
def resolve_server(user: User, *_):
return user.server
def resolve_left_server(self, user: User, *_):
return self._ujs.find_active_user_joined_server_by_user_id(user.user_id) is None

View File

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

View File

@ -1,8 +1,10 @@
import { Data } from "./data.model";
import { User } from "./user.model";
import { Level } from "./level.model";
export interface Server extends Data {
id?: number;
discordId?: number;
discordId?: String;
name?: string;
iconURL?: string;
autoRoleCount?: number;
@ -10,7 +12,13 @@ export interface Server extends Data {
clientCount?: number;
clients?: [];
levelCount?: number;
levels?: [];
levels?: Level[];
userCount?: number;
users?: [];
users?: User[];
}
export interface ServerFilter {
id?: number;
discordId?: String;
name?: String;
}

View File

@ -0,0 +1,36 @@
import { Data } from "./data.model";
import { Level, LevelFilter } from "./level.model";
import { Server, ServerFilter } from "./server.model";
export interface User extends Data {
id?: number;
discordId?: number;
name?: string;
xp?: number;
minecraftId?: number;
ontime?: number;
level?: Level;
server?: Server;
leftServer?: boolean;
joinedServerCount?: number;
joinedServers?: [];
joinedVoiceChannelCount?: number;
joinedVoiceChannels?: [];
userJoinedGameServerCount?: number;
userJoinedGameServers?: [];
}
export interface UserFilter {
id?: number;
discordId?: number;
name?: string;
xp?: number;
minecraftId?: number;
ontime?: number;
level?: LevelFilter;
server?: ServerFilter;
leftServer?: boolean;
}

View File

@ -0,0 +1,17 @@
export class Mutations {
static updateUser = `
mutation updateUser($id: ID, $xp: Int, $levelId: ID) {
user {
updateUser(input: { id: $id, xp: $xp, levelId: $levelId }) {
id
name
xp
level {
id
name
}
}
}
}
`;
}

View File

@ -21,4 +21,61 @@ export class Queries {
}
}
`;
static levelQuery= `
query LevelsList($filter: LevelFilter, $page: Page, $sort: Sort) {
levelCount
levels(filter: $filter, page: $page, sort: $sort) {
id
name
color
minXp
permissions
server {
id
name
}
}
}
`;
static usersQuery= `
query UsersList($filter: UserFilter, $page: Page, $sort: Sort) {
userCount
users(filter: $filter, page: $page, sort: $sort) {
id
discordId
name
xp
ontime
level {
id
name
}
server {
id
name
}
leftServer
joinedServerCount
joinedServers {
id
}
joinedVoiceChannelCount
joinedVoiceChannels {
id
channelId
channelName
}
userJoinedGameServerCount
userJoinedGameServers {
id
gameServer
}
}
}
`;
}

View File

@ -1,6 +1,18 @@
import { Server } from "../data/server.model";
import { User } from "../data/user.model";
export interface Query {
serverCount: number;
servers: Server[];
}
export interface UserListQuery {
userCount: number;
users: User[];
}
export interface LevelListQuery {
levelCount: number;
levels: User[];
}

View File

@ -1,5 +1,11 @@
import { Query } from "./query.model";
import { User } from "../data/user.model";
export interface QueryResult {
data: any;
}
export interface UpdateUserMutationResult {
user: {
updateUser: User
};
}

View File

@ -32,7 +32,7 @@
<th pSortableColumn="firstName">
<div class="table-header-label">
<div class="table-header-text">{{'admin.auth_users.headers.first_name' | translate}}</div>
<p-sortIcon icon="sort" field="firstName" class="table-header-icon"></p-sortIcon>
<p-sortIcon field="firstName" class="table-header-icon"></p-sortIcon>
</div>
</th>
@ -70,24 +70,11 @@
</div>
</th>
<th class="table-header-small-dropdown">
<div class="table-header-label">
<div class="table-header-text">{{'admin.auth_users.headers.created_at' | translate}}</div>
</div>
</th>
<th class="table-header-small-dropdown">
<div class="table-header-label">
<div class="table-header-text">{{'admin.auth_users.headers.modified_at' | translate}}</div>
</div>
</th>
<th class="table-header-actions">
<div class="table-header-label">
<div class="table-header-text">{{'admin.auth_users.headers.actions' | translate}}</div>
</div>
</th>
</tr>
<tr>
<th>
@ -108,7 +95,7 @@
<th></th>
<th>
<form [formGroup]="filterForm">
<p-dropdown formControlName="authRole" [options]="authRoles"></p-dropdown>
<p-dropdown formControlName="authRole" [options]="authRoles" placeholder="{{'admin.auth_users.headers.auth_role' | translate}}"></p-dropdown>
</form>
</th>
<th></th>
@ -223,7 +210,7 @@
</tr>
</ng-template>
<ng-template pTemplate="Invalidmessage">
<ng-template pTemplate="emptymessage">
<tr>
<td colspan="7">{{'admin.auth_users.no_entries_found' | translate}}</td>
</tr>
@ -234,3 +221,4 @@
</p-table>
</div>
</div>

View File

@ -0,0 +1,192 @@
<h1>
{{'view.server.members.header' | translate}}
</h1>
<div class="content-wrapper">
<div class="content">
<p-table #dt [value]="members" dataKey="id" editMode="row" [rowHover]="true" [rows]="10"
[rowsPerPageOptions]="[10,25,50]" [paginator]="true" [loading]="loading" [totalRecords]="totalRecords"
[lazy]="true" (onLazyLoad)="nextPage($event)">
<ng-template pTemplate="caption">
<div class="table-caption">
<div class="table-caption-text">
<ng-container *ngIf="!loading">{{members.length}} {{'view.server.members.of' | translate}}
{{dt.totalRecords}}
</ng-container>
{{'view.server.members.members' | translate}}
</div>
<div class="table-caption-btn-wrapper btn-wrapper">
<button pButton label="{{'view.server.members.reset_filters' | translate}}" icon="pi pi-undo"
class="icon-btn btn" (click)="resetFilters()">
</button>
</div>
</div>
</ng-template>
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="id">
<div class="table-header-label">
<div class="table-header-text">{{'view.server.members.headers.id' | translate}}</div>
<p-sortIcon field="id" class="table-header-icon"></p-sortIcon>
</div>
</th>
<th pSortableColumn="discordId">
<div class="table-header-label">
<div class="table-header-text">{{'view.server.members.headers.discord_id' | translate}}</div>
<p-sortIcon field="discordId" class="table-header-icon"></p-sortIcon>
</div>
</th>
<th pSortableColumn="name">
<div class="table-header-label">
<div class="table-header-text">{{'view.server.members.headers.name' | translate}}</div>
<p-sortIcon field="name" class="table-header-icon"></p-sortIcon>
</div>
</th>
<th pSortableColumn="xp">
<div class="table-header-label">
<div class="table-header-text">{{'view.server.members.headers.xp' | translate}}</div>
<p-sortIcon field="xp" class="table-header-icon"></p-sortIcon>
</div>
</th>
<th pSortableColumn="ontime">
<div class="table-header-label">
<div class="table-header-text">{{'view.server.members.headers.ontime' | translate}}</div>
<p-sortIcon field="ontime" class="table-header-icon"></p-sortIcon>
</div>
</th>
<th class="table-header-small-dropdown" pSortableColumn="level">
<div class="table-header-label">
<div class="table-header-text">{{'view.server.members.headers.level' | translate}}</div>
<p-sortIcon field="level" class="table-header-icon"></p-sortIcon>
</div>
</th>
<th class="table-header-actions">
<div class="table-header-label">
<div class="table-header-text">{{'view.server.members.headers.actions' | translate}}</div>
</div>
</th>
</tr>
<tr>
<th>
<form [formGroup]="filterForm">
<input type="text" pInputText formControlName="id" placeholder="{{'view.server.members.headers.id' | translate}}">
</form>
</th>
<th>
<form [formGroup]="filterForm">
<input type="text" pInputText formControlName="discordId" placeholder="{{'view.server.members.headers.discord_id' | translate}}">
</form>
</th>
<th>
<form [formGroup]="filterForm">
<input type="text" pInputText formControlName="name" placeholder="{{'view.server.members.headers.name' | translate}}">
</form>
</th>
<th></th>
<th></th>
<th>
<form [formGroup]="filterForm">
<p-dropdown formControlName="level" [options]="levels" placeholder="{{'view.server.members.headers.level' | translate}}"></p-dropdown>
</form>
</th>
<th></th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-member let-editing="editing" let-ri="rowIndex">
<tr [pEditableRow]="member">
<td>
<p-cellEditor>
<ng-template pTemplate="input">
{{member.id}}
</ng-template>
<ng-template pTemplate="output">
{{member.id}}
</ng-template>
</p-cellEditor>
</td>
<td>
<p-cellEditor>
<ng-template pTemplate="input">
{{member.discordId}}
</ng-template>
<ng-template pTemplate="output">
{{member.discordId}}
</ng-template>
</p-cellEditor>
</td>
<td>
<p-cellEditor>
<ng-template pTemplate="input">
{{member.name}}
</ng-template>
<ng-template pTemplate="output">
{{member.name}}
</ng-template>
</p-cellEditor>
</td>
<td>
<p-cellEditor>
<ng-template pTemplate="input">
<input class="table-edit-input" pInputText type="number" [(ngModel)]="member.xp">
</ng-template>
<ng-template pTemplate="output">
{{member.xp}}
</ng-template>
</p-cellEditor>
</td>
<td>
<p-cellEditor>
<ng-template pTemplate="input">
{{member.ontime}}
</ng-template>
<ng-template pTemplate="output">
{{member.ontime}}
</ng-template>
</p-cellEditor>
</td>
<td>
<p-cellEditor>
<ng-template pTemplate="input">
<p-dropdown [options]="levels" [(ngModel)]="member.level" placeholder="{{'view.server.members.headers.level' | translate}}"></p-dropdown>
</ng-template>
<ng-template pTemplate="output">
{{member.level.name}}
</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, member, ri)"></button>
<button *ngIf="editing" pButton pSaveEditableRow class="btn icon-btn"
icon="pi pi-check-circle" (click)="onRowEditSave(dt, member, ri)"></button>
<button *ngIf="editing" pButton pCancelEditableRow class="btn icon-btn danger-icon-btn"
icon="pi pi-times-circle" (click)="onRowEditCancel(member, ri)"></button>
</div>
</td>
</tr>
</ng-template>
<ng-template pTemplate="emptymessage">
<tr></tr>
<tr>
<td colspan="7">{{'view.server.members.no_entries_found' | translate}}</td>
</tr>
<tr></tr>
</ng-template>
<ng-template pTemplate="paginatorleft">
</ng-template>
</p-table>
</div>
</div>

View File

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

View File

@ -0,0 +1,235 @@
import { Component } from "@angular/core";
import { FormBuilder, FormControl, FormGroup } from "@angular/forms";
import { AuthService } from "../../../../services/auth/auth.service";
import { SpinnerService } from "../../../../services/spinner/spinner.service";
import { ToastService } from "../../../../services/toast/toast.service";
import { ConfirmationDialogService } from "../../../../services/confirmation-dialog/confirmation-dialog.service";
import { TranslateService } from "@ngx-translate/core";
import { catchError, debounceTime } from "rxjs/operators";
import { LazyLoadEvent, MenuItem } from "primeng/api";
import { Table } from "primeng/table";
import { User, UserFilter } from "../../../../models/data/user.model";
import { Queries } from "../../../../models/graphql/queries.model";
import { LevelListQuery, UserListQuery } from "../../../../models/graphql/query.model";
import { DataService } from "../../../../services/data/data.service";
import { Page } from "../../../../models/graphql/filter/page.model";
import { Sort } from "../../../../models/graphql/filter/sort.model";
import { SidebarService } from "../../../../services/sidebar/sidebar.service";
import { Mutations } from "../../../../models/graphql/mutations.model";
import { throwError } from "rxjs";
import { UpdateUserMutationResult } from "../../../../models/graphql/result.model";
@Component({
selector: "app-members",
templateUrl: "./members.component.html",
styleUrls: ["./members.component.scss"]
})
export class MembersComponent {
members!: User[];
// levelsFilter!: MenuItem[];
levels!: MenuItem[];
loading = true;
clonedUsers: { [s: string]: User; } = {};
isEditingNew: boolean = false;
newUserTemplate: User = {
id: 0,
discordId: 0,
name: "",
xp: 0,
ontime: 0,
level: undefined,
server: undefined,
joinedServerCount: 0,
joinedServers: [],
joinedVoiceChannelCount: 0,
joinedVoiceChannels: [],
userJoinedGameServerCount: 0,
userJoinedGameServers: [],
createdAt: "",
modifiedAt: ""
};
filterForm!: FormGroup<{
id: FormControl<number | null>,
discordId: FormControl<number | null>,
name: FormControl<string | null>,
level: FormControl<number | null>
}>;
filter: UserFilter = {
leftServer: false
};
page: Page = {
pageSize: undefined,
pageIndex: undefined
};
sort: Sort = {
sortColumn: undefined,
sortDirection: undefined
};
totalRecords!: number;
constructor(
private authService: AuthService,
private spinner: SpinnerService,
private toastService: ToastService,
private confirmDialog: ConfirmationDialogService,
private fb: FormBuilder,
private translate: TranslateService,
private data: DataService,
private sidebar: SidebarService
) {
}
ngOnInit(): void {
this.spinner.showSpinner();
this.data.query<LevelListQuery>(Queries.levelQuery, {
filter: {
server: { id: this.sidebar.server$.value?.id }
}
}
).subscribe(data => {
this.levels = data.levels.map(level => {
return { label: level.name, value: level };
});
this.spinner.hideSpinner();
});
this.setFilterForm();
this.loadNextPage();
}
setFilterForm() {
this.filterForm = this.fb.group({
id: new FormControl<number | null>(null),
discordId: new FormControl<number | null>(null),
name: [""],
level: new FormControl<number | null>(null)
});
this.filterForm.valueChanges.pipe(
debounceTime(600)
).subscribe(changes => {
if (changes.id) {
this.filter.id = changes.id;
} else {
this.filter.id = undefined;
}
if (changes.discordId) {
this.filter.discordId = changes.discordId;
} else {
this.filter.discordId = undefined;
}
if (changes.name) {
this.filter.name = changes.name;
} else {
this.filter.name = undefined;
}
if (changes.level) {
this.filter.level = {
id: changes.level
};
} else {
this.filter.level = undefined;
}
if (this.page.pageSize)
this.page.pageSize = 10;
if (this.page.pageIndex)
this.page.pageIndex = 0;
this.loadNextPage();
});
}
loadNextPage() {
this.loading = true;
this.data.query<UserListQuery>(Queries.usersQuery, {
filter: this.filter, page: this.page, sort: this.sort
}
).subscribe(data => {
this.totalRecords = data.userCount;
this.members = data.users;
this.spinner.hideSpinner();
this.loading = false;
});
}
nextPage(event: LazyLoadEvent) {
this.page.pageSize = event.rows ?? 0;
if (event.first != null && event.rows != null)
this.page.pageIndex = event.first / event.rows;
this.sort.sortColumn = event.sortField ?? undefined;
this.sort.sortDirection = event.sortOrder === 1 ? "asc" : event.sortOrder === -1 ? "desc" : "asc";
this.loadNextPage();
}
resetFilters() {
this.filterForm.reset();
}
onRowEditInit(table: Table, user: User, index: number) {
this.clonedUsers[index] = { ...user };
}
onRowEditSave(table: Table, newUser: User, index: number) {
// const oldUser = this.clonedUsers[index];
// delete this.clonedUsers[index];
// if (JSON.stringify(oldUser) === JSON.stringify(newUser) && !this.isEditingNew) {
// console.log(1, oldUser, newUser, JSON.stringify(oldUser) === JSON.stringify(newUser), !this.isEditingNew);
// return;
// }
if (this.isEditingNew && JSON.stringify(newUser) === JSON.stringify(this.newUserTemplate)) {
this.isEditingNew = false;
this.members.splice(index, 1);
return;
}
if (!newUser.id || !newUser.xp && !newUser.level?.id) {
return;
}
this.spinner.showSpinner();
this.data.mutation<UpdateUserMutationResult>(Mutations.updateUser, {
id: newUser.id,
xp: newUser.xp,
levelId: newUser.level?.id
}
).pipe(catchError(err => {
this.spinner.hideSpinner();
this.toastService.error(this.translate.instant("view.server.members.message.user_change_failed"), this.translate.instant("view.server.members.message.user_change_failed_d", { name: newUser.name }));
return throwError(err);
})).subscribe(_ => {
this.spinner.hideSpinner();
this.toastService.success(this.translate.instant("view.server.members.message.user_changed"), this.translate.instant("view.server.members.message.user_changed_d", { name: newUser.name }));
this.loadNextPage();
});
}
onRowEditCancel(user: User, index: number) {
if (this.isEditingNew) {
this.members.splice(index, 1);
delete this.clonedUsers[index];
this.isEditingNew = false;
return;
}
this.members[index] = this.clonedUsers[index];
delete this.clonedUsers[index];
}
}

View File

@ -2,10 +2,12 @@ import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { ServerDashboardComponent } from "./server-dashboard/server-dashboard.component";
import { ProfileComponent } from "./profile/profile.component";
import { MembersComponent } from "./members/members.component";
const routes: Routes = [
{ path: '', component: ServerDashboardComponent },
{ path: 'profile', component: ProfileComponent },
{ path: 'members', component: MembersComponent },
];
@NgModule({

View File

@ -4,13 +4,15 @@ import { ServerDashboardComponent } from './server-dashboard/server-dashboard.co
import { ServerRoutingModule } from './server-routing.module';
import { SharedModule } from '../../shared/shared.module';
import { ProfileComponent } from './profile/profile.component';
import { MembersComponent } from './members/members.component';
@NgModule({
declarations: [
ServerDashboardComponent,
ProfileComponent
ProfileComponent,
MembersComponent
],
imports: [
CommonModule,

View File

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

View File

@ -65,6 +65,7 @@
"first_name": "Vorname",
"last_name": "Nachname",
"e_mail": "E-Mail",
"auth_role": "Rolle",
"active": "Aktiv",
"role": "Rolle",
"password": "Passwort",
@ -160,6 +161,29 @@
"header": "Server",
"dashboard": {
"header": "Server dashboard"
},
"members": {
"header": "Mitglieder",
"of": "von",
"add": "Hinzufügen",
"reset_filters": "Filter zurücksetzen",
"members": "Mitgliedern",
"headers": {
"id": "ID",
"discord_id": "Discord ID",
"name": "Name",
"xp": "XP",
"ontime": "Ontime",
"level": "Level",
"actions": "Aktionen"
},
"no_entries_found": "Keine Einträge gefunden",
"message": {
"user_changed": "Benutzer geändert",
"user_changed_d": "Benutzer {{name}} erfolgreich geändert",
"user_change_failed": "Benutzer änderung fehlgeschlagen",
"user_change_failed_d": "Benutzer {{name}} konnte nicht geändert werden!"
}
}
},
"user-list": {},

View File

@ -60,7 +60,7 @@ header {
text-align: right;
flex: 1;
justify-content: flex-end;
margin: 0px 5px 0px 0px;
margin: 0 5px 0 0;
}
.p-menu-overlay {
@ -122,7 +122,7 @@ header {
display: flex;
flex-direction: row;
flex: 1;
margin: 1.5px 0px;
margin: 1.5px 0;
}
.content-column {
@ -147,7 +147,7 @@ header {
}
.content-divider {
margin: 5px 0px;
margin: 5px 0;
}
.content-input-field {
@ -165,7 +165,7 @@ header {
}
.input-field-info-text {
margin: 15px 0px;
margin: 15px 0;
width: 100%;
}
@ -212,6 +212,10 @@ header {
}
}
.table-header-small {
width: 25px;
}
.table-header-actions {
width: 100px;
}
@ -260,7 +264,7 @@ header {
gap: 10px;
.name {
margin: 0px;
margin: 0;
justify-content: center;
align-items: center;
@ -287,7 +291,7 @@ header {
}
.p-dialog-footer {
padding: 0px 20px 20px 20px !important;
padding: 0 20px 20px 20px !important;
}
.p-dialog-content {
@ -295,7 +299,7 @@ header {
display: flex;
flex-direction: row;
flex: 1;
margin: 1.5px 0px;
margin: 1.5px 0;
}
.content-column {
@ -320,7 +324,7 @@ header {
}
.content-divider {
margin: 5px 0px;
margin: 5px 0;
}
.content-input-field {
@ -332,7 +336,7 @@ header {
footer {
width: 100%;
height: $footerHeight;
padding: 0px 10px;
padding: 0 10px;
display: flex;
align-items: center;
@ -347,7 +351,7 @@ footer {
// }
.version-divider {
margin: 0px 5px;
margin: 0 5px;
}
// .backend-version {
@ -394,7 +398,7 @@ footer {
}
.input-field-info-text {
margin: 15px 0px;
margin: 15px 0;
width: 100%;
}
@ -435,7 +439,7 @@ footer {
}
.input-field {
margin: 15px 0px;
margin: 15px 0;
input,
.p-password {

View File

@ -11,15 +11,15 @@
background: none !important;
border: none !important;
width: auto !important;
border-radius: 0px !important;
border-radius: 0 !important;
padding: 0 !important;
.p-menuitem-link,
.p-panelmenu-header > a,
.p-panelmenu-content .p-menuitem .p-menuitem-link {
$distance: 10px;
padding: $distance 0px $distance $distance !important;
margin: 4px 0px 4px 6px !important;
padding: $distance 0 $distance $distance !important;
margin: 4px 0 4px 6px !important;
}
}
@ -44,8 +44,8 @@ header,
.p-panelmenu-header > a {
border: none !important;
border-radius: none !important;
font-weight: none !important;
border-radius: unset !important;
font-weight: unset !important;
transition: none !important;
}
@ -71,8 +71,12 @@ ui-menu .ui-menu-parent .ui-menu-child {
box-shadow: none !important;
}
.p-datatable > .p-datatable-wrapper {
overflow: visible !important;
}
.p-password {
padding: 0px !important;
padding: 0 !important;
}
.p-paginator {