Added server config to frontend #127

This commit is contained in:
Sven Heidemann 2023-08-14 18:06:32 +02:00
parent 5d36f1188a
commit 6c39cd1ae1
30 changed files with 699 additions and 276 deletions

View File

@ -150,6 +150,12 @@ class ServerConfigMutation(QueryABC):
self._server_configs.delete_server_team_role_id_config(role_id)
for role_id in new_config.team_role_ids:
guild = self._bot.get_guild(new_config.server.discord_id)
role = guild.get_role(int(role_id.role_id))
if role is None:
raise ValueError(f"Invalid roleId")
for role_id in new_config.team_role_ids:
if role_id.role_id in old_config.team_role_ids.select(lambda x: str(x.role_id)):
continue

View File

@ -0,0 +1,21 @@
import { DataWithHistory } from "../data/data.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;
afkCommandChannelId?: string;
helpVoiceChannelId?: string;
teamChannelId?: string;
loginMessageChannelId?: string;
afkChannelIds: string[];
moderatorRoleIds: string[];
adminRoleIds: string[];
}

View File

@ -6,6 +6,6 @@ export interface TechnicianConfig extends DataWithHistory {
waitForRestart?: number;
waitForShutdown?: number;
cacheMaxMessages?: number;
pingURLs?: string[];
technicianIds?: string[];
pingURLs: string[];
technicianIds: string[];
}

View File

@ -166,8 +166,6 @@ export class Mutations {
}
`;
static updateTechnicianConfig = `
mutation updateTechnicianConfig($id: ID, $helpCommandReferenceUrl: String, $waitForRestart: Int, $waitForShutdown: Int, $cacheMaxMessages: Int, $pingURLs: [String], $technicianIds: [String]) {
technicianConfig {
@ -191,4 +189,70 @@ export class Mutations {
}
}
`;
static updateServerConfig = `
mutation updateServerConfig(
$id: ID,
$messageDeleteTimer: Int,
$notificationChatId: String,
$maxVoiceStateHours: Int,
$xpPerMessage: Int,
$xpPerReaction: Int,
$maxMessageXpPerHour: Int,
$xpPerOntimeHour: Int,
$xpPerEventParticipation: Int,
$xpPerAchievement: Int,
$afkCommandChannelId: String,
$helpVoiceChannelId: String,
$teamChannelId: String,
$loginMessageChannelId: String,
$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
afkCommandChannelId: $afkCommandChannelId
helpVoiceChannelId: $helpVoiceChannelId
teamChannelId: $teamChannelId
loginMessageChannelId: $loginMessageChannelId
afkChannelIds: $afkChannelIds
moderatorRoleIds: $moderatorRoleIds
adminRoleIds: $adminRoleIds
}) {
id
messageDeleteTimer
notificationChatId
maxVoiceStateHours
xpPerMessage
xpPerReaction
maxMessageXpPerHour
xpPerOntimeHour
xpPerEventParticipation
xpPerAchievement
afkCommandChannelId
helpVoiceChannelId
teamChannelId
loginMessageChannelId
afkChannelIds
moderatorRoleIds
adminRoleIds
server {
id
}
}
}
}
`;
}

View File

@ -1,22 +1,5 @@
export class Queries {
static technicianConfigQuery = `
query technicianConfigQuery {
technicianConfig {
id
helpCommandReferenceUrl
waitForRestart
waitForShutdown
cacheMaxMessages
pingURLs
technicianIds
createdAt
modifiedAt
}
}
`;
static guildsQuery = `
query GuildsQuery($id: ID) {
guilds(filter: {id: $id}) {
@ -71,7 +54,7 @@ export class Queries {
}
}
}
`
`;
static levelQuery = `
query LevelsList($serverId: ID, $filter: LevelFilter, $page: Page, $sort: Sort) {
@ -361,4 +344,52 @@ export class Queries {
}
}
`;
static technicianConfigQuery = `
query technicianConfigQuery {
technicianConfig {
id
helpCommandReferenceUrl
waitForRestart
waitForShutdown
cacheMaxMessages
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
afkCommandChannelId
helpVoiceChannelId
teamChannelId
loginMessageChannelId
afkChannelIds
moderatorRoleIds
adminRoleIds
server {
id
}
}
}
}
`;
}

View File

