You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2026/05/07 13:24:46 UTC

(camel) branch dia2 created (now 6e574c3c511c)

This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a change to branch dia2
in repository https://gitbox.apache.org/repos/asf/camel.git


      at 6e574c3c511c CAMEL-23400: camel-diagram - prepare for being used in maven tooling and also as SPI in camel-core

This branch includes the following new commits:

     new 6e574c3c511c CAMEL-23400: camel-diagram - prepare for being used in maven tooling and also as SPI in camel-core

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(camel) 01/01: CAMEL-23400: camel-diagram - prepare for being used in maven tooling and also as SPI in camel-core

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch dia2
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 6e574c3c511c0c6de6fa356c04a0482cd14115e3
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Thu May 7 15:20:57 2026 +0200

    CAMEL-23400: camel-diagram - prepare for being used in maven tooling and also as SPI in camel-core
---
 .../apache/camel/catalog/dev-consoles.properties   |   1 +
 .../camel/catalog/dev-consoles/route-diagram.json  |  15 ++
 components/camel-diagram/pom.xml                   |  43 ++++++
 .../apache/camel/dev-console/route-diagram.json    |  15 ++
 .../org/apache/camel/dev-console/route-diagram     |   2 +
 .../org/apache/camel/dev-consoles.properties       |   7 +
 .../services/org/apache/camel/route-diagram-dumper |   2 +
 .../camel-diagram/src/main/docs/diagram.adoc       |  12 ++
 .../camel/diagram/DefaultRouteDiagramDumper.java   | 154 +++++++++++++++++++++
 .../apache/camel/diagram}/DiagramDevConsole.java   | 109 +++++----------
 .../diagram/DefaultRouteDiagramDumperTest.java     |  75 +++++-----
 .../camel/diagram}/DiagramDevConsoleTest.java      |   2 +-
 .../src/test/resources/log4j2.properties           |   2 +-
 .../org/apache/camel/spi/RouteDiagramDumper.java   |  72 ++++++++++
 .../camel/impl/engine/AbstractCamelContext.java    |  22 ++-
 .../impl/console/RouteStructureDevConsole.java     |   2 +
 .../camel/impl/DefaultDumpRoutesStrategy.java      |  33 +++++
 .../camel-main-configuration-metadata.json         |   2 +-
 .../camel/main/DefaultConfigurationProperties.java |   2 +-
 .../org/apache/camel/support/PluginHelper.java     |  15 ++
 dsl/camel-jbang/camel-jbang-console/pom.xml        |  42 ------
 .../org/apache/camel/dev-consoles.properties       |   2 +-
 dsl/camel-kamelet-main/pom.xml                     |   4 +
 ...mel-factoryfinder-known-dependencies.properties |   1 +
 24 files changed, 475 insertions(+), 161 deletions(-)

diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles.properties b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles.properties
index db039fb9379c..3248722ceb09 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles.properties
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles.properties
@@ -43,6 +43,7 @@ resilience4j
 rest
 route
 route-controller
+route-diagram
 route-dump
 route-group
 route-structure
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route-diagram.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route-diagram.json
new file mode 100644
index 000000000000..219a621ece9f
--- /dev/null
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/route-diagram.json
@@ -0,0 +1,15 @@
+{
+  "console": {
+    "kind": "console",
+    "group": "camel",
+    "name": "route-diagram",
+    "title": "Route Diagram",
+    "description": "Visual route diagrams",
+    "deprecated": false,
+    "javaType": "org.apache.camel.diagram.DiagramDevConsole",
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-diagram",
+    "version": "4.21.0-SNAPSHOT"
+  }
+}
+
diff --git a/components/camel-diagram/pom.xml b/components/camel-diagram/pom.xml
index 2204d71fdd64..e25ac06f9f72 100644
--- a/components/camel-diagram/pom.xml
+++ b/components/camel-diagram/pom.xml
@@ -37,6 +37,11 @@
     </properties>
 
     <dependencies>
+
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-console</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-util</artifactId>
@@ -52,11 +57,49 @@
         </dependency>
 
         <!-- test dependencies -->
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-test-junit6</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-management</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.junit.jupiter</groupId>
             <artifactId>junit-jupiter-api</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <!-- logging -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-api</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-slf4j2-impl</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+
     </dependencies>
 
 </project>
diff --git a/components/camel-diagram/src/generated/resources/META-INF/org/apache/camel/dev-console/route-diagram.json b/components/camel-diagram/src/generated/resources/META-INF/org/apache/camel/dev-console/route-diagram.json
new file mode 100644
index 000000000000..219a621ece9f
--- /dev/null
+++ b/components/camel-diagram/src/generated/resources/META-INF/org/apache/camel/dev-console/route-diagram.json
@@ -0,0 +1,15 @@
+{
+  "console": {
+    "kind": "console",
+    "group": "camel",
+    "name": "route-diagram",
+    "title": "Route Diagram",
+    "description": "Visual route diagrams",
+    "deprecated": false,
+    "javaType": "org.apache.camel.diagram.DiagramDevConsole",
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-diagram",
+    "version": "4.21.0-SNAPSHOT"
+  }
+}
+
diff --git a/components/camel-diagram/src/generated/resources/META-INF/services/org/apache/camel/dev-console/route-diagram b/components/camel-diagram/src/generated/resources/META-INF/services/org/apache/camel/dev-console/route-diagram
new file mode 100644
index 000000000000..dfc0819f996d
--- /dev/null
+++ b/components/camel-diagram/src/generated/resources/META-INF/services/org/apache/camel/dev-console/route-diagram
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.diagram.DiagramDevConsole
diff --git a/components/camel-diagram/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties b/components/camel-diagram/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties
new file mode 100644
index 000000000000..1946bb584da1
--- /dev/null
+++ b/components/camel-diagram/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties
@@ -0,0 +1,7 @@
+# Generated by camel build tools - do NOT edit this file!
+dev-consoles=route-diagram
+groupId=org.apache.camel
+artifactId=camel-diagram
+version=4.21.0-SNAPSHOT
+projectName=Camel :: Diagram
+projectDescription=Camel Route Diagram rendering
diff --git a/components/camel-diagram/src/generated/resources/META-INF/services/org/apache/camel/route-diagram-dumper b/components/camel-diagram/src/generated/resources/META-INF/services/org/apache/camel/route-diagram-dumper
new file mode 100644
index 000000000000..6d88d8f1d817
--- /dev/null
+++ b/components/camel-diagram/src/generated/resources/META-INF/services/org/apache/camel/route-diagram-dumper
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.diagram.DefaultRouteDiagramDumper
diff --git a/components/camel-diagram/src/main/docs/diagram.adoc b/components/camel-diagram/src/main/docs/diagram.adoc
index 103910eb7de7..814acfacb812 100644
--- a/components/camel-diagram/src/main/docs/diagram.adoc
+++ b/components/camel-diagram/src/main/docs/diagram.adoc
@@ -35,6 +35,18 @@ Add the `camel-diagram` dependency to your project:
 </dependency>
 ----
 
