feat: scaffold PulseNode dashboard (M1)
Next.js 16 + TypeScript + Tailwind v4 app with a YAML-driven config system: schema validation (zod), .env interpolation, and a widget registry rendering bookmark/search/group widgets. CSS custom properties (--pn-*) drive theming and are overridable from config.yml's theme.variables. Config load errors surface as a readable error page instead of crashing the app. Hot reload, docker/system/http collectors, and the WebSocket push layer land in later milestones per the approved plan.
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,49 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
--pn-bg: #0b0d12;
|
||||
--pn-surface: #12151c;
|
||||
--pn-surface-raised: #171b24;
|
||||
--pn-border: #242938;
|
||||
--pn-fg: #e6e8ee;
|
||||
--pn-fg-muted: #8a90a2;
|
||||
--pn-accent: #38bdf8;
|
||||
--pn-radius: 0.75rem;
|
||||
--pn-status-up: #34d399;
|
||||
--pn-status-down: #f87171;
|
||||
--pn-status-degraded: #fbbf24;
|
||||
}
|
||||
|
||||
:root[data-theme="light"] {
|
||||
--pn-bg: #f5f6f8;
|
||||
--pn-surface: #ffffff;
|
||||
--pn-surface-raised: #f0f1f4;
|
||||
--pn-border: #e2e4ea;
|
||||
--pn-fg: #14161c;
|
||||
--pn-fg-muted: #666b7a;
|
||||
--pn-accent: #0284c7;
|
||||
--pn-status-up: #059669;
|
||||
--pn-status-down: #dc2626;
|
||||
--pn-status-degraded: #d97706;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-bg: var(--pn-bg);
|
||||
--color-surface: var(--pn-surface);
|
||||
--color-surface-raised: var(--pn-surface-raised);
|
||||
--color-border: var(--pn-border);
|
||||
--color-fg: var(--pn-fg);
|
||||
--color-fg-muted: var(--pn-fg-muted);
|
||||
--color-accent: var(--pn-accent);
|
||||
--color-status-up: var(--pn-status-up);
|
||||
--color-status-down: var(--pn-status-down);
|
||||
--color-status-degraded: var(--pn-status-degraded);
|
||||
--radius-widget: var(--pn-radius);
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--pn-bg);
|
||||
color: var(--pn-fg);
|
||||
font-family:
|
||||
ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { Metadata } from "next";
|
||||
import "./globals.css";
|
||||
import { configStore } from "@/lib/config/loader";
|
||||
import { ThemeStyleInjector } from "@/components/layout/ThemeStyleInjector";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "PulseNode",
|
||||
description: "Self-hosted infrastructure dashboard",
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }: LayoutProps<"/">) {
|
||||
let mode: "dark" | "light" = "dark";
|
||||
let variables: Record<string, string> = {};
|
||||
|
||||
try {
|
||||
const config = configStore.get();
|
||||
mode = config.theme.mode === "light" ? "light" : "dark";
|
||||
variables = config.theme.variables;
|
||||
} catch {
|
||||
// page.tsx surfaces the actual config error; layout falls back to defaults
|
||||
}
|
||||
|
||||
return (
|
||||
<html lang="en" data-theme={mode} className="h-full">
|
||||
<head>
|
||||
<ThemeStyleInjector variables={variables} />
|
||||
</head>
|
||||
<body className="min-h-full antialiased">{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { configStore, ConfigError } from "@/lib/config/loader";
|
||||
import { Dashboard } from "@/components/layout/Dashboard";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default function Home() {
|
||||
let config;
|
||||
try {
|
||||
config = configStore.get();
|
||||
} catch (err) {
|
||||
const message = err instanceof ConfigError ? err.message : "Unknown configuration error";
|
||||
return (
|
||||
<main className="mx-auto flex max-w-2xl flex-col gap-4 px-6 py-16">
|
||||
<h1 className="text-lg font-semibold text-status-down">Configuration Error</h1>
|
||||
<pre className="rounded-[var(--radius-widget)] border border-status-down bg-surface p-4 text-sm whitespace-pre-wrap text-fg">
|
||||
{message}
|
||||
</pre>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return <Dashboard config={config} />;
|
||||
}
|
||||
Reference in New Issue
Block a user