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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user