+==== Using Camel Java API
+
+You can use the diagram render with Camel based APIs such as:
+
+[source,java]
+----
+RouteDiagramDumper dumper = PluginHelper.getRouteDiagramDumper(context);
+BufferedImage image = dumper.dumpRoutesAsImage("*", RouteDiagramDumper.Theme.DARK);
+----
+
+==== Using standalone Java API
+
 Then use the API to render diagrams:
 
 [source,java]
diff --git a/components/camel-diagram/src/main/java/org/apache/camel/diagram/DefaultRouteDiagramDumper.java b/components/camel-diagram/src/main/java/org/apache/camel/diagram/DefaultRouteDiagramDumper.java
new file mode 100644
index 000000000000..de63eaa9f09f
--- /dev/null
+++ b/components/camel-diagram/src/main/java/org/apache/camel/diagram/DefaultRouteDiagramDumper.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.diagram;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.imageio.ImageIO;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
+import org.apache.camel.console.DevConsole;
+import org.apache.camel.console.DevConsoleRegistry;
+import org.apache.camel.spi.RouteDiagramDumper;
+import org.apache.camel.spi.annotations.JdkService;
+import org.apache.camel.support.LoggerHelper;
+import org.apache.camel.support.service.ServiceSupport;
+import org.apache.camel.util.FileUtil;
+import org.apache.camel.util.IOHelper;
+import org.apache.camel.util.StringHelper;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+
+@JdkService(RouteDiagramDumper.FACTORY)
+public class DefaultRouteDiagramDumper extends ServiceSupport implements CamelContextAware, RouteDiagramDumper {
+
+    private CamelContext camelContext;
+
+    @Override
+    public CamelContext getCamelContext() {
+        return camelContext;
+    }
+
+    @Override
+    public void setCamelContext(CamelContext camelContext) {
+        this.camelContext = camelContext;
+    }
+
+    @Override
+    protected void doInit() throws Exception {
+        super.doInit();
+        System.setProperty("java.awt.headless", "true");
+    }
+
+    @Override
+    public void dumpRoutesToFile(String filter, Theme theme, File file) throws IOException {
+        BufferedImage image = dumpRoutesAsImage(filter, theme);
+        ImageIO.write(image, "png", file);
+    }
+
+    @Override
+    public void dumpRoutesToFolder(String filter, Theme theme, File folder) throws IOException {
+        // use dev-console to render the route structure in json format which the diagram render expects
+        DevConsole dc = getCamelContext().getCamelContextExtension().getContextPlugin(DevConsoleRegistry.class)
+                .resolveById("route-structure");
+        JsonObject root = (JsonObject) dc.call(DevConsole.MediaType.JSON, Map.of("filter", filter));
+
+        // group by source filename / routeId
+        Set<String> groups = new LinkedHashSet<>();
+        JsonArray arr = root.getJsonArray("routes");
+        for (int i = 0; i < arr.size(); i++) {
+            JsonObject jo = arr.getJsonObject(i);
+            String id = jo.getString("routeId");
+            String source = jo.getString("source");
+            if (source != null) {
+                // favor source over route id
+                id = LoggerHelper.sourceNameOnly(source);
+            }
+            groups.add(id);
+        }
+
+        // render by groups
+        for (String group : groups) {
+            root = (JsonObject) dc.call(DevConsole.MediaType.JSON, Map.of("filter", group));
+            var routes = RouteDiagramHelper.parseRoutes(root);
+            BufferedImage image = renderImage(routes, theme.name(), 12, 180, "CODE");
+            // normalize as file name
+            String name = StringHelper.after(group, ":", group);
+            name = name.replace(' ', '-');
+            name = FileUtil.stripExt(name);
+            folder.mkdirs();
+            File f = new File(folder, name + ".png");
+            ImageIO.write(image, "png", f);
+        }
+    }
+
+    @Override
+    public BufferedImage dumpRoutesAsImage(String filter, Theme theme, NodeLabelMode nodeLabel, int nodeWidth, int fontSize) {
+        // use dev-console to render the route structure in json format which the diagram render expects
+        DevConsole dc = getCamelContext().getCamelContextExtension().getContextPlugin(DevConsoleRegistry.class)
+                .resolveById("route-structure");
+        JsonObject root = (JsonObject) dc.call(DevConsole.MediaType.JSON, Map.of("filter", filter));
+        var routes = RouteDiagramHelper.parseRoutes(root);
+        return renderImage(routes, theme.name(), fontSize, nodeWidth, nodeLabel.name());
+    }
+
+    @Override
+    public String imageToBase64(BufferedImage image) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        ImageIO.write(image, "PNG", baos);
+        baos.flush();
+        try {
+            byte[] imageBytes = baos.toByteArray();
+            return Base64.getEncoder().encodeToString(imageBytes);
+        } finally {
+            IOHelper.close(baos);
+        }
+    }
+
+    private static BufferedImage renderImage(
+            List<RouteDiagramLayoutEngine.RouteInfo> routes, String theme, int fontSize, int nodeWidth, String nodeLabel) {
+        RouteDiagramRenderer renderer = new RouteDiagramRenderer(
+                nodeWidth * RouteDiagramLayoutEngine.SCALE, fontSize * RouteDiagramLayoutEngine.SCALE);
+        RouteDiagramLayoutEngine engine = new RouteDiagramLayoutEngine(
+                nodeWidth, fontSize, RouteDiagramLayoutEngine.NodeLabelMode.valueOf(nodeLabel.toUpperCase()));
+
+        List<RouteDiagramLayoutEngine.LayoutRoute> layoutRoutes = new ArrayList<>();
+        int currentY = RouteDiagramLayoutEngine.PADDING;
+        for (RouteDiagramLayoutEngine.RouteInfo route : routes) {
+            RouteDiagramLayoutEngine.LayoutRoute lr = engine.layoutRoute(route, currentY);
+            layoutRoutes.add(lr);
+            currentY = lr.maxY + RouteDiagramLayoutEngine.V_GAP;
+        }
+        if (theme == null) {
+            theme = "transparent";
+        }
+        theme = theme.toLowerCase();
+        RouteDiagramRenderer.DiagramColors colors = RouteDiagramRenderer.DiagramColors.parse(theme);
+        return renderer.renderDiagram(layoutRoutes, currentY, colors);
+    }
+
+}
diff --git a/dsl/camel-jbang/camel-jbang-console/src/main/java/org/apache/camel/jbang/console/DiagramDevConsole.java b/components/camel-diagram/src/main/java/org/apache/camel/diagram/DiagramDevConsole.java
similarity index 52%
rename from dsl/camel-jbang/camel-jbang-console/src/main/java/org/apache/camel/jbang/console/DiagramDevConsole.java
rename to components/camel-diagram/src/main/java/org/apache/camel/diagram/DiagramDevConsole.java
index d2b79e5b6095..a4ad608e8722 100644
--- a/dsl/camel-jbang/camel-jbang-console/src/main/java/org/apache/camel/jbang/console/DiagramDevConsole.java
+++ b/components/camel-diagram/src/main/java/org/apache/camel/diagram/DiagramDevConsole.java
@@ -14,29 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.jbang.console;
+package org.apache.camel.diagram;
 
 import java.awt.image.BufferedImage;
