From 917a4d32b1630440a6f4ed00fb547ba55a908c07 Mon Sep 17 00:00:00 2001 From: 9qeklajc Date: Sat, 10 Jan 2026 20:10:59 +0100 Subject: [PATCH] ui: visualize parent-child relationship in balances page --- ui/components/temporary-balances.tsx | 79 +++++++++++++++++++++++----- ui/lib/api/services/admin.ts | 1 + 2 files changed, 67 insertions(+), 13 deletions(-) diff --git a/ui/components/temporary-balances.tsx b/ui/components/temporary-balances.tsx index c33d0d43..004ba77f 100644 --- a/ui/components/temporary-balances.tsx +++ b/ui/components/temporary-balances.tsx @@ -60,7 +60,11 @@ export function TemporaryBalances({ let totalRequests = 0; balances.forEach((balance) => { - totalBalance += balance.balance || 0; + // Only count parents for total balance to avoid double counting + // since child keys use parent balance + if (!balance.parent_key_hash) { + totalBalance += balance.balance || 0; + } totalSpent += balance.total_spent || 0; totalRequests += balance.total_requests || 0; }); @@ -72,6 +76,34 @@ export function TemporaryBalances({ ? calculateTotals(data) : { totalBalance: 0, totalSpent: 0, totalRequests: 0 }; + // Group parents and children + const hierarchicalData = (() => { + if (!data) return []; + + const parents = filteredData.filter((item) => !item.parent_key_hash); + const result: (TemporaryBalance & { isChild?: boolean })[] = []; + + parents.forEach((parent) => { + result.push(parent); + const children = data.filter( + (item) => item.parent_key_hash === parent.hashed_key + ); + children.forEach((child) => { + result.push({ ...child, isChild: true }); + }); + }); + + // Add children whose parents didn't match the search or aren't in the list + const orphans = filteredData.filter( + (item) => + item.parent_key_hash && + !result.some((r) => r.hashed_key === item.hashed_key) + ); + result.push(...orphans.map((o) => ({ ...o, isChild: true }))); + + return result; + })(); + return ( <> @@ -182,22 +214,32 @@ export function TemporaryBalances({
Expiry Time
- {filteredData.length > 0 ? ( - filteredData.map((balance, index) => ( + {hierarchicalData.length > 0 ? ( + hierarchicalData.map((balance, index) => (
{/* Desktop Layout */}
-
+
+ {balance.isChild && ( + + Child + + )} {balance.hashed_key}
- {formatBalance(balance.balance)} + {balance.isChild ? ( + (Parent) + ) : ( + formatBalance(balance.balance) + )}
{formatBalance(balance.total_spent)} @@ -226,13 +268,20 @@ export function TemporaryBalances({ {/* Mobile Layout */}
-
- - Key - -
- {balance.hashed_key} +
+
+ + {balance.isChild ? 'Child Key' : 'Key'} + +
+ {balance.hashed_key} +
+ {balance.isChild && ( + + Child + + )}
@@ -241,7 +290,11 @@ export function TemporaryBalances({ Balance
- {formatBalance(balance.balance)} + {balance.isChild ? ( + (Uses Parent) + ) : ( + formatBalance(balance.balance) + )}
diff --git a/ui/lib/api/services/admin.ts b/ui/lib/api/services/admin.ts index f94729ba..2ea27e7e 100644 --- a/ui/lib/api/services/admin.ts +++ b/ui/lib/api/services/admin.ts @@ -926,6 +926,7 @@ export const TemporaryBalanceSchema = z.object({ total_requests: z.number(), refund_address: z.string().nullable(), key_expiry_time: z.number().nullable(), + parent_key_hash: z.string().nullable().optional(), }); export type TemporaryBalance = z.infer;