health panel implementation

This commit is contained in:
Storme-bit
2026-04-17 23:35:31 -07:00
parent 8a5caf7399
commit 072758df9c

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useCallback } from 'react';
import { useSettings } from '../hooks/useSettings';
import { getServiceHealth } from '../api/orchestration';
export default function SettingsView({ onNavigate }) {
const { settings, saveSetting, saving } = useSettings();
@@ -52,8 +53,16 @@ export default function SettingsView({ onNavigate }) {
</SettingsSection>
<SettingsSection title="About">
<SettingsRow label="Service Health" description="Ping all four services" action={<ComingSoon />} />
<SettingsRow label="Version" description="NexusAI" action={<span className="text-sm text-muted">v0.1.0</span>} />
<SettingsRow
label="Service Health"
description="Ping all four services"
action={<ServiceHealth />}
/>
<SettingsRow
label="Version"
description="NexusAI"
action={<span className="text-sm text-muted">v0.1.0</span>}
/>
</SettingsSection>
<SettingsSection title="Appearance">
@@ -149,4 +158,83 @@ function SettingsRow({ label, description, action }) {
function ComingSoon() {
return <span className="text-xs text-muted" style={{ fontStyle: 'italic' }}>Coming soon</span>;
}
function ServiceHealth() {
const [services, setServices] = useState(null);
const [loading, setLoading] = useState(false);
const [lastChecked, setLastChecked] = useState(null);
const check = useCallback(async () => {
setLoading(true);
try {
setServices(await getServiceHealth());
setLastChecked(new Date());
} catch (err) {
console.error('[ServiceHealth]', err.message);
} finally {
setLoading(false);
}
}, []);
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<button
className="btn-primary"
style={{ padding: '5px 12px', fontSize: '12px' }}
disabled={loading}
onClick={check}
>
{loading ? 'Checking…' : 'Check Now'}
</button>
{lastChecked && (
<span className="text-xs text-muted">
{lastChecked.toLocaleTimeString()}
</span>
)}
</div>
{services && (
<div style={{
display: 'flex', flexDirection: 'column',
border: '1px solid var(--border)',
borderRadius: 'var(--radius-md)',
overflow: 'hidden', marginTop: 4,
}}>
{services.map((svc, i) => (
<div key={svc.key} style={{
display: 'flex', alignItems: 'center', gap: 10,
padding: '8px 12px',
borderBottom: i < services.length - 1 ? '1px solid var(--border)' : 'none',
background: 'var(--bg-elevated)',
}}>
{/* Status dot */}
<div style={{
width: 8, height: 8, borderRadius: '50%', flexShrink: 0,
background: svc.status === 'healthy' ? '#2ecc71' : '#e74c3c',
}} />
{/* Label */}
<span className="text-sm" style={{ minWidth: 90, color: 'var(--text-primary)' }}>
{svc.label}
</span>
{/* Detail — show model for inference, nothing extra for others */}
<span className="text-xs text-muted" style={{ flex: 1 }}>
{svc.key === 'inference' && svc.detail?.model
? svc.detail.model
: svc.status === 'unreachable' ? 'Unreachable' : ''}
</span>
{/* Latency */}
<span className="text-xs text-muted" style={{ flexShrink: 0 }}>
{svc.latency}ms
</span>
</div>
))}
</div>
)}
</div>
);
}