Sanitized mirror from private repository - 2026-04-18 10:57:41 UTC
This commit is contained in:
17
dashboard/ui/lib/api.ts
Normal file
17
dashboard/ui/lib/api.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
// In the browser, API calls go to the same origin (Next.js rewrites to backend).
|
||||
// On the server, they go directly to the backend.
|
||||
const API = typeof window === "undefined"
|
||||
? (process.env.BACKEND_URL || "http://localhost:18888")
|
||||
: "";
|
||||
|
||||
export async function fetchAPI<T>(path: string): Promise<T> {
|
||||
const res = await fetch(`${API}${path}`);
|
||||
if (!res.ok) throw new Error(`API error: ${res.status}`);
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function postAPI<T>(path: string): Promise<T> {
|
||||
const res = await fetch(`${API}${path}`, { method: "POST" });
|
||||
if (!res.ok) throw new Error(`API error: ${res.status}`);
|
||||
return res.json();
|
||||
}
|
||||
982
dashboard/ui/lib/themes.ts
Normal file
982
dashboard/ui/lib/themes.ts
Normal file
@@ -0,0 +1,982 @@
|
||||
export interface Theme {
|
||||
name: string;
|
||||
label: string;
|
||||
isDark: boolean;
|
||||
bgGradient: string;
|
||||
bodyBg: string;
|
||||
// Preview swatch colors for the switcher
|
||||
swatch: [string, string];
|
||||
vars: Record<string, string>;
|
||||
}
|
||||
|
||||
export const themes: Theme[] = [
|
||||
// 1. Midnight (default)
|
||||
{
|
||||
name: "midnight",
|
||||
label: "Midnight",
|
||||
isDark: true,
|
||||
swatch: ["#3b82f6", "#8b5cf6"],
|
||||
bodyBg: "linear-gradient(135deg, #060611 0%, #0d1117 40%, #0a0e1a 100%)",
|
||||
bgGradient:
|
||||
"radial-gradient(ellipse 140% 70% at 5% 5%, rgba(59, 130, 246, 0.38), transparent 50%), radial-gradient(ellipse 100% 90% at 95% 15%, rgba(139, 92, 246, 0.30), transparent 50%), radial-gradient(ellipse 120% 70% at 50% 105%, rgba(16, 185, 129, 0.22), transparent 50%), radial-gradient(ellipse 80% 50% at 75% 55%, rgba(236, 72, 153, 0.15), transparent 50%)",
|
||||
vars: {
|
||||
"--background": "230 25% 4%",
|
||||
"--foreground": "210 40% 98%",
|
||||
"--card": "220 30% 8% / 0.4",
|
||||
"--card-foreground": "210 40% 98%",
|
||||
"--popover": "220 30% 8% / 0.8",
|
||||
"--popover-foreground": "210 40% 93%",
|
||||
"--primary": "217 91% 60%",
|
||||
"--primary-foreground": "210 40% 93%",
|
||||
"--secondary": "217 33% 12% / 0.5",
|
||||
"--secondary-foreground": "210 40% 93%",
|
||||
"--muted": "217 33% 12% / 0.5",
|
||||
"--muted-foreground": "215 20% 68%",
|
||||
"--accent": "217 33% 12% / 0.5",
|
||||
"--accent-foreground": "210 40% 93%",
|
||||
"--destructive": "0 84% 60%",
|
||||
"--border": "0 0% 100% / 0.08",
|
||||
"--input": "0 0% 100% / 0.06",
|
||||
"--ring": "217 91% 60%",
|
||||
"--chart-1": "217 91% 60%",
|
||||
"--chart-2": "160 60% 45%",
|
||||
"--chart-3": "30 80% 55%",
|
||||
"--chart-4": "280 65% 60%",
|
||||
"--chart-5": "340 75% 55%",
|
||||
"--sidebar": "220 30% 6%",
|
||||
"--sidebar-foreground": "210 40% 93%",
|
||||
"--sidebar-primary": "217 91% 60%",
|
||||
"--sidebar-primary-foreground": "210 40% 93%",
|
||||
"--sidebar-accent": "217 33% 12%",
|
||||
"--sidebar-accent-foreground": "210 40% 93%",
|
||||
"--sidebar-border": "0 0% 100% / 0.06",
|
||||
"--sidebar-ring": "217 91% 60%",
|
||||
"--card-bg": "rgba(15, 20, 40, 0.35)",
|
||||
"--card-border": "rgba(255, 255, 255, 0.12)",
|
||||
"--card-hover-bg": "rgba(15, 20, 40, 0.45)",
|
||||
"--card-hover-border": "rgba(255, 255, 255, 0.2)",
|
||||
"--glass-bg": "rgba(15, 20, 40, 0.30)",
|
||||
"--glass-border": "rgba(255, 255, 255, 0.08)",
|
||||
"--glass-hover": "rgba(255, 255, 255, 0.03)",
|
||||
"--glass-input-bg": "rgba(255, 255, 255, 0.06)",
|
||||
"--glass-input-border": "rgba(255, 255, 255, 0.1)",
|
||||
"--glass-input-focus": "rgba(59, 130, 246, 0.3)",
|
||||
"--glass-input-focus-bg": "rgba(255, 255, 255, 0.08)",
|
||||
"--glass-table-header": "rgba(255, 255, 255, 0.08)",
|
||||
"--glass-bar-track": "rgba(255, 255, 255, 0.10)",
|
||||
"--nav-bg": "rgba(6, 6, 17, 0.65)",
|
||||
"--nav-border": "rgba(255, 255, 255, 0.08)",
|
||||
"--nav-active": "rgba(255, 255, 255, 0.06)",
|
||||
"--nav-hover": "rgba(255, 255, 255, 0.04)",
|
||||
"--accent-color": "#3b82f6",
|
||||
"--accent-glow": "rgba(59, 130, 246, 0.3)",
|
||||
"--card-lift-shadow": "0 8px 40px rgba(0, 0, 0, 0.3), 0 0 40px rgba(59, 130, 246, 0.04)",
|
||||
"--stat-glow": "0 0 20px rgba(59, 130, 246, 0.15)",
|
||||
"--nav-active-glow": "0 2px 10px rgba(59, 130, 246, 0.3)",
|
||||
},
|
||||
},
|
||||
|
||||
// 2. Light
|
||||
{
|
||||
name: "light",
|
||||
label: "Light",
|
||||
isDark: false,
|
||||
swatch: ["#2563eb", "#e2e8f0"],
|
||||
bodyBg: "linear-gradient(135deg, #fafafa 0%, #f1f5f9 40%, #f8fafc 100%)",
|
||||
bgGradient:
|
||||
"radial-gradient(ellipse 80% 50% at 50% -20%, rgba(37, 99, 235, 0.06), transparent), radial-gradient(ellipse 60% 40% at 80% 50%, rgba(99, 102, 241, 0.04), transparent)",
|
||||
vars: {
|
||||
"--background": "210 20% 98%",
|
||||
"--foreground": "215 25% 15%",
|
||||
"--card": "0 0% 100%",
|
||||
"--card-foreground": "215 25% 15%",
|
||||
"--popover": "0 0% 100%",
|
||||
"--popover-foreground": "215 25% 15%",
|
||||
"--primary": "217 91% 53%",
|
||||
"--primary-foreground": "0 0% 100%",
|
||||
"--secondary": "214 32% 91%",
|
||||
"--secondary-foreground": "215 25% 15%",
|
||||
"--muted": "214 32% 91%",
|
||||
"--muted-foreground": "215 16% 47%",
|
||||
"--accent": "214 32% 91%",
|
||||
"--accent-foreground": "215 25% 15%",
|
||||
"--destructive": "0 84% 60%",
|
||||
"--border": "214 32% 88%",
|
||||
"--input": "214 32% 88%",
|
||||
"--ring": "217 91% 53%",
|
||||
"--card-bg": "rgba(255, 255, 255, 0.9)",
|
||||
"--card-border": "rgba(0, 0, 0, 0.12)",
|
||||
"--card-hover-bg": "rgba(255, 255, 255, 1)",
|
||||
"--card-hover-border": "rgba(0, 0, 0, 0.12)",
|
||||
"--glass-bg": "rgba(255, 255, 255, 0.7)",
|
||||
"--glass-border": "rgba(0, 0, 0, 0.10)",
|
||||
"--glass-hover": "rgba(0, 0, 0, 0.02)",
|
||||
"--glass-input-bg": "rgba(255, 255, 255, 0.8)",
|
||||
"--glass-input-border": "rgba(0, 0, 0, 0.1)",
|
||||
"--glass-input-focus": "rgba(37, 99, 235, 0.3)",
|
||||
"--glass-input-focus-bg": "rgba(255, 255, 255, 0.95)",
|
||||
"--glass-table-header": "rgba(0, 0, 0, 0.03)",
|
||||
"--glass-bar-track": "rgba(0, 0, 0, 0.10)",
|
||||
"--nav-bg": "rgba(255, 255, 255, 0.8)",
|
||||
"--nav-border": "rgba(0, 0, 0, 0.06)",
|
||||
"--nav-active": "rgba(0, 0, 0, 0.05)",
|
||||
"--nav-hover": "rgba(0, 0, 0, 0.03)",
|
||||
"--accent-color": "#2563eb",
|
||||
"--accent-glow": "rgba(37, 99, 235, 0.2)",
|
||||
"--card-lift-shadow": "0 8px 40px rgba(0, 0, 0, 0.08), 0 0 40px rgba(37, 99, 235, 0.02)",
|
||||
"--stat-glow": "0 0 20px rgba(37, 99, 235, 0.08)",
|
||||
"--nav-active-glow": "0 2px 10px rgba(37, 99, 235, 0.15)",
|
||||
},
|
||||
},
|
||||
|
||||
// 3. Cyberpunk
|
||||
{
|
||||
name: "cyberpunk",
|
||||
label: "Cyberpunk",
|
||||
isDark: true,
|
||||
swatch: ["#ec4899", "#06b6d4"],
|
||||
bodyBg: "linear-gradient(135deg, #0a0a0f 0%, #0d0515 40%, #05080f 100%)",
|
||||
bgGradient:
|
||||
"radial-gradient(ellipse 140% 70% at 10% 10%, rgba(236, 72, 153, 0.38), transparent 50%), radial-gradient(ellipse 100% 90% at 90% 20%, rgba(6, 182, 212, 0.30), transparent 50%), radial-gradient(ellipse 120% 70% at 50% 95%, rgba(139, 92, 246, 0.22), transparent 50%), radial-gradient(ellipse 80% 50% at 30% 60%, rgba(6, 182, 212, 0.15), transparent 50%)",
|
||||
vars: {
|
||||
"--background": "260 30% 4%",
|
||||
"--foreground": "185 10% 98%",
|
||||
"--card": "270 30% 8% / 0.4",
|
||||
"--card-foreground": "185 10% 98%",
|
||||
"--popover": "270 30% 8% / 0.8",
|
||||
"--popover-foreground": "185 20% 92%",
|
||||
"--primary": "330 80% 60%",
|
||||
"--primary-foreground": "185 20% 95%",
|
||||
"--secondary": "270 30% 14% / 0.5",
|
||||
"--secondary-foreground": "185 20% 92%",
|
||||
"--muted": "270 30% 14% / 0.5",
|
||||
"--muted-foreground": "200 15% 68%",
|
||||
"--accent": "185 80% 45%",
|
||||
"--accent-foreground": "185 20% 95%",
|
||||
"--destructive": "330 80% 55%",
|
||||
"--border": "0 0% 100% / 0.06",
|
||||
"--input": "0 0% 100% / 0.06",
|
||||
"--ring": "330 80% 60%",
|
||||
"--card-bg": "rgba(25, 10, 35, 0.35)",
|
||||
"--card-border": "rgba(236, 72, 153, 0.12)",
|
||||
"--card-hover-bg": "rgba(25, 10, 35, 0.45)",
|
||||
"--card-hover-border": "rgba(236, 72, 153, 0.2)",
|
||||
"--glass-bg": "rgba(25, 10, 35, 0.30)",
|
||||
"--glass-border": "rgba(236, 72, 153, 0.08)",
|
||||
"--glass-hover": "rgba(236, 72, 153, 0.03)",
|
||||
"--glass-input-bg": "rgba(255, 255, 255, 0.06)",
|
||||
"--glass-input-border": "rgba(236, 72, 153, 0.1)",
|
||||
"--glass-input-focus": "rgba(6, 182, 212, 0.3)",
|
||||
"--glass-input-focus-bg": "rgba(255, 255, 255, 0.08)",
|
||||
"--glass-table-header": "rgba(236, 72, 153, 0.08)",
|
||||
"--glass-bar-track": "rgba(255, 255, 255, 0.10)",
|
||||
"--nav-bg": "rgba(10, 10, 15, 0.65)",
|
||||
"--nav-border": "rgba(236, 72, 153, 0.08)",
|
||||
"--nav-active": "rgba(236, 72, 153, 0.08)",
|
||||
"--nav-hover": "rgba(236, 72, 153, 0.04)",
|
||||
"--accent-color": "#ec4899",
|
||||
"--accent-glow": "rgba(236, 72, 153, 0.3)",
|
||||
"--card-lift-shadow": "0 8px 40px rgba(0, 0, 0, 0.3), 0 0 40px rgba(236, 72, 153, 0.06)",
|
||||
"--stat-glow": "0 0 20px rgba(6, 182, 212, 0.15)",
|
||||
"--nav-active-glow": "0 2px 10px rgba(236, 72, 153, 0.3)",
|
||||
},
|
||||
},
|
||||
|
||||
// 4. Steampunk
|
||||
{
|
||||
name: "steampunk",
|
||||
label: "Steampunk",
|
||||
isDark: true,
|
||||
swatch: ["#d4a76a", "#b87333"],
|
||||
bodyBg: "linear-gradient(135deg, #0f0a07 0%, #12100b 40%, #0e0905 100%)",
|
||||
bgGradient:
|
||||
"radial-gradient(ellipse 140% 70% at 5% 5%, rgba(212, 167, 106, 0.30), transparent 50%), radial-gradient(ellipse 100% 90% at 90% 20%, rgba(184, 115, 51, 0.27), transparent 50%), radial-gradient(ellipse 120% 70% at 50% 95%, rgba(245, 158, 11, 0.22), transparent 50%), radial-gradient(ellipse 80% 50% at 30% 60%, rgba(184, 115, 51, 0.15), transparent 50%)",
|
||||
vars: {
|
||||
"--background": "30 30% 4%",
|
||||
"--foreground": "40 20% 97%",
|
||||
"--card": "30 25% 10% / 0.4",
|
||||
"--card-foreground": "40 20% 97%",
|
||||
"--popover": "30 25% 10% / 0.8",
|
||||
"--popover-foreground": "40 30% 88%",
|
||||
"--primary": "35 55% 62%",
|
||||
"--primary-foreground": "30 40% 10%",
|
||||
"--secondary": "30 20% 14% / 0.5",
|
||||
"--secondary-foreground": "40 30% 88%",
|
||||
"--muted": "30 20% 14% / 0.5",
|
||||
"--muted-foreground": "35 15% 68%",
|
||||
"--accent": "25 60% 46%",
|
||||
"--accent-foreground": "40 30% 92%",
|
||||
"--destructive": "15 70% 50%",
|
||||
"--border": "35 30% 50% / 0.08",
|
||||
"--input": "35 30% 50% / 0.06",
|
||||
"--ring": "35 55% 62%",
|
||||
"--card-bg": "rgba(30, 20, 12, 0.35)",
|
||||
"--card-border": "rgba(212, 167, 106, 0.12)",
|
||||
"--card-hover-bg": "rgba(30, 20, 12, 0.45)",
|
||||
"--card-hover-border": "rgba(212, 167, 106, 0.22)",
|
||||
"--glass-bg": "rgba(30, 20, 12, 0.30)",
|
||||
"--glass-border": "rgba(212, 167, 106, 0.08)",
|
||||
"--glass-hover": "rgba(212, 167, 106, 0.03)",
|
||||
"--glass-input-bg": "rgba(212, 167, 106, 0.06)",
|
||||
"--glass-input-border": "rgba(212, 167, 106, 0.1)",
|
||||
"--glass-input-focus": "rgba(184, 115, 51, 0.3)",
|
||||
"--glass-input-focus-bg": "rgba(212, 167, 106, 0.08)",
|
||||
"--glass-table-header": "rgba(212, 167, 106, 0.08)",
|
||||
"--glass-bar-track": "rgba(212, 167, 106, 0.10)",
|
||||
"--nav-bg": "rgba(15, 10, 7, 0.65)",
|
||||
"--nav-border": "rgba(212, 167, 106, 0.08)",
|
||||
"--nav-active": "rgba(212, 167, 106, 0.08)",
|
||||
"--nav-hover": "rgba(212, 167, 106, 0.04)",
|
||||
"--accent-color": "#d4a76a",
|
||||
"--accent-glow": "rgba(212, 167, 106, 0.3)",
|
||||
"--card-lift-shadow": "0 8px 40px rgba(0, 0, 0, 0.3), 0 0 40px rgba(212, 167, 106, 0.04)",
|
||||
"--stat-glow": "0 0 20px rgba(212, 167, 106, 0.15)",
|
||||
"--nav-active-glow": "0 2px 10px rgba(212, 167, 106, 0.3)",
|
||||
},
|
||||
},
|
||||
|
||||
// 5. Portland
|
||||
{
|
||||
name: "portland",
|
||||
label: "Portland",
|
||||
isDark: true,
|
||||
swatch: ["#15803d", "#0e7490"],
|
||||
bodyBg: "linear-gradient(135deg, #060d08 0%, #081210 40%, #050b08 100%)",
|
||||
bgGradient:
|
||||
"radial-gradient(ellipse 140% 70% at 5% 5%, rgba(21, 128, 61, 0.30), transparent 50%), radial-gradient(ellipse 100% 90% at 90% 20%, rgba(14, 116, 144, 0.27), transparent 50%), radial-gradient(ellipse 120% 70% at 50% 95%, rgba(34, 197, 94, 0.22), transparent 50%), radial-gradient(ellipse 80% 50% at 30% 60%, rgba(14, 116, 144, 0.15), transparent 50%)",
|
||||
vars: {
|
||||
"--background": "140 25% 4%",
|
||||
"--foreground": "140 10% 97%",
|
||||
"--card": "150 20% 9% / 0.4",
|
||||
"--card-foreground": "140 10% 97%",
|
||||
"--popover": "150 20% 9% / 0.8",
|
||||
"--popover-foreground": "140 15% 88%",
|
||||
"--primary": "142 64% 36%",
|
||||
"--primary-foreground": "140 20% 95%",
|
||||
"--secondary": "150 18% 14% / 0.5",
|
||||
"--secondary-foreground": "140 15% 88%",
|
||||
"--muted": "150 18% 14% / 0.5",
|
||||
"--muted-foreground": "150 10% 68%",
|
||||
"--accent": "189 80% 32%",
|
||||
"--accent-foreground": "140 20% 95%",
|
||||
"--destructive": "25 80% 50%",
|
||||
"--border": "140 20% 50% / 0.08",
|
||||
"--input": "140 20% 50% / 0.06",
|
||||
"--ring": "142 64% 36%",
|
||||
"--card-bg": "rgba(10, 25, 15, 0.35)",
|
||||
"--card-border": "rgba(21, 128, 61, 0.12)",
|
||||
"--card-hover-bg": "rgba(10, 25, 15, 0.45)",
|
||||
"--card-hover-border": "rgba(21, 128, 61, 0.22)",
|
||||
"--glass-bg": "rgba(10, 25, 15, 0.30)",
|
||||
"--glass-border": "rgba(21, 128, 61, 0.08)",
|
||||
"--glass-hover": "rgba(21, 128, 61, 0.03)",
|
||||
"--glass-input-bg": "rgba(21, 128, 61, 0.06)",
|
||||
"--glass-input-border": "rgba(21, 128, 61, 0.1)",
|
||||
"--glass-input-focus": "rgba(14, 116, 144, 0.3)",
|
||||
"--glass-input-focus-bg": "rgba(21, 128, 61, 0.08)",
|
||||
"--glass-table-header": "rgba(21, 128, 61, 0.08)",
|
||||
"--glass-bar-track": "rgba(21, 128, 61, 0.10)",
|
||||
"--nav-bg": "rgba(6, 13, 8, 0.65)",
|
||||
"--nav-border": "rgba(21, 128, 61, 0.08)",
|
||||
"--nav-active": "rgba(21, 128, 61, 0.08)",
|
||||
"--nav-hover": "rgba(21, 128, 61, 0.04)",
|
||||
"--accent-color": "#15803d",
|
||||
"--accent-glow": "rgba(21, 128, 61, 0.3)",
|
||||
"--card-lift-shadow": "0 8px 40px rgba(0, 0, 0, 0.3), 0 0 40px rgba(21, 128, 61, 0.04)",
|
||||
"--stat-glow": "0 0 20px rgba(21, 128, 61, 0.15)",
|
||||
"--nav-active-glow": "0 2px 10px rgba(21, 128, 61, 0.3)",
|
||||
},
|
||||
},
|
||||
|
||||
// 6. Racing
|
||||
{
|
||||
name: "racing",
|
||||
label: "Racing",
|
||||
isDark: true,
|
||||
swatch: ["#dc2626", "#a1a1aa"],
|
||||
bodyBg: "linear-gradient(135deg, #080808 0%, #0c0c0c 40%, #070707 100%)",
|
||||
bgGradient:
|
||||
"radial-gradient(ellipse 140% 70% at 5% 5%, rgba(220, 38, 38, 0.30), transparent 50%), radial-gradient(ellipse 100% 90% at 90% 20%, rgba(161, 161, 170, 0.22), transparent 50%), radial-gradient(ellipse 120% 70% at 50% 95%, rgba(220, 38, 38, 0.22), transparent 50%), radial-gradient(ellipse 80% 50% at 30% 60%, rgba(161, 161, 170, 0.15), transparent 50%)",
|
||||
vars: {
|
||||
"--background": "0 0% 4%",
|
||||
"--foreground": "0 0% 98%",
|
||||
"--card": "0 0% 9% / 0.4",
|
||||
"--card-foreground": "0 0% 98%",
|
||||
"--popover": "0 0% 9% / 0.8",
|
||||
"--popover-foreground": "0 0% 93%",
|
||||
"--primary": "0 72% 51%",
|
||||
"--primary-foreground": "0 0% 98%",
|
||||
"--secondary": "0 0% 14% / 0.5",
|
||||
"--secondary-foreground": "0 0% 93%",
|
||||
"--muted": "0 0% 14% / 0.5",
|
||||
"--muted-foreground": "0 0% 68%",
|
||||
"--accent": "0 0% 63%",
|
||||
"--accent-foreground": "0 0% 93%",
|
||||
"--destructive": "0 72% 51%",
|
||||
"--border": "0 0% 100% / 0.06",
|
||||
"--input": "0 0% 100% / 0.06",
|
||||
"--ring": "0 72% 51%",
|
||||
"--card-bg": "rgba(20, 18, 20, 0.35)",
|
||||
"--card-border": "rgba(220, 38, 38, 0.12)",
|
||||
"--card-hover-bg": "rgba(20, 18, 20, 0.45)",
|
||||
"--card-hover-border": "rgba(220, 38, 38, 0.22)",
|
||||
"--glass-bg": "rgba(20, 18, 20, 0.30)",
|
||||
"--glass-border": "rgba(220, 38, 38, 0.08)",
|
||||
"--glass-hover": "rgba(220, 38, 38, 0.03)",
|
||||
"--glass-input-bg": "rgba(255, 255, 255, 0.06)",
|
||||
"--glass-input-border": "rgba(255, 255, 255, 0.1)",
|
||||
"--glass-input-focus": "rgba(220, 38, 38, 0.3)",
|
||||
"--glass-input-focus-bg": "rgba(255, 255, 255, 0.08)",
|
||||
"--glass-table-header": "rgba(255, 255, 255, 0.08)",
|
||||
"--glass-bar-track": "rgba(255, 255, 255, 0.10)",
|
||||
"--nav-bg": "rgba(8, 8, 8, 0.65)",
|
||||
"--nav-border": "rgba(220, 38, 38, 0.08)",
|
||||
"--nav-active": "rgba(220, 38, 38, 0.08)",
|
||||
"--nav-hover": "rgba(220, 38, 38, 0.04)",
|
||||
"--accent-color": "#dc2626",
|
||||
"--accent-glow": "rgba(220, 38, 38, 0.3)",
|
||||
"--card-lift-shadow": "0 8px 40px rgba(0, 0, 0, 0.3), 0 0 40px rgba(220, 38, 38, 0.04)",
|
||||
"--stat-glow": "0 0 20px rgba(220, 38, 38, 0.15)",
|
||||
"--nav-active-glow": "0 2px 10px rgba(220, 38, 38, 0.3)",
|
||||
},
|
||||
},
|
||||
|
||||
// 7. Ocean
|
||||
{
|
||||
name: "ocean",
|
||||
label: "Ocean",
|
||||
isDark: true,
|
||||
swatch: ["#0284c7", "#2dd4bf"],
|
||||
bodyBg: "linear-gradient(135deg, #030712 0%, #0a1628 40%, #041020 100%)",
|
||||
bgGradient:
|
||||
"radial-gradient(ellipse 140% 70% at 5% 5%, rgba(2, 132, 199, 0.30), transparent 50%), radial-gradient(ellipse 100% 90% at 90% 20%, rgba(45, 212, 191, 0.27), transparent 50%), radial-gradient(ellipse 120% 70% at 50% 95%, rgba(2, 132, 199, 0.22), transparent 50%), radial-gradient(ellipse 80% 50% at 30% 60%, rgba(45, 212, 191, 0.15), transparent 50%)",
|
||||
vars: {
|
||||
"--background": "220 50% 4%",
|
||||
"--foreground": "200 10% 98%",
|
||||
"--card": "215 40% 9% / 0.4",
|
||||
"--card-foreground": "200 10% 98%",
|
||||
"--popover": "215 40% 9% / 0.8",
|
||||
"--popover-foreground": "200 20% 90%",
|
||||
"--primary": "200 80% 44%",
|
||||
"--primary-foreground": "200 20% 95%",
|
||||
"--secondary": "210 30% 14% / 0.5",
|
||||
"--secondary-foreground": "200 20% 90%",
|
||||
"--muted": "210 30% 14% / 0.5",
|
||||
"--muted-foreground": "200 15% 68%",
|
||||
"--accent": "170 70% 50%",
|
||||
"--accent-foreground": "200 20% 95%",
|
||||
"--destructive": "10 70% 55%",
|
||||
"--border": "200 30% 50% / 0.08",
|
||||
"--input": "200 30% 50% / 0.06",
|
||||
"--ring": "200 80% 44%",
|
||||
"--card-bg": "rgba(8, 15, 30, 0.35)",
|
||||
"--card-border": "rgba(2, 132, 199, 0.12)",
|
||||
"--card-hover-bg": "rgba(8, 15, 30, 0.45)",
|
||||
"--card-hover-border": "rgba(2, 132, 199, 0.22)",
|
||||
"--glass-bg": "rgba(8, 15, 30, 0.30)",
|
||||
"--glass-border": "rgba(2, 132, 199, 0.08)",
|
||||
"--glass-hover": "rgba(2, 132, 199, 0.03)",
|
||||
"--glass-input-bg": "rgba(2, 132, 199, 0.06)",
|
||||
"--glass-input-border": "rgba(2, 132, 199, 0.1)",
|
||||
"--glass-input-focus": "rgba(45, 212, 191, 0.3)",
|
||||
"--glass-input-focus-bg": "rgba(2, 132, 199, 0.08)",
|
||||
"--glass-table-header": "rgba(2, 132, 199, 0.08)",
|
||||
"--glass-bar-track": "rgba(2, 132, 199, 0.10)",
|
||||
"--nav-bg": "rgba(3, 7, 18, 0.65)",
|
||||
"--nav-border": "rgba(2, 132, 199, 0.08)",
|
||||
"--nav-active": "rgba(2, 132, 199, 0.08)",
|
||||
"--nav-hover": "rgba(2, 132, 199, 0.04)",
|
||||
"--accent-color": "#0284c7",
|
||||
"--accent-glow": "rgba(2, 132, 199, 0.3)",
|
||||
"--card-lift-shadow": "0 8px 40px rgba(0, 0, 0, 0.3), 0 0 40px rgba(2, 132, 199, 0.04)",
|
||||
"--stat-glow": "0 0 20px rgba(45, 212, 191, 0.15)",
|
||||
"--nav-active-glow": "0 2px 10px rgba(2, 132, 199, 0.3)",
|
||||
},
|
||||
},
|
||||
|
||||
// 8. Aurora
|
||||
{
|
||||
name: "aurora",
|
||||
label: "Aurora",
|
||||
isDark: true,
|
||||
swatch: ["#4ade80", "#a78bfa"],
|
||||
bodyBg: "linear-gradient(135deg, #050510 0%, #080818 40%, #050510 100%)",
|
||||
bgGradient:
|
||||
"radial-gradient(ellipse 140% 70% at 5% 5%, rgba(74, 222, 128, 0.30), transparent 50%), radial-gradient(ellipse 100% 90% at 90% 20%, rgba(167, 139, 250, 0.27), transparent 50%), radial-gradient(ellipse 120% 70% at 50% 95%, rgba(56, 189, 248, 0.22), transparent 50%), radial-gradient(ellipse 80% 50% at 30% 60%, rgba(74, 222, 128, 0.18), transparent 50%)",
|
||||
vars: {
|
||||
"--background": "240 33% 4%",
|
||||
"--foreground": "220 15% 88%",
|
||||
"--card": "240 25% 9% / 0.4",
|
||||
"--card-foreground": "220 15% 88%",
|
||||
"--popover": "240 25% 9% / 0.8",
|
||||
"--popover-foreground": "220 15% 88%",
|
||||
"--primary": "142 70% 58%",
|
||||
"--primary-foreground": "240 20% 8%",
|
||||
"--secondary": "240 20% 14% / 0.5",
|
||||
"--secondary-foreground": "220 15% 88%",
|
||||
"--muted": "240 20% 14% / 0.5",
|
||||
"--muted-foreground": "230 12% 62%",
|
||||
"--accent": "263 70% 72%",
|
||||
"--accent-foreground": "220 15% 95%",
|
||||
"--destructive": "45 90% 55%",
|
||||
"--border": "260 20% 60% / 0.08",
|
||||
"--input": "260 20% 60% / 0.06",
|
||||
"--ring": "142 70% 58%",
|
||||
"--card-bg": "rgba(12, 10, 25, 0.35)",
|
||||
"--card-border": "rgba(167, 139, 250, 0.12)",
|
||||
"--card-hover-bg": "rgba(12, 10, 25, 0.45)",
|
||||
"--card-hover-border": "rgba(167, 139, 250, 0.22)",
|
||||
"--glass-bg": "rgba(12, 10, 25, 0.30)",
|
||||
"--glass-border": "rgba(167, 139, 250, 0.08)",
|
||||
"--glass-hover": "rgba(74, 222, 128, 0.03)",
|
||||
"--glass-input-bg": "rgba(167, 139, 250, 0.06)",
|
||||
"--glass-input-border": "rgba(167, 139, 250, 0.1)",
|
||||
"--glass-input-focus": "rgba(74, 222, 128, 0.3)",
|
||||
"--glass-input-focus-bg": "rgba(167, 139, 250, 0.08)",
|
||||
"--glass-table-header": "rgba(167, 139, 250, 0.08)",
|
||||
"--glass-bar-track": "rgba(167, 139, 250, 0.10)",
|
||||
"--nav-bg": "rgba(5, 5, 16, 0.65)",
|
||||
"--nav-border": "rgba(167, 139, 250, 0.08)",
|
||||
"--nav-active": "rgba(74, 222, 128, 0.06)",
|
||||
"--nav-hover": "rgba(167, 139, 250, 0.04)",
|
||||
"--accent-color": "#4ade80",
|
||||
"--accent-glow": "rgba(74, 222, 128, 0.3)",
|
||||
"--card-lift-shadow": "0 8px 40px rgba(0, 0, 0, 0.3), 0 0 40px rgba(167, 139, 250, 0.04)",
|
||||
"--stat-glow": "0 0 20px rgba(74, 222, 128, 0.15)",
|
||||
"--nav-active-glow": "0 2px 10px rgba(74, 222, 128, 0.3)",
|
||||
},
|
||||
},
|
||||
|
||||
// 9. Sakura
|
||||
{
|
||||
name: "sakura",
|
||||
label: "Sakura",
|
||||
isDark: true,
|
||||
swatch: ["#f472b6", "#fda4af"],
|
||||
bodyBg: "linear-gradient(135deg, #0a0508 0%, #100810 40%, #0a0508 100%)",
|
||||
bgGradient:
|
||||
"radial-gradient(ellipse 140% 70% at 5% 5%, rgba(244, 114, 182, 0.35), transparent 50%), radial-gradient(ellipse 100% 90% at 95% 15%, rgba(253, 164, 175, 0.28), transparent 50%), radial-gradient(ellipse 120% 70% at 50% 105%, rgba(251, 113, 133, 0.22), transparent 50%), radial-gradient(ellipse 80% 50% at 75% 55%, rgba(244, 114, 182, 0.15), transparent 50%)",
|
||||
vars: {
|
||||
"--background": "330 30% 4%",
|
||||
"--foreground": "340 15% 95%",
|
||||
"--card": "330 25% 9% / 0.4",
|
||||
"--card-foreground": "340 15% 95%",
|
||||
"--popover": "330 25% 9% / 0.8",
|
||||
"--popover-foreground": "340 15% 90%",
|
||||
"--primary": "330 70% 65%",
|
||||
"--primary-foreground": "330 20% 10%",
|
||||
"--secondary": "330 20% 14% / 0.5",
|
||||
"--secondary-foreground": "340 15% 90%",
|
||||
"--muted": "330 20% 14% / 0.5",
|
||||
"--muted-foreground": "330 12% 65%",
|
||||
"--accent": "350 65% 70%",
|
||||
"--accent-foreground": "340 15% 95%",
|
||||
"--destructive": "0 84% 60%",
|
||||
"--border": "330 20% 60% / 0.08",
|
||||
"--input": "330 20% 60% / 0.06",
|
||||
"--ring": "330 70% 65%",
|
||||
"--chart-1": "330 70% 65%",
|
||||
"--chart-2": "350 65% 70%",
|
||||
"--chart-3": "310 60% 55%",
|
||||
"--chart-4": "0 60% 65%",
|
||||
"--chart-5": "340 75% 55%",
|
||||
"--sidebar": "330 25% 6%",
|
||||
"--sidebar-foreground": "340 15% 90%",
|
||||
"--sidebar-primary": "330 70% 65%",
|
||||
"--sidebar-primary-foreground": "340 15% 90%",
|
||||
"--sidebar-accent": "330 20% 12%",
|
||||
"--sidebar-accent-foreground": "340 15% 90%",
|
||||
"--sidebar-border": "330 20% 60% / 0.06",
|
||||
"--sidebar-ring": "330 70% 65%",
|
||||
"--card-bg": "rgba(25, 10, 15, 0.35)",
|
||||
"--card-border": "rgba(244, 114, 182, 0.12)",
|
||||
"--card-hover-bg": "rgba(25, 10, 15, 0.45)",
|
||||
"--card-hover-border": "rgba(244, 114, 182, 0.22)",
|
||||
"--glass-bg": "rgba(25, 10, 15, 0.30)",
|
||||
"--glass-border": "rgba(244, 114, 182, 0.08)",
|
||||
"--glass-hover": "rgba(244, 114, 182, 0.03)",
|
||||
"--glass-input-bg": "rgba(244, 114, 182, 0.06)",
|
||||
"--glass-input-border": "rgba(244, 114, 182, 0.1)",
|
||||
"--glass-input-focus": "rgba(253, 164, 175, 0.3)",
|
||||
"--glass-input-focus-bg": "rgba(244, 114, 182, 0.08)",
|
||||
"--glass-table-header": "rgba(244, 114, 182, 0.08)",
|
||||
"--glass-bar-track": "rgba(244, 114, 182, 0.10)",
|
||||
"--nav-bg": "rgba(10, 5, 8, 0.65)",
|
||||
"--nav-border": "rgba(244, 114, 182, 0.08)",
|
||||
"--nav-active": "rgba(244, 114, 182, 0.08)",
|
||||
"--nav-hover": "rgba(244, 114, 182, 0.04)",
|
||||
"--accent-color": "#f472b6",
|
||||
"--accent-glow": "rgba(244, 114, 182, 0.3)",
|
||||
"--card-lift-shadow": "0 8px 40px rgba(0, 0, 0, 0.3), 0 0 40px rgba(244, 114, 182, 0.06)",
|
||||
"--stat-glow": "0 0 20px rgba(244, 114, 182, 0.15)",
|
||||
"--nav-active-glow": "0 2px 10px rgba(244, 114, 182, 0.3)",
|
||||
},
|
||||
},
|
||||
|
||||
// 10. Emerald
|
||||
{
|
||||
name: "emerald",
|
||||
label: "Emerald",
|
||||
isDark: true,
|
||||
swatch: ["#34d399", "#059669"],
|
||||
bodyBg: "linear-gradient(135deg, #040a06 0%, #061008 40%, #040a06 100%)",
|
||||
bgGradient:
|
||||
"radial-gradient(ellipse 140% 70% at 5% 5%, rgba(52, 211, 153, 0.35), transparent 50%), radial-gradient(ellipse 100% 90% at 95% 15%, rgba(5, 150, 105, 0.28), transparent 50%), radial-gradient(ellipse 120% 70% at 50% 105%, rgba(16, 185, 129, 0.22), transparent 50%), radial-gradient(ellipse 80% 50% at 75% 55%, rgba(52, 211, 153, 0.15), transparent 50%)",
|
||||
vars: {
|
||||
"--background": "155 40% 3%",
|
||||
"--foreground": "155 15% 95%",
|
||||
"--card": "155 30% 8% / 0.4",
|
||||
"--card-foreground": "155 15% 95%",
|
||||
"--popover": "155 30% 8% / 0.8",
|
||||
"--popover-foreground": "155 15% 90%",
|
||||
"--primary": "160 60% 52%",
|
||||
"--primary-foreground": "155 40% 8%",
|
||||
"--secondary": "155 25% 14% / 0.5",
|
||||
"--secondary-foreground": "155 15% 90%",
|
||||
"--muted": "155 25% 14% / 0.5",
|
||||
"--muted-foreground": "155 12% 62%",
|
||||
"--accent": "160 84% 39%",
|
||||
"--accent-foreground": "155 15% 95%",
|
||||
"--destructive": "0 84% 60%",
|
||||
"--border": "155 25% 50% / 0.08",
|
||||
"--input": "155 25% 50% / 0.06",
|
||||
"--ring": "160 60% 52%",
|
||||
"--chart-1": "160 60% 52%",
|
||||
"--chart-2": "170 70% 45%",
|
||||
"--chart-3": "145 55% 50%",
|
||||
"--chart-4": "180 60% 40%",
|
||||
"--chart-5": "140 70% 55%",
|
||||
"--sidebar": "155 30% 5%",
|
||||
"--sidebar-foreground": "155 15% 90%",
|
||||
"--sidebar-primary": "160 60% 52%",
|
||||
"--sidebar-primary-foreground": "155 15% 90%",
|
||||
"--sidebar-accent": "155 25% 12%",
|
||||
"--sidebar-accent-foreground": "155 15% 90%",
|
||||
"--sidebar-border": "155 25% 50% / 0.06",
|
||||
"--sidebar-ring": "160 60% 52%",
|
||||
"--card-bg": "rgba(8, 25, 15, 0.35)",
|
||||
"--card-border": "rgba(52, 211, 153, 0.12)",
|
||||
"--card-hover-bg": "rgba(8, 25, 15, 0.45)",
|
||||
"--card-hover-border": "rgba(52, 211, 153, 0.22)",
|
||||
"--glass-bg": "rgba(8, 25, 15, 0.30)",
|
||||
"--glass-border": "rgba(52, 211, 153, 0.08)",
|
||||
"--glass-hover": "rgba(52, 211, 153, 0.03)",
|
||||
"--glass-input-bg": "rgba(52, 211, 153, 0.06)",
|
||||
"--glass-input-border": "rgba(52, 211, 153, 0.1)",
|
||||
"--glass-input-focus": "rgba(5, 150, 105, 0.3)",
|
||||
"--glass-input-focus-bg": "rgba(52, 211, 153, 0.08)",
|
||||
"--glass-table-header": "rgba(52, 211, 153, 0.08)",
|
||||
"--glass-bar-track": "rgba(52, 211, 153, 0.10)",
|
||||
"--nav-bg": "rgba(4, 10, 6, 0.65)",
|
||||
"--nav-border": "rgba(52, 211, 153, 0.08)",
|
||||
"--nav-active": "rgba(52, 211, 153, 0.08)",
|
||||
"--nav-hover": "rgba(52, 211, 153, 0.04)",
|
||||
"--accent-color": "#34d399",
|
||||
"--accent-glow": "rgba(52, 211, 153, 0.3)",
|
||||
"--card-lift-shadow": "0 8px 40px rgba(0, 0, 0, 0.3), 0 0 40px rgba(52, 211, 153, 0.06)",
|
||||
"--stat-glow": "0 0 20px rgba(52, 211, 153, 0.15)",
|
||||
"--nav-active-glow": "0 2px 10px rgba(52, 211, 153, 0.3)",
|
||||
},
|
||||
},
|
||||
|
||||
// 11. Sunset
|
||||
{
|
||||
name: "sunset",
|
||||
label: "Sunset",
|
||||
isDark: true,
|
||||
swatch: ["#f97316", "#dc2626"],
|
||||
bodyBg: "linear-gradient(135deg, #0a0604 0%, #100a06 40%, #0a0604 100%)",
|
||||
bgGradient:
|
||||
"radial-gradient(ellipse 140% 70% at 5% 5%, rgba(249, 115, 22, 0.35), transparent 50%), radial-gradient(ellipse 100% 90% at 95% 15%, rgba(220, 38, 38, 0.28), transparent 50%), radial-gradient(ellipse 120% 70% at 50% 105%, rgba(245, 158, 11, 0.22), transparent 50%), radial-gradient(ellipse 80% 50% at 75% 55%, rgba(249, 115, 22, 0.15), transparent 50%)",
|
||||
vars: {
|
||||
"--background": "20 35% 3%",
|
||||
"--foreground": "25 15% 95%",
|
||||
"--card": "20 30% 9% / 0.4",
|
||||
"--card-foreground": "25 15% 95%",
|
||||
"--popover": "20 30% 9% / 0.8",
|
||||
"--popover-foreground": "25 15% 90%",
|
||||
"--primary": "25 95% 53%",
|
||||
"--primary-foreground": "25 30% 8%",
|
||||
"--secondary": "20 25% 14% / 0.5",
|
||||
"--secondary-foreground": "25 15% 90%",
|
||||
"--muted": "20 25% 14% / 0.5",
|
||||
"--muted-foreground": "20 12% 62%",
|
||||
"--accent": "0 72% 51%",
|
||||
"--accent-foreground": "25 15% 95%",
|
||||
"--destructive": "0 84% 60%",
|
||||
"--border": "20 25% 50% / 0.08",
|
||||
"--input": "20 25% 50% / 0.06",
|
||||
"--ring": "25 95% 53%",
|
||||
"--chart-1": "25 95% 53%",
|
||||
"--chart-2": "0 72% 51%",
|
||||
"--chart-3": "40 90% 55%",
|
||||
"--chart-4": "15 80% 50%",
|
||||
"--chart-5": "350 75% 55%",
|
||||
"--sidebar": "20 30% 5%",
|
||||
"--sidebar-foreground": "25 15% 90%",
|
||||
"--sidebar-primary": "25 95% 53%",
|
||||
"--sidebar-primary-foreground": "25 15% 90%",
|
||||
"--sidebar-accent": "20 25% 12%",
|
||||
"--sidebar-accent-foreground": "25 15% 90%",
|
||||
"--sidebar-border": "20 25% 50% / 0.06",
|
||||
"--sidebar-ring": "25 95% 53%",
|
||||
"--card-bg": "rgba(25, 15, 8, 0.35)",
|
||||
"--card-border": "rgba(249, 115, 22, 0.12)",
|
||||
"--card-hover-bg": "rgba(25, 15, 8, 0.45)",
|
||||
"--card-hover-border": "rgba(249, 115, 22, 0.22)",
|
||||
"--glass-bg": "rgba(25, 15, 8, 0.30)",
|
||||
"--glass-border": "rgba(249, 115, 22, 0.08)",
|
||||
"--glass-hover": "rgba(249, 115, 22, 0.03)",
|
||||
"--glass-input-bg": "rgba(249, 115, 22, 0.06)",
|
||||
"--glass-input-border": "rgba(249, 115, 22, 0.1)",
|
||||
"--glass-input-focus": "rgba(220, 38, 38, 0.3)",
|
||||
"--glass-input-focus-bg": "rgba(249, 115, 22, 0.08)",
|
||||
"--glass-table-header": "rgba(249, 115, 22, 0.08)",
|
||||
"--glass-bar-track": "rgba(249, 115, 22, 0.10)",
|
||||
"--nav-bg": "rgba(10, 6, 4, 0.65)",
|
||||
"--nav-border": "rgba(249, 115, 22, 0.08)",
|
||||
"--nav-active": "rgba(249, 115, 22, 0.08)",
|
||||
"--nav-hover": "rgba(249, 115, 22, 0.04)",
|
||||
"--accent-color": "#f97316",
|
||||
"--accent-glow": "rgba(249, 115, 22, 0.3)",
|
||||
"--card-lift-shadow": "0 8px 40px rgba(0, 0, 0, 0.3), 0 0 40px rgba(249, 115, 22, 0.06)",
|
||||
"--stat-glow": "0 0 20px rgba(249, 115, 22, 0.15)",
|
||||
"--nav-active-glow": "0 2px 10px rgba(249, 115, 22, 0.3)",
|
||||
},
|
||||
},
|
||||
|
||||
// 12. Arctic
|
||||
{
|
||||
name: "arctic",
|
||||
label: "Arctic",
|
||||
isDark: true,
|
||||
swatch: ["#38bdf8", "#e0f2fe"],
|
||||
bodyBg: "linear-gradient(135deg, #040810 0%, #061018 40%, #040810 100%)",
|
||||
bgGradient:
|
||||
"radial-gradient(ellipse 140% 70% at 5% 5%, rgba(56, 189, 248, 0.40), transparent 50%), radial-gradient(ellipse 100% 90% at 95% 15%, rgba(224, 242, 254, 0.25), transparent 50%), radial-gradient(ellipse 120% 70% at 50% 105%, rgba(56, 189, 248, 0.28), transparent 50%), radial-gradient(ellipse 80% 50% at 75% 55%, rgba(125, 211, 252, 0.18), transparent 50%)",
|
||||
vars: {
|
||||
"--background": "215 50% 4%",
|
||||
"--foreground": "210 20% 96%",
|
||||
"--card": "215 40% 9% / 0.4",
|
||||
"--card-foreground": "210 20% 96%",
|
||||
"--popover": "215 40% 9% / 0.8",
|
||||
"--popover-foreground": "210 20% 92%",
|
||||
"--primary": "199 89% 56%",
|
||||
"--primary-foreground": "215 50% 8%",
|
||||
"--secondary": "215 30% 14% / 0.5",
|
||||
"--secondary-foreground": "210 20% 92%",
|
||||
"--muted": "215 30% 14% / 0.5",
|
||||
"--muted-foreground": "210 15% 62%",
|
||||
"--accent": "204 94% 94%",
|
||||
"--accent-foreground": "215 50% 8%",
|
||||
"--destructive": "0 84% 60%",
|
||||
"--border": "210 25% 60% / 0.08",
|
||||
"--input": "210 25% 60% / 0.06",
|
||||
"--ring": "199 89% 56%",
|
||||
"--chart-1": "199 89% 56%",
|
||||
"--chart-2": "190 80% 50%",
|
||||
"--chart-3": "210 70% 60%",
|
||||
"--chart-4": "185 70% 45%",
|
||||
"--chart-5": "200 80% 65%",
|
||||
"--sidebar": "215 40% 5%",
|
||||
"--sidebar-foreground": "210 20% 92%",
|
||||
"--sidebar-primary": "199 89% 56%",
|
||||
"--sidebar-primary-foreground": "210 20% 92%",
|
||||
"--sidebar-accent": "215 30% 12%",
|
||||
"--sidebar-accent-foreground": "210 20% 92%",
|
||||
"--sidebar-border": "210 25% 60% / 0.06",
|
||||
"--sidebar-ring": "199 89% 56%",
|
||||
"--card-bg": "rgba(8, 15, 30, 0.35)",
|
||||
"--card-border": "rgba(56, 189, 248, 0.12)",
|
||||
"--card-hover-bg": "rgba(8, 15, 30, 0.45)",
|
||||
"--card-hover-border": "rgba(56, 189, 248, 0.22)",
|
||||
"--glass-bg": "rgba(8, 15, 30, 0.30)",
|
||||
"--glass-border": "rgba(56, 189, 248, 0.08)",
|
||||
"--glass-hover": "rgba(56, 189, 248, 0.03)",
|
||||
"--glass-input-bg": "rgba(56, 189, 248, 0.06)",
|
||||
"--glass-input-border": "rgba(56, 189, 248, 0.1)",
|
||||
"--glass-input-focus": "rgba(224, 242, 254, 0.3)",
|
||||
"--glass-input-focus-bg": "rgba(56, 189, 248, 0.08)",
|
||||
"--glass-table-header": "rgba(56, 189, 248, 0.08)",
|
||||
"--glass-bar-track": "rgba(56, 189, 248, 0.10)",
|
||||
"--nav-bg": "rgba(4, 8, 16, 0.65)",
|
||||
"--nav-border": "rgba(56, 189, 248, 0.08)",
|
||||
"--nav-active": "rgba(56, 189, 248, 0.08)",
|
||||
"--nav-hover": "rgba(56, 189, 248, 0.04)",
|
||||
"--accent-color": "#38bdf8",
|
||||
"--accent-glow": "rgba(56, 189, 248, 0.3)",
|
||||
"--card-lift-shadow": "0 8px 40px rgba(0, 0, 0, 0.3), 0 0 40px rgba(56, 189, 248, 0.06)",
|
||||
"--stat-glow": "0 0 20px rgba(56, 189, 248, 0.15)",
|
||||
"--nav-active-glow": "0 2px 10px rgba(56, 189, 248, 0.3)",
|
||||
},
|
||||
},
|
||||
// 13. Crimson
|
||||
{
|
||||
name: "crimson",
|
||||
label: "Crimson",
|
||||
isDark: true,
|
||||
swatch: ["#ef4444", "#1a1a1a"],
|
||||
bodyBg: "linear-gradient(135deg, #080404 0%, #0c0606 40%, #080404 100%)",
|
||||
bgGradient:
|
||||
"radial-gradient(ellipse 140% 70% at 5% 5%, rgba(239, 68, 68, 0.30), transparent 50%), radial-gradient(ellipse 100% 90% at 95% 85%, rgba(185, 28, 28, 0.25), transparent 50%), radial-gradient(ellipse 80% 50% at 50% 50%, rgba(127, 29, 29, 0.15), transparent 50%)",
|
||||
vars: {
|
||||
"--background": "0 20% 3%",
|
||||
"--foreground": "0 0% 98%",
|
||||
"--card": "0 15% 9% / 0.4",
|
||||
"--card-foreground": "0 0% 98%",
|
||||
"--popover": "0 15% 9% / 0.8",
|
||||
"--popover-foreground": "0 0% 93%",
|
||||
"--primary": "0 84% 60%",
|
||||
"--primary-foreground": "0 0% 98%",
|
||||
"--secondary": "0 15% 14% / 0.5",
|
||||
"--secondary-foreground": "0 0% 93%",
|
||||
"--muted": "0 15% 14% / 0.5",
|
||||
"--muted-foreground": "0 10% 68%",
|
||||
"--accent": "0 84% 60%",
|
||||
"--accent-foreground": "0 0% 98%",
|
||||
"--destructive": "0 84% 60%",
|
||||
"--border": "0 60% 40% / 0.12",
|
||||
"--input": "0 60% 40% / 0.06",
|
||||
"--ring": "0 84% 60%",
|
||||
"--chart-1": "0 84% 60%",
|
||||
"--chart-2": "350 70% 55%",
|
||||
"--chart-3": "15 80% 50%",
|
||||
"--chart-4": "330 60% 50%",
|
||||
"--chart-5": "0 60% 45%",
|
||||
"--sidebar": "0 15% 5%",
|
||||
"--sidebar-foreground": "0 0% 93%",
|
||||
"--sidebar-primary": "0 84% 60%",
|
||||
"--sidebar-primary-foreground": "0 0% 93%",
|
||||
"--sidebar-accent": "0 15% 12%",
|
||||
"--sidebar-accent-foreground": "0 0% 93%",
|
||||
"--sidebar-border": "0 60% 40% / 0.06",
|
||||
"--sidebar-ring": "0 84% 60%",
|
||||
"--card-bg": "rgba(25, 8, 8, 0.35)",
|
||||
"--card-border": "rgba(239, 68, 68, 0.12)",
|
||||
"--card-hover-bg": "rgba(25, 8, 8, 0.45)",
|
||||
"--card-hover-border": "rgba(239, 68, 68, 0.22)",
|
||||
"--glass-bg": "rgba(25, 8, 8, 0.30)",
|
||||
"--glass-border": "rgba(239, 68, 68, 0.08)",
|
||||
"--glass-hover": "rgba(239, 68, 68, 0.03)",
|
||||
"--glass-input-bg": "rgba(239, 68, 68, 0.06)",
|
||||
"--glass-input-border": "rgba(239, 68, 68, 0.1)",
|
||||
"--glass-input-focus": "rgba(239, 68, 68, 0.3)",
|
||||
"--glass-input-focus-bg": "rgba(239, 68, 68, 0.08)",
|
||||
"--glass-table-header": "rgba(239, 68, 68, 0.08)",
|
||||
"--glass-bar-track": "rgba(239, 68, 68, 0.10)",
|
||||
"--nav-bg": "rgba(8, 4, 4, 0.65)",
|
||||
"--nav-border": "rgba(239, 68, 68, 0.08)",
|
||||
"--nav-active": "rgba(239, 68, 68, 0.08)",
|
||||
"--nav-hover": "rgba(239, 68, 68, 0.04)",
|
||||
"--accent-color": "#ef4444",
|
||||
"--accent-glow": "rgba(239, 68, 68, 0.3)",
|
||||
"--card-lift-shadow": "0 8px 40px rgba(0, 0, 0, 0.3), 0 0 40px rgba(239, 68, 68, 0.06)",
|
||||
"--stat-glow": "0 0 20px rgba(239, 68, 68, 0.15)",
|
||||
"--nav-active-glow": "0 2px 10px rgba(239, 68, 68, 0.3)",
|
||||
},
|
||||
},
|
||||
|
||||
// 14. Trinidad
|
||||
{
|
||||
name: "trinidad",
|
||||
label: "Trinidad",
|
||||
isDark: true,
|
||||
swatch: ["#ef4444", "#fbbf24"],
|
||||
bodyBg: "linear-gradient(135deg, #080604 0%, #0c0906 40%, #080604 100%)",
|
||||
bgGradient:
|
||||
"radial-gradient(ellipse 140% 70% at 5% 5%, rgba(239, 68, 68, 0.28), transparent 50%), radial-gradient(ellipse 100% 80% at 90% 30%, rgba(251, 191, 36, 0.22), transparent 50%), radial-gradient(ellipse 120% 60% at 50% 100%, rgba(239, 68, 68, 0.15), transparent 50%)",
|
||||
vars: {
|
||||
"--background": "30 20% 3%",
|
||||
"--foreground": "40 20% 97%",
|
||||
"--card": "30 18% 9% / 0.4",
|
||||
"--card-foreground": "40 20% 97%",
|
||||
"--popover": "30 18% 9% / 0.8",
|
||||
"--popover-foreground": "40 20% 90%",
|
||||
"--primary": "45 93% 58%",
|
||||
"--primary-foreground": "30 30% 8%",
|
||||
"--secondary": "30 18% 14% / 0.5",
|
||||
"--secondary-foreground": "40 20% 90%",
|
||||
"--muted": "30 18% 14% / 0.5",
|
||||
"--muted-foreground": "35 15% 68%",
|
||||
"--accent": "0 84% 60%",
|
||||
"--accent-foreground": "40 20% 97%",
|
||||
"--destructive": "0 84% 60%",
|
||||
"--border": "35 40% 45% / 0.12",
|
||||
"--input": "35 40% 45% / 0.06",
|
||||
"--ring": "45 93% 58%",
|
||||
"--chart-1": "45 93% 58%",
|
||||
"--chart-2": "0 84% 60%",
|
||||
"--chart-3": "30 80% 55%",
|
||||
"--chart-4": "15 80% 50%",
|
||||
"--chart-5": "50 90% 55%",
|
||||
"--sidebar": "30 18% 5%",
|
||||
"--sidebar-foreground": "40 20% 90%",
|
||||
"--sidebar-primary": "45 93% 58%",
|
||||
"--sidebar-primary-foreground": "40 20% 90%",
|
||||
"--sidebar-accent": "30 18% 12%",
|
||||
"--sidebar-accent-foreground": "40 20% 90%",
|
||||
"--sidebar-border": "35 40% 45% / 0.06",
|
||||
"--sidebar-ring": "45 93% 58%",
|
||||
"--card-bg": "rgba(20, 12, 5, 0.35)",
|
||||
"--card-border": "rgba(251, 191, 36, 0.12)",
|
||||
"--card-hover-bg": "rgba(20, 12, 5, 0.45)",
|
||||
"--card-hover-border": "rgba(251, 191, 36, 0.22)",
|
||||
"--glass-bg": "rgba(20, 12, 5, 0.30)",
|
||||
"--glass-border": "rgba(251, 191, 36, 0.08)",
|
||||
"--glass-hover": "rgba(251, 191, 36, 0.03)",
|
||||
"--glass-input-bg": "rgba(251, 191, 36, 0.06)",
|
||||
"--glass-input-border": "rgba(251, 191, 36, 0.1)",
|
||||
"--glass-input-focus": "rgba(239, 68, 68, 0.3)",
|
||||
"--glass-input-focus-bg": "rgba(251, 191, 36, 0.08)",
|
||||
"--glass-table-header": "rgba(251, 191, 36, 0.08)",
|
||||
"--glass-bar-track": "rgba(251, 191, 36, 0.10)",
|
||||
"--nav-bg": "rgba(8, 6, 4, 0.65)",
|
||||
"--nav-border": "rgba(251, 191, 36, 0.08)",
|
||||
"--nav-active": "rgba(251, 191, 36, 0.08)",
|
||||
"--nav-hover": "rgba(251, 191, 36, 0.04)",
|
||||
"--accent-color": "#fbbf24",
|
||||
"--accent-glow": "rgba(251, 191, 36, 0.3)",
|
||||
"--card-lift-shadow": "0 8px 40px rgba(0, 0, 0, 0.3), 0 0 40px rgba(251, 191, 36, 0.06)",
|
||||
"--stat-glow": "0 0 20px rgba(251, 191, 36, 0.15)",
|
||||
"--nav-active-glow": "0 2px 10px rgba(251, 191, 36, 0.3)",
|
||||
},
|
||||
},
|
||||
|
||||
// 15. Samurai
|
||||
{
|
||||
name: "samurai",
|
||||
label: "Samurai",
|
||||
isDark: true,
|
||||
swatch: ["#dc2626", "#fafafa"],
|
||||
bodyBg: "linear-gradient(135deg, #0a0808 0%, #0e0b0b 40%, #0a0808 100%)",
|
||||
bgGradient:
|
||||
"radial-gradient(ellipse 100% 80% at 20% 10%, rgba(220, 38, 38, 0.25), transparent 50%), radial-gradient(ellipse 80% 60% at 80% 80%, rgba(180, 83, 9, 0.15), transparent 50%), radial-gradient(ellipse 60% 40% at 50% 50%, rgba(255, 255, 255, 0.03), transparent 50%)",
|
||||
vars: {
|
||||
"--background": "0 10% 4%",
|
||||
"--foreground": "0 0% 97%",
|
||||
"--card": "0 8% 9% / 0.4",
|
||||
"--card-foreground": "0 0% 97%",
|
||||
"--popover": "0 8% 9% / 0.8",
|
||||
"--popover-foreground": "0 0% 92%",
|
||||
"--primary": "0 72% 51%",
|
||||
"--primary-foreground": "0 0% 97%",
|
||||
"--secondary": "0 8% 14% / 0.5",
|
||||
"--secondary-foreground": "0 0% 92%",
|
||||
"--muted": "0 8% 14% / 0.5",
|
||||
"--muted-foreground": "0 5% 65%",
|
||||
"--accent": "0 72% 51%",
|
||||
"--accent-foreground": "0 0% 97%",
|
||||
"--destructive": "0 72% 51%",
|
||||
"--border": "0 10% 50% / 0.10",
|
||||
"--input": "0 10% 50% / 0.06",
|
||||
"--ring": "0 72% 51%",
|
||||
"--chart-1": "0 72% 51%",
|
||||
"--chart-2": "30 70% 40%",
|
||||
"--chart-3": "350 60% 50%",
|
||||
"--chart-4": "15 50% 45%",
|
||||
"--chart-5": "0 50% 40%",
|
||||
"--sidebar": "0 8% 5%",
|
||||
"--sidebar-foreground": "0 0% 92%",
|
||||
"--sidebar-primary": "0 72% 51%",
|
||||
"--sidebar-primary-foreground": "0 0% 92%",
|
||||
"--sidebar-accent": "0 8% 12%",
|
||||
"--sidebar-accent-foreground": "0 0% 92%",
|
||||
"--sidebar-border": "0 10% 50% / 0.06",
|
||||
"--sidebar-ring": "0 72% 51%",
|
||||
"--card-bg": "rgba(18, 10, 10, 0.35)",
|
||||
"--card-border": "rgba(220, 38, 38, 0.10)",
|
||||
"--card-hover-bg": "rgba(18, 10, 10, 0.45)",
|
||||
"--card-hover-border": "rgba(220, 38, 38, 0.18)",
|
||||
"--glass-bg": "rgba(18, 10, 10, 0.30)",
|
||||
"--glass-border": "rgba(220, 38, 38, 0.06)",
|
||||
"--glass-hover": "rgba(220, 38, 38, 0.03)",
|
||||
"--glass-input-bg": "rgba(255, 255, 255, 0.06)",
|
||||
"--glass-input-border": "rgba(255, 255, 255, 0.08)",
|
||||
"--glass-input-focus": "rgba(220, 38, 38, 0.3)",
|
||||
"--glass-input-focus-bg": "rgba(255, 255, 255, 0.08)",
|
||||
"--glass-table-header": "rgba(255, 255, 255, 0.06)",
|
||||
"--glass-bar-track": "rgba(255, 255, 255, 0.08)",
|
||||
"--nav-bg": "rgba(10, 8, 8, 0.65)",
|
||||
"--nav-border": "rgba(220, 38, 38, 0.06)",
|
||||
"--nav-active": "rgba(220, 38, 38, 0.08)",
|
||||
"--nav-hover": "rgba(220, 38, 38, 0.04)",
|
||||
"--accent-color": "#dc2626",
|
||||
"--accent-glow": "rgba(220, 38, 38, 0.2)",
|
||||
"--card-lift-shadow": "0 8px 40px rgba(0, 0, 0, 0.35)",
|
||||
"--stat-glow": "0 0 20px rgba(220, 38, 38, 0.12)",
|
||||
"--nav-active-glow": "0 2px 10px rgba(220, 38, 38, 0.25)",
|
||||
},
|
||||
},
|
||||
|
||||
// 16. Supra
|
||||
{
|
||||
name: "supra",
|
||||
label: "Supra",
|
||||
isDark: true,
|
||||
swatch: ["#f97316", "#18181b"],
|
||||
bodyBg: "linear-gradient(135deg, #060606 0%, #0a0806 40%, #060606 100%)",
|
||||
bgGradient:
|
||||
"radial-gradient(ellipse 120% 60% at 10% 10%, rgba(249, 115, 22, 0.30), transparent 50%), radial-gradient(ellipse 80% 70% at 85% 70%, rgba(234, 88, 12, 0.20), transparent 50%), radial-gradient(ellipse 100% 40% at 50% 100%, rgba(249, 115, 22, 0.12), transparent 50%)",
|
||||
vars: {
|
||||
"--background": "24 10% 3%",
|
||||
"--foreground": "24 10% 97%",
|
||||
"--card": "24 8% 9% / 0.4",
|
||||
"--card-foreground": "24 10% 97%",
|
||||
"--popover": "24 8% 9% / 0.8",
|
||||
"--popover-foreground": "24 10% 92%",
|
||||
"--primary": "25 95% 53%",
|
||||
"--primary-foreground": "24 20% 8%",
|
||||
"--secondary": "24 10% 14% / 0.5",
|
||||
"--secondary-foreground": "24 10% 92%",
|
||||
"--muted": "24 10% 14% / 0.5",
|
||||
"--muted-foreground": "24 10% 65%",
|
||||
"--accent": "25 95% 53%",
|
||||
"--accent-foreground": "24 10% 97%",
|
||||
"--destructive": "0 84% 60%",
|
||||
"--border": "25 40% 40% / 0.10",
|
||||
"--input": "25 40% 40% / 0.06",
|
||||
"--ring": "25 95% 53%",
|
||||
"--chart-1": "25 95% 53%",
|
||||
"--chart-2": "15 80% 50%",
|
||||
"--chart-3": "35 90% 55%",
|
||||
"--chart-4": "10 70% 45%",
|
||||
"--chart-5": "30 85% 50%",
|
||||
"--sidebar": "24 8% 5%",
|
||||
"--sidebar-foreground": "24 10% 92%",
|
||||
"--sidebar-primary": "25 95% 53%",
|
||||
"--sidebar-primary-foreground": "24 10% 92%",
|
||||
"--sidebar-accent": "24 10% 12%",
|
||||
"--sidebar-accent-foreground": "24 10% 92%",
|
||||
"--sidebar-border": "25 40% 40% / 0.06",
|
||||
"--sidebar-ring": "25 95% 53%",
|
||||
"--card-bg": "rgba(15, 10, 5, 0.35)",
|
||||
"--card-border": "rgba(249, 115, 22, 0.12)",
|
||||
"--card-hover-bg": "rgba(15, 10, 5, 0.45)",
|
||||
"--card-hover-border": "rgba(249, 115, 22, 0.22)",
|
||||
"--glass-bg": "rgba(15, 10, 5, 0.30)",
|
||||
"--glass-border": "rgba(249, 115, 22, 0.08)",
|
||||
"--glass-hover": "rgba(249, 115, 22, 0.03)",
|
||||
"--glass-input-bg": "rgba(249, 115, 22, 0.06)",
|
||||
"--glass-input-border": "rgba(249, 115, 22, 0.1)",
|
||||
"--glass-input-focus": "rgba(249, 115, 22, 0.3)",
|
||||
"--glass-input-focus-bg": "rgba(249, 115, 22, 0.08)",
|
||||
"--glass-table-header": "rgba(249, 115, 22, 0.08)",
|
||||
"--glass-bar-track": "rgba(249, 115, 22, 0.10)",
|
||||
"--nav-bg": "rgba(6, 6, 6, 0.65)",
|
||||
"--nav-border": "rgba(249, 115, 22, 0.08)",
|
||||
"--nav-active": "rgba(249, 115, 22, 0.08)",
|
||||
"--nav-hover": "rgba(249, 115, 22, 0.04)",
|
||||
"--accent-color": "#f97316",
|
||||
"--accent-glow": "rgba(249, 115, 22, 0.3)",
|
||||
"--card-lift-shadow": "0 8px 40px rgba(0, 0, 0, 0.3), 0 0 40px rgba(249, 115, 22, 0.06)",
|
||||
"--stat-glow": "0 0 20px rgba(249, 115, 22, 0.15)",
|
||||
"--nav-active-glow": "0 2px 10px rgba(249, 115, 22, 0.3)",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const DEFAULT_THEME = "midnight";
|
||||
|
||||
export function getTheme(name: string): Theme {
|
||||
return themes.find((t) => t.name === name) ?? themes[0];
|
||||
}
|
||||
151
dashboard/ui/lib/types.ts
Normal file
151
dashboard/ui/lib/types.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
// Types aligned with the actual API response from /api/stats/overview
|
||||
export interface OverviewStats {
|
||||
containers: {
|
||||
total: number;
|
||||
running?: number;
|
||||
endpoints?: Record<string, { total: number; running: number; error?: boolean }>;
|
||||
by_endpoint?: Record<string, { total: number; running: number; error?: boolean }>;
|
||||
};
|
||||
gpu: {
|
||||
available: boolean;
|
||||
name?: string;
|
||||
temp_c?: number;
|
||||
// API may use either naming convention
|
||||
power_w?: number;
|
||||
power_draw_w?: number;
|
||||
power_limit_w?: number;
|
||||
vram_used_mb?: number;
|
||||
vram_total_mb?: number;
|
||||
memory_used_mb?: number;
|
||||
memory_total_mb?: number;
|
||||
utilization_pct?: number;
|
||||
};
|
||||
// API returns either a number or {gmail, dvish, proton, total}
|
||||
emails_today?: number | Record<string, number>;
|
||||
email_today?: number | Record<string, number>;
|
||||
alerts?: number;
|
||||
unhealthy_count?: number;
|
||||
// API returns either an object or a boolean
|
||||
ollama?: { available: boolean; url: string };
|
||||
ollama_available?: boolean;
|
||||
hosts_online?: number;
|
||||
}
|
||||
|
||||
export interface ActivityEvent {
|
||||
type: string;
|
||||
timestamp: string;
|
||||
source: string;
|
||||
raw: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface Container {
|
||||
id: string;
|
||||
name: string;
|
||||
image: string;
|
||||
state: string;
|
||||
status: string;
|
||||
endpoint: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface JellyfinStatus {
|
||||
version: string;
|
||||
server_name: string;
|
||||
libraries: { name: string; type: string; paths: string[] }[];
|
||||
active_sessions: {
|
||||
user: string;
|
||||
device: string;
|
||||
client: string;
|
||||
title: string;
|
||||
type: string;
|
||||
}[];
|
||||
idle_sessions: number;
|
||||
}
|
||||
|
||||
export interface EmailStats {
|
||||
accounts: {
|
||||
account: string;
|
||||
today: number;
|
||||
categories: Record<string, number>;
|
||||
}[];
|
||||
sender_cache: Record<string, number>;
|
||||
}
|
||||
|
||||
export interface ExpenseSummary {
|
||||
month: string;
|
||||
total: number;
|
||||
count: number;
|
||||
top_vendors: { vendor: string; amount: number }[];
|
||||
}
|
||||
|
||||
export interface HealthScore {
|
||||
score: number;
|
||||
grade: string;
|
||||
details: { name: string; score: number; max: number; status: string }[];
|
||||
}
|
||||
|
||||
export interface KumaMonitor {
|
||||
id: number;
|
||||
name: string;
|
||||
url?: string;
|
||||
type: string;
|
||||
active: boolean;
|
||||
status: boolean; // true = up
|
||||
parent?: number;
|
||||
}
|
||||
|
||||
export interface KumaStats {
|
||||
monitors: KumaMonitor[];
|
||||
total: number;
|
||||
up: number;
|
||||
down: number;
|
||||
}
|
||||
|
||||
export interface JellyfinLatestItem {
|
||||
name: string;
|
||||
type: string;
|
||||
series?: string;
|
||||
date: string;
|
||||
year?: number;
|
||||
}
|
||||
|
||||
export interface ArrHistoryItem {
|
||||
title: string;
|
||||
event: string;
|
||||
date: string;
|
||||
quality: string;
|
||||
}
|
||||
|
||||
export interface CloudflareStats {
|
||||
total: number;
|
||||
proxied: number;
|
||||
dns_only: number;
|
||||
types: Record<string, number>;
|
||||
}
|
||||
|
||||
export interface AuthentikStats {
|
||||
active_sessions: number;
|
||||
users?: { username: string; last_login: string; active: boolean }[];
|
||||
sessions?: { user: string; last_used?: string }[];
|
||||
recent_events?: { action: string; user?: string; created?: string; timestamp?: string }[];
|
||||
}
|
||||
|
||||
export interface GiteaActivity {
|
||||
commits: { sha: string; message: string; author: string; date: string }[];
|
||||
open_prs: { title: string; number: number; author: string }[];
|
||||
}
|
||||
|
||||
export interface AutomationTimelineEntry {
|
||||
name: string;
|
||||
last_run: string;
|
||||
exists: boolean;
|
||||
}
|
||||
|
||||
export interface DiskUsageEntry {
|
||||
host: string;
|
||||
mount: string;
|
||||
total_gb: number;
|
||||
avail_gb: number;
|
||||
used_pct: number;
|
||||
}
|
||||
9
dashboard/ui/lib/use-poll.ts
Normal file
9
dashboard/ui/lib/use-poll.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import useSWR from "swr";
|
||||
import { fetchAPI } from "./api";
|
||||
|
||||
export function usePoll<T>(path: string, interval: number = 60000) {
|
||||
return useSWR<T>(path, () => fetchAPI<T>(path), {
|
||||
refreshInterval: interval,
|
||||
revalidateOnFocus: false,
|
||||
});
|
||||
}
|
||||
52
dashboard/ui/lib/use-sse.ts
Normal file
52
dashboard/ui/lib/use-sse.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import type { ActivityEvent } from "./types";
|
||||
|
||||
// Use same origin — Next.js rewrites /api/* to backend
|
||||
|
||||
export function useSSE(path: string, maxEvents: number = 30) {
|
||||
const [events, setEvents] = useState<ActivityEvent[]>([]);
|
||||
const retryTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let es: EventSource | null = null;
|
||||
|
||||
function connect() {
|
||||
es = new EventSource(path);
|
||||
|
||||
// Backend sends "init" with the full batch and "update" with new events
|
||||
es.addEventListener("init", (e: MessageEvent) => {
|
||||
try {
|
||||
const batch: ActivityEvent[] = JSON.parse(e.data);
|
||||
setEvents(batch.slice(0, maxEvents));
|
||||
} catch {
|
||||
// ignore malformed events
|
||||
}
|
||||
});
|
||||
|
||||
es.addEventListener("update", (e: MessageEvent) => {
|
||||
try {
|
||||
const batch: ActivityEvent[] = JSON.parse(e.data);
|
||||
setEvents((prev) => [...batch, ...prev].slice(0, maxEvents));
|
||||
} catch {
|
||||
// ignore malformed events
|
||||
}
|
||||
});
|
||||
|
||||
es.onerror = () => {
|
||||
es?.close();
|
||||
retryTimeout.current = setTimeout(connect, 5000);
|
||||
};
|
||||
}
|
||||
|
||||
connect();
|
||||
|
||||
return () => {
|
||||
es?.close();
|
||||
if (retryTimeout.current) clearTimeout(retryTimeout.current);
|
||||
};
|
||||
}, [path, maxEvents]);
|
||||
|
||||
return events;
|
||||
}
|
||||
6
dashboard/ui/lib/utils.ts
Normal file
6
dashboard/ui/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
Reference in New Issue
Block a user