2026-04-13 00:46:58 -06:00
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { useQuery } from "@tanstack/react-query";
|
UX/UI/perf pass: admin tabs, theme toggle, log polish, mod search, JAR cache
- Admin page split into tabs (Server/Players/Chat/Mods/Backups/Logs) with
hash + localStorage persistence; inactive tabs no longer mount.
- Log viewer: level color-coding, search, level filter chips, auto-scroll
toggle, copy button, visible-line count.
- Installed mods list: search field + side filter (all/both/server/client)
with live count; public ModList gets skeleton + empty states and search.
- Theme toggle with no-flash inline init, localStorage + system preference.
- Layout: full OG / Twitter metadata, title template, keywords,
dual-theme themeColor, metadataBase.
- lib/mods.ts: per-jar mtime+size parse cache (cold 6s -> warm ~45ms on
/api/mods for the full mod list); cache eviction on mod removal.
- ChatBridge polling eased 3s -> 5s with 2s stale window.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 04:58:25 -06:00
|
|
|
import { useMemo, useRef, useEffect, useState } from "react";
|
2026-04-13 00:46:58 -06:00
|
|
|
import { Button } from "@/components/ui/button";
|
UX/UI/perf pass: admin tabs, theme toggle, log polish, mod search, JAR cache
- Admin page split into tabs (Server/Players/Chat/Mods/Backups/Logs) with
hash + localStorage persistence; inactive tabs no longer mount.
- Log viewer: level color-coding, search, level filter chips, auto-scroll
toggle, copy button, visible-line count.
- Installed mods list: search field + side filter (all/both/server/client)
with live count; public ModList gets skeleton + empty states and search.
- Theme toggle with no-flash inline init, localStorage + system preference.
- Layout: full OG / Twitter metadata, title template, keywords,
dual-theme themeColor, metadataBase.
- lib/mods.ts: per-jar mtime+size parse cache (cold 6s -> warm ~45ms on
/api/mods for the full mod list); cache eviction on mod removal.
- ChatBridge polling eased 3s -> 5s with 2s stale window.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 04:58:25 -06:00
|
|
|
import { Input } from "@/components/ui/input";
|
2026-04-13 00:46:58 -06:00
|
|
|
import {
|
|
|
|
|
Card,
|
|
|
|
|
CardContent,
|
|
|
|
|
CardDescription,
|
|
|
|
|
CardHeader,
|
|
|
|
|
CardTitle,
|
|
|
|
|
} from "@/components/ui/card";
|
|
|
|
|
|
UX/UI/perf pass: admin tabs, theme toggle, log polish, mod search, JAR cache
- Admin page split into tabs (Server/Players/Chat/Mods/Backups/Logs) with
hash + localStorage persistence; inactive tabs no longer mount.
- Log viewer: level color-coding, search, level filter chips, auto-scroll
toggle, copy button, visible-line count.
- Installed mods list: search field + side filter (all/both/server/client)
with live count; public ModList gets skeleton + empty states and search.
- Theme toggle with no-flash inline init, localStorage + system preference.
- Layout: full OG / Twitter metadata, title template, keywords,
dual-theme themeColor, metadataBase.
- lib/mods.ts: per-jar mtime+size parse cache (cold 6s -> warm ~45ms on
/api/mods for the full mod list); cache eviction on mod removal.
- ChatBridge polling eased 3s -> 5s with 2s stale window.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 04:58:25 -06:00
|
|
|
type Level = "ERROR" | "WARN" | "INFO" | "DEBUG" | "OTHER";
|
|
|
|
|
|
|
|
|
|
const LEVEL_ORDER: Level[] = ["ERROR", "WARN", "INFO", "DEBUG", "OTHER"];
|
|
|
|
|
|
|
|
|
|
const levelClass: Record<Level, string> = {
|
|
|
|
|
ERROR: "text-red-300",
|
|
|
|
|
WARN: "text-amber-300",
|
|
|
|
|
INFO: "text-muted-foreground",
|
|
|
|
|
DEBUG: "text-blue-300/70",
|
|
|
|
|
OTHER: "text-muted-foreground/70",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function detectLevel(line: string): Level {
|
|
|
|
|
if (/\b(ERROR|FATAL|SEVERE|Exception|Traceback)\b/i.test(line)) return "ERROR";
|
|
|
|
|
if (/\b(WARN|WARNING)\b/i.test(line)) return "WARN";
|
|
|
|
|
if (/\bINFO\b/i.test(line)) return "INFO";
|
|
|
|
|
if (/\bDEBUG\b/i.test(line)) return "DEBUG";
|
|
|
|
|
return "OTHER";
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-13 00:46:58 -06:00
|
|
|
export function LogViewer() {
|
UX/UI/perf pass: admin tabs, theme toggle, log polish, mod search, JAR cache
- Admin page split into tabs (Server/Players/Chat/Mods/Backups/Logs) with
hash + localStorage persistence; inactive tabs no longer mount.
- Log viewer: level color-coding, search, level filter chips, auto-scroll
toggle, copy button, visible-line count.
- Installed mods list: search field + side filter (all/both/server/client)
with live count; public ModList gets skeleton + empty states and search.
- Theme toggle with no-flash inline init, localStorage + system preference.
- Layout: full OG / Twitter metadata, title template, keywords,
dual-theme themeColor, metadataBase.
- lib/mods.ts: per-jar mtime+size parse cache (cold 6s -> warm ~45ms on
/api/mods for the full mod list); cache eviction on mod removal.
- ChatBridge polling eased 3s -> 5s with 2s stale window.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 04:58:25 -06:00
|
|
|
const logRef = useRef<HTMLDivElement>(null);
|
2026-04-13 00:46:58 -06:00
|
|
|
const [autoRefresh, setAutoRefresh] = useState(false);
|
|
|
|
|
const [lines, setLines] = useState(100);
|
UX/UI/perf pass: admin tabs, theme toggle, log polish, mod search, JAR cache
- Admin page split into tabs (Server/Players/Chat/Mods/Backups/Logs) with
hash + localStorage persistence; inactive tabs no longer mount.
- Log viewer: level color-coding, search, level filter chips, auto-scroll
toggle, copy button, visible-line count.
- Installed mods list: search field + side filter (all/both/server/client)
with live count; public ModList gets skeleton + empty states and search.
- Theme toggle with no-flash inline init, localStorage + system preference.
- Layout: full OG / Twitter metadata, title template, keywords,
dual-theme themeColor, metadataBase.
- lib/mods.ts: per-jar mtime+size parse cache (cold 6s -> warm ~45ms on
/api/mods for the full mod list); cache eviction on mod removal.
- ChatBridge polling eased 3s -> 5s with 2s stale window.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 04:58:25 -06:00
|
|
|
const [search, setSearch] = useState("");
|
|
|
|
|
const [levelFilter, setLevelFilter] = useState<Set<Level>>(
|
|
|
|
|
new Set(LEVEL_ORDER)
|
|
|
|
|
);
|
|
|
|
|
const [autoScroll, setAutoScroll] = useState(true);
|
|
|
|
|
const [copied, setCopied] = useState(false);
|
2026-04-13 00:46:58 -06:00
|
|
|
const autoScrollRef = useRef(true);
|
|
|
|
|
|
|
|
|
|
const { data, refetch, isFetching, isError, error } = useQuery<{ logs: string }>({
|
|
|
|
|
queryKey: ["logs", lines],
|
|
|
|
|
queryFn: async () => {
|
|
|
|
|
const res = await fetch(`/api/logs?lines=${lines}`);
|
|
|
|
|
if (!res.ok) throw new Error(`Failed to load logs (${res.status})`);
|
|
|
|
|
return res.json();
|
|
|
|
|
},
|
|
|
|
|
refetchInterval: autoRefresh ? 3000 : false,
|
|
|
|
|
});
|
|
|
|
|
|
UX/UI/perf pass: admin tabs, theme toggle, log polish, mod search, JAR cache
- Admin page split into tabs (Server/Players/Chat/Mods/Backups/Logs) with
hash + localStorage persistence; inactive tabs no longer mount.
- Log viewer: level color-coding, search, level filter chips, auto-scroll
toggle, copy button, visible-line count.
- Installed mods list: search field + side filter (all/both/server/client)
with live count; public ModList gets skeleton + empty states and search.
- Theme toggle with no-flash inline init, localStorage + system preference.
- Layout: full OG / Twitter metadata, title template, keywords,
dual-theme themeColor, metadataBase.
- lib/mods.ts: per-jar mtime+size parse cache (cold 6s -> warm ~45ms on
/api/mods for the full mod list); cache eviction on mod removal.
- ChatBridge polling eased 3s -> 5s with 2s stale window.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 04:58:25 -06:00
|
|
|
const parsed = useMemo(() => {
|
|
|
|
|
if (!data?.logs) return [] as { line: string; level: Level }[];
|
|
|
|
|
return data.logs
|
|
|
|
|
.split("\n")
|
|
|
|
|
.filter((l) => l.length > 0)
|
|
|
|
|
.map((line) => ({ line, level: detectLevel(line) }));
|
|
|
|
|
}, [data?.logs]);
|
|
|
|
|
|
|
|
|
|
const filtered = useMemo(() => {
|
|
|
|
|
const q = search.trim().toLowerCase();
|
|
|
|
|
return parsed.filter(
|
|
|
|
|
(r) =>
|
|
|
|
|
levelFilter.has(r.level) &&
|
|
|
|
|
(!q || r.line.toLowerCase().includes(q))
|
|
|
|
|
);
|
|
|
|
|
}, [parsed, search, levelFilter]);
|
|
|
|
|
|
2026-04-13 00:46:58 -06:00
|
|
|
useEffect(() => {
|
UX/UI/perf pass: admin tabs, theme toggle, log polish, mod search, JAR cache
- Admin page split into tabs (Server/Players/Chat/Mods/Backups/Logs) with
hash + localStorage persistence; inactive tabs no longer mount.
- Log viewer: level color-coding, search, level filter chips, auto-scroll
toggle, copy button, visible-line count.
- Installed mods list: search field + side filter (all/both/server/client)
with live count; public ModList gets skeleton + empty states and search.
- Theme toggle with no-flash inline init, localStorage + system preference.
- Layout: full OG / Twitter metadata, title template, keywords,
dual-theme themeColor, metadataBase.
- lib/mods.ts: per-jar mtime+size parse cache (cold 6s -> warm ~45ms on
/api/mods for the full mod list); cache eviction on mod removal.
- ChatBridge polling eased 3s -> 5s with 2s stale window.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 04:58:25 -06:00
|
|
|
if (autoScroll && logRef.current && autoScrollRef.current) {
|
2026-04-13 00:46:58 -06:00
|
|
|
logRef.current.scrollTop = logRef.current.scrollHeight;
|
|
|
|
|
}
|
UX/UI/perf pass: admin tabs, theme toggle, log polish, mod search, JAR cache
- Admin page split into tabs (Server/Players/Chat/Mods/Backups/Logs) with
hash + localStorage persistence; inactive tabs no longer mount.
- Log viewer: level color-coding, search, level filter chips, auto-scroll
toggle, copy button, visible-line count.
- Installed mods list: search field + side filter (all/both/server/client)
with live count; public ModList gets skeleton + empty states and search.
- Theme toggle with no-flash inline init, localStorage + system preference.
- Layout: full OG / Twitter metadata, title template, keywords,
dual-theme themeColor, metadataBase.
- lib/mods.ts: per-jar mtime+size parse cache (cold 6s -> warm ~45ms on
/api/mods for the full mod list); cache eviction on mod removal.
- ChatBridge polling eased 3s -> 5s with 2s stale window.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 04:58:25 -06:00
|
|
|
}, [filtered, autoScroll]);
|
2026-04-13 00:46:58 -06:00
|
|
|
|
|
|
|
|
const handleScroll = () => {
|
|
|
|
|
if (!logRef.current) return;
|
|
|
|
|
const { scrollTop, scrollHeight, clientHeight } = logRef.current;
|
|
|
|
|
autoScrollRef.current = scrollHeight - scrollTop - clientHeight < 80;
|
|
|
|
|
};
|
|
|
|
|
|
UX/UI/perf pass: admin tabs, theme toggle, log polish, mod search, JAR cache
- Admin page split into tabs (Server/Players/Chat/Mods/Backups/Logs) with
hash + localStorage persistence; inactive tabs no longer mount.
- Log viewer: level color-coding, search, level filter chips, auto-scroll
toggle, copy button, visible-line count.
- Installed mods list: search field + side filter (all/both/server/client)
with live count; public ModList gets skeleton + empty states and search.
- Theme toggle with no-flash inline init, localStorage + system preference.
- Layout: full OG / Twitter metadata, title template, keywords,
dual-theme themeColor, metadataBase.
- lib/mods.ts: per-jar mtime+size parse cache (cold 6s -> warm ~45ms on
/api/mods for the full mod list); cache eviction on mod removal.
- ChatBridge polling eased 3s -> 5s with 2s stale window.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 04:58:25 -06:00
|
|
|
const toggleLevel = (lvl: Level) => {
|
|
|
|
|
setLevelFilter((prev) => {
|
|
|
|
|
const next = new Set(prev);
|
|
|
|
|
if (next.has(lvl)) next.delete(lvl);
|
|
|
|
|
else next.add(lvl);
|
|
|
|
|
return next;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const copyAll = async () => {
|
|
|
|
|
try {
|
|
|
|
|
await navigator.clipboard.writeText(filtered.map((r) => r.line).join("\n"));
|
|
|
|
|
setCopied(true);
|
|
|
|
|
setTimeout(() => setCopied(false), 1500);
|
|
|
|
|
} catch {}
|
|
|
|
|
};
|
2026-04-13 00:46:58 -06:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Card>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
|
|
|
|
<div>
|
|
|
|
|
<CardTitle>Console Logs</CardTitle>
|
|
|
|
|
<CardDescription>Server output from journalctl</CardDescription>
|
|
|
|
|
</div>
|
UX/UI/perf pass: admin tabs, theme toggle, log polish, mod search, JAR cache
- Admin page split into tabs (Server/Players/Chat/Mods/Backups/Logs) with
hash + localStorage persistence; inactive tabs no longer mount.
- Log viewer: level color-coding, search, level filter chips, auto-scroll
toggle, copy button, visible-line count.
- Installed mods list: search field + side filter (all/both/server/client)
with live count; public ModList gets skeleton + empty states and search.
- Theme toggle with no-flash inline init, localStorage + system preference.
- Layout: full OG / Twitter metadata, title template, keywords,
dual-theme themeColor, metadataBase.
- lib/mods.ts: per-jar mtime+size parse cache (cold 6s -> warm ~45ms on
/api/mods for the full mod list); cache eviction on mod removal.
- ChatBridge polling eased 3s -> 5s with 2s stale window.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 04:58:25 -06:00
|
|
|
<div className="flex items-center gap-2 flex-wrap">
|
2026-04-13 00:46:58 -06:00
|
|
|
<select
|
|
|
|
|
value={lines}
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
setLines(Number(e.target.value));
|
|
|
|
|
setTimeout(() => refetch(), 50);
|
|
|
|
|
}}
|
UX/UI/perf pass: admin tabs, theme toggle, log polish, mod search, JAR cache
- Admin page split into tabs (Server/Players/Chat/Mods/Backups/Logs) with
hash + localStorage persistence; inactive tabs no longer mount.
- Log viewer: level color-coding, search, level filter chips, auto-scroll
toggle, copy button, visible-line count.
- Installed mods list: search field + side filter (all/both/server/client)
with live count; public ModList gets skeleton + empty states and search.
- Theme toggle with no-flash inline init, localStorage + system preference.
- Layout: full OG / Twitter metadata, title template, keywords,
dual-theme themeColor, metadataBase.
- lib/mods.ts: per-jar mtime+size parse cache (cold 6s -> warm ~45ms on
/api/mods for the full mod list); cache eviction on mod removal.
- ChatBridge polling eased 3s -> 5s with 2s stale window.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 04:58:25 -06:00
|
|
|
className="h-9 rounded-md border border-input bg-muted px-2 text-sm text-foreground focus:outline-none"
|
2026-04-13 00:46:58 -06:00
|
|
|
>
|
|
|
|
|
<option value={50}>50 lines</option>
|
|
|
|
|
<option value={100}>100 lines</option>
|
|
|
|
|
<option value={200}>200 lines</option>
|
|
|
|
|
</select>
|
|
|
|
|
<Button
|
|
|
|
|
size="sm"
|
|
|
|
|
variant={autoRefresh ? "default" : "outline"}
|
|
|
|
|
onClick={() => {
|
|
|
|
|
setAutoRefresh(!autoRefresh);
|
|
|
|
|
if (!autoRefresh) autoScrollRef.current = true;
|
|
|
|
|
}}
|
|
|
|
|
className={`text-xs ${autoRefresh ? "animate-pulse" : ""}`}
|
|
|
|
|
>
|
|
|
|
|
{autoRefresh ? "Live" : "Auto"}
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
size="sm"
|
|
|
|
|
variant="secondary"
|
|
|
|
|
onClick={() => refetch()}
|
|
|
|
|
disabled={isFetching}
|
|
|
|
|
>
|
|
|
|
|
{isFetching ? "..." : "Refresh"}
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
UX/UI/perf pass: admin tabs, theme toggle, log polish, mod search, JAR cache
- Admin page split into tabs (Server/Players/Chat/Mods/Backups/Logs) with
hash + localStorage persistence; inactive tabs no longer mount.
- Log viewer: level color-coding, search, level filter chips, auto-scroll
toggle, copy button, visible-line count.
- Installed mods list: search field + side filter (all/both/server/client)
with live count; public ModList gets skeleton + empty states and search.
- Theme toggle with no-flash inline init, localStorage + system preference.
- Layout: full OG / Twitter metadata, title template, keywords,
dual-theme themeColor, metadataBase.
- lib/mods.ts: per-jar mtime+size parse cache (cold 6s -> warm ~45ms on
/api/mods for the full mod list); cache eviction on mod removal.
- ChatBridge polling eased 3s -> 5s with 2s stale window.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 04:58:25 -06:00
|
|
|
|
|
|
|
|
<div className="flex flex-wrap items-center gap-2 pt-3">
|
|
|
|
|
<Input
|
|
|
|
|
placeholder="Search logs..."
|
|
|
|
|
value={search}
|
|
|
|
|
onChange={(e) => setSearch(e.target.value)}
|
|
|
|
|
className="h-8 text-sm max-w-[220px]"
|
|
|
|
|
/>
|
|
|
|
|
<div className="flex gap-1">
|
|
|
|
|
{LEVEL_ORDER.map((lvl) => {
|
|
|
|
|
const active = levelFilter.has(lvl);
|
|
|
|
|
return (
|
|
|
|
|
<button
|
|
|
|
|
key={lvl}
|
|
|
|
|
onClick={() => toggleLevel(lvl)}
|
Pass 3 first slice: mod update action, error boundaries, a11y, palette
- New POST /api/mods/update SSE route: per-file Modrinth lookup → snapshot →
download latest → swap old jar → restart + verify (if server-side) →
rebuild modpack, with automatic rollback on any failure.
- ModManager: "Update" button next to each mod with an available update,
plus "Update all (N)" in the installed list header. Reuses the existing
install timeline UI (same event shape). SSE reader extracted as
consumeSSE helper.
- Error boundaries: app/error.tsx (scoped), app/admin/error.tsx (admin
subtree retry), app/not-found.tsx, app/global-error.tsx (hard-fail
fallback with inline styles, no app shell dependency).
- A11y sweep: aria-pressed + aria-label on LogViewer level chips and
ModManager side filter; aria-label on admin TabsList; skip-to-content
link in Navbar targeting <main id="main"> on public + admin pages;
role/aria-live on install/update timeline; global Esc in ModManager
clears open confirm prompts and exits search/review wizard steps.
- Command palette (cmdk): global Ctrl/⌘+K dialog mounted in Providers.
Navigate admin tabs, toggle theme, start/stop/restart server, create
backup, re-check mod updates, jump to any cached mod/player/snapshot/
backup. Auth-aware — public users see only Home / Log in / Theme.
- AdminTabs listens to hashchange so palette navigation updates the
active tab live.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 05:30:23 -06:00
|
|
|
aria-pressed={active}
|
|
|
|
|
aria-label={`${active ? "Hide" : "Show"} ${lvl} lines`}
|
UX/UI/perf pass: admin tabs, theme toggle, log polish, mod search, JAR cache
- Admin page split into tabs (Server/Players/Chat/Mods/Backups/Logs) with
hash + localStorage persistence; inactive tabs no longer mount.
- Log viewer: level color-coding, search, level filter chips, auto-scroll
toggle, copy button, visible-line count.
- Installed mods list: search field + side filter (all/both/server/client)
with live count; public ModList gets skeleton + empty states and search.
- Theme toggle with no-flash inline init, localStorage + system preference.
- Layout: full OG / Twitter metadata, title template, keywords,
dual-theme themeColor, metadataBase.
- lib/mods.ts: per-jar mtime+size parse cache (cold 6s -> warm ~45ms on
/api/mods for the full mod list); cache eviction on mod removal.
- ChatBridge polling eased 3s -> 5s with 2s stale window.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 04:58:25 -06:00
|
|
|
className={`text-[10px] font-semibold uppercase rounded px-2 py-1 border transition ${
|
|
|
|
|
active
|
|
|
|
|
? `${levelClass[lvl]} border-current/40 bg-muted`
|
|
|
|
|
: "text-muted-foreground/40 border-transparent hover:text-muted-foreground"
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
{lvl}
|
|
|
|
|
</button>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</div>
|
|
|
|
|
<label className="flex items-center gap-1.5 text-xs text-muted-foreground ml-auto">
|
|
|
|
|
<input
|
|
|
|
|
type="checkbox"
|
|
|
|
|
checked={autoScroll}
|
|
|
|
|
onChange={(e) => setAutoScroll(e.target.checked)}
|
|
|
|
|
className="h-3.5 w-3.5 accent-primary"
|
|
|
|
|
/>
|
|
|
|
|
Auto-scroll
|
|
|
|
|
</label>
|
|
|
|
|
<Button size="sm" variant="ghost" onClick={copyAll} className="text-xs h-8">
|
|
|
|
|
{copied ? "Copied" : "Copy"}
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
2026-04-13 00:46:58 -06:00
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent>
|
UX/UI/perf pass: admin tabs, theme toggle, log polish, mod search, JAR cache
- Admin page split into tabs (Server/Players/Chat/Mods/Backups/Logs) with
hash + localStorage persistence; inactive tabs no longer mount.
- Log viewer: level color-coding, search, level filter chips, auto-scroll
toggle, copy button, visible-line count.
- Installed mods list: search field + side filter (all/both/server/client)
with live count; public ModList gets skeleton + empty states and search.
- Theme toggle with no-flash inline init, localStorage + system preference.
- Layout: full OG / Twitter metadata, title template, keywords,
dual-theme themeColor, metadataBase.
- lib/mods.ts: per-jar mtime+size parse cache (cold 6s -> warm ~45ms on
/api/mods for the full mod list); cache eviction on mod removal.
- ChatBridge polling eased 3s -> 5s with 2s stale window.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 04:58:25 -06:00
|
|
|
<div
|
2026-04-13 00:46:58 -06:00
|
|
|
ref={logRef}
|
|
|
|
|
onScroll={handleScroll}
|
UX/UI/perf pass: admin tabs, theme toggle, log polish, mod search, JAR cache
- Admin page split into tabs (Server/Players/Chat/Mods/Backups/Logs) with
hash + localStorage persistence; inactive tabs no longer mount.
- Log viewer: level color-coding, search, level filter chips, auto-scroll
toggle, copy button, visible-line count.
- Installed mods list: search field + side filter (all/both/server/client)
with live count; public ModList gets skeleton + empty states and search.
- Theme toggle with no-flash inline init, localStorage + system preference.
- Layout: full OG / Twitter metadata, title template, keywords,
dual-theme themeColor, metadataBase.
- lib/mods.ts: per-jar mtime+size parse cache (cold 6s -> warm ~45ms on
/api/mods for the full mod list); cache eviction on mod removal.
- ChatBridge polling eased 3s -> 5s with 2s stale window.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 04:58:25 -06:00
|
|
|
className={`rounded-lg border bg-background p-3 sm:p-4 h-[300px] sm:h-[450px] overflow-y-auto font-mono text-xs leading-relaxed ${
|
|
|
|
|
isError ? "border-red-500/30" : "border-border"
|
2026-04-13 00:46:58 -06:00
|
|
|
}`}
|
|
|
|
|
>
|
UX/UI/perf pass: admin tabs, theme toggle, log polish, mod search, JAR cache
- Admin page split into tabs (Server/Players/Chat/Mods/Backups/Logs) with
hash + localStorage persistence; inactive tabs no longer mount.
- Log viewer: level color-coding, search, level filter chips, auto-scroll
toggle, copy button, visible-line count.
- Installed mods list: search field + side filter (all/both/server/client)
with live count; public ModList gets skeleton + empty states and search.
- Theme toggle with no-flash inline init, localStorage + system preference.
- Layout: full OG / Twitter metadata, title template, keywords,
dual-theme themeColor, metadataBase.
- lib/mods.ts: per-jar mtime+size parse cache (cold 6s -> warm ~45ms on
/api/mods for the full mod list); cache eviction on mod removal.
- ChatBridge polling eased 3s -> 5s with 2s stale window.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 04:58:25 -06:00
|
|
|
{isError ? (
|
|
|
|
|
<p className="text-red-300">
|
|
|
|
|
Failed to load logs:{" "}
|
|
|
|
|
{error instanceof Error ? error.message : "unknown error"}
|
|
|
|
|
</p>
|
|
|
|
|
) : filtered.length === 0 ? (
|
|
|
|
|
<p className="text-muted-foreground">
|
|
|
|
|
{isFetching ? "Loading logs..." : "No log lines match the current filter."}
|
|
|
|
|
</p>
|
|
|
|
|
) : (
|
|
|
|
|
filtered.map((r, i) => (
|
|
|
|
|
<div
|
|
|
|
|
key={i}
|
|
|
|
|
className={`whitespace-pre-wrap break-all ${levelClass[r.level]}`}
|
|
|
|
|
>
|
|
|
|
|
{r.line}
|
|
|
|
|
</div>
|
|
|
|
|
))
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-[10px] text-muted-foreground mt-2 tabular-nums">
|
|
|
|
|
{filtered.length} / {parsed.length} lines
|
|
|
|
|
</p>
|
2026-04-13 00:46:58 -06:00
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
);
|
|
|
|
|
}
|