From 2ce274707a7ab36b2e9c6aa8083cf971779e1f07 Mon Sep 17 00:00:00 2001 From: D4rkst3r Date: Fri, 31 Jul 2026 16:13:33 +0200 Subject: [PATCH] Config statt Setup: Bereiche gruppiert, Suche, Status als Einstieg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Die Seite hieß Setup und listete 15 Bereiche flach untereinander. Wer nicht wusste, dass Geburtstage unter „Community" stecken und Willkommens- Texte unter „Support", hat geklickt bis er es fand. Die Seitenleiste gruppiert jetzt nach Überblick, Auftritt, Inhalte, Community, Technik und Zugang. Darüber steht ein Suchfeld, das nicht nur Beschriftungen durchsucht, sondern auch Stichwörter je Bereich — „geburtstag" führt zu Community, „ticket" zu Support. Enter springt zum ersten Treffer. Der aktive Bereich steht in der Adresse (/settings#texte). Damit überlebt er das Neuladen, und man kann jemandem einen Link auf genau die Stelle schicken. Der Status-Bereich war die leerste Seite im ganzen Panel: sechs Zahlen. Er ist jetzt der Einstieg und beantwortet die Frage, die man beim Öffnen wirklich hat — was läuft noch nicht? Eingeschaltete Module, denen ein Kanal oder eine Rolle fehlt, stehen dort mit einem Knopf, der direkt an die richtige Stelle springt. Ist alles eingerichtet, sagt die Seite genau das. Umbenannt in „Config", weil „Setup" nach einmaliger Einrichtung klingt — die Seite ist aber der Ort, an dem man dauerhaft alles einstellt. Schmale Bildschirme: die Leiste wird zur umbrechenden Zeile ohne Gruppen-Überschriften, das Suchfeld nimmt die volle Breite. Co-Authored-By: Claude Fable 5 --- README.md | 48 +++++-- docs/konzept-zwei-seiten.md | 10 +- docs/sso.md | 6 +- frontend/src/App.jsx | 4 +- frontend/src/pages/Impressum.jsx | 2 +- frontend/src/pages/Settings.jsx | 208 +++++++++++++++++++++------ frontend/src/style.css | 64 ++++++++- src/bot/commands/galerie-backfill.js | 2 +- src/bot/commands/playtester-setup.js | 2 +- src/bot/commands/tag.js | 2 +- src/bot/commands/ticket-setup.js | 2 +- src/bot/release-feed.js | 2 +- src/bot/watchdog.js | 2 +- src/runtime-settings.js | 2 +- src/web/api-v1.js | 2 +- 15 files changed, 280 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index 1971b6b..3e8652c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Discord-Bot + Webinterface: Devlog-Tagebuch, Commit-Feed, Changelog und Community-Tools für die EcoGame-Entwicklung — alles gebrandet, alles über -die Setup-Seite steuerbar. +die Config-Seite steuerbar. **Stack:** Node.js 20+ (ESM), discord.js v14, Fastify 5, React 19 + Vite, SQLite (better-sqlite3, FTS5), Docker Multi-Stage — deploybar als Portainer-Stack. @@ -22,7 +22,7 @@ SQLite (better-sqlite3, FTS5), Docker Multi-Stage — deploybar als Portainer-St | 📊 **Wochen-Rückblick** | Sonntags 20:00 automatisch: Commits pro Tag als Balken, Devlogs, Top-Projekte | | 🐛 **`/bug`** | Member melden Bugs → Gitea-Issue (inkl. Screenshot-Upload); Issue geschlossen → DM an den Reporter | | 🚨 **Watchdog** | Prüft eigene Dienste alle 2 min, DM-Alarm bei Ausfall + Entwarnung | -| 🧪 **Playtester-Programm** | `/playtester-setup` postet den Bewerbungs-Button; Rolle + Liste (Setup-Seite); Alpha-Keys später per DM über die API | +| 🧪 **Playtester-Programm** | `/playtester-setup` postet den Bewerbungs-Button; Rolle + Liste (Config-Seite); Alpha-Keys später per DM über die API | | ⭐ **Starboard** | Nachrichten mit genug ⭐-Reaktionen landen automatisch im Best-of-Kanal | | 📸 **Screenshot-Galerie** | Bilder aus dem Screenshot-Kanal → öffentliche `/galerie` (lokal gespeichert); Historie via `/galerie-backfill` | | 📬 **Modmail** | DM an den Bot → Thread im privaten Staff-Kanal; Antworten im Thread gehen als DM zurück | @@ -47,14 +47,14 @@ SQLite (better-sqlite3, FTS5), Docker Multi-Stage — deploybar als Portainer-St | 📢 **Auto-Publish** | Devlog-/Release-/Composer-Posts in Ankündigungs-Kanälen werden automatisch veröffentlicht (Follower bekommen sie) | | 🔗 **Link-Vorschau** | Jedes Devlog hat einen Permalink (`/devlogs/:id`); geteilte Links zeigen überall gebrandete Open-Graph-Vorschau mit Bild | | 📋 **Mod-Log** | Gelöschte/bearbeitete Nachrichten in einen privaten Log-Kanal | -| 🎮 **Server-Monitor** | DiscordGSM-Stil: eigener Setup-Tab, pro Server ein Live-Embed (🟢/🔴, Spieler-Balken mit %, Map, Spieler-Liste, klickbarer Connect-Link, Ping); **300+ Spiele via gamedig** (Minecraft, Rust, CS2, Valheim, ARK …) + FiveM + HTTP-Check; Down/Up-Alerts (🚨 nach 2 Fehlversuchen, ✅ mit Downtime bei Recovery) in eigenen Alert-Kanal; Spielerzahl in der Presence | +| 🎮 **Server-Monitor** | DiscordGSM-Stil: eigener Config-Tab, pro Server ein Live-Embed (🟢/🔴, Spieler-Balken mit %, Map, Spieler-Liste, klickbarer Connect-Link, Ping); **300+ Spiele via gamedig** (Minecraft, Rust, CS2, Valheim, ARK …) + FiveM + HTTP-Check; Down/Up-Alerts (🚨 nach 2 Fehlversuchen, ✅ mit Downtime bei Recovery) in eigenen Alert-Kanal; Spielerzahl in der Presence | | 👥 **Team-Rechte** | Team-Mitglieder bekommen gezielten Web-Zugriff (Composer, Bewerbungen, Rollen, Server, …) — Brand, System, API-Keys bleiben Owner-only; **Audit-Log** im Team-Tab zeigt dem Owner die letzten Aktionen | | 🔑 **Single Sign-On** | Andere Dienste (Kanban, Platform …) nutzen den Discord-Login des Bots mit und bekommen die Discord-Rollen gleich mitgeliefert — [Anleitung](docs/sso.md) | | 🎨 **Geteiltes Design** | `/brand.css` + `/brand-nav.js` geben jeder anderen App den D4RKST3R-Look samt Navigation — Farben kommen live aus dem Brand-Tab; dazu ein [Gitea-Theme](docs/gitea-theme/) im selben Look | | 💾 **Repo-Backups** | Nachts werden alle Gitea-Repos als git-Bundle gesichert (komplette Historie, direkt wieder klonbar) | | ⚡ **Auto-Deploy** | Push auf `main` → Portainer rollt neu aus; optional mit Code-Prüfung über Gitea Actions — [Anleitung](docs/auto-deploy.md) | -| 🏠 **Portal** | Dienste (Gitea, Kanban, Cloud …) im Setup pflegen → Kacheln auf der Startseite und Links in der geteilten Navigation | -| ⚖️ **Rechtstexte** | `/impressum` + `/datenschutz` aus dem Setup gepflegt; Member können ihre Daten selbst löschen (DSGVO Art. 17) | +| 🏠 **Portal** | Dienste (Gitea, Kanban, Cloud …) in der Config pflegen → Kacheln auf der Startseite und Links in der geteilten Navigation | +| ⚖️ **Rechtstexte** | `/impressum` + `/datenschutz` in der Config gepflegt; Member können ihre Daten selbst löschen (DSGVO Art. 17) | | 💡 **Feature-Voting** | `/wunsch` → Voting-Post mit 👍; Top-Wünsche öffentlich auf der Roadmap-Seite | | 🎉 **Giveaways** | `/giveaway` (Admin): Teilnahme-Button, automatische Ziehung nach Ablauf; am Gewinner-Post: 🔁 Neu auslosen (Admin) + 👥 Teilnehmerliste | | 📈 **Contribution-Heatmap** | GitHub-Style-Jahreskalender aus dem Commit-Archiv auf der Roadmap-Seite | @@ -72,12 +72,12 @@ SQLite (better-sqlite3, FTS5), Docker Multi-Stage — deploybar als Portainer-St | `/changelog` | öffentlich | Alle Releases mit Notes, Tag- und Pre-Release-Chips | | `/feed.xml` | öffentlich | RSS-Feed der Devlogs | | `/profil` | Member | Eigenes Profil: Rang, XP, Rollen (togglebar), Alpha-Key, DSGVO-Löschung | -| `/impressum` · `/datenschutz` | öffentlich | Rechtstexte — Betreiber-Angaben aus dem Setup (Owner-only) | +| `/impressum` · `/datenschutz` | öffentlich | Rechtstexte — Betreiber-Angaben aus der Config (Owner-only) | | `/` | öffentlich | **Frei angelegte Seiten** (Regeln, Über uns, FAQ …) — im Seiten-Tab gepflegt | | `/` | öffentlich | Startseite: Hero, Live-Zahlen (Member/Server/Spieler), Bereichs-Kacheln, neuestes Devlog | | `/server` | öffentlich | Live-Status aller Game-Server: Logo, Spieler-Balken, 24h-Verlauf, Uptime & Peak, Copy-Adresse | | `/commits` | nur Admin | Archivierte Commits aller Repos (SHA → Gitea-Link) | -| `/settings` | nur Admin | **Setup-Seite** — siehe unten | +| `/settings` | nur Admin | **Config-Seite** — siehe unten | Login via Discord-OAuth2 (identify-Scope, signierte Session-Cookies, keine Token-Speicherung). Design: D4RKST3R-Brand (Neon-Gelb/Orange auf Schwarz, Bebas Neue + Barlow Condensed + @@ -105,11 +105,29 @@ Ein leeres Feld stellt den Standardtext wieder her. Die Vorlagen stehen in [`src/templates.js`](src/templates.js). Der Standard lebt im Code, die Datenbank enthält nur echte Abweichungen. -### Setup-Seite (`/settings`) -Aufgeteilt in **Tabs mit Sidebar-Navigation** (Status · Brand · Feeds · Community · -Rollen · Support · Bewerbungen · Composer · Server · System · API · Team) — -Team-Mitglieder sehen nur die Tabs ihrer Bereiche. Alles zur Laufzeit änderbar — gespeichert in -SQLite, Env-Variablen sind nur Fallback, kein Redeploy nötig: +### Config-Seite (`/settings`) +15 Bereiche, in der Seitenleiste nach Themen gruppiert: + +| Gruppe | Bereiche | +|---|---| +| Überblick | Status · Module · Texte | +| Auftritt | Brand · Seiten | +| Inhalte | Feeds · Composer | +| Community | Community · Rollen · Support · Bewerbungen | +| Technik | Server · System | +| Zugang | API · Team | + +Das Suchfeld über der Leiste findet Bereiche auch über Stichwörter — „geburtstag" +führt zu Community, „willkommen" zu Support. Enter springt zum ersten Treffer. +Der aktive Bereich steht in der Adresse (`/settings#texte`), Links und Neuladen +landen also wieder dort. + +Der Status-Bereich ist der Einstieg: Kennzahlen plus **„Noch einzurichten"** — +alle eingeschalteten Module, denen noch ein Kanal oder eine Rolle fehlt, mit +Sprung direkt zur richtigen Stelle. + +Team-Mitglieder sehen nur die Bereiche ihrer Rechte. Alles zur Laufzeit änderbar — +gespeichert in SQLite, Env-Variablen sind nur Fallback, kein Redeploy nötig: - **Allgemein:** Öffentliche URL, Gitea-URL - **Kanäle** (Devlog / Commit / Release) als Dropdown + „Test senden"-Button je Kanal @@ -172,7 +190,7 @@ in Portainer als Stack-Environment-Variables. | `GITEA_API_TOKEN` | optional | Für `/bug` → Issues (Scope `write:issue`); alternativ im Brand-Tab hinterlegen | | `GITEA_URL` | optional | Default `https://git.d4rkst3r.de` | | `PUBLIC_URL` | optional | Default `https://bot.d4rkst3r.de` | -| `COMMIT_CHANNEL_ID` / `DEVLOG_CHANNEL_ID` | optional | Fallbacks — Kanäle kommen normal von der Setup-Seite | +| `COMMIT_CHANNEL_ID` / `DEVLOG_CHANNEL_ID` | optional | Fallbacks — Kanäle kommen normal von der Config-Seite | | `TZ` | optional | Default `Europe/Berlin` (Wochen-Rückblick, Datumsformate) | ¹ Generieren: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"` @@ -255,7 +273,7 @@ node tools/test-webhook.mjs Der Bot ist die zentrale Discord-Brücke der Infrastruktur: devlog.py, Platform, FiveM-Server, CI-Jobs … reden alle mit einer API statt mit zig Discord-Webhooks. -**Auth:** API-Keys auf der Setup-Seite erstellen (Name + Scopes, Key wird einmalig +**Auth:** API-Keys auf der Config-Seite erstellen (Name + Scopes, Key wird einmalig angezeigt, Widerruf jederzeit). Jeder Request: ``` Authorization: Bearer d4rk_ @@ -305,7 +323,7 @@ urllib.request.urlopen(req) | `GET /brand.css` · `/brand-nav.js` | — | Design-Tokens + Navigation für andere Apps (CORS offen) | | `GET /api/services` | — | Dienste-Kacheln fürs Portal (Pflege: Scope `settings`) | | `GET /api/legal` · `DELETE /api/profile` | — / Member | Impressums-Angaben · DSGVO-Löschung | -| `GET/PUT /api/settings`, `POST /api/settings/test/:target` | Admin | Setup-Seite | +| `GET/PUT /api/settings`, `POST /api/settings/test/:target` | Admin | Config-Seite | | `GET /api/commits?page=` · `DELETE /api/devlogs/:id` | Admin | Commit-Archiv · Devlog löschen | | `GET /auth/login` · `/auth/callback` · `/auth/logout` | — | Discord-OAuth2 | diff --git a/docs/konzept-zwei-seiten.md b/docs/konzept-zwei-seiten.md index 510015a..89e2001 100644 --- a/docs/konzept-zwei-seiten.md +++ b/docs/konzept-zwei-seiten.md @@ -1,11 +1,11 @@ # Konzept: Bot-Produktseite und Community-Hub trennen **Stand:** 31.07.2026 · Umgesetzt. Zum Aktivieren müssen nur noch die beiden -Adressen im Setup eingetragen werden (System → Zwei Seiten). +Adressen in der Config eingetragen werden (System → Zwei Seiten). ## Aktivieren -1. **Setup → System → Zwei Seiten** ausfüllen: +1. **Config → System → Zwei Seiten** ausfüllen: * Community-Hub: `https://hub.d4rkst3r.de` * Bot-Produktseite: `https://bot.d4rkst3r.de` * Login-Domain: `.d4rkst3r.de` (damit die Anmeldung auf beiden gilt) @@ -43,7 +43,7 @@ Konfigurieren. | `/features` | Alle Funktionen ausführlich, gruppiert (Community, Moderation, Feeds, Server) | | `/commands` | Slash-Command-Referenz, aus dem Code erzeugt | | `/docs` | Kurzanleitungen: API v1, SSO, Webhooks, Design-System | -| `/dashboard` | die heutige Setup-Seite (Login + Berechtigung nötig) | +| `/dashboard` | die heutige Config-Seite (Login + Berechtigung nötig) | | `/impressum`, `/datenschutz` | Rechtstexte (auf beiden Domains identisch) | Die 16 Slash-Commands, die es aktuell gibt: `ping`, `bug`, `wunsch`, @@ -83,7 +83,7 @@ Zeile ändern, ohne dass ein Commit nötig wird. Deshalb ein schlanker Seiten-Editor statt eines Forums: -* Seiten werden im Setup angelegt: Titel, Adress-Kürzel, Inhalt in Markdown +* Seiten werden in der Config angelegt: Titel, Adress-Kürzel, Inhalt in Markdown * Live-Vorschau wie im Composer, gerendert mit dem vorhandenen Markdown-Parser * pro Seite wählbar: veröffentlicht oder Entwurf, im Menü sichtbar oder nur über den Direktlink erreichbar, Reihenfolge im Menü @@ -185,7 +185,7 @@ Login-Umleitung und Dashboard-Verweise nutzen `botUrl()`. 4. Frontend in `shared/`, `hub/` und `bot/` aufteilen, zwei Builds einrichten 5. Host-Routing in Backend und Frontend (siehe oben) 6. Bot-Landing, Feature- und Command-Seite bauen -7. Setup-Seite nach `/dashboard` umhängen +7. Config-Seite nach `/dashboard` umhängen 8. Weiterleitungen für alte Links 9. Cookie-Domain umstellen 10. `hub_url`/`bot_url` in den Einstellungen ergänzen und alle Verwendungen umziehen diff --git a/docs/sso.md b/docs/sso.md index 4091172..cf76ca6 100644 --- a/docs/sso.md +++ b/docs/sso.md @@ -6,7 +6,7 @@ und bekommst dabei gleich die **Discord-Rollen** des Users mitgeliefert. ## 1. App registrieren -Setup → **API** → *Single Sign-On*: +Config → **API** → *Single Sign-On*: | Feld | Beispiel | Bedeutung | |---|---|---| @@ -116,7 +116,7 @@ Brand-Tokens und eine fertige Navigation aus: Die Farben kommen live aus dem Brand-Tab — änderst du sie dort, ändern sie sich überall mit (Cache: 5 Minuten). * **`/brand-nav.js`** — fügt oben die D4RKST3R-Leiste ein, mit Links zurück zum Hub - und zu allen im Setup gepflegten Diensten. Die Links stecken fertig im Skript, + und zu allen in der Config gepflegten Diensten. Die Links stecken fertig im Skript, es sind also keine weiteren Anfragen und kein CORS nötig. Die Schriften installierst du in der App selbst, damit sie lokal ausgeliefert werden @@ -134,4 +134,4 @@ npm i @fontsource/bebas-neue @fontsource/barlow-condensed @fontsource/share-tech * Rollen werden bei jedem Login frisch von Discord geholt. Wer eine Rolle verliert, verliert die Rechte beim nächsten Login — für sofortige Wirkung die Sitzung in der App kurz halten oder periodisch neu prüfen. -* Wird eine App im Setup gelöscht, funktioniert ihr Login sofort nicht mehr. +* Wird eine App in der Config gelöscht, funktioniert ihr Login sofort nicht mehr. diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index c3f8a0f..5c640a6 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -136,7 +136,7 @@ export default function App() { {p.title} ))} - {(me.admin || me.scopes?.length > 0) && Setup} + {(me.admin || me.scopes?.length > 0) && Config}
@@ -178,7 +178,7 @@ export default function App() { ))} {me.user && Profil} {me.admin && Commits} - {(me.admin || me.scopes?.length > 0) && Setup} + {(me.admin || me.scopes?.length > 0) && Config} {me.user ? Logout : Login mit Discord} diff --git a/frontend/src/pages/Impressum.jsx b/frontend/src/pages/Impressum.jsx index e8a7529..dbfba92 100644 --- a/frontend/src/pages/Impressum.jsx +++ b/frontend/src/pages/Impressum.jsx @@ -30,7 +30,7 @@ export default function Impressum() { {incomplete && (

✗ Die Impressums-Angaben sind noch nicht vollständig hinterlegt - (Setup → System → Impressum). + (Config → System → Impressum).

)} diff --git a/frontend/src/pages/Settings.jsx b/frontend/src/pages/Settings.jsx index 0de4a03..db2563c 100644 --- a/frontend/src/pages/Settings.jsx +++ b/frontend/src/pages/Settings.jsx @@ -6,6 +6,7 @@ import { IconApplications, IconComposer, IconServer, IconSystem, IconApi, IconTeam, IconLock, IconBot, IconUpload, IconChevronDown, IconKey, IconPages, IconX, IconHome as IconLayers, IconWarning, IconCheck, IconMessage, IconRefresh, + IconSearch, } from '../icons.jsx'; import { Markdown } from '../markdown.jsx'; @@ -16,22 +17,54 @@ const API_SCOPES = [ { id: 'read', label: 'read', hint: 'Member-Infos & Statistiken lesen' }, ]; +// Bereiche der Seitenleiste — bei 15 Tabs findet man ohne Gruppierung nichts mehr. +const TAB_SECTIONS = [ + { id: 'ueberblick', label: 'Überblick' }, + { id: 'auftritt', label: 'Auftritt' }, + { id: 'inhalte', label: 'Inhalte' }, + { id: 'community', label: 'Community' }, + { id: 'technik', label: 'Technik' }, + { id: 'zugang', label: 'Zugang' }, +]; + +// `find` sind zusätzliche Suchbegriffe: was jemand tippt, wenn er den Tab-Namen +// nicht kennt („geburtstag" → Community, „willkommen" → Support). const TABS = [ - { id: 'status', Icon: IconStatus, label: 'Status' }, - { id: 'module', Icon: IconLayers, label: 'Module' }, - { id: 'texte', Icon: IconMessage, label: 'Texte' }, - { id: 'brand', Icon: IconBrand, label: 'Brand' }, - { id: 'feeds', Icon: IconFeeds, label: 'Feeds' }, - { id: 'community', Icon: IconCommunity, label: 'Community' }, - { id: 'rollen', Icon: IconRoles, label: 'Rollen' }, - { id: 'support', Icon: IconSupport, label: 'Support' }, - { id: 'bewerbungen', Icon: IconApplications, label: 'Bewerbungen' }, - { id: 'composer', Icon: IconComposer, label: 'Composer' }, - { id: 'seiten', Icon: IconPages, label: 'Seiten' }, - { id: 'server', Icon: IconServer, label: 'Server' }, - { id: 'system', Icon: IconSystem, label: 'System' }, - { id: 'api', Icon: IconApi, label: 'API' }, - { id: 'team', Icon: IconTeam, label: 'Team' }, + { id: 'status', Icon: IconStatus, label: 'Status', section: 'ueberblick', + find: 'uptime laufzeit datenbank backup version zahlen übersicht' }, + { id: 'module', Icon: IconLayers, label: 'Module', section: 'ueberblick', + find: 'funktionen an aus schalter aktivieren deaktivieren' }, + { id: 'texte', Icon: IconMessage, label: 'Texte', section: 'ueberblick', + find: 'nachrichten vorlagen platzhalter begrüßung wortlaut' }, + + { id: 'brand', Icon: IconBrand, label: 'Brand', section: 'auftritt', + find: 'avatar banner farbe logo name footer aussehen embed' }, + { id: 'seiten', Icon: IconPages, label: 'Seiten', section: 'auftritt', + find: 'regeln über uns faq markdown menü impressum' }, + + { id: 'feeds', Icon: IconFeeds, label: 'Feeds', section: 'inhalte', + find: 'devlog commit release changelog rückblick threads ping' }, + { id: 'composer', Icon: IconComposer, label: 'Composer', section: 'inhalte', + find: 'nachricht senden embed bauen tags trigger geplant zeitplan' }, + + { id: 'community', Icon: IconCommunity, label: 'Community', section: 'community', + find: 'level xp starboard galerie geburtstag playtester alpha keys voting wünsche autorolle' }, + { id: 'rollen', Icon: IconRoles, label: 'Rollen', section: 'community', + find: 'rollen menü selbstbedienung buttons reaction roles' }, + { id: 'support', Icon: IconSupport, label: 'Support', section: 'community', + find: 'ticket modmail willkommen protokoll modlog moderation warnung' }, + { id: 'bewerbungen', Icon: IconApplications, label: 'Bewerbungen', section: 'community', + find: 'formular fragen bewerbung prüfen annehmen ablehnen' }, + + { id: 'server', Icon: IconServer, label: 'Server', section: 'technik', + find: 'game server monitor fivem minecraft status spieler gamedig' }, + { id: 'system', Icon: IconSystem, label: 'System', section: 'technik', + find: 'backup gitea watchdog twitch youtube events sprachkanäle impressum datenschutz' }, + + { id: 'api', Icon: IconApi, label: 'API', section: 'zugang', + find: 'schlüssel key token zugriff sso webhook' }, + { id: 'team', Icon: IconTeam, label: 'Team', section: 'zugang', + find: 'mitarbeiter rechte bereiche protokoll audit zugriff' }, ]; // Welcher Team-Scope schaltet welchen Tab frei (null = nur Owner) @@ -125,7 +158,15 @@ function fmtBytes(b) { } export default function Settings({ me }) { - const [tab, setTab] = useState('status'); + // Tab steht in der Adresse (#texte), damit Neuladen und Links den Bereich behalten + const [tab, setTabState] = useState( + () => TABS.find((t) => t.id === window.location.hash.slice(1))?.id ?? 'status' + ); + const setTab = (id) => { + setTabState(id); + window.history.replaceState(null, '', `#${id}`); + }; + const [tabSearch, setTabSearch] = useState(''); const [data, setData] = useState(null); const [form, setForm] = useState(null); const [error, setError] = useState(null); @@ -202,6 +243,12 @@ export default function Settings({ me }) { return need ? me.scopes?.includes(need) : false; }; const visibleTabs = TABS.filter((t) => canTab(t.id)); + // Suche greift auf Beschriftung und Stichwörter — wer „geburtstag" tippt, + // landet bei Community, ohne den Tab-Namen zu kennen. + const query = tabSearch.trim().toLowerCase(); + const foundTabs = query + ? visibleTabs.filter((t) => `${t.label} ${t.find}`.toLowerCase().includes(query)) + : visibleTabs; // Tags + geplante Posts const [tags, setTags] = useState([]); const [tagDraft, setTagDraft] = useState({ name: '', content: '' }); @@ -666,11 +713,11 @@ export default function Settings({ me }) { const header = (
-
SETUP
+
CONFIG
@@ -706,21 +753,66 @@ export default function Settings({ me }) { /* ── Tab-Inhalte ───────────────────────────────── */ + // Eingeschaltete Module, denen noch etwas fehlt — die tun bis dahin nichts + const activeModules = modules.filter((m) => m.enabled); + const unfinished = activeModules.filter((m) => m.missing.length > 0); + const tabStatus = ( -
-

// Status

-
-
{data.status.botTag ?? '—'}Bot
-
{fmtUptime(data.status.uptimeSeconds)}Uptime
-
{data.status.devlogs}Devlogs
-
{data.status.commits}Commits
-
{fmtBytes(data.status.dbSizeBytes)}Datenbank
-
- {data.status.lastBackup ? data.status.lastBackup.slice(0, 10) : '—'} - Letztes Backup + <> +
+

// Status

+
+
{data.status.botTag ?? '—'}Bot
+
{fmtUptime(data.status.uptimeSeconds)}Uptime
+ {modules.length > 0 && ( +
+ {activeModules.length}/{modules.length} + Module an +
+ )} +
{data.status.devlogs}Devlogs
+
{data.status.commits}Commits
+
{fmtBytes(data.status.dbSizeBytes)}Datenbank
+
+ {data.status.lastBackup ? data.status.lastBackup.slice(0, 10) : '—'} + Letztes Backup +
-
+ + {modules.length > 0 && ( +
+

// Noch einzurichten

+ {unfinished.length === 0 ? ( +

+ Alles eingerichtet — jedes eingeschaltete Modul hat, was es braucht. +

+ ) : ( + <> +

+ Diese Module sind eingeschaltet, aber es fehlt ihnen noch etwas — + bis dahin tun sie nichts. +

+
+ {unfinished.map((m) => ( +
+ {m.name} + + {m.missing.join(', ')} + + {m.tab && ( + + )} +
+ ))} +
+ + )} +
+ )} + ); const statusDot = { online: '#23a55a', idle: '#f0b232', dnd: '#f23f43' }[form.bot_presence_status] ?? '#23a55a'; @@ -2089,16 +2181,52 @@ export default function Settings({ me }) {
diff --git a/frontend/src/style.css b/frontend/src/style.css index 6f77f12..eec5cc8 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -635,9 +635,59 @@ a.sha:hover { border-color: var(--neon); background: rgba(245, 197, 24, .07); } gap: .25rem; position: sticky; top: 5rem; - min-width: 11rem; + width: 12.5rem; flex-shrink: 0; + /* Bei 15 Bereichen wird die Leiste höher als der Bildschirm — dann scrollt sie selbst */ + max-height: calc(100vh - 6.5rem); + overflow-y: auto; + padding-right: .3rem; } +.settings-nav::-webkit-scrollbar { width: 5px; } +.settings-nav::-webkit-scrollbar-thumb { background: var(--border); border-radius: 99px; } + +/* Suche über den Bereichen */ +.settings-search { + display: flex; align-items: center; gap: .5rem; + background: var(--bg2); border: 1px solid var(--border); + border-radius: 9px; padding: .45rem .6rem; + margin-bottom: .5rem; color: var(--muted2); + transition: border-color .15s; +} +.settings-search:focus-within { border-color: var(--neon); } +.settings-search input { + flex: 1; min-width: 0; + background: none; border: 0; outline: none; padding: 0; + font-family: var(--mono); font-size: .72rem; letter-spacing: .08em; + color: var(--text); +} +.settings-search input::placeholder { color: var(--muted2); letter-spacing: .12em; } +.settings-search-clear { + background: none; border: 0; padding: 0; display: inline-flex; + color: var(--muted2); cursor: pointer; +} +.settings-search-clear:hover { color: var(--neon); } + +/* Bereichs-Gruppen */ +.settings-nav-group { display: flex; flex-direction: column; gap: .2rem; margin-bottom: .7rem; } +.settings-nav-label { + font-family: var(--mono); + font-size: .55rem; letter-spacing: .22em; text-transform: uppercase; + color: var(--muted2); + padding: .3rem .9rem .25rem; +} +.settings-nav-empty { + font-family: var(--mono); font-size: .66rem; letter-spacing: .1em; + color: var(--muted2); padding: .6rem .9rem; +} + +/* Status-Seite: „Noch einzurichten" */ +.stat-of { color: var(--muted2); font-size: .6em; } +.todo-missing { + font-family: var(--mono); font-size: .62rem; + letter-spacing: .1em; text-transform: uppercase; + color: var(--neon2); flex: 1; +} +.todo-done { color: var(--success); } .settings-nav-item { display: flex; align-items: center; gap: .7rem; font-family: var(--mono); @@ -712,15 +762,21 @@ a.sha:hover { border-color: var(--neon); background: rgba(245, 197, 24, .07); } @media (max-width: 760px) { .settings-layout { flex-direction: column; } + /* Schmal wird aus der Leiste eine Zeile: Suche oben, Bereiche daneben scrollend. + Die Gruppen-Überschriften fallen weg — waagerecht bringen sie nichts. */ .settings-nav { position: static; flex-direction: row; - overflow-x: auto; + flex-wrap: wrap; + overflow: visible; + max-height: none; width: 100%; - min-width: 0; gap: .35rem; - padding-bottom: .35rem; + padding: 0 0 .35rem; } + .settings-search { width: 100%; margin-bottom: .2rem; } + .settings-nav-group { flex-direction: row; flex-wrap: wrap; margin-bottom: 0; gap: .35rem; } + .settings-nav-label { display: none; } .settings-nav-item { border-left: none; border-bottom: 2px solid transparent; diff --git a/src/bot/commands/galerie-backfill.js b/src/bot/commands/galerie-backfill.js index e40343c..deb152b 100644 --- a/src/bot/commands/galerie-backfill.js +++ b/src/bot/commands/galerie-backfill.js @@ -13,7 +13,7 @@ export async function execute(interaction) { const channelId = screenshotChannelId(); if (!channelId) { - await interaction.editReply('❌ Erst auf der Setup-Seite einen Screenshot-Kanal wählen.'); + await interaction.editReply('❌ Erst auf der Config-Seite einen Screenshot-Kanal wählen.'); return; } const channel = await interaction.client.channels.fetch(channelId); diff --git a/src/bot/commands/playtester-setup.js b/src/bot/commands/playtester-setup.js index b42a83a..eff2421 100644 --- a/src/bot/commands/playtester-setup.js +++ b/src/bot/commands/playtester-setup.js @@ -14,7 +14,7 @@ export const data = new SlashCommandBuilder() export async function execute(interaction) { if (!playtesterRoleId()) { await interaction.reply({ - content: '❌ Erst auf der Setup-Seite eine Playtester-Rolle wählen.', + content: '❌ Erst auf der Config-Seite eine Playtester-Rolle wählen.', flags: MessageFlags.Ephemeral, }); return; diff --git a/src/bot/commands/tag.js b/src/bot/commands/tag.js index bd6682f..f77ddf9 100644 --- a/src/bot/commands/tag.js +++ b/src/bot/commands/tag.js @@ -1,4 +1,4 @@ -// /tag — gespeicherte Text-Bausteine abrufen (verwaltet über die Setup-Seite) +// /tag — gespeicherte Text-Bausteine abrufen (verwaltet über die Config-Seite) import { SlashCommandBuilder, MessageFlags } from 'discord.js'; import { getTag, listTags } from '../../db.js'; diff --git a/src/bot/commands/ticket-setup.js b/src/bot/commands/ticket-setup.js index ee14ead..894c0e6 100644 --- a/src/bot/commands/ticket-setup.js +++ b/src/bot/commands/ticket-setup.js @@ -14,7 +14,7 @@ export const data = new SlashCommandBuilder() export async function execute(interaction) { if (!ticketChannelId()) { await interaction.reply({ - content: '❌ Erst auf der Setup-Seite einen Ticket-Kanal wählen.', + content: '❌ Erst auf der Config-Seite einen Ticket-Kanal wählen.', flags: MessageFlags.Ephemeral, }); return; diff --git a/src/bot/release-feed.js b/src/bot/release-feed.js index a781754..100f8d2 100644 --- a/src/bot/release-feed.js +++ b/src/bot/release-feed.js @@ -1,4 +1,4 @@ -// Release-Ankündigungen: großes Embed in den konfigurierten Kanal (Setup-Seite) +// Release-Ankündigungen: großes Embed in den konfigurierten Kanal (Config-Seite) import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from 'discord.js'; import { releaseChannelId, publicUrl, brandColor2, brandFooter } from '../runtime-settings.js'; import { config } from '../config.js'; diff --git a/src/bot/watchdog.js b/src/bot/watchdog.js index 2b83219..b09151c 100644 --- a/src/bot/watchdog.js +++ b/src/bot/watchdog.js @@ -1,5 +1,5 @@ // Watchdog: prüft konfigurierte URLs und schickt dem Admin eine DM bei Ausfall. -// URLs kommen aus dem Setting watchdog_urls (Setup-Seite), leer = deaktiviert. +// URLs kommen aus dem Setting watchdog_urls (Config-Seite), leer = deaktiviert. import { getSetting } from '../db.js'; import { config } from '../config.js'; import { moduleEnabled } from '../modules.js'; diff --git a/src/runtime-settings.js b/src/runtime-settings.js index b448146..4af4a9d 100644 --- a/src/runtime-settings.js +++ b/src/runtime-settings.js @@ -75,7 +75,7 @@ export function discordGuildId() { return getSetting('discord_guild_id') || config.discordGuildId || null; } -/* ── Branding (Setup → Brand-Tab) ── */ +/* ── Branding (Config → Brand-Tab) ── */ /** Brand-Name für Footer/Embeds (Default: D4RKST3R) */ export function brandName() { diff --git a/src/web/api-v1.js b/src/web/api-v1.js index 1f0d926..73b0fd9 100644 --- a/src/web/api-v1.js +++ b/src/web/api-v1.js @@ -1,5 +1,5 @@ // Öffentliche v1-API für eigene Skripte/Dienste (devlog.py, Platform, FiveM, CI …) -// Auth: Authorization: Bearer d4rk_ — Keys + Scopes über die Setup-Seite. +// Auth: Authorization: Bearer d4rk_ — Keys + Scopes über die Config-Seite. import { verifyApiKey, archiveStats } from '../db.js'; /** Bearer-Key prüfen; bei Erfolg Key-Info zurückgeben, sonst Fehler-Antwort senden */