-import java.io.ByteArrayOutputStream;
-import java.util.ArrayList;
-import java.util.Base64;
-import java.util.List;
 import java.util.Map;
 import java.util.StringJoiner;
 
-import javax.imageio.ImageIO;
-
 import org.apache.camel.console.DevConsoleRegistry;
-import org.apache.camel.diagram.RouteDiagramHelper;
-import org.apache.camel.diagram.RouteDiagramLayoutEngine;
-import org.apache.camel.diagram.RouteDiagramRenderer;
+import org.apache.camel.spi.RouteDiagramDumper;
 import org.apache.camel.spi.annotations.DevConsole;
+import org.apache.camel.support.PluginHelper;
 import org.apache.camel.support.console.AbstractDevConsole;
-import org.apache.camel.util.IOHelper;
-import org.apache.camel.util.json.JsonArray;
 import org.apache.camel.util.json.JsonObject;
 
-@DevConsole(name = "route-diagram", group = "camel-jbang", displayName = "Route Diagram", description = "Visual route diagrams")
+@DevConsole(name = "route-diagram", group = "camel", displayName = "Route Diagram", description = "Visual route diagrams")
 public class DiagramDevConsole extends AbstractDevConsole {
 
     /**
@@ -65,7 +56,7 @@ public class DiagramDevConsole extends AbstractDevConsole {
     public static final String NODE_LABEL = "nodeLabel";
 
     public DiagramDevConsole() {
-        super("camel-jbang", "route-diagram", "Route Diagram", "Visual route diagrams");
+        super("camel", "route-diagram", "Route Diagram", "Visual route diagrams");
     }
 
     @Override
@@ -73,30 +64,22 @@ public class DiagramDevConsole extends AbstractDevConsole {
         final StringJoiner sj = new StringJoiner("\n");
 
         String filter = (String) options.getOrDefault(FILTER, "*");
-        String theme = (String) options.get(THEME);
+        String theme = (String) options.getOrDefault(THEME, RouteDiagramDumper.Theme.TRANSPARENT.name());
         int fontSize
                 = Integer.parseInt(options.getOrDefault(FONT_SIZE, "" + RouteDiagramLayoutEngine.DEFAULT_FONT_SIZE).toString());
         int nodeWidth = Integer
                 .parseInt(options.getOrDefault(NODE_WIDTH, "" + RouteDiagramLayoutEngine.DEFAULT_BOX_WIDTH).toString());
-        String nodeLabel = (String) options.getOrDefault(NODE_LABEL, "CODE");
+        String nodeLabel = (String) options.getOrDefault(NODE_LABEL, RouteDiagramDumper.NodeLabelMode.CODE.name());
 
-        org.apache.camel.console.DevConsole dc
-                = getCamelContext().getCamelContextExtension().getContextPlugin(DevConsoleRegistry.class)
-                        .resolveById("route-structure");
-        JsonObject root = (JsonObject) dc.call(MediaType.JSON, Map.of("filter", filter));
-        // parseRoutes expect a JsonArray and not ArrayList
-        JsonArray arr = new JsonArray(root.getCollection("routes"));
-        root.put("routes", arr);
-        var routes = RouteDiagramHelper.parseRoutes(root);
-        if ("text".equals(theme)) {
-            RouteDiagramRenderer renderer = new RouteDiagramRenderer();
-            var lines = renderer.printTextDiagram(routes);
-            lines.forEach(sj::add);
-            sj.add("");
+        // special for text
+        if ("text".equalsIgnoreCase(theme)) {
+            sj.add(renderTextTheme(filter));
         } else {
             try {
-                BufferedImage image = renderImage(routes, theme, fontSize, nodeWidth, nodeLabel);
-                String base64 = imageToBase64(image, "png");
+                RouteDiagramDumper dumper = PluginHelper.getRouteDiagramDumper(getCamelContext());
+                BufferedImage image = dumper.dumpRoutesAsImage(filter, RouteDiagramDumper.Theme.valueOf(theme.toUpperCase()),
+                        RouteDiagramDumper.NodeLabelMode.valueOf(nodeLabel.toUpperCase()), nodeWidth, fontSize);
+                String base64 = dumper.imageToBase64(image);
                 // For HTML embedding:
                 String html = String.format(
                         "<html>\n<body>\n<img src=\"data:image/png;base64,%s\" alt=\"Route Diagram\">\n</body>\n</html>",
@@ -106,32 +89,26 @@ public class DiagramDevConsole extends AbstractDevConsole {
                 // ignore
             }
         }
+
         return sj.toString();
     }
 
     @Override
     protected Map<String, Object> doCallJson(Map<String, Object> options) {
         String filter = (String) options.getOrDefault(FILTER, "*");
-        String theme = (String) options.get(THEME);
+        String theme = (String) options.getOrDefault(THEME, RouteDiagramDumper.Theme.TRANSPARENT.name());
         int fontSize
                 = Integer.parseInt(options.getOrDefault(FONT_SIZE, "" + RouteDiagramLayoutEngine.DEFAULT_FONT_SIZE).toString());
         int nodeWidth = Integer
                 .parseInt(options.getOrDefault(NODE_WIDTH, "" + RouteDiagramLayoutEngine.DEFAULT_BOX_WIDTH).toString());
-        String nodeLabel = (String) options.getOrDefault(NODE_LABEL, "CODE");
-
-        org.apache.camel.console.DevConsole dc
-                = getCamelContext().getCamelContextExtension().getContextPlugin(DevConsoleRegistry.class)
-                        .resolveById("route-structure");
-        JsonObject root = (JsonObject) dc.call(MediaType.JSON, Map.of("filter", filter));
-        // parseRoutes expect a JsonArray and not ArrayList
-        JsonArray arr = new JsonArray(root.getCollection("routes"));
-        root.put("routes", arr);
-        var routes = RouteDiagramHelper.parseRoutes(root);
+        String nodeLabel = (String) options.getOrDefault(NODE_LABEL, RouteDiagramDumper.NodeLabelMode.CODE.name());
 
-        root = new JsonObject();
+        JsonObject root = new JsonObject();
         try {
-            BufferedImage image = renderImage(routes, theme, fontSize, nodeWidth, nodeLabel);
-            String base64 = imageToBase64(image, "png");
+            RouteDiagramDumper dumper = PluginHelper.getRouteDiagramDumper(getCamelContext());
+            BufferedImage image = dumper.dumpRoutesAsImage(filter, RouteDiagramDumper.Theme.valueOf(theme.toUpperCase()),
+                    RouteDiagramDumper.NodeLabelMode.valueOf(nodeLabel.toUpperCase()), nodeWidth, fontSize);
+            String base64 = dumper.imageToBase64(image);
             root.put("image", base64);
         } catch (Exception e) {
             // ignore
@@ -140,36 +117,18 @@ public class DiagramDevConsole extends AbstractDevConsole {
         return root;
     }
 
-    private static BufferedImage renderImage(
-            List<RouteDiagramLayoutEngine.RouteInfo> routes, String theme, int fontSize, int nodeWidth, String nodeLabel) {
-        RouteDiagramRenderer renderer = new RouteDiagramRenderer(
-                nodeWidth * RouteDiagramLayoutEngine.SCALE, fontSize * RouteDiagramLayoutEngine.SCALE);
-        RouteDiagramLayoutEngine engine = new RouteDiagramLayoutEngine(
-                nodeWidth, fontSize, RouteDiagramLayoutEngine.NodeLabelMode.valueOf(nodeLabel.toUpperCase()));
-
-        List<RouteDiagramLayoutEngine.LayoutRoute> layoutRoutes = new ArrayList<>();
-        int currentY = RouteDiagramLayoutEngine.PADDING;
-        for (RouteDiagramLayoutEngine.RouteInfo route : routes) {
-            RouteDiagramLayoutEngine.LayoutRoute lr = engine.layoutRoute(route, currentY);
-            layoutRoutes.add(lr);
-            currentY = lr.maxY + RouteDiagramLayoutEngine.V_GAP;
-        }
-        if (theme == null) {
-            theme = "transparent";
-        }
-        RouteDiagramRenderer.DiagramColors colors = RouteDiagramRenderer.DiagramColors.parse(theme);
-        return renderer.renderDiagram(layoutRoutes, currentY, colors);
-    }
+    private String renderTextTheme(String filter) {
+        final StringJoiner sj = new StringJoiner("\n");
 
-    private static String imageToBase64(BufferedImage image, String format) throws Exception {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        ImageIO.write(image, format, baos);
-        baos.flush();
-        try {
-            byte[] imageBytes = baos.toByteArray();
-            return Base64.getEncoder().encodeToString(imageBytes);
-        } finally {
-            IOHelper.close(baos);
-        }
+        org.apache.camel.console.DevConsole dc
+                = getCamelContext().getCamelContextExtension().getContextPlugin(DevConsoleRegistry.class)
+                        .resolveById("route-structure");
+        JsonObject root = (JsonObject) dc.call(MediaType.JSON, Map.of("filter", filter));
+        var routes = RouteDiagramHelper.parseRoutes(root);
+        RouteDiagramRenderer renderer = new RouteDiagramRenderer();
+        var lines = renderer.printTextDiagram(routes);
+        lines.forEach(sj::add);
+        sj.add("");
+        return sj.toString();
     }
 }
diff --git a/dsl/camel-jbang/camel-jbang-console/src/test/java/org/apache/camel/jbang/console/DiagramDevConsoleTest.java b/components/camel-diagram/src/test/java/org/apache/camel/diagram/DefaultRouteDiagramDumperTest.java
similarity index 57%
copy from dsl/camel-jbang/camel-jbang-console/src/test/java/org/apache/camel/jbang/console/DiagramDevConsoleTest.java
copy to components/camel-diagram/src/test/java/org/apache/camel/diagram/DefaultRouteDiagramDumperTest.java
index 87043b725a38..6c76c95ae4be 100644
--- a/dsl/camel-jbang/camel-jbang-console/src/test/java/org/apache/camel/jbang/console/DiagramDevConsoleTest.java
+++ b/components/camel-diagram/src/test/java/org/apache/camel/diagram/DefaultRouteDiagramDumperTest.java
@@ -14,21 +14,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.jbang.console;
+package org.apache.camel.diagram;
 
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
 import java.util.Base64;
-import java.util.Map;
 
 import org.apache.camel.builder.RouteBuilder;
-import org.apache.camel.console.DevConsole;
+import org.apache.camel.spi.RouteDiagramDumper;
 import org.apache.camel.support.PluginHelper;
 import org.apache.camel.test.junit6.CamelTestSupport;
-import org.apache.camel.util.json.JsonObject;
 import org.junit.jupiter.api.Test;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
-class DiagramDevConsoleTest extends CamelTestSupport {
+class DefaultRouteDiagramDumperTest extends CamelTestSupport {
 
     @Override
     protected boolean useJmx() {
@@ -56,49 +57,57 @@ class DiagramDevConsoleTest extends CamelTestSupport {
         };
     }
 
-    private DevConsole resolveConsole() {
-        DevConsole console = PluginHelper.getDevConsoleResolver(context).resolveDevConsole("route-diagram");
-        assertThat(console).as("route-diagram console should be resolvable").isNotNull();
-        assertThat(console.getId()).isEqualTo("route-diagram");
-        return console;
+    @Test
+    void testImage() {
+        System.setProperty("java.awt.headless", "true");
+
+        RouteDiagramDumper dumper = PluginHelper.getRouteDiagramDumper(context);
+        BufferedImage image = dumper.dumpRoutesAsImage("*", RouteDiagramDumper.Theme.DARK);
+        assertThat(image).isNotNull();
     }
 
     @Test
-    void testTextOutputDefault() {
+    void testImageToFile() throws IOException {
         System.setProperty("java.awt.headless", "true");
-        DevConsole console = resolveConsole();
-        String text = (String) console.call(DevConsole.MediaType.TEXT);
-        assertThat(text).isNotNull();
-        // default theme renders HTML with inline image
-        assertThat(text).contains("<html>");
-        assertThat(text).contains("data:image/png;base64,");
+
+        File f = new File("target/deleteme.png");
+        f.delete();
+
+        RouteDiagramDumper dumper = PluginHelper.getRouteDiagramDumper(context);
+        dumper.dumpRoutesToFile("*", RouteDiagramDumper.Theme.DARK, f);
+
+        assertThat(f.exists());
     }
 
     @Test
-    void testTextOutputTextTheme() {
-        DevConsole console = resolveConsole();
-        String text = (String) console.call(DevConsole.MediaType.TEXT, Map.of(DiagramDevConsole.THEME, "text"));
-        assertThat(text).contains("Route: myRoute");
-        assertThat(text).contains("Route: otherRoute");
-        assertThat(text).contains("[from]");
-        assertThat(text).contains("[choice]");
+    void testImageToFolder() throws IOException {
+        System.setProperty("java.awt.headless", "true");
+
+        File f = new File("target/dump");
+        f.mkdirs();
+
+        RouteDiagramDumper dumper = PluginHelper.getRouteDiagramDumper(context);
+        dumper.dumpRoutesToFolder("*", RouteDiagramDumper.Theme.LIGHT, f);
+
+        assertThat(f.exists());
+        assertThat(f.list()).contains("myRoute.png", "otherRoute.png");
     }
 
     @Test
-    void testJsonOutput() {
+    void testBase64() throws Exception {
         System.setProperty("java.awt.headless", "true");
-        DevConsole console = resolveConsole();
-        JsonObject json = (JsonObject) console.call(DevConsole.MediaType.JSON);
-        assertThat(json).isNotNull();
-        assertThat(json).containsKey("image");
-
-        String image = json.getString("image");
-        assertThat(image).isNotEmpty();
-        byte[] decoded = Base64.getDecoder().decode(image);
+
+        RouteDiagramDumper dumper = PluginHelper.getRouteDiagramDumper(context);
+        BufferedImage image = dumper.dumpRoutesAsImage("*", RouteDiagramDumper.Theme.DARK);
+        assertThat(image).isNotNull();
+
+        String base64 = dumper.imageToBase64(image);
+        byte[] decoded = Base64.getDecoder().decode(base64);
         // PNG magic bytes
         assertThat(decoded[0] & 0xFF).isEqualTo(0x89);
         assertThat(decoded[1] & 0xFF).isEqualTo(0x50); // 'P'
         assertThat(decoded[2] & 0xFF).isEqualTo(0x4E); // 'N'
         assertThat(decoded[3] & 0xFF).isEqualTo(0x47); // 'G'
     }
+
 }
diff --git a/dsl/camel-jbang/camel-jbang-console/src/test/java/org/apache/camel/jbang/console/DiagramDevConsoleTest.java b/components/camel-diagram/src/test/java/org/apache/camel/diagram/DiagramDevConsoleTest.java
similarity index 98%
rename from dsl/camel-jbang/camel-jbang-console/src/test/java/org/apache/camel/jbang/console/DiagramDevConsoleTest.java
rename to components/camel-diagram/src/test/java/org/apache/camel/diagram/DiagramDevConsoleTest.java
index 87043b725a38..017118151793 100644
--- a/dsl/camel-jbang/camel-jbang-console/src/test/java/org/apache/camel/jbang/console/DiagramDevConsoleTest.java
+++ b/components/camel-diagram/src/test/java/org/apache/camel/diagram/DiagramDevConsoleTest.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.jbang.console;
+package org.apache.camel.diagram;
 
 import java.util.Base64;
 import java.util.Map;
diff --git a/dsl/camel-jbang/camel-jbang-console/src/test/resources/log4j2.properties b/components/camel-diagram/src/test/resources/log4j2.properties
similarity index 95%
rename from dsl/camel-jbang/camel-jbang-console/src/test/resources/log4j2.properties
rename to components/camel-diagram/src/test/resources/log4j2.properties
index 8f1917ed219a..6f6ad2b8abb8 100644
--- a/dsl/camel-jbang/camel-jbang-console/src/test/resources/log4j2.properties
+++ b/components/camel-diagram/src/test/resources/log4j2.properties
@@ -17,7 +17,7 @@
 
 appender.file.type = File
 appender.file.name = file
-appender.file.fileName = target/camel-jbang-console-test.log
+appender.file.fileName = target/camel-diagram-test.log
 appender.file.layout.type = PatternLayout
 appender.file.layout.pattern = %d [%-15.15t] %-5p %-30.30c{1} - %m%n
 appender.out.type = Console
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/RouteDiagramDumper.java b/core/camel-api/src/main/java/org/apache/camel/spi/RouteDiagramDumper.java
new file mode 100644
index 000000000000..be0e9d95f4a7
--- /dev/null
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/RouteDiagramDumper.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.spi;
+
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * To dump routes as visual diagrams as PNG images.
+ */
+public interface RouteDiagramDumper {
+
+    enum NodeLabelMode {
+        CODE,
+        DESCRIPTION,
+        BOTH
+    }
+
+    enum Theme {
+        DARK,
+        LIGHT,
+        TRANSPARENT
+    }
+
+    /**
+     * Service factory key.
+     */
+    String FACTORY = "route-diagram-dumper";
+
+    /**
+     * Dumps the routes into a single combined PNG file
+     */
+    void dumpRoutesToFile(String filter, Theme theme, File file) throws IOException;
+
+    /**
+     * Dumps the routes as PNG files in the given folder
+     */
+    void dumpRoutesToFolder(String filter, Theme theme, File folder) throws IOException;
+
+    /**
+     * Dumps the routes as a PNG image
+     */
+    default BufferedImage dumpRoutesAsImage(String filter, Theme theme) {
+        return dumpRoutesAsImage(filter, theme, NodeLabelMode.CODE, 180, 12);
+    }
+
+    /**
+     * Dumps the routes as a PNG image
+     */
+    BufferedImage dumpRoutesAsImage(String filter, Theme theme, NodeLabelMode nodeLabel, int nodeWidth, int fontSize);
+
+    /**
+     * Converts the image to base64
+     */
+    String imageToBase64(BufferedImage image) throws IOException;
+
+}
diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
index e19ed149107c..7a50b8fdd244 100644
--- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
+++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
@@ -161,6 +161,7 @@ import org.apache.camel.spi.RestConfiguration;
 import org.apache.camel.spi.RestRegistry;
 import org.apache.camel.spi.RestRegistryFactory;
 import org.apache.camel.spi.RouteController;
+import org.apache.camel.spi.RouteDiagramDumper;
 import org.apache.camel.spi.RouteError.Phase;
 import org.apache.camel.spi.RouteFactory;
 import org.apache.camel.spi.RoutePolicyFactory;
@@ -410,6 +411,7 @@ public abstract class AbstractCamelContext extends BaseService
         camelContextExtension.lazyAddContextPlugin(GroovyScriptCompiler.class, this::createGroovyScriptCompiler);
         camelContextExtension.lazyAddContextPlugin(SimpleFunctionRegistry.class, this::createSimpleFunctionRegistry);
         camelContextExtension.lazyAddContextPlugin(RestRegistry.class, this::createRestRegistry);
+        camelContextExtension.lazyAddContextPlugin(RouteDiagramDumper.class, this::createRouteDiagramDumper);
     }
 
     protected static <T> T lookup(CamelContext context, String ref, Class<T> type) {
@@ -3174,12 +3176,6 @@ public abstract class AbstractCamelContext extends BaseService
             LOG.debug("Skip starting routes as CamelContext has been configured with autoStartup=false");
         }
 
