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} from "lucide-react"
import * as LucideIcons from "lucide-react"
import { useTheme } from "@/components/theme-provider"
import { useMetrics } from "./hooks/useMetrics"
import { useConfig } from "./hooks/useConfig"
import { ServiceStatus } from "./types/metrics"
import { ServiceCard, Section, CardStyle } from "./types/services"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { AlertCircle } from "lucide-react"
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, section, isDark }: { service: ServiceCard, section: Section, 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)
}
}}
/>
)
}
// Use section's default icon
const Icon = (LucideIcons as any)[section.icon] || LucideIcons.Box
return
}
function ThemeToggle() {
const { theme, setTheme } = useTheme()
const cycleTheme = () => {
if (theme === 'light') setTheme('dark')
else if (theme === 'dark') setTheme('system')
else setTheme('light')
}
return (
)
}
function StatusIndicator({ status, responseTime, certDaysRemaining }: {
status: ServiceStatus,
responseTime: number,
certDaysRemaining: number
}) {
const getStatusColor = () => {
switch (status) {
case 'up':
return 'bg-green-500'
case 'down':
return 'bg-red-500'
case 'pending':
return 'bg-yellow-500'
case 'maintenance':
return 'bg-blue-500'
}
}
const getStatusText = () => {
const statusText = status.charAt(0).toUpperCase() + status.slice(1)
const responseTimeText = `Response time: ${responseTime}ms`
const certText = certDaysRemaining > 0
? `SSL cert expires in ${certDaysRemaining} days`
: 'SSL cert expired'
return `${statusText} • ${responseTimeText} • ${certText}`
}
return (
{getStatusText()}
)
}
function ServiceSection({ section, services, metrics }: {
section: Section,
services: ServiceCard[],
metrics: Record
}) {
const { theme } = useTheme()
const isDark = theme === 'dark' || (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)
return (
{section.title}
{services.map((service) => {
const serviceMetrics = metrics[service.monitorName || service.name] || {
status: 'pending',
responseTime: 0,
certDaysRemaining: 0
}
const style: CardStyle = {
background: service.cardStyle?.background || section.cardStyle?.background || "bg-gray-50 dark:bg-gray-950/30",
iconColor: service.cardStyle?.iconColor || section.cardStyle?.iconColor || "text-gray-600 dark:text-gray-400"
}
return (
{service.description}
)
})}
)
}
function App() {
const { metrics } = useMetrics()
const { config, error } = useConfig()
const { services, sections } = config
return (
Kent.pw Homepage
{error && (
Error
Failed to load configuration: {error}
)}
{sections?.length > 0 ? (
<>
{sections.map((section: Section) => {
const sectionServices = services.filter((s: { category: string }) => s.category === section.id)
if (sectionServices.length === 0) return null
return (
)
})}
>
) : !error && (
Loading
Loading service configuration...
)}
)
}
export default App