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})`;
- })()}
/>
)}