Yayın durdurma, kanal duraklat/devam (tekli+toplu), şimdi kontrol et, manuel URL ile zorla kayıt başlatma, canlı player+log önizleme, oturum süre sayacı, başarısız/takılı render için tekrar dene ve iptal, poll aralığı + ham segment TTL'yi panelden canlı değiştirme, cookie bayatlık uyarısı, Telegram bildirim aç/kapa, ve kullanım/maliyet özeti (/stats). api-daemon'ın yeni state-değiştiren endpoint'leri (stop/force-start/ render/cancel) INTERNAL_API_TOKEN ile korunuyor — bu daemon'ın portu Coolify'de public'e açık olduğu için korumasız bırakmak güvenlik açığı olurdu. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
54 lines
1.3 KiB
TypeScript
54 lines
1.3 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef, useState } from "react";
|
|
|
|
export function LiveLogViewer({ sessionId }: { sessionId: string }) {
|
|
const [lines, setLines] = useState<string[]>([]);
|
|
const boxRef = useRef<HTMLPreElement>(null);
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
|
|
async function poll() {
|
|
try {
|
|
const res = await fetch(`/api/debug/capture/${sessionId}`, { cache: "no-store" });
|
|
const data = (await res.json()) as { lines: string[] };
|
|
if (!cancelled) setLines(data.lines);
|
|
} catch {
|
|
// transient fetch failures are fine to just skip — next tick retries
|
|
}
|
|
}
|
|
|
|
poll();
|
|
const timer = setInterval(poll, 4000);
|
|
return () => {
|
|
cancelled = true;
|
|
clearInterval(timer);
|
|
};
|
|
}, [sessionId]);
|
|
|
|
useEffect(() => {
|
|
if (boxRef.current) boxRef.current.scrollTop = boxRef.current.scrollHeight;
|
|
}, [lines]);
|
|
|
|
return (
|
|
<pre
|
|
ref={boxRef}
|
|
className="mono"
|
|
style={{
|
|
maxHeight: "220px",
|
|
overflowY: "auto",
|
|
background: "var(--bg)",
|
|
border: "1px solid var(--border)",
|
|
borderRadius: "6px",
|
|
padding: "0.6rem 0.7rem",
|
|
fontSize: "0.7rem",
|
|
whiteSpace: "pre-wrap",
|
|
marginTop: "0.5rem",
|
|
}}
|
|
>
|
|
{lines.length > 0 ? lines.join("\n") : "Log bekleniyor…"}
|
|
</pre>
|
|
);
|
|
}
|