This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch topology-external-endpoints in repository https://gitbox.apache.org/repos/asf/camel.git
commit b9b31650b2534ec5fac60ea19931f57e24fd1402 Author: Claus Ibsen <[email protected]> AuthorDate: Tue Jun 2 12:02:40 2026 +0200 Add external systems toggle to TUI diagram tab Press 'e' to toggle external systems on/off in the topology diagram. When enabled, the diagram shows a three-band layout with external consumers at the top, routes in the middle, and external producers at the bottom. External node names are colored cyan in the TUI. Co-Authored-By: Claude Opus 4.6 <[email protected]> --- .../camel/diagram/RouteDiagramAsciiRenderer.java | 3 +- dsl/camel-jbang/camel-jbang-core/pom.xml | 4 + .../examples/camel-jbang-example-catalog.json | 23 +++++ .../resources/examples/route-topology/README.md | 61 ++++++++++++++ .../examples/route-topology/application.properties | 3 + .../route-topology/route-topology.camel.yaml | 97 ++++++++++++++++++++++ .../jbang/core/commands/tui/DiagramSupport.java | 17 ++-- .../dsl/jbang/core/commands/tui/DiagramTab.java | 25 +++++- 8 files changed, 226 insertions(+), 7 deletions(-) diff --git a/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramAsciiRenderer.java b/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramAsciiRenderer.java index 654c166a7114..d32e850300c0 100644 --- a/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramAsciiRenderer.java +++ b/components/camel-diagram/src/main/java/org/apache/camel/diagram/RouteDiagramAsciiRenderer.java @@ -65,7 +65,8 @@ public class RouteDiagramAsciiRenderer { OK, FAIL, HIGHLIGHT_SUCCESS, - HIGHLIGHT_FAIL + HIGHLIGHT_FAIL, + EXTERNAL } public record CounterPos(int row, int col, int length, CounterType type) { diff --git a/dsl/camel-jbang/camel-jbang-core/pom.xml b/dsl/camel-jbang/camel-jbang-core/pom.xml index b0b9f0c33b89..1b0e2716e499 100644 --- a/dsl/camel-jbang/camel-jbang-core/pom.xml +++ b/dsl/camel-jbang/camel-jbang-core/pom.xml @@ -308,6 +308,10 @@ <sync-example name="rest-api" file="README.md"/> <sync-example name="rest-api" file="application.properties"/> <sync-example name="rest-api" file="rest-api.camel.yaml"/> + <!-- route-topology --> + <sync-example name="route-topology" file="README.md"/> + <sync-example name="route-topology" file="application.properties"/> + <sync-example name="route-topology" file="route-topology.camel.yaml"/> <!-- routes --> <sync-example name="routes" file="Greeter.java"/> <sync-example name="routes" file="README.md"/> diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json index 10c4a6e65692..1d5361fa33f6 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json @@ -372,6 +372,29 @@ "rest-api.camel.yaml" ] }, + { + "name": "route-topology", + "title": "Route Topology", + "description": "Demonstrates inter-route topology with triggers, shared routes, and external systems", + "level": "intermediate", + "tags": [ + "topology", + "direct", + "kafka", + "timer" + ], + "bundled": true, + "requiresDocker": false, + "hasCitrusTests": false, + "files": [ + "README.md", + "application.properties", + "route-topology.camel.yaml" + ], + "infraServices": [ + "kafka" + ] + }, { "name": "routes", "title": "Routes", diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route-topology/README.md b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route-topology/README.md new file mode 100644 index 000000000000..194f09d6ca33 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route-topology/README.md @@ -0,0 +1,61 @@ +## Route Topology + +This example demonstrates inter-route topology in an order processing system. +It showcases how multiple routes connect through shared endpoints — both internal (direct) +and external (kafka) — making it a good example for the `route-topology` command. + +### How to run + + camel run route-topology.camel.yaml + +You can use `--stub` to run without a Kafka broker installed. +This replaces Kafka with an internal in-memory queue, so the routes still connect and messages flow end-to-end: + + camel run route-topology.camel.yaml --stub=kafka + +### View the route topology + + camel cmd route-topology + +View as a Unicode diagram with live metrics and route descriptions: + + camel cmd route-topology --theme=unicode --metric --description + +Sample output: + +``` + ┌──────────────────────┐ ┌──────────────────────┐ + │ Generate Orders │ │ Order REST API │ + │ 54 │ └──────────────────────┘ + └──────────────────────┘ + │ │ + │ ┬─────────────┘ + └─────────────│ + ▼ + ┌──────────────────────┐ + │ Process Order │ + │ 54 │ + └──────────────────────┘ + │ + ┬─────────────┴─────────────┬ + ▼ ▼ + ┌──────────────────────┐ ┌──────────────────────┐ + │ Dispatch Order │ │ Validate Order │ + │ 54 │ │ 54 │ + └──────────────────────┘ └──────────────────────┘ + │ + └───────────────────────────┬ + ▼ ▼ + ┌──────────────────────┐ ┌──────────────────────┐ + │ Fulfill Order │ │ Send Notification │ + │ 54 │ │ 54 │ + └──────────────────────┘ └──────────────────────┘ +``` + +Save as PNG image: + + camel cmd route-topology --theme=dark --output=topology.png + +### View as JSON + + camel cmd route-topology --json diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route-topology/application.properties b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route-topology/application.properties new file mode 100644 index 000000000000..663bdcf00ac3 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route-topology/application.properties @@ -0,0 +1,3 @@ +# Kafka defaults to localhost:9092 +# To use a different broker, uncomment and adjust: +# camel.component.kafka.brokers=localhost:9092 diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route-topology/route-topology.camel.yaml b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route-topology/route-topology.camel.yaml new file mode 100644 index 000000000000..a89d27b967de --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/route-topology/route-topology.camel.yaml @@ -0,0 +1,97 @@ +# Order Processing System +# This example demonstrates inter-route topology with triggers, shared routes, and external systems. +# Use "camel cmd route-topology" to visualize how the routes connect to each other. + +# Trigger: generates a new order every 5 seconds +- route: + id: order-generator + description: Generate Orders + from: + uri: timer + parameters: + timerName: orders + period: 5000 + steps: + - setBody: + expression: + simple: + expression: '{"orderId": "${exchangeId}", "item": "Camel T-Shirt", "quantity": 1}' + - to: + uri: direct:process-order + +# HTTP entry point: receives orders from external clients +- route: + id: order-api + description: Order REST API + from: + uri: platform-http + parameters: + path: /api/orders + httpMethodRestrict: POST + steps: + - to: + uri: direct:process-order + +# Shared route: validates and publishes orders (used by both order-generator and order-api) +- route: + id: process-order + description: Process Order + from: + uri: direct:process-order + steps: + - to: + uri: direct:validate-order + - log: + message: "Processing order: ${body}" + - to: + uri: kafka:orders + +# Validation: checks order contents +- route: + id: validate-order + description: Validate Order + from: + uri: direct:validate-order + steps: + - log: + message: "Validating order: ${body}" + +# Kafka consumer: picks up orders and fans out to fulfillment and notifications +- route: + id: order-dispatcher + description: Dispatch Order + from: + uri: kafka:orders + steps: + - log: + message: "Dispatching order: ${body}" + - multicast: + steps: + - to: + uri: kafka:fulfillment + - to: + uri: kafka:notifications + +# Kafka consumer: handles fulfillment and reports to warehouse +- route: + id: fulfillment + description: Fulfill Order + from: + uri: kafka:fulfillment + steps: + - log: + message: "Fulfilling order: ${body}" + - to: + uri: kafka:warehouse-shipments + +# Kafka consumer: sends notifications via external email service +- route: + id: notification + description: Send Notification + from: + uri: kafka:notifications + steps: + - log: + message: "Sending notification for: ${body}" + - to: + uri: kafka:email-outbox diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java index 7d67c3475565..43985f4db3fc 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramSupport.java @@ -431,8 +431,8 @@ class DiagramSupport { } void loadTopologyDiagramInBackground( - MonitorContext ctx, String pid, boolean textMode, boolean metrics) { - JsonObject jo = requestRouteTopology(ctx, pid); + MonitorContext ctx, String pid, boolean textMode, boolean metrics, boolean external) { + JsonObject jo = requestRouteTopology(ctx, pid, external); if (jo == null) { applyResult(ctx, List.of("(No response from integration)"), null, null, null); return; @@ -440,6 +440,9 @@ class DiagramSupport { List<TopologyNodeInfo> nodes = TopologyHelper.parseNodes(jo); List<TopologyEdgeInfo> edges = TopologyHelper.parseEdges(jo); + if (external) { + TopologyHelper.addExternalEndpoints(nodes, edges, jo); + } if (nodes.isEmpty()) { applyResult(ctx, List.of("(No routes in response)"), null, null, null); return; @@ -473,7 +476,7 @@ class DiagramSupport { case OK -> RouteDiagramAsciiRenderer.CounterType.OK; case FAIL -> RouteDiagramAsciiRenderer.CounterType.FAIL; case TRIGGER -> RouteDiagramAsciiRenderer.CounterType.HIGHLIGHT_SUCCESS; - case EXTERNAL -> RouteDiagramAsciiRenderer.CounterType.OK; + case EXTERNAL -> RouteDiagramAsciiRenderer.CounterType.EXTERNAL; }; positions.add(new RouteDiagramAsciiRenderer.CounterPos( rowMapping[cp.row()], cp.col(), cp.length(), mapped)); @@ -501,13 +504,16 @@ class DiagramSupport { } } - private JsonObject requestRouteTopology(MonitorContext ctx, String pid) { + private JsonObject requestRouteTopology(MonitorContext ctx, String pid, boolean external) { Path outputFile = ctx.getOutputFile(pid); PathUtils.deleteFile(outputFile); JsonObject root = new JsonObject(); root.put("action", "route-topology"); root.put("metric", "true"); + if (external) { + root.put("external", "true"); + } Path actionFile = ctx.getActionFile(pid); PathUtils.writeTextSafely(root.toJson(), actionFile); @@ -689,6 +695,7 @@ class DiagramSupport { end = Math.min(end, text.length()); int colorFlag = switch (cp.type()) { case OK, HIGHLIGHT_SUCCESS -> 1; // green + case EXTERNAL -> 3; // cyan default -> 2; // red }; counterRanges.add(new int[] { start, end, colorFlag }); @@ -737,7 +744,7 @@ class DiagramSupport { spans.add(Span.styled(text.substring(pos, cr[0]), Style.EMPTY.fg(defaultColor))); } int counterEnd = Math.min(cr[1], to); - Color counterColor = cr[2] == 1 ? Color.GREEN : Color.LIGHT_RED; + Color counterColor = cr[2] == 1 ? Color.GREEN : cr[2] == 3 ? Color.CYAN : Color.LIGHT_RED; spans.add(Span.styled(text.substring(cr[0], counterEnd), Style.EMPTY.fg(counterColor).bold())); pos = counterEnd; } else { diff --git a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java index 0311dbad42ef..3c08ef6fbbee 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java +++ b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DiagramTab.java @@ -36,6 +36,7 @@ class DiagramTab implements MonitorTab { private final MonitorContext ctx; private final DiagramSupport diagram = new DiagramSupport(); private boolean diagramMetrics = true; + private boolean showExternal; private boolean topologyMode = true; private String drillDownRouteId; @@ -61,6 +62,14 @@ class DiagramTab implements MonitorTab { return true; } + // Toggle external systems + if (diagram.isShowDiagram() && topologyMode && ke.isCharIgnoreCase('e')) { + showExternal = !showExternal; + diagram.endLoad(); + reloadDiagram(); + return true; + } + // Toggle description if (diagram.isShowDiagram() && ke.isCharIgnoreCase('n')) { diagram.setShowDescription(!diagram.isShowDescription()); @@ -151,6 +160,7 @@ class DiagramTab implements MonitorTab { if (diagram.isShowDiagram()) { diagram.renderFooterHints(spans); hint(spans, "m", "metrics" + (diagramMetrics ? " [on]" : " [off]")); + hint(spans, "e", "external" + (showExternal ? " [on]" : " [off]")); hint(spans, "n", "description" + (diagram.isShowDescription() ? " [on]" : " [off]")); } } @@ -179,6 +189,7 @@ class DiagramTab implements MonitorTab { String pid = ctx.selectedPid; boolean showMetrics = diagramMetrics; + boolean external = showExternal; if (showPlaceholder) { diagram.setLoadingPlaceholder(); @@ -188,7 +199,7 @@ class DiagramTab implements MonitorTab { try { if (topologyMode) { diagram.setTopologyMode(true); - diagram.loadTopologyDiagramInBackground(ctx, pid, true, showMetrics); + diagram.loadTopologyDiagramInBackground(ctx, pid, true, showMetrics, external); } else { diagram.setTopologyMode(false); diagram.loadRouteDiagramInBackground(ctx, pid, true, drillDownRouteId, showMetrics); @@ -244,9 +255,21 @@ class DiagramTab implements MonitorTab { - **Red** number with `!` — failed exchanges - Combined as `3748/12!` means 3748 ok and 12 failed + ## External Systems + + When external systems are enabled, the diagram shows a three-band layout: + - **Top band** — external consumers sending messages INTO Camel + - **Middle band** — the Camel routes and their internal connections + - **Bottom band** — external producers where Camel sends messages OUT + + External system boxes are drawn with dashed borders to distinguish + them from route boxes. Dashed edges connect routes to external systems. + ## Keys - `m` — toggle metrics on/off (default: on) + - `e` — toggle external systems on/off (default: off) + - `n` — toggle description labels on/off (default: off) - `↑↓←→` — scroll diagram - `PgUp/PgDn` — page scroll - `Home/End` — top/end