-        // dump routes when as we have model before creating the runtime models, which can help during troubleshooting
-        // when routes have problems starting
-        if (getDumpRoutes() != null && !"false".equals(getDumpRoutes())) {
-            doDumpRoutes();
-        }
-
         if (!getRouteController().isSupervising()) {
             // invoke this logic to warmup the routes and if possible also start the routes (using default route controller)
             StartupStep subStep
@@ -3191,6 +3187,13 @@ public abstract class AbstractCamelContext extends BaseService
             startupStepRecorder.endStep(subStep);
         }
 
+        // TODO: json,png should be later
+        // dump routes when as we have model before creating the runtime models, which can help during troubleshooting
+        // when routes have problems starting
+        if (getDumpRoutes() != null && !"false".equals(getDumpRoutes())) {
+            doDumpRoutes();
+        }
+
         // ensure extra dev consoles is loaded in case additional JARs has been dynamically added to the classpath
         if (devConsole) {
             StartupStep step = startupStepRecorder.beginStep(CamelContext.class, null, "Scan DevConsoles (phase 2)");
@@ -4349,6 +4352,13 @@ public abstract class AbstractCamelContext extends BaseService
         return factory.createRegistry();
     }
 
+    protected RouteDiagramDumper createRouteDiagramDumper() {
+        return ResolverHelper.resolveMandatoryService(getCamelContextReference(),
+                RouteDiagramDumper.FACTORY,
+                RouteDiagramDumper.class,
+                "camel-diagram");
+    }
+
     @Override
     public String getGlobalOption(String key) {
         String value = getGlobalOptions().get(key);
diff --git a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteStructureDevConsole.java b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteStructureDevConsole.java
index afeaac03d63e..2e7188c4480f 100644
--- a/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteStructureDevConsole.java
+++ b/core/camel-console/src/main/java/org/apache/camel/impl/console/RouteStructureDevConsole.java
@@ -43,6 +43,8 @@ import static org.apache.camel.impl.console.ConsoleHelper.extractSourceLocationN
 @DevConsole(name = "route-structure", description = "Dump route structure")
 public class RouteStructureDevConsole extends AbstractDevConsole {
 
+    // TODO: no JMX but model only
+
     /**
      * Filters the routes matching by route id, route uri, and source location
      */
diff --git a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultDumpRoutesStrategy.java b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultDumpRoutesStrategy.java
index a380537ba119..cf5e5c38db59 100644
--- a/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultDumpRoutesStrategy.java
+++ b/core/camel-core-engine/src/main/java/org/apache/camel/impl/DefaultDumpRoutesStrategy.java
@@ -48,6 +48,7 @@ import org.apache.camel.spi.ModelToStructureDumper;
 import org.apache.camel.spi.ModelToXMLDumper;
 import org.apache.camel.spi.ModelToYAMLDumper;
 import org.apache.camel.spi.Resource;
+import org.apache.camel.spi.RouteDiagramDumper;
 import org.apache.camel.spi.annotations.JdkService;
 import org.apache.camel.support.LoggerHelper;
 import org.apache.camel.support.PluginHelper;
@@ -159,6 +160,38 @@ public class DefaultDumpRoutesStrategy extends ServiceSupport implements DumpRou
             doDumpRoutesAsXml(camelContext);
         } else if ("json".equals(format)) {
             doDumpRoutesStructureAsJSon(camelContext);
+        } else if ("png".equals(format)) {
+            doDumpRoutesDiagram(camelContext);
+        }
+    }
+
+    private void doDumpRoutesDiagram(CamelContext camelContext) {
+        String name = outputFileName;
+        String folder = output;
+        if (name == null && folder == null) {
+            name = "camel-route-diagrams.png";
+        }
+
+        RouteDiagramDumper dumper = PluginHelper.getRouteDiagramDumper(camelContext);
+        try {
+            // use include option as filter
+            String filter = "*";
+            if (!"routes".equals(include)) {
+                filter = include;
+            }
+            if (name != null) {
+                if (log) {
+                    LOG.info("Dumping route diagrams as PNG to file: {}", name);
+                }
+                dumper.dumpRoutesToFile(filter, RouteDiagramDumper.Theme.LIGHT, new File(name));
+            } else {
+                if (log) {
+                    LOG.info("Dumping route diagrams as PNG files to folder: {}", folder);
+                }
+                dumper.dumpRoutesToFolder(filter, RouteDiagramDumper.Theme.LIGHT, new File(folder));
+            }
+        } catch (IOException e) {
+            LOG.warn("Error dumping routes diagrams. This exception is ignored.", e);
         }
     }
 
