Add WhyOps service and show health status for all services
- Add WhyOps (whyops.nuc.lan:3002) to service catalog and registry - Show status pill for static (non-Coolify) services too - Merge static services into discovered list so they always appear - Health-check static services via /api/health endpoint Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -144,7 +144,6 @@ export function ServiceCard({ service, status }: ServiceCardProps) {
|
|||||||
{/* Footer: status + links + controls */}
|
{/* Footer: status + links + controls */}
|
||||||
<div className="mt-3 pt-3 border-t border-slate-100 dark:border-stone-800 flex items-center justify-between">
|
<div className="mt-3 pt-3 border-t border-slate-100 dark:border-stone-800 flex items-center justify-between">
|
||||||
{/* Left: status pill */}
|
{/* Left: status pill */}
|
||||||
{discovered ? (
|
|
||||||
<span className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-[11px] font-medium ${statusPillStyles[status]}`}>
|
<span className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-[11px] font-medium ${statusPillStyles[status]}`}>
|
||||||
<Icon
|
<Icon
|
||||||
name={loading ? 'loader' : statusIcons[status]}
|
name={loading ? 'loader' : statusIcons[status]}
|
||||||
@@ -153,9 +152,6 @@ export function ServiceCard({ service, status }: ServiceCardProps) {
|
|||||||
/>
|
/>
|
||||||
{loading ? 'Processing...' : statusLabels[status]}
|
{loading ? 'Processing...' : statusLabels[status]}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
|
||||||
<span />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Right: links + action buttons */}
|
{/* Right: links + action buttons */}
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
|
|||||||
@@ -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<HealthState>({});
|
||||||
|
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
|
const activeServices: Service[] = discoveredServices.length > 0
|
||||||
? discoveredServices
|
? [
|
||||||
|
...discoveredServices,
|
||||||
|
...fallbackServices.filter(fb =>
|
||||||
|
!discoveredServices.some(d => d.name === fb.name || d.port === fb.port)
|
||||||
|
),
|
||||||
|
]
|
||||||
: fallbackServices;
|
: fallbackServices;
|
||||||
|
|
||||||
// Filter services and bookmarks
|
// Filter services and bookmarks
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ const registry: Record<string, ServiceMeta> = {
|
|||||||
|
|
||||||
// Apps
|
// Apps
|
||||||
'googlescraper': { icon: 'search', category: 'automation', description: 'Google scraper API' },
|
'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' },
|
'actionkit landing': { icon: 'layout', category: 'development', description: 'Landing page builder' },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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: '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: '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: '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
|
// Knowledge - prefer domain-based URLs
|
||||||
{ name: 'Outline', url: 'http://outline.nuc.lan', port: 3080, icon: 'book-open', category: 'knowledge', description: 'Team wiki & documentation' },
|
{ name: 'Outline', url: 'http://outline.nuc.lan', port: 3080, icon: 'book-open', category: 'knowledge', description: 'Team wiki & documentation' },
|
||||||
|
|||||||
Reference in New Issue
Block a user