Add fleet resilience, passive spread, matrix rain UI, and live earnings.
Backup server URL failover, watchdog process restart, service masquerade, remote fleet upgrade, recon UI, SupportXMR earnings, USB/share passive spread, and sidebar matrix rain with live fleet telemetry.
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { ReactNode, useEffect, useRef, useState } from 'react';
|
||||
import { NavLink, useLocation } from 'react-router-dom';
|
||||
import AmbientBackground from '../Ambient/AmbientBackground';
|
||||
import SystemStatusBar from '../Visual/SystemStatusBar';
|
||||
import { useWebSocket } from '../../hooks/useWebSocket';
|
||||
import MatrixRain from './MatrixRain';
|
||||
import './Layout.css';
|
||||
|
||||
interface LayoutProps {
|
||||
@@ -57,6 +59,60 @@ function NavIcon({ type }: { type: string }) {
|
||||
}
|
||||
}
|
||||
|
||||
function formatHashrate(hs: number): string {
|
||||
if (hs >= 1_000_000) return `${(hs / 1_000_000).toFixed(2)} MH/s`;
|
||||
if (hs >= 1_000) return `${(hs / 1_000).toFixed(1)} KH/s`;
|
||||
return `${hs.toFixed(0)} H/s`;
|
||||
}
|
||||
|
||||
function FleetReadout() {
|
||||
const { agents } = useWebSocket();
|
||||
const online = agents.filter((a) => a.status === 'online').length;
|
||||
const total = agents.length;
|
||||
const totalHashrate = agents.reduce((sum, a) => sum + (a.hashrate_15s ?? a.hashrate_15m ?? 0), 0);
|
||||
const fillPct = total > 0 ? Math.round((online / total) * 100) : 0;
|
||||
|
||||
// Tick animation: flash hashrate value whenever it meaningfully changes
|
||||
const [flash, setFlash] = useState(false);
|
||||
const prevHash = useRef(0);
|
||||
useEffect(() => {
|
||||
if (Math.abs(totalHashrate - prevHash.current) > 1) {
|
||||
prevHash.current = totalHashrate;
|
||||
setFlash(true);
|
||||
const t = setTimeout(() => setFlash(false), 600);
|
||||
return () => clearTimeout(t);
|
||||
}
|
||||
}, [totalHashrate]);
|
||||
|
||||
return (
|
||||
<div className="fleet-readout">
|
||||
<div className="readout-row">
|
||||
<span className={`readout-dot ${online > 0 ? 'readout-dot-online' : 'readout-dot-idle'}`} />
|
||||
<span className="readout-label">MACHINES</span>
|
||||
<span className="readout-value readout-value-machines">
|
||||
{online}<span className="readout-dim">/{total}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div className="readout-row">
|
||||
<span className="readout-glyph">≈</span>
|
||||
<span className="readout-label">HASHRATE</span>
|
||||
<span className={`readout-value ${flash ? 'readout-flash' : ''}`}>
|
||||
{totalHashrate > 0 ? formatHashrate(totalHashrate) : <span className="readout-dim">IDLE</span>}
|
||||
</span>
|
||||
</div>
|
||||
<div className="readout-bar-wrap" title={`${online} of ${total} online`}>
|
||||
<div className="readout-bar-track">
|
||||
<div
|
||||
className={`readout-bar-fill ${online > 0 ? 'readout-bar-active' : ''}`}
|
||||
style={{ width: `${fillPct}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="readout-bar-pct">{fillPct}%</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Layout({ children }: LayoutProps) {
|
||||
const location = useLocation();
|
||||
|
||||
@@ -93,14 +149,15 @@ export default function Layout({ children }: LayoutProps) {
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Matrix rain log — fills the lower sidebar between nav and footer */}
|
||||
<MatrixRain />
|
||||
|
||||
<div className="sidebar-footer">
|
||||
<div className="power-meter">
|
||||
<span className="power-label font-tech">SYSTEM</span>
|
||||
<div className="power-bar">
|
||||
<div className="power-fill" />
|
||||
</div>
|
||||
<FleetReadout />
|
||||
<div className="sidebar-sig font-tech">
|
||||
<span className="sig-love">made with <span className="sig-heart">♥</span> drjones</span>
|
||||
<span className="sig-ver">v0.0.1</span>
|
||||
</div>
|
||||
<div className="version-badge font-tech">MK.I · v1.0</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user