Files
NexusOS/interface/web/src/Logs.jsx

94 lines
3.0 KiB
React

import { useEffect, useRef, useState } from "react";
import { API_BASE } from "./config";
export function Logs() {
const [tabs, setTabs] = useState([]);
const [active, setActive] = useState(null);
const [content, setContent] = useState("");
const [follow, setFollow] = useState(true);
const preRef = useRef(null);
// Load the available log names once.
useEffect(() => {
fetch(`${API_BASE}/logs`)
.then(r => r.json())
.then(d => {
setTabs(d.logs || []);
setActive(prev => prev ?? (d.logs || [])[0] ?? null);
})
.catch(() => setTabs([]));
}, []);
// Poll the active log every 2s.
useEffect(() => {
if (!active) return;
let cancelled = false;
const load = () => {
fetch(`${API_BASE}/logs/${active}`)
.then(r => r.json())
.then(d => { if (!cancelled) setContent(d.content || (d.missing ? "(no log file yet)" : "")); })
.catch(() => { if (!cancelled) setContent("(failed to load log)"); });
};
load();
const id = setInterval(load, 2000);
return () => { cancelled = true; clearInterval(id); };
}, [active]);
// Auto-scroll to bottom when following.
useEffect(() => {
if (follow && preRef.current) preRef.current.scrollTop = preRef.current.scrollHeight;
}, [content, follow]);
return (
<div style={{ display: "flex", flexDirection: "column", flexGrow: 1, minHeight: 0 }}>
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "0.75rem" }}>
<div style={{ display: "flex", gap: "0.4rem", flexWrap: "wrap" }}>
{tabs.map(name => (
<button
key={name}
onClick={() => setActive(name)}
style={{
padding: "0.4rem 0.8rem",
background: active === name ? "#007acc" : "#161616",
color: "#fff",
border: "1px solid " + (active === name ? "#0099ff" : "#2a2a2a"),
borderRadius: "6px",
cursor: "pointer",
fontSize: "0.8rem",
textTransform: "capitalize",
}}
>
{name}
</button>
))}
</div>
<label style={{ fontSize: "0.75rem", color: "#888", display: "flex", alignItems: "center", gap: "0.35rem", cursor: "pointer" }}>
<input type="checkbox" checked={follow} onChange={e => setFollow(e.target.checked)} />
Follow
</label>
</div>
<pre
ref={preRef}
style={{
flexGrow: 1,
minHeight: 0,
overflow: "auto",
background: "#0a0a0a",
border: "1px solid #2a2a2a",
borderRadius: "8px",
padding: "0.9rem",
margin: 0,
fontSize: "0.78rem",
lineHeight: 1.45,
color: "#cfcfcf",
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
whiteSpace: "pre-wrap",
wordBreak: "break-word",
}}
>
{content || "(empty)"}
</pre>
</div>
);
}