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 (
+
{
+ 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')}`)
})
}