diff --git a/src/components/ServiceCard.tsx b/src/components/ServiceCard.tsx index a505f28..8f35ea5 100644 --- a/src/components/ServiceCard.tsx +++ b/src/components/ServiceCard.tsx @@ -144,8 +144,7 @@ export function ServiceCard({ service, status }: ServiceCardProps) { {/* Footer: status + links + controls */}
{/* Left: status pill */} - {discovered ? ( - + {loading ? 'Processing...' : statusLabels[status]} - ) : ( - - )} {/* Right: links + action buttons */}
diff --git a/src/lib/PortalContext.tsx b/src/lib/PortalContext.tsx index ac453e6..4fd4cc5 100644 --- a/src/lib/PortalContext.tsx +++ b/src/lib/PortalContext.tsx @@ -93,9 +93,43 @@ export function PortalProvider({ children }: { children: ReactNode }) { } } - // Active services: discovered or fallback + // Health-check static (non-discovered) services via /api/health + const [staticHealth, setStaticHealth] = useState({}); + useEffect(() => { + if (discoveredServices.length === 0) return; + const statics = fallbackServices.filter(fb => + !discoveredServices.some(d => d.name === fb.name || d.port === fb.port) + ); + if (statics.length === 0) return; + let cancelled = false; + async function check() { + try { + const res = await fetch('/api/health'); + if (!res.ok) return; + const data = await res.json(); + if (!cancelled) setStaticHealth(data); + } catch { /* ignore */ } + } + check(); + const interval = setInterval(check, 30000); + return () => { cancelled = true; clearInterval(interval); }; + }, [discoveredServices.length]); + + // Merge static health into healthStatus + for (const [name, status] of Object.entries(staticHealth)) { + if (!healthStatus[name]) { + healthStatus[name] = status; + } + } + + // Active services: discovered + any fallback services not already discovered const activeServices: Service[] = discoveredServices.length > 0 - ? discoveredServices + ? [ + ...discoveredServices, + ...fallbackServices.filter(fb => + !discoveredServices.some(d => d.name === fb.name || d.port === fb.port) + ), + ] : fallbackServices; // Filter services and bookmarks diff --git a/src/lib/service-registry.ts b/src/lib/service-registry.ts index b285f8d..1016358 100644 --- a/src/lib/service-registry.ts +++ b/src/lib/service-registry.ts @@ -66,6 +66,8 @@ const registry: Record = { // Apps 'googlescraper': { icon: 'search', category: 'automation', description: 'Google scraper API' }, + 'whyops': { icon: 'settings', category: 'automation', description: 'WhyRating scraping, pipelines & testing' }, + 'whyrating-dashboard': { icon: 'settings', category: 'automation', description: 'WhyRating scraping, pipelines & testing' }, 'actionkit landing': { icon: 'layout', category: 'development', description: 'Landing page builder' }, }; diff --git a/src/lib/services.ts b/src/lib/services.ts index 4cc6ef2..824c417 100644 --- a/src/lib/services.ts +++ b/src/lib/services.ts @@ -60,6 +60,7 @@ export const fallbackServices: Service[] = [ { name: 'Gitea', url: 'http://gitea.nuc.lan', port: 3030, icon: 'git-branch', category: 'development', description: 'Self-hosted Git service' }, { name: 'CloudBeaver', url: `http://${h}:8978`, port: 8978, icon: 'database', category: 'development', description: 'Database management UI' }, { name: 'Adminer', url: `http://${h}:8088`, port: 8088, icon: 'table', category: 'development', description: 'Lightweight database admin' }, + { name: 'WhyOps', url: 'http://whyops.nuc.lan', port: 3002, icon: 'settings', category: 'automation', description: 'WhyRating scraping, pipelines & testing' }, // Knowledge - prefer domain-based URLs { name: 'Outline', url: 'http://outline.nuc.lan', port: 3080, icon: 'book-open', category: 'knowledge', description: 'Team wiki & documentation' },