feat(web): add Fleet dashboard view for cross-instance observability
Adds a new /fleet route that shows a fleet-wide overview of all configured Honcho instances. Each row renders per-instance metrics (workspace count, total conclusions, queue activity, last seen, health) by fanning out scoped fetches via createScopedClient, and aggregates into top-level metric cards. Reuses the Phase 2 scoped-client pattern. Extends compareQueries.ts with useScopedQueueStatus and useScopedConclusionsCount, plus option builders so useQueries can fan out per-workspace requests inside FleetRow without duplicating query logic. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,9 @@ const CK = {
|
||||
["compare", instId, "peer-representation", wsId, pId] as const,
|
||||
peerCard: (instId: string, wsId: string, pId: string) =>
|
||||
["compare", instId, "peer-card", wsId, pId] as const,
|
||||
queueStatus: (instId: string, wsId: string) => ["compare", instId, "queue-status", wsId] as const,
|
||||
conclusionsCount: (instId: string, wsId: string) =>
|
||||
["compare", instId, "conclusions-count", wsId] as const,
|
||||
};
|
||||
|
||||
export function useScopedWorkspaces(instance: Instance, page = 1, pageSize = 20) {
|
||||
@@ -83,3 +86,43 @@ export function useScopedPeerCard(instance: Instance, workspaceId: string, peerI
|
||||
enabled: Boolean(workspaceId) && Boolean(peerId),
|
||||
});
|
||||
}
|
||||
|
||||
// Option builders — used by both single-fetch hooks and useQueries fan-out (e.g. Fleet view).
|
||||
|
||||
export function scopedQueueStatusOptions(instance: Instance, workspaceId: string) {
|
||||
return {
|
||||
queryKey: CK.queueStatus(instance.id, workspaceId),
|
||||
queryFn: async () => {
|
||||
const client = createScopedClient(instance);
|
||||
const { data, error } = await client.GET("/v3/workspaces/{workspace_id}/queue/status", {
|
||||
params: { path: { workspace_id: workspaceId } },
|
||||
});
|
||||
return data ?? err(error);
|
||||
},
|
||||
enabled: Boolean(workspaceId),
|
||||
refetchInterval: 10_000,
|
||||
} as const;
|
||||
}
|
||||
|
||||
export function scopedConclusionsCountOptions(instance: Instance, workspaceId: string) {
|
||||
return {
|
||||
queryKey: CK.conclusionsCount(instance.id, workspaceId),
|
||||
queryFn: async () => {
|
||||
const client = createScopedClient(instance);
|
||||
const { data, error } = await client.POST("/v3/workspaces/{workspace_id}/conclusions/list", {
|
||||
params: { path: { workspace_id: workspaceId }, query: { page: 1, size: 1 } },
|
||||
body: {},
|
||||
});
|
||||
return data ?? err(error);
|
||||
},
|
||||
enabled: Boolean(workspaceId),
|
||||
} as const;
|
||||
}
|
||||
|
||||
export function useScopedQueueStatus(instance: Instance, workspaceId: string) {
|
||||
return useQuery(scopedQueueStatusOptions(instance, workspaceId));
|
||||
}
|
||||
|
||||
export function useScopedConclusionsCount(instance: Instance, workspaceId: string) {
|
||||
return useQuery(scopedConclusionsCountOptions(instance, workspaceId));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user