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

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


The following commit(s) were added to refs/heads/main by this push:
     new 0f4679e666a2 CAMEL-23837: Add DataSource connection pool dev-console 
and TUI tab
0f4679e666a2 is described below

commit 0f4679e666a209d15c399377430c7637eb9b95ca
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Jun 28 08:49:59 2026 +0200

    CAMEL-23837: Add DataSource connection pool dev-console and TUI tab
    
    Adds a datasource dev-console that discovers javax.sql.DataSource beans
    from the Camel registry and extracts pool metrics via reflection for
    HikariCP and Agroal. Includes a TUI DataSource tab with pool exhaustion
    warnings, a `camel get datasource` CLI command, dynamic infra panel
    sizing in the overview tab, and synced jbang examples catalog.
    
    Closes #24278
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 .../apache/camel/catalog/dev-consoles.properties   |   1 +
 .../camel/catalog/dev-consoles/datasource.json     |  15 ++
 .../console/DataSourceDevConsoleConfigurer.java    |  63 +++++
 .../org/apache/camel/dev-console/datasource.json   |  15 ++
 ....apache.camel.impl.console.DataSourceDevConsole |   2 +
 .../org/apache/camel/dev-console/datasource        |   2 +
 .../org/apache/camel/dev-consoles.properties       |   2 +-
 .../camel/impl/console/DataSourceDevConsole.java   | 225 ++++++++++++++++
 .../jbang-commands/camel-jbang-get-datasource.adoc |  28 ++
 .../ROOT/pages/jbang-commands/camel-jbang-get.adoc |   1 +
 .../camel/cli/connector/LocalCliConnector.java     |   7 +
 .../META-INF/camel-jbang-commands-metadata.json    |   2 +-
 .../dsl/jbang/core/commands/CamelJBangMain.java    |   1 +
 .../core/commands/process/ListDataSource.java      | 190 ++++++++++++++
 .../examples/camel-jbang-example-catalog.json      |   4 -
 .../src/main/resources/examples/sql/sql.camel.yaml |   3 +-
 .../dsl/jbang/core/commands/tui/CamelMonitor.java  |  57 +++--
 .../jbang/core/commands/tui/DataSourceInfo.java    |  32 +++
 .../dsl/jbang/core/commands/tui/DataSourceTab.java | 283 +++++++++++++++++++++
 .../jbang/core/commands/tui/IntegrationInfo.java   |   1 +
 .../dsl/jbang/core/commands/tui/OverviewTab.java   |  18 +-
 .../dsl/jbang/core/commands/tui/StatusParser.java  |  25 ++
 22 files changed, 943 insertions(+), 34 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 b993922f1751..536dbe0e6b98 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
@@ -8,6 +8,7 @@ browse
 circuit-breaker
 consumer
 context
+datasource
 debug
 endpoint
 errors
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/datasource.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/datasource.json
new file mode 100644
index 000000000000..8889232ffa1a
--- /dev/null
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/datasource.json
@@ -0,0 +1,15 @@
+{
+  "console": {
+    "kind": "console",
+    "group": "camel",
+    "name": "datasource",
+    "title": "DataSource",
+    "description": "Displays DataSource connection pool metrics",
+    "deprecated": false,
+    "javaType": "org.apache.camel.impl.console.DataSourceDevConsole",
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-console",
+    "version": "4.21.0-SNAPSHOT"
+  }
+}
+
diff --git 
a/core/camel-console/src/generated/java/org/apache/camel/impl/console/DataSourceDevConsoleConfigurer.java
 
b/core/camel-console/src/generated/java/org/apache/camel/impl/console/DataSourceDevConsoleConfigurer.java
new file mode 100644
index 000000000000..f84a33a843d1
--- /dev/null
+++ 
b/core/camel-console/src/generated/java/org/apache/camel/impl/console/DataSourceDevConsoleConfigurer.java
@@ -0,0 +1,63 @@
+/* Generated by camel build tools - do NOT edit this file! */
+package org.apache.camel.impl.console;
+
+import javax.annotation.processing.Generated;
+import java.util.Map;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.spi.ExtendedPropertyConfigurerGetter;
+import org.apache.camel.spi.PropertyConfigurerGetter;
+import org.apache.camel.spi.ConfigurerStrategy;
+import org.apache.camel.spi.GeneratedPropertyConfigurer;
+import org.apache.camel.util.CaseInsensitiveMap;
+import org.apache.camel.impl.console.DataSourceDevConsole;
+
+/**
+ * Generated by camel build tools - do NOT edit this file!
+ */
+@Generated("org.apache.camel.maven.packaging.GenerateConfigurerMojo")
+@SuppressWarnings("unchecked")
+public class DataSourceDevConsoleConfigurer extends 
org.apache.camel.support.component.PropertyConfigurerSupport implements 
GeneratedPropertyConfigurer, ExtendedPropertyConfigurerGetter {
+
+    private static final Map<String, Object> ALL_OPTIONS;
+    static {
+        Map<String, Object> map = new CaseInsensitiveMap();
+        map.put("CamelContext", org.apache.camel.CamelContext.class);
+        ALL_OPTIONS = map;
+    }
+
+    @Override
+    public boolean configure(CamelContext camelContext, Object obj, String 
name, Object value, boolean ignoreCase) {
+        org.apache.camel.impl.console.DataSourceDevConsole target = 
(org.apache.camel.impl.console.DataSourceDevConsole) obj;
+        switch (ignoreCase ? name.toLowerCase() : name) {
+        case "camelcontext":
+        case "camelContext": target.setCamelContext(property(camelContext, 
org.apache.camel.CamelContext.class, value)); return true;
+        default: return false;
+        }
+    }
+
+    @Override
+    public Map<String, Object> getAllOptions(Object target) {
+        return ALL_OPTIONS;
+    }
+
+    @Override
+    public Class<?> getOptionType(String name, boolean ignoreCase) {
+        switch (ignoreCase ? name.toLowerCase() : name) {
+        case "camelcontext":
+        case "camelContext": return org.apache.camel.CamelContext.class;
+        default: return null;
+        }
+    }
+
+    @Override
+    public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
+        org.apache.camel.impl.console.DataSourceDevConsole target = 
(org.apache.camel.impl.console.DataSourceDevConsole) obj;
+        switch (ignoreCase ? name.toLowerCase() : name) {
+        case "camelcontext":
+        case "camelContext": return target.getCamelContext();
+        default: return null;
+        }
+    }
+}
+
diff --git 
a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/datasource.json
 
