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
|
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 type Health = 'healthy' | 'degraded' | 'down' | 'unknown'
|
||||||
|
|
||||||
export interface GraphView {
|
export interface GraphView {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import DOMPurify from 'dompurify'
|
import DOMPurify from 'dompurify'
|
||||||
import {
|
import {
|
||||||
fetchEntity,
|
fetchEntity,
|
||||||
fetchGraph,
|
fetchEntityRelations,
|
||||||
fetchMetrics,
|
fetchMetrics,
|
||||||
fetchEntityEvents,
|
fetchEntityEvents,
|
||||||
fetchEntitySignals,
|
fetchEntitySignals,
|
||||||
@@ -58,10 +58,10 @@
|
|||||||
let actingSignal = $state<string | null>(null)
|
let actingSignal = $state<string | null>(null)
|
||||||
let chartContainers: Record<string, HTMLDivElement> = {}
|
let chartContainers: Record<string, HTMLDivElement> = {}
|
||||||
|
|
||||||
// `relations` is the whole depth-1 neighborhood's edge set (any edge
|
// fetchEntityRelations already scopes to edges incident to this entity
|
||||||
// between any two nodes in the subgraph, e.g. two sibling LXCs' shared
|
// (source OR target = entity, both directions), so a plain split by which
|
||||||
// LAN), not just edges touching this entity — so "incoming"/"outgoing"
|
// side matches is enough — no risk of an unrelated sibling-to-sibling edge
|
||||||
// only make sense for the subset actually incident to it.
|
// sneaking into either group.
|
||||||
const outgoingRelations = $derived(entity ? relations.filter((r) => r.source === entity!.slug) : [])
|
const outgoingRelations = $derived(entity ? relations.filter((r) => r.source === entity!.slug) : [])
|
||||||
const incomingRelations = $derived(
|
const incomingRelations = $derived(
|
||||||
entity ? relations.filter((r) => r.target === entity!.slug && r.source !== entity!.slug) : []
|
entity ? relations.filter((r) => r.target === entity!.slug && r.source !== entity!.slug) : []
|
||||||
@@ -74,8 +74,8 @@
|
|||||||
loading = false
|
loading = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const [graphView, m, ev, sig, tk, kh, oc, ch, aa, au] = await Promise.all([
|
const [rel, m, ev, sig, tk, kh, oc, ch, aa, au] = await Promise.all([
|
||||||
fetchGraph({ root: entity.id, depth: 1 }),
|
fetchEntityRelations(entity.id),
|
||||||
fetchMetrics(entity.id),
|
fetchMetrics(entity.id),
|
||||||
fetchEntityEvents(entity.id),
|
fetchEntityEvents(entity.id),
|
||||||
fetchEntitySignals(entity.id),
|
fetchEntitySignals(entity.id),
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
fetchAgentActivity({ entity_id: entity.id, limit: 50 }),
|
fetchAgentActivity({ entity_id: entity.id, limit: 50 }),
|
||||||
fetchAudit({ entity_id: entity.id, limit: 50 })
|
fetchAudit({ entity_id: entity.id, limit: 50 })
|
||||||
])
|
])
|
||||||
relations = graphView?.edges ?? []
|
relations = rel
|
||||||
metrics = m
|
metrics = m
|
||||||
events = ev
|
events = ev
|
||||||
signals = sig
|
signals = sig
|
||||||
|
|||||||
Reference in New Issue
Block a user