diff --git a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
index 702a8e2364f2..00c0e8ce0082 100644
--- a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
+++ b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
@@ -55,7 +55,7 @@
     { "name": "camel.main.contextReloadEnabled", "required": false, "description": "Used for enabling context reloading. If enabled then Camel allow external systems such as security vaults (AWS secrets manager, etc.) to trigger refreshing Camel by updating property placeholders and reload all existing routes to take changes into effect.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": false, "secret": false },
     { "name": "camel.main.description", "required": false, "description": "Sets the description (intended for humans) of the Camel application.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false },
     { "name": "camel.main.devConsoleEnabled", "required": false, "description": "Whether to enable developer console (requires camel-console on classpath). The developer console is only for assisting during development. This is NOT for production usage.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": false, "secret": false, "security": "insecure:dev" },
-    { "name": "camel.main.dumpRoutes", "required": false, "description": "If dumping is enabled then Camel will during startup dump all loaded routes (incl rests and route templates) represented as XML\/YAML DSL into the log. This is intended for trouble shooting or to assist during development. Sensitive information that may be configured in the route endpoints could potentially be included in the dump output and is therefore not recommended being used for production usage. This require [...]
+    { "name": "camel.main.dumpRoutes", "required": false, "description": "If dumping is enabled then Camel will during startup dump all loaded routes (incl rests and route templates) represented as XML\/YAML DSL into the log. This is intended for trouble shooting or to assist during development. Sensitive information that may be configured in the route endpoints could potentially be included in the dump output and is therefore not recommended being used for production usage. This require [...]
     { "name": "camel.main.dumpRoutesGeneratedIds", "required": false, "description": "Whether to include auto generated IDs in the dumped output. Default is false.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": false, "secret": false },
     { "name": "camel.main.dumpRoutesInclude", "required": false, "description": "Controls what to include in output for route dumping. Possible values: all, routes, rests, routeConfigurations, routeTemplates, beans, dataFormats. Multiple values can be separated by comma. Default is routes.", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "string", "javaType": "java.lang.String", "defaultValue": "routes", "secret": false },
     { "name": "camel.main.dumpRoutesLog", "required": false, "description": "Whether to log route dumps to Logger", "sourceType": "org.apache.camel.main.DefaultConfigurationProperties", "type": "boolean", "javaType": "boolean", "defaultValue": true, "secret": false },
diff --git a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
index 0792192dce94..03dc3443eec2 100644
--- a/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
+++ b/core/camel-main/src/main/java/org/apache/camel/main/DefaultConfigurationProperties.java
@@ -151,7 +151,7 @@ public abstract class DefaultConfigurationProperties<T> {
     private String exchangeFactory = "default";
     private int exchangeFactoryCapacity = 100;
     private boolean exchangeFactoryStatisticsEnabled;
-    @Metadata(enums = "xml,yaml,json")
+    @Metadata(enums = "xml,yaml,json,png")
     private String dumpRoutes;
     private String dumpRoutesInclude = "routes";
     private boolean dumpRoutesLog = true;
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/PluginHelper.java b/core/camel-support/src/main/java/org/apache/camel/support/PluginHelper.java
index f423f1dbd35e..1d270ef76137 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/PluginHelper.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/PluginHelper.java
@@ -54,6 +54,7 @@ import org.apache.camel.spi.ResourceLoader;
 import org.apache.camel.spi.RestBindingJacksonXmlDataFormatFactory;
 import org.apache.camel.spi.RestBindingJaxbDataFormatFactory;
 import org.apache.camel.spi.RestRegistry;
+import org.apache.camel.spi.RouteDiagramDumper;
 import org.apache.camel.spi.RouteFactory;
 import org.apache.camel.spi.RoutesLoader;
 import org.apache.camel.spi.SimpleFunctionRegistry;
@@ -654,4 +655,18 @@ public final class PluginHelper {
         return extendedCamelContext.getContextPlugin(RestRegistry.class);
     }
 
+    /**
+     * Gets the {@link RouteDiagramDumper} to use.
+     */
+    public static RouteDiagramDumper getRouteDiagramDumper(CamelContext camelContext) {
+        return camelContext.getCamelContextExtension().getContextPlugin(RouteDiagramDumper.class);
+    }
+
+    /**
+     * Gets the {@link RouteDiagramDumper} to use.
+     */
+    public static RouteDiagramDumper getRouteDiagramDumper(ExtendedCamelContext extendedCamelContext) {
+        return extendedCamelContext.getContextPlugin(RouteDiagramDumper.class);
+    }
+
 }
diff --git a/dsl/camel-jbang/camel-jbang-console/pom.xml b/dsl/camel-jbang/camel-jbang-console/pom.xml
index db8c71c0dace..f9f80dd3d44d 100644
--- a/dsl/camel-jbang/camel-jbang-console/pom.xml
+++ b/dsl/camel-jbang/camel-jbang-console/pom.xml
@@ -50,53 +50,11 @@
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-support</artifactId>
         </dependency>
-        <dependency>
-            <groupId>org.apache.camel</groupId>
-            <artifactId>camel-diagram</artifactId>
-        </dependency>
         <dependency>
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-management</artifactId>
         </dependency>
 
-        <dependency>
-            <groupId>org.apache.camel</groupId>
-            <artifactId>camel-test-junit6</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.junit-pioneer</groupId>
-            <artifactId>junit-pioneer</artifactId>
-            <version>${junit-pioneer-version}</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.assertj</groupId>
-            <artifactId>assertj-core</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <!-- logging -->
-        <dependency>
-            <groupId>org.slf4j</groupId>
-            <artifactId>slf4j-api</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.logging.log4j</groupId>
-            <artifactId>log4j-api</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.logging.log4j</groupId>
-            <artifactId>log4j-core</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.logging.log4j</groupId>
-            <artifactId>log4j-slf4j2-impl</artifactId>
-            <scope>test</scope>
-        </dependency>
-
     </dependencies>
 
 </project>
diff --git a/dsl/camel-jbang/camel-jbang-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties b/dsl/camel-jbang/camel-jbang-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties
index ec45d06d2d45..952b9e2c8844 100644
--- a/dsl/camel-jbang/camel-jbang-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties
+++ b/dsl/camel-jbang/camel-jbang-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties
@@ -1,5 +1,5 @@
 # Generated by camel build tools - do NOT edit this file!
-dev-consoles=jbang route-diagram source-dir
+dev-consoles=jbang source-dir
 groupId=org.apache.camel
 artifactId=camel-jbang-console
 version=4.21.0-SNAPSHOT
diff --git a/dsl/camel-kamelet-main/pom.xml b/dsl/camel-kamelet-main/pom.xml
index a5a7b2d1880a..6201643b7f0f 100644
--- a/dsl/camel-kamelet-main/pom.xml
+++ b/dsl/camel-kamelet-main/pom.xml
@@ -60,6 +60,10 @@
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-console</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-diagram</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-dsl-modeline</artifactId>
diff --git a/dsl/camel-kamelet-main/src/generated/resources/camel-factoryfinder-known-dependencies.properties b/dsl/camel-kamelet-main/src/generated/resources/camel-factoryfinder-known-dependencies.properties
index fa64e4d779c0..0caddd31267d 100644
--- a/dsl/camel-kamelet-main/src/generated/resources/camel-factoryfinder-known-dependencies.properties
+++ b/dsl/camel-kamelet-main/src/generated/resources/camel-factoryfinder-known-dependencies.properties
@@ -51,6 +51,7 @@ META-INF/services/org/apache/camel/rest-binding-jaxb-dataformat-factory=camel:ja
 META-INF/services/org/apache/camel/rest-client-request-validator-factory=camel:openapi-validator
 META-INF/services/org/apache/camel/rest-client-response-validator-factory=camel:openapi-validator
 META-INF/services/org/apache/camel/rest-registry-factory=camel:rest
+META-INF/services/org/apache/camel/route-diagram-dumper=camel:diagram
 META-INF/services/org/apache/camel/startup-step-recorder=camel:jfr
 META-INF/services/org/apache/camel/telemetry-dev-tracer=camel:telemetry-dev
 META-INF/services/org/apache/camel/thread-factory-listener=camel:opentelemetry