Skip to content

Commit c26e636

Browse files
Add Toggle Switch and Info Tooltip to DataUsageTable and Enhance Styling on Connections Page (#1691)
* feat(connections)!: responsive table + sort controls - 2/3 column mobile layout - inline cell labels - sort dropdown with toggle - text justification * fix: reorder class names for consistent styling in Header, Connections, and Rules * chore: update package manager version to [email protected] * fix: adjust sort label styling for improved visibility * feat: synchronize DataUsageTable with TrafficWidget and auto-reset on service restart - Implement global background effect for continuous data usage monitoring - Add automatic service restart detection via total traffic monitoring - Sync data clearing behavior with TrafficWidget on service restart - Use baseline tracking to persist data across browser sessions - Implement per-connection incremental calculation for accuracy - Add automatic cleanup of inactive connection tracking data - Persist data usage statistics to localStorage with migration support - Add console logging for debugging service restart events - Fix import statements in DataUsageTable component This ensures DataUsageTable data stays synchronized with TrafficWidget totals and automatically resets when the backend service restarts, providing consistent behavior across the application. Fixes memory leaks by cleaning up stale connection tracking data. Improves data accuracy through baseline + incremental approach. * feat: add toggle for data usage table visibility and enhance UI with info tooltip * fix: update initial visibility state of DataUsageTable and enhance styling in Connections page --------- Signed-off-by: Anggun Caksono <[email protected]>
1 parent 7baee35 commit c26e636

File tree

7 files changed

+436
-305
lines changed

7 files changed

+436
-305
lines changed

src/components/DataUsageTable.tsx

Lines changed: 288 additions & 257 deletions
Large diffs are not rendered by default.

src/components/LogoText.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { endpoint } from '~/signals'
33
export const LogoText = () => (
44
<div class="text-md flex items-center gap-1 font-bold whitespace-nowrap uppercase sm:text-xl">
55
<A
6-
class="bg-gradient-to-br from-primary to-secondary bg-clip-text text-transparent"
6+
class="bg-linear-to-br from-primary to-secondary bg-clip-text text-transparent"
77
href={endpoint() ? '/' : '/setup'}
88
>
99
metacube

src/i18n/en.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,4 +140,7 @@ export default {
140140
sortBy: 'Sort by:',
141141
ipShort: 'IP',
142142
na: 'N/A',
143+
show: 'Show',
144+
dataUsageInfo:
145+
'Data usage monitoring is performed on the client-side (browser). When the browser is closed, monitoring will likely not run.',
143146
}

src/i18n/ru.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,7 @@ export default {
142142
sortBy: 'Сортировать по:',
143143
ipShort: 'IP',
144144
na: 'Н/Д',
145+
show: 'Показать',
146+
dataUsageInfo:
147+
'Мониторинг использования данных выполняется на стороне клиента (браузер). Когда браузер закрыт, мониторинг, скорее всего, не будет работать.',
145148
} satisfies Dict

src/i18n/zh.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,7 @@ export default {
142142
sortBy: '排序:',
143143
ipShort: 'IP',
144144
na: '无',
145+
show: '显示',
146+
dataUsageInfo:
147+
'数据用量监控在客户端(浏览器)执行。当浏览器关闭时,监控可能不会运行。',
145148
} satisfies Dict

src/pages/Connections.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -627,15 +627,18 @@ export default () => {
627627
data={table.getRowModel().rows}
628628
as="tbody"
629629
item={(props) => (
630-
<tr {...props} class="flex flex-wrap md:table-row" />
630+
<tr
631+
{...props}
632+
class="border-base-400 even:bg-base-400 flex flex-wrap border-t border-b-2 odd:bg-base-100 md:table-row md:border-t-0"
633+
/>
631634
)}
632635
>
633636
{(row) => (
634637
<For each={row.getVisibleCells()}>
635638
{(cell) => {
636639
return (
637640
<td
638-
class="w-1/2 min-w-[50%] py-2 text-justify align-top wrap-break-word nth-[2n]:text-right sm:w-1/3 sm:min-w-[33.333%] sm:nth-[2n]:text-justify sm:nth-[3n]:text-right md:inline-block md:w-44 md:min-w-44 md:text-start lg:w-48 lg:min-w-48"
641+
class="w-1/2 min-w-[50%] py-4 text-justify align-top wrap-break-word nth-[2n]:text-right sm:w-1/3 sm:min-w-[33.333%] sm:nth-[2n]:text-justify sm:nth-[3n]:text-right md:inline-block md:w-44 md:min-w-44 md:py-3 md:text-start md:nth-[2n]:text-start md:nth-[3n]:text-start lg:w-68 lg:min-w-48"
639642
onContextMenu={(e) => {
640643
e.preventDefault()
641644

src/signals/connections.ts

Lines changed: 133 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ export const [latestConnectionMsg, setLatestConnectionMsg] =
3333
let lastUploadTotal = 0
3434
let lastDownloadTotal = 0
3535

36-
// Global effect to monitor data usage - runs always, independent of page
36+
// Global effect to monitor data usage - runs always in background, independent of page
37+
// This effect continuously tracks connection data and updates data usage statistics
38+
// It also detects service restarts by monitoring if total values decrease
3739
createEffect(() => {
3840
const msg = latestConnectionMsg()
3941
const rawConns = msg?.connections
@@ -42,13 +44,15 @@ createEffect(() => {
4244
const currentUploadTotal = msg?.uploadTotal || 0
4345
const currentDownloadTotal = msg?.downloadTotal || 0
4446

45-
// If totals decreased, service was restarted - reset tracking
47+
// If totals decreased, service was restarted - reset all tracking
48+
// This ensures data usage stats are cleared on service restart, same as TrafficWidget behavior
4649
if (
4750
currentUploadTotal < lastUploadTotal ||
4851
currentDownloadTotal < lastDownloadTotal
4952
) {
50-
// Service restarted, clear connection tracking data
53+
// Service restarted, clear connection tracking data and data usage
5154
resetConnectionTracking()
55+
clearDataUsage()
5256
}
5357

5458
lastUploadTotal = currentUploadTotal
@@ -63,7 +67,7 @@ createEffect(() => {
6367
const prevConns = allConnections()
6468
const activeConns = restructRawMsgToConnection(rawConns, prevConns)
6569

66-
// Update data usage tracking
70+
// Update data usage tracking - accumulates data per source IP
6771
updateDataUsage(activeConns)
6872

6973
// Cleanup inactive connection tracking data periodically
@@ -233,23 +237,69 @@ export const [dataUsageMap, setDataUsageMap] = makePersisted(
233237
},
234238
)
235239

240+
// Store baseline totals from backend to calculate incremental changes across browser sessions
241+
export const [baselineTotals, setBaselineTotals] = makePersisted(
242+
createSignal<{ upload: number; download: number }>({
243+
upload: 0,
244+
download: 0,
245+
}),
246+
{
247+
name: 'dataUsageBaseline',
248+
storage: localStorage,
249+
},
250+
)
251+
236252
// Track last known data for each connection to calculate incremental changes
237253
const connectionLastData = new Map<
238254
string,
239255
{ upload: number; download: number }
240256
>()
241257

258+
// Track if session has been initialized
259+
let hasInitializedSession = false
260+
242261
// Reset connection tracking data when service restarts
243262
export const resetConnectionTracking = () => {
244263
connectionLastData.clear()
245-
console.log('[Data Usage] Connection tracking reset due to service restart')
264+
setBaselineTotals({ upload: 0, download: 0 })
265+
hasInitializedSession = false
266+
console.log(
267+
'[Data Usage] Connection tracking reset due to service restart. Data usage map will be cleared.',
268+
)
246269
}
247270

271+
// Update data usage statistics by tracking incremental changes per connection
272+
// This function is called continuously in the background to accumulate data usage per IP
273+
// The accumulated totals are persisted in localStorage and synced with TrafficWidget behavior
248274
export const updateDataUsage = (connections: Connection[]) => {
249-
const updates: Record<string, DataUsageEntry> = { ...dataUsageMap() }
275+
const msg = latestConnectionMsg()
276+
const currentGlobalUpload = msg?.uploadTotal || 0
277+
const currentGlobalDownload = msg?.downloadTotal || 0
278+
279+
// Initialize session baseline on first run
280+
if (!hasInitializedSession) {
281+
const baseline = baselineTotals()
282+
hasInitializedSession = true
283+
console.log(
284+
'[Data Usage] Session initialized. Baseline:',
285+
baseline,
286+
'Current global:',
287+
{ upload: currentGlobalUpload, download: currentGlobalDownload },
288+
)
289+
}
250290

251-
// Group connections by source IP and sum their current data
252-
const ipDataMap = new Map<string, { upload: number; download: number }>()
291+
const updates: Record<string, DataUsageEntry> = { ...dataUsageMap() }
292+
const now = Date.now()
293+
294+
// Group connections by source IP and use their current cumulative data
295+
const ipDataMap = new Map<
296+
string,
297+
{
298+
upload: number
299+
download: number
300+
connectionIds: Set<string>
301+
}
302+
>()
253303

254304
connections.forEach((conn) => {
255305
const sourceIP = conn.metadata.sourceIP
@@ -259,78 +309,114 @@ export const updateDataUsage = (connections: Connection[]) => {
259309
const currentUpload = conn.upload || 0
260310
const currentDownload = conn.download || 0
261311

312+
// Track connection IDs per IP
313+
if (!ipDataMap.has(sourceIP)) {
314+
ipDataMap.set(sourceIP, {
315+
upload: 0,
316+
download: 0,
317+
connectionIds: new Set(),
318+
})
319+
}
320+
321+
const ipData = ipDataMap.get(sourceIP)!
322+
ipData.connectionIds.add(conn.id)
323+
262324
// Get last known data for this connection
263325
const lastData = connectionLastData.get(conn.id)
264326

265-
// Calculate incremental change
266-
let uploadDelta = 0
267-
let downloadDelta = 0
268-
269327
if (lastData) {
270-
uploadDelta = currentUpload - lastData.upload
271-
downloadDelta = currentDownload - lastData.download
328+
// Calculate incremental change from last known state
329+
// Only count positive deltas to avoid negative values on connection resets
330+
const uploadDelta = Math.max(0, currentUpload - lastData.upload)
331+
const downloadDelta = Math.max(0, currentDownload - lastData.download)
332+
333+
ipData.upload += uploadDelta
334+
ipData.download += downloadDelta
272335
} else {
273-
// First time seeing this connection, use full amount
274-
uploadDelta = currentUpload
275-
downloadDelta = currentDownload
336+
// First time seeing this connection, add its current cumulative data
337+
ipData.upload += currentUpload
338+
ipData.download += currentDownload
276339
}
277340

278-
// Update last known data
341+
// Update last known data for this connection
279342
connectionLastData.set(conn.id, {
280343
upload: currentUpload,
281344
download: currentDownload,
282345
})
346+
})
283347

284-
// Accumulate data per IP
285-
if (!ipDataMap.has(sourceIP)) {
286-
ipDataMap.set(sourceIP, {
287-
upload: 0,
288-
download: 0,
289-
})
290-
}
348+
// Update baseline totals based on current global totals
349+
// This ensures data persists across browser sessions
350+
const totalTracked = Object.values(updates).reduce(
351+
(acc, entry) => ({
352+
upload: acc.upload + entry.upload,
353+
download: acc.download + entry.download,
354+
}),
355+
{ upload: 0, download: 0 },
356+
)
291357

292-
const ipData = ipDataMap.get(sourceIP)!
358+
// Add new data from this update cycle
359+
ipDataMap.forEach((data) => {
360+
totalTracked.upload += data.upload
361+
totalTracked.download += data.download
362+
})
293363

294-
ipData.upload += uploadDelta
295-
ipData.download += downloadDelta
364+
// Update baseline to reflect all data we've tracked so far
365+
setBaselineTotals({
366+
upload: totalTracked.upload,
367+
download: totalTracked.download,
296368
})
297369

298370
// Update data usage map with accumulated changes
299371
ipDataMap.forEach((data, sourceIP) => {
300372
const existing = updates[sourceIP]
301-
const now = Date.now()
302373

303374
if (existing) {
304-
updates[sourceIP] = {
305-
...existing,
306-
upload: existing.upload + data.upload,
307-
download: existing.download + data.download,
308-
total:
309-
existing.upload + data.upload + existing.download + data.download,
310-
firstSeen: existing.firstSeen || now, // Ensure firstSeen exists
311-
lastSeen: now,
375+
// Only update if there's actual new data
376+
if (data.upload > 0 || data.download > 0) {
377+
updates[sourceIP] = {
378+
...existing,
379+
upload: existing.upload + data.upload,
380+
download: existing.download + data.download,
381+
total:
382+
existing.upload + data.upload + (existing.download + data.download),
383+
firstSeen: existing.firstSeen || now,
384+
lastSeen: now,
385+
}
386+
} else {
387+
// Just update lastSeen to show it's still active
388+
updates[sourceIP] = {
389+
...existing,
390+
lastSeen: now,
391+
}
312392
}
313393
} else {
314-
updates[sourceIP] = {
315-
sourceIP,
316-
macAddress: '',
317-
upload: data.upload,
318-
download: data.download,
319-
total: data.upload + data.download,
320-
firstSeen: now,
321-
lastSeen: now,
394+
// New IP entry
395+
if (data.upload > 0 || data.download > 0) {
396+
updates[sourceIP] = {
397+
sourceIP,
398+
macAddress: '',
399+
upload: data.upload,
400+
download: data.download,
401+
total: data.upload + data.download,
402+
firstSeen: now,
403+
lastSeen: now,
404+
}
322405
}
323406
}
324407
})
325408

326409
setDataUsageMap(updates)
327410
}
328411

412+
// Clear all data usage statistics and reset connection tracking
413+
// This is called on manual clear or automatic service restart detection
329414
export const clearDataUsage = () => {
330415
setDataUsageMap({})
331416
connectionLastData.clear()
332417
}
333418

419+
// Remove a specific IP entry from data usage tracking
334420
export const removeDataUsageEntry = (sourceIP: string) => {
335421
setDataUsageMap((prev) => {
336422
const updates = { ...prev }
@@ -341,6 +427,8 @@ export const removeDataUsageEntry = (sourceIP: string) => {
341427
})
342428
}
343429

430+
// Cleanup tracking data for connections that no longer exist
431+
// This prevents memory leaks from accumulating stale connection data
344432
export const cleanupInactiveConnections = (activeConns?: Connection[]) => {
345433
const activeConnectionIds = activeConns
346434
? new Set(activeConns.map((conn) => conn.id))

0 commit comments

Comments
 (0)