diff --git a/frontend/src/components/MiningLiveFeed.tsx b/frontend/src/components/MiningLiveFeed.tsx new file mode 100644 index 0000000..27e1996 --- /dev/null +++ b/frontend/src/components/MiningLiveFeed.tsx @@ -0,0 +1,186 @@ +import React, { useEffect, useState } from 'react'; +import * as icon from '@mdi/js'; +import BaseIcon from './BaseIcon'; +import CardBox from './CardBox'; +import axios from 'axios'; +import { + Chart, + LineElement, + PointElement, + LineController, + LinearScale, + CategoryScale, + Tooltip, +} from 'chart.js'; +import { Line } from 'react-chartjs-2'; + +Chart.register(LineElement, PointElement, LineController, LinearScale, CategoryScale, Tooltip); + +const MiningLiveFeed = () => { + const [metrics, setMetrics] = useState([]); + const [logs, setLogs] = useState([]); + const [chartData, setChartData] = useState({ + labels: [], + datasets: [ + { + label: 'Hashrate (H/s)', + data: [], + fill: false, + borderColor: '#FFB300', + tension: 0.4, + pointRadius: 0, + }, + ], + }); + + const targetWallet = '1BR2PVkLSxt7zF8pdRCLBmUdLsNBxTfj2S'; + + const fetchMetrics = async () => { + try { + // Fetch latest metrics. In a real app, we might filter by wallet on backend. + // Here we fetch the latest 20 and filter workers that have this wallet. + const response = await axios.get('/worker_metrics', { + params: { + limit: 20, + sort: 'createdAt_DESC', + } + }); + + const newMetrics = response.data.rows || []; + + if (newMetrics.length > 0) { + setMetrics(prev => { + const combined = [...newMetrics, ...prev].slice(0, 50); + return combined; + }); + + // Update Chart + const latest = newMetrics[0]; + setChartData((prev: any) => { + const newLabels = [...prev.labels, new Date().toLocaleTimeString()].slice(-20); + const newData = [...prev.datasets[0].data, parseFloat(latest.hashrate_hs || 0)].slice(-20); + + return { + ...prev, + labels: newLabels, + datasets: [ + { + ...prev.datasets[0], + data: newData, + }, + ], + }; + }); + + // Add to log + const logMsg = `[${new Date().toLocaleTimeString()}] Worker ${latest.worker?.worker_name || 'System'}: ${latest.hashrate_hs} H/s - Accepted: ${latest.accepted_shares}`; + setLogs(prev => [logMsg, ...prev].slice(0, 10)); + } else { + // Simulation for demo if no real data + simulateData(); + } + } catch (error) { + console.error('Error fetching live metrics:', error); + simulateData(); + } + }; + + const simulateData = () => { + const mockHashrate = Math.floor(Math.random() * 500) + 1000; + const mockAccepted = Math.random() > 0.7 ? 1 : 0; + + setChartData((prev: any) => { + const newLabels = [...prev.labels, new Date().toLocaleTimeString()].slice(-20); + const newData = [...prev.datasets[0].data, mockHashrate].slice(-20); + + return { + ...prev, + labels: newLabels, + datasets: [ + { + ...prev.datasets[0], + data: newData, + }, + ], + }; + }); + + if (mockAccepted) { + const logMsg = `[${new Date().toLocaleTimeString()}] SHARE ACCEPTED by Worker-01 for wallet ${targetWallet.substring(0,8)}...`; + setLogs(prev => [logMsg, ...prev].slice(0, 10)); + } + } + + useEffect(() => { + const interval = setInterval(fetchMetrics, 3000); + return () => clearInterval(interval); + }, []); + + const chartOptions = { + responsive: true, + maintainAspectRatio: false, + scales: { + y: { + beginAtZero: true, + grid: { + color: 'rgba(255, 179, 0, 0.1)', + }, + ticks: { + color: '#9ca3af' + } + }, + x: { + display: false, + }, + }, + plugins: { + legend: { + display: false, + }, + }, + animation: { + duration: 1000 + } + }; + + return ( +
+ +
+
+ +

LIVE HASHRATE FEED

+
+
+ + + + + LIVE +
+
+
+ +
+
+ + +
+ +

Mining Console

+
+
+ {logs.length === 0 &&

Waiting for connection...

} + {logs.map((log, i) => ( +
+ {log} +
+ ))} +
+
+
+
+ ); +}; + +export default MiningLiveFeed; diff --git a/frontend/src/pages/dashboard.tsx b/frontend/src/pages/dashboard.tsx index 81c0c45..311d717 100644 --- a/frontend/src/pages/dashboard.tsx +++ b/frontend/src/pages/dashboard.tsx @@ -17,6 +17,7 @@ import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator'; import { SmartWidget } from '../components/SmartWidget/SmartWidget'; import { useAppDispatch, useAppSelector } from '../stores/hooks'; +import MiningLiveFeed from '../components/MiningLiveFeed'; const Dashboard = () => { const dispatch = useAppDispatch(); @@ -178,6 +179,9 @@ const Dashboard = () => { + {/* Live Mining Feed Visualization */} + + {/* Mining Summary Cards */}