Initial NUC Portal dashboard

- 17 internal services with live health status
- 28 external bookmarks organized by category
- Dark/light mode toggle with persistence
- Cmd+K search filtering
- Health API polling every 30 seconds

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-02-01 22:52:38 +00:00
commit 1a7a0ed4d3
25 changed files with 7675 additions and 0 deletions

View File

@@ -0,0 +1,67 @@
'use client';
import { Service } from '@/lib/services';
import { HealthStatus } from '@/lib/PortalContext';
import { Icon } from './Icons';
interface ServiceCardProps {
service: Service;
status: HealthStatus;
}
const statusColors: Record<HealthStatus, string> = {
running: 'bg-emerald-500',
stopped: 'bg-red-500',
unknown: 'bg-slate-400 dark:bg-stone-500',
loading: 'bg-amber-500 animate-pulse',
};
const statusLabels: Record<HealthStatus, string> = {
running: 'Running',
stopped: 'Stopped',
unknown: 'Unknown',
loading: 'Checking...',
};
export function ServiceCard({ service, status }: ServiceCardProps) {
return (
<a
href={service.url}
target="_blank"
rel="noopener noreferrer"
className="group relative block p-4 bg-white dark:bg-stone-900 rounded-xl border border-slate-200 dark:border-stone-800 hover:border-slate-300 dark:hover:border-stone-700 hover:shadow-lg transition-all duration-200"
>
{/* Status indicator */}
<div className="absolute top-3 right-3 flex items-center gap-1.5">
<span
className={`w-2 h-2 rounded-full ${statusColors[status]}`}
title={statusLabels[status]}
/>
</div>
{/* Icon */}
<div className="w-10 h-10 flex items-center justify-center rounded-lg bg-slate-100 dark:bg-stone-800 mb-3 group-hover:bg-slate-200 dark:group-hover:bg-stone-700 transition-colors">
<Icon
name={service.icon}
size={20}
className="text-slate-600 dark:text-stone-400"
/>
</div>
{/* Content */}
<h3 className="font-medium text-slate-900 dark:text-stone-100 mb-1">
{service.name}
</h3>
{service.description && (
<p className="text-sm text-slate-500 dark:text-stone-500 line-clamp-2">
{service.description}
</p>
)}
{/* Port badge */}
<div className="mt-3 text-xs text-slate-400 dark:text-stone-600 font-mono">
:{service.port}
</div>
</a>
);
}