import { useState, useEffect, useMemo, useCallback } from 'react'; import { api } from '../api/client'; import { useWebSocket } from '../hooks/useWebSocket'; import type { Agent, HashrateSample, ServerInfo } from '../types'; import HashrateChart from '../components/Charts/HashrateChart'; import NeonCard from '../components/NeonCard/NeonCard'; import AgentRemoteActions from '../components/Fleet/AgentRemoteActions'; import AgentListItem from '../components/Fleet/AgentListItem'; import FleetToolbar from '../components/Fleet/FleetToolbar'; import { DEFAULT_FLEET_FILTERS, filterFleetAgents, agentIsIdleMiner, formatHashrate, formatUptime, } from '../help/fleetFilters'; import type { FleetFilterState } from '../help/fleetFilters'; import '../components/Fleet/FleetToolbar.css'; import './Pages.css'; function QuickDeployPanel({ serverInfo }: { serverInfo: ServerInfo | null }) { const [copied, setCopied] = useState(null); const base = serverInfo?.suggested_url?.replace(/\/$/, '') ?? window.location.origin; const copy = (text: string, key: string) => { navigator.clipboard.writeText(text).then(() => { setCopied(key); setTimeout(() => setCopied(null), 2000); }); }; const ps1 = `iex (irm '${base}/install.ps1')`; const sh = `curl -sL ${base}/install.sh | bash`; const dlWin = `${base}/get?os=windows`; const dlLin = `${base}/get?os=linux`; const dlMac = `${base}/get?os=darwin`; const Row = ({ label, cmd, id }: { label: string; cmd: string; id: string }) => (
{label} {cmd}
); return (
One-liner Quick Deploy

Run any of these commands on a remote machine — the agent downloads itself and connects back automatically. No files to transfer manually.

Install & run (auto-launches)
Direct download only (saves file)
); } export default function AgentsPage() { const { agents: liveAgents, isConnected, agentLogs, commandResults } = useWebSocket(); const [agents, setAgents] = useState([]); const [selectedAgent, setSelectedAgent] = useState(null); const [expandedId, setExpandedId] = useState(null); const [selectedIds, setSelectedIds] = useState>(new Set()); const [filters, setFilters] = useState(DEFAULT_FLEET_FILTERS); const [bulkBusy, setBulkBusy] = useState(false); const [hashrateHistory, setHashrateHistory] = useState([]); const [loading, setLoading] = useState(true); const [serverInfo, setServerInfo] = useState(null); const [loadError, setLoadError] = useState(''); const [logContent, setLogContent] = useState(''); const [logLoading, setLogLoading] = useState(false); const [notesDraft, setNotesDraft] = useState(''); const [tagsDraft, setTagsDraft] = useState(''); const [metaSaving, setMetaSaving] = useState(false); const [metaMsg, setMetaMsg] = useState(''); useEffect(() => { api.listAgents() .then(setAgents) .catch((err) => setLoadError(err instanceof Error ? err.message : 'Failed to load agents')) .finally(() => setLoading(false)); api.getServerInfo().then(setServerInfo).catch(() => {}); }, []); useEffect(() => { if (!selectedAgent) return; setNotesDraft(selectedAgent.notes || ''); setTagsDraft((selectedAgent.tags || []).join(', ')); }, [selectedAgent?.id]); useEffect(() => { if (!isConnected) return; setAgents(liveAgents); if (!selectedAgent) return; const updated = liveAgents.find((a) => a.id === selectedAgent.id); if (updated) { setSelectedAgent(updated); } else { setSelectedAgent(null); setLogContent(''); } }, [liveAgents, isConnected, selectedAgent?.id]); useEffect(() => { if (selectedAgent && agentLogs[selectedAgent.id]) { setLogContent(agentLogs[selectedAgent.id]); } }, [selectedAgent?.id, agentLogs]); const filteredAgents = useMemo( () => filterFleetAgents(agents, filters), [agents, filters] ); 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); setNotesDraft(agent.notes || ''); setTagsDraft((agent.tags || []).join(', ')); setMetaMsg(''); setLogContent(''); try { const history = await api.getAgentStats(agent.id, 60); setHashrateHistory(history); } catch (err) { console.error(err); } }; const saveMeta = async () => { if (!selectedAgent) return; setMetaSaving(true); setMetaMsg(''); const tags = tagsDraft.split(',').map((t) => t.trim()).filter(Boolean); try { const res = await api.updateAgentMeta(selectedAgent.id, notesDraft, tags); const updated = res.agent; setAgents((prev) => prev.map((a) => (a.id === updated.id ? { ...a, ...updated } : a))); setSelectedAgent((prev) => (prev?.id === updated.id ? { ...prev, ...updated } : prev)); setMetaMsg('Saved'); setTimeout(() => setMetaMsg(''), 2000); } catch (err) { setMetaMsg(err instanceof Error ? err.message : 'Save failed'); } finally { setMetaSaving(false); } }; const toggleSelect = useCallback((id: string, on: boolean) => { setSelectedIds((prev) => { const next = new Set(prev); if (on) next.add(id); else next.delete(id); return next; }); }, []); const handleBulkAction = async (action: string) => { const ids = [...selectedIds]; if (ids.length === 0) return; let targetIds = ids; if (action === 'restart_idle') { targetIds = agents.filter((a) => ids.includes(a.id) && agentIsIdleMiner(a)).map((a) => a.id); if (targetIds.length === 0) { alert('No selected online agents with idle hashrate (< 100 H/s).'); return; } action = 'restart'; } const onlineIds = targetIds.filter((id) => agents.find((a) => a.id === id)?.status === 'online'); if (onlineIds.length === 0) { alert('No online agents in selection.'); return; } if (action === 'stop' && !window.confirm(`Stop miner on ${onlineIds.length} agent(s)?`)) return; setBulkBusy(true); try { await api.sendBulkCommand(onlineIds, action); } catch (err) { console.error(err); } finally { setBulkBusy(false); } }; return (

FLEET REGISTRY

Fleet Roster

Compact list — click a row to expand quick actions or inspect full telemetry on the right.

{filteredAgents.length}/{agents.length} NODES
{loadError && (

{loadError}

)} {loading ? (

Scanning network...

) : agents.length === 0 ? (

No agents registered

Deploy a worker to any machine (Windows, Linux, or macOS) using the Forge and it will appear here automatically.

) : (
{filteredAgents.map((agent) => ( toggleSelect(agent.id, on)} onSelect={() => void selectAgent(agent)} onToggleExpand={() => setExpandedId((prev) => (prev === agent.id ? null : agent.id))} commandResults={commandResults} /> ))} {filteredAgents.length === 0 && (

No agents match filters.

)}
{selectedAgent && (

{selectedAgent.name}

{(selectedAgent.tags?.length ?? 0) > 0 && (
{selectedAgent.tags!.map((t) => ( {t} ))}
)}

Notes & Tags

Labels like "Living room PC" or "Rack B" — stored on the server, shown on list cards.