Added server config to frontend #127
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
							
								
								
									
										21
									
								
								kdb-web/src/app/models/config/server-config.model.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								kdb-web/src/app/models/config/server-config.model.ts
									
									
									
									
									
										Normal 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[]; | ||||
| } | ||||
| @@ -6,6 +6,6 @@ export interface TechnicianConfig extends DataWithHistory { | ||||
|   waitForRestart?: number; | ||||
|   waitForShutdown?: number; | ||||
|   cacheMaxMessages?: number; | ||||
|   pingURLs?: string[]; | ||||
|   technicianIds?: string[]; | ||||
|   pingURLs: string[]; | ||||
|   technicianIds: string[]; | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   `; | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     } | ||||
|   `; | ||||
| } | ||||
|   | ||||
| @@ -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[]; | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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]; | ||||
|   } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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> | ||||
| @@ -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(); | ||||
|   }); | ||||
| }); | ||||
| @@ -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]; | ||||
|   } | ||||
| } | ||||
| @@ -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, | ||||
| @@ -59,34 +61,35 @@ import { DataViewModule, DataViewLayoutOptions } from 'primeng/dataview'; | ||||
|     SidebarModule, | ||||
|     DataViewModule, | ||||
|   ], | ||||
|   exports: [ | ||||
|     ButtonModule, | ||||
|     PasswordModule, | ||||
|     MenuModule, | ||||
|     DialogModule, | ||||
|     ProgressSpinnerModule, | ||||
|     HttpClientModule, | ||||
|     FormsModule, | ||||
|     ReactiveFormsModule, | ||||
|     ToastModule, | ||||
|     ConfirmDialogModule, | ||||
|     TableModule, | ||||
|     InputTextModule, | ||||
|     CheckboxModule, | ||||
|     DropdownModule, | ||||
|     TranslateModule, | ||||
|     DynamicDialogModule, | ||||
|     PanelMenuModule, | ||||
|     PanelModule, | ||||
|     AuthRolePipe, | ||||
|     IpAddressPipe, | ||||
|     BoolPipe, | ||||
|     InputNumberModule, | ||||
|     ImageModule, | ||||
|     SidebarModule, | ||||
|     HistoryBtnComponent, | ||||
|     DataViewModule, | ||||
|     DataViewLayoutOptions | ||||
|   ] | ||||
|     exports: [ | ||||
|         ButtonModule, | ||||
|         PasswordModule, | ||||
|         MenuModule, | ||||
|         DialogModule, | ||||
|         ProgressSpinnerModule, | ||||
|         HttpClientModule, | ||||
|         FormsModule, | ||||
|         ReactiveFormsModule, | ||||
|         ToastModule, | ||||
|         ConfirmDialogModule, | ||||
|         TableModule, | ||||
|         InputTextModule, | ||||
|         CheckboxModule, | ||||
|         DropdownModule, | ||||
|         TranslateModule, | ||||
|         DynamicDialogModule, | ||||
|         PanelMenuModule, | ||||
|         PanelModule, | ||||
|         AuthRolePipe, | ||||
|         IpAddressPipe, | ||||
|         BoolPipe, | ||||
|         InputNumberModule, | ||||
|         ImageModule, | ||||
|         SidebarModule, | ||||
|         HistoryBtnComponent, | ||||
|         DataViewModule, | ||||
|         DataViewLayoutOptions, | ||||
|         ConfigListComponent | ||||
|     ] | ||||
| }) | ||||
| export class SharedModule { } | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -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> | ||||
| @@ -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(); | ||||
|   }); | ||||
| }); | ||||
| @@ -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")); | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| @@ -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 { } | ||||
							
								
								
									
										19
									
								
								kdb-web/src/app/modules/view/server/config/config.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								kdb-web/src/app/modules/view/server/config/config.module.ts
									
									
									
									
									
										Normal 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 { } | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -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({ | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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; | ||||
|       } | ||||
|   | ||||
| @@ -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", | ||||
|   | ||||
| @@ -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": { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user