@ -5,6 +5,7 @@ import { 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";
export interface Query {
serverCount: number;
@ -15,6 +16,10 @@ export interface TechnicianConfigQuery {
technicianConfig: TechnicianConfig;
}
export interface ServerConfigQuery {
config: ServerConfig;
}
export interface SingleDiscordQuery {
guilds: Guild[];
}

View File

@ -4,6 +4,7 @@ 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";
export interface GraphQLResult {
data: {
@ -54,6 +55,12 @@ export interface TechnicianConfigMutationResult {
};
}
export interface ServerConfigMutationResult {
serverConfig: {
updateServerConfig?: ServerConfig
};
}
export interface AchievementMutationResult {
achievement: {
createAchievement?: Achievement

View File

@ -160,103 +160,13 @@
</div>
</div>
<div class="content-divider"></div>
<div class="content-row">
<div class="content-column">
<div class="content-data-name">
{{'admin.settings.bot.ping_urls' | translate}}:
</div>
<div class="content-data-value-row">
<p-table #dt [value]="pingUrls" dataKey="id" editMode="row">
<ng-template pTemplate="caption">
<div class="table-caption">
<div class="table-caption-btn-wrapper btn-wrapper">
<button pButton class="icon-btn btn"
icon="pi pi-plus" (click)="pingUrlsAddNew(dt)">
</button>
</div>
</div>
</ng-template>
<ng-template pTemplate="body" let-pingUrl let-editing="editing" let-ri="rowIndex">
<tr [pEditableRow]="pingUrl">
<td>
<p-cellEditor>
<ng-template pTemplate="input">
<input class="table-edit-input" pInputText type="text" [(ngModel)]="pingUrl.value">
</ng-template>
<ng-template pTemplate="output">
{{pingUrl.value}}
</ng-template>
</p-cellEditor>
</td>
<td>
<div class="btn-wrapper">
<button *ngIf="!editing" pButton type="button" pInitEditableRow class="btn icon-btn" icon="pi pi-pencil" (click)="pingUrlsEditInit(pingUrl, ri)"></button>
<button *ngIf="!editing" pButton type="button" class="btn danger-icon-btn" icon="pi pi-trash" (click)="pingUrlsDelete(ri)"></button>
<button *ngIf="editing" pButton type="button" pSaveEditableRow class="btn icon-btn" icon="pi pi-check" (click)="pingUrlsEditSave(pingUrl, ri)"></button>
<button *ngIf="editing" pButton type="button" pCancelEditableRow class="btn danger-icon-btn" icon="pi pi-times"
(click)="pingUrlsEditCancel(pingUrl, ri)"></button>
</div>
</td>
</tr>
</ng-template>
</p-table>
</div>
</div>
</div>
<div class="content-divider"></div>
<div class="content-row">
<div class="content-column">
<div class="content-data-name">
{{'admin.settings.bot.technicianIds' | translate}}:
</div>
<div class="content-data-value-row">
<p-table #dt [value]="technicianIds" dataKey="id" editMode="row">
<ng-template pTemplate="caption">
<div class="table-caption">
<div class="table-caption-btn-wrapper btn-wrapper">
<button pButton class="icon-btn btn"
icon="pi pi-plus" (click)="technicianIdsAddNew(dt)">
</button>
</div>
</div>
</ng-template>
<ng-template pTemplate="body" let-technicianId let-editing="editing" let-ri="rowIndex">
<tr [pEditableRow]="technicianId">
<td>
<p-cellEditor>
<ng-template pTemplate="input">
<input class="table-edit-input" pInputText type="text" [(ngModel)]="technicianId.value">
</ng-template>
<ng-template pTemplate="output">
{{technicianId.value}}
</ng-template>
</p-cellEditor>
</td>
<td>
<div class="btn-wrapper">
<button *ngIf="!editing" pButton type="button" pInitEditableRow class="btn icon-btn" icon="pi pi-pencil" (click)="technicianIdsEditInit(technicianId, ri)"></button>
<button *ngIf="!editing" pButton type="button" class="btn danger-icon-btn" icon="pi pi-trash" (click)="technicianIdsDelete(ri)"></button>
<button *ngIf="editing" pButton type="button" pSaveEditableRow class="btn icon-btn" icon="pi pi-check" (click)="technicianIdsEditSave(technicianId, ri)"></button>
<button *ngIf="editing" pButton type="button" pCancelEditableRow class="btn danger-icon-btn" icon="pi pi-times"
(click)="technicianIdsEditCancel(technicianId, ri)"></button>
</div>
</td>
</tr>
</ng-template>
</p-table>
</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" [(data)]="config.technicianIds"></app-config-list>
<div class="content-row">
<button pButton icon="pi pi-save" label="{{'common.save' | translate}}" class="btn login-form-submit-btn"
(click)="saveTechnicianConfig()" [disabled]="technicianConfigForm.invalid"></button>
(click)="saveTechnicianConfig()"></button>
</div>
</div>
</div>

View File

@ -1,5 +1,5 @@
import { Component, OnInit } from "@angular/core";
import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from "@angular/forms";
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";
@ -17,17 +17,7 @@ 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 { Table } from "primeng/table";
type PingUrl = {
id: number;
value: string;
};
type TechnicianId = {
id: number;
value: string;
};
@Component({
selector: "app-settings",
@ -37,16 +27,6 @@ type TechnicianId = {
export class SettingsComponent implements OnInit {
testMailForm!: FormGroup;
technicianConfigForm: FormGroup = this.formBuilder.group({
helpCommandReferenceUrl: [null, [Validators.required]],
waitForRestart: [null, [Validators.required]],
waitForShutdown: [null, [Validators.required]],
cacheMaxMessages: [null, [Validators.required]]
});
pingUrls: PingUrl[] = [];
clonedPingUrls: { [s: number]: PingUrl } = {};
technicianIds: TechnicianId[] = [];
clonedTechnicianIds: { [s: number]: TechnicianId } = {};
data: SettingsDTO = {
webVersion: "",
apiVersion: "",
@ -114,23 +94,6 @@ export class SettingsComponent implements OnInit {
this.testMailForm = this.formBuilder.group({
mail: [null, [Validators.required, Validators.email]]
});
this.technicianConfigForm = this.formBuilder.group({
helpCommandReferenceUrl: [this.config.helpCommandReferenceUrl, [Validators.required]],
waitForRestart: [this.config.waitForRestart, [Validators.required]],
waitForShutdown: [this.config.waitForShutdown, [Validators.required]],
cacheMaxMessages: [this.config.cacheMaxMessages, [Validators.required]]
});
let id = 0;
for (const url of this.config.pingURLs ?? []) {
this.pingUrls.push({ id: id, value: url });
id++;
}
id = 0;
for (const technicianId of this.config.technicianIds ?? []) {
this.technicianIds.push({ id: id, value: technicianId });
id++;
}
}
testMail(): void {
@ -187,16 +150,11 @@ export class SettingsComponent implements OnInit {
waitForRestart: this.config.waitForRestart,
waitForShutdown: this.config.waitForShutdown,
cacheMaxMessages: this.config.cacheMaxMessages,
pingURLs: this.pingUrls.filter(value => value.value).map(value => {
return value.value;
}),
technicianIds: this.technicianIds.filter(value => value.value).map(value => {
return value.value;
})
pingURLs: this.config.pingURLs,
technicianIds: this.config.technicianIds
}
).pipe(catchError(err => {
this.spinner.hideSpinner();
this.toastService.error(this.translate.instant("admin.settings.message.technician_config_create_failed"), this.translate.instant("admin.settings.message.technician_config_create_failed_d"));
return throwError(err);
})).subscribe(result => {
this.spinner.hideSpinner();
@ -204,71 +162,5 @@ export class SettingsComponent implements OnInit {
});
}
pingUrlsAddNew(table: Table) {
const id = Math.max.apply(Math, this.pingUrls.map(url => {
return url.id ?? 0;
})) + 1;
const newItem = { id: id, value: "" };
this.pingUrls.push(newItem);
table.initRowEdit(newItem);
const index = this.pingUrls.findIndex(l => l.id == newItem.id);
this.pingUrlsEditInit(newItem, index);
}
pingUrlsEditInit(url: PingUrl, index: number) {
this.clonedPingUrls[index] = { ...url };
}
pingUrlsDelete(index: number) {
this.pingUrls.splice(index, 1);
}
pingUrlsEditSave(url: PingUrl, index: number) {
if (!url.value || this.pingUrls[index] == this.clonedPingUrls[index]) {
return;
}
delete this.clonedPingUrls[index];
}
pingUrlsEditCancel(url: PingUrl, index: number) {
this.pingUrls[index] = this.clonedPingUrls[index];
delete this.clonedPingUrls[index];
}
technicianIdsAddNew(table: Table) {
const id = Math.max.apply(Math, this.technicianIds.map(url => {
return url.id ?? 0;
})) + 1;
const newItem = { id: id, value: "" };
this.technicianIds.push(newItem);
table.initRowEdit(newItem);
const index = this.technicianIds.findIndex(l => l.id == newItem.id);
this.technicianIdsEditInit(newItem, index);
}
technicianIdsEditInit(url: PingUrl, index: number) {
this.clonedTechnicianIds[index] = { ...url };
}
technicianIdsDelete(index: number) {
this.technicianIds.splice(index, 1);
}
technicianIdsEditSave(url: PingUrl, index: number) {
if (!url.value || this.technicianIds[index] == this.clonedTechnicianIds[index]) {
return;
}
delete this.clonedTechnicianIds[index];
}
technicianIdsEditCancel(url: PingUrl, index: number) {
this.technicianIds[index] = this.clonedTechnicianIds[index];
delete this.clonedTechnicianIds[index];
}
}

View File

@ -0,0 +1,45 @@
<div class="content-row">
<div class="content-column">
<div class="content-data-name">
{{translationKey | translate}}:
</div>
<div class="content-data-value-row">
<p-table #dt [value]="internal_data" dataKey="id" editMode="row">
<ng-template pTemplate="caption">
<div class="table-caption">
<div class="table-caption-btn-wrapper btn-wrapper">
<button pButton class="icon-btn btn"
icon="pi pi-plus" (click)="addNew(dt)">
</button>
</div>
</div>
</ng-template>
<ng-template pTemplate="body" let-value let-editing="editing" let-ri="rowIndex">
<tr [pEditableRow]="value">
<td>
<p-cellEditor>
<ng-template pTemplate="input">
<input class="table-edit-input" pInputText type="text" [(ngModel)]="value.value">
</ng-template>
<ng-template pTemplate="output">
{{value.value}}
</ng-template>
</p-cellEditor>
</td>
<td>
<div class="btn-wrapper">
<button *ngIf="!editing" pButton type="button" pInitEditableRow class="btn icon-btn" icon="pi pi-pencil" (click)="editInit(value, ri)"></button>
<button *ngIf="!editing" pButton type="button" class="btn danger-icon-btn" icon="pi pi-trash" (click)="delete(ri)"></button>
<button *ngIf="editing" pButton type="button" pSaveEditableRow class="btn icon-btn" icon="pi pi-check" (click)="editSave(value, ri)"></button>
<button *ngIf="editing" pButton type="button" pCancelEditableRow class="btn danger-icon-btn" icon="pi pi-times"
(click)="editCancel(ri)"></button>
</div>
</td>
</tr>
</ng-template>
</p-table>
</div>
</div>
</div>
<div class="content-divider"></div>

View File

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

View File

@ -0,0 +1,72 @@
import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { Table } from "primeng/table";
@Component({
selector: "app-config-list",
templateUrl: "./config-list.component.html",
styleUrls: ["./config-list.component.scss"]
})
export class ConfigListComponent {
internal_data: any[] = [];
@Input() translationKey: string = "";
@Input()
set data(val: any[]) {
this.dataChange.emit(val);
let id = 0;
this.internal_data = val.map(value => {
value = { id: id, value: value };
id++;
return value;
});
}
get data() {
return this.getData();
}
@Output() dataChange: EventEmitter<any> = new EventEmitter<any>();
clonedData: { [s: number]: any } = {};
private getData(): any[] {
return this.internal_data.map(value => {
return value.value;
});
}
addNew(table: Table) {
const id = Math.max.apply(Math, this.internal_data.map(value => {
return value.id ?? 0;
})) + 1;
const newItem = { id: id, value: "" };
this.internal_data.push(newItem);
table.initRowEdit(newItem);
const index = this.internal_data.findIndex(l => l.id == newItem.id);
this.editInit(newItem, index);
}
editInit(value: any, index: number) {
this.clonedData[index] = { ...value };
}
delete(index: number) {
this.internal_data.splice(index, 1);
this.dataChange.emit(this.getData());
}
editSave(value: any, index: number) {
if (!value.value || this.internal_data[index] == this.clonedData[index]) {
return;
}
delete this.clonedData[index];
this.dataChange.emit(this.getData());
}
editCancel(index: number) {
this.internal_data[index] = this.clonedData[index];
delete this.clonedData[index];
}
}

View File

@ -25,6 +25,7 @@ import { ImageModule } from "primeng/image";
import { SidebarModule } from "primeng/sidebar";
import { HistoryBtnComponent } from './components/history-btn/history-btn.component';
import { DataViewModule, DataViewLayoutOptions } from 'primeng/dataview';
import { ConfigListComponent } from './components/config-list/config-list.component';
@NgModule({
@ -33,6 +34,7 @@ import { DataViewModule, DataViewLayoutOptions } from 'primeng/dataview';
IpAddressPipe,
BoolPipe,
HistoryBtnComponent,
ConfigListComponent,
],
imports: [
CommonModule,
@ -86,7 +88,8 @@ import { DataViewModule, DataViewLayoutOptions } from 'primeng/dataview';
SidebarModule,
HistoryBtnComponent,
DataViewModule,
DataViewLayoutOptions
DataViewLayoutOptions,
ConfigListComponent
]
})
export class SharedModule { }

View File

@ -236,7 +236,6 @@ export class AchievementComponent implements OnInit, OnDestroy {
).pipe(catchError(err => {
this.isEditingNew = false;
this.spinner.hideSpinner();
this.toastService.error(this.translate.instant("view.server.achievements.message.achievement_create_failed"), this.translate.instant("view.server.achievements.message.achievement_create_failed_d"));
return throwError(err);
})).subscribe(result => {
this.isEditingNew = false;
@ -258,7 +257,6 @@ export class AchievementComponent implements OnInit, OnDestroy {
}
).pipe(catchError(err => {
this.spinner.hideSpinner();
this.toastService.error(this.translate.instant("view.server.achievements.message.achievement_update_failed"), this.translate.instant("view.server.achievements.message.achievement_update_failed_d", { name: newAchievement.name }));
return throwError(err);
})).subscribe(_ => {
this.spinner.hideSpinner();
@ -290,7 +288,6 @@ export class AchievementComponent implements OnInit, OnDestroy {
}
).pipe(catchError(err => {
this.spinner.hideSpinner();
this.toastService.error(this.translate.instant("view.server.achievements.message.achievement_delete_failed"), this.translate.instant("view.server.achievements.message.achievement_delete_failed_d", { name: achievement.name }));
return throwError(err);
})).subscribe(l => {
this.spinner.hideSpinner();

View File

@ -222,7 +222,6 @@ export class AutoRolesRulesComponent implements OnInit, OnDestroy {
).pipe(catchError(err => {
this.isEditingNew = false;
this.spinner.hideSpinner();
this.toastService.error(this.translate.instant("view.server.auto_roles.rules.message.auto_role_rule_create_failed"), this.translate.instant("view.server.auto_roles.rules.message.auto_role_rule_create_failed_d"));
return throwError(err);
})).subscribe(result => {
this.isEditingNew = false;
@ -241,7 +240,6 @@ export class AutoRolesRulesComponent implements OnInit, OnDestroy {
}
).pipe(catchError(err => {
this.spinner.hideSpinner();
this.toastService.error(this.translate.instant("view.server.auto_roles.rules.message.auto_role_rule_update_failed"), this.translate.instant("view.server.auto_roles.rules.message.auto_role_rule_update_failed_d"));
return throwError(err);
})).subscribe(result => {
this.spinner.hideSpinner();
@ -272,7 +270,6 @@ export class AutoRolesRulesComponent implements OnInit, OnDestroy {
}
).pipe(catchError(err => {
this.spinner.hideSpinner();
this.toastService.error(this.translate.instant("view.server.auto_roles.rules.message.auto_role_rule_delete_failed"), this.translate.instant("view.server.auto_roles.rules.message.auto_role_rule_delete_failed_d", { id: autoRoleRule.id }));
return throwError(err);
})).subscribe(_ => {
this.spinner.hideSpinner();

View File

@ -208,7 +208,6 @@ export class AutoRolesComponent implements OnInit, OnDestroy {
).pipe(catchError(err => {
this.isEditingNew = false;
this.spinner.hideSpinner();
this.toastService.error(this.translate.instant("view.server.auto_roles.message.auto_role_create_failed"), this.translate.instant("view.server.auto_roles.message.auto_role_create_failed_d"));
return throwError(err);
})).subscribe(result => {
this.isEditingNew = false;
@ -240,7 +239,6 @@ export class AutoRolesComponent implements OnInit, OnDestroy {
}
).pipe(catchError(err => {
this.spinner.hideSpinner();
this.toastService.error(this.translate.instant("view.server.auto_roles.message.auto_role_delete_failed"), this.translate.instant("view.server.auto_roles.message.auto_role_delete_failed_d", { id: autoRole.id }));
return throwError(err);
})).subscribe(_ => {
this.spinner.hideSpinner();

View File

@ -0,0 +1,123 @@
<h1>
{{'view.server.config.header' | translate}}
</h1>
<div class="content-wrapper">
<div class="content-header">
<h2>
{{'view.server.config.bot.header' | translate}}
</h2>
</div>
<div class="content">
<div class="content-row">
<div class="content-column">
<div class="content-data-name">{{'view.server.config.bot.message_delete_timer' | translate}}:</div>
<input class="content-data-value" type="number" pInputText [(ngModel)]="config.messageDeleteTimer"
placeholder="{{'view.server.config.bot.message_delete_timer' | translate}}">
</div>
</div>
<div class="content-row">
<div class="content-column">
<div class="content-data-name">{{'view.server.config.bot.notification_chat_id' | translate}}:</div>
<input class="content-data-value" type="text" pInputText [(ngModel)]="config.notificationChatId"
placeholder="{{'view.server.config.bot.notification_chat_id' | translate}}">
</div>
</div>
<div class="content-row">
<div class="content-column">
<div class="content-data-name">{{'view.server.config.bot.max_voice_state_hours' | translate}}:</div>
<input class="content-data-value" type="number" pInputText [(ngModel)]="config.maxVoiceStateHours"
placeholder="{{'view.server.config.bot.max_voice_state_hours' | translate}}">
</div>
</div>
<div class="content-row">
<div class="content-column">
<div class="content-data-name">{{'view.server.config.bot.xp_per_message' | translate}}:</div>
<input class="content-data-value" type="number" pInputText [(ngModel)]="config.xpPerMessage" placeholder="{{'view.server.config.bot.xp_per_message' | translate}}">
</div>
</div>
<div class="content-row">
<div class="content-column">
<div class="content-data-name">{{'view.server.config.bot.xp_per_reaction' | translate}}:</div>
<input class="content-data-value" type="number" pInputText [(ngModel)]="config.xpPerReaction" placeholder="{{'view.server.config.bot.xp_per_reaction' | translate}}">
</div>
</div>
<div class="content-row">
<div class="content-column">
<div class="content-data-name">{{'view.server.config.bot.max_message_xp_per_hour' | translate}}:</div>
<input class="content-data-value" type="number" pInputText [(ngModel)]="config.maxMessageXpPerHour"
placeholder="{{'view.server.config.bot.max_message_xp_per_hour' | translate}}">
</div>
</div>
<div class="content-row">
<div class="content-column">
<div class="content-data-name">{{'view.server.config.bot.xp_per_ontime_hour' | translate}}:</div>
<input class="content-data-value" type="number" pInputText [(ngModel)]="config.xpPerOntimeHour" placeholder="{{'view.server.config.bot.xp_per_ontime_hour' | translate}}">
</div>
</div>
<div class="content-row">
<div class="content-column">
<div class="content-data-name">{{'view.server.config.bot.xp_per_event_participation' | translate}}:</div>
<input class="content-data-value" type="number" pInputText [(ngModel)]="config.xpPerEventParticipation"
placeholder="{{'view.server.config.bot.xp_per_event_participation' | translate}}">
</div>
</div>
<div class="content-row">
<div class="content-column">
<div class="content-data-name">{{'view.server.config.bot.xp_per_achievement' | translate}}:</div>
<input class="content-data-value" type="number" pInputText [(ngModel)]="config.xpPerAchievement" placeholder="{{'view.server.config.bot.xp_per_achievement' | translate}}">
</div>
</div>
<div class="content-row">
<div class="content-column">
<div class="content-data-name">{{'view.server.config.bot.afk_command_channel_id' | translate}}:</div>
<input class="content-data-value" type="text" pInputText [(ngModel)]="config.afkCommandChannelId"
placeholder="{{'view.server.config.bot.afk_command_channel_id' | translate}}">
</div>
</div>
<div class="content-row">
<div class="content-column">
<div class="content-data-name">{{'view.server.config.bot.help_voice_channel_id' | translate}}:</div>
<input class="content-data-value" type="text" pInputText [(ngModel)]="config.helpVoiceChannelId"
placeholder="{{'view.server.config.bot.help_voice_channel_id' | translate}}">
</div>
</div>
<div class="content-row">
<div class="content-column">
<div class="content-data-name">{{'view.server.config.bot.team_channel_id' | translate}}:</div>
<input class="content-data-value" type="text" pInputText [(ngModel)]="config.teamChannelId" placeholder="{{'view.server.config.bot.team_channel_id' | translate}}">
</div>
</div>
<div class="content-row">
<div class="content-column">
<div class="content-data-name">{{'view.server.config.bot.login_message_channel_id' | translate}}:</div>
<input class="content-data-value" type="text" pInputText [(ngModel)]="config.loginMessageChannelId"
placeholder="{{'view.server.config.bot.login_message_channel_id' | translate}}">
</div>
</div>
<div class="content-divider"></div>
<app-config-list translationKey="view.server.config.bot.afk_channels" [(data)]="config.afkChannelIds"></app-config-list>
<app-config-list translationKey="view.server.config.bot.moderator_roles" [(data)]="config.moderatorRoleIds"></app-config-list>
<app-config-list translationKey="view.server.config.bot.admin_roles" [(data)]="config.adminRoleIds"></app-config-list>
<div class="content-row">
<button pButton icon="pi pi-save" label="{{'common.save' | translate}}" class="btn login-form-submit-btn"
(click)="saveServerConfig()"></button>
</div>
</div>
</div>

View File

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

View File

@ -0,0 +1,116 @@
import { Component, OnInit } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { TranslateService } from "@ngx-translate/core";
import { catchError } from "rxjs/operators";
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 { throwError } from "rxjs";
import { Query, ServerConfigQuery } from "../../../../../../models/graphql/query.model";
import { Queries } from "../../../../../../models/graphql/queries.model";
import { DataService } from "../../../../../../services/data/data.service";
import { ServerConfigMutationResult } from "../../../../../../models/graphql/result.model";
import { Mutations } from "../../../../../../models/graphql/mutations.model";
import { AuthService } from "../../../../../../services/auth/auth.service";
import { ServerConfig } from "../../../../../../models/config/server-config.model";
import { Server } from "../../../../../../models/data/server.model";
import { ActivatedRoute } from "@angular/router";
import { Table } from "primeng/table";
type AFKChannelId = {
id: number;
value: string;
};
@Component({
selector: "app-config",
templateUrl: "./config.component.html",
styleUrls: ["./config.component.scss"]
})
export class ConfigComponent implements OnInit {
config: ServerConfig = {
messageDeleteTimer: 0,
afkChannelIds: [],
moderatorRoleIds: [],
adminRoleIds: []
};
afkChannelIds: AFKChannelId[] = [];
clonedAfkChannelIds: { [s: number]: AFKChannelId } = {};
private server: Server = {};
constructor(
private data: 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,
private route: ActivatedRoute
) {
}
ngOnInit(): void {
this.spinnerService.showSpinner();
this.data.getServerFromRoute(this.route).then(async server => {
this.server = server;
this.loadConfig();
});
}
loadConfig() {
this.data.query<ServerConfigQuery>(Queries.serverConfigQuery, {
serverId: this.server.id
},
(data: Query) => {
return data.servers[0];
}).subscribe(data => {
this.config = data.config;
let id = 0;
for (const afkChannelId of this.config.afkChannelIds ?? []) {
this.afkChannelIds.push({ id: id, value: afkChannelId });
id++;
}
this.spinnerService.hideSpinner();
});
}
saveServerConfig() {
this.spinner.showSpinner();
this.data.mutation<ServerConfigMutationResult>(Mutations.updateServerConfig, {
id: this.config.id,
messageDeleteTimer: this.config.messageDeleteTimer,
notificationChatId: this.config.notificationChatId,
maxVoiceStateHours: this.config.maxVoiceStateHours,
xpPerMessage: this.config.xpPerMessage,
xpPerReaction: this.config.xpPerReaction,
maxMessageXpPerHour: this.config.maxMessageXpPerHour,
xpPerOntimeHour: this.config.xpPerOntimeHour,
xpPerEventParticipation: this.config.xpPerEventParticipation,
xpPerAchievement: this.config.xpPerAchievement,
afkCommandChannelId: this.config.afkCommandChannelId,
helpVoiceChannelId: this.config.helpVoiceChannelId,
teamChannelId: this.config.teamChannelId,
loginMessageChannelId: this.config.loginMessageChannelId,
afkChannelIds: this.config.afkChannelIds,
moderatorRoleIds: this.config.moderatorRoleIds,
adminRoleIds: this.config.adminRoleIds
}
).pipe(catchError(err => {
this.spinner.hideSpinner();
return throwError(err);
})).subscribe(result => {
this.spinner.hideSpinner();
this.toastService.success(this.translate.instant("view.server.config.message.technician_config_create"), this.translate.instant("view.server.config.message.technician_config_create_d"));
});
}
}

View File

@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ConfigComponent } from "./components/config/config.component";
const routes: Routes = [
{path: '', component: ConfigComponent},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ConfigRoutingModule { }

View File

@ -0,0 +1,19 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ConfigRoutingModule } from './config-routing.module';
import { ConfigComponent } from './components/config/config.component';
import { SharedModule } from "../../../shared/shared.module";
@NgModule({
declarations: [
ConfigComponent
],
imports: [
CommonModule,
ConfigRoutingModule,
SharedModule,
]
})
export class ConfigModule { }

View File

@ -196,7 +196,6 @@ export class LevelsComponent implements OnInit, OnDestroy {
).pipe(catchError(err => {
this.isEditingNew = false;
this.spinner.hideSpinner();
this.toastService.error(this.translate.instant("view.server.levels.message.level_create_failed"), this.translate.instant("view.server.levels.message.level_create_failed_d"));
return throwError(err);
})).subscribe(result => {
this.isEditingNew = false;
@ -217,7 +216,6 @@ export class LevelsComponent implements OnInit, OnDestroy {
}
).pipe(catchError(err => {
this.spinner.hideSpinner();
this.toastService.error(this.translate.instant("view.server.levels.message.level_update_failed"), this.translate.instant("view.server.levels.message.level_update_failed_d", { name: newLevel.name }));
return throwError(err);
})).subscribe(_ => {
this.spinner.hideSpinner();
@ -249,7 +247,6 @@ export class LevelsComponent implements OnInit, OnDestroy {
}
).pipe(catchError(err => {
this.spinner.hideSpinner();
this.toastService.error(this.translate.instant("view.server.levels.message.level_delete_failed"), this.translate.instant("view.server.levels.message.level_delete_failed_d", { name: level.name }));
return throwError(err);
})).subscribe(l => {
this.spinner.hideSpinner();

View File

@ -237,7 +237,6 @@ export class MembersComponent implements OnInit, OnDestroy {
}
).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();

View File

@ -10,7 +10,8 @@ const routes: Routes = [
{ path: "members/:memberId", component: ProfileComponent },
{ path: "auto-roles", loadChildren: () => import("./auto-role/auto-role.module").then(m => m.AutoRoleModule) },
{ path: "levels", loadChildren: () => import("./levels/levels.module").then(m => m.LevelsModule) },
{ path: "achievements", loadChildren: () => import("./achievements/achievements.module").then(m => m.AchievementsModule) }
{ path: "achievements", loadChildren: () => import("./achievements/achievements.module").then(m => m.AchievementsModule) },
{ path: "config", loadChildren: () => import("./config/config.module").then(m => m.ConfigModule) }
];
@NgModule({

View File

@ -10,6 +10,8 @@ import { Query } from "../../models/graphql/query.model";
import { SidebarService } from "../sidebar/sidebar.service";
import { SpinnerService } from "../spinner/spinner.service";
import { GraphQLResult } from "../../models/graphql/result.model";
import { ToastService } from "../toast/toast.service";
import { TranslateService } from "@ngx-translate/core";
@Injectable({
providedIn: "root"
@ -21,7 +23,9 @@ export class DataService {
private http: HttpClient,
private sidebar: SidebarService,
private spinner: SpinnerService,
private router: Router
private router: Router,
private toast: ToastService,
private translate: TranslateService,
) {
}
@ -72,6 +76,10 @@ export class DataService {
})
.pipe(map(d => {
if (d.errors && d.errors.length > 0) {
d.errors.forEach((error: Error) => {
this.toast.error(this.translate.instant("common.error"), error.message)
});
throw new Error(d.errors.toString());
}
return d.data;

View File

@ -3,7 +3,7 @@ import { MenuItem } from "primeng/api";
import { BehaviorSubject } from "rxjs";
import { AuthRoles } from "../../models/auth/auth-roles.enum";
import { AuthService } from "../auth/auth.service";
import { LangChangeEvent, TranslateService } from "@ngx-translate/core";
import { TranslateService } from "@ngx-translate/core";
import { NavigationEnd, Router } from "@angular/router";
import { ThemeService } from "../theme/theme.service";
import { Server } from "../../models/data/server.model";
@ -25,6 +25,7 @@ export class SidebarService {
serverAutoRoles: MenuItem = {};
serverLevels: MenuItem = {};
serverAchievements: MenuItem = {};
serverConfig: MenuItem = {};
serverMenu: MenuItem = {};
adminConfig: MenuItem = {};
adminUsers: MenuItem = {};
@ -110,12 +111,19 @@ export class SidebarService {
routerLink: `server/${this.server$.value?.id}/achievements`
};
this.serverConfig = {
label: this.isSidebarOpen ? this.translateService.instant("sidebar.server.config") : "",
icon: "pi pi-cog",
visible: true,
routerLink: `server/${this.server$.value?.id}/config`
};
this.serverMenu = {
label: this.isSidebarOpen ? this.server$.value?.name : "",
icon: "pi pi-server",
visible: false,
expanded: true,
items: [this.serverDashboard, this.serverProfile, this.serverMembers, this.serverAutoRoles, this.serverLevels, this.serverAchievements]
items: [this.serverDashboard, this.serverProfile, this.serverMembers, this.serverAutoRoles, this.serverLevels, this.serverAchievements, this.serverConfig]
};
this.adminConfig = {
label: this.isSidebarOpen ? this.translateService.instant("sidebar.config") : "",
@ -151,6 +159,7 @@ export class SidebarService {
this.serverAutoRoles.visible = !!user?.isModerator;
this.serverLevels.visible = !!user?.isModerator;
this.serverAchievements.visible = !!user?.isModerator;
this.serverConfig.visible = !!user?.isAdmin;
} else {
this.serverMenu.visible = false;
}

View File

@ -37,7 +37,7 @@
"header": "Bot",
"help_url": "Befehlsreferenz",
"ping_urls": "Ping Adressen",
"technicianIds": "Techniker Ids",
"technician_ids": "Techniker Ids",
"wait_for_restart": "Wartezeit vor Neustart",
"wait_for_shutdown": "Wartezeit vor Herunterfahren"
},
@ -156,7 +156,7 @@
"id": "Id",
"leftServer": "Server verlassen",
"messageId": "Nachricht Id",
"minXp": "Min. XP",
"min_xp": "Min. XP",
"name": "Name",
"operator": "Operator",
"permissions": "Berechtigung",
@ -407,6 +407,28 @@
}
}
},
"config": {
"bot": {
"admin_roles": "Admin Rollen",
"afk_channels": "AFK Sprachkanäle",
"afk_command_channel_id": "AFK Kanal für den Befehl /afk",
"header": "Bot Konfiguration",
"help_voice_channel_id": "Sprachkanal für Hilfsbenachrichtung",
"login_message_channel_id": "Kanal für die Nachricht vom Bot nach Start",
"max_message_xp_per_hour": "Maximale XP pro Stunde durch Nachrichten",
"max_voice_state_hours": "Maximale Stunden für eine ontime nach Bot neustart",
"message_delete_timer": "Zeit bis zum löschen einer Botnachricht in sekunden",
"moderator_roles": "Moderator Rollen",
"notification_chat_id": "Benachrichtungskanal",
"team_channel_id": "Team chat",
"xp_per_achievement": "XP für Errungenschaft",
"xp_per_event_participation": "XP für Event Teilnahme",
"xp_per_message": "XP für eine Nachricht",
"xp_per_ontime_hour": "XP für eine Stunde im Sprachkanal",
"xp_per_reaction": "XP für eine Reaktion"
},
"header": "Server Konfiguration"
},
"dashboard": {
"deleted_message_count": "Gelöschte Nachrichten",
"header": "Server dashboard",

View File

@ -37,7 +37,7 @@
"header": "Bot",
"help_url": "Help URL",
"ping_urls": "Ping addresses",
"technicianIds": "Technician Ids",
"technician_ids": "Technician Ids",
"wait_for_restart": "Time to wait before restart",
"wait_for_shutdown": "Time to wait before shutdown"
},
@ -156,7 +156,7 @@
"id": "Id",
"leftServer": "Left server",
"messageId": "Message Id",
"minXp": "Min. XP",
"min_xp": "Min. XP",
"name": "Name",
"operator": "Operator",
"permissions": "Permissions",
@ -407,6 +407,28 @@
}
}
},
"config": {
"bot": {
"admin_roles": "Admin Roles",
"afk_channels": "AFK Voicechannel",
"afk_command_channel_id": "AFK Channel for the command /afk",
"header": "Bot configuration",
"help_voice_channel_id": "Voicechannel für help notifications",
"login_message_channel_id": "Channel for bot message after start",
"max_message_xp_per_hour": "Max xp per hour with message",
"max_voice_state_hours": "Max ontime hours after bot restart",
"message_delete_timer": "Time to wait before delete bot messages",
"moderator_roles": "Moderator roles",
"notification_chat_id": "Notification channel",
"team_channel_id": "Team chat",
"xp_per_achievement": "XP for achievement",
"xp_per_event_participation": "XP for event participation",
"xp_per_message": "XP for message",
"xp_per_ontime_hour": "XP for one hour in an voice channel",
"xp_per_reaction": "XP for an reaction"
},
"header": "Server configuration"
},
"dashboard": {
"deleted_message_count": "Deleted messages",
"header": "Server dashboard",
@ -487,7 +509,7 @@
"ontime": "Ontime",
"permission_denied": "Access denied!",
"permission_denied_d": "You have to be moderator to see other profiles!",
"xp": "Xp"
"xp": "XP"
}
},
"user_settings": {