Complete private Monero miner control stack.
Implement Windows agent with RandomX mining and WebSocket fleet reporting, wire dashboard settings into the builder with saved exe paths, and add project README.
This commit is contained in:
122
server/web/src/hooks/useWebSocket.ts
Normal file
122
server/web/src/hooks/useWebSocket.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import { useEffect, useRef, useCallback, useState } from 'react';
|
||||
import type { WSMessage, Agent, FleetStats, Share } from '../types';
|
||||
|
||||
interface DashboardData {
|
||||
agents: Agent[];
|
||||
stats: FleetStats;
|
||||
}
|
||||
|
||||
interface UseWebSocketReturn {
|
||||
isConnected: boolean;
|
||||
agents: Agent[];
|
||||
stats: FleetStats | null;
|
||||
recentShares: Share[];
|
||||
}
|
||||
|
||||
export function useWebSocket(): UseWebSocketReturn {
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [agents, setAgents] = useState<Agent[]>([]);
|
||||
const [stats, setStats] = useState<FleetStats | null>(null);
|
||||
const [recentShares, setRecentShares] = useState<Share[]>([]);
|
||||
|
||||
const connect = useCallback(() => {
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsUrl = `${protocol}//${window.location.host}/ws/dashboard`;
|
||||
|
||||
const ws = new WebSocket(wsUrl);
|
||||
wsRef.current = ws;
|
||||
|
||||
ws.onopen = () => {
|
||||
setIsConnected(true);
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
setIsConnected(false);
|
||||
// Reconnect after 3 seconds
|
||||
setTimeout(connect, 3000);
|
||||
};
|
||||
|
||||
ws.onerror = () => {
|
||||
ws.close();
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
try {
|
||||
const msg: WSMessage = JSON.parse(event.data);
|
||||
|
||||
switch (msg.type) {
|
||||
case 'init': {
|
||||
const data = msg.payload as DashboardData;
|
||||
if (data.agents) setAgents(data.agents);
|
||||
if (data.stats) setStats(data.stats);
|
||||
break;
|
||||
}
|
||||
case 'agent_online': {
|
||||
const agent = msg.payload as Agent;
|
||||
setAgents((prev) => {
|
||||
const idx = prev.findIndex((a) => a.id === agent.id);
|
||||
if (idx >= 0) {
|
||||
const updated = [...prev];
|
||||
updated[idx] = agent;
|
||||
return updated;
|
||||
}
|
||||
return [...prev, agent];
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'agent_offline': {
|
||||
const { agent_id } = msg.payload as { agent_id: string };
|
||||
setAgents((prev) =>
|
||||
prev.map((a) =>
|
||||
a.id === agent_id ? { ...a, status: 'offline' as const } : a
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'stats_update': {
|
||||
const update = msg.payload as {
|
||||
agent_id: string;
|
||||
hashrate_15s: number;
|
||||
hashrate_1m: number;
|
||||
hashrate_15m: number;
|
||||
cpu_usage_pct: number;
|
||||
};
|
||||
setAgents((prev) =>
|
||||
prev.map((a) =>
|
||||
a.id === update.agent_id
|
||||
? {
|
||||
...a,
|
||||
hashrate_15s: update.hashrate_15s,
|
||||
hashrate_1m: update.hashrate_1m,
|
||||
hashrate_15m: update.hashrate_15m,
|
||||
cpu_usage_pct: update.cpu_usage_pct,
|
||||
}
|
||||
: a
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'new_share': {
|
||||
const share = msg.payload as Share;
|
||||
setRecentShares((prev) => [share, ...prev].slice(0, 50));
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to parse WebSocket message:', err);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
connect();
|
||||
return () => {
|
||||
if (wsRef.current) {
|
||||
wsRef.current.close();
|
||||
}
|
||||
};
|
||||
}, [connect]);
|
||||
|
||||
return { isConnected, agents, stats, recentShares };
|
||||
}
|
||||
Reference in New Issue
Block a user