b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/datasource.json
new file mode 100644
index 000000000000..8889232ffa1a
--- /dev/null
+++ 
b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/datasource.json
@@ -0,0 +1,15 @@
+{
+  "console": {
+    "kind": "console",
+    "group": "camel",
+    "name": "datasource",
+    "title": "DataSource",
+    "description": "Displays DataSource connection pool metrics",
+    "deprecated": false,
+    "javaType": "org.apache.camel.impl.console.DataSourceDevConsole",
+    "groupId": "org.apache.camel",
+    "artifactId": "camel-console",
+    "version": "4.21.0-SNAPSHOT"
+  }
+}
+
diff --git 
a/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.impl.console.DataSourceDevConsole
 
b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.impl.console.DataSourceDevConsole
new file mode 100644
index 000000000000..891a3bb086d7
--- /dev/null
+++ 
b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.impl.console.DataSourceDevConsole
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.impl.console.DataSourceDevConsoleConfigurer
diff --git 
a/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-console/datasource
 
b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-console/datasource
new file mode 100644
index 000000000000..84c6e3b35cca
--- /dev/null
+++ 
b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-console/datasource
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.impl.console.DataSourceDevConsole
diff --git 
a/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties
 
b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties
index 84bbd4e88131..e25def6b5f32 100644
--- 
a/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-consoles.properties
+++ 
b/core/camel-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=bean blocked browse circuit-breaker consumer context debug 
endpoint errors eval-language event gc health inflight internal-tasks 
java-security jvm log memory message-history processor producer properties 
receive reload rest rest-spec route route-controller route-dump route-group 
route-structure route-topology send service simple-language source 
startup-recorder system-properties thread top trace transformers 
type-converters variables
+dev-consoles=bean blocked browse circuit-breaker consumer context datasource 
debug endpoint errors eval-language event gc health inflight internal-tasks 
java-security jvm log memory message-history processor producer properties 
receive reload rest rest-spec route route-controller route-dump route-group 
route-structure route-topology send service simple-language source 
startup-recorder system-properties thread top trace transformers 
type-converters variables
 groupId=org.apache.camel
 artifactId=camel-console
 version=4.21.0-SNAPSHOT
diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/DataSourceDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/DataSourceDevConsole.java
new file mode 100644
index 000000000000..496add4835f7
--- /dev/null
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/DataSourceDevConsole.java
@@ -0,0 +1,225 @@
+/*
+ * 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.impl.console;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+import org.apache.camel.spi.Configurer;
+import org.apache.camel.spi.annotations.DevConsole;
+import org.apache.camel.support.console.AbstractDevConsole;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+
+@DevConsole(name = "datasource", displayName = "DataSource", description = 
"Displays DataSource connection pool metrics")
+@Configurer(extended = true)
+public class DataSourceDevConsole extends AbstractDevConsole {
+
+    private static final String HIKARI_CLASS = 
"com.zaxxer.hikari.HikariDataSource";
+    private static final String AGROAL_CLASS = 
"io.agroal.api.AgroalDataSource";
+
+    public DataSourceDevConsole() {
+        super("camel", "datasource", "DataSource", "Displays DataSource 
connection pool metrics");
+    }
+
+    @Override
+    protected String doCallText(Map<String, Object> options) {
+        StringBuilder sb = new StringBuilder();
+
+        Map<String, DataSource> dataSources
+                = 
getCamelContext().getRegistry().findByTypeWithName(DataSource.class);
+
+        if (dataSources.isEmpty()) {
+            sb.append("No DataSources found in registry\n");
+            return sb.toString();
+        }
+
+        for (Map.Entry<String, DataSource> entry : dataSources.entrySet()) {
+            DataSource ds = entry.getValue();
+            String poolType = detectPoolType(ds);
+
+            sb.append(String.format("DataSource: %s (%s)%n", entry.getKey(), 
poolType));
+            sb.append(String.format("  Type: %s%n", ds.getClass().getName()));
+
+            if ("HikariCP".equals(poolType)) {
+                appendHikariText(sb, ds);
+            } else if ("Agroal".equals(poolType)) {
+                appendAgroalText(sb, ds);
+            }
+            sb.append("\n");
+        }
+
+        return sb.toString();
+    }
+
+    @Override
+    protected JsonObject doCallJson(Map<String, Object> options) {
+        JsonObject root = new JsonObject();
+        JsonArray arr = new JsonArray();
+
+        Map<String, DataSource> dataSources
+                = 
getCamelContext().getRegistry().findByTypeWithName(DataSource.class);
+
+        for (Map.Entry<String, DataSource> entry : dataSources.entrySet()) {
+            DataSource ds = entry.getValue();
+            String poolType = detectPoolType(ds);
+
+            JsonObject jo = new JsonObject();
+            jo.put("name", entry.getKey());
+            jo.put("type", ds.getClass().getName());
+            jo.put("poolType", poolType);
+
+            if ("HikariCP".equals(poolType)) {
+                collectHikariMetrics(jo, ds);
+            } else if ("Agroal".equals(poolType)) {
+                collectAgroalMetrics(jo, ds);
+            }
+
+            arr.add(jo);
+        }
+
+        root.put("dataSources", arr);
+        return root;
+    }
+
+    private static String detectPoolType(DataSource ds) {
+        String className = ds.getClass().getName();
+        if (HIKARI_CLASS.equals(className)) {
+            return "HikariCP";
+        } else if (AGROAL_CLASS.equals(className)) {
+            return "Agroal";
+        }
+        return "Unknown";
+    }
+
+    // ---- HikariCP ----
+
+    private void collectHikariMetrics(JsonObject jo, DataSource ds) {
+        Object poolName = invokeMethod(ds, "getPoolName");
+        if (poolName != null) {
+            jo.put("poolName", String.valueOf(poolName));
+        }
+        Object maxPoolSize = invokeMethod(ds, "getMaximumPoolSize");
+        if (maxPoolSize instanceof Number n) {
+            jo.put("maxPoolSize", n.intValue());
+        }
+
+        Object mxBean = invokeMethod(ds, "getHikariPoolMXBean");
+        if (mxBean != null) {
+            putInt(jo, "active", invokeMethod(mxBean, "getActiveConnections"));
+            putInt(jo, "idle", invokeMethod(mxBean, "getIdleConnections"));
+            putInt(jo, "total", invokeMethod(mxBean, "getTotalConnections"));
+            putInt(jo, "waiting", invokeMethod(mxBean, 
"getThreadsAwaitingConnection"));
+        }
+    }
+
+    private void appendHikariText(StringBuilder sb, DataSource ds) {
+        Object poolName = invokeMethod(ds, "getPoolName");
+        if (poolName != null) {
+            sb.append(String.format("  Pool Name: %s%n", poolName));
+        }
+        Object maxPoolSize = invokeMethod(ds, "getMaximumPoolSize");
+        if (maxPoolSize != null) {
+            sb.append(String.format("  Max Pool Size: %s%n", maxPoolSize));
+        }
+
+        Object mxBean = invokeMethod(ds, "getHikariPoolMXBean");
+        if (mxBean != null) {
+            sb.append(String.format("  Active: %s%n", invokeMethod(mxBean, 
"getActiveConnections")));
+            sb.append(String.format("  Idle: %s%n", invokeMethod(mxBean, 
"getIdleConnections")));
+            sb.append(String.format("  Total: %s%n", invokeMethod(mxBean, 
"getTotalConnections")));
+            sb.append(String.format("  Waiting: %s%n", invokeMethod(mxBean, 
"getThreadsAwaitingConnection")));
+        } else {
+            sb.append("  Pool not yet initialized\n");
+        }
+    }
+
+    // ---- Agroal ----
+
+    private void collectAgroalMetrics(JsonObject jo, DataSource ds) {
+        Object metrics = invokeMethod(ds, "getMetrics");
+        if (metrics != null) {
+            putLong(jo, "active", invokeMethod(metrics, "activeCount"));
+            putLong(jo, "idle", invokeMethod(metrics, "availableCount"));
+            putLong(jo, "maxUsed", invokeMethod(metrics, "maxUsedCount"));
+            putLong(jo, "leakDetection", invokeMethod(metrics, 
"leakDetectionCount"));
+            putLong(jo, "created", invokeMethod(metrics, "creationCount"));
+        }
+
+        // max pool size via configuration chain
+        Object config = invokeMethod(ds, "getConfiguration");
+        if (config != null) {
+            Object poolConfig = invokeMethod(config, 
"connectionPoolConfiguration");
+            if (poolConfig != null) {
+                Object maxSize = invokeMethod(poolConfig, "maxSize");
+                if (maxSize instanceof Number n) {
+                    jo.put("maxPoolSize", n.intValue());
+                }
+            }
+        }
+
+        // compute total from active + idle
+        Object active = jo.get("active");
+        Object idle = jo.get("idle");
+        if (active instanceof Number a && idle instanceof Number i) {
+            jo.put("total", a.longValue() + i.longValue());
+        }
+    }
+
+    private void appendAgroalText(StringBuilder sb, DataSource ds) {
+        Object metrics = invokeMethod(ds, "getMetrics");
+        if (metrics != null) {
+            sb.append(String.format("  Active: %s%n", invokeMethod(metrics, 
"activeCount")));
+            sb.append(String.format("  Available: %s%n", invokeMethod(metrics, 
"availableCount")));
+            sb.append(String.format("  Max Used: %s%n", invokeMethod(metrics, 
"maxUsedCount")));
+            sb.append(String.format("  Leak Detection: %s%n", 
invokeMethod(metrics, "leakDetectionCount")));
+        }
+
+        Object config = invokeMethod(ds, "getConfiguration");
+        if (config != null) {
+            Object poolConfig = invokeMethod(config, 
"connectionPoolConfiguration");
+            if (poolConfig != null) {
+                sb.append(String.format("  Max Pool Size: %s%n", 
invokeMethod(poolConfig, "maxSize")));
+            }
+        }
+    }
+
+    // ---- Reflection helpers ----
+
+    private static Object invokeMethod(Object target, String methodName) {
+        try {
+            Method m = target.getClass().getMethod(methodName);
+            return m.invoke(target);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    private static void putInt(JsonObject jo, String key, Object value) {
+        if (value instanceof Number n) {
+            jo.put(key, n.intValue());
+        }
+    }
+
+    private static void putLong(JsonObject jo, String key, Object value) {
+        if (value instanceof Number n) {
+            jo.put(key, n.longValue());
+        }
+    }
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get-datasource.adoc
 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get-datasource.adoc
new file mode 100644
index 000000000000..aed7954ba37a
--- /dev/null
+++ 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get-datasource.adoc
@@ -0,0 +1,28 @@
+
+// AUTO-GENERATED by camel-package-maven-plugin - DO NOT EDIT THIS FILE
+= camel get datasource
+
+Get status of DataSource connection pools
+
+
+== Usage
+
+[source,bash]
+----
+camel get datasource [options]
+----
+
+
+
+== Options
+
+[cols="2,5,1,2",options="header"]
+|===
+| Option | Description | Default | Type
+| `--json` | Output in JSON Format |  | boolean
+| `--sort` | Sort by pid, name or age | pid | String
+| `--watch` | Execute periodically and showing output fullscreen |  | boolean
+| `-h,--help` | Display the help and sub-commands |  | boolean
+|===
+
+
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get.adoc 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get.adoc
index 3384abd60ecb..8d2eb511b28e 100644
--- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get.adoc
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get.adoc
@@ -24,6 +24,7 @@ camel get [options]
 | xref:jbang-commands/camel-jbang-get-consumer.adoc[consumer] | Get status of 
Camel consumers
 | xref:jbang-commands/camel-jbang-get-context.adoc[context] | Get status of 
Camel integrations
 | xref:jbang-commands/camel-jbang-get-count.adoc[count] | Get total and failed 
exchanges
+| xref:jbang-commands/camel-jbang-get-datasource.adoc[datasource] | Get status 
of DataSource connection pools
 | xref:jbang-commands/camel-jbang-get-endpoint.adoc[endpoint] | Get usage of 
Camel endpoints
 | xref:jbang-commands/camel-jbang-get-error.adoc[error] | Get captured routing 
errors of Camel integrations
 | xref:jbang-commands/camel-jbang-get-event.adoc[event] | Get latest events of 
Camel integrations
diff --git 
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
 
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
index 98a2f5b5366d..21688dde8ca6 100644
--- 
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
+++ 
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
@@ -1443,6 +1443,13 @@ public class LocalCliConnector extends ServiceSupport 
implements CliConnector, C
                         root.put("errors", summary);
                     }
                 }
+                DevConsole dc27 = dcr.resolveById("datasource");
+                if (dc27 != null) {
+                    JsonObject json = (JsonObject) 
dc27.call(DevConsole.MediaType.JSON);
+                    if (json != null && !json.isEmpty()) {
+                        root.put("dataSources", json);
+                    }
+                }
             }
             // various details
             JsonObject mem = collectMemory();
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
index 791f8dd75b40..19c7b6505642 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
@@ -15,7 +15,7 @@
     { "name": "eval", "fullName": "eval", "description": "Evaluate Camel 
expressions and scripts", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.EvalCommand", "options": [ { "names": 
"-h,--help", "description": "Display the help and sub-commands", "javaType": 
"boolean", "type": "boolean" } ], "subcommands": [ { "name": "expression", 
"fullName": "eval expression", "description": "Evaluates Camel expression", 
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.EvalEx [...]
     { "name": "explain", "fullName": "explain", "description": "Explain what a 
Camel route does using AI\/LLM", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Explain", "options": [ { "names": 
"--api-key", "description": "API key for authentication. Also reads 
ANTHROPIC_API_KEY, OPENAI_API_KEY, or LLM_API_KEY env vars", "javaType": 
"java.lang.String", "type": "string" }, { "names": "--api-type", "description": 
"API type: 'ollama', 'openai' (OpenAI-compatible), or 'anthropic' (A [...]
     { "name": "export", "fullName": "export", "description": "Export to other 
runtimes (Camel Main, Spring Boot, or Quarkus)", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Export", "options": [ { "names": 
"--build-property", "description": "Maven build properties, ex. 
--build-property=prop1=foo", "javaType": "java.util.List", "type": "array" }, { 
"names": "--camel-spring-boot-version", "description": "Camel version to use 
with Spring Boot", "javaType": "java.lang.String", "ty [...]
-    { "name": "get", "fullName": "get", "description": "Get status of Camel 
integrations", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.process.CamelStatus", "options": [ { 
"names": "--watch", "description": "Execute periodically and showing output 
fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": 
"-h,--help", "description": "Display the help and sub-commands", "javaType": 
"boolean", "type": "boolean" } ], "subcommands": [ { "name": "bean", 
"fullName": "get  [...]
+    { "name": "get", "fullName": "get", "description": "Get status of Camel 
integrations", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.process.CamelStatus", "options": [ { 
"names": "--watch", "description": "Execute periodically and showing output 
fullscreen", "javaType": "boolean", "type": "boolean" }, { "names": 
"-h,--help", "description": "Display the help and sub-commands", "javaType": 
"boolean", "type": "boolean" } ], "subcommands": [ { "name": "bean", 
"fullName": "get  [...]
     { "name": "harden", "fullName": "harden", "description": "Suggest security 
hardening for Camel routes using AI\/LLM", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Harden", "options": [ { "names": 
"--api-key", "description": "API key for authentication. Also reads 
OPENAI_API_KEY or LLM_API_KEY env vars", "javaType": "java.lang.String", 
"type": "string" }, { "names": "--api-type", "description": "API type: 'ollama' 
or 'openai' (OpenAI-compatible)", "defaultValue": "ollama", [...]
     { "name": "hawtio", "fullName": "hawtio", "description": "Launch Hawtio 
web console", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.process.Hawtio", "options": [ { 
"names": "--host", "description": "Hostname to bind the Hawtio web console to", 
"defaultValue": "127.0.0.1", "javaType": "java.lang.String", "type": "string" 
}, { "names": "--openUrl", "description": "To automatic open Hawtio web console 
in the web browser", "defaultValue": "true", "javaType": "boolean", "type": 
[...]
     { "name": "infra", "fullName": "infra", "description": "List and Run 
external services for testing and prototyping", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.infra.InfraCommand", "options": [ { 
"names": "--json", "description": "Output in JSON Format", "javaType": 
"boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display 
the help and sub-commands", "javaType": "boolean", "type": "boolean" } ], 
"subcommands": [ { "name": "get", "fullName": "infra  [...]
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
index 1b3f9de7dc29..5113540c571f 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
@@ -158,6 +158,7 @@ public class CamelJBangMain implements Callable<Integer> {
                         .addSubcommand("consumer", new CommandLine(new 
ListConsumer(this)))
                         .addSubcommand("context", new CommandLine(new 
CamelContextStatus(this)))
                         .addSubcommand("count", new CommandLine(new 
CamelCount(this)))
+                        .addSubcommand("datasource", new CommandLine(new 
ListDataSource(this)))
                         .addSubcommand("endpoint", new CommandLine(new 
ListEndpoint(this)))
                         .addSubcommand("error", new CommandLine(new 
ListError(this)))
                         .addSubcommand("event", new CommandLine(new 
ListEvent(this)))
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListDataSource.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListDataSource.java
new file mode 100644
index 000000000000..0e78ed4f6cf4
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListDataSource.java
@@ -0,0 +1,190 @@
+/*
+ * 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.dsl.jbang.core.commands.process;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import com.github.freva.asciitable.AsciiTable;
+import com.github.freva.asciitable.Column;
+import com.github.freva.asciitable.HorizontalAlign;
+import com.github.freva.asciitable.OverflowBehaviour;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.PidNameAgeCompletionCandidates;
+import org.apache.camel.dsl.jbang.core.common.ProcessHelper;
+import org.apache.camel.util.TimeUtils;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+
+@Command(name = "datasource", description = "Get status of DataSource 
connection pools", sortOptions = false,
+         showDefaultValues = true,
+         footer = {
+                 "%nExamples:",
+                 "  camel get datasource",
+                 "  camel get datasource --watch" })
+public class ListDataSource extends ProcessWatchCommand {
+
+    @CommandLine.Parameters(description = "Name or pid of running Camel 
integration", arity = "0..1")
+    String name = "*";
+
+    @CommandLine.Option(names = { "--sort" }, completionCandidates = 
PidNameAgeCompletionCandidates.class,
+                        description = "Sort by pid, name or age", defaultValue 
= "pid")
+    String sort;
+
+    public ListDataSource(CamelJBangMain main) {
+        super(main);
+    }
+
+    @Override
+    public Integer doProcessWatchCall() throws Exception {
+        List<Row> rows = new ArrayList<>();
+
+        List<Long> pids = findPids(name);
+        ProcessHandle.allProcesses()
+                .filter(ph -> pids.contains(ph.pid()))
+                .forEach(ph -> {
+                    JsonObject root = loadStatus(ph.pid());
+                    if (root != null) {
+                        JsonObject context = (JsonObject) root.get("context");
+                        JsonObject ds = (JsonObject) root.get("dataSources");
+                        if (context != null && ds != null) {
+                            JsonArray array = (JsonArray) 
ds.get("dataSources");
+                            if (array != null) {
+                                for (int i = 0; i < array.size(); i++) {
+                                    JsonObject o = (JsonObject) array.get(i);
+                                    Row row = new Row();
+                                    row.name = context.getString("name");
+                                    if ("CamelJBang".equals(row.name)) {
+                                        row.name = 
ProcessHelper.extractName(root, ph);
+                                    }
+                                    row.pid = Long.toString(ph.pid());
+                                    row.uptime = extractSince(ph);
+                                    row.age = TimeUtils.printSince(row.uptime);
+                                    row.dsName = o.getString("name");
+                                    row.type = o.getString("type");
+                                    row.poolType = o.getString("poolType");
+                                    row.poolName = o.getString("poolName");
+                                    row.active = 
o.getIntegerOrDefault("active", 0);
+                                    row.idle = o.getIntegerOrDefault("idle", 
0);
+                                    row.total = o.getIntegerOrDefault("total", 
0);
+                                    row.maxPoolSize = 
o.getIntegerOrDefault("maxPoolSize", 0);
+                                    row.waiting = 
o.getIntegerOrDefault("waiting", 0);
+                                    rows.add(row);
+                                }
+                            }
+                        }
+                    }
+                });
+
+        // sort rows
+        rows.sort(this::sortRow);
+
+        if (!rows.isEmpty()) {
+            printTable(rows);
+        }
+
+        return 0;
+    }
+
+    protected void printTable(List<Row> rows) {
+        if (jsonOutput) {
+            printer().println(Jsoner.serialize(rows.stream().map(r -> {
+                JsonObject jo = new JsonObject();
+                jo.put("pid", r.pid);
+                jo.put("name", r.name);
+                jo.put("age", r.age);
+                jo.put("dsName", r.dsName);
+                jo.put("poolType", r.poolType);
+                if (r.poolName != null) {
+                    jo.put("poolName", r.poolName);
+                }
+                jo.put("active", r.active);
+                jo.put("idle", r.idle);
+                jo.put("total", r.total);
+                jo.put("maxPoolSize", r.maxPoolSize);
+                jo.put("waiting", r.waiting);
+                jo.put("type", r.type);
+                return jo;
+            }).collect(Collectors.toList())));
+            return;
+        }
+        int tw = terminalWidth();
+        int typeW = Math.max(20, Math.min(60, tw - 100));
+        printer().println(AsciiTable.getTable(AsciiTable.NO_BORDERS, rows, 
Arrays.asList(
+                new 
Column().header("PID").headerAlign(HorizontalAlign.CENTER).with(r -> r.pid),
+                new 
Column().header("NAME").dataAlign(HorizontalAlign.LEFT).maxWidth(30, 
OverflowBehaviour.ELLIPSIS_RIGHT)
+                        .with(r -> r.name),
+                new 
Column().header("AGE").headerAlign(HorizontalAlign.CENTER).with(r -> r.age),
+                new 
Column().header("DS-NAME").dataAlign(HorizontalAlign.LEFT).maxWidth(25, 
OverflowBehaviour.ELLIPSIS_RIGHT)
+                        .with(r -> r.dsName),
+                new Column().header("POOL").dataAlign(HorizontalAlign.LEFT)
+                        .with(r -> r.poolName != null ? r.poolName : 
(r.poolType != null ? r.poolType : "")),
+                new 
Column().header("ACTIVE").headerAlign(HorizontalAlign.RIGHT).dataAlign(HorizontalAlign.RIGHT)
+                        .with(r -> Integer.toString(r.active)),
+                new 
Column().header("IDLE").headerAlign(HorizontalAlign.RIGHT).dataAlign(HorizontalAlign.RIGHT)
+                        .with(r -> Integer.toString(r.idle)),
+                new 
Column().header("TOTAL").headerAlign(HorizontalAlign.RIGHT).dataAlign(HorizontalAlign.RIGHT)
+                        .with(r -> Integer.toString(r.total)),
+                new 
Column().header("MAX").headerAlign(HorizontalAlign.RIGHT).dataAlign(HorizontalAlign.RIGHT)
+                        .with(r -> r.maxPoolSize > 0 ? 
Integer.toString(r.maxPoolSize) : ""),
+                new 
Column().header("WAITING").headerAlign(HorizontalAlign.RIGHT).dataAlign(HorizontalAlign.RIGHT)
+                        .with(r -> Integer.toString(r.waiting)),
+                new Column().header("TYPE").dataAlign(HorizontalAlign.LEFT)
+                        .maxWidth(typeW, OverflowBehaviour.ELLIPSIS_RIGHT)
+                        .with(r -> r.type))));
+    }
+
+    protected int sortRow(Row o1, Row o2) {
+        String s = sort;
+        int negate = 1;
+        if (s.startsWith("-")) {
+            s = s.substring(1);
+            negate = -1;
+        }
+        switch (s) {
+            case "pid":
+                return Long.compare(Long.parseLong(o1.pid), 
Long.parseLong(o2.pid)) * negate;
+            case "name":
+                return o1.name.compareToIgnoreCase(o2.name) * negate;
+            case "age":
+                return Long.compare(o1.uptime, o2.uptime) * negate;
+            default:
+                return 0;
+        }
+    }
+
+    static class Row {
+        String pid;
+        String name;
+        long uptime;
+        String age;
+        String dsName;
+        String type;
+        String poolType;
+        String poolName;
+        int active;
+        int idle;
+        int total;
+        int maxPoolSize;
+        int waiting;
+    }
+}
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 91941c4cdfa8..33129c74ff89 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
@@ -144,7 +144,6 @@
         "files": [
             "README.md",
             "application.properties",
-            "compose.yaml",
             "docling-langchain4j-rag.yaml",
             "sample.md"
         ],
@@ -197,7 +196,6 @@
         "files": [
             "README.md",
             "application.properties",
-            "compose.yaml",
             "ftp.camel.yaml",
             "jbang.properties"
         ],
@@ -305,7 +303,6 @@
         "files": [
             "README.md",
             "application.properties",
-            "compose.yaml",
             "infra/mosquitto.conf",
             "mqtt.camel.yaml",
             "start.sh"
@@ -460,7 +457,6 @@
             "analyzer/error-analyzer.camel.yaml",
             "containers/caches/infinispan-events-config.json",
             "containers/caches/infinispan-events-to-process-config.json",
-            "containers/docker-compose.yaml",
             "containers/otel-collector-config.yaml",
             "correlator/application-dev.properties",
             "correlator/correlated-log-schema.json",
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/sql/sql.camel.yaml
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/sql/sql.camel.yaml
index e7a18a2ab2bf..3153d25c1917 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/sql/sql.camel.yaml
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/sql/sql.camel.yaml
@@ -29,8 +29,7 @@
       uri: timer
       parameters:
         timerName: select
-        repeatCount: 1
-        delay: 2000
+        delay: 5000
       steps:
         - to:
             uri: sql
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
index e9113704af78..f9985158d512 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
@@ -224,6 +224,7 @@ public class CamelMonitor extends CamelCommand {
     private SpansTab spansTab;
     private ProcessTab processTab;
     private OverviewTab overviewTab;
+    private DataSourceTab dataSourceTab;
 
     // "Switch integration" popup state
     private boolean showSwitchPopup;
@@ -277,6 +278,7 @@ public class CamelMonitor extends CamelCommand {
         diagramTab = new DiagramTab(ctx);
         routesTab = new RoutesTab(ctx);
         consumersTab = new ConsumersTab(ctx);
+        dataSourceTab = new DataSourceTab(ctx);
         endpointsTab = new EndpointsTab(ctx, metrics);
         httpTab = new HttpTab(ctx);
         healthTab = new HealthTab(ctx);
@@ -436,7 +438,7 @@ public class CamelMonitor extends CamelCommand {
                 return true;
             }
             if (ke.isDown()) {
-                morePopupState.selectNext(13);
+                morePopupState.selectNext(14);
                 return true;
             }
             int shortcutSel = morePopupShortcut(ke);
@@ -455,13 +457,14 @@ public class CamelMonitor extends CamelCommand {
                         case 3 -> classpathTab;
                         case 4 -> configurationTab;
                         case 5 -> consumersTab;
-                        case 6 -> inflightTab;
-                        case 7 -> memoryTab;
-                        case 8 -> metricsTab;
-                        case 9 -> spansTab;
-                        case 10 -> processTab;
-                        case 11 -> startupTab;
-                        case 12 -> threadsTab;
+                        case 6 -> dataSourceTab;
+                        case 7 -> inflightTab;
+                        case 8 -> memoryTab;
+                        case 9 -> metricsTab;
+                        case 10 -> spansTab;
+                        case 11 -> processTab;
+                        case 12 -> startupTab;
+                        case 13 -> threadsTab;
                         default -> null;
                     };
                     if (activeMoreTab != null) {
@@ -938,6 +941,7 @@ public class CamelMonitor extends CamelCommand {
         startupTab.onIntegrationChanged();
         configurationTab.onIntegrationChanged();
         consumersTab.onIntegrationChanged();
+        dataSourceTab.onIntegrationChanged();
         circuitBreakerTab.onIntegrationChanged();
         inflightTab.onIntegrationChanged();
         spansTab.onIntegrationChanged();
@@ -1240,7 +1244,7 @@ public class CamelMonitor extends CamelCommand {
 
     private void renderMorePopup(Frame frame, Rect area) {
         int popupW = 22;
-        int popupH = 15;
+        int popupH = 16;
         // Position just below the "0 More▾" tab label
         int dividerW = CharWidth.of(" | ");
         int tabBarX = 0;
@@ -1268,6 +1272,7 @@ public class CamelMonitor extends CamelCommand {
                 ListItem.from(Line.from(Span.raw("  Cl"), Span.styled("a", 
keyStyle), Span.raw("sspath"))),
                 ListItem.from(Line.from(Span.raw("  Confi"), Span.styled("g", 
keyStyle), Span.raw("uration"))),
                 ListItem.from(Line.from(Span.raw("  Co"), Span.styled("n", 
keyStyle), Span.raw("sumers"))),
+                ListItem.from(Line.from(Span.raw("  "), Span.styled("D", 
keyStyle), Span.raw("ataSource"))),
                 ListItem.from(Line.from(Span.raw("  "), Span.styled("I", 
keyStyle), Span.raw("nflight"))),
                 ListItem.from(Line.from(Span.raw("  "), Span.styled("M", 
keyStyle), Span.raw("emory"))),
                 ListItem.from(Line.from(Span.raw("  M"), Span.styled("e", 
keyStyle), Span.raw("trics"))),
@@ -1370,27 +1375,30 @@ public class CamelMonitor extends CamelCommand {
         if (ke.isChar('n')) {
             return 5;
         }
-        if (ke.isChar('i')) {
+        if (ke.isChar('d')) {
             return 6;
         }
-        if (ke.isChar('m')) {
+        if (ke.isChar('i')) {
             return 7;
         }
-        if (ke.isChar('e')) {
+        if (ke.isChar('m')) {
             return 8;
         }
-        if (ke.isChar('o')) {
+        if (ke.isChar('e')) {
             return 9;
         }
-        if (ke.isChar('p')) {
+        if (ke.isChar('o')) {
             return 10;
         }
-        if (ke.isChar('s')) {
+        if (ke.isChar('p')) {
             return 11;
         }
-        if (ke.isChar('t')) {
+        if (ke.isChar('s')) {
             return 12;
         }
+        if (ke.isChar('t')) {
+            return 13;
+        }
         return -1;
     }
 
@@ -2470,7 +2478,7 @@ public class CamelMonitor extends CamelCommand {
 
     private static final String[] MORE_TAB_NAMES = {
             "Beans", "Browse", "Circuit Breaker", "Classpath", "Configuration",
-            "Consumers", "Inflight", "Memory", "Metrics", "Spans", "Process", 
"Startup", "Threads"
+            "Consumers", "DataSource", "Inflight", "Memory", "Metrics", 
"Spans", "Process", "Startup", "Threads"
     };
 
     String navigateToTab(String tabName) {
@@ -2492,13 +2500,14 @@ public class CamelMonitor extends CamelCommand {
                     case 3 -> classpathTab;
                     case 4 -> configurationTab;
                     case 5 -> consumersTab;
-                    case 6 -> inflightTab;
-                    case 7 -> memoryTab;
-                    case 8 -> metricsTab;
-                    case 9 -> spansTab;
-                    case 10 -> processTab;
-                    case 11 -> startupTab;
-                    case 12 -> threadsTab;
+                    case 6 -> dataSourceTab;
+                    case 7 -> inflightTab;
+                    case 8 -> memoryTab;
+                    case 9 -> metricsTab;
+                    case 10 -> spansTab;
+                    case 11 -> processTab;
+                    case 12 -> startupTab;
+                    case 13 -> threadsTab;
                     default -> null;
                 };
                 if (activeMoreTab != null) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DataSourceInfo.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DataSourceInfo.java
new file mode 100644
index 000000000000..83cbfd3be650
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DataSourceInfo.java
@@ -0,0 +1,32 @@
+/*
+ * 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.dsl.jbang.core.commands.tui;
+
+class DataSourceInfo {
+    String name;
+    String type;
+    String poolType;
+    String poolName;
+    int active;
+    int idle;
+    int total;
+    int waiting;
+    int maxPoolSize;
+    int maxUsed;
+    int leakDetection;
+    int created;
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DataSourceTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DataSourceTab.java
new file mode 100644
index 000000000000..79db403af498
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DataSourceTab.java
@@ -0,0 +1,283 @@
+/*
+ * 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.dsl.jbang.core.commands.tui;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import dev.tamboui.layout.Constraint;
+import dev.tamboui.layout.Rect;
+import dev.tamboui.style.Color;
+import dev.tamboui.style.Style;
+import dev.tamboui.terminal.Frame;
+import dev.tamboui.text.Span;
+import dev.tamboui.tui.event.KeyEvent;
+import dev.tamboui.widgets.block.Block;
+import dev.tamboui.widgets.block.BorderType;
+import dev.tamboui.widgets.table.Cell;
+import dev.tamboui.widgets.table.Row;
+import dev.tamboui.widgets.table.Table;
+import dev.tamboui.widgets.table.TableState;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+
+import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.*;
+
+class DataSourceTab implements MonitorTab {
+
+    private static final String[] SORT_COLUMNS = { "name", "pool", "active", 
"idle", "total", "max", "waiting" };
+
+    private final MonitorContext ctx;
+    private final TableState tableState = new TableState();
+    private String sort = "name";
+    private int sortIndex;
+    private boolean sortReversed;
+
+    DataSourceTab(MonitorContext ctx) {
+        this.ctx = ctx;
+    }
+
+    @Override
+    public boolean handleKeyEvent(KeyEvent ke) {
+        if (ke.isChar('s')) {
+            sortIndex = (sortIndex + 1) % SORT_COLUMNS.length;
+            sort = SORT_COLUMNS[sortIndex];
+            sortReversed = false;
+            return true;
+        }
+        if (ke.isChar('S')) {
+            sortReversed = !sortReversed;
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean handleEscape() {
+        return false;
+    }
+
+    @Override
+    public void navigateUp() {
+    }
+
+    @Override
+    public void navigateDown() {
+    }
+
+    @Override
+    public void render(Frame frame, Rect area) {
+        IntegrationInfo info = ctx.findSelectedIntegration();
+        if (info == null) {
+            renderNoSelection(frame, area);
+            return;
+        }
+
+        List<DataSourceInfo> sorted = new ArrayList<>(info.dataSources);
+        sorted.sort(this::sortDataSource);
+
+        List<Row> rows = new ArrayList<>();
+        for (DataSourceInfo di : sorted) {
+            boolean exhausted = di.maxPoolSize > 0 && di.active >= 
di.maxPoolSize;
+            boolean waiters = di.waiting > 0;
+
+            Style activeStyle = exhausted
+                    ? Style.EMPTY.fg(Color.LIGHT_RED)
+                    : Style.EMPTY.fg(Color.CYAN);
+            Style waitingStyle = waiters
+                    ? Style.EMPTY.fg(Color.YELLOW)
+                    : Style.EMPTY;
+
+            String poolLabel = di.poolName != null ? di.poolName : 
(di.poolType != null ? di.poolType : "");
+
+            rows.add(Row.from(
+                    Cell.from(Span.styled(di.name != null ? di.name : "", 
Style.EMPTY.fg(Color.CYAN))),
+                    Cell.from(poolLabel),
+                    rightCell(String.valueOf(di.active), 8, activeStyle),
+                    rightCell(String.valueOf(di.idle), 8),
+                    rightCell(String.valueOf(di.total), 8),
+                    rightCell(di.maxPoolSize > 0 ? 
String.valueOf(di.maxPoolSize) : "", 8),
+                    rightCell(String.valueOf(di.waiting), 8, waitingStyle),
+                    Cell.from(Span.styled(shortType(di.type), 
Style.EMPTY.dim()))));
+        }
+
+        if (rows.isEmpty()) {
+            rows.add(Row.from(
+                    Cell.from(Span.styled("No DataSources", 
Style.EMPTY.dim())),
+                    Cell.from(""), Cell.from(""), Cell.from(""),
+                    Cell.from(""), Cell.from(""), Cell.from(""), 
Cell.from("")));
+        }
+
+        Table table = Table.builder()
+                .rows(rows)
+                .header(Row.from(
+                        Cell.from(Span.styled(sortLabel("NAME", "name"), 
sortStyle("name"))),
+                        Cell.from(Span.styled(sortLabel("POOL", "pool"), 
sortStyle("pool"))),
+                        rightCell(sortLabel("ACTIVE", "active"), 8, 
sortStyle("active")),
+                        rightCell(sortLabel("IDLE", "idle"), 8, 
sortStyle("idle")),
+                        rightCell(sortLabel("TOTAL", "total"), 8, 
sortStyle("total")),
+                        rightCell(sortLabel("MAX", "max"), 8, 
sortStyle("max")),
+                        rightCell(sortLabel("WAITING", "waiting"), 8, 
sortStyle("waiting")),
+                        Cell.from(Span.styled("TYPE", Style.EMPTY.bold()))))
+                .widths(
+                        Constraint.length(22),
+                        Constraint.length(16),
+                        Constraint.length(8),
+                        Constraint.length(8),
+                        Constraint.length(8),
+                        Constraint.length(8),
+                        Constraint.length(8),
+                        Constraint.fill())
+                .block(Block.builder().borderType(BorderType.ROUNDED)
+                        .title(" DataSource sort:" + sort + " ").build())
+                .build();
+
+        frame.renderStatefulWidget(table, area, tableState);
+    }
+
+    @Override
+    public void renderFooter(List<Span> spans) {
+        hint(spans, "Esc", "back");
+        hint(spans, "s", "sort");
+    }
+
+    private String sortLabel(String label, String column) {
+        return MonitorContext.sortLabel(label, column, sort, sortReversed);
+    }
+
+    private Style sortStyle(String column) {
+        return MonitorContext.sortStyle(column, sort);
+    }
+
+    private int sortDataSource(DataSourceInfo a, DataSourceInfo b) {
+        int result = switch (sort) {
+            case "pool" -> {
+                String pa = a.poolType != null ? a.poolType : "";
+                String pb = b.poolType != null ? b.poolType : "";
+                yield pa.compareToIgnoreCase(pb);
+            }
+            case "active" -> Integer.compare(b.active, a.active);
+            case "idle" -> Integer.compare(b.idle, a.idle);
+            case "total" -> Integer.compare(b.total, a.total);
+            case "max" -> Integer.compare(b.maxPoolSize, a.maxPoolSize);
+            case "waiting" -> Integer.compare(b.waiting, a.waiting);
+            default -> { // "name"
+                String na = a.name != null ? a.name : "";
+                String nb = b.name != null ? b.name : "";
+                yield na.compareToIgnoreCase(nb);
+            }
+        };
+        return sortReversed ? -result : result;
+    }
+
+    private static String shortType(String type) {
+        if (type == null) {
+            return "";
+        }
+        int dot = type.lastIndexOf('.');
+        return dot >= 0 ? type.substring(dot + 1) : type;
+    }
+
+    @Override
+    public SelectionContext getSelectionContext() {
+        IntegrationInfo info = ctx.findSelectedIntegration();
+        if (info == null || info.dataSources.isEmpty()) {
+            return null;
+        }
+        List<DataSourceInfo> sorted = new ArrayList<>(info.dataSources);
+        sorted.sort(this::sortDataSource);
+        List<String> items = sorted.stream().map(d -> d.name != null ? d.name 
: "").toList();
+        Integer sel = tableState.selected();
+        return new SelectionContext("table", items, sel != null ? sel : -1, 
items.size(), "DataSource");
+    }
+
+    @Override
+    public String getHelpText() {
+        return """
+                # DataSource
+
+                Displays connection pool metrics for all 
`javax.sql.DataSource` beans
+                registered in the Camel registry. Supports **HikariCP** 
(Spring Boot default)
+                and **Agroal** (Quarkus default) connection pools.
+
+                ## Table Columns
+
+                - **NAME** — The bean name of the DataSource in the registry
+                - **POOL** — The pool name (HikariCP pool name) or pool type 
(HikariCP / Agroal / Unknown)
+                - **ACTIVE** — Number of connections currently in use. Shown 
in red when equal to MAX (pool exhausted)
+                - **IDLE** — Number of idle connections available in the pool
+                - **TOTAL** — Total connections (active + idle) currently 
managed by the pool
+                - **MAX** — Maximum pool size configured. When ACTIVE reaches 
MAX, new connection requests will wait
+                - **WAITING** — Number of threads waiting for a connection. 
Shown in yellow when > 0
+                - **TYPE** — The Java class of the DataSource implementation
+
+                ## Pool Exhaustion
+
+                When **ACTIVE = MAX**, the pool is exhausted — no idle 
connections remain.
+                New requests must wait for a connection to be released. The 
ACTIVE column
+                turns red to signal this condition. If WAITING also increases, 
the application
+                may experience timeouts.
+
+                ## Connection Pools
+
+                ### HikariCP
+                The default connection pool for Spring Boot. Known for its 
speed and small
+                footprint. Default max pool size is 10.
+
+                ### Agroal
+                The default connection pool for Quarkus. Provides leak 
detection and
+                connection validation. Shows additional metrics like max-used 
and leak count.
+
+                ## Keys
+
+                - `Up/Down` — select datasource
+                - `s` — cycle sort column
+                - `S` — reverse sort order
+                """;
+    }
+
+    @Override
+    public JsonObject getTableDataAsJson() {
+        IntegrationInfo info = ctx.findSelectedIntegration();
+        if (info == null) {
+            return null;
+        }
+        JsonObject result = new JsonObject();
+        result.put("tab", "DataSource");
+        JsonArray rows = new JsonArray();
+        for (DataSourceInfo di : info.dataSources) {
+            JsonObject row = new JsonObject();
+            row.put("name", di.name);
+            row.put("type", di.type);
+            row.put("poolType", di.poolType);
+            if (di.poolName != null) {
+                row.put("poolName", di.poolName);
+            }
+            row.put("active", di.active);
+            row.put("idle", di.idle);
+            row.put("total", di.total);
+            row.put("maxPoolSize", di.maxPoolSize);
+            row.put("waiting", di.waiting);
+            rows.add(row);
+        }
+        result.put("rows", rows);
+        result.put("totalRows", info.dataSources.size());
+        Integer sel = tableState.selected();
+        result.put("selectedIndex", sel != null ? sel : -1);
+        return result;
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/IntegrationInfo.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/IntegrationInfo.java
index e51122f1e4c2..a4009ab251c4 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/IntegrationInfo.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/IntegrationInfo.java
@@ -82,6 +82,7 @@ class IntegrationInfo {
     final List<MicrometerMeterInfo> meters = new ArrayList<>();
     final List<HttpEndpointInfo> httpEndpoints = new ArrayList<>();
     final List<ConfigurationTab.ConfigProperty> configProperties = new 
ArrayList<>();
+    final List<DataSourceInfo> dataSources = new ArrayList<>();
     String httpServer;
     String readmeFiles;
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
index 805458e3ad30..70e454488d9c 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/OverviewTab.java
@@ -160,13 +160,15 @@ class OverviewTab implements MonitorTab {
 
         boolean hasSparkline = chartMode != CHART_OFF && 
!throughputHistory.isEmpty() && !ctx.isInfraSelected()
                 && ctx.shellPercent < 50;
-        boolean showInfoPanel = ctx.isInfraSelected() && 
ctx.findSelectedInfra() != null && !hasSparkline;
+        InfraInfo infraSel = ctx.isInfraSelected() ? ctx.findSelectedInfra() : 
null;
+        boolean showInfoPanel = infraSel != null && !hasSparkline;
         List<Constraint> constraints = new ArrayList<>();
         constraints.add(Constraint.fill());
         if (hasSparkline) {
             constraints.add(Constraint.length(14));
         } else if (showInfoPanel) {
-            constraints.add(Constraint.length(10));
+            int panelH = countInfraLines(infraSel) + 2;
+            constraints.add(Constraint.length(Math.min(panelH, area.height() / 
2)));
         }
         List<Rect> chunks = Layout.vertical()
                 .constraints(constraints)
@@ -575,6 +577,18 @@ class OverviewTab implements MonitorTab {
         frame.renderWidget(Paragraph.builder().text(Text.from(lines)).build(), 
inner);
     }
 
+    private static int countInfraLines(InfraInfo infra) {
+        int count = 2; // "Service: ..." + blank line
+        for (Map.Entry<String, Object> e : infra.properties.entrySet()) {
+            if (e.getValue() instanceof Map<?, ?> map) {
+                count += 1 + map.size();
+            } else {
+                count++;
+            }
+        }
+        return count;
+    }
+
     private void renderInfraInfoPanel(Frame frame, Rect area, InfraInfo infra) 
{
         Block infoBlock = 
Block.builder().borderType(BorderType.ROUNDED).build();
         frame.renderWidget(infoBlock, area);
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
index 930a966f75ae..515c080554fb 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
@@ -497,6 +497,31 @@ final class StatusParser {
             }
         }
 
+        // Parse dataSources
+        JsonObject dsObj = (JsonObject) root.get("dataSources");
+        if (dsObj != null) {
+            JsonArray dsList = (JsonArray) dsObj.get("dataSources");
+            if (dsList != null) {
+                for (Object d : dsList) {
+                    JsonObject dj = (JsonObject) d;
+                    DataSourceInfo di = new DataSourceInfo();
+                    di.name = dj.getString("name");
+                    di.type = dj.getString("type");
+                    di.poolType = dj.getString("poolType");
+                    di.poolName = dj.getString("poolName");
+                    di.active = dj.getIntegerOrDefault("active", 0);
+                    di.idle = dj.getIntegerOrDefault("idle", 0);
+                    di.total = dj.getIntegerOrDefault("total", 0);
+                    di.waiting = dj.getIntegerOrDefault("waiting", 0);
+                    di.maxPoolSize = dj.getIntegerOrDefault("maxPoolSize", 0);
+                    di.maxUsed = dj.getIntegerOrDefault("maxUsed", 0);
+                    di.leakDetection = dj.getIntegerOrDefault("leakDetection", 
0);
+                    di.created = dj.getIntegerOrDefault("created", 0);
+                    info.dataSources.add(di);
+                }
+            }
+        }
+
         return info;
     }
 

Reply via email to