[deploy] Restore dashboard sections and clean navigation icons
All checks were successful
Deploy Discord Bot / deploy (push) Successful in 37s
All checks were successful
Deploy Discord Bot / deploy (push) Successful in 37s
This commit is contained in:
@@ -43,19 +43,18 @@ router.get('/', (req, res) => {
|
|||||||
<aside class="sidebar">
|
<aside class="sidebar">
|
||||||
<div class="brand">Papo Control</div>
|
<div class="brand">Papo Control</div>
|
||||||
<div class="nav">
|
<div class="nav">
|
||||||
<a class="active" href="#overview" data-target="overview"><span class="icon">🧭</span> Uebersicht</a>
|
<a class="active" href="#overview" data-target="overview"><span class="icon">🏠</span> Uebersicht</a>
|
||||||
<a href="#tickets" data-target="tickets"><span class="icon">🎫</span> Ticketsystem</a>
|
<a href="#tickets" data-target="tickets"><span class="icon">🎫</span> Ticketsystem</a>
|
||||||
<a href="#automod" data-target="automod" class="automod-link"><span class="icon">🛡️</span> Automod</a>
|
<a href="#automod" data-target="automod" class="automod-link"><span class="icon">🛡️</span> Automod</a>
|
||||||
<a href="#welcome" data-target="welcome" class="welcome-link"><span class="icon">👋</span> Willkommen</a>
|
<a href="#welcome" data-target="welcome" class="welcome-link"><span class="icon">✨</span> Willkommen</a>
|
||||||
<a href="#dynamicvoice" data-target="dynamicvoice" class="dynamicvoice-link"><span class="icon">🎧</span> Dynamic Voice</a>
|
<a href="#dynamicvoice" data-target="dynamicvoice" class="dynamicvoice-link"><span class="icon">🎧</span> Dynamic Voice</a>
|
||||||
<a href="#birthday" data-target="birthday" class="birthday-link"><span class="icon">🎂</span> Birthday</a>
|
<a href="#birthday" data-target="birthday" class="birthday-link"><span class="icon">🎂</span> Birthday</a>
|
||||||
<a href="#reactionroles" data-target="reactionroles" class="reactionroles-link"><span class="icon">🎭</span> Reaction Roles</a>
|
<a href="#reactionroles" data-target="reactionroles" class="reactionroles-link"><span class="icon">😎</span> Reaction Roles</a>
|
||||||
<a href="#statuspage" data-target="statuspage" class="statuspage-link"><span class="icon">🖥️</span> Statuspage</a>
|
<a href="#statuspage" data-target="statuspage" class="statuspage-link"><span class="icon">🖥️</span> Statuspage</a>
|
||||||
<a href="#serverstats" data-target="serverstats" class="serverstats-link"><span class="icon">📈</span> Server Stats</a>
|
|
||||||
<a href="#settings" data-target="settings"><span class="icon">⚙️</span> Einstellungen</a>
|
<a href="#settings" data-target="settings"><span class="icon">⚙️</span> Einstellungen</a>
|
||||||
<a href="#modules" data-target="modules"><span class="icon">🧩</span> Module</a>
|
<a href="#modules" data-target="modules"><span class="icon">🧩</span> Module</a>
|
||||||
<a href="#events" data-target="events" class="events-link"><span class="icon">📅</span> Events</a>
|
<a href="#events" data-target="events" class="events-link"><span class="icon">📅</span> Events</a>
|
||||||
<a href="#admin" data-target="admin" class="admin-link hidden"><span class="icon">🛠️</span> Admin</a>
|
<a href="#admin" data-target="admin" class="admin-link hidden"><span class="icon">🛡</span> Admin</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="muted">Angemeldet als <span id="userInfo"></span></div>
|
<div class="muted">Angemeldet als <span id="userInfo"></span></div>
|
||||||
<button id="logoutBtn" class="logout">Logout</button>
|
<button id="logoutBtn" class="logout">Logout</button>
|
||||||
@@ -331,10 +330,10 @@ router.get('/', (req, res) => {
|
|||||||
<div class="card" style="display:flex; gap:10px; flex-wrap:wrap; align-items:center; justify-content:space-between;">
|
<div class="card" style="display:flex; gap:10px; flex-wrap:wrap; align-items:center; justify-content:space-between;">
|
||||||
<div>
|
<div>
|
||||||
<p class="section-title">Tickets</p>
|
<p class="section-title">Tickets</p>
|
||||||
<p class="section-sub">ÃÂÃÂbersicht, Pipeline, SLA, Automationen, Knowledge-Base.</p>
|
<p class="section-sub"><EFBFBD>bersicht, Pipeline, SLA, Automationen, Knowledge-Base.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="row" style="gap:8px; flex-wrap:wrap;">
|
<div class="row" style="gap:8px; flex-wrap:wrap;">
|
||||||
<button class="secondary-btn ticket-tab-btn active" data-tab="overview">ÃÂÃÂbersicht</button>
|
<button class="secondary-btn ticket-tab-btn active" data-tab="overview"><EFBFBD>bersicht</button>
|
||||||
<button class="secondary-btn ticket-tab-btn" data-tab="pipeline">Pipeline</button>
|
<button class="secondary-btn ticket-tab-btn" data-tab="pipeline">Pipeline</button>
|
||||||
<button class="secondary-btn ticket-tab-btn" data-tab="sla">SLA</button>
|
<button class="secondary-btn ticket-tab-btn" data-tab="sla">SLA</button>
|
||||||
<button class="secondary-btn ticket-tab-btn" data-tab="automations">Automationen</button>
|
<button class="secondary-btn ticket-tab-btn" data-tab="automations">Automationen</button>
|
||||||
@@ -347,7 +346,7 @@ router.get('/', (req, res) => {
|
|||||||
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px;">
|
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px;">
|
||||||
<div>
|
<div>
|
||||||
<p class="section-title">Ticketliste</p>
|
<p class="section-title">Ticketliste</p>
|
||||||
<p class="section-sub">Links auswÃÂählen, Details im Modal. Plus ÃÂöffnet Panel-Erstellung.</p>
|
<p class="section-sub">Links ausw<EFBFBD>hlen, Details im Modal. Plus <EFBFBD>ffnet Panel-Erstellung.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="row" style="gap:10px;">
|
<div class="row" style="gap:10px;">
|
||||||
<button class="secondary-btn" id="openSupportLogin" type="button">Support-Login Einstellungen</button>
|
<button class="secondary-btn" id="openSupportLogin" type="button">Support-Login Einstellungen</button>
|
||||||
@@ -382,7 +381,7 @@ router.get('/', (req, res) => {
|
|||||||
<div style="display:flex; justify-content:space-between; align-items:center; gap:12px;">
|
<div style="display:flex; justify-content:space-between; align-items:center; gap:12px;">
|
||||||
<div>
|
<div>
|
||||||
<p class="section-title">Status-Pipeline</p>
|
<p class="section-title">Status-Pipeline</p>
|
||||||
<p class="section-sub">Tickets nach Phase. Status per Dropdown ÃÂändern.</p>
|
<p class="section-sub">Tickets nach Phase. Status per Dropdown <EFBFBD>ndern.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid" style="grid-template-columns: repeat(auto-fit, minmax(240px,1fr)); gap:12px;" id="pipelineGrid">
|
<div class="grid" style="grid-template-columns: repeat(auto-fit, minmax(240px,1fr)); gap:12px;" id="pipelineGrid">
|
||||||
@@ -431,7 +430,7 @@ router.get('/', (req, res) => {
|
|||||||
<div style="display:flex; justify-content:space-between; gap:12px; flex-wrap:wrap;">
|
<div style="display:flex; justify-content:space-between; gap:12px; flex-wrap:wrap;">
|
||||||
<div>
|
<div>
|
||||||
<p class="section-title">Automationen</p>
|
<p class="section-title">Automationen</p>
|
||||||
<p class="section-sub">Regeln fÃÂür Ticket-Aktionen.</p>
|
<p class="section-sub">Regeln f<EFBFBD>r Ticket-Aktionen.</p>
|
||||||
</div>
|
</div>
|
||||||
<button class="secondary-btn" id="addAutomation">Neue Regel</button>
|
<button class="secondary-btn" id="addAutomation">Neue Regel</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -468,7 +467,7 @@ router.get('/', (req, res) => {
|
|||||||
<div style="display:flex; justify-content:space-between; gap:12px; flex-wrap:wrap;">
|
<div style="display:flex; justify-content:space-between; gap:12px; flex-wrap:wrap;">
|
||||||
<div>
|
<div>
|
||||||
<p class="section-title">Knowledge-Base</p>
|
<p class="section-title">Knowledge-Base</p>
|
||||||
<p class="section-sub">Artikel fÃÂür Self-Service.</p>
|
<p class="section-sub">Artikel f<EFBFBD>r Self-Service.</p>
|
||||||
</div>
|
</div>
|
||||||
<button class="secondary-btn" id="addKb">Neuer Artikel</button>
|
<button class="secondary-btn" id="addKb">Neuer Artikel</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -500,6 +499,353 @@ router.get('/', (req, res) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form id="automodForm" style="margin-top:14px; display:flex; flex-direction:column; gap:12px;">
|
<form id="automodForm" style="margin-top:14px; display:flex; flex-direction:column; gap:12px;">
|
||||||
|
<div class="option-grid">
|
||||||
|
<div class="option-card">
|
||||||
|
<label for="badWordFilter">Bad-Word-Filter</label>
|
||||||
|
<div id="badWordFilter" class="switch"></div>
|
||||||
|
</div>
|
||||||
|
<div class="option-card">
|
||||||
|
<label for="linkFilter">Link-/Invite-Filter</label>
|
||||||
|
<div id="linkFilter" class="switch"></div>
|
||||||
|
</div>
|
||||||
|
<div class="option-card">
|
||||||
|
<label for="spamFilter">Spam-/Flood-Erkennung</label>
|
||||||
|
<div id="spamFilter" class="switch"></div>
|
||||||
|
</div>
|
||||||
|
<div class="option-card">
|
||||||
|
<label for="capsFilter">Capslock-Filter</label>
|
||||||
|
<div id="capsFilter" class="switch"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-field">
|
||||||
|
<label class="form-label">Log Channel ID</label>
|
||||||
|
<input id="automodLogChannel" placeholder="123456789012345678" />
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label class="form-label">Empfindlichkeit</label>
|
||||||
|
<select id="automodSensitivity">
|
||||||
|
<option value="low">niedrig</option>
|
||||||
|
<option value="medium">mittel</option>
|
||||||
|
<option value="high">hoch</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-field">
|
||||||
|
<label class="form-label">Whitelist-Links (kommagetrennt)</label>
|
||||||
|
<input id="automodWhitelist" placeholder="example.com, partner.de" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-field">
|
||||||
|
<label class="form-label">Custom Badwords (kommagetrennt oder zeilenweise)</label>
|
||||||
|
<textarea id="automodBadwords" rows="3" placeholder="badword1, badword2"></textarea>
|
||||||
|
<p class="muted">Diese Woerter werden zusaetzlich zum Standard-Filter geblockt.</p>
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label class="form-label">Whitelist-Rollen (IDs, Kommagetrennt)</label>
|
||||||
|
<textarea id="automodWhitelistRoles" rows="3" placeholder="123,456"></textarea>
|
||||||
|
<p class="muted">Nachrichten von diesen Rollen werden nicht gefiltert.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex; justify-content:flex-end; gap:10px;">
|
||||||
|
<button type="submit">Speichern</button>
|
||||||
|
</div>
|
||||||
|
<p class="muted" id="automodStatus"></p>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div class="section" data-section="welcome">
|
||||||
|
<section class="card">
|
||||||
|
<div style="display:flex; justify-content:space-between; align-items:center; gap:12px; flex-wrap:wrap;">
|
||||||
|
<div>
|
||||||
|
<p class="section-title">Willkommensnachrichten</p>
|
||||||
|
<p class="section-sub">Embed konfigurieren und Feature aktivieren.</p>
|
||||||
|
</div>
|
||||||
|
<div class="inline">
|
||||||
|
<span class="form-label">Aktivieren</span>
|
||||||
|
<div id="welcomeToggle" class="switch"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form id="welcomeForm" style="display:flex; flex-direction:column; gap:12px; margin-top:12px;">
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-field">
|
||||||
|
<label class="form-label">Channel ID</label>
|
||||||
|
<input id="welcomeChannel" placeholder="123456789012345678" />
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label class="form-label">Embed-Farbe</label>
|
||||||
|
<input id="welcomeColor" type="color" value="#f97316" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-field">
|
||||||
|
<label class="form-label">Embed Titel</label>
|
||||||
|
<input id="welcomeTitle" placeholder="Willkommen!" />
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label class="form-label">Embed Footer</label>
|
||||||
|
<input id="welcomeFooter" placeholder="Schön, dass du da bist!" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label class="form-label">Embed Beschreibung</label>
|
||||||
|
<textarea id="welcomeDescription" rows="3" placeholder="Du kannst {user} verwenden."></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-field">
|
||||||
|
<label class="form-label">Thumbnail URL oder Upload</label>
|
||||||
|
<input id="welcomeThumbnail" placeholder="https://..." />
|
||||||
|
<input type="file" id="welcomeThumbnailFile" accept="image/*" />
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label class="form-label">Bild URL oder Upload (GIFs erlaubt)</label>
|
||||||
|
<input id="welcomeImage" placeholder="https://..." />
|
||||||
|
<input type="file" id="welcomeImageFile" accept="image/*" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="embed-preview" id="welcomePreview">
|
||||||
|
<div class="embed-color" id="welcomePreviewColor"></div>
|
||||||
|
<div class="embed-body">
|
||||||
|
<div class="embed-title" id="welcomePreviewTitle">Willkommen!</div>
|
||||||
|
<div class="embed-desc" id="welcomePreviewDesc">Schön, dass du da bist.</div>
|
||||||
|
<div class="embed-footer" id="welcomePreviewFooter">Footer</div>
|
||||||
|
<img id="welcomePreviewImage" class="embed-image" style="display:none;" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex; justify-content:flex-end; gap:10px;">
|
||||||
|
<button type="submit">Speichern</button>
|
||||||
|
</div>
|
||||||
|
<p class="muted" id="welcomeStatus"></p>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div class="section" data-section="dynamicvoice">
|
||||||
|
<section class="card">
|
||||||
|
<div style="display:flex; justify-content:space-between; align-items:center; gap:12px; flex-wrap:wrap;">
|
||||||
|
<div>
|
||||||
|
<p class="section-title">Dynamic Voice</p>
|
||||||
|
<p class="section-sub">Lobby waehlen, Channel-Namen & Limits setzen.</p>
|
||||||
|
</div>
|
||||||
|
<div class="inline">
|
||||||
|
<span class="form-label">Aktivieren</span>
|
||||||
|
<div id="dynamicVoiceToggle" class="switch"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form id="dynamicVoiceForm" style="display:flex; flex-direction:column; gap:12px; margin-top:12px;">
|
||||||
|
<div class="grid">
|
||||||
|
<div class="form-field">
|
||||||
|
<label class="form-label">Lobby Channel ID (Voice)</label>
|
||||||
|
<input id="dynamicVoiceLobby" placeholder="123456789012345678" />
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label class="form-label">Kategorie ID (optional)</label>
|
||||||
|
<input id="dynamicVoiceCategory" placeholder="123456789012345678" />
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label class="form-label">Name-Template</label>
|
||||||
|
<input id="dynamicVoiceTemplate" placeholder="{user}s Channel" />
|
||||||
|
<p class="muted">{user} wird durch den Mitgliedsnamen ersetzt.</p>
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label class="form-label">Userlimit (optional)</label>
|
||||||
|
<input id="dynamicVoiceUserLimit" type="number" min="0" placeholder="0 = kein Limit" />
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label class="form-label">Bitrate (optional)</label>
|
||||||
|
<input id="dynamicVoiceBitrate" type="number" min="8000" placeholder="z.B. 64000" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex; justify-content:flex-end; gap:10px;">
|
||||||
|
<button type="submit">Speichern</button>
|
||||||
|
</div>
|
||||||
|
<p class="muted" id="dynamicVoiceStatus"></p>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div class="section" data-section="modules">
|
||||||
|
<section class="card">
|
||||||
|
<p class="label">Module</p>
|
||||||
|
<div id="moduleList" class="module-list"></div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div class="section" data-section="birthday">
|
||||||
|
<section class="card">
|
||||||
|
<div class="row" style="justify-content:space-between; align-items:flex-start; gap:12px;">
|
||||||
|
<div>
|
||||||
|
<p class="section-title">Birthday</p>
|
||||||
|
<p class="section-sub">Automatische Glueckwuensche je Guild.</p>
|
||||||
|
</div>
|
||||||
|
<div class="row" style="align-items:center; gap:10px; flex-wrap:wrap; justify-content:flex-end;">
|
||||||
|
<label class="form-label">Sendezeit (Stunde)</label>
|
||||||
|
<input id="birthdayHour" type="number" min="0" max="23" placeholder="9" style="width:80px;" />
|
||||||
|
<label class="form-label">Channel ID</label>
|
||||||
|
<input id="birthdayChannel" placeholder="123456789012345678" style="width:180px;" />
|
||||||
|
<div id="birthdayToggle" class="switch"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label class="form-label">Template</label>
|
||||||
|
<textarea id="birthdayTemplate" rows="3" placeholder="Alles Gute zum Geburtstag, {user}!"></textarea>
|
||||||
|
<p class="muted">Nutze {user} als Platzhalter.</p>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex; justify-content:flex-end; gap:10px;">
|
||||||
|
<button id="birthdaySave" type="button">Speichern</button>
|
||||||
|
</div>
|
||||||
|
<p class="muted" id="birthdayStatus"></p>
|
||||||
|
</section>
|
||||||
|
<section class="card">
|
||||||
|
<div class="row" style="justify-content:space-between; align-items:center;">
|
||||||
|
<div>
|
||||||
|
<p class="section-title">Gespeicherte Geburtstage</p>
|
||||||
|
<p class="section-sub">Eintraege werden per /birthday angelegt.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="birthdayList" class="module-list"></div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div class="section" data-section="reactionroles">
|
||||||
|
<section class="card">
|
||||||
|
<div class="row" style="justify-content:space-between; align-items:flex-start; gap:12px;">
|
||||||
|
<div>
|
||||||
|
<p class="section-title">Reaction Roles</p>
|
||||||
|
<p class="section-sub">Reaktionen verteilen oder entfernen Rollen.</p>
|
||||||
|
</div>
|
||||||
|
<div class="muted" id="reactionRoleStatus"></div>
|
||||||
|
</div>
|
||||||
|
<form id="reactionRoleForm" style="display:flex; flex-direction:column; gap:12px; margin-top:8px;">
|
||||||
|
<input type="hidden" id="reactionRoleId" />
|
||||||
|
<div class="grid">
|
||||||
|
<div class="form-field">
|
||||||
|
<label class="form-label">Channel ID</label>
|
||||||
|
<input id="reactionRoleChannel" placeholder="123456789012345678" />
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label class="form-label">Message ID (optional)</label>
|
||||||
|
<input id="reactionRoleMessageId" placeholder="Bestehende Nachricht verwenden" />
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label class="form-label">Titel</label>
|
||||||
|
<input id="reactionRoleTitle" placeholder="Rollenwahl" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label class="form-label">Beschreibung</label>
|
||||||
|
<textarea id="reactionRoleDescription" rows="2" placeholder="Kurze Beschreibung fuer das Embed"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<label class="form-label">Eintraege (Emoji | Role ID | Label | Beschreibung)</label>
|
||||||
|
<textarea id="reactionRoleEntries" rows="4" placeholder="😀 | 123456789 | Freunde | Erhaelt Freunde Rolle"></textarea>
|
||||||
|
<p class="muted">Eine Zeile pro Zuordnung. Label/Beschreibung optional.</p>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex; justify-content:flex-end; gap:10px;">
|
||||||
|
<button type="button" class="secondary-btn" id="reactionRoleReset">Reset</button>
|
||||||
|
<button type="submit" id="reactionRoleSubmit">Speichern & senden</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
<section class="card">
|
||||||
|
<div class="row" style="justify-content:space-between; align-items:center;">
|
||||||
|
<div>
|
||||||
|
<p class="section-title">Reaction Role Nachrichten</p>
|
||||||
|
<p class="section-sub">Bestehende Setups bearbeiten oder syncen.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="reactionRoleList" class="module-list"></div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div class="section" data-section="statuspage">
|
||||||
|
<section class="card">
|
||||||
|
<div class="row" style="justify-content:space-between;">
|
||||||
|
<div>
|
||||||
|
<p class="section-title">Statuspage</p>
|
||||||
|
<p class="section-sub">Services verwalten und Checks steuern.</p>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<label class="form-label">Intervall (ms)</label>
|
||||||
|
<input id="statuspageInterval" type="number" min="30000" step="5000" placeholder="60000" />
|
||||||
|
<label class="form-label">Channel ID</label>
|
||||||
|
<input id="statuspageChannel" placeholder="Optional" />
|
||||||
|
<div id="statuspageToggle" class="switch"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="card">
|
||||||
|
<div class="row" style="justify-content:space-between;align-items:center;">
|
||||||
|
<div>
|
||||||
|
<p class="section-title">Services</p>
|
||||||
|
<p class="section-sub">Status, Uptime, letzter Check</p>
|
||||||
|
</div>
|
||||||
|
<button class="icon-button" id="statuspageAddService">+</button>
|
||||||
|
</div>
|
||||||
|
<div id="statuspageServices" class="module-list"></div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div class="section" data-section="events">
|
||||||
|
<section class="card">
|
||||||
|
<div class="row" style="justify-content:space-between; align-items:center;">
|
||||||
|
<div>
|
||||||
|
<p class="section-title">Events</p>
|
||||||
|
<p class="section-sub">Termine planen und Erinnerungen senden.</p>
|
||||||
|
</div>
|
||||||
|
<button class="icon-button" id="openEventModal">+</button>
|
||||||
|
</div>
|
||||||
|
<div id="eventList" class="ticket-list-pane"></div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div class="section" data-section="admin">
|
||||||
|
<div class="grid">
|
||||||
|
<section class="card">
|
||||||
|
<p class="section-title">Guilds</p>
|
||||||
|
<p class="stat" id="adminGuilds">-</p>
|
||||||
|
<p class="label">aktiv (24h): <span id="adminActiveGuilds">-</span></p>
|
||||||
|
</section>
|
||||||
|
<section class="card">
|
||||||
|
<p class="section-title">Uptime</p>
|
||||||
|
<p class="stat" id="adminUptime">-</p>
|
||||||
|
<p class="label">Start: <span id="adminStart">-</span></p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<section class="card">
|
||||||
|
<div class="row" style="justify-content:space-between;">
|
||||||
|
<div>
|
||||||
|
<p class="section-title">Aktivität (letzte 24h)</p>
|
||||||
|
<p class="section-sub">Events/Commands pro Stunde</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="activity-bars" id="adminActivity"></div>
|
||||||
|
</section>
|
||||||
|
<section class="card">
|
||||||
|
<div class="row" style="justify-content:space-between;">
|
||||||
|
<div>
|
||||||
|
<p class="section-title">Logs</p>
|
||||||
|
<p class="section-sub">Neueste Einträge</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul class="log-list" id="adminLogs"></ul>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div class="section" data-section="settings">
|
||||||
|
<section class="card">
|
||||||
|
<p class="label">Einstellungen speichern</p>
|
||||||
|
<form id="settingsForm">
|
||||||
|
<input name="welcomeChannelId" placeholder="Welcome Channel ID" />
|
||||||
|
<input name="logChannelId" placeholder="Log Channel ID" />
|
||||||
|
<input name="supportRoleId" placeholder="Support Role ID (optional)" />
|
||||||
|
<button type="submit">Speichern</button>
|
||||||
|
<p class="muted" id="saveStatus"></p>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
<section class="card">
|
||||||
|
<p class="label">Logging</p>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-field">
|
||||||
|
<label class="form-label">Log Channel ID (Logging)</label>
|
||||||
|
<input id="loggingChannel" placeholder="123456789012345678" />
|
||||||
|
<p class="muted">Falls leer, wird der allgemeine Log Channel genutzt.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="option-grid">
|
<div class="option-grid">
|
||||||
<div class="option-card"><span>👋 User Join / Leave</span><div id="logJoinLeave" class="switch on"></div></div>
|
<div class="option-card"><span>👋 User Join / Leave</span><div id="logJoinLeave" class="switch on"></div></div>
|
||||||
<div class="option-card"><span>✏️ Message Edit</span><div id="logMsgEdit" class="switch on"></div></div>
|
<div class="option-card"><span>✏️ Message Edit</span><div id="logMsgEdit" class="switch on"></div></div>
|
||||||
@@ -507,7 +853,7 @@ router.get('/', (req, res) => {
|
|||||||
<div class="option-card"><span>🛡️ Automod Actions</span><div id="logAutomod" class="switch on"></div></div>
|
<div class="option-card"><span>🛡️ Automod Actions</span><div id="logAutomod" class="switch on"></div></div>
|
||||||
<div class="option-card"><span>🎫 Ticket Actions</span><div id="logTickets" class="switch on"></div></div>
|
<div class="option-card"><span>🎫 Ticket Actions</span><div id="logTickets" class="switch on"></div></div>
|
||||||
<div class="option-card"><span>🎵 Musik-Events</span><div id="logMusic" class="switch on"></div></div>
|
<div class="option-card"><span>🎵 Musik-Events</span><div id="logMusic" class="switch on"></div></div>
|
||||||
<div class="option-card"><span>🛰️ System / Channels</span><div id="logSystem" class="switch on"></div></div>
|
<div class="option-card"><span>⚙️ System / Channels</span><div id="logSystem" class="switch on"></div></div>
|
||||||
</div>
|
</div>
|
||||||
<div style="display:flex; justify-content:flex-end; gap:10px; margin-top:12px;">
|
<div style="display:flex; justify-content:flex-end; gap:10px; margin-top:12px;">
|
||||||
<button id="loggingSave" type="button">Logging speichern</button>
|
<button id="loggingSave" type="button">Logging speichern</button>
|
||||||
@@ -767,9 +1113,7 @@ router.get('/', (req, res) => {
|
|||||||
const welcomeFooter = document.getElementById('welcomeFooter');
|
const welcomeFooter = document.getElementById('welcomeFooter');
|
||||||
const welcomeDescription = document.getElementById('welcomeDescription');
|
const welcomeDescription = document.getElementById('welcomeDescription');
|
||||||
const welcomeThumbnail = document.getElementById('welcomeThumbnail');
|
const welcomeThumbnail = document.getElementById('welcomeThumbnail');
|
||||||
const welcomeThumbnailFile = document.getElementById('welcomeThumbnailFile');
|
|
||||||
const welcomeImage = document.getElementById('welcomeImage');
|
const welcomeImage = document.getElementById('welcomeImage');
|
||||||
const welcomeImageFile = document.getElementById('welcomeImageFile');
|
|
||||||
const welcomeStatus = document.getElementById('welcomeStatus');
|
const welcomeStatus = document.getElementById('welcomeStatus');
|
||||||
const dynamicVoiceToggle = document.getElementById('dynamicVoiceToggle');
|
const dynamicVoiceToggle = document.getElementById('dynamicVoiceToggle');
|
||||||
const dynamicVoiceLobby = document.getElementById('dynamicVoiceLobby');
|
const dynamicVoiceLobby = document.getElementById('dynamicVoiceLobby');
|
||||||
@@ -822,11 +1166,6 @@ router.get('/', (req, res) => {
|
|||||||
const statuspageChannel = document.getElementById('statuspageChannel');
|
const statuspageChannel = document.getElementById('statuspageChannel');
|
||||||
const statuspageServices = document.getElementById('statuspageServices');
|
const statuspageServices = document.getElementById('statuspageServices');
|
||||||
const statuspageAddService = document.getElementById('statuspageAddService');
|
const statuspageAddService = document.getElementById('statuspageAddService');
|
||||||
const statsToggle = document.getElementById('statsToggle');
|
|
||||||
const statsCategoryName = document.getElementById('statsCategoryName');
|
|
||||||
const statsRefresh = document.getElementById('statsRefresh');
|
|
||||||
const statsItems = document.getElementById('statsItems');
|
|
||||||
const statsAddItem = document.getElementById('statsAddItem');
|
|
||||||
const adminGuilds = document.getElementById('adminGuilds');
|
const adminGuilds = document.getElementById('adminGuilds');
|
||||||
const adminActiveGuilds = document.getElementById('adminActiveGuilds');
|
const adminActiveGuilds = document.getElementById('adminActiveGuilds');
|
||||||
const adminUptime = document.getElementById('adminUptime');
|
const adminUptime = document.getElementById('adminUptime');
|
||||||
@@ -867,7 +1206,6 @@ router.get('/', (req, res) => {
|
|||||||
let dynamicVoiceCache = {};
|
let dynamicVoiceCache = {};
|
||||||
let isAdmin = false;
|
let isAdmin = false;
|
||||||
let statuspageCache = { services: [] };
|
let statuspageCache = { services: [] };
|
||||||
let serverStatsCache = { items: [] };
|
|
||||||
let birthdayCache = { config: {}, birthdays: [] };
|
let birthdayCache = { config: {}, birthdays: [] };
|
||||||
let reactionRoleCache = [];
|
let reactionRoleCache = [];
|
||||||
let editingReactionRole = null;
|
let editingReactionRole = null;
|
||||||
@@ -902,7 +1240,6 @@ router.get('/', (req, res) => {
|
|||||||
const welcomeEnabled = Object.prototype.hasOwnProperty.call(modulesCache, 'welcomeEnabled') ? modulesCache['welcomeEnabled'] : true;
|
const welcomeEnabled = Object.prototype.hasOwnProperty.call(modulesCache, 'welcomeEnabled') ? modulesCache['welcomeEnabled'] : true;
|
||||||
const dynamicVoiceEnabled = Object.prototype.hasOwnProperty.call(modulesCache, 'dynamicVoiceEnabled') ? modulesCache['dynamicVoiceEnabled'] : true;
|
const dynamicVoiceEnabled = Object.prototype.hasOwnProperty.call(modulesCache, 'dynamicVoiceEnabled') ? modulesCache['dynamicVoiceEnabled'] : true;
|
||||||
const statuspageEnabled = Object.prototype.hasOwnProperty.call(modulesCache, 'statuspageEnabled') ? modulesCache['statuspageEnabled'] : true;
|
const statuspageEnabled = Object.prototype.hasOwnProperty.call(modulesCache, 'statuspageEnabled') ? modulesCache['statuspageEnabled'] : true;
|
||||||
const serverStatsEnabled = Object.prototype.hasOwnProperty.call(modulesCache, 'serverStatsEnabled') ? modulesCache['serverStatsEnabled'] : false;
|
|
||||||
const birthdayEnabled = Object.prototype.hasOwnProperty.call(modulesCache, 'birthdayEnabled') ? modulesCache['birthdayEnabled'] : true;
|
const birthdayEnabled = Object.prototype.hasOwnProperty.call(modulesCache, 'birthdayEnabled') ? modulesCache['birthdayEnabled'] : true;
|
||||||
const reactionRolesEnabled = Object.prototype.hasOwnProperty.call(modulesCache, 'reactionRolesEnabled') ? modulesCache['reactionRolesEnabled'] : true;
|
const reactionRolesEnabled = Object.prototype.hasOwnProperty.call(modulesCache, 'reactionRolesEnabled') ? modulesCache['reactionRolesEnabled'] : true;
|
||||||
const eventsEnabled = Object.prototype.hasOwnProperty.call(modulesCache, 'eventsEnabled') ? modulesCache['eventsEnabled'] : true;
|
const eventsEnabled = Object.prototype.hasOwnProperty.call(modulesCache, 'eventsEnabled') ? modulesCache['eventsEnabled'] : true;
|
||||||
@@ -911,8 +1248,6 @@ router.get('/', (req, res) => {
|
|||||||
if (dynamicVoiceNav) dynamicVoiceNav.classList.toggle('hidden', !dynamicVoiceEnabled);
|
if (dynamicVoiceNav) dynamicVoiceNav.classList.toggle('hidden', !dynamicVoiceEnabled);
|
||||||
const statuspageNav = document.querySelector('.nav .statuspage-link');
|
const statuspageNav = document.querySelector('.nav .statuspage-link');
|
||||||
if (statuspageNav) statuspageNav.classList.toggle('hidden', !statuspageEnabled);
|
if (statuspageNav) statuspageNav.classList.toggle('hidden', !statuspageEnabled);
|
||||||
const serverstatsNav = document.querySelector('.nav .serverstats-link');
|
|
||||||
if (serverstatsNav) serverstatsNav.classList.toggle('hidden', !serverStatsEnabled);
|
|
||||||
const birthdayNav = document.querySelector('.nav .birthday-link');
|
const birthdayNav = document.querySelector('.nav .birthday-link');
|
||||||
if (birthdayNav) birthdayNav.classList.toggle('hidden', !birthdayEnabled);
|
if (birthdayNav) birthdayNav.classList.toggle('hidden', !birthdayEnabled);
|
||||||
const reactionRolesNav = document.querySelector('.nav .reactionroles-link');
|
const reactionRolesNav = document.querySelector('.nav .reactionroles-link');
|
||||||
@@ -927,7 +1262,6 @@ router.get('/', (req, res) => {
|
|||||||
(current === 'welcome' && !welcomeEnabled) ||
|
(current === 'welcome' && !welcomeEnabled) ||
|
||||||
(current === 'dynamicvoice' && !dynamicVoiceEnabled) ||
|
(current === 'dynamicvoice' && !dynamicVoiceEnabled) ||
|
||||||
(current === 'statuspage' && !statuspageEnabled) ||
|
(current === 'statuspage' && !statuspageEnabled) ||
|
||||||
(current === 'serverstats' && !serverStatsEnabled) ||
|
|
||||||
(current === 'birthday' && !birthdayEnabled) ||
|
(current === 'birthday' && !birthdayEnabled) ||
|
||||||
(current === 'reactionroles' && !reactionRolesEnabled) ||
|
(current === 'reactionroles' && !reactionRolesEnabled) ||
|
||||||
(current === 'events' && !eventsEnabled) ||
|
(current === 'events' && !eventsEnabled) ||
|
||||||
@@ -1050,115 +1384,6 @@ router.get('/', (req, res) => {
|
|||||||
renderStatuspageServices();
|
renderStatuspageServices();
|
||||||
}
|
}
|
||||||
|
|
||||||
const STAT_LABELS = {
|
|
||||||
members_total: 'Mitglieder (gesamt)',
|
|
||||||
members_humans: 'Mitglieder (ohne Bots)',
|
|
||||||
members_bots: 'Bots',
|
|
||||||
boosts: 'Server Boosts',
|
|
||||||
text_channels: 'Text Channels',
|
|
||||||
voice_channels: 'Voice Channels',
|
|
||||||
roles: 'Rollen'
|
|
||||||
};
|
|
||||||
|
|
||||||
async function loadServerStats() {
|
|
||||||
if (!currentGuild) return;
|
|
||||||
const res = await fetch('/api/server-stats?guildId=' + encodeURIComponent(currentGuild));
|
|
||||||
if (!res.ok) return;
|
|
||||||
const data = await res.json();
|
|
||||||
serverStatsCache = data.config || { items: [] };
|
|
||||||
setSwitch(statsToggle, serverStatsCache.enabled !== false);
|
|
||||||
if (statsCategoryName) statsCategoryName.value = serverStatsCache.categoryName || 'ðÃÂÃÂàServer Stats';
|
|
||||||
if (statsRefresh) statsRefresh.value = serverStatsCache.refreshMinutes ?? 10;
|
|
||||||
renderServerStats();
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderServerStats() {
|
|
||||||
if (!statsItems) return;
|
|
||||||
statsItems.innerHTML = '';
|
|
||||||
(serverStatsCache.items || []).forEach((item) => {
|
|
||||||
const row = document.createElement('div');
|
|
||||||
row.className = 'module-item';
|
|
||||||
const meta = document.createElement('div');
|
|
||||||
meta.className = 'module-meta';
|
|
||||||
const label = STAT_LABELS[item.type] || item.type;
|
|
||||||
meta.innerHTML =
|
|
||||||
'<div class=\"module-title\">' +
|
|
||||||
label +
|
|
||||||
'</div><div class=\"module-desc\">' +
|
|
||||||
(item.label || label) +
|
|
||||||
' âÃÂâ ' +
|
|
||||||
(item.format || '{label}: {value}') +
|
|
||||||
'</div>';
|
|
||||||
const buttons = document.createElement('div');
|
|
||||||
buttons.className = 'row';
|
|
||||||
const edit = document.createElement('button');
|
|
||||||
edit.className = 'secondary-btn';
|
|
||||||
edit.textContent = 'Bearbeiten';
|
|
||||||
edit.addEventListener('click', () => editServerStat(item));
|
|
||||||
const del = document.createElement('button');
|
|
||||||
del.className = 'danger-btn';
|
|
||||||
del.textContent = 'Entfernen';
|
|
||||||
del.addEventListener('click', () => {
|
|
||||||
serverStatsCache.items = (serverStatsCache.items || []).filter((x) => x !== item);
|
|
||||||
renderServerStats();
|
|
||||||
saveServerStats();
|
|
||||||
});
|
|
||||||
buttons.appendChild(edit);
|
|
||||||
buttons.appendChild(del);
|
|
||||||
row.appendChild(meta);
|
|
||||||
row.appendChild(buttons);
|
|
||||||
statsItems.appendChild(row);
|
|
||||||
});
|
|
||||||
if (!(serverStatsCache.items || []).length) statsItems.innerHTML = '<div class=\"muted\">Keine Statistiken</div>';
|
|
||||||
}
|
|
||||||
|
|
||||||
function editServerStat(item) {
|
|
||||||
const typeKeys = Object.keys(STAT_LABELS);
|
|
||||||
const nextType = prompt('Typ (' + typeKeys.join(', ') + ')', item?.type || 'members_total');
|
|
||||||
if (!nextType || !STAT_LABELS[nextType]) return;
|
|
||||||
const nextLabel = prompt('Label', item?.label || STAT_LABELS[nextType]) || STAT_LABELS[nextType];
|
|
||||||
const nextFormat = prompt('Format ({label} / {value})', item?.format || '{label}: {value}') || '{label}: {value}';
|
|
||||||
if (item) {
|
|
||||||
item.type = nextType;
|
|
||||||
item.label = nextLabel;
|
|
||||||
item.format = nextFormat;
|
|
||||||
} else {
|
|
||||||
(serverStatsCache.items = serverStatsCache.items || []).push({
|
|
||||||
id: (crypto.randomUUID && crypto.randomUUID()) || String(Date.now()),
|
|
||||||
type: nextType,
|
|
||||||
label: nextLabel,
|
|
||||||
format: nextFormat
|
|
||||||
});
|
|
||||||
}
|
|
||||||
renderServerStats();
|
|
||||||
saveServerStats();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveServerStats() {
|
|
||||||
if (!currentGuild) return;
|
|
||||||
const payload = {
|
|
||||||
guildId: currentGuild,
|
|
||||||
config: {
|
|
||||||
enabled: getSwitch(statsToggle),
|
|
||||||
categoryName: statsCategoryName?.value || undefined,
|
|
||||||
refreshMinutes: statsRefresh?.value ? Number(statsRefresh.value) : undefined,
|
|
||||||
items: serverStatsCache.items || []
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const res = await fetch('/api/server-stats', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(payload)
|
|
||||||
});
|
|
||||||
if (!res.ok) {
|
|
||||||
showToast('Server Stats speichern fehlgeschlagen', true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const data = await res.json();
|
|
||||||
serverStatsCache = data.config || serverStatsCache;
|
|
||||||
renderServerStats();
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderStatuspageServices() {
|
function renderStatuspageServices() {
|
||||||
if (!statuspageServices) return;
|
if (!statuspageServices) return;
|
||||||
statuspageServices.innerHTML = '';
|
statuspageServices.innerHTML = '';
|
||||||
@@ -1194,7 +1419,7 @@ router.get('/', (req, res) => {
|
|||||||
actions.className = 'row';
|
actions.className = 'row';
|
||||||
const del = document.createElement('button');
|
const del = document.createElement('button');
|
||||||
del.className = 'danger-btn';
|
del.className = 'danger-btn';
|
||||||
del.textContent = 'LÃÂÃÂÃÂöschen';
|
del.textContent = 'Löschen';
|
||||||
del.addEventListener('click', async () => {
|
del.addEventListener('click', async () => {
|
||||||
await deleteService(svc.id);
|
await deleteService(svc.id);
|
||||||
});
|
});
|
||||||
@@ -1252,7 +1477,7 @@ router.get('/', (req, res) => {
|
|||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
await loadStatuspage();
|
await loadStatuspage();
|
||||||
} else {
|
} else {
|
||||||
showToast('Service lÃÂÃÂÃÂöschen fehlgeschlagen', true);
|
showToast('Service löschen fehlgeschlagen', true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1530,7 +1755,7 @@ router.get('/', (req, res) => {
|
|||||||
'</div>' +
|
'</div>' +
|
||||||
'<div class=\"ticket-meta\">User: ' +
|
'<div class=\"ticket-meta\">User: ' +
|
||||||
(t.userId || '-') +
|
(t.userId || '-') +
|
||||||
(t.claimedBy ? ' ÃÂ÷ Supporter: ' + t.claimedBy : '') +
|
(t.claimedBy ? ' <EFBFBD> Supporter: ' + t.claimedBy : '') +
|
||||||
'</div>';
|
'</div>';
|
||||||
const select = document.createElement('select');
|
const select = document.createElement('select');
|
||||||
select.innerHTML =
|
select.innerHTML =
|
||||||
@@ -1618,22 +1843,22 @@ router.get('/', (req, res) => {
|
|||||||
if (!list) return;
|
if (!list) return;
|
||||||
list.innerHTML = '';
|
list.innerHTML = '';
|
||||||
if (!automationCache.length) {
|
if (!automationCache.length) {
|
||||||
list.innerHTML = '<div class="ticket-empty">Keine Regeln.</div>';
|
list.innerHTML = '<div class=\"ticket-empty\">Keine Regeln.</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
automationCache.forEach((r) => {
|
automationCache.forEach((r) => {
|
||||||
const row = document.createElement('div');
|
const row = document.createElement('div');
|
||||||
row.className = 'ticket-list-item';
|
row.className = 'ticket-list-item';
|
||||||
row.innerHTML =
|
row.innerHTML =
|
||||||
'<div class="ticket-item-top"><div class="ticket-title">' +
|
'<div class=\"ticket-item-top\"><div class=\"ticket-title\">' +
|
||||||
(r.name || 'Regel') +
|
(r.name || 'Regel') +
|
||||||
'</div><div class="ticket-status-badge">' +
|
'</div><div class=\"ticket-status-badge\">' +
|
||||||
(r.active ? 'aktiv' : 'inaktiv') +
|
(r.active ? 'aktiv' : 'inaktiv') +
|
||||||
'</div></div>' +
|
'</div></div>' +
|
||||||
'<div class="ticket-meta">Condition: ' +
|
'<div class=\"ticket-meta\">Condition: ' +
|
||||||
JSON.stringify(r.condition || {}) +
|
JSON.stringify(r.condition || {}) +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'<div class="ticket-meta">Action: ' +
|
'<div class=\"ticket-meta\">Action: ' +
|
||||||
JSON.stringify(r.action || {}) +
|
JSON.stringify(r.action || {}) +
|
||||||
'</div>';
|
'</div>';
|
||||||
const actions = document.createElement('div');
|
const actions = document.createElement('div');
|
||||||
@@ -1644,14 +1869,14 @@ router.get('/', (req, res) => {
|
|||||||
edit.addEventListener('click', () => fillAutomationForm(r));
|
edit.addEventListener('click', () => fillAutomationForm(r));
|
||||||
const del = document.createElement('button');
|
const del = document.createElement('button');
|
||||||
del.className = 'danger-btn';
|
del.className = 'danger-btn';
|
||||||
del.textContent = 'Loeschen';
|
del.textContent = 'L<EFBFBD>schen';
|
||||||
del.addEventListener('click', async () => {
|
del.addEventListener('click', async () => {
|
||||||
const res = await fetch('/api/automations/' + r.id, {
|
const res = await fetch('/api/automations/' + r.id, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ guildId: currentGuild })
|
body: JSON.stringify({ guildId: currentGuild })
|
||||||
});
|
});
|
||||||
showToast(res.ok ? 'Regel geloescht' : 'Loeschen fehlgeschlagen', !res.ok);
|
showToast(res.ok ? 'Regel gel<EFBFBD>scht' : 'L<EFBFBD>schen fehlgeschlagen', !res.ok);
|
||||||
if (res.ok) loadAutomations();
|
if (res.ok) loadAutomations();
|
||||||
});
|
});
|
||||||
actions.appendChild(edit);
|
actions.appendChild(edit);
|
||||||
@@ -1660,7 +1885,8 @@ router.get('/', (req, res) => {
|
|||||||
list.appendChild(row);
|
list.appendChild(row);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function fillAutomationForm(rule) {
|
|
||||||
|
function fillAutomationForm(rule) {
|
||||||
document.getElementById('automationId').value = rule.id || '';
|
document.getElementById('automationId').value = rule.id || '';
|
||||||
document.getElementById('automationName').value = rule.name || '';
|
document.getElementById('automationName').value = rule.name || '';
|
||||||
document.getElementById('automationConditionType').value =
|
document.getElementById('automationConditionType').value =
|
||||||
@@ -1710,14 +1936,14 @@ function fillAutomationForm(rule) {
|
|||||||
edit.addEventListener('click', () => fillKbForm(a));
|
edit.addEventListener('click', () => fillKbForm(a));
|
||||||
const del = document.createElement('button');
|
const del = document.createElement('button');
|
||||||
del.className = 'danger-btn';
|
del.className = 'danger-btn';
|
||||||
del.textContent = 'LÃÂöschen';
|
del.textContent = 'L<EFBFBD>schen';
|
||||||
del.addEventListener('click', async () => {
|
del.addEventListener('click', async () => {
|
||||||
const res = await fetch('/api/kb/' + a.id, {
|
const res = await fetch('/api/kb/' + a.id, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ guildId: currentGuild })
|
body: JSON.stringify({ guildId: currentGuild })
|
||||||
});
|
});
|
||||||
showToast(res.ok ? 'Artikel gelÃÂöscht' : 'LÃÂöschen fehlgeschlagen', !res.ok);
|
showToast(res.ok ? 'Artikel gel<EFBFBD>scht' : 'L<EFBFBD>schen fehlgeschlagen', !res.ok);
|
||||||
if (res.ok) loadKb();
|
if (res.ok) loadKb();
|
||||||
});
|
});
|
||||||
actions.appendChild(edit);
|
actions.appendChild(edit);
|
||||||
@@ -1756,12 +1982,9 @@ function fillAutomationForm(rule) {
|
|||||||
if (!res.ok) return;
|
if (!res.ok) return;
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
const cfg = data.settings || {};
|
const cfg = data.settings || {};
|
||||||
const welcomeChannelInput = document.querySelector('input[name="welcomeChannelId"]');
|
document.querySelector('[name="welcomeChannelId"]').value = cfg.welcomeChannelId || '';
|
||||||
const logChannelInput = document.querySelector('input[name="logChannelId"]');
|
document.querySelector('[name="logChannelId"]').value = cfg.logChannelId || '';
|
||||||
const supportRoleInput = document.querySelector('input[name="supportRoleId"]');
|
document.querySelector('[name="supportRoleId"]').value = cfg.supportRoleId || '';
|
||||||
if (welcomeChannelInput) welcomeChannelInput.value = cfg.welcomeChannelId || '';
|
|
||||||
if (logChannelInput) logChannelInput.value = cfg.logChannelId || '';
|
|
||||||
if (supportRoleInput) supportRoleInput.value = cfg.supportRoleId || '';
|
|
||||||
automodConfigCache = cfg;
|
automodConfigCache = cfg;
|
||||||
modulesCache['ticketsEnabled'] = cfg.ticketsEnabled !== false;
|
modulesCache['ticketsEnabled'] = cfg.ticketsEnabled !== false;
|
||||||
modulesCache['automodEnabled'] = cfg.automodEnabled !== false;
|
modulesCache['automodEnabled'] = cfg.automodEnabled !== false;
|
||||||
@@ -1893,7 +2116,7 @@ function fillAutomationForm(rule) {
|
|||||||
(s.userId || '-') +
|
(s.userId || '-') +
|
||||||
'</strong></div><div class="muted">Ende: ' +
|
'</strong></div><div class="muted">Ende: ' +
|
||||||
formatDate(s.endedAt || Date.now()) +
|
formatDate(s.endedAt || Date.now()) +
|
||||||
' ÃÂÃÂÃÂ÷ Dauer: ' +
|
' · Dauer: ' +
|
||||||
dur +
|
dur +
|
||||||
'</div>';
|
'</div>';
|
||||||
supportRecentList.appendChild(div);
|
supportRecentList.appendChild(div);
|
||||||
@@ -1951,9 +2174,9 @@ function fillAutomationForm(rule) {
|
|||||||
(ev.repeatType || 'none') +
|
(ev.repeatType || 'none') +
|
||||||
'</span></div><div class="ticket-meta">Start: ' +
|
'</span></div><div class="ticket-meta">Start: ' +
|
||||||
formatDate(ev.startTime) +
|
formatDate(ev.startTime) +
|
||||||
' ÃÂÃÂÃÂ÷ Channel: ' +
|
' · Channel: ' +
|
||||||
(ev.channelId || '-') +
|
(ev.channelId || '-') +
|
||||||
' ÃÂÃÂÃÂ÷ Anmeldungen: ' +
|
' · Anmeldungen: ' +
|
||||||
(ev._count?.signups ?? 0) +
|
(ev._count?.signups ?? 0) +
|
||||||
'</div>';
|
'</div>';
|
||||||
const actions = document.createElement('div');
|
const actions = document.createElement('div');
|
||||||
@@ -2231,11 +2454,9 @@ function fillAutomationForm(rule) {
|
|||||||
if (!res.ok) return;
|
if (!res.ok) return;
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
const list = document.getElementById('moduleList');
|
const list = document.getElementById('moduleList');
|
||||||
if (!list) return;
|
|
||||||
list.innerHTML = '';
|
list.innerHTML = '';
|
||||||
let ticketsActive = false;
|
let ticketsActive = false;
|
||||||
let statuspageActive = false;
|
let statuspageActive = false;
|
||||||
let serverStatsActive = false;
|
|
||||||
let birthdayActive = false;
|
let birthdayActive = false;
|
||||||
let reactionRolesActive = false;
|
let reactionRolesActive = false;
|
||||||
let eventsActive = false;
|
let eventsActive = false;
|
||||||
@@ -2260,12 +2481,10 @@ function fillAutomationForm(rule) {
|
|||||||
if (m.key === 'automodEnabled') modulesCache['automodEnabled'] = willEnable;
|
if (m.key === 'automodEnabled') modulesCache['automodEnabled'] = willEnable;
|
||||||
if (m.key === 'welcomeEnabled') modulesCache['welcomeEnabled'] = willEnable;
|
if (m.key === 'welcomeEnabled') modulesCache['welcomeEnabled'] = willEnable;
|
||||||
if (m.key === 'statuspageEnabled') modulesCache['statuspageEnabled'] = willEnable;
|
if (m.key === 'statuspageEnabled') modulesCache['statuspageEnabled'] = willEnable;
|
||||||
if (m.key === 'serverStatsEnabled') modulesCache['serverStatsEnabled'] = willEnable;
|
|
||||||
if (m.key === 'birthdayEnabled') modulesCache['birthdayEnabled'] = willEnable;
|
if (m.key === 'birthdayEnabled') modulesCache['birthdayEnabled'] = willEnable;
|
||||||
if (m.key === 'reactionRolesEnabled') modulesCache['reactionRolesEnabled'] = willEnable;
|
if (m.key === 'reactionRolesEnabled') modulesCache['reactionRolesEnabled'] = willEnable;
|
||||||
if (m.key === 'eventsEnabled') modulesCache['eventsEnabled'] = willEnable;
|
if (m.key === 'eventsEnabled') modulesCache['eventsEnabled'] = willEnable;
|
||||||
applyNavVisibility();
|
applyNavVisibility();
|
||||||
if (m.key === 'serverStatsEnabled' && willEnable) loadServerStats();
|
|
||||||
} else {
|
} else {
|
||||||
showToast('Speichern fehlgeschlagen', true);
|
showToast('Speichern fehlgeschlagen', true);
|
||||||
}
|
}
|
||||||
@@ -2278,7 +2497,6 @@ function fillAutomationForm(rule) {
|
|||||||
if (m.key === 'welcomeEnabled') modulesCache['welcomeEnabled'] = !!m.enabled;
|
if (m.key === 'welcomeEnabled') modulesCache['welcomeEnabled'] = !!m.enabled;
|
||||||
if (m.key === 'dynamicVoiceEnabled') modulesCache['dynamicVoiceEnabled'] = !!m.enabled;
|
if (m.key === 'dynamicVoiceEnabled') modulesCache['dynamicVoiceEnabled'] = !!m.enabled;
|
||||||
if (m.key === 'statuspageEnabled') statuspageActive = !!m.enabled;
|
if (m.key === 'statuspageEnabled') statuspageActive = !!m.enabled;
|
||||||
if (m.key === 'serverStatsEnabled') serverStatsActive = !!m.enabled;
|
|
||||||
if (m.key === 'birthdayEnabled') birthdayActive = !!m.enabled;
|
if (m.key === 'birthdayEnabled') birthdayActive = !!m.enabled;
|
||||||
if (m.key === 'reactionRolesEnabled') reactionRolesActive = !!m.enabled;
|
if (m.key === 'reactionRolesEnabled') reactionRolesActive = !!m.enabled;
|
||||||
if (m.key === 'eventsEnabled') eventsActive = !!m.enabled;
|
if (m.key === 'eventsEnabled') eventsActive = !!m.enabled;
|
||||||
@@ -2286,7 +2504,6 @@ function fillAutomationForm(rule) {
|
|||||||
applyNavVisibility();
|
applyNavVisibility();
|
||||||
applyTicketsVisibility(ticketsActive);
|
applyTicketsVisibility(ticketsActive);
|
||||||
if (statuspageActive) loadStatuspage();
|
if (statuspageActive) loadStatuspage();
|
||||||
if (serverStatsActive) loadServerStats();
|
|
||||||
if (birthdayActive) loadBirthday();
|
if (birthdayActive) loadBirthday();
|
||||||
if (reactionRolesActive) loadReactionRoles();
|
if (reactionRolesActive) loadReactionRoles();
|
||||||
if (eventsActive) loadEvents();
|
if (eventsActive) loadEvents();
|
||||||
@@ -2295,7 +2512,7 @@ function fillAutomationForm(rule) {
|
|||||||
async function saveModuleToggle(key, enabled) {
|
async function saveModuleToggle(key, enabled) {
|
||||||
if (!currentGuild) return false;
|
if (!currentGuild) return false;
|
||||||
const payload = { guildId: currentGuild };
|
const payload = { guildId: currentGuild };
|
||||||
['ticketsEnabled', 'automodEnabled', 'welcomeEnabled', 'musicEnabled', 'levelingEnabled', 'statuspageEnabled', 'serverStatsEnabled', 'birthdayEnabled', 'reactionRolesEnabled', 'eventsEnabled'].forEach((k) => {
|
['ticketsEnabled', 'automodEnabled', 'welcomeEnabled', 'musicEnabled', 'levelingEnabled', 'statuspageEnabled', 'birthdayEnabled', 'reactionRolesEnabled', 'eventsEnabled'].forEach((k) => {
|
||||||
if (modulesCache[k] !== undefined) payload[k] = modulesCache[k];
|
if (modulesCache[k] !== undefined) payload[k] = modulesCache[k];
|
||||||
});
|
});
|
||||||
payload['dynamicVoiceEnabled'] = modulesCache['dynamicVoiceEnabled'];
|
payload['dynamicVoiceEnabled'] = modulesCache['dynamicVoiceEnabled'];
|
||||||
@@ -2317,9 +2534,7 @@ function fillAutomationForm(rule) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const settingsForm = document.getElementById('settingsForm');
|
document.getElementById('settingsForm').addEventListener('submit', async (e) => {
|
||||||
if (settingsForm) {
|
|
||||||
settingsForm.addEventListener('submit', async (e) => {
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!currentGuild) return;
|
if (!currentGuild) return;
|
||||||
const form = new FormData(e.currentTarget);
|
const form = new FormData(e.currentTarget);
|
||||||
@@ -2327,45 +2542,35 @@ function fillAutomationForm(rule) {
|
|||||||
payload.supportRoleId = payload.supportRoleId || undefined;
|
payload.supportRoleId = payload.supportRoleId || undefined;
|
||||||
payload.guildId = currentGuild;
|
payload.guildId = currentGuild;
|
||||||
const res = await fetch('/api/settings', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload) });
|
const res = await fetch('/api/settings', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload) });
|
||||||
const saveStatus = document.getElementById('saveStatus');
|
document.getElementById('saveStatus').textContent = res.ok ? 'Gespeichert' : 'Fehler';
|
||||||
if (saveStatus) saveStatus.textContent = res.ok ? 'Gespeichert' : 'Fehler';
|
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
await loadSettings(currentGuild);
|
await loadSettings(currentGuild);
|
||||||
await loadModules();
|
await loadModules();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const openPanelModalBtn = document.getElementById('openPanelModal');
|
document.getElementById('openPanelModal').addEventListener('click', () => {
|
||||||
if (openPanelModalBtn) {
|
document.getElementById('panelStatus').textContent = '';
|
||||||
openPanelModalBtn.addEventListener('click', () => {
|
|
||||||
const panelStatus = document.getElementById('panelStatus');
|
|
||||||
if (panelStatus) panelStatus.textContent = '';
|
|
||||||
showModal(panelModal);
|
showModal(panelModal);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
if (openSupportLogin) openSupportLogin.addEventListener('click', () => { showModal(supportLoginModal); });
|
if (openSupportLogin) openSupportLogin.addEventListener('click', () => { showModal(supportLoginModal); });
|
||||||
if (supportLoginSave) supportLoginSave.addEventListener('click', saveSupportLogin);
|
if (supportLoginSave) supportLoginSave.addEventListener('click', saveSupportLogin);
|
||||||
if (refreshSupportStatus) refreshSupportStatus.addEventListener('click', loadSupportLogin);
|
if (refreshSupportStatus) refreshSupportStatus.addEventListener('click', loadSupportLogin);
|
||||||
|
|
||||||
const panelSubmit = document.getElementById('panelSubmit');
|
document.getElementById('panelSubmit').addEventListener('click', async () => {
|
||||||
if (panelSubmit) {
|
|
||||||
panelSubmit.addEventListener('click', async () => {
|
|
||||||
if (!currentGuild) return;
|
if (!currentGuild) return;
|
||||||
const formEl = document.getElementById('panelForm');
|
const form = new FormData(document.getElementById('panelForm'));
|
||||||
const form = formEl ? new FormData(formEl) : null;
|
const channelId = form.get('channelId');
|
||||||
const channelId = form?.get('channelId');
|
const panelTitle = form.get('panelTitle');
|
||||||
const panelTitle = form?.get('panelTitle');
|
const panelDescription = form.get('panelDescription');
|
||||||
const panelDescription = form?.get('panelDescription');
|
const panelCategories = form.get('panelCategories');
|
||||||
const panelCategories = form?.get('panelCategories');
|
|
||||||
const status = document.getElementById('panelStatus');
|
const status = document.getElementById('panelStatus');
|
||||||
const ok = await createPanel(channelId, panelTitle, panelDescription, panelCategories);
|
const ok = await createPanel(channelId, panelTitle, panelDescription, panelCategories);
|
||||||
if (status) status.textContent = ok ? 'Panel gesendet' : 'Fehler beim Senden';
|
if (status) status.textContent = ok ? 'Panel gesendet' : 'Fehler beim Senden';
|
||||||
showToast(ok ? 'Ticket-Panel gesendet' : 'Ticket-Panel Fehler', !ok);
|
showToast(ok ? 'Ticket-Panel gesendet' : 'Ticket-Panel Fehler', !ok);
|
||||||
if (ok) hideModal();
|
if (ok) hideModal();
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
document.querySelectorAll('[data-close-modal]').forEach((btn) => btn.addEventListener('click', hideModal));
|
document.querySelectorAll('[data-close-modal]').forEach((btn) => btn.addEventListener('click', hideModal));
|
||||||
modalBackdrop.addEventListener('click', hideModal);
|
modalBackdrop.addEventListener('click', hideModal);
|
||||||
@@ -2511,10 +2716,6 @@ function fillAutomationForm(rule) {
|
|||||||
if (statuspageInterval) statuspageInterval.addEventListener('change', saveStatuspageConfig);
|
if (statuspageInterval) statuspageInterval.addEventListener('change', saveStatuspageConfig);
|
||||||
if (statuspageChannel) statuspageChannel.addEventListener('change', saveStatuspageConfig);
|
if (statuspageChannel) statuspageChannel.addEventListener('change', saveStatuspageConfig);
|
||||||
if (statuspageAddService) statuspageAddService.addEventListener('click', addServicePrompt);
|
if (statuspageAddService) statuspageAddService.addEventListener('click', addServicePrompt);
|
||||||
if (statsToggle) statsToggle.addEventListener('click', async () => { statsToggle.classList.toggle('on'); await saveServerStats(); });
|
|
||||||
if (statsCategoryName) statsCategoryName.addEventListener('change', saveServerStats);
|
|
||||||
if (statsRefresh) statsRefresh.addEventListener('change', saveServerStats);
|
|
||||||
if (statsAddItem) statsAddItem.addEventListener('click', () => editServerStat(null));
|
|
||||||
[welcomeTitle, welcomeDescription, welcomeFooter, welcomeColor].forEach((el) => {
|
[welcomeTitle, welcomeDescription, welcomeFooter, welcomeColor].forEach((el) => {
|
||||||
if (el) el.addEventListener('input', updateWelcomePreview);
|
if (el) el.addEventListener('input', updateWelcomePreview);
|
||||||
});
|
});
|
||||||
@@ -2682,4 +2883,3 @@ router.get('/settings', (_req, res) => {
|
|||||||
export default router;
|
export default router;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user