diff --git a/web/src/lib/api.ts b/web/src/lib/api.ts index 2e3c575..9e5a452 100644 --- a/web/src/lib/api.ts +++ b/web/src/lib/api.ts @@ -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 { + 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 { diff --git a/web/src/lib/components/EntityDetailContent.svelte b/web/src/lib/components/EntityDetailContent.svelte index 78f22ab..ecb399c 100644 --- a/web/src/lib/components/EntityDetailContent.svelte +++ b/web/src/lib/components/EntityDetailContent.svelte @@ -6,7 +6,7 @@ import DOMPurify from 'dompurify' import { fetchEntity, - fetchGraph, + fetchEntityRelations, fetchMetrics, fetchEntityEvents, fetchEntitySignals, @@ -58,10 +58,10 @@ let actingSignal = $state(null) let chartContainers: Record = {} - // `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