123 lines
4.3 KiB
TypeScript
123 lines
4.3 KiB
TypeScript
import { Button } from "@minmon/ui/components/button";
|
|
import { cn } from "@minmon/ui/lib/utils";
|
|
import { Link, Outlet, useRouterState } from "@tanstack/react-router";
|
|
import { Activity, BookText, Globe, LayoutGrid, Server, Wrench } from "lucide-react";
|
|
|
|
const dashboardLinks = [
|
|
{
|
|
to: "/dashboard",
|
|
label: "Overview",
|
|
description: "Health, alerts, and recent checks",
|
|
icon: LayoutGrid,
|
|
},
|
|
{
|
|
to: "/dashboard/servers",
|
|
label: "Servers",
|
|
description: "Inventory and host details",
|
|
icon: Server,
|
|
},
|
|
{
|
|
to: "/dashboard/services",
|
|
label: "Services",
|
|
description: "Processes and uptime targets",
|
|
icon: Wrench,
|
|
},
|
|
{
|
|
to: "/dashboard/domains",
|
|
label: "Domains",
|
|
description: "DNS, SSL, and expiry status",
|
|
icon: Globe,
|
|
},
|
|
{
|
|
to: "/dashboard/notes",
|
|
label: "Notes",
|
|
description: "Runbooks and operational handover",
|
|
icon: BookText,
|
|
},
|
|
] as const;
|
|
|
|
export function DashboardShell({
|
|
userName,
|
|
userEmail,
|
|
}: {
|
|
userName?: string | null;
|
|
userEmail?: string | null;
|
|
}) {
|
|
const pathname = useRouterState({
|
|
select: (state) => state.location.pathname,
|
|
});
|
|
|
|
return (
|
|
<div className="min-h-full bg-muted/20">
|
|
<div className="mx-auto flex w-full max-w-7xl flex-col gap-6 px-4 py-6 lg:px-6">
|
|
<section className="border bg-card">
|
|
<div className="grid gap-4 px-5 py-5 lg:grid-cols-[1.4fr_0.6fr] lg:px-6">
|
|
<div className="space-y-2">
|
|
<p className="text-[11px] uppercase tracking-[0.24em] text-muted-foreground">
|
|
Server Management Dashboard
|
|
</p>
|
|
<h1 className="text-2xl font-semibold tracking-tight">Operations overview for demos and day-to-day admin tasks</h1>
|
|
<p className="max-w-2xl text-sm text-muted-foreground">
|
|
Use this area to review monitored assets, inspect detail states, and stage CRUD forms that can connect to tRPC routers as they are added.
|
|
</p>
|
|
</div>
|
|
<div className="grid gap-3 border-l-0 border-t pt-4 lg:border-l lg:border-t-0 lg:pl-6 lg:pt-0">
|
|
<div>
|
|
<p className="text-[11px] uppercase tracking-[0.16em] text-muted-foreground">Signed in as</p>
|
|
<p className="mt-1 text-sm font-medium">{userName ?? "Administrator"}</p>
|
|
<p className="text-xs text-muted-foreground">{userEmail ?? "admin@minmon.local"}</p>
|
|
</div>
|
|
<div className="flex flex-wrap gap-2">
|
|
<Link to="/dashboard/servers/new">
|
|
<Button size="sm">Add server</Button>
|
|
</Link>
|
|
<Link to="/dashboard/notes/new">
|
|
<Button size="sm" variant="outline">
|
|
New note
|
|
</Button>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<div className="grid gap-6 lg:grid-cols-[240px_minmax(0,1fr)]">
|
|
<aside className="h-fit border bg-card">
|
|
<div className="border-b px-4 py-3">
|
|
<div className="flex items-center gap-2 text-sm font-medium">
|
|
<Activity className="size-4" />
|
|
Navigation
|
|
</div>
|
|
</div>
|
|
<nav className="grid gap-1 p-2">
|
|
{dashboardLinks.map(({ to, label, description, icon: Icon }) => {
|
|
const active = pathname === to || pathname.startsWith(`${to}/`);
|
|
|
|
return (
|
|
<Link
|
|
key={to}
|
|
to={to}
|
|
className={cn(
|
|
"grid gap-0.5 border px-3 py-2 text-left transition-colors",
|
|
active ? "border-foreground/20 bg-muted" : "border-transparent hover:border-border hover:bg-muted/40",
|
|
)}
|
|
>
|
|
<span className="flex items-center gap-2 text-sm font-medium">
|
|
<Icon className="size-4" />
|
|
{label}
|
|
</span>
|
|
<span className="text-xs text-muted-foreground">{description}</span>
|
|
</Link>
|
|
);
|
|
})}
|
|
</nav>
|
|
</aside>
|
|
|
|
<main className="min-w-0">
|
|
<Outlet />
|
|
</main>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|