From 4fb487edb3dc591408af0c344ec1e98740fbaad2 Mon Sep 17 00:00:00 2001 From: Pavlenex <36959754+pavlenex@users.noreply.github.com> Date: Mon, 30 Mar 2026 13:34:43 +0200 Subject: [PATCH] Add sorting to workers table --- src/components/data/Sv1ClientTable.tsx | 137 +++++++++++++++---------- src/pages/UnifiedDashboard.tsx | 37 +++++-- 2 files changed, 111 insertions(+), 63 deletions(-) diff --git a/src/components/data/Sv1ClientTable.tsx b/src/components/data/Sv1ClientTable.tsx index 8a6a5bb..0b0dd3d 100644 --- a/src/components/data/Sv1ClientTable.tsx +++ b/src/components/data/Sv1ClientTable.tsx @@ -1,3 +1,4 @@ +import { ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react'; import { Table, TableBody, @@ -10,16 +11,28 @@ import { formatHashrate, truncateHex } from '@/lib/utils'; import { cn } from '@/lib/utils'; import type { Sv1ClientInfo } from '@/types/api'; +export type SortKey = 'client_id' | 'authorized_worker_name' | 'user_identity' | 'hashrate'; + interface Sv1ClientTableProps { clients: Sv1ClientInfo[]; isLoading?: boolean; + sortKey: SortKey; + sortDir: 'asc' | 'desc'; + onSort: (key: SortKey) => void; +} + +function SortIcon({ column, sortKey, sortDir }: { column: SortKey; sortKey: SortKey; sortDir: 'asc' | 'desc' }) { + if (column !== sortKey) return ; + return sortDir === 'asc' + ? + : ; } /** * Table component for displaying SV1 clients connected to Translator. * SV1 clients are legacy mining hardware using Stratum V1 protocol. */ -export function Sv1ClientTable({ clients, isLoading }: Sv1ClientTableProps) { +export function Sv1ClientTable({ clients, isLoading, sortKey, sortDir, onSort }: Sv1ClientTableProps) { if (isLoading) { return (
@@ -37,60 +50,76 @@ export function Sv1ClientTable({ clients, isLoading }: Sv1ClientTableProps) { No SV1 clients connected
) : ( - - - - ID - Worker Name - User Identity - Hashrate - Channel - Extranonce1 - Version Rolling - - - - {clients.map((client) => ( - - - {client.client_id} - - -
-
- {client.authorized_worker_name || '-'} -
- - - {client.user_identity || '-'} - - - {client.hashrate !== null ? formatHashrate(client.hashrate) : '-'} - - - {client.channel_id !== null ? client.channel_id : '-'} - - - {truncateHex(client.extranonce1_hex, 4)} - - - {client.version_rolling_mask ? ( - - {truncateHex(client.version_rolling_mask, 4)} - - ) : ( - - Disabled - - )} - +
+ + + onSort('client_id')}> + + ID + + + onSort('authorized_worker_name')}> + + Worker Name + + + onSort('user_identity')}> + + User Identity + + + onSort('hashrate')}> + + Hashrate + + + Channel + Extranonce1 + Version Rolling - ))} - -
+ + + {clients.map((client) => ( + + + {client.client_id} + + +
+
+ {client.authorized_worker_name || '-'} +
+ + + {client.user_identity || '-'} + + + {client.hashrate !== null ? formatHashrate(client.hashrate) : '-'} + + + {client.channel_id !== null ? client.channel_id : '-'} + + + {truncateHex(client.extranonce1_hex, 4)} + + + {client.version_rolling_mask ? ( + + {truncateHex(client.version_rolling_mask, 4)} + + ) : ( + + Disabled + + )} + + + ))} + + )}
); diff --git a/src/pages/UnifiedDashboard.tsx b/src/pages/UnifiedDashboard.tsx index f14bd2a..ae42ef7 100644 --- a/src/pages/UnifiedDashboard.tsx +++ b/src/pages/UnifiedDashboard.tsx @@ -4,7 +4,7 @@ import { MinerConnectionInfo } from '@/components/setup/MinerConnectionInfo'; import { Shell } from '@/components/layout/Shell'; import { StatCard } from '@/components/data/StatCard'; import { HashrateChart } from '@/components/data/HashrateChart'; -import { Sv1ClientTable } from '@/components/data/Sv1ClientTable'; +import { Sv1ClientTable, type SortKey } from '@/components/data/Sv1ClientTable'; import { usePoolData, useSv1ClientsData, useTranslatorHealth, useJdcHealth } from '@/hooks/usePoolData'; import { useHashrateHistory } from '@/hooks/useHashrateHistory'; import { useSetupStatus } from '@/hooks/useSetupStatus'; @@ -27,6 +27,8 @@ import type { Sv1ClientInfo } from '@/types/api'; export function UnifiedDashboard() { const [searchTerm, setSearchTerm] = useState(''); const [currentPage, setCurrentPage] = useState(1); + const [sortKey, setSortKey] = useState('client_id'); + const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc'); const itemsPerPage = 15; // Get configured template mode from setup status @@ -154,15 +156,25 @@ export function UnifiedDashboard() { ? (clientChannels?.total_extended || 0) + (clientChannels?.total_standard || 0) : activeCount; - // Filter clients by search + // Filter and sort clients const filteredClients = useMemo(() => { - if (!searchTerm) return allClients; - const term = searchTerm.toLowerCase(); - return allClients.filter((c: Sv1ClientInfo) => - c.authorized_worker_name?.toLowerCase().includes(term) || - c.user_identity?.toLowerCase().includes(term) - ); - }, [allClients, searchTerm]); + let list = allClients; + if (searchTerm) { + const term = searchTerm.toLowerCase(); + list = allClients.filter((c: Sv1ClientInfo) => + c.authorized_worker_name?.toLowerCase().includes(term) || + c.user_identity?.toLowerCase().includes(term) + ); + } + const nullLast = sortDir === 'asc' ? Infinity : -Infinity; + return [...list].sort((a, b) => { + const av = a[sortKey] ?? nullLast; + const bv = b[sortKey] ?? nullLast; + if (av < bv) return sortDir === 'asc' ? -1 : 1; + if (av > bv) return sortDir === 'asc' ? 1 : -1; + return 0; + }); + }, [allClients, searchTerm, sortKey, sortDir]); // Pagination const totalPages = Math.ceil(filteredClients.length / itemsPerPage); @@ -328,6 +340,13 @@ export function UnifiedDashboard() { { + if (key === sortKey) setSortDir(d => d === 'asc' ? 'desc' : 'asc'); + else { setSortKey(key); setSortDir('asc'); } + setCurrentPage(1); + }} /> {/* Pagination Footer */}