Add fleet ops dashboard, Calibrate enforcement, and dead-code cleanup.

Ship live alerts, pool status, AI monitor, remote agent commands, build manager, and uninstall flow; wire Calibrate settings (WS ping, pool traffic log, retention limits) at runtime and exclude server/data from git.
This commit is contained in:
drjones
2026-05-27 09:16:04 -07:00
parent 9d223b8137
commit df81eb7744
75 changed files with 8891 additions and 966 deletions

View File

@@ -1,25 +1,63 @@
import { useState, useEffect } from 'react';
import { api } from '../api/client';
import { useWebSocket } from '../hooks/useWebSocket';
import type { Agent, HashrateSample } from '../types';
import HashrateChart from '../components/Charts/HashrateChart';
import NeonCard from '../components/NeonCard/NeonCard';
import AgentRemoteActions from '../components/Fleet/AgentRemoteActions';
import '../components/Fleet/FleetPanels.css';
import '../components/Fleet/AgentRemoteActions.css';
import './Pages.css';
export default function AgentsPage() {
const { agents: liveAgents, isConnected, agentLogs } = useWebSocket();
const [agents, setAgents] = useState<Agent[]>([]);
const [selectedAgent, setSelectedAgent] = useState<Agent | null>(null);
const [hashrateHistory, setHashrateHistory] = useState<HashrateSample[]>([]);
const [loading, setLoading] = useState(true);
const [loadError, setLoadError] = useState('');
const [logContent, setLogContent] = useState('');
const [logLoading, setLogLoading] = useState(false);
useEffect(() => {
api.listAgents()
.then(setAgents)
.catch(console.error)
.catch((err) => setLoadError(err instanceof Error ? err.message : 'Failed to load agents'))
.finally(() => setLoading(false));
}, []);
useEffect(() => {
if (isConnected) {
setAgents(liveAgents);
if (selectedAgent) {
const updated = liveAgents.find((a) => a.id === selectedAgent.id);
if (updated) setSelectedAgent(updated);
}
}
}, [liveAgents, isConnected, selectedAgent?.id]);
useEffect(() => {
if (selectedAgent && agentLogs[selectedAgent.id]) {
setLogContent(agentLogs[selectedAgent.id]);
}
}, [selectedAgent?.id, agentLogs]);
const refreshLog = async (refresh = false) => {
if (!selectedAgent) return;
setLogLoading(true);
try {
const res = await api.getAgentLog(selectedAgent.id, refresh);
setLogContent(res.content || '');
} catch (err) {
setLogContent(err instanceof Error ? err.message : 'Failed to load log');
} finally {
setLogLoading(false);
}
};
const selectAgent = async (agent: Agent) => {
setSelectedAgent(agent);
setLogContent('');
try {
const history = await api.getAgentStats(agent.id, 60);
setHashrateHistory(history);
@@ -39,6 +77,12 @@ export default function AgentsPage() {
<span className="header-count font-tech">{agents.length} NODES</span>
</header>
{loadError && (
<NeonCard accent="amber" className="empty-state">
<p>{loadError}</p>
</NeonCard>
)}
{loading ? (
<NeonCard accent="brass" className="empty-state">
<p>Scanning network...</p>
@@ -76,6 +120,7 @@ export default function AgentsPage() {
<span>v{agent.version || '?'}</span>
<span>{agent.cpu_cores} cores</span>
</div>
<AgentRemoteActions agent={agent} compact />
</div>
))}
</div>
@@ -171,6 +216,22 @@ export default function AgentsPage() {
/>
</div>
)}
<div className="detail-section">
<h3>Remote Control</h3>
<AgentRemoteActions
agent={selectedAgent}
onCommandSent={(action) => {
if (action === 'get_log') refreshLog(true);
}}
/>
</div>
<div className="detail-section">
<h3>Agent Log <button type="button" className="agent-action-btn" onClick={() => refreshLog(true)} disabled={logLoading}>{logLoading ? '…' : 'Refresh'}</button></h3>
<p className="form-hint">Streams miner.log when file_logging is enabled (non-stealth builds).</p>
<pre className="log-viewer">{logContent || (selectedAgent.status === 'online' ? 'Click Fetch Log or Refresh' : 'Agent offline')}</pre>
</div>
</NeonCard>
)}
</div>