import { useEffect, useState } from 'react'; import { apiGet, apiPut, apiPost, apiDelete } from '../api.js'; const API_SCOPES = [ { id: 'message', label: 'message', hint: 'Nachrichten/Embeds in Kanäle posten' }, { id: 'dm', label: 'dm', hint: 'Direktnachrichten senden' }, { id: 'roles', label: 'roles', hint: 'Rollen geben/nehmen' }, { id: 'read', label: 'read', hint: 'Member-Infos & Statistiken lesen' }, ]; /** Sekunden → "2d 4h 13m" */ function fmtUptime(s) { const d = Math.floor(s / 86400); const h = Math.floor((s % 86400) / 3600); const m = Math.floor((s % 3600) / 60); return [d && `${d}d`, (d || h) && `${h}h`, `${m}m`].filter(Boolean).join(' '); } function fmtBytes(b) { if (b > 1024 * 1024) return `${(b / 1024 / 1024).toFixed(1)} MB`; return `${(b / 1024).toFixed(0)} KB`; } export default function Settings({ me }) { const [data, setData] = useState(null); const [form, setForm] = useState(null); const [error, setError] = useState(null); const [feedback, setFeedback] = useState(null); // Community const [playtesters, setPlaytesters] = useState([]); // Composer const [composer, setComposer] = useState({ channel: '', title: '', text: '', messageId: '' }); // API-Keys const [keys, setKeys] = useState([]); const [newKeyName, setNewKeyName] = useState(''); const [newKeyScopes, setNewKeyScopes] = useState(['message']); const [createdKey, setCreatedKey] = useState(null); useEffect(() => { if (!me.admin) return; apiGet('/api/settings') .then((d) => { setData(d); setForm(d.settings); }) .catch((e) => setError(e.status)); apiGet('/api/apikeys').then((d) => setKeys(d.keys)).catch(() => {}); apiGet('/api/playtesters').then((d) => setPlaytesters(d.playtesters)).catch(() => {}); }, [me.admin]); async function createKey() { if (!newKeyName.trim() || newKeyScopes.length === 0) return; try { const res = await apiPost('/api/apikeys', { name: newKeyName.trim(), scopes: newKeyScopes }); setCreatedKey(res); setNewKeyName(''); setKeys((await apiGet('/api/apikeys')).keys); } catch { flash('✗ Key-Erstellung fehlgeschlagen'); } } async function revokeKey(id) { if (!window.confirm('Diesen API-Key widerrufen? Skripte mit diesem Key verlieren sofort den Zugriff.')) return; await apiDelete(`/api/apikeys/${id}`).catch(() => {}); setKeys((await apiGet('/api/apikeys')).keys); if (createdKey?.id === id) setCreatedKey(null); } function flash(msg) { setFeedback(msg); setTimeout(() => setFeedback(null), 3000); } async function save() { try { const res = await apiPut('/api/settings', form); setForm(res.settings); flash('✓ Gespeichert'); } catch { flash('✗ Speichern fehlgeschlagen'); } } async function sendCompose() { if (!composer.channel || !composer.text.trim()) { flash('✗ Kanal und Text nötig'); return; } try { const res = await apiPost('/api/compose', { channel_id: composer.channel, message_id: composer.messageId.trim() || undefined, embed_title: composer.title.trim() || undefined, content: composer.text, }); flash(res.edited ? '✓ Aktualisiert' : `✓ Gesendet (ID ${res.message_id})`); if (!res.edited) setComposer({ ...composer, messageId: res.message_id }); } catch { flash('✗ Senden fehlgeschlagen'); } } async function sendTest(target) { try { await apiPost(`/api/settings/test/${target}`); flash('✓ Test-Nachricht gesendet'); } catch { flash('✗ Test fehlgeschlagen — Kanal/Rechte prüfen'); } } const header = (
SETUP
); if (me.loading) return <>{header}

Lade …

; if (!me.user || !me.admin) { return ( <> {header}

🔒 Nur für den Admin.

{!me.user && Login mit Discord}
); } if (error) return <>{header}

Fehler beim Laden ({error}).

; if (!data || !form) return <>{header}

Lade …

; const channelOptions = data.channels.map((c) => ( )); return ( <> {header}
{/* Status */}

// 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
{/* Allgemein */}

// Allgemein

setForm({ ...form, public_url: e.target.value })} />
setForm({ ...form, gitea_url: e.target.value })} />
{/* Kanäle */}

// Kanäle

{/* Devlog */}

// Devlog

{/* Bug-Reports */}

// Bug-Reports (/bug)

{!data.status.giteaTokenConfigured && (

⚠️ Kein GITEA_API_TOKEN gesetzt — /bug ist deaktiviert.

)}
setForm({ ...form, bug_report_repo: e.target.value })} />
setForm({ ...form, roadmap_repo: e.target.value })} />
{/* Watchdog */}

// Watchdog

setForm({ ...form, watchdog_urls: e.target.value })} />
{/* Community */}

// Community

{playtesters.length > 0 && (
{playtesters.map((p) => (
{p.username} seit {p.applied_at.slice(0, 10)}
))}
)}
setForm({ ...form, starboard_threshold: e.target.value })} />
{/* Moderation & Kontakt */}

// Moderation & Kontakt

{/* Composer */}

// Post verfassen

setComposer({ ...composer, title: e.target.value })} />