This commit is contained in:
Flatlogic Bot 2026-02-27 00:22:22 +00:00
parent 760a6a656a
commit 8ed774fac3
2 changed files with 190 additions and 0 deletions

View File

@ -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<any[]>([]);
const [logs, setLogs] = useState<string[]>([]);
const [chartData, setChartData] = useState<any>({
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 (
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
<CardBox className="lg:col-span-2 bg-dark-900 border-amber-500/30">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<BaseIcon path={icon.mdiChartTimelineVariant} className="text-amber-500" />
<h3 className="text-lg font-bold text-gray-200">LIVE HASHRATE FEED</h3>
</div>
<div className="flex items-center gap-2">
<span className="relative flex h-3 w-3">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
<span className="relative inline-flex rounded-full h-3 w-3 bg-emerald-500"></span>
</span>
<span className="text-xs font-mono text-emerald-500">LIVE</span>
</div>
</div>
<div className="h-64">
<Line options={chartOptions} data={chartData} />
</div>
</CardBox>
<CardBox className="bg-black border-emerald-500/30 font-mono text-xs overflow-hidden">
<div className="flex items-center gap-2 mb-4 border-b border-emerald-500/20 pb-2">
<BaseIcon path={icon.mdiConsole} className="text-emerald-500" size={18} />
<h3 className="text-emerald-500 font-bold uppercase tracking-wider">Mining Console</h3>
</div>
<div className="space-y-1 h-64 overflow-y-auto aside-scrollbars">
{logs.length === 0 && <p className="text-gray-600 italic">Waiting for connection...</p>}
{logs.map((log, i) => (
<div key={i} className={`${i === 0 ? 'text-emerald-400 font-bold' : 'text-emerald-700'}`}>
{log}
</div>
))}
<div className="animate-pulse inline-block w-2 h-4 bg-emerald-500 ml-1 mt-1"></div>
</div>
</CardBox>
</div>
);
};
export default MiningLiveFeed;

View File

@ -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 = () => {
</div>
</CardBox>
{/* Live Mining Feed Visualization */}
<MiningLiveFeed />
{/* Mining Summary Cards */}
<div className="grid grid-cols-1 gap-6 lg:grid-cols-4 mb-6">
<CardBox className="border-l-4 border-amber-500">