Skip to content

refactor(host-detail): collapse NodeDetails into EntityDetailModal (#578) - #581

Merged
NotYuSheng merged 4 commits into
mainfrom
feature/collapse-host-modals
Jul 26, 2026
Merged

refactor(host-detail): collapse NodeDetails into EntityDetailModal (#578)#581
NotYuSheng merged 4 commits into
mainfrom
feature/collapse-host-modals

Conversation

@NotYuSheng

@NotYuSheng NotYuSheng commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Closes #578.

Problem

Host inspection rendered through two near-identical modals — the graph's NodeDetails and EntityDetailModal (monitor drift panels / conversation / analysis). After #575 both embedded the shared HostIdentitySection, but the surrounding shells still diverged, so the same host looked "almost but not quite" the same depending on which tab you clicked from (the concrete report: clicking 203.0.113.1 in monitor vs the graph).

Change

Unify on EntityDetailModal as the single host-detail shell. When given graph context (graphNode / graphEdges), it renders the graph-only pieces that used to live in NodeDetails:

  • GraphNodeDetailsSection — ghost-node warning, measured traffic counters, protocol chips, and the per-peer Connections table (built from graph edges).
  • GraphHistoryTab — the cross-capture History tab with the per-file role trail (carried/stale badges, "current" marker).
  • Service-role log tabs (DNS/web/…) and the change-event highlight banner.

All three NodeDetails call sites migrated via a small graphNodeEntity(node) helper (L2 → DEVICE/MAC, else IP):

  • pages/NetworkDiagram/NetworkDiagramPage.tsx
  • pages/Compare/ComparePage.tsx
  • components/monitor/SnapshotDetailModal/SnapshotDetailModal.tsx

NodeDetails.tsx + .css + index.ts deleted.

Side fix

The .tp-nested-modal z-index rules lived in NodeDetails.css and only loaded when the graph panel was mounted — so the add-evidence / explainer / role modals could stack wrong on non-graph surfaces. They now ship with EntityDetailModal.css, fixing that everywhere.

Verification

  • Frontend-only; no backend/OpenAPI changes.
  • Type-check clean; all 145 unit tests pass.
  • Playwright-verified: clicking the 203.0.113.1 node in the network graph opens the unified modal showing Identity + evidence axes + Geolocation (the original gap) + Connections + Protocols + History/service tabs — see below.

Now every surface — graph, monitor snapshot, conversation, analysis — shows the identical host-detail modal.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a unified entity details modal for network graph nodes across comparison, monitoring, and network diagram views.
    • Added graph-specific details, connection summaries, traffic metrics, protocols, phantom-node warnings, and peer navigation.
    • Added history and service-role tabs with capture information, status indicators, and links to analysis views.
    • Added change highlights and improved nested modal layering and navigation behavior.

)

There were two near-identical host-detail modals: the graph's NodeDetails
and the entity panel EntityDetailModal (monitor/conversation/analysis).
After #575 both embedded HostIdentitySection, but the shells still diverged
— so a host clicked in the graph looked "almost but not quite" like the same
host clicked in monitor, and geo/other bits differed by surface.

Unify on EntityDetailModal as the single shell. When given graph context
(`graphNode`/`graphEdges`), it now renders the graph-only pieces that used to
live in NodeDetails:
- GraphNodeDetailsSection: ghost-node warning, traffic counters, protocol
  chips, and the per-peer Connections table (from graph edges).
- GraphHistoryTab: the cross-capture role-trail history (its own tab).
- Service-role log tabs (DNS/web/…) and the change-event highlight banner.

Migrate all three NodeDetails call sites — NetworkDiagramPage, ComparePage,
SnapshotDetailModal (monitor) — to EntityDetailModal via a small
`graphNodeEntity(node)` helper (L2 → DEVICE/MAC, else IP). Delete NodeDetails
and its CSS.

Side fix: the .tp-nested-modal z-index rules lived in NodeDetails.css and
only loaded when the graph panel was mounted; they now ship with
EntityDetailModal.css, so the add-evidence/explainer/role modals stack
correctly on every surface, not just the graph.

Frontend-only. Type-check clean; all 145 tests pass. Playwright-verified the
graph node click opens the unified modal with Identity + evidence axes +
geolocation + Connections + Protocols + History/service tabs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@NotYuSheng, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 17 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: eb3f8515-0d8a-4a53-b1c9-05652383f6e5

📥 Commits

Reviewing files that changed from the base of the PR and between c6ec277 and df9dc02.

📒 Files selected for processing (10)
  • frontend/src/components/common/EntityDetailModal/format.ts
  • frontend/src/components/common/EntityDetailModal/sections/GraphHistoryTab.tsx
  • frontend/src/components/common/EntityDetailModal/sections/GraphNodeDetailsSection.tsx
  • frontend/src/components/common/EntityDetailModal/types.ts
  • frontend/src/components/common/HostIdentitySection/HostIdentitySection.tsx
  • frontend/src/components/conversation/ConversationDetail/ConversationDetail.tsx
  • frontend/src/components/monitor/IpDriftPanel/IpDriftPanel.tsx
  • frontend/src/components/monitor/SubnetDiagramModal/SubnetDiagramModal.tsx
  • frontend/src/utils/__tests__/ipClassification.test.ts
  • frontend/src/utils/ipClassification.ts
📝 Walkthrough

Walkthrough

EntityDetailModal now supports graph-node details, peer connections, capture history, service-role tabs, and graph navigation. Snapshot, compare, and network diagram views use it instead of NodeDetails, which is removed.

Changes

Graph modal consolidation

Layer / File(s) Summary
Graph modal contracts and entity mapping
frontend/src/components/common/EntityDetailModal/types.ts, frontend/src/components/common/EntityDetailModal/graphNodeEntity.ts, frontend/src/components/common/EntityDetailModal/index.ts
Graph context props and tab keys are added, and graph nodes are mapped to modal entity identity props.
Graph details and history sections
frontend/src/components/common/EntityDetailModal/sections/*, frontend/src/components/common/EntityDetailModal/EntityDetailModal.css
Graph node identity, traffic, phantom flags, protocols, peer connections, pagination, and capture history with role enrichment are rendered in dedicated sections.
EntityDetailModal graph integration
frontend/src/components/common/EntityDetailModal/EntityDetailModal.tsx
Graph mode adds history and service-role tabs, graph-specific content, navigation callbacks, change highlights, and nested-modal layering support.
Graph surface migration
frontend/src/components/monitor/SnapshotDetailModal/SnapshotDetailModal.tsx, frontend/src/pages/Compare/ComparePage.tsx, frontend/src/pages/NetworkDiagram/NetworkDiagramPage.tsx, frontend/src/components/network/NodeDetails/*
Graph surfaces render EntityDetailModal with graph context and router navigation; the NodeDetails component and stylesheet are removed.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GraphSurface
  participant EntityDetailModal
  participant GraphNodeDetailsSection
  participant GraphHistoryTab
  GraphSurface->>EntityDetailModal: open selected graph node
  EntityDetailModal->>GraphNodeDetailsSection: render details and connections
  GraphNodeDetailsSection->>GraphSurface: navigate to peer conversations
  EntityDetailModal->>GraphHistoryTab: render history tab
  GraphHistoryTab->>GraphSurface: navigate to capture analysis
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: collapsing NodeDetails into EntityDetailModal.
Linked Issues check ✅ Passed The PR unifies host detail on EntityDetailModal, adds graph-specific sections, migrates the shown call sites, and removes NodeDetails.
Out of Scope Changes check ✅ Passed The changes stay focused on the host-detail modal refactor and related graph-context wiring.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@frontend/src/components/common/EntityDetailModal/sections/GraphHistoryTab.tsx`:
- Around line 35-56: Update the useEffect history-loading flow to track whether
the effect is still current and guard every asynchronous state update, including
setHistory, setHistoryRoles, setHistoryError, and setHistoryLoading, so results
from a previous entityType/entityKey cannot overwrite the newer entity’s state.
Return an effect cleanup that invalidates the request before dependencies change
or the component unmounts; leave the visible-page role-fetch optimization as a
separate follow-up.

In
`@frontend/src/components/common/EntityDetailModal/sections/GraphNodeDetailsSection.tsx`:
- Around line 9-15: Update formatBytes so its unit selection remains valid for
values at or above 1 TB. Extend the sizes array with the required larger units,
or clamp the computed index to the last supported entry, ensuring the formatted
result never uses an undefined label while preserving existing formatting for
smaller values.
- Around line 76-79: Add explicit spacing or a separator between independently
rendered ghost-flag messages in GraphNodeDetailsSection, including the
arp-no-reply and ttl-exceeded combination. Preserve the existing flag conditions
and message precedence while ensuring any multiple displayed sentences are
readable.

In `@frontend/src/components/common/EntityDetailModal/types.ts`:
- Line 7: Update the Tab type alias to preserve autocomplete for the known
'details', 'notes', and 'history' values while still accepting arbitrary dynamic
tab strings such as svc:<role>. Replace the bare string union member with the
string-intersection pattern (string & {}) and leave the existing tab names
unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f2cf7e9d-f3d4-4393-a150-c13900cf189c

📥 Commits

Reviewing files that changed from the base of the PR and between fff6c38 and c6ec277.

📒 Files selected for processing (13)
  • frontend/src/components/common/EntityDetailModal/EntityDetailModal.css
  • frontend/src/components/common/EntityDetailModal/EntityDetailModal.tsx
  • frontend/src/components/common/EntityDetailModal/graphNodeEntity.ts
  • frontend/src/components/common/EntityDetailModal/index.ts
  • frontend/src/components/common/EntityDetailModal/sections/GraphHistoryTab.tsx
  • frontend/src/components/common/EntityDetailModal/sections/GraphNodeDetailsSection.tsx
  • frontend/src/components/common/EntityDetailModal/types.ts
  • frontend/src/components/monitor/SnapshotDetailModal/SnapshotDetailModal.tsx
  • frontend/src/components/network/NodeDetails/NodeDetails.css
  • frontend/src/components/network/NodeDetails/NodeDetails.tsx
  • frontend/src/components/network/NodeDetails/index.ts
  • frontend/src/pages/Compare/ComparePage.tsx
  • frontend/src/pages/NetworkDiagram/NetworkDiagramPage.tsx
💤 Files with no reviewable changes (3)
  • frontend/src/components/network/NodeDetails/NodeDetails.css
  • frontend/src/components/network/NodeDetails/index.ts
  • frontend/src/components/network/NodeDetails/NodeDetails.tsx

Comment on lines +9 to +15
function formatBytes(bytes: number): string {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

formatBytes breaks above 1 TB.

sizes only covers up to 'GB' (index 3), but the computed index is unclamped. For totals ≥ 1024⁴ bytes, sizes[i] is undefined, rendering labels like "1.00 undefined".

🐛 Proposed fix
 function formatBytes(bytes: number): string {
   if (bytes === 0) return '0 B';
   const k = 1024;
-  const sizes = ['B', 'KB', 'MB', 'GB'];
-  const i = Math.floor(Math.log(bytes) / Math.log(k));
+  const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
+  const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), sizes.length - 1);
   return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function formatBytes(bytes: number): string {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
}
function formatBytes(bytes: number): string {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), sizes.length - 1);
return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/src/components/common/EntityDetailModal/sections/GraphNodeDetailsSection.tsx`
around lines 9 - 15, Update formatBytes so its unit selection remains valid for
values at or above 1 TB. Extend the sizes array with the required larger units,
or clamp the computed index to the last supported entry, ensuring the formatted
result never uses an undefined label while preserving existing formatting for
smaller values.

Comment thread frontend/src/components/common/EntityDetailModal/types.ts Outdated
- GraphHistoryTab: add a cancellation guard so a slow history/role fetch for a
  previous node can't overwrite the newer node's state (the modal is reused
  across nodes).
- formatBytes: clamp the unit index and extend to TB/PB so multi-TB totals no
  longer render "1.00 undefined" (fixed once in the shared format.ts;
  GraphNodeDetailsSection now imports it instead of its own copy).
- Phantom-node messages: build them as an array joined with a space so
  arp-no-reply + ttl-exceeded no longer concatenate without a separator.
- Tab type: use `(string & {})` so literal autocomplete for the known tab
  names survives alongside the dynamic `svc:<role>` tabs.

Type-check clean; all 145 tests pass; graph modal re-verified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@NotYuSheng

Copy link
Copy Markdown
Owner Author

Thanks @coderabbitai — all 4 addressed in 68bfe06:

  • GraphHistoryTab stale async (Major) — added an alive cancellation guard around every async state write.
  • formatBytes above 1 TB (Minor) — clamped the unit index and extended to TB/PB. Fixed once in the shared format.ts; GraphNodeDetailsSection now imports it rather than keeping its own copy.
  • Ghost-flag messages run together (Minor) — rebuilt as an array joined with a space, preserving the original precedence.
  • Tab union collapses to string (Trivial) — switched to (string & {}) to keep literal autocomplete alongside dynamic svc:<role> tabs.

Type-check clean; 145 tests pass; graph modal re-verified end-to-end.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@NotYuSheng Thanks for addressing all four findings and validating the unified graph modal. The fixes and test results look good. 🐇


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

NotYuSheng and others added 2 commits July 26, 2026 14:43
Previously the Geolocation block was hidden entirely when a host had no geo
data, so private IPs (and unresolved public ones) showed a blank where the
section should be — reads as missing/broken.

Now the section always renders; when there is no data it says why:
- Private IP (RFC 1918 / loopback / link-local / ULA): "Private IP — not
  geolocated. Internal addresses (RFC 1918) are not registered to any location."
- Public IP with no cache record: "No geolocation on record for this address."

Shared HostIdentitySection change, so it applies on every host-detail surface
(graph, monitor, conversation, analysis) at once.

145 tests pass; verified the private-IP empty state renders.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses the /code-review findings on the geo empty-state:

- **Custom private-range overrides ignored** (correctness): the GeoBlock's
  private/public decision used a bare RFC1918 regex, so an IP the user
  overrode (public→private or vice versa) via Classification-overrides got the
  wrong "why no geo" message. HostIdentitySection now fetches the custom ranges
  and feeds them to the shared isPrivateIp, matching the rest of the app.
- **Duplicated logic**: isPrivateIp/isRfc1918/ipInCidr/ipToInt were copied
  across ConversationDetail, IpDriftPanel, SubnetDiagramModal and (newly)
  HostIdentitySection, already drifting. Consolidated into
  utils/ipClassification.ts as the single source of truth; all callers import
  it. (SubnetDiagramModal's ipInCidr also gains the IPv6 guard it lacked.)
- **Malformed JSDoc**: removed the stray '/**' left over from the replaced
  GeoBlock comment.
- The narrow-regex fallback is now the one canonical, unit-tested heuristic.

New utils/ipClassification.test.ts covers RFC1918 edges, the IPv6 CIDR guard,
and override-wins-either-direction. 153 tests pass; private-IP geo message
re-verified in the UI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@NotYuSheng
NotYuSheng merged commit 6ac9717 into main Jul 26, 2026
3 checks passed
@NotYuSheng
NotYuSheng deleted the feature/collapse-host-modals branch July 26, 2026 07:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Collapse NodeDetails and EntityDetailModal into one host-detail modal

1 participant