Add route diagram page with execution overlay and group-aware APIs
Backend: Add group filtering to agent list, search, stats, and timeseries
endpoints. Add diagram lookup by group+routeId. Resolve application group
to agent IDs server-side for ClickHouse IN-clause queries.
Frontend: New route detail page at /apps/{group}/routes/{routeId} with
three tabs (Diagram, Performance, Processor Tree). SVG diagram rendering
with panzoom, execution overlay (glow effects, duration/sequence badges,
flow particles, minimap), and processor detail panel. uPlot charts for
performance tab replacing old SVG sparklines. Ctrl+Click from
ExecutionExplorer navigates to route diagram with overlay.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -236,6 +236,14 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "group",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "offset",
|
||||
"in": "query",
|
||||
@@ -933,6 +941,22 @@
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "routeId",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "group",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -984,6 +1008,22 @@
|
||||
"format": "int32",
|
||||
"default": 24
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "routeId",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "group",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -1097,6 +1137,49 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/diagrams": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Diagrams"
|
||||
],
|
||||
"summary": "Find diagram by application group and route ID",
|
||||
"description": "Resolves group to agent IDs and finds the latest diagram for the route",
|
||||
"operationId": "findByGroupAndRoute",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "group",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "routeId",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Diagram layout returned",
|
||||
"content": {
|
||||
"*/*": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/DiagramLayout"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "No diagram found for the given group and route"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/diagrams/{contentHash}/render": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -1191,7 +1274,7 @@
|
||||
"Agent Management"
|
||||
],
|
||||
"summary": "List all agents",
|
||||
"description": "Returns all registered agents, optionally filtered by status",
|
||||
"description": "Returns all registered agents, optionally filtered by status and/or group",
|
||||
"operationId": "listAgents",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -1201,6 +1284,14 @@
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "group",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -1509,6 +1600,15 @@
|
||||
"processorType": {
|
||||
"type": "string"
|
||||
},
|
||||
"group": {
|
||||
"type": "string"
|
||||
},
|
||||
"agentIds": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"offset": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
@@ -2119,6 +2219,12 @@
|
||||
"height": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"children": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/PositionedNode"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
31
ui/src/api/queries/diagrams.ts
Normal file
31
ui/src/api/queries/diagrams.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { api } from '../client';
|
||||
|
||||
export function useDiagramLayout(contentHash: string | null) {
|
||||
return useQuery({
|
||||
queryKey: ['diagrams', 'layout', contentHash],
|
||||
queryFn: async () => {
|
||||
const { data, error } = await api.GET('/diagrams/{contentHash}/render', {
|
||||
params: { path: { contentHash: contentHash! } },
|
||||
headers: { Accept: 'application/json' },
|
||||
});
|
||||
if (error) throw new Error('Failed to load diagram layout');
|
||||
return data!;
|
||||
},
|
||||
enabled: !!contentHash,
|
||||
});
|
||||
}
|
||||
|
||||
export function useDiagramByRoute(group: string | undefined, routeId: string | undefined) {
|
||||
return useQuery({
|
||||
queryKey: ['diagrams', 'byRoute', group, routeId],
|
||||
queryFn: async () => {
|
||||
const { data, error } = await api.GET('/diagrams', {
|
||||
params: { query: { group: group!, routeId: routeId! } },
|
||||
});
|
||||
if (error) throw new Error('Failed to load diagram for route');
|
||||
return data!;
|
||||
},
|
||||
enabled: !!group && !!routeId,
|
||||
});
|
||||
}
|
||||
61
ui/src/api/schema.d.ts
vendored
61
ui/src/api/schema.d.ts
vendored
@@ -394,6 +394,26 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/diagrams": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/**
|
||||
* Find diagram by application group and route ID
|
||||
* @description Resolves group to agent IDs and finds the latest diagram for the route
|
||||
*/
|
||||
get: operations["findByGroupAndRoute"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/diagrams/{contentHash}/render": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@@ -440,7 +460,7 @@ export interface paths {
|
||||
};
|
||||
/**
|
||||
* List all agents
|
||||
* @description Returns all registered agents, optionally filtered by status
|
||||
* @description Returns all registered agents, optionally filtered by status and/or group
|
||||
*/
|
||||
get: operations["listAgents"];
|
||||
put?: never;
|
||||
@@ -558,6 +578,8 @@ export interface components {
|
||||
routeId?: string;
|
||||
agentId?: string;
|
||||
processorType?: string;
|
||||
group?: string;
|
||||
agentIds?: string[];
|
||||
/** Format: int32 */
|
||||
offset?: number;
|
||||
/** Format: int32 */
|
||||
@@ -759,6 +781,7 @@ export interface components {
|
||||
width?: number;
|
||||
/** Format: double */
|
||||
height?: number;
|
||||
children?: components["schemas"]["PositionedNode"][];
|
||||
};
|
||||
/** @description OIDC configuration for SPA login flow */
|
||||
OidcPublicConfigResponse: {
|
||||
@@ -915,6 +938,7 @@ export interface operations {
|
||||
routeId?: string;
|
||||
agentId?: string;
|
||||
processorType?: string;
|
||||
group?: string;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
sortField?: string;
|
||||
@@ -1452,6 +1476,8 @@ export interface operations {
|
||||
query: {
|
||||
from: string;
|
||||
to?: string;
|
||||
routeId?: string;
|
||||
group?: string;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
@@ -1476,6 +1502,8 @@ export interface operations {
|
||||
from: string;
|
||||
to?: string;
|
||||
buckets?: number;
|
||||
routeId?: string;
|
||||
group?: string;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
@@ -1561,6 +1589,36 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
findByGroupAndRoute: {
|
||||
parameters: {
|
||||
query: {
|
||||
group: string;
|
||||
routeId: string;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Diagram layout returned */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"*/*": components["schemas"]["DiagramLayout"];
|
||||
};
|
||||
};
|
||||
/** @description No diagram found for the given group and route */
|
||||
404: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
renderDiagram: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@@ -1635,6 +1693,7 @@ export interface operations {
|
||||
parameters: {
|
||||
query?: {
|
||||
status?: string;
|
||||
group?: string;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
|
||||
@@ -15,3 +15,6 @@ export type OidcTestResult = components['schemas']['OidcTestResult'];
|
||||
export type OidcPublicConfigResponse = components['schemas']['OidcPublicConfigResponse'];
|
||||
export type AuthTokenResponse = components['schemas']['AuthTokenResponse'];
|
||||
export type ErrorResponse = components['schemas']['ErrorResponse'];
|
||||
export type DiagramLayout = components['schemas']['DiagramLayout'];
|
||||
export type PositionedNode = components['schemas']['PositionedNode'];
|
||||
export type PositionedEdge = components['schemas']['PositionedEdge'];
|
||||
|
||||
Reference in New Issue
Block a user