radio/web/frontend/src/components/LogsPanel.jsx
profit 3d635b742c Initial commit: self-hosted personal radio station
Flask + React web UI with audio player, podcast queue, feed management,
episode browser, music library, schedule viewer, and log tail.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 19:01:33 -07:00

108 lines
3.3 KiB
JavaScript

import { useState, useEffect, useRef } from "react";
import { api } from "../api";
const LOG_FILES = ["poller", "liquidsoap", "icecast_access", "icecast_error"];
export default function LogsPanel() {
const [logFile, setLogFile] = useState("poller");
const [lines, setLines] = useState([]);
const [lineCount, setLineCount] = useState(100);
const [autoRefresh, setAutoRefresh] = useState(false);
const [exists, setExists] = useState(true);
const bottomRef = useRef(null);
const fetchLogs = () => {
api.getLogs({ file: logFile, lines: lineCount })
.then((data) => {
setLines(data.lines);
setExists(data.exists);
})
.catch(() => {});
};
useEffect(fetchLogs, [logFile, lineCount]);
useEffect(() => {
if (!autoRefresh) return;
const id = setInterval(fetchLogs, 5000);
return () => clearInterval(id);
}, [autoRefresh, logFile, lineCount]);
useEffect(() => {
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
}, [lines]);
return (
<div>
<div className="flex items-center justify-between mb-6 flex-wrap gap-3">
<h1 className="text-2xl font-bold">Logs</h1>
<div className="flex gap-3 items-center">
<select
value={logFile}
onChange={(e) => setLogFile(e.target.value)}
className="input text-sm"
>
{LOG_FILES.map((f) => (
<option key={f} value={f}>{f}.log</option>
))}
</select>
<select
value={lineCount}
onChange={(e) => setLineCount(parseInt(e.target.value))}
className="input text-sm"
>
<option value={50}>50 lines</option>
<option value={100}>100 lines</option>
<option value={200}>200 lines</option>
</select>
<label className="flex items-center gap-2 text-sm cursor-pointer">
<input
type="checkbox"
checked={autoRefresh}
onChange={(e) => setAutoRefresh(e.target.checked)}
className="accent-radio-accent"
/>
Auto-refresh
</label>
<button onClick={fetchLogs} className="btn btn-ghost text-sm">
Refresh
</button>
</div>
</div>
<div className="card p-0">
{!exists ? (
<div className="p-8 text-center text-radio-muted">
Log file <code>{logFile}.log</code> does not exist yet
</div>
) : lines.length === 0 ? (
<div className="p-8 text-center text-radio-muted">
Log file is empty
</div>
) : (
<pre className="text-xs font-mono overflow-x-auto p-4 max-h-[70vh] overflow-y-auto leading-relaxed">
{lines.map((line, i) => (
<div
key={i}
className={`py-0.5 ${
line.includes("ERROR") || line.includes("error")
? "text-radio-danger"
: line.includes("WARNING") || line.includes("warning")
? "text-yellow-400"
: "text-radio-muted"
}`}
>
{line}
</div>
))}
<div ref={bottomRef} />
</pre>
)}
</div>
</div>
);
}