diff --git a/src/components/OverviewTab.tsx b/src/components/OverviewTab.tsx index bd90ff1..cf66d27 100644 --- a/src/components/OverviewTab.tsx +++ b/src/components/OverviewTab.tsx @@ -6,6 +6,8 @@ import { SystemTrends } from './SystemTrends'; import { formatUptime } from '@/lib/stats'; import { STATUS_COLORS, STATUS_LABELS, formatRelativeTime, formatDuration } from '@/lib/deployments'; import type { DeploymentStatus } from '@/lib/deployments'; +import type { HealthStatus } from '@/lib/PortalContext'; +import type { Service } from '@/lib/services'; const quickLinks = [ { name: 'Coolify', url: 'http://192.168.1.3:8000', icon: 'coolify', desc: 'Service manager' }, @@ -16,12 +18,67 @@ const quickLinks = [ { name: 'Adminer', url: 'http://192.168.1.3:8088', icon: 'database', desc: 'DB admin' }, ]; -const whyratingApps = [ - { name: 'WhyRating Hub', url: 'http://whyrating.nuc.lan', icon: 'whyrating' }, - { name: 'Brand Site', url: 'http://brand.nuc.lan', icon: 'whyrating' }, - { name: 'Templates', url: 'http://templates.nuc.lan', icon: 'whyrating' }, +interface ProjectDef { + name: string; + icon: string; + apps: { name: string; url: string }[]; +} + +const projects: ProjectDef[] = [ + { + name: 'WhyRating', + icon: 'whyrating', + apps: [ + { name: 'Hub', url: 'http://whyrating.nuc.lan' }, + { name: 'Brand', url: 'http://brand.nuc.lan' }, + { name: 'Templates', url: 'http://templates.nuc.lan' }, + ], + }, + { + name: 'Knosia', + icon: 'book-open', + apps: [ + { name: 'App', url: 'http://knosia.nuc.lan' }, + ], + }, ]; +function ProjectCard({ project, services, healthStatus }: { + project: ProjectDef; + services: Service[]; + healthStatus: Record; +}) { + return ( +
+

+ + {project.name} +

+
+ {project.apps.map(app => { + const svc = services.find(s => s.url === app.url); + const status = svc ? healthStatus[svc.name] : undefined; + const dot = status === 'running' ? 'bg-emerald-500' : status === 'stopped' ? 'bg-red-500' : 'bg-slate-300 dark:bg-stone-700'; + + return ( + + + {app.name} + + + ); + })} +
+
+ ); +} + export function OverviewTab() { const { systemStats, @@ -40,117 +97,138 @@ export function OverviewTab() { const isDiscovered = discoveredServices.length > 0; return ( -
+
- {/* Row 1: System Trends (full-width hero) with live stats in header */} + {/* Row 1: System Trends (full-width hero) */} - {/* Row 2 left: Services */} -
-

- - Services - {isDiscovered && ( - - Auto-discovered - - )} -

+ {/* Row 2: Services | Deployments */} +
+ {/* Services */} +
+

+ + Services + {isDiscovered && ( + + Auto-discovered + + )} +

-
- {runningCount} - of {totalCount} running -
- -
-
- - {runningCount} running +
+ {runningCount} + of {totalCount} running
- {stoppedCount > 0 && ( + +
- - {stoppedCount} stopped + + {runningCount} running
- )} - {totalCount - runningCount - stoppedCount > 0 && ( -
- - {totalCount - runningCount - stoppedCount} unknown -
- )} -
- -
- {services.map(s => { - const status = healthStatus[s.name]; - const color = status === 'running' ? 'bg-emerald-500' : status === 'stopped' ? 'bg-red-500' : 'bg-slate-400'; - return ( -
- ); - })} -
-
- - {/* Row 2 right: Recent Deployments */} -
-

- - Recent Deployments -

- - {deploymentsLoading && deployments.length === 0 ? ( -
- {[1, 2, 3].map(i => ( -
-
-
-
+ {stoppedCount > 0 && ( +
+ + {stoppedCount} stopped
- ))} + )} + {totalCount - runningCount - stoppedCount > 0 && ( +
+ + {totalCount - runningCount - stoppedCount} unknown +
+ )}
- ) : recentDeployments.length > 0 ? ( -
- {recentDeployments.map(d => ( -
- - - {d.application_name} - - - {STATUS_LABELS[d.status as DeploymentStatus] || d.status} - - {d.duration != null && d.duration > 0 && ( - - {formatDuration(d.duration)} + +
+ {services.map(s => { + const status = healthStatus[s.name]; + const color = status === 'running' ? 'bg-emerald-500' : status === 'stopped' ? 'bg-red-500' : 'bg-slate-400'; + return ( +
+ ); + })} +
+
+ + {/* Recent Deployments */} +
+

+ + Recent Deployments +

+ + {deploymentsLoading && deployments.length === 0 ? ( +
+ {[1, 2, 3].map(i => ( +
+
+
+
+
+ ))} +
+ ) : recentDeployments.length > 0 ? ( +
+ {recentDeployments.map(d => ( +
+ + + {d.application_name} - )} - - {formatRelativeTime(d.created_at)} - -
- ))} -
- ) : ( -

No deployments yet

- )} + + {STATUS_LABELS[d.status as DeploymentStatus] || d.status} + + {d.duration != null && d.duration > 0 && ( + + {formatDuration(d.duration)} + + )} + + {formatRelativeTime(d.created_at)} + +
+ ))} +
+ ) : ( +

No deployments yet

+ )} +
- {/* Row 3 left: Quick Links */} + {/* Row 3: Projects (side by side, scales with more projects) */} +
+

+ + Projects +

+
+ {projects.map(project => ( + + ))} +
+
+ + {/* Row 4: Quick Links */}

Quick Links

- ); } diff --git a/src/components/SystemTrends.tsx b/src/components/SystemTrends.tsx index a07d04f..5ba886f 100644 --- a/src/components/SystemTrends.tsx +++ b/src/components/SystemTrends.tsx @@ -136,7 +136,7 @@ export function SystemTrends({ uptimeLabel, loadAvg }: SystemTrendsProps) { if (error && !data) return null; return ( -
+
{/* Header with title, live stats, and Grafana link */}