health panel implementation
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user