feat(web): browse Knowledge Base by mixed Network/Fleet/Services/Storage/Identity/Knowledge categories
Replace the layer-based (Infrastructure/Governance/Cognition) browsing tabs with a synthesized category taxonomy built from the ontology's finer-grained `domain` field, since layer lumped unrelated entity types (an LXC and a DNS record and a storage volume) into one bucket. Network and Fleet each span two domains, so the table view now fans out per-domain fetches and merges, while the graph view maps domain->category client-side. Also carries over several detail-panel polish items (Tasks-not-raw-executions, slug URL encoding, MultiSelectFilter) from earlier in this session. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -185,6 +185,7 @@ export interface Entity {
|
||||
updated_at: string
|
||||
health?: EntityHealth | null
|
||||
last_check_at?: string | null
|
||||
maintenance_until?: string | null
|
||||
}
|
||||
|
||||
export interface EntityFilters {
|
||||
@@ -518,14 +519,17 @@ export interface BlastRadiusItem {
|
||||
}
|
||||
|
||||
export async function fetchBlastRadius(id: string): Promise<BlastRadiusItem[]> {
|
||||
const res = await fetchWithAuth(`${API}/entities/${id}/blast-radius`)
|
||||
const res = await fetchWithAuth(`${API}/entities/${encodeURIComponent(id)}/blast-radius`)
|
||||
if (!res.ok) return []
|
||||
const data = await res.json()
|
||||
return data.items ?? []
|
||||
}
|
||||
|
||||
// id is commonly a slug, not a UUID (e.g. "document:infrastructure/network") —
|
||||
// slugs can contain '/', which must be percent-encoded or it splits the path
|
||||
// into extra segments the router won't match.
|
||||
export async function fetchEntity(id: string): Promise<Entity | null> {
|
||||
const res = await fetchWithAuth(`${API}/entities/${id}`)
|
||||
const res = await fetchWithAuth(`${API}/entities/${encodeURIComponent(id)}`)
|
||||
if (!res.ok) return null
|
||||
return res.json()
|
||||
}
|
||||
@@ -621,6 +625,44 @@ export async function fetchEntityExecutions(entityId: string): Promise<Execution
|
||||
return data.items ?? []
|
||||
}
|
||||
|
||||
export interface EntityTask {
|
||||
task: Entity
|
||||
executionCount: number
|
||||
}
|
||||
|
||||
// Executions aren't directly browsable from an entity in a useful way — what
|
||||
// matters is which task/session acted on it, and how many times. There's no
|
||||
// "incoming relationships" endpoint (blast_radius/graph only walks outgoing
|
||||
// edges), so this composes it client-side: find executions targeting this
|
||||
// entity, then check each task's own outgoing edges (task --involves--> entity
|
||||
// directly, or task --involves--> execution) to attribute them. Cheap while
|
||||
// the task count is small; would want a dedicated query if that changes.
|
||||
export async function fetchEntityTasks(entity: Entity): Promise<EntityTask[]> {
|
||||
const [executions, tasks] = await Promise.all([
|
||||
fetchEntityExecutions(entity.id),
|
||||
fetchEntities({ type: 'task' })
|
||||
])
|
||||
const executionIds = new Set(executions.map((e) => e.id))
|
||||
|
||||
const results = await Promise.all(
|
||||
tasks.map(async (task): Promise<EntityTask | null> => {
|
||||
const g = await fetchGraph({ root: task.slug, depth: 1 })
|
||||
if (!g) return null
|
||||
const involvesThisEntity = g.edges.some((e) => e.type === 'involves' && e.target === entity.slug)
|
||||
const nodeTypeById = new Map(g.nodes.map((n) => [n.id, n.type]))
|
||||
const idBySlug = new Map(g.nodes.map((n) => [n.slug, n.id]))
|
||||
const executionCount = g.edges.filter((e) => {
|
||||
if (e.type !== 'involves') return false
|
||||
const targetId = idBySlug.get(e.target)
|
||||
return targetId != null && nodeTypeById.get(targetId) === 'execution' && executionIds.has(targetId)
|
||||
}).length
|
||||
if (!involvesThisEntity && executionCount === 0) return null
|
||||
return { task, executionCount }
|
||||
})
|
||||
)
|
||||
return results.filter((r): r is EntityTask => r !== null)
|
||||
}
|
||||
|
||||
export interface Check {
|
||||
id: string
|
||||
slug: string
|
||||
|
||||
Reference in New Issue
Block a user