|
|
|
@@ -1,38 +1,78 @@
|
|
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
|
|
import { Separator } from "@/components/ui/separator"
|
|
|
|
import { Separator } from "@/components/ui/separator"
|
|
|
|
import { Button } from "@/components/ui/button"
|
|
|
|
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 { useTheme } from "@/components/theme-provider"
|
|
|
|
import { useMetrics } from "./hooks/useMetrics"
|
|
|
|
import { useMetrics } from "./hooks/useMetrics"
|
|
|
|
import { ServiceStatus } from "./types/metrics"
|
|
|
|
import { ServiceStatus } from "./types/metrics"
|
|
|
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
|
|
|
|
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 {
|
|
|
|
interface ServiceCard {
|
|
|
|
name: string
|
|
|
|
name: string
|
|
|
|
description: string
|
|
|
|
description: string
|
|
|
|
url: string
|
|
|
|
url: string
|
|
|
|
icon: React.ReactNode
|
|
|
|
icon?: React.ReactNode
|
|
|
|
|
|
|
|
iconName?: string
|
|
|
|
category: 'system' | 'media' | 'monitoring' | 'tools' | 'acot' | 'home'
|
|
|
|
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 <div className="h-6 w-6">{service.icon}</div>
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 <Icon className="h-6 w-6" />
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Handle selfh.st icons
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
|
|
<img
|
|
|
|
|
|
|
|
src={getIconUrl(service.iconName, isDark)}
|
|
|
|
|
|
|
|
className="h-6 w-6 object-contain"
|
|
|
|
|
|
|
|
alt={`${service.name} icon`}
|
|
|
|
|
|
|
|
onError={(e) => {
|
|
|
|
|
|
|
|
if (isDark && e.currentTarget.src.includes('-light')) {
|
|
|
|
|
|
|
|
e.currentTarget.src = getIconUrl(service.iconName, false)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Default category icons
|
|
|
|
|
|
|
|
const categoryIcons: Record<string, string> = {
|
|
|
|
|
|
|
|
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 <Icon className="h-6 w-6" />
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const services: ServiceCard[] = [
|
|
|
|
const services: ServiceCard[] = [
|
|
|
|
@@ -40,28 +80,28 @@ const services: ServiceCard[] = [
|
|
|
|
name: "Portainer",
|
|
|
|
name: "Portainer",
|
|
|
|
description: "Container Management",
|
|
|
|
description: "Container Management",
|
|
|
|
url: "https://portainer.kent.pw",
|
|
|
|
url: "https://portainer.kent.pw",
|
|
|
|
icon: <Box className="h-6 w-6" />,
|
|
|
|
iconName: "portainer",
|
|
|
|
category: "system"
|
|
|
|
category: "system"
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{
|
|
|
|
name: "Gitea",
|
|
|
|
name: "Gitea",
|
|
|
|
description: "Git Server",
|
|
|
|
description: "Git Server",
|
|
|
|
url: "https://gitea.kent.pw",
|
|
|
|
url: "https://gitea.kent.pw",
|
|
|
|
icon: <Server className="h-6 w-6" />,
|
|
|
|
iconName: "gitea",
|
|
|
|
category: "system"
|
|
|
|
category: "system"
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{
|
|
|
|
name: "Cockpit",
|
|
|
|
name: "Cockpit",
|
|
|
|
description: "Server Management",
|
|
|
|
description: "Server Management",
|
|
|
|
url: "https://cockpit.kent.pw",
|
|
|
|
url: "https://cockpit.kent.pw",
|
|
|
|
icon: <Settings className="h-6 w-6" />,
|
|
|
|
iconName: "cockpit",
|
|
|
|
category: "system"
|
|
|
|
category: "system"
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{
|
|
|
|
name: "DiskStation",
|
|
|
|
name: "DiskStation",
|
|
|
|
description: "Synology NAS",
|
|
|
|
description: "Synology NAS",
|
|
|
|
url: "https://diskstation.kent.pw",
|
|
|
|
url: "https://diskstation.kent.pw",
|
|
|
|
icon: <HardDrive className="h-6 w-6" />,
|
|
|
|
iconName: "synology",
|
|
|
|
category: "system",
|
|
|
|
category: "system",
|
|
|
|
monitorName: "Synology Diskstation"
|
|
|
|
monitorName: "Synology Diskstation"
|
|
|
|
},
|
|
|
|
},
|
|
|
|
@@ -69,42 +109,43 @@ const services: ServiceCard[] = [
|
|
|
|
name: "Plex",
|
|
|
|
name: "Plex",
|
|
|
|
description: "Media Server",
|
|
|
|
description: "Media Server",
|
|
|
|
url: "https://plex.kent.pw",
|
|
|
|
url: "https://plex.kent.pw",
|
|
|
|
icon: <Play className="h-6 w-6" />,
|
|
|
|
iconName: "plex",
|
|
|
|
category: "media"
|
|
|
|
category: "media"
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{
|
|
|
|
name: "Sonarr",
|
|
|
|
name: "Sonarr",
|
|
|
|
description: "TV Show Management",
|
|
|
|
description: "TV Show Management",
|
|
|
|
url: "https://sonarr.kent.pw",
|
|
|
|
url: "https://sonarr.kent.pw",
|
|
|
|
icon: <Tv className="h-6 w-6" />,
|
|
|
|
iconName: "sonarr",
|
|
|
|
category: "media"
|
|
|
|
category: "media"
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{
|
|
|
|
name: "Jackett",
|
|
|
|
name: "Jackett",
|
|
|
|
description: "Torrent Indexer",
|
|
|
|
description: "Torrent Indexer",
|
|
|
|
url: "https://jackett.kent.pw",
|
|
|
|
url: "https://jackett.kent.pw",
|
|
|
|
icon: <Search className="h-6 w-6" />,
|
|
|
|
iconName: "jackett",
|
|
|
|
category: "media"
|
|
|
|
category: "media"
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{
|
|
|
|
name: "Deluge",
|
|
|
|
name: "Deluge",
|
|
|
|
description: "Torrent Client",
|
|
|
|
description: "Torrent Client",
|
|
|
|
url: "https://deluge.kent.pw",
|
|
|
|
url: "https://deluge.kent.pw",
|
|
|
|
icon: <Download className="h-6 w-6" />,
|
|
|
|
iconName: "deluge",
|
|
|
|
category: "media"
|
|
|
|
category: "media"
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{
|
|
|
|
name: "Uptime",
|
|
|
|
name: "Uptime",
|
|
|
|
description: "Service Monitoring",
|
|
|
|
description: "Service Monitoring",
|
|
|
|
url: "https://uptime.kent.pw",
|
|
|
|
url: "https://uptime.kent.pw",
|
|
|
|
icon: <Activity className="h-6 w-6" />,
|
|
|
|
iconName: "uptime-kuma",
|
|
|
|
category: "monitoring"
|
|
|
|
category: "monitoring",
|
|
|
|
|
|
|
|
monitorName: "Uptime Kuma"
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{
|
|
|
|
name: "AdGuard",
|
|
|
|
name: "AdGuard",
|
|
|
|
description: "Network Ad Blocking",
|
|
|
|
description: "Network Ad Blocking",
|
|
|
|
url: "https://adguard.kent.pw",
|
|
|
|
url: "https://adguard.kent.pw",
|
|
|
|
icon: <Shield className="h-6 w-6" />,
|
|
|
|
iconName: "adguard-home",
|
|
|
|
category: "system",
|
|
|
|
category: "system",
|
|
|
|
monitorName: "Adguard Home"
|
|
|
|
monitorName: "Adguard Home"
|
|
|
|
},
|
|
|
|
},
|
|
|
|
@@ -112,7 +153,7 @@ const services: ServiceCard[] = [
|
|
|
|
name: "NocoDB",
|
|
|
|
name: "NocoDB",
|
|
|
|
description: "Database Platform",
|
|
|
|
description: "Database Platform",
|
|
|
|
url: "https://noco.kent.pw",
|
|
|
|
url: "https://noco.kent.pw",
|
|
|
|
icon: <Database className="h-6 w-6" />,
|
|
|
|
iconName: "nocodb",
|
|
|
|
category: "tools",
|
|
|
|
category: "tools",
|
|
|
|
monitorName: "NocoDB"
|
|
|
|
monitorName: "NocoDB"
|
|
|
|
},
|
|
|
|
},
|
|
|
|
@@ -120,7 +161,7 @@ const services: ServiceCard[] = [
|
|
|
|
name: "IT Tools",
|
|
|
|
name: "IT Tools",
|
|
|
|
description: "Developer Utilities",
|
|
|
|
description: "Developer Utilities",
|
|
|
|
url: "https://ittools.kent.pw",
|
|
|
|
url: "https://ittools.kent.pw",
|
|
|
|
icon: <Settings className="h-6 w-6" />,
|
|
|
|
iconName: "it-tools",
|
|
|
|
category: "tools",
|
|
|
|
category: "tools",
|
|
|
|
monitorName: "IT Tools"
|
|
|
|
monitorName: "IT Tools"
|
|
|
|
},
|
|
|
|
},
|
|
|
|
@@ -128,14 +169,14 @@ const services: ServiceCard[] = [
|
|
|
|
name: "Firefox",
|
|
|
|
name: "Firefox",
|
|
|
|
description: "Browser Instance",
|
|
|
|
description: "Browser Instance",
|
|
|
|
url: "https://firefox.kent.pw",
|
|
|
|
url: "https://firefox.kent.pw",
|
|
|
|
icon: <Globe className="h-6 w-6" />,
|
|
|
|
iconName: "firefox",
|
|
|
|
category: "tools"
|
|
|
|
category: "tools"
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{
|
|
|
|
name: "Speedtest",
|
|
|
|
name: "Speedtest",
|
|
|
|
description: "Network Speed Monitor",
|
|
|
|
description: "Network Speed Monitor",
|
|
|
|
url: "https://speedtest.kent.pw",
|
|
|
|
url: "https://speedtest.kent.pw",
|
|
|
|
icon: <Gauge className="h-6 w-6" />,
|
|
|
|
iconName: "speedtest-tracker",
|
|
|
|
category: "monitoring",
|
|
|
|
category: "monitoring",
|
|
|
|
monitorName: "Speedtest Tracker"
|
|
|
|
monitorName: "Speedtest Tracker"
|
|
|
|
},
|
|
|
|
},
|
|
|
|
@@ -143,7 +184,7 @@ const services: ServiceCard[] = [
|
|
|
|
name: "Drive",
|
|
|
|
name: "Drive",
|
|
|
|
description: "File Storage",
|
|
|
|
description: "File Storage",
|
|
|
|
url: "https://drive.kent.pw",
|
|
|
|
url: "https://drive.kent.pw",
|
|
|
|
icon: <HardDrive className="h-6 w-6" />,
|
|
|
|
iconName: "synology",
|
|
|
|
category: "tools",
|
|
|
|
category: "tools",
|
|
|
|
monitorName: "Synology Drive"
|
|
|
|
monitorName: "Synology Drive"
|
|
|
|
},
|
|
|
|
},
|
|
|
|
@@ -151,28 +192,28 @@ const services: ServiceCard[] = [
|
|
|
|
name: "Dashboard",
|
|
|
|
name: "Dashboard",
|
|
|
|
description: "ACOT Dashboard",
|
|
|
|
description: "ACOT Dashboard",
|
|
|
|
url: "https://dashboard.kent.pw",
|
|
|
|
url: "https://dashboard.kent.pw",
|
|
|
|
icon: <LayoutDashboard className="h-6 w-6" />,
|
|
|
|
iconName: "lucide-layout-dashboard",
|
|
|
|
category: "acot"
|
|
|
|
category: "acot"
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{
|
|
|
|
name: "Inventory",
|
|
|
|
name: "Inventory",
|
|
|
|
description: "ACOT Inventory",
|
|
|
|
description: "ACOT Inventory",
|
|
|
|
url: "https://inventory.kent.pw",
|
|
|
|
url: "https://inventory.kent.pw",
|
|
|
|
icon: <Package className="h-6 w-6" />,
|
|
|
|
iconName: "lucide-box",
|
|
|
|
category: "acot"
|
|
|
|
category: "acot"
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{
|
|
|
|
name: "Homebridge",
|
|
|
|
name: "Homebridge",
|
|
|
|
description: "HomeKit Bridge",
|
|
|
|
description: "HomeKit Bridge",
|
|
|
|
url: "https://homebridge.kent.pw",
|
|
|
|
url: "https://homebridge.kent.pw",
|
|
|
|
icon: <Home className="h-6 w-6" />,
|
|
|
|
iconName: "homebridge",
|
|
|
|
category: "home"
|
|
|
|
category: "home"
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{
|
|
|
|
name: "Scrypted",
|
|
|
|
name: "Scrypted",
|
|
|
|
description: "Smart Home Integration",
|
|
|
|
description: "Smart Home Integration",
|
|
|
|
url: "https://scrypted.kent.pw",
|
|
|
|
url: "https://scrypted.kent.pw",
|
|
|
|
icon: <Smartphone className="h-6 w-6" />,
|
|
|
|
iconName: "scrypted",
|
|
|
|
category: "home"
|
|
|
|
category: "home"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
]
|
|
|
|
@@ -212,16 +253,16 @@ function StatusIndicator({ status, responseTime, certDaysRemaining }: {
|
|
|
|
responseTime: number,
|
|
|
|
responseTime: number,
|
|
|
|
certDaysRemaining: number
|
|
|
|
certDaysRemaining: number
|
|
|
|
}) {
|
|
|
|
}) {
|
|
|
|
const getStatusIcon = () => {
|
|
|
|
const getStatusColor = () => {
|
|
|
|
switch (status) {
|
|
|
|
switch (status) {
|
|
|
|
case 'up':
|
|
|
|
case 'up':
|
|
|
|
return <CheckCircle2 className="h-4 w-4 text-green-500" />
|
|
|
|
return 'bg-green-500'
|
|
|
|
case 'down':
|
|
|
|
case 'down':
|
|
|
|
return <XCircle className="h-4 w-4 text-red-500" />
|
|
|
|
return 'bg-red-500'
|
|
|
|
case 'pending':
|
|
|
|
case 'pending':
|
|
|
|
return <Clock className="h-4 w-4 text-yellow-500" />
|
|
|
|
return 'bg-yellow-500'
|
|
|
|
case 'maintenance':
|
|
|
|
case 'maintenance':
|
|
|
|
return <AlertCircle className="h-4 w-4 text-blue-500" />
|
|
|
|
return 'bg-blue-500'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -238,8 +279,8 @@ function StatusIndicator({ status, responseTime, certDaysRemaining }: {
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<TooltipProvider>
|
|
|
|
<TooltipProvider>
|
|
|
|
<Tooltip>
|
|
|
|
<Tooltip>
|
|
|
|
<TooltipTrigger>
|
|
|
|
<TooltipTrigger className="bg-transparent border-none px-0 pr-2 py-0">
|
|
|
|
{getStatusIcon()}
|
|
|
|
<div className={`w-3 h-3 rounded-full ${getStatusColor()}`} />
|
|
|
|
</TooltipTrigger>
|
|
|
|
</TooltipTrigger>
|
|
|
|
<TooltipContent>
|
|
|
|
<TooltipContent>
|
|
|
|
<p>{getStatusText()}</p>
|
|
|
|
<p>{getStatusText()}</p>
|
|
|
|
@@ -254,6 +295,9 @@ function ServiceSection({ title, services, metrics }: {
|
|
|
|
services: ServiceCard[],
|
|
|
|
services: ServiceCard[],
|
|
|
|
metrics: Record<string, any>
|
|
|
|
metrics: Record<string, any>
|
|
|
|
}) {
|
|
|
|
}) {
|
|
|
|
|
|
|
|
const { theme } = useTheme()
|
|
|
|
|
|
|
|
const isDark = theme === 'dark' || (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<div className="space-y-4">
|
|
|
|
<div className="space-y-4">
|
|
|
|
<div className="flex items-center">
|
|
|
|
<div className="flex items-center">
|
|
|
|
@@ -268,42 +312,40 @@ function ServiceSection({ title, services, metrics }: {
|
|
|
|
certDaysRemaining: 0
|
|
|
|
certDaysRemaining: 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Get background color based on service
|
|
|
|
|
|
|
|
const bgColor = (() => {
|
|
|
|
const bgColor = (() => {
|
|
|
|
switch (service.name) {
|
|
|
|
switch (service.name) {
|
|
|
|
case "Portainer":
|
|
|
|
case "Portainer":
|
|
|
|
return "bg-blue-50 dark:bg-blue-950/30" // Portainer blue
|
|
|
|
return "bg-blue-50 dark:bg-blue-950/30"
|
|
|
|
case "Gitea":
|
|
|
|
case "Gitea":
|
|
|
|
return "bg-green-50 dark:bg-green-950/30" // Gitea green
|
|
|
|
return "bg-green-50 dark:bg-green-950/30"
|
|
|
|
case "Plex":
|
|
|
|
case "Plex":
|
|
|
|
return "bg-orange-50 dark:bg-orange-950/30" // Plex orange
|
|
|
|
return "bg-orange-50 dark:bg-orange-950/30"
|
|
|
|
case "Sonarr":
|
|
|
|
case "Sonarr":
|
|
|
|
return "bg-blue-50 dark:bg-blue-950/30" // Sonarr blue
|
|
|
|
return "bg-blue-50 dark:bg-blue-950/30"
|
|
|
|
case "AdGuard":
|
|
|
|
case "AdGuard":
|
|
|
|
return "bg-emerald-50 dark:bg-emerald-950/30" // AdGuard green
|
|
|
|
return "bg-emerald-50 dark:bg-emerald-950/30"
|
|
|
|
case "Homebridge":
|
|
|
|
case "Homebridge":
|
|
|
|
return "bg-purple-50 dark:bg-purple-950/30" // Homebridge purple
|
|
|
|
return "bg-purple-50 dark:bg-purple-950/30"
|
|
|
|
case "Scrypted":
|
|
|
|
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 "Dashboard":
|
|
|
|
case "Inventory":
|
|
|
|
case "Inventory":
|
|
|
|
return "bg-sky-50 dark:bg-sky-950/30" // ACOT blue
|
|
|
|
return "bg-sky-50 dark:bg-sky-950/30"
|
|
|
|
case "DiskStation":
|
|
|
|
case "DiskStation":
|
|
|
|
return "bg-slate-50 dark:bg-slate-950/30" // Synology gray
|
|
|
|
return "bg-slate-50 dark:bg-slate-950/30"
|
|
|
|
case "Deluge":
|
|
|
|
case "Deluge":
|
|
|
|
return "bg-green-50 dark:bg-green-950/30" // Deluge green
|
|
|
|
return "bg-green-50 dark:bg-green-950/30"
|
|
|
|
case "Firefox":
|
|
|
|
case "Firefox":
|
|
|
|
return "bg-orange-50 dark:bg-orange-950/30" // Firefox orange
|
|
|
|
return "bg-orange-50 dark:bg-orange-950/30"
|
|
|
|
case "Uptime":
|
|
|
|
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":
|
|
|
|
case "Speedtest":
|
|
|
|
return "bg-blue-50 dark:bg-blue-950/30" // Speedtest blue
|
|
|
|
return "bg-blue-50 dark:bg-blue-950/30"
|
|
|
|
default:
|
|
|
|
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 = (() => {
|
|
|
|
const iconColor = (() => {
|
|
|
|
switch (service.name) {
|
|
|
|
switch (service.name) {
|
|
|
|
case "Portainer":
|
|
|
|
case "Portainer":
|
|
|
|
@@ -349,7 +391,9 @@ function ServiceSection({ title, services, metrics }: {
|
|
|
|
<Card className={bgColor}>
|
|
|
|
<Card className={bgColor}>
|
|
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
|
|
<div className="flex items-center">
|
|
|
|
<div className="flex items-center">
|
|
|
|
<div className={iconColor}>{service.icon}</div>
|
|
|
|
<div className={iconColor}>
|
|
|
|
|
|
|
|
<ServiceIcon service={service} isDark={isDark} />
|
|
|
|
|
|
|
|
</div>
|
|
|
|
<CardTitle className="ml-2">{service.name}</CardTitle>
|
|
|
|
<CardTitle className="ml-2">{service.name}</CardTitle>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<StatusIndicator
|
|
|
|
<StatusIndicator
|
|
|
|
@@ -369,9 +413,8 @@ function ServiceSection({ title, services, metrics }: {
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function App() {
|
|
|
|
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 systemServices = services.filter(s => s.category === 'system')
|
|
|
|
const mediaServices = services.filter(s => s.category === 'media')
|
|
|
|
const mediaServices = services.filter(s => s.category === 'media')
|
|
|
|
const monitoringServices = services.filter(s => s.category === 'monitoring')
|
|
|
|
const monitoringServices = services.filter(s => s.category === 'monitoring')
|
|
|
|
@@ -380,7 +423,7 @@ function App() {
|
|
|
|
const homeServices = services.filter(s => s.category === 'home')
|
|
|
|
const homeServices = services.filter(s => s.category === 'home')
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<div className="min-h-screen bg-gray-100 dark:bg-background p-8 space-y-8">
|
|
|
|
<div className="min-h-screen p-8 space-y-8">
|
|
|
|
<div className="flex justify-between items-center">
|
|
|
|
<div className="flex justify-between items-center">
|
|
|
|
<h1 className="text-4xl font-bold">Kent.pw Homepage</h1>
|
|
|
|
<h1 className="text-4xl font-bold">Kent.pw Homepage</h1>
|
|
|
|
<ThemeToggle />
|
|
|
|
<ThemeToggle />
|
|
|
|
@@ -391,7 +434,7 @@ function App() {
|
|
|
|
<ServiceSection title="Tools" services={toolServices} metrics={metrics} />
|
|
|
|
<ServiceSection title="Tools" services={toolServices} metrics={metrics} />
|
|
|
|
<ServiceSection title="ACOT" services={acotServices} metrics={metrics} />
|
|
|
|
<ServiceSection title="ACOT" services={acotServices} metrics={metrics} />
|
|
|
|
<ServiceSection title="Home" services={homeServices} metrics={metrics} />
|
|
|
|
<ServiceSection title="Home" services={homeServices} metrics={metrics} />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|