+
{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' },