Add route diagram page with execution overlay and group-aware APIs
All checks were successful
CI / build (push) Successful in 1m10s
CI / docker (push) Successful in 1m3s
CI / deploy (push) Successful in 31s

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:
hsiegeln
2026-03-14 21:35:42 +01:00
parent b64edaa16f
commit 7778793e7b
41 changed files with 2770 additions and 26 deletions

View File

@@ -167,6 +167,15 @@ public class AgentRegistryService {
.collect(Collectors.toList());
}
/**
* Return all agents belonging to the given application group.
*/
public List<AgentInfo> findByGroup(String group) {
return agents.values().stream()
.filter(a -> group.equals(a.group()))
.collect(Collectors.toList());
}
/**
* Add a command to an agent's pending queue.
* Notifies the event listener if one is set.

View File

@@ -1,5 +1,7 @@
package com.cameleer3.server.core.search;
import java.util.List;
/**
* Swappable search backend abstraction.
* <p>
@@ -34,6 +36,17 @@ public interface SearchEngine {
*/
ExecutionStats stats(java.time.Instant from, java.time.Instant to);
/**
* Compute aggregate stats scoped to specific routes and agents.
*
* @param from start of the time window
* @param to end of the time window
* @param routeId optional route ID filter
* @param agentIds optional agent ID filter (from group resolution)
* @return execution stats
*/
ExecutionStats stats(java.time.Instant from, java.time.Instant to, String routeId, List<String> agentIds);
/**
* Compute bucketed time-series stats over a time window.
*
@@ -43,4 +56,17 @@ public interface SearchEngine {
* @return bucketed stats
*/
StatsTimeseries timeseries(java.time.Instant from, java.time.Instant to, int bucketCount);
/**
* Compute bucketed time-series stats scoped to specific routes and agents.
*
* @param from start of the time window
* @param to end of the time window
* @param bucketCount number of buckets to divide the window into
* @param routeId optional route ID filter
* @param agentIds optional agent ID filter (from group resolution)
* @return bucketed stats
*/
StatsTimeseries timeseries(java.time.Instant from, java.time.Instant to, int bucketCount,
String routeId, List<String> agentIds);
}

View File

@@ -1,6 +1,7 @@
package com.cameleer3.server.core.search;
import java.time.Instant;
import java.util.List;
/**
* Immutable search criteria for querying route executions.
@@ -21,6 +22,8 @@ import java.time.Instant;
* @param routeId exact match on route_id
* @param agentId exact match on agent_id
* @param processorType matches processor_types array via has()
* @param group application group filter (resolved to agentIds server-side)
* @param agentIds list of agent IDs (resolved from group, used for IN clause)
* @param offset pagination offset (0-based)
* @param limit page size (default 50, max 500)
* @param sortField column to sort by (default: startTime)
@@ -40,6 +43,8 @@ public record SearchRequest(
String routeId,
String agentId,
String processorType,
String group,
List<String> agentIds,
int offset,
int limit,
String sortField,
@@ -74,4 +79,14 @@ public record SearchRequest(
public String sortColumn() {
return SORT_FIELD_TO_COLUMN.getOrDefault(sortField, "start_time");
}
/** Create a copy with resolved agentIds (from group lookup). */
public SearchRequest withAgentIds(List<String> resolvedAgentIds) {
return new SearchRequest(
status, timeFrom, timeTo, durationMin, durationMax, correlationId,
text, textInBody, textInHeaders, textInErrors,
routeId, agentId, processorType, group, resolvedAgentIds,
offset, limit, sortField, sortDir
);
}
}

View File

@@ -1,5 +1,7 @@
package com.cameleer3.server.core.search;
import java.util.List;
/**
* Orchestrates search operations, delegating to a {@link SearchEngine} backend.
* <p>
@@ -36,10 +38,26 @@ public class SearchService {
return engine.stats(from, to);
}
/**
* Compute aggregate execution stats scoped to specific routes and agents.
*/
public ExecutionStats stats(java.time.Instant from, java.time.Instant to,
String routeId, List<String> agentIds) {
return engine.stats(from, to, routeId, agentIds);
}
/**
* Compute bucketed time-series stats over a time window.
*/
public StatsTimeseries timeseries(java.time.Instant from, java.time.Instant to, int bucketCount) {
return engine.timeseries(from, to, bucketCount);
}
/**
* Compute bucketed time-series stats scoped to specific routes and agents.
*/
public StatsTimeseries timeseries(java.time.Instant from, java.time.Instant to, int bucketCount,
String routeId, List<String> agentIds) {
return engine.timeseries(from, to, bucketCount, routeId, agentIds);
}
}

View File

@@ -3,6 +3,7 @@ package com.cameleer3.server.core.storage;
import com.cameleer3.common.graph.RouteGraph;
import com.cameleer3.server.core.ingestion.TaggedDiagram;
import java.util.List;
import java.util.Optional;
/**
@@ -24,4 +25,11 @@ public interface DiagramRepository {
* Find the content hash for the latest diagram of a given route and agent.
*/
Optional<String> findContentHashForRoute(String routeId, String agentId);
/**
* Find the content hash for the latest diagram of a route across any agent in the given list.
* All instances of the same application produce the same route graph, so any agent's
* diagram for the same route will have the same content hash.
*/
Optional<String> findContentHashForRouteByAgents(String routeId, List<String> agentIds);
}