Skip to content

Commit f25b5c0

Browse files
committed
Improve bandwidth chart UX and log viewer IP resolution
- Show dates on X-axis when chart spans multiple days - Display both selected range and full totals in historical mode - Add IP to device name resolution in log viewer
1 parent ce5d44f commit f25b5c0

2 files changed

Lines changed: 76 additions & 13 deletions

File tree

frontend/src/lib/components/charts/BandwidthChart.svelte

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -134,32 +134,57 @@
134134
};
135135
});
136136
137-
// Format time for axis
137+
// Format time for axis - include date if range spans multiple days
138138
function formatTime(time: Date): string {
139+
const { minTime, maxTime } = chartBounds;
140+
const rangeMs = maxTime - minTime;
141+
const dayMs = 24 * 60 * 60 * 1000;
142+
143+
if (rangeMs > dayMs) {
144+
// Show date + time for multi-day ranges
145+
return time.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) +
146+
' ' + time.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
147+
}
139148
return time.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
140149
}
141150
151+
// Calculate full totals (all chart data)
152+
const fullTotals = $derived.by(() => {
153+
const tx = chartData.reduce((sum, d) => sum + d.txBytes, 0);
154+
const rx = chartData.reduce((sum, d) => sum + d.rxBytes, 0);
155+
return { tx, rx, total: tx + rx };
156+
});
157+
142158
// Calculate totals for selected range only (in historical mode) or all data
143159
const totals = $derived.by(() => {
144160
const selectedStart = $dataSourceStore.selectedStart;
145161
const selectedEnd = $dataSourceStore.selectedEnd;
146162
const mode = $dataSourceStore.mode;
147163
148-
let dataToSum = chartData;
149-
150-
// In historical mode, only sum data within selected range
151-
if (mode === 'historical' && selectedStart && selectedEnd) {
152-
dataToSum = chartData.filter((d) => {
153-
const t = d.time.getTime();
154-
return t >= selectedStart.getTime() && t <= selectedEnd.getTime();
155-
});
164+
// In live mode or if no selection, use full totals
165+
if (mode !== 'historical' || !selectedStart || !selectedEnd) {
166+
return fullTotals;
156167
}
157168
169+
// In historical mode, sum data within selected range
170+
const dataToSum = chartData.filter((d) => {
171+
const t = d.time.getTime();
172+
return t >= selectedStart.getTime() && t <= selectedEnd.getTime();
173+
});
174+
158175
const tx = dataToSum.reduce((sum, d) => sum + d.txBytes, 0);
159176
const rx = dataToSum.reduce((sum, d) => sum + d.rxBytes, 0);
160177
return { tx, rx, total: tx + rx };
161178
});
162179
180+
// Check if we're showing a subset of data
181+
const isShowingSubset = $derived(
182+
$dataSourceStore.mode === 'historical' &&
183+
$dataSourceStore.selectedStart !== null &&
184+
$dataSourceStore.selectedEnd !== null &&
185+
totals.total !== fullTotals.total
186+
);
187+
163188
// Generate time axis ticks
164189
const timeAxisTicks = $derived.by(() => {
165190
if (chartData.length === 0) return [];
@@ -193,7 +218,7 @@
193218
<div class="flex items-center gap-4">
194219
<span class="text-xs font-medium text-muted-foreground">
195220
Bandwidth Over Time
196-
{#if $dataSourceStore.mode === 'historical'}
221+
{#if isShowingSubset}
197222
<span class="text-primary/70">(selected range)</span>
198223
{/if}
199224
</span>
@@ -209,6 +234,11 @@
209234
<span class="text-muted-foreground">
210235
Total: {formatBytes(totals.total)}
211236
</span>
237+
{#if isShowingSubset}
238+
<span class="text-muted-foreground/60">
239+
| All: {formatBytes(fullTotals.total)}
240+
</span>
241+
{/if}
212242
</div>
213243
</div>
214244
{#if chartData.length > 0}

frontend/src/lib/components/logs/LogViewer.svelte

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,28 @@
11
<script lang="ts">
2-
import { networkLogs, uiStore, filteredNodes, filteredEdges, filterStore } from '$lib/stores';
2+
import { networkLogs, uiStore, filteredNodes, filteredEdges, filterStore, devices } from '$lib/stores';
33
import { formatBytes, formatDate, extractIP, getProtocolName } from '$lib/utils';
44
import { ArrowRight } from 'lucide-svelte';
55
import type { NetworkLog } from '$lib/types';
66
7+
// Build IP to device name lookup map
8+
const ipToDevice = $derived.by(() => {
9+
const map = new Map<string, string>();
10+
for (const device of $devices) {
11+
const displayName = device.hostname || device.name.split('.')[0];
12+
for (const addr of device.addresses) {
13+
map.set(addr, displayName);
14+
}
15+
}
16+
return map;
17+
});
18+
19+
// Helper to resolve IP to device name or return the IP
20+
function resolveIP(address: string): { ip: string; deviceName?: string } {
21+
const ip = extractIP(address);
22+
const deviceName = ipToDevice.get(ip);
23+
return { ip, deviceName };
24+
}
25+
726
// Flatten traffic entries for display
827
interface FlatTrafficEntry {
928
logged: string;
@@ -154,6 +173,8 @@
154173
</thead>
155174
<tbody>
156175
{#each flattenedEntries as entry}
176+
{@const srcResolved = resolveIP(entry.src)}
177+
{@const dstResolved = resolveIP(entry.dst)}
157178
<tr class="border-b border-border/50 hover:bg-secondary/50">
158179
<td class="whitespace-nowrap px-2 py-1 text-muted-foreground">
159180
{formatDate(entry.logged).split(',')[1]?.trim() || formatDate(entry.logged)}
@@ -166,9 +187,21 @@
166187
</td>
167188
<td class="px-2 py-1">
168189
<div class="flex items-center gap-1">
169-
<span class="truncate" title={entry.src}>{extractIP(entry.src)}</span>
190+
<span class="truncate" title={srcResolved.deviceName ? `${srcResolved.deviceName} (${srcResolved.ip})` : srcResolved.ip}>
191+
{#if srcResolved.deviceName}
192+
<span class="text-primary">{srcResolved.deviceName}</span>
193+
{:else}
194+
{srcResolved.ip}
195+
{/if}
196+
</span>
170197
<ArrowRight class="h-3 w-3 shrink-0 text-muted-foreground" />
171-
<span class="truncate" title={entry.dst}>{extractIP(entry.dst)}</span>
198+
<span class="truncate" title={dstResolved.deviceName ? `${dstResolved.deviceName} (${dstResolved.ip})` : dstResolved.ip}>
199+
{#if dstResolved.deviceName}
200+
<span class="text-primary">{dstResolved.deviceName}</span>
201+
{:else}
202+
{dstResolved.ip}
203+
{/if}
204+
</span>
172205
</div>
173206
</td>
174207
<td class="px-2 py-1">

0 commit comments

Comments
 (0)