import { useEffect, useState } from 'react'
import api from '../api/client'
import { useAuthStore } from '../stores/auth'
import { copyToClipboard } from '../utils/clipboard'
import { Plus, Trash2, KeyRound, ShieldAlert, Copy, Check, Key, Save, Settings, QrCode, ShieldCheck, ShieldOff } from 'lucide-react'
import { useSettingsStore } from '../stores/settings'
export default function SettingsPage() {
const [users, setUsers] = useState([])
const [showAdd, setShowAdd] = useState(false)
const [form, setForm] = useState({ username: '', password: '', display_name: '' })
const [pwChange, setPwChange] = useState(null)
const [pwForm, setPwForm] = useState({ password: '', confirm: '' })
const [pwMsg, setPwMsg] = useState('')
const { user: currentUser } = useAuthStore()
const reload = () => api.getUsers().then((u) => setUsers(u || []))
useEffect(() => { reload() }, [])
const handleAdd = async () => {
if (!form.username || !form.password) return
await api.createUser(form)
setForm({ username: '', password: '', display_name: '' })
setShowAdd(false)
reload()
}
const handleDelete = async (id) => {
if (!confirm('Benutzer wirklich loeschen?')) return
await api.deleteUser(id)
reload()
}
const handlePasswordChange = async () => {
setPwMsg('')
if (!pwForm.password || pwForm.password.length < 6) {
setPwMsg('Mindestens 6 Zeichen')
return
}
if (pwForm.password !== pwForm.confirm) {
setPwMsg('Passwoerter stimmen nicht ueberein')
return
}
try {
await api.changePassword(pwChange, pwForm.password)
setPwMsg('Passwort geaendert')
setTimeout(() => { setPwChange(null); setPwMsg(''); setPwForm({ password: '', confirm: '' }) }, 1500)
} catch (e) {
setPwMsg('Fehler: ' + e.message)
}
}
return (
Einstellungen
{/* Users */}
Benutzer
setShowAdd(true)}
className="flex items-center gap-1 text-sm text-orange-400 hover:text-orange-300"
>
Hinzufuegen
{showAdd && (
)}
{users.map((u) => (
{u.username}
{u.display_name && (
({u.display_name})
)}
{u.role}
{currentUser?.username === u.username && (
(Du)
)}
{u.last_login ? `Login: ${new Date(u.last_login).toLocaleString('de-DE')}` : 'Nie eingeloggt'}
{ setPwChange(pwChange === u.id ? null : u.id); setPwMsg(''); setPwForm({ password: '', confirm: '' }) }}
className="text-gray-500 hover:text-orange-400 p-1 rounded hover:bg-gray-800 transition-colors"
title="Passwort aendern"
>
handleDelete(u.id)}
className="text-gray-500 hover:text-red-400 p-1 rounded hover:bg-gray-800 transition-colors"
title="Benutzer loeschen"
>
{/* Password Change */}
{pwChange === u.id && (
)}
))}
{/* API Keys */}
{/* 2FA */}
{/* System Settings */}
)
}
function APIKeysSection() {
const [keys, setKeys] = useState([])
const [showAdd, setShowAdd] = useState(false)
const [newName, setNewName] = useState('')
const [newKey, setNewKey] = useState(null)
const [copied, setCopied] = useState(null)
const [loading, setLoading] = useState(true)
const reload = () => {
setLoading(true)
api.getAPIKeys()
.then(k => setKeys(k || []))
.finally(() => setLoading(false))
}
useEffect(() => { reload() }, [])
const handleCreate = async () => {
if (!newName.trim()) return
try {
const result = await api.createAPIKey(newName.trim())
setNewKey(result)
setNewName('')
reload()
} catch (e) {
alert('Fehler: ' + e.message)
}
}
const handleDelete = async (id, name) => {
if (!confirm(`API-Key "${name}" wirklich loeschen? Alle Agents mit diesem Key verlieren den Zugang.`)) return
await api.deleteAPIKey(id)
reload()
}
const copyText = (text, id) => {
copyToClipboard(text)
setCopied(id)
setTimeout(() => setCopied(null), 2000)
}
return (
API-Keys
({keys.length})
{ setShowAdd(true); setNewKey(null); setNewName('') }}
className="flex items-center gap-1 text-sm text-orange-400 hover:text-orange-300"
>
Neuer Key
{/* Neuer Key Dialog */}
{showAdd && !newKey && (
)}
{/* Neuer Key Anzeige */}
{newKey && (
API-Key erstellt — jetzt kopieren, er wird nur einmal angezeigt!
{newKey.key}
copyText(newKey.key, 'new')}
className="px-3 py-2 bg-gray-800 hover:bg-gray-700 rounded text-gray-300 transition-colors"
>
{copied === 'new' ? : }
{ setNewKey(null); setShowAdd(false) }}
className="mt-2 text-xs text-gray-500 hover:text-gray-400"
>
Fertig
)}
{/* Key Liste */}
{loading ? (
Laden...
) : keys.length === 0 ? (
Keine API-Keys vorhanden
) : (
{keys.map((k) => (
{k.name}
{k.key.substring(0, 8)}...{k.key.substring(k.key.length - 4)}
copyText(k.key, k.id)}
className="text-gray-600 hover:text-orange-400 transition-colors"
title="Key kopieren"
>
{copied === k.id ? : }
Erstellt: {new Date(k.created_at).toLocaleDateString('de-DE')}
{k.last_used && (
Letzter Zugriff: {new Date(k.last_used).toLocaleString('de-DE')}
)}
handleDelete(k.id, k.name)}
className="text-gray-500 hover:text-red-400 p-1 rounded hover:bg-gray-800 transition-colors ml-2"
title="Key loeschen"
>
))}
)}
)
}
function TwoFactorSection() {
const { user } = useAuthStore()
const [step, setStep] = useState('idle') // idle | setup | confirm | disable
const [setupData, setSetupData] = useState(null)
const [code, setCode] = useState('')
const [password, setPassword] = useState('')
const [msg, setMsg] = useState('')
const [error, setError] = useState('')
const totpEnabled = user?.totp_enabled
const handleSetup = async () => {
setError(''); setMsg('')
try {
const data = await api.setupTOTP()
setSetupData(data)
setStep('setup')
} catch (e) {
setError(e.message)
}
}
const handleConfirm = async () => {
setError('')
try {
await api.confirmTOTP(code)
setMsg('2FA aktiviert')
setStep('idle')
setCode('')
setSetupData(null)
// user neu laden
await useAuthStore.getState().checkAuth()
} catch (e) {
setError(e.message)
}
}
const handleDisable = async () => {
setError('')
try {
await api.disableTOTP(password, code)
setMsg('2FA deaktiviert')
setStep('idle')
setCode(''); setPassword('')
await useAuthStore.getState().checkAuth()
} catch (e) {
setError(e.message)
}
}
return (
Zwei-Faktor-Authentifizierung (2FA)
{totpEnabled
? Aktiv
: Inaktiv
}
{error &&
{error}
}
{msg &&
{msg}
}
{step === 'idle' && (
{totpEnabled
? 'TOTP-Authentifizierung ist aktiv. Jeder Login erfordert einen 6-stelligen Code.'
: 'Schütze deinen Account mit einer Authenticator-App (z.B. Google Authenticator, Aegis).'}
{totpEnabled
?
{ setStep('disable'); setMsg('') }}
className="ml-4 shrink-0 text-sm text-red-400 hover:text-red-300">Deaktivieren
:
Einrichten
}
)}
{step === 'setup' && setupData && (
1. Scanne den QR-Code mit deiner Authenticator-App oder gib den Secret manuell ein.
Secret (manuell)
{setupData.secret}
OTP URI (für QR-Code-Scanner)
{setupData.uri}
2. Gib den ersten Code ein um die Einrichtung zu bestätigen:
setCode(e.target.value.replace(/\D/g, ''))}
placeholder="000000"
className="w-40 bg-gray-800 border border-gray-700 rounded px-3 py-2 text-white text-center text-lg tracking-widest focus:outline-none focus:border-orange-500"
/>
Aktivieren
{ setStep('idle'); setCode(''); setSetupData(null); setError('') }}
className="px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded text-sm text-gray-300">
Abbrechen
)}
{step === 'disable' && (
)}
)
}
function SystemSettingsSection() {
const { settings, fetchSettings } = useSettingsStore()
const [form, setForm] = useState({})
const [saving, setSaving] = useState(false)
const [msg, setMsg] = useState('')
const [copied, setCopied] = useState(null)
useEffect(() => {
fetchSettings()
}, [])
useEffect(() => {
setForm({
backend_url_internal: settings.backend_url_internal || '',
backend_url_external: settings.backend_url_external || '',
api_key: settings.api_key || '',
})
}, [settings])
const handleSave = async () => {
setSaving(true)
setMsg('')
try {
await api.updateSettings(form)
await fetchSettings()
setMsg('Gespeichert')
setTimeout(() => setMsg(''), 3000)
} catch (e) {
setMsg('Fehler: ' + e.message)
} finally {
setSaving(false)
}
}
const copyText = (text, id) => {
copyToClipboard(text)
setCopied(id)
setTimeout(() => setCopied(null), 2000)
}
return (
System-Einstellungen
{saving ? 'Speichere...' : 'Speichern'}
{msg && (
{msg}
)}
)
}