Skip to content

Commit f388069

Browse files
NotYuShengclaude
andauthored
fix: top external ASNs panel showing zero entries (#179)
* fix: check both src and dst IPs when computing top external ASNs Previously only dstIp was checked, so external IPs appearing as the source (inbound connections, responses) were silently skipped, causing the Top External Destinations panel to show zero entries. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: handle sql.Timestamp cast in beacon candidate detection JPA native queries return java.sql.Timestamp for timestamp columns, not LocalDateTime. The direct cast threw a ClassCastException which was silently caught, causing all aggregates (including top external ASNs) to be empty on every story generation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: log tshark stderr on parse failure Previously stderr was discarded, making it impossible to diagnose why tshark rejected a file. Now stderr is captured and included in both the log message and the thrown exception. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: dark mode text contrast for error message and loading view - ErrorMessage: override hardcoded dark-red title/text colors with light pink tones under [data-theme='dark'] - sgds-overrides: add explicit dark mode rules for .text-body and .text-muted so Bootstrap utility classes respect the dark theme; also wire --bs-body-color and --bs-secondary-color tokens Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: address PR #179 review comments - Replace StringBuilder with StringBuffer for stderrBuf to prevent race condition if join(5000) times out and background thread is still writing - Cache isPrivate() results in computeTopAsns to avoid repeated string parsing per conversation; replace nested ternary with if-else for readability - Remove duplicate [data-theme='dark'] .text-muted rule in sgds-overrides.css Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent db218b9 commit f388069

File tree

4 files changed

+50
-7
lines changed

4 files changed

+50
-7
lines changed

backend/src/main/java/com/tracepcap/analysis/service/PcapParserService.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,24 @@ public PcapAnalysisResult analyzePcapFile(File pcapFile) {
8181
"arp.dst.proto_ipv4",
8282
"-e",
8383
"eth.dst");
84-
pb.redirectError(ProcessBuilder.Redirect.DISCARD);
84+
pb.redirectErrorStream(false);
8585

8686
long packetNumber = 0;
8787
try {
8888
Process process = pb.start();
8989

90+
// Drain stderr in a background thread so it doesn't block stdout
91+
StringBuffer stderrBuf = new StringBuffer();
92+
Thread stderrThread = new Thread(() -> {
93+
try (BufferedReader err =
94+
new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
95+
String l;
96+
while ((l = err.readLine()) != null) stderrBuf.append(l).append('\n');
97+
} catch (Exception ignored) {}
98+
});
99+
stderrThread.setDaemon(true);
100+
stderrThread.start();
101+
90102
try (BufferedReader reader =
91103
new BufferedReader(new InputStreamReader(process.getInputStream()))) {
92104
String line;
@@ -221,10 +233,12 @@ public PcapAnalysisResult analyzePcapFile(File pcapFile) {
221233
}
222234
}
223235

236+
stderrThread.join(5000);
224237
int exitCode = process.waitFor();
225238
if (exitCode != 0 && packetNumber == 0) {
226-
log.error("tshark exited with code {} and parsed 0 packets", exitCode);
227-
throw new RuntimeException("tshark failed to parse PCAP file (exit " + exitCode + ")");
239+
String stderr = stderrBuf.toString().trim();
240+
log.error("tshark exited with code {} and parsed 0 packets. stderr: {}", exitCode, stderr);
241+
throw new RuntimeException("tshark failed to parse PCAP file (exit " + exitCode + "): " + stderr);
228242
}
229243

230244
} catch (RuntimeException e) {

backend/src/main/java/com/tracepcap/story/service/StoryAggregatesService.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,21 @@ private List<AsnEntry> computeTopAsns(UUID fileId, long totalBytes) {
9090
// Fetch all conversations to get dst IPs and their byte counts
9191
List<ConversationEntity> all = conversationRepository.findByFileId(fileId);
9292

93-
// Group external dstIps → total bytes
93+
// Group external IPs → total bytes (check both src and dst)
9494
Map<String, Long> ipBytes = new HashMap<>();
9595
Map<String, Long> ipFlows = new HashMap<>();
96+
Map<String, Boolean> privateCache = new HashMap<>();
9697
for (ConversationEntity c : all) {
97-
String ip = c.getDstIp();
98-
if (ip != null && !isPrivate(ip)) {
98+
String dst = c.getDstIp();
99+
String src = c.getSrcIp();
100+
// Prefer dstIp as the "remote" endpoint; fall back to srcIp if dst is private/null
101+
String ip = null;
102+
if (dst != null && !privateCache.computeIfAbsent(dst, StoryAggregatesService::isPrivate)) {
103+
ip = dst;
104+
} else if (src != null && !privateCache.computeIfAbsent(src, StoryAggregatesService::isPrivate)) {
105+
ip = src;
106+
}
107+
if (ip != null) {
99108
ipBytes.merge(ip, c.getTotalBytes(), Long::sum);
100109
ipFlows.merge(ip, 1L, Long::sum);
101110
}
@@ -207,8 +216,11 @@ record FlowKey(String src, String dst, String port, String proto, String app) {}
207216
String proto = String.valueOf(row[3]);
208217
String app = row[4] != null ? String.valueOf(row[4]) : null;
209218
FlowKey key = new FlowKey(src, dst, port, proto, app);
219+
LocalDateTime ts = row[5] instanceof java.sql.Timestamp t
220+
? t.toLocalDateTime()
221+
: (LocalDateTime) row[5];
210222
groups.computeIfAbsent(key, k -> new ArrayList<>())
211-
.add((LocalDateTime) row[5]);
223+
.add(ts);
212224
}
213225

214226
List<BeaconCandidate> candidates = new ArrayList<>();

frontend/src/assets/styles/sgds-overrides.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@
3333
[data-theme='dark'] {
3434
color-scheme: dark;
3535

36+
/* ── Bootstrap utility tokens (text-muted, text-body, etc.) ── */
37+
--bs-body-color: var(--tp-text);
38+
--bs-secondary-color: var(--tp-text-muted);
39+
--bs-tertiary-color: var(--tp-text-muted);
40+
3641
/* ── SGDS global tokens ── */
3742
--sgds-body-bg: var(--tp-bg);
3843
--sgds-body-color: var(--tp-text);
@@ -307,6 +312,10 @@
307312
border-color: var(--tp-border) !important;
308313
}
309314

315+
[data-theme='dark'] .text-body {
316+
color: var(--tp-text) !important;
317+
}
318+
310319
/* Bootstrap utility classes */
311320
[data-theme='dark'] .bg-light,
312321
[data-theme='dark'] .bg-white {

frontend/src/components/common/ErrorMessage/ErrorMessage.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@
3131
color: #58151c;
3232
}
3333

34+
[data-theme='dark'] .error-title {
35+
color: #f8d7da;
36+
}
37+
38+
[data-theme='dark'] .error-text {
39+
color: #f1aeb5;
40+
}
41+
3442
.error-content button {
3543
display: flex;
3644
align-items: center;

0 commit comments

Comments
 (0)