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 */}