import { useCallback, useEffect, useRef, useState } from 'react'; const MAX_LINES = 2000; /** * Live-Konsole eines Containers im Admin-Bereich. Liest `GET /api/admin/logs/...` als Stream * (fetch statt EventSource, weil die Anmeldung im Authorization-Kopf mitgeht). */ export default function ConsoleDialog({ title, path, token, onClose, note }) { const [lines, setLines] = useState([]); const [status, setStatus] = useState('verbinde'); const [follow, setFollow] = useState(true); const [filter, setFilter] = useState(''); const [run, setRun] = useState(0); const boxRef = useRef(null); useEffect(() => { const ctl = new AbortController(); let rest = ''; setLines([]); setStatus('verbinde'); (async () => { try { const res = await fetch(`/api${path}?tail=300`, { headers: { authorization: `Bearer ${token}` }, signal: ctl.signal }); if (!res.ok) { const body = await res.json().catch(() => ({})); throw new Error(body.error ?? `Fehler ${res.status}`); } setStatus('live'); const reader = res.body.getReader(); const decoder = new TextDecoder(); for (;;) { const { value, done } = await reader.read(); if (done) break; const parts = (rest + decoder.decode(value, { stream: true })).split('\n'); rest = parts.pop(); if (parts.length) setLines((cur) => [...cur, ...parts].slice(-MAX_LINES)); } if (rest) setLines((cur) => [...cur, rest].slice(-MAX_LINES)); setStatus('beendet'); } catch (err) { if (err.name !== 'AbortError') setStatus(`Fehler: ${err.message}`); } })(); return () => ctl.abort(); }, [path, token, run]); // Mitlaufen: ans Ende scrollen, solange "Mitlaufen" an ist useEffect(() => { if (follow && boxRef.current) boxRef.current.scrollTop = boxRef.current.scrollHeight; }, [lines, follow]); const onKey = useCallback((e) => { if (e.key === 'Escape') onClose(); }, [onClose]); useEffect(() => { document.addEventListener('keydown', onKey); return () => document.removeEventListener('keydown', onKey); }, [onKey]); const q = filter.trim().toLowerCase(); const shown = q ? lines.filter((l) => l.toLowerCase().includes(q)) : lines; return (
e.stopPropagation()}>
Konsole: {title} {status === 'live' ? 'live' : status}
setFilter(e.target.value)} placeholder="Zeilen filtern" aria-label="Zeilen filtern" /> {shown.length} Zeilen
 {
          const el = e.currentTarget;
          const atEnd = el.scrollHeight - el.scrollTop - el.clientHeight < 40;
          if (atEnd !== follow) setFollow(atEnd);
        }}>
          {shown.length ? shown.join('\n') : status === 'verbinde' ? 'Verbinde …' : 'Noch keine Ausgabe.'}
        

{note ?? 'Passwörter sind ausgeblendet.'} Die Ansicht zeigt die letzten {MAX_LINES} Zeilen.

); }