fix(web): entity Relations panel was missing true incoming edges
fetchGraph({root, depth:1}) is backed by blast_radius, which only
walks outgoing edges — so it could never surface an edge some other
entity points at this one (e.g. host:hubris —hosts→ lxc:sophia) unless
that other entity happened to also be reachable going forward from
here. The Outgoing/Incoming split was filtering correctly, but
"Incoming" was starved of data by construction.
Switch to GET /entities/{id}/relations?direction=both — a dedicated
endpoint that matches on source_id OR target_id directly — via a new
fetchEntityRelations(). Simplifies the incoming/outgoing derivation
too, since every relation returned is now actually incident to the
entity (no more sibling-edge filtering needed).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -494,6 +494,18 @@ export interface Relationship {
|
||||
valid_to?: string | null
|
||||
}
|
||||
|
||||
// Direct relationships of an entity, both directions — unlike fetchGraph's
|
||||
// blast_radius (which only walks outgoing edges, so it can never surface an
|
||||
// edge some other entity points at this one unless that entity is also
|
||||
// reachable going forward from here), this hits a dedicated endpoint that
|
||||
// matches on source_id OR target_id directly.
|
||||
export async function fetchEntityRelations(id: string): Promise<Relationship[]> {
|
||||
const res = await fetchWithAuth(`${API}/entities/${encodeURIComponent(id)}/relations?direction=both`)
|
||||
if (!res.ok) return []
|
||||
const data = await res.json()
|
||||
return data.items ?? []
|
||||
}
|
||||
|
||||
export type Health = 'healthy' | 'degraded' | 'down' | 'unknown'
|
||||
|
||||
export interface GraphView {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import DOMPurify from 'dompurify'
|
||||
import {
|
||||
fetchEntity,
|
||||
fetchGraph,
|
||||
fetchEntityRelations,
|
||||
fetchMetrics,
|
||||
fetchEntityEvents,
|
||||
fetchEntitySignals,
|
||||
@@ -58,10 +58,10 @@
|
||||
let actingSignal = $state<string | null>(null)
|
||||
let chartContainers: Record<string, HTMLDivElement> = {}
|
||||
|
||||
// `relations` is the whole depth-1 neighborhood's edge set (any edge
|
||||
// between any two nodes in the subgraph, e.g. two sibling LXCs' shared
|
||||
// LAN), not just edges touching this entity — so "incoming"/"outgoing"
|
||||
// only make sense for the subset actually incident to it.
|
||||
// fetchEntityRelations already scopes to edges incident to this entity
|
||||
// (source OR target = entity, both directions), so a plain split by which
|
||||
// side matches is enough — no risk of an unrelated sibling-to-sibling edge
|
||||
// sneaking into either group.
|
||||
const outgoingRelations = $derived(entity ? relations.filter((r) => r.source === entity!.slug) : [])
|
||||
const incomingRelations = $derived(
|
||||
entity ? relations.filter((r) => r.target === entity!.slug && r.source !== entity!.slug) : []
|
||||
@@ -74,8 +74,8 @@
|
||||
loading = false
|
||||
return
|
||||
}
|
||||
const [graphView, m, ev, sig, tk, kh, oc, ch, aa, au] = await Promise.all([
|
||||
fetchGraph({ root: entity.id, depth: 1 }),
|
||||
const [rel, m, ev, sig, tk, kh, oc, ch, aa, au] = await Promise.all([
|
||||
fetchEntityRelations(entity.id),
|
||||
fetchMetrics(entity.id),
|
||||
fetchEntityEvents(entity.id),
|
||||
fetchEntitySignals(entity.id),
|
||||
@@ -86,7 +86,7 @@
|
||||
fetchAgentActivity({ entity_id: entity.id, limit: 50 }),
|
||||
fetchAudit({ entity_id: entity.id, limit: 50 })
|
||||
])
|
||||
relations = graphView?.edges ?? []
|
||||
relations = rel
|
||||
metrics = m
|
||||
events = ev
|
||||
signals = sig
|
||||
|
||||
Reference in New Issue
Block a user