refactor(host-detail): collapse NodeDetails into EntityDetailModal (#578) - #581
Conversation
) 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>
|
Warning Review limit reached
Next review available in: 17 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (10)
📝 WalkthroughWalkthroughEntityDetailModal 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. ChangesGraph modal consolidation
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (13)
frontend/src/components/common/EntityDetailModal/EntityDetailModal.cssfrontend/src/components/common/EntityDetailModal/EntityDetailModal.tsxfrontend/src/components/common/EntityDetailModal/graphNodeEntity.tsfrontend/src/components/common/EntityDetailModal/index.tsfrontend/src/components/common/EntityDetailModal/sections/GraphHistoryTab.tsxfrontend/src/components/common/EntityDetailModal/sections/GraphNodeDetailsSection.tsxfrontend/src/components/common/EntityDetailModal/types.tsfrontend/src/components/monitor/SnapshotDetailModal/SnapshotDetailModal.tsxfrontend/src/components/network/NodeDetails/NodeDetails.cssfrontend/src/components/network/NodeDetails/NodeDetails.tsxfrontend/src/components/network/NodeDetails/index.tsfrontend/src/pages/Compare/ComparePage.tsxfrontend/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
| 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]}`; | ||
| } |
There was a problem hiding this comment.
🎯 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.
| 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.
- 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>
|
Thanks @coderabbitai — all 4 addressed in 68bfe06:
Type-check clean; 145 tests pass; graph modal re-verified end-to-end. |
|
Tip For best results, initiate chat on the files or code changes.
If you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit! |
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>
Closes #578.
Problem
Host inspection rendered through two near-identical modals — the graph's
NodeDetailsandEntityDetailModal(monitor drift panels / conversation / analysis). After #575 both embedded the sharedHostIdentitySection, 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: clicking203.0.113.1in monitor vs the graph).Change
Unify on
EntityDetailModalas the single host-detail shell. When given graph context (graphNode/graphEdges), it renders the graph-only pieces that used to live inNodeDetails: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).All three
NodeDetailscall sites migrated via a smallgraphNodeEntity(node)helper (L2 → DEVICE/MAC, else IP):pages/NetworkDiagram/NetworkDiagramPage.tsxpages/Compare/ComparePage.tsxcomponents/monitor/SnapshotDetailModal/SnapshotDetailModal.tsxNodeDetails.tsx+.css+index.tsdeleted.Side fix
The
.tp-nested-modalz-index rules lived inNodeDetails.cssand 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 withEntityDetailModal.css, fixing that everywhere.Verification
203.0.113.1node 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