feat: upgrade Dashboard with server status, repo summary, and quick actions
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user