diff --git a/src/App.tsx b/src/App.tsx index b925f70..51ec6a0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,38 +1,78 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Separator } from "@/components/ui/separator" import { Button } from "@/components/ui/button" -import { Moon, Sun, Monitor, CheckCircle2, XCircle, AlertCircle, Clock } from "lucide-react" +import { Moon, Sun, Monitor} from "lucide-react" +import * as LucideIcons from "lucide-react" import { useTheme } from "@/components/theme-provider" import { useMetrics } from "./hooks/useMetrics" import { ServiceStatus } from "./types/metrics" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip" -import { - Server, - Box, - Play, - Activity, - Shield, - Database, - Download, - Home, - Tv, - Globe, - Gauge, - HardDrive, - Search, - Settings, - LayoutDashboard, - Package, - Smartphone -} from "lucide-react" interface ServiceCard { name: string description: string url: string - icon: React.ReactNode + icon?: React.ReactNode + iconName?: string category: 'system' | 'media' | 'monitoring' | 'tools' | 'acot' | 'home' - monitorName?: string // Optional monitor name that matches Uptime Kuma + monitorName?: string +} + +const getIconUrl = (name: string | undefined, isDark: boolean = false): string => { + if (!name) return '' + // Convert to selfh.st reference format + const ref = name.toLowerCase().replace(/[^a-z0-9]/g, '-') + // Use light version for dark mode if available + const suffix = isDark ? '-light' : '' + return `https://cdn.jsdelivr.net/gh/selfhst/icons/png/${ref}${suffix}.png` +} + +function ServiceIcon({ service, isDark }: { service: ServiceCard, isDark: boolean }) { + if (service.icon) { + return
{service.icon}
+ } + + if (service.iconName) { + // Handle Lucide icons + if (service.iconName.startsWith('lucide-')) { + const iconName = service.iconName.replace('lucide-', '') + .split('-') + .map(part => part.charAt(0).toUpperCase() + part.slice(1)) + .join('') + + // Cast to any to avoid TypeScript complexity with dynamic imports + const Icon = (LucideIcons as any)[iconName] || LucideIcons.Box + return + } + + // Handle selfh.st icons + return ( + {`${service.name} { + if (isDark && e.currentTarget.src.includes('-light')) { + e.currentTarget.src = getIconUrl(service.iconName, false) + } + }} + /> + ) + } + + // Default category icons + const categoryIcons: Record = { + system: 'Server', + media: 'Play', + monitoring: 'Activity', + tools: 'Settings', + acot: 'LayoutDashboard', + home: 'Home' + } + + // Cast to any to avoid TypeScript complexity with dynamic imports + const Icon = (LucideIcons as any)[categoryIcons[service.category] || 'Box'] + return } const services: ServiceCard[] = [ @@ -40,28 +80,28 @@ const services: ServiceCard[] = [ name: "Portainer", description: "Container Management", url: "https://portainer.kent.pw", - icon: , + iconName: "portainer", category: "system" }, { name: "Gitea", description: "Git Server", url: "https://gitea.kent.pw", - icon: , + iconName: "gitea", category: "system" }, { name: "Cockpit", description: "Server Management", url: "https://cockpit.kent.pw", - icon: , + iconName: "cockpit", category: "system" }, { name: "DiskStation", description: "Synology NAS", url: "https://diskstation.kent.pw", - icon: , + iconName: "synology", category: "system", monitorName: "Synology Diskstation" }, @@ -69,42 +109,43 @@ const services: ServiceCard[] = [ name: "Plex", description: "Media Server", url: "https://plex.kent.pw", - icon: , + iconName: "plex", category: "media" }, { name: "Sonarr", description: "TV Show Management", url: "https://sonarr.kent.pw", - icon: , + iconName: "sonarr", category: "media" }, { name: "Jackett", description: "Torrent Indexer", url: "https://jackett.kent.pw", - icon: , + iconName: "jackett", category: "media" }, { name: "Deluge", description: "Torrent Client", url: "https://deluge.kent.pw", - icon: , + iconName: "deluge", category: "media" }, { name: "Uptime", description: "Service Monitoring", url: "https://uptime.kent.pw", - icon: , - category: "monitoring" + iconName: "uptime-kuma", + category: "monitoring", + monitorName: "Uptime Kuma" }, { name: "AdGuard", description: "Network Ad Blocking", url: "https://adguard.kent.pw", - icon: , + iconName: "adguard-home", category: "system", monitorName: "Adguard Home" }, @@ -112,7 +153,7 @@ const services: ServiceCard[] = [ name: "NocoDB", description: "Database Platform", url: "https://noco.kent.pw", - icon: , + iconName: "nocodb", category: "tools", monitorName: "NocoDB" }, @@ -120,7 +161,7 @@ const services: ServiceCard[] = [ name: "IT Tools", description: "Developer Utilities", url: "https://ittools.kent.pw", - icon: , + iconName: "it-tools", category: "tools", monitorName: "IT Tools" }, @@ -128,14 +169,14 @@ const services: ServiceCard[] = [ name: "Firefox", description: "Browser Instance", url: "https://firefox.kent.pw", - icon: , + iconName: "firefox", category: "tools" }, { name: "Speedtest", description: "Network Speed Monitor", url: "https://speedtest.kent.pw", - icon: , + iconName: "speedtest-tracker", category: "monitoring", monitorName: "Speedtest Tracker" }, @@ -143,7 +184,7 @@ const services: ServiceCard[] = [ name: "Drive", description: "File Storage", url: "https://drive.kent.pw", - icon: , + iconName: "synology", category: "tools", monitorName: "Synology Drive" }, @@ -151,28 +192,28 @@ const services: ServiceCard[] = [ name: "Dashboard", description: "ACOT Dashboard", url: "https://dashboard.kent.pw", - icon: , + iconName: "lucide-layout-dashboard", category: "acot" }, { name: "Inventory", description: "ACOT Inventory", url: "https://inventory.kent.pw", - icon: , + iconName: "lucide-box", category: "acot" }, { name: "Homebridge", description: "HomeKit Bridge", url: "https://homebridge.kent.pw", - icon: , + iconName: "homebridge", category: "home" }, { name: "Scrypted", description: "Smart Home Integration", url: "https://scrypted.kent.pw", - icon: , + iconName: "scrypted", category: "home" } ] @@ -212,16 +253,16 @@ function StatusIndicator({ status, responseTime, certDaysRemaining }: { responseTime: number, certDaysRemaining: number }) { - const getStatusIcon = () => { + const getStatusColor = () => { switch (status) { case 'up': - return + return 'bg-green-500' case 'down': - return + return 'bg-red-500' case 'pending': - return + return 'bg-yellow-500' case 'maintenance': - return + return 'bg-blue-500' } } @@ -238,8 +279,8 @@ function StatusIndicator({ status, responseTime, certDaysRemaining }: { return ( - - {getStatusIcon()} + +

{getStatusText()}

@@ -254,6 +295,9 @@ function ServiceSection({ title, services, metrics }: { services: ServiceCard[], metrics: Record }) { + const { theme } = useTheme() + const isDark = theme === 'dark' || (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches) + return (
@@ -268,42 +312,40 @@ function ServiceSection({ title, services, metrics }: { certDaysRemaining: 0 } - // Get background color based on service const bgColor = (() => { switch (service.name) { case "Portainer": - return "bg-blue-50 dark:bg-blue-950/30" // Portainer blue + return "bg-blue-50 dark:bg-blue-950/30" case "Gitea": - return "bg-green-50 dark:bg-green-950/30" // Gitea green + return "bg-green-50 dark:bg-green-950/30" case "Plex": - return "bg-orange-50 dark:bg-orange-950/30" // Plex orange + return "bg-orange-50 dark:bg-orange-950/30" case "Sonarr": - return "bg-blue-50 dark:bg-blue-950/30" // Sonarr blue + return "bg-blue-50 dark:bg-blue-950/30" case "AdGuard": - return "bg-emerald-50 dark:bg-emerald-950/30" // AdGuard green + return "bg-emerald-50 dark:bg-emerald-950/30" case "Homebridge": - return "bg-purple-50 dark:bg-purple-950/30" // Homebridge purple + return "bg-purple-50 dark:bg-purple-950/30" case "Scrypted": - return "bg-indigo-50 dark:bg-indigo-950/30" // Scrypted blue/purple + return "bg-indigo-50 dark:bg-indigo-950/30" case "Dashboard": case "Inventory": - return "bg-sky-50 dark:bg-sky-950/30" // ACOT blue + return "bg-sky-50 dark:bg-sky-950/30" case "DiskStation": - return "bg-slate-50 dark:bg-slate-950/30" // Synology gray + return "bg-slate-50 dark:bg-slate-950/30" case "Deluge": - return "bg-green-50 dark:bg-green-950/30" // Deluge green + return "bg-green-50 dark:bg-green-950/30" case "Firefox": - return "bg-orange-50 dark:bg-orange-950/30" // Firefox orange + return "bg-orange-50 dark:bg-orange-950/30" case "Uptime": - return "bg-emerald-50 dark:bg-emerald-950/30" // Uptime green (success color) + return "bg-emerald-50 dark:bg-emerald-950/30" case "Speedtest": - return "bg-blue-50 dark:bg-blue-950/30" // Speedtest blue + return "bg-blue-50 dark:bg-blue-950/30" default: - return "bg-gray-50 dark:bg-gray-950/30" // Default subtle background + return "bg-gray-50 dark:bg-gray-950/30" } })() - // Get icon color based on service const iconColor = (() => { switch (service.name) { case "Portainer": @@ -349,7 +391,9 @@ function ServiceSection({ title, services, metrics }: {
-
{service.icon}
+
+ +
{service.name}
) } - function App() { - const { metrics, loading, error } = useMetrics(import.meta.env.VITE_UPTIME_API_KEY) + const { metrics } = useMetrics() const systemServices = services.filter(s => s.category === 'system') const mediaServices = services.filter(s => s.category === 'media') const monitoringServices = services.filter(s => s.category === 'monitoring') @@ -380,7 +423,7 @@ function App() { const homeServices = services.filter(s => s.category === 'home') return ( -
+

Kent.pw Homepage

@@ -391,7 +434,7 @@ function App() { -
+
) } diff --git a/src/components/theme-provider.tsx b/src/components/theme-provider.tsx index 21ca373..be31182 100644 --- a/src/components/theme-provider.tsx +++ b/src/components/theme-provider.tsx @@ -1,8 +1,7 @@ "use client" -import * as React from "react" import { ThemeProvider as NextThemesProvider, useTheme as useNextTheme } from "next-themes" -import { type ThemeProviderProps } from "next-themes/dist/types" +import type { ThemeProviderProps } from "next-themes" export function ThemeProvider({ children, ...props }: ThemeProviderProps) { return {children} diff --git a/src/hooks/useMetrics.ts b/src/hooks/useMetrics.ts index 0798d02..8fe589b 100644 --- a/src/hooks/useMetrics.ts +++ b/src/hooks/useMetrics.ts @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react' -import { MetricsData, ServiceMetrics } from '../types/metrics' +import { MetricsData } from '../types/metrics' const parseMetricsData = (data: string): MetricsData => { const metrics: MetricsData = {} @@ -92,7 +92,7 @@ const parseMetricsData = (data: string): MetricsData => { return metrics } -export function useMetrics(apiKey: string) { +export function useMetrics() { const [metrics, setMetrics] = useState({}) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) diff --git a/src/index.css b/src/index.css index e36039e..92c8440 100644 --- a/src/index.css +++ b/src/index.css @@ -73,7 +73,7 @@ button:focus-visible { @layer base { :root { - --background: 0 0% 100%; + --background: 210 20% 96%; --foreground: 222.2 84% 4.9%; --card: 0 0% 100%; --card-foreground: 222.2 84% 4.9%; diff --git a/vite.config.ts b/vite.config.ts index 6b37344..ca58213 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -20,8 +20,8 @@ export default defineConfig(({ mode }) => { target: 'https://uptime.kent.pw', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, ''), - configure: (proxy, options) => { - proxy.on('proxyReq', (proxyReq, req, res) => { + configure: (proxy, _options) => { + proxy.on('proxyReq', (proxyReq, _req, _res) => { proxyReq.setHeader('Authorization', `Basic ${Buffer.from(':' + apiKey).toString('base64')}`) }) }