diff --git a/src/components/data/DownstreamWorkerTable.tsx b/src/components/data/DownstreamWorkerTable.tsx index 1bb5743e..5746b389 100644 --- a/src/components/data/DownstreamWorkerTable.tsx +++ b/src/components/data/DownstreamWorkerTable.tsx @@ -198,24 +198,9 @@ export function DownstreamWorkerTable({ - onSort('connection_id')}> + onSort('channel_type')}> - Connection Id - - - Worker channels with the same connection ID belong to the same downstream - connection. - - - - onSort('channel_id')}> - - Channel Id - - - onSort('channel_type')}> - - Channel Type + Connection Type onSort('identity')}> @@ -223,15 +208,6 @@ export function DownstreamWorkerTable({ Username / Identity - Management IP - Telemetry - Miner - Firmware - State - Temp - Power - Efficiency - Uptime onSort('estimated_hashrate')}> Hashrate @@ -252,17 +228,20 @@ export function DownstreamWorkerTable({ )} + Telemetry + Management IP + Miner + Firmware + State + Temp + Power + Efficiency + Uptime {workers.map((worker) => ( - - {worker.connection_id} - - - {worker.channel_id ?? '-'} - {worker.identity || '-'} - - {worker.management_ip || '-'} + + {formatWorkerHashrate(worker)} + {showBestDiff && ( + + {worker.best_diff !== null && worker.best_diff > 0 ? formatDifficulty(worker.best_diff) : '-'} + + )} {getTelemetryStatusLabel(worker.miner_telemetry_status) || '-'} + + {worker.management_ip || '-'} + {getMinerLabel(worker.miner_telemetry)} @@ -313,14 +300,6 @@ export function DownstreamWorkerTable({ {getUptimeLabel(worker.miner_telemetry)} - - {formatWorkerHashrate(worker)} - - {showBestDiff && ( - - {worker.best_diff !== null && worker.best_diff > 0 ? formatDifficulty(worker.best_diff) : '-'} - - )} ))} diff --git a/src/components/setup/MinerConnectionInfo.tsx b/src/components/setup/MinerConnectionInfo.tsx index cc0acf48..e77620cf 100644 --- a/src/components/setup/MinerConnectionInfo.tsx +++ b/src/components/setup/MinerConnectionInfo.tsx @@ -2,6 +2,7 @@ import { useState } from 'react'; import { Copy, Check } from 'lucide-react'; import { TRANSLATOR_PORT, JDC_PORT, JDC_AUTHORITY_PUBLIC_KEY } from '@/lib/ports'; import { useHostEnv } from '@/hooks/useHostEnv'; +import { InfoPopover } from '@/components/ui/info-popover'; function copyWithSelectionFallback(text: string): boolean { const textArea = document.createElement('textarea'); @@ -82,6 +83,22 @@ function CopyableAddress({ address }: { address: string }) { ); } +function HostHint() { + return ( + + Replace{' '} + + <your-machine-ip> + {' '} + with your local network IP (for example,{' '} + + 192.168.1.100 + + ). + + ); +} + interface MinerConnectionInfoProps { isJdMode: boolean; centered?: boolean; @@ -93,12 +110,6 @@ export function MinerConnectionInfo({ isJdMode, centered = false }: MinerConnect const translatorUrl = `stratum+tcp://${host}:${TRANSLATOR_PORT}`; const jdcUrl = `stratum2+tcp://${host}:${JDC_PORT}/${JDC_AUTHORITY_PUBLIC_KEY}`; - const hint = ( -

- Replace <your-machine-ip> with your local network IP (e.g. 192.168.1.100). -

- ); - // When only one card is shown (SV1-only mode) it should stretch to full width. // In JD mode two cards sit side-by-side on md+ screens. const wrapperClass = centered @@ -110,18 +121,20 @@ export function MinerConnectionInfo({ isJdMode, centered = false }: MinerConnect return (
-
SV1 Firmware
-
Point to the Translator Proxy
+
+
SV1 Firmware
+ {!stratumHost && } +
- {!stratumHost && hint}
{isJdMode && (
-
SV2 Firmware
-
Point directly to the JD Client
+
+
SV2 Firmware
+ {!stratumHost && } +
- {!stratumHost && hint}
)}
diff --git a/src/hooks/useConnectionStatus.ts b/src/hooks/useConnectionStatus.ts index daf4287d..a4e77a7f 100644 --- a/src/hooks/useConnectionStatus.ts +++ b/src/hooks/useConnectionStatus.ts @@ -71,7 +71,7 @@ export function useConnectionStatus(): ConnectionStatus { return { status, - statusLabel: isSovereignSolo ? 'Mining services healthy' : null, + statusLabel: isSovereignSolo ? 'Sovereign Solo' : null, poolName: isPoolConnected ? (poolName ?? null) : null, uptime: isPoolConnected ? (poolGlobal?.uptime_secs ?? 0) : 0, }; diff --git a/src/pages/UnifiedDashboard.tsx b/src/pages/UnifiedDashboard.tsx index 61ddb7be..d6fb0882 100644 --- a/src/pages/UnifiedDashboard.tsx +++ b/src/pages/UnifiedDashboard.tsx @@ -16,6 +16,7 @@ import { import { usePoolData, useSv1ClientsData, + useTranslatorServerChannels, useTranslatorHealth, useJdcHealth, } from '@/hooks/usePoolData'; @@ -94,6 +95,8 @@ export function UnifiedDashboard() { isLoading: poolLoading, isError: poolError, } = usePoolData(templateMode); + const { data: translatorServerChannels } = useTranslatorServerChannels(isJdMode); + const sv1ServerChannels = isJdMode ? translatorServerChannels : serverChannels; const isAggregatedTproxy = !isJdMode && isAggregatedTproxyPoolName(configPoolName); // SV1 clients (always from Translator) @@ -248,14 +251,6 @@ export function UnifiedDashboard() { const directSv2WorkerCount = downstreamWorkers.length; const jdWorkerCount = directSv2WorkerCount + sv1TotalClients; const jdActiveWorkerCount = directSv2WorkerCount + sv1ActiveCount; - const jdClientChannelCount = directSv2WorkerCount + sv1ActiveCount; - const userFacingDownstreamConnectionCount = useMemo(() => { - if (!isJdMode) { - return 0; - } - - return new Set(downstreamWorkers.map((worker) => worker.connection_id)).size + sv1TotalClients; - }, [downstreamWorkers, isJdMode, sv1TotalClients]); const totalWorkers = isJdMode ? jdWorkerCount : sv1TotalClients; const activeWorkers = isJdMode ? jdActiveWorkerCount : sv1ActiveCount; const workerTableLoading = isJdMode ? (isSv2ClientsLoading || sv1Loading) : sv1Loading; @@ -269,10 +264,6 @@ export function UnifiedDashboard() { : (poolGlobal?.sv2_clients?.total_hashrate ?? 0)) : (sv1Data ? sv1TotalHashrate : (poolGlobal?.sv1_clients?.total_hashrate ?? 0)); - const totalClientChannels = isJdMode - ? jdClientChannelCount - : sv1ActiveCount; - // Scope hashrate history to the active pool + mode so stale samples from a // previous configuration are never shown after a reconfigure. // Falls back to 'default' while setup status is still loading or in @@ -322,10 +313,21 @@ export function UnifiedDashboard() { }, [directSv2Clients, isJdMode, serverChannels, sv2Clients]); const bestDiffEntries = useMemo(() => { - if (isJdMode) { - if (!sv2Clients) return []; + const sv1BestDiffEntries = sv1ServerChannels ? [ + ...sv1ServerChannels.extended_channels.map((channel) => ({ + key: `translator:server:extended:${channel.channel_id}:${channel.user_identity}`, + value: channel.best_diff, + })), + ...sv1ServerChannels.standard_channels.map((channel) => ({ + key: `translator:server:standard:${channel.channel_id}:${channel.user_identity}`, + value: channel.best_diff, + })), + ] : []; - return directSv2Clients.flatMap((client) => [ + if (!isJdMode || !sv2Clients) return sv1BestDiffEntries; + + return [ + ...directSv2Clients.flatMap((client) => [ ...client.extended_channels.map((channel) => ({ key: `jdc:${client.client_id}:extended:${channel.channel_id}:${channel.user_identity}`, value: channel.best_diff, @@ -334,22 +336,23 @@ export function UnifiedDashboard() { key: `jdc:${client.client_id}:standard:${channel.channel_id}:${channel.user_identity}`, value: channel.best_diff, })), - ]); - } + ]), + ...sv1BestDiffEntries, + ]; + }, [directSv2Clients, isJdMode, sv1ServerChannels, sv2Clients]); - if (!serverChannels) return []; + const sv1BestDiffByChannelId = useMemo(() => { + const bestDiffByChannelId = new Map(); - return [ - ...serverChannels.extended_channels.map((channel) => ({ - key: `translator:server:extended:${channel.channel_id}:${channel.user_identity}`, - value: channel.best_diff, - })), - ...serverChannels.standard_channels.map((channel) => ({ - key: `translator:server:standard:${channel.channel_id}:${channel.user_identity}`, - value: channel.best_diff, - })), - ]; - }, [directSv2Clients, isJdMode, serverChannels, sv2Clients]); + if (!sv1ServerChannels) return bestDiffByChannelId; + + [...sv1ServerChannels.extended_channels, ...sv1ServerChannels.standard_channels].forEach((channel) => { + const currentBestDiff = bestDiffByChannelId.get(channel.channel_id) ?? 0; + bestDiffByChannelId.set(channel.channel_id, Math.max(currentBestDiff, channel.best_diff)); + }); + + return bestDiffByChannelId; + }, [sv1ServerChannels]); const shareStatsEntries = useMemo(() => { if (!serverChannels) return []; @@ -376,18 +379,9 @@ export function UnifiedDashboard() { const bestDiff = usePersistentBestDifficulty(bestDiffEntries, historyConfigKey); const shareStats = usePersistentShareStats(shareStatsEntries, historyConfigKey); - // Number of upstream pool channels (for shares subtitle) - const poolChannelCount = (serverChannels?.total_extended || 0) + (serverChannels?.total_standard || 0); - - // Number of client channels (for best diff subtitle) - const clientChannelCount = isJdMode - ? jdClientChannelCount - : sv1ActiveCount; - const bestDiffSubtitle = clientChannelCount > 0 - ? `from ${clientChannelCount} client channel(s)` - : undefined; - - const hasBestDiffSource = isJdMode ? !!sv2Clients : !!serverChannels; + const hasBestDiffSource = isJdMode + ? !!sv2Clients || !!sv1ServerChannels + : !!sv1ServerChannels; useEffect(() => { if (isAggregatedTproxy && sortKey === 'best_diff') { @@ -412,7 +406,9 @@ export function UnifiedDashboard() { miner_telemetry: client.miner_telemetry, estimated_hashrate: resolvedHashrate.hashrate, hashrate_source: resolvedHashrate.source, - best_diff: null, + best_diff: client.channel_id === null || client.channel_id === undefined + ? null + : (sv1BestDiffByChannelId.get(client.channel_id) ?? null), search_text: [ client.sv1_username, client.sv1_worker_name, @@ -427,7 +423,7 @@ export function UnifiedDashboard() { ].join(' ').toLowerCase(), }; }); - }, [allSv1Clients]); + }, [allSv1Clients, sv1BestDiffByChannelId]); const dashboardWorkers = useMemo(() => { if (isJdMode) { @@ -580,7 +576,6 @@ export function UnifiedDashboard() { Uses miner-reported telemetry when available. Otherwise falls back to the proxy's @@ -600,11 +595,6 @@ export function UnifiedDashboard() { ) } - subtitle={ - isJdMode - ? `${userFacingDownstreamConnectionCount} downstream connection(s)` - : `${totalWorkers - activeWorkers} offline workers` - } /> {label}; })()} - subtitle={(() => { - const { submitted, rejected } = shareStats; - if (submitted === 0) return `via ${poolChannelCount} channel(s)`; - const rejectionRate = (rejected / submitted) * 100; - const rejRateLabel = rejected === 0 - ? '0%' - : `${Math.max(rejectionRate, 0.01).toFixed(2)}%`; - return `${submitted.toLocaleString()} submitted ยท ${rejected.toLocaleString()} rejected (${rejRateLabel})`; - })()} /> )}