- Add services/{http,chain138,explorer,proxmox,dbisCore} + hooks/{useLiveChain,useOnChainBalances}
- Add BackendStatusBar + LiveNetworkPanel components on DashboardPage
- Overlay on-chain META balance on account rows carrying a walletAddress
- Normalize EIP-55 checksum in chain138.getNativeBalance so hand-typed
sample custody addresses (e.g. 0x742d35Cc...bD38) don't silently drop
out of the balance map
- Default RPC: https://rpc.d-bis.org (user-preferred gateway)
- proxmox.ts stays mocked (CF-Access, needs BFF); dbisCore.ts stays
mocked (no public deployment yet)
Co-Authored-By: Nakamoto, S <defi@defi-oracle.io>
53 lines
2.4 KiB
TypeScript
53 lines
2.4 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { Circle, AlertCircle, CheckCircle2, MinusCircle } from 'lucide-react';
|
|
import { backendCatalog, type BackendDescriptor, type BackendStatus } from '../../config/endpoints';
|
|
import { getChainHealth } from '../../services/chain138';
|
|
import { getExplorerStats } from '../../services/explorer';
|
|
|
|
const STATUS_STYLE: Record<BackendStatus, { color: string; label: string; Icon: typeof CheckCircle2 }> = {
|
|
live: { color: '#22c55e', label: 'Live', Icon: CheckCircle2 },
|
|
'bff-required': { color: '#eab308', label: 'BFF required', Icon: AlertCircle },
|
|
mocked: { color: '#6b7280', label: 'Mocked', Icon: MinusCircle },
|
|
degraded: { color: '#ef4444', label: 'Degraded', Icon: AlertCircle },
|
|
};
|
|
|
|
export default function BackendStatusBar() {
|
|
const [probed, setProbed] = useState<Record<string, BackendStatus>>({});
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
(async () => {
|
|
const results = await Promise.allSettled([
|
|
getChainHealth().then(() => 'live' as const),
|
|
getExplorerStats().then(() => 'live' as const),
|
|
]);
|
|
if (cancelled) return;
|
|
setProbed({
|
|
chain138: results[0].status === 'fulfilled' ? 'live' : 'degraded',
|
|
explorer: results[1].status === 'fulfilled' ? 'live' : 'degraded',
|
|
});
|
|
})();
|
|
return () => { cancelled = true; };
|
|
}, []);
|
|
|
|
const withProbed = (b: BackendDescriptor): BackendDescriptor => ({
|
|
...b,
|
|
status: probed[b.id] ?? b.status,
|
|
});
|
|
|
|
return (
|
|
<div className="backend-status-bar" style={{ display: 'flex', gap: 10, flexWrap: 'wrap', padding: '6px 12px', background: 'rgba(17,24,39,0.6)', borderRadius: 8, border: '1px solid rgba(75,85,99,0.3)', alignItems: 'center', fontSize: 11 }}>
|
|
<Circle size={8} style={{ color: '#9ca3af', fill: '#9ca3af' }} />
|
|
<span style={{ color: '#9ca3af', fontWeight: 600, textTransform: 'uppercase', letterSpacing: 0.5 }}>Backends</span>
|
|
{backendCatalog.map(withProbed).map(b => {
|
|
const s = STATUS_STYLE[b.status];
|
|
return (
|
|
<span key={b.id} title={`${b.name} — ${b.note}\n${b.url}`} style={{ display: 'inline-flex', alignItems: 'center', gap: 4, padding: '2px 8px', borderRadius: 12, background: `${s.color}18`, color: s.color, cursor: 'help' }}>
|
|
<s.Icon size={11} /> {b.name} <span style={{ opacity: 0.7 }}>· {s.label}</span>
|
|
</span>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|