feat: upgrade Dashboard with server status, repo summary, and quick actions

This commit is contained in:
plenarius
2026-04-20 22:11:37 -04:00
parent 8df7a78c08
commit 72027a3024
+233 -9
View File
@@ -1,13 +1,237 @@
export function Dashboard() { import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { api } from "../services/api";
function StatusBadge({ status }: { status: string }) {
const color =
status === "running"
? "#4ade80"
: status === "stopped"
? "#f87171"
: "#fbbf24";
return ( return (
<div className="flex h-screen items-center justify-center"> <span
<div className="text-center"> className="inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold"
<h1 className="font-serif text-4xl font-bold text-forge-accent"> style={{ backgroundColor: `${color}20`, color }}
Layonara Forge >
</h1> <span className="inline-block h-2 w-2 rounded-full" style={{ backgroundColor: color }} />
<p className="mt-2 text-forge-text-secondary"> {status}
NWN Development Environment </span>
</p> );
}
function ServerCard() {
const [status, setStatus] = useState<{ nwserver: string; mariadb: string }>({
nwserver: "unknown",
mariadb: "unknown",
});
const [loading, setLoading] = useState(false);
const poll = () => {
api.server
.status()
.then(setStatus)
.catch(() => setStatus({ nwserver: "error", mariadb: "error" }));
};
useEffect(() => {
poll();
const id = setInterval(poll, 10000);
return () => clearInterval(id);
}, []);
const toggle = async () => {
setLoading(true);
try {
if (status.nwserver === "running") {
await api.server.stop();
} else {
await api.server.start();
}
poll();
} catch {
// ignore
} finally {
setLoading(false);
}
};
return (
<div
className="rounded-lg p-6"
style={{
backgroundColor: "var(--forge-surface)",
border: "1px solid var(--forge-border)",
}}
>
<h3 className="text-sm font-semibold" style={{ color: "var(--forge-text-secondary)" }}>
Server Status
</h3>
<div className="mt-4 flex items-center gap-4">
<div className="flex-1 space-y-2">
<div className="flex items-center justify-between">
<span className="text-sm" style={{ color: "var(--forge-text)" }}>
NWServer
</span>
<StatusBadge status={status.nwserver} />
</div>
<div className="flex items-center justify-between">
<span className="text-sm" style={{ color: "var(--forge-text)" }}>
MariaDB
</span>
<StatusBadge status={status.mariadb} />
</div>
</div>
</div>
<div className="mt-4">
<button
onClick={toggle}
disabled={loading}
className="rounded px-4 py-2 text-sm font-semibold disabled:opacity-40"
style={{
backgroundColor:
status.nwserver === "running" ? "#7f1d1d" : "var(--forge-accent)",
color: status.nwserver === "running" ? "#fca5a5" : "#000",
}}
>
{loading
? "..."
: status.nwserver === "running"
? "Stop Server"
: "Start Server"}
</button>
</div>
</div>
);
}
function ReposSummary() {
const repos = ["nwn-module", "nwn-haks", "unified"];
const [repoStatus, setRepoStatus] = useState<Record<string, Record<string, unknown>>>({});
useEffect(() => {
for (const repo of repos) {
api.repos
.status(repo)
.then((s) => setRepoStatus((prev) => ({ ...prev, [repo]: s })))
.catch(() => {});
}
}, []);
return (
<div
className="rounded-lg p-6"
style={{
backgroundColor: "var(--forge-surface)",
border: "1px solid var(--forge-border)",
}}
>
<h3 className="text-sm font-semibold" style={{ color: "var(--forge-text-secondary)" }}>
Repositories
</h3>
<div className="mt-4 space-y-2">
{repos.map((repo) => {
const s = repoStatus[repo];
const branch = (s?.branch as string) || "\u2014";
const clean = s?.clean !== false;
return (
<div
key={repo}
className="flex items-center justify-between rounded p-3"
style={{ backgroundColor: "var(--forge-bg)" }}
>
<span className="font-mono text-sm" style={{ color: "var(--forge-text)" }}>
{repo}
</span>
<div className="flex items-center gap-2">
<span className="text-xs" style={{ color: "var(--forge-text-secondary)" }}>
{branch}
</span>
<span
className="inline-block h-2 w-2 rounded-full"
style={{ backgroundColor: clean ? "#4ade80" : "#fbbf24" }}
title={clean ? "Clean" : "Uncommitted changes"}
/>
</div>
</div>
);
})}
</div>
</div>
);
}
function QuickActions() {
const navigate = useNavigate();
const actions = [
{ label: "Build Module", onClick: () => navigate("/build") },
{ label: "Build Haks", onClick: () => navigate("/build") },
{ label: "Open Editor", onClick: () => navigate("/editor") },
{
label: "Open Terminal",
onClick: () => {
/* terminal is toggled from IDELayout via Ctrl+` */
navigate("/editor");
},
},
];
return (
<div
className="rounded-lg p-6"
style={{
backgroundColor: "var(--forge-surface)",
border: "1px solid var(--forge-border)",
}}
>
<h3 className="text-sm font-semibold" style={{ color: "var(--forge-text-secondary)" }}>
Quick Actions
</h3>
<div className="mt-4 grid grid-cols-2 gap-2">
{actions.map((a) => (
<button
key={a.label}
onClick={a.onClick}
className="rounded p-3 text-sm font-medium transition-colors hover:bg-white/5"
style={{
backgroundColor: "var(--forge-bg)",
color: "var(--forge-text)",
border: "1px solid var(--forge-border)",
}}
>
{a.label}
</button>
))}
</div>
</div>
);
}
export function Dashboard() {
return (
<div className="h-full overflow-y-auto p-6">
<div className="mb-8 text-center">
<h1
className="text-3xl font-bold"
style={{
fontFamily: "'Baskerville', 'Georgia', 'Palatino', serif",
color: "var(--forge-accent)",
}}
>
Layonara Forge
</h1>
<p className="mt-1 text-sm" style={{ color: "var(--forge-text-secondary)" }}>
NWN Development Environment
</p>
</div>
<div className="mx-auto max-w-3xl space-y-4">
<ServerCard />
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<ReposSummary />
<QuickActions />
</div>
</div> </div>
</div> </div>
); );