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 d1ce35d4b186 CAMEL-23862: Add SQL Trace dev console, TUI tab, and CLI 
command (#24339)
d1ce35d4b186 is described below

commit d1ce35d4b186d51ed4237b019985082d1baebf78
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Jul 1 06:56:34 2026 +0200

    CAMEL-23862: Add SQL Trace dev console, TUI tab, and CLI command (#24339)
    
    * CAMEL-23862: Add SQL Trace dev console, TUI tab, and CLI command
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
    
    * CAMEL-23862: SQL Trace - prefer CamelSqlQuery header and URL-decode query 
from URI
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
    
    * chore(tui): speed up process discovery by scanning status files instead 
of all OS processes
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
    
    * CAMEL-23862: SQL Trace - master/detail panel, borders, and resource file 
resolution
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
    
    * CAMEL-23862: SQL Trace - fix cursor navigation in master table
    
    Remove identity tracking that was overriding user navigation on every
    render cycle. Cursor now stays at its index position while new entries
    scroll in at the top.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
    
    * CAMEL-23862: SQL Trace - stable cursor and Home/End master navigation
    
    Restore identity tracking so cursor follows the same row when new data
    shifts indices. Navigation clears the key to avoid fighting with user
    input. Home/End now jump to first/last row in the master table instead
    of scrolling the detail panel.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
    
    * CAMEL-23862: SQL Trace - rename CAT column header to TYPE
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
    
    * CAMEL-23862: SQL Trace - add edit SQL shortcut and rename sort column
    
    Add 'e' key to copy selected SQL and open it in the SQL Query tab for
    editing. Rename sort column from category to type to match the TYPE
    column header.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
    
    * CAMEL-23862: SQL Trace - add processorId tracking, MCP tab descriptions, 
and camel ask SQL tools
    
    Co-Authored-By: Claude <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
    
    * CAMEL-23862: Fix test failures - update CAT->TYPE in render test, fix 
LoadAvg locale
    
    Co-Authored-By: Claude <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
    
    ---------
    
    Signed-off-by: Claus Ibsen <[email protected]>
    Co-authored-by: Claude Opus 4.6 <[email protected]>
---
 .../apache/camel/catalog/dev-consoles.properties   |   1 +
 .../camel/catalog/dev-consoles/sql-trace.json      |  15 +
 .../impl/console/SqlTraceDevConsoleConfigurer.java |  67 +++
 .../org/apache/camel/dev-console/sql-trace.json    |  15 +
 ...rg.apache.camel.impl.console.SqlTraceDevConsole |   2 +
 .../org/apache/camel/dev-console/sql-trace         |   2 +
 .../org/apache/camel/dev-consoles.properties       |   2 +-
 .../camel/impl/console/SqlTraceDevConsole.java     | 318 ++++++++++++
 .../jbang-commands/camel-jbang-get-sql-trace.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 +-
 .../camel/dsl/jbang/core/commands/AskTools.java    |  40 ++
 .../dsl/jbang/core/commands/CamelJBangMain.java    |   1 +
 .../jbang/core/commands/process/ListSqlTrace.java  | 211 ++++++++
 .../core/commands/tui/DataRefreshService.java      |  16 +
 .../jbang/core/commands/tui/IntegrationInfo.java   |   6 +
 .../camel/dsl/jbang/core/commands/tui/LoadAvg.java |   4 +-
 .../dsl/jbang/core/commands/tui/McpFacade.java     |  37 +-
 .../dsl/jbang/core/commands/tui/PopupManager.java  |  16 +-
 .../tui/{LoadAvg.java => SqlTraceInfo.java}        |  31 +-
 .../dsl/jbang/core/commands/tui/SqlTraceTab.java   | 531 +++++++++++++++++++++
 .../dsl/jbang/core/commands/tui/StatusParser.java  |  33 ++
 .../dsl/jbang/core/commands/tui/TabRegistry.java   |  17 +-
 .../dsl/jbang/core/commands/tui/TuiHelper.java     |  81 +++-
 .../dsl/jbang/core/commands/tui/TuiMcpServer.java  |  12 +-
 .../core/commands/tui/SqlTraceTabRenderTest.java   | 196 ++++++++
 27 files changed, 1637 insertions(+), 55 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 217aa1876741..aff1673d5525 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
@@ -57,6 +57,7 @@ sftp
 simple-language
 source
 sql-query
+sql-trace
 startup-recorder
 stub
 system-properties
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/sql-trace.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/sql-trace.json
new file mode 100644
index 000000000000..55335065a456
--- /dev/null
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/sql-trace.json
@@ -0,0 +1,15 @@
+{
+  "console": {
+    "kind": "console",
+    "group": "camel",
+    "name": "sql-trace",
+    "title": "SQL Trace",
+    "description": "Trace SQL query executions",
+    "deprecated": false,
+    "javaType": "org.apache.camel.impl.console.SqlTraceDevConsole",
+    "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/SqlTraceDevConsoleConfigurer.java
 
b/core/camel-console/src/generated/java/org/apache/camel/impl/console/SqlTraceDevConsoleConfigurer.java
new file mode 100644
index 000000000000..0948021b78e5
--- /dev/null
+++ 
b/core/camel-console/src/generated/java/org/apache/camel/impl/console/SqlTraceDevConsoleConfigurer.java
@@ -0,0 +1,67 @@
+/* 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.SqlTraceDevConsole;
+
+/**
+ * Generated by camel build tools - do NOT edit this file!
+ */
+@Generated("org.apache.camel.maven.packaging.GenerateConfigurerMojo")
+@SuppressWarnings("unchecked")
+public class SqlTraceDevConsoleConfigurer 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);
+        map.put("Capacity", int.class);
+        ALL_OPTIONS = map;
+    }
+
+    @Override
+    public boolean configure(CamelContext camelContext, Object obj, String 
name, Object value, boolean ignoreCase) {
+        org.apache.camel.impl.console.SqlTraceDevConsole target = 
(org.apache.camel.impl.console.SqlTraceDevConsole) obj;
+        switch (ignoreCase ? name.toLowerCase() : name) {
+        case "camelcontext":
+        case "camelContext": target.setCamelContext(property(camelContext, 
org.apache.camel.CamelContext.class, value)); return true;
+        case "capacity": target.setCapacity(property(camelContext, int.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;
+        case "capacity": return int.class;
+        default: return null;
+        }
+    }
+
+    @Override
+    public Object getOptionValue(Object obj, String name, boolean ignoreCase) {
+        org.apache.camel.impl.console.SqlTraceDevConsole target = 
(org.apache.camel.impl.console.SqlTraceDevConsole) obj;
+        switch (ignoreCase ? name.toLowerCase() : name) {
+        case "camelcontext":
+        case "camelContext": return target.getCamelContext();
+        case "capacity": return target.getCapacity();
+        default: return null;
+        }
+    }
+}
+
diff --git 
a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/sql-trace.json
 
b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/sql-trace.json
new file mode 100644
index 000000000000..55335065a456
--- /dev/null
+++ 
b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/sql-trace.json
@@ -0,0 +1,15 @@
+{
+  "console": {
+    "kind": "console",
+    "group": "camel",
+    "name": "sql-trace",
+    "title": "SQL Trace",
+    "description": "Trace SQL query executions",
+    "deprecated": false,
+    "javaType": "org.apache.camel.impl.console.SqlTraceDevConsole",
+    "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.SqlTraceDevConsole
 
b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.impl.console.SqlTraceDevConsole
new file mode 100644
index 000000000000..41852b612ce6
--- /dev/null
+++ 
b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/configurer/org.apache.camel.impl.console.SqlTraceDevConsole
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.impl.console.SqlTraceDevConsoleConfigurer
diff --git 
a/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-console/sql-trace
 
b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-console/sql-trace
new file mode 100644
index 000000000000..4d0f5e4e974b
--- /dev/null
+++ 
b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-console/sql-trace
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.impl.console.SqlTraceDevConsole
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 40b26b866b52..fe5d76fe5112 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 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 sql-query 
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 sql-query 
sql-trace 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/SqlTraceDevConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/SqlTraceDevConsole.java
new file mode 100644
index 000000000000..95c58fc43598
--- /dev/null
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/SqlTraceDevConsole.java
@@ -0,0 +1,318 @@
+/*
+ * 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.io.InputStream;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.NonManagedService;
+import org.apache.camel.spi.CamelEvent;
+import org.apache.camel.spi.Configurer;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.annotations.DevConsole;
+import org.apache.camel.support.EventNotifierSupport;
+import org.apache.camel.support.ResourceHelper;
+import org.apache.camel.support.console.AbstractDevConsole;
+import org.apache.camel.util.StringHelper;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+
+@DevConsole(name = "sql-trace", displayName = "SQL Trace", description = 
"Trace SQL query executions")
+@Configurer(extended = true)
+public class SqlTraceDevConsole extends AbstractDevConsole {
+
+    @Metadata(defaultValue = "200",
+              description = "Maximum capacity of traced SQL statements 
(capacity must be between 25 and 1000)")
+    private int capacity = 200;
+
+    private JsonObject[] events;
+    private final AtomicInteger pos = new AtomicInteger();
+    private final ConsoleEventNotifier listener = new ConsoleEventNotifier();
+
+    public SqlTraceDevConsole() {
+        super("camel", "sql-trace", "SQL Trace", "Trace SQL query executions");
+    }
+
+    public int getCapacity() {
+        return capacity;
+    }
+
+    public void setCapacity(int capacity) {
+        this.capacity = capacity;
+    }
+
+    @Override
+    protected void doInit() throws Exception {
+        if (capacity > 1000 || capacity < 25) {
+            throw new IllegalArgumentException("Capacity must be between 25 
and 1000");
+        }
+        this.events = new JsonObject[capacity];
+    }
+
+    @Override
+    protected void doStart() throws Exception {
+        getCamelContext().getManagementStrategy().addEventNotifier(listener);
+    }
+
+    @Override
+    protected void doStop() throws Exception {
+        
getCamelContext().getManagementStrategy().removeEventNotifier(listener);
+    }
+
+    @Override
+    protected String doCallText(Map<String, Object> options) {
+        StringBuilder sb = new StringBuilder();
+
+        List<JsonObject> list = collectEvents();
+        for (JsonObject jo : list) {
+            sb.append(String.format("    %s %s %s (%d ms) route:%s%n",
+                    jo.getString("category"),
+                    jo.getString("query"),
+                    jo.getBooleanOrDefault("failed", false) ? "FAILED" : "OK",
+                    jo.getLongOrDefault("duration", 0),
+                    jo.getString("routeId")));
+        }
+        if (!list.isEmpty()) {
+            sb.insert(0, String.format("Last %d SQL Statements:%n", 
list.size()));
+        }
+
+        return sb.toString();
+    }
+
+    @Override
+    protected JsonObject doCallJson(Map<String, Object> options) {
+        JsonObject root = new JsonObject();
+
+        List<JsonObject> list = collectEvents();
+        if (!list.isEmpty()) {
+            JsonArray arr = new JsonArray();
+            arr.addAll(list);
+            root.put("statements", arr);
+
+            // compute summary
+            JsonObject summary = new JsonObject();
+            long total = list.size();
+            long totalTime = 0;
+            long slowest = 0;
+            long slowCount = 0;
+            long failedCount = 0;
+            long selectCount = 0;
+            long insertCount = 0;
+            long updateCount = 0;
+            long deleteCount = 0;
+
+            for (JsonObject jo : list) {
+                long duration = jo.getLongOrDefault("duration", 0);
+                totalTime += duration;
+                if (duration > slowest) {
+                    slowest = duration;
+                }
+                if (duration >= 100) {
+                    slowCount++;
+                }
+                if (jo.getBooleanOrDefault("failed", false)) {
+                    failedCount++;
+                }
+                String cat = jo.getStringOrDefault("category", "");
+                switch (cat) {
+                    case "SELECT":
+                        selectCount++;
+                        break;
+                    case "INSERT":
+                        insertCount++;
+                        break;
+                    case "UPDATE":
+                        updateCount++;
+                        break;
+                    case "DELETE":
+                        deleteCount++;
+                        break;
+                    default:
+                        break;
+                }
+            }
+
+            summary.put("totalQueries", total);
+            summary.put("avgTime", total > 0 ? totalTime / total : 0);
+            summary.put("slowestTime", slowest);
+            summary.put("slowCount", slowCount);
+            summary.put("failedCount", failedCount);
+            summary.put("selectCount", selectCount);
+            summary.put("insertCount", insertCount);
+            summary.put("updateCount", updateCount);
+            summary.put("deleteCount", deleteCount);
+            root.put("summary", summary);
+        }
+
+        return root;
+    }
+
+    private List<JsonObject> collectEvents() {
+        List<JsonObject> list = new ArrayList<>();
+        int cursor = pos.get();
+        // cursor points to the NEXT write slot, so walk backward from cursor-1
+        for (int i = 0; i < capacity; i++) {
+            cursor = (cursor - 1 + capacity) % capacity;
+            JsonObject event = events[cursor];
+            if (event != null) {
+                list.add(event);
+            }
+        }
+        return list;
+    }
+
+    private static String extractQuery(String endpointUri) {
+        if (endpointUri.startsWith("sql:")) {
+            String query = StringHelper.after(endpointUri, "sql:");
+            if (query != null) {
+                // strip :// scheme separator if present
+                if (query.startsWith("//")) {
+                    query = query.substring(2);
+                }
+                // remove query parameters
+                int idx = query.indexOf('?');
+                if (idx > 0) {
+                    query = query.substring(0, idx);
+                }
+                // URI path is URL-encoded, decode it
+                query = URLDecoder.decode(query, StandardCharsets.UTF_8);
+                return query;
+            }
+        } else if (endpointUri.startsWith("jdbc:")) {
+            // for jdbc component, the URI path is the datasource name, not 
the SQL query
+            return null;
+        }
+        return null;
+    }
+
+    private String resolveResource(String uri) {
+        try (InputStream is = 
ResourceHelper.resolveResourceAsInputStream(getCamelContext(), uri)) {
+            if (is != null) {
+                return new String(is.readAllBytes(), 
StandardCharsets.UTF_8).strip();
+            }
+        } catch (Exception e) {
+            // ignore
+        }
+        return "resource:" + uri;
+    }
+
+    private static String detectCategory(String query) {
+        if (query != null && !query.isEmpty()) {
+            String upper = query.stripLeading().toUpperCase(Locale.ENGLISH);
+            if (upper.startsWith("SELECT")) {
+                return "SELECT";
+            } else if (upper.startsWith("INSERT")) {
+                return "INSERT";
+            } else if (upper.startsWith("UPDATE")) {
+                return "UPDATE";
+            } else if (upper.startsWith("DELETE")) {
+                return "DELETE";
+            } else if (upper.startsWith("CALL") || upper.startsWith("EXEC")) {
+                return "CALL";
+            }
+        }
+        return "OTHER";
+    }
+
+    private class ConsoleEventNotifier extends EventNotifierSupport implements 
NonManagedService {
+
+        ConsoleEventNotifier() {
+            setIgnoreCamelContextEvents(true);
+            setIgnoreRouteEvents(true);
+            setIgnoreServiceEvents(true);
+            setIgnoreExchangeCreatedEvent(true);
+            setIgnoreExchangeCompletedEvent(true);
+            setIgnoreExchangeFailedEvents(true);
+            setIgnoreExchangeRedeliveryEvents(true);
+            setIgnoreExchangeSendingEvents(true);
+            setIgnoreStepEvents(true);
+        }
+
+        @Override
+        public void notify(CamelEvent event) throws Exception {
+            if (event instanceof CamelEvent.ExchangeSentEvent ese) {
+                String uri = ese.getEndpoint().getEndpointUri();
+                if (uri.startsWith("sql:") || uri.startsWith("jdbc:")) {
+                    Exchange exchange = ese.getExchange();
+
+                    // prefer the CamelSqlQuery header (runtime override) over 
the URI
+                    String query = null;
+                    Object headerQuery = 
exchange.getMessage().getHeader("CamelSqlQuery");
+                    if (headerQuery != null) {
+                        query = headerQuery.toString();
+                    }
+                    if (query == null) {
+                        query = extractQuery(uri);
+                    }
+                    // resolve resource: references to actual SQL content
+                    if (query != null && query.startsWith("resource:")) {
+                        query = 
resolveResource(query.substring("resource:".length()));
+                    }
+
+                    JsonObject jo = new JsonObject();
+                    jo.put("timestamp", event.getTimestamp());
+                    jo.put("exchangeId", exchange.getExchangeId());
+                    jo.put("routeId", exchange.getFromRouteId());
+                    String nodeId = 
exchange.getExchangeExtension().getHistoryNodeId();
+                    if (nodeId != null) {
+                        jo.put("nodeId", nodeId);
+                    }
+                    String nodeSource = 
exchange.getExchangeExtension().getHistoryNodeSource();
+                    if (nodeSource != null) {
+                        jo.put("location", nodeSource);
+                    }
+                    jo.put("endpoint", uri);
+                    if (query != null) {
+                        jo.put("query", query);
+                        jo.put("category", detectCategory(query));
+                    } else {
+                        jo.put("query", uri);
+                        jo.put("category", "OTHER");
+                    }
+                    jo.put("duration", ese.getTimeTaken());
+                    jo.put("failed", exchange.isFailed());
+
+                    // row/update counts from sql and jdbc component headers
+                    Object rc = 
exchange.getMessage().getHeader("CamelSqlRowCount");
+                    if (rc == null) {
+                        rc = 
exchange.getMessage().getHeader("CamelJdbcRowCount");
+                    }
+                    if (rc instanceof Number) {
+                        jo.put("rowCount", ((Number) rc).intValue());
+                    }
+                    Object uc = 
exchange.getMessage().getHeader("CamelSqlUpdateCount");
+                    if (uc == null) {
+                        uc = 
exchange.getMessage().getHeader("CamelJdbcUpdateCount");
+                    }
+                    if (uc instanceof Number) {
+                        jo.put("updateCount", ((Number) uc).intValue());
+                    }
+
+                    int p = pos.getAndUpdate(operand -> ++operand % capacity);
+                    events[p] = jo;
+                }
+            }
+        }
+    }
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get-sql-trace.adoc
 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get-sql-trace.adoc
new file mode 100644
index 000000000000..65255889b756
--- /dev/null
+++ 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-get-sql-trace.adoc
@@ -0,0 +1,28 @@
+
+// AUTO-GENERATED by camel-package-maven-plugin - DO NOT EDIT THIS FILE
+= camel get sql-trace
+
+Get SQL query trace data
+
+
+== Usage
+
+[source,bash]
+----
+camel get sql-trace [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 8d2eb511b28e..5365803b1101 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
@@ -45,6 +45,7 @@ camel get [options]
 | xref:jbang-commands/camel-jbang-get-route-controller.adoc[route-controller] 
| List status of route controller
 | xref:jbang-commands/camel-jbang-get-service.adoc[service] | Get services of 
Camel integrations
 | xref:jbang-commands/camel-jbang-get-source.adoc[source] | Display Camel 
route source code
+| xref:jbang-commands/camel-jbang-get-sql-trace.adoc[sql-trace] | Get SQL 
query trace data
 | xref:jbang-commands/camel-jbang-get-startup-recorder.adoc[startup-recorder] 
| Display startup recording
 | xref:jbang-commands/camel-jbang-get-transformer.adoc[transformer] | Get list 
of data type transformers
 | xref:jbang-commands/camel-jbang-get-variable.adoc[variable] | List variables 
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 fe943b169bb7..d8d562fb77a9 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
@@ -1504,6 +1504,13 @@ public class LocalCliConnector extends ServiceSupport 
implements CliConnector, C
                         root.put("dataSources", json);
                     }
                 }
+                DevConsole dc28 = dcr.resolveById("sql-trace");
+                if (dc28 != null) {
+                    JsonObject json = (JsonObject) 
dc28.call(DevConsole.MediaType.JSON);
+                    if (json != null && !json.isEmpty()) {
+                        root.put("sqlTrace", 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 6ee1954980ad..93ef87cda9f2 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/AskTools.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/AskTools.java
index 05a89ddddbe4..aa4097c77503 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/AskTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/AskTools.java
@@ -182,6 +182,24 @@ public class AskTools {
                 objectParams(Map.of(
                         "endpoint", stringProp("Endpoint URI to browse (e.g., 
seda:queue)"),
                         "limit", stringProp("Maximum number of messages to 
return (default: 50)")))));
+        tools.add(new LlmClient.ToolDef(
+                "get_sql_trace",
+                "Get traced SQL query executions flowing through camel-sql and 
camel-jdbc components. "
+                                 + "Returns per-query timing, row counts, 
category (SELECT/INSERT/UPDATE/DELETE), "
+                                 + "route ID, and failure status. Includes 
summary statistics: total queries, "
+                                 + "average time, slowest time, slow count 
(>=100ms), and failed count. "
+                                 + "Use to identify slow queries, fastest 
queries, most frequent queries, "
+                                 + "and failed SQL executions.",
+                emptyParams()));
+        tools.add(new LlmClient.ToolDef(
+                "execute_sql",
+                "Execute a SQL query against a DataSource in the running Camel 
application. "
+                               + "Returns structured JSON with columns, rows, 
and metadata for SELECT queries, "
+                               + "or an update count for 
INSERT/UPDATE/DELETE.",
+                objectParams(Map.of(
+                        "query", stringProp("The SQL query to execute"),
+                        "datasource", stringProp("Name of the DataSource bean 
(auto-detected if only one exists)"),
+                        "maxRows", stringProp("Maximum number of rows to 
return (default: 100)")))));
         tools.add(new LlmClient.ToolDef(
                 "get_thread_dump",
                 "Get a JVM thread dump showing thread names, states, and stack 
traces.",
@@ -325,6 +343,9 @@ public class AskTools {
                 case "send_message" -> targetPid < 0 ? NO_PROCESS : 
executeSendMessage(args);
                 case "eval_expression" -> targetPid < 0 ? NO_PROCESS : 
executeEvalExpression(args);
                 case "browse_endpoint" -> targetPid < 0 ? NO_PROCESS : 
executeBrowseEndpoint(args);
+                case "get_sql_trace" ->
+                    targetPid < 0 ? NO_PROCESS : 
RuntimeHelper.readStatusSection(targetPid, "sqlTrace");
+                case "execute_sql" -> targetPid < 0 ? NO_PROCESS : 
executeSQL(args);
                 case "get_thread_dump" ->
                     targetPid < 0 ? NO_PROCESS : 
RuntimeHelper.executeAction(targetPid, "thread-dump", null);
                 case "stop_route" -> targetPid < 0 ? NO_PROCESS : 
executeRouteCommand(args, "stop");
@@ -544,6 +565,25 @@ public class AskTools {
         });
     }
 
+    private String executeSQL(JsonObject args) {
+        String sql = args.getString("query");
+        if (sql == null || sql.isBlank()) {
+            return "Error: 'query' parameter is required";
+        }
+        String datasource = args.getString("datasource");
+        int maxRows = 100;
+        String maxRowsStr = args.getString("maxRows");
+        if (maxRowsStr != null) {
+            try {
+                maxRows = Integer.parseInt(maxRowsStr);
+            } catch (NumberFormatException e) {
+                // use default
+            }
+        }
+        JsonObject result = RuntimeHelper.executeSqlQuery(targetPid, sql, 
datasource, maxRows, 30);
+        return Jsoner.serialize(result);
+    }
+
     // ---- Catalog tools ----
 
     private String executeCatalogComponents(JsonObject args) {
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 da28f9ad87bb..86f19a1abd67 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
@@ -180,6 +180,7 @@ public class CamelJBangMain implements Callable<Integer> {
                         .addSubcommand("route-controller", new CommandLine(new 
RouteControllerAction(this)))
                         .addSubcommand("service", new CommandLine(new 
ListService(this)))
                         .addSubcommand("source", new CommandLine(new 
CamelSourceAction(this)))
+                        .addSubcommand("sql-trace", new CommandLine(new 
ListSqlTrace(this)))
                         .addSubcommand("startup-recorder", new CommandLine(new 
CamelStartupRecorderAction(this)))
                         .addSubcommand("transformer", new CommandLine(new 
ListTransformer(this)))
                         .addSubcommand("variable", new CommandLine(new 
ListVariable(this)))
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListSqlTrace.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListSqlTrace.java
new file mode 100644
index 000000000000..275fe015b6b2
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListSqlTrace.java
@@ -0,0 +1,211 @@
+/*
+ * 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.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+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 = "sql-trace", description = "Get SQL query trace data", 
sortOptions = false,
+         showDefaultValues = true,
+         footer = {
+                 "%nExamples:",
+                 "  camel get sql-trace",
+                 "  camel get sql-trace --watch" })
+public class ListSqlTrace extends ProcessWatchCommand {
+
+    private static final DateTimeFormatter TIME_FMT = 
DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
+
+    @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 ListSqlTrace(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 sqlTrace = (JsonObject) 
root.get("sqlTrace");
+                        if (context != null && sqlTrace != null) {
+                            JsonArray array = (JsonArray) 
sqlTrace.get("statements");
+                            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.timestamp = 
o.getLongOrDefault("timestamp", 0);
+                                    row.exchangeId = o.getString("exchangeId");
+                                    row.routeId = o.getString("routeId");
+                                    row.query = o.getString("query");
+                                    row.category = o.getString("category");
+                                    row.endpoint = o.getString("endpoint");
+                                    row.duration = 
o.getLongOrDefault("duration", 0);
+                                    row.rowCount = 
o.getIntegerOrDefault("rowCount", 0);
+                                    row.updateCount = 
o.getIntegerOrDefault("updateCount", 0);
+                                    row.failed = 
o.getBooleanOrDefault("failed", false);
+                                    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("timestamp", r.timestamp);
+                jo.put("exchangeId", r.exchangeId);
+                jo.put("routeId", r.routeId);
+                jo.put("query", r.query);
+                jo.put("category", r.category);
+                jo.put("endpoint", r.endpoint);
+                jo.put("duration", r.duration);
+                jo.put("rowCount", r.rowCount);
+                jo.put("updateCount", r.updateCount);
+                jo.put("failed", r.failed);
+                return jo;
+            }).collect(Collectors.toList())));
+            return;
+        }
+        int tw = terminalWidth();
+        int sqlW = Math.max(20, Math.min(80, 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("TIME").headerAlign(HorizontalAlign.CENTER).with(this::formatTime),
+                new 
Column().header("CAT").dataAlign(HorizontalAlign.LEFT).with(r -> r.category != 
null ? r.category : ""),
+                new Column().header("SQL").dataAlign(HorizontalAlign.LEFT)
+                        .maxWidth(sqlW, OverflowBehaviour.ELLIPSIS_RIGHT)
+                        .with(r -> r.query != null ? r.query : ""),
+                new Column().header("ROUTE").dataAlign(HorizontalAlign.LEFT)
+                        .maxWidth(20, OverflowBehaviour.ELLIPSIS_RIGHT)
+                        .with(r -> r.routeId != null ? r.routeId : ""),
+                new 
Column().header("DURATION").headerAlign(HorizontalAlign.RIGHT).dataAlign(HorizontalAlign.RIGHT)
+                        .with(r -> r.duration + " ms"),
+                new 
Column().header("ROWS").headerAlign(HorizontalAlign.RIGHT).dataAlign(HorizontalAlign.RIGHT)
+                        .with(this::formatRows),
+                new Column().header("STATUS").dataAlign(HorizontalAlign.CENTER)
+                        .with(r -> r.failed ? "FAIL" : "OK"))));
+    }
+
+    private String formatTime(Row r) {
+        if (r.timestamp > 0) {
+            return LocalDateTime.ofInstant(Instant.ofEpochMilli(r.timestamp), 
ZoneId.systemDefault())
+                    .format(TIME_FMT);
+        }
+        return "";
+    }
+
+    private String formatRows(Row r) {
+        if (r.rowCount > 0) {
+            return Integer.toString(r.rowCount);
+        } else if (r.updateCount > 0) {
+            return Integer.toString(r.updateCount);
+        }
+        return "";
+    }
+
+    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;
+        long timestamp;
+        String exchangeId;
+        String routeId;
+        String query;
+        String category;
+        String endpoint;
+        long duration;
+        int rowCount;
+        int updateCount;
+        boolean failed;
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DataRefreshService.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DataRefreshService.java
index 2556eae8eef3..1120c041aa92 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DataRefreshService.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/DataRefreshService.java
@@ -50,6 +50,7 @@ import org.apache.camel.util.json.Jsoner;
 class DataRefreshService {
 
     private static final long VANISH_DURATION_MS = 6000;
+    private static final long LIVENESS_CHECK_INTERVAL_MS = 30_000;
     private static final int MAX_TRACES = 200;
 
     /**
@@ -81,6 +82,7 @@ class DataRefreshService {
     // Cached PID list -- full process scan throttled to every 2 seconds (1 
second in burst mode)
     private volatile List<Long> cachedPids = Collections.emptyList();
     private volatile long lastFullScanTime;
+    private volatile long lastLivenessCheckTime;
     private volatile long burstModeUntil;
     final Set<String> stoppingPids = ConcurrentHashMap.newKeySet();
 
@@ -266,9 +268,23 @@ class DataRefreshService {
             }
         }
         if (!fullScan && ctx.selectedPid != null) {
+            boolean checkLiveness = now - lastLivenessCheckTime >= 
LIVENESS_CHECK_INTERVAL_MS;
+            if (checkLiveness) {
+                lastLivenessCheckTime = now;
+            }
             List<IntegrationInfo> previous = data.get();
             for (IntegrationInfo prev : previous) {
                 if (!prev.vanishing && !ctx.selectedPid.equals(prev.pid)) {
+                    if (checkLiveness) {
+                        try {
+                            long pid = Long.parseLong(prev.pid);
+                            if 
(!ProcessHandle.of(pid).map(ProcessHandle::isAlive).orElse(false)) {
+                                continue;
+                            }
+                        } catch (NumberFormatException e) {
+                            // keep it
+                        }
+                    }
                     infos.add(prev);
                 }
             }
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 a4009ab251c4..2820a1956d93 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
@@ -83,6 +83,12 @@ class IntegrationInfo {
     final List<HttpEndpointInfo> httpEndpoints = new ArrayList<>();
     final List<ConfigurationTab.ConfigProperty> configProperties = new 
ArrayList<>();
     final List<DataSourceInfo> dataSources = new ArrayList<>();
+    final List<SqlTraceInfo> sqlTraceStatements = new ArrayList<>();
+    long sqlTraceTotal;
+    long sqlTraceAvgTime;
+    long sqlTraceSlowestTime;
+    long sqlTraceSlowCount;
+    long sqlTraceFailedCount;
     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/LoadAvg.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LoadAvg.java
index a9c2ccdc7aad..02687cb5e4a4 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LoadAvg.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LoadAvg.java
@@ -16,6 +16,8 @@
  */
 package org.apache.camel.dsl.jbang.core.commands.tui;
 
+import java.util.Locale;
+
 class LoadAvg {
     private static final double EXP_1 = Math.exp(-1 / 60.0);
     private static final double EXP_5 = Math.exp(-1 / (60.0 * 5.0));
@@ -32,6 +34,6 @@ class LoadAvg {
     }
 
     synchronized String format(String fmt) {
-        return Double.isNaN(load1) ? "-" : String.format(fmt, load1, load5, 
load15);
+        return Double.isNaN(load1) ? "-" : String.format(Locale.US, fmt, 
load1, load5, load15);
     }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
index 99efb93f2856..15a84d382290 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
@@ -23,6 +23,7 @@ import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Queue;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -89,10 +90,42 @@ class McpFacade {
 
     static final String[] MORE_TAB_NAMES = {
             "Beans", "Browse", "Circuit Breaker", "Classpath", "Configuration",
-            "Consumers", "DataSource", "Inflight", "Memory", "Metrics", "SQL 
Query", "Spans", "Process", "Startup",
-            "Threads"
+            "Consumers", "DataSource", "Inflight", "Memory", "Metrics", "SQL 
Query", "SQL Trace", "Spans", "Process",
+            "Startup", "Threads"
     };
 
+    static final Map<String, String> TAB_DESCRIPTIONS = Map.ofEntries(
+            Map.entry("Overview", "Running integrations with PID, uptime, and 
exchange statistics"),
+            Map.entry("Log", "Live application log with ANSI color support and 
filtering"),
+            Map.entry("Diagram", "Route topology diagram showing how routes 
connect to each other and external systems"),
+            Map.entry("Routes", "Route list with state, message counts, 
throughput, and failure statistics"),
+            Map.entry("Endpoints", "Registered endpoints with usage statistics 
(hits, direction)"),
+            Map.entry("HTTP", "HTTP endpoint probe — send requests and inspect 
responses"),
+            Map.entry("Health", "Health check status for readiness and 
liveness probes"),
+            Map.entry("Inspect", "Message history trace showing route path, 
headers, body, and timing"),
+            Map.entry("Errors", "Routing errors with exception details, stack 
traces, and exchange context"),
+            Map.entry("Beans", "Registered beans in the Camel registry"),
+            Map.entry("Browse", "Browse messages queued in browsable endpoints 
(e.g. SEDA)"),
+            Map.entry("Circuit Breaker", "Circuit breaker state and statistics 
(Resilience4j)"),
+            Map.entry("Classpath", "JVM classpath entries with filtering"),
+            Map.entry("Configuration", "Application configuration properties"),
+            Map.entry("Consumers", "Consumer statistics (polling and 
event-driven consumers)"),
+            Map.entry("DataSource", "JDBC DataSource pool statistics (active, 
idle, max connections)"),
+            Map.entry("Inflight", "Currently in-flight exchanges being 
processed"),
+            Map.entry("Memory", "JVM memory usage (heap/non-heap), GC stats, 
and thread counts"),
+            Map.entry("Metrics", "Micrometer metrics (counters, gauges, 
timers, distribution summaries)"),
+            Map.entry("SQL Query",
+                    "Execute SQL queries against DataSources in the running 
application and browse results"),
+            Map.entry("SQL Trace",
+                    "Trace SQL query executions through camel-sql and 
camel-jdbc components. "
+                                   + "Shows per-query timing, row counts, and 
failure status. "
+                                   + "Use to identify slow queries, fastest 
queries, most frequent queries, "
+                                   + "and failed SQL executions. Sortable by 
time, type, duration, and rows."),
+            Map.entry("Spans", "OpenTelemetry spans with trace/span IDs, 
timing, and attributes"),
+            Map.entry("Process", "OS process information (PID, CPU, memory, 
file descriptors)"),
+            Map.entry("Startup", "Startup step recorder showing initialization 
timing"),
+            Map.entry("Threads", "JVM thread dump with thread names, states, 
and stack traces"));
+
     private final MonitorContext ctx;
     private final AtomicReference<List<IntegrationInfo>> data;
     private final TabsState tabsState;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/PopupManager.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/PopupManager.java
index 6a615d808037..ba1c87e859ed 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/PopupManager.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/PopupManager.java
@@ -176,7 +176,7 @@ class PopupManager {
             return true;
         }
         if (ke.isDown()) {
-            morePopupState.selectNext(15);
+            morePopupState.selectNext(16);
             return true;
         }
         int shortcutSel = morePopupShortcut(ke);
@@ -239,7 +239,7 @@ class PopupManager {
 
     void renderMorePopup(Frame frame, Rect area) {
         int popupW = 22;
-        int popupH = 17;
+        int popupH = 18;
         // Position just below the "0 More▾" tab label
         int dividerW = CharWidth.of(" | ");
         int tabBarX = 0;
@@ -272,6 +272,7 @@ class PopupManager {
                 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"))),
                 ListItem.from(Line.from(Span.raw("  S"), Span.styled("Q", 
keyStyle), Span.raw("L Query"))),
+                ListItem.from(Line.from(Span.raw("  SQL T"), Span.styled("r", 
keyStyle), Span.raw("ace"))),
                 ListItem.from(Line.from(Span.raw("  "), Span.styled("O", 
keyStyle), Span.raw("Tel Spans"))),
                 ListItem.from(Line.from(Span.raw("  "), Span.styled("P", 
keyStyle), Span.raw("rocess"))),
                 ListItem.from(Line.from(Span.raw("  "), Span.styled("S", 
keyStyle), Span.raw("tartup"))),
@@ -408,18 +409,21 @@ class PopupManager {
         if (ke.isChar('q')) {
             return 10;
         }
-        if (ke.isChar('o')) {
+        if (ke.isChar('r')) {
             return 11;
         }
-        if (ke.isChar('p')) {
+        if (ke.isChar('o')) {
             return 12;
         }
-        if (ke.isChar('s')) {
+        if (ke.isChar('p')) {
             return 13;
         }
-        if (ke.isChar('t')) {
+        if (ke.isChar('s')) {
             return 14;
         }
+        if (ke.isChar('t')) {
+            return 15;
+        }
         return -1;
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LoadAvg.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SqlTraceInfo.java
similarity index 52%
copy from 
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LoadAvg.java
copy to 
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SqlTraceInfo.java
index a9c2ccdc7aad..572b9ca91812 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/LoadAvg.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SqlTraceInfo.java
@@ -16,22 +16,17 @@
  */
 package org.apache.camel.dsl.jbang.core.commands.tui;
 
-class LoadAvg {
-    private static final double EXP_1 = Math.exp(-1 / 60.0);
-    private static final double EXP_5 = Math.exp(-1 / (60.0 * 5.0));
-    private static final double EXP_15 = Math.exp(-1 / (60.0 * 15.0));
-
-    private double load1 = Double.NaN;
-    private double load5 = Double.NaN;
-    private double load15 = Double.NaN;
-
-    synchronized void update(double value) {
-        load1 = Double.isNaN(load1) ? value : value + EXP_1 * (load1 - value);
-        load5 = Double.isNaN(load5) ? value : value + EXP_5 * (load5 - value);
-        load15 = Double.isNaN(load15) ? value : value + EXP_15 * (load15 - 
value);
-    }
-
-    synchronized String format(String fmt) {
-        return Double.isNaN(load1) ? "-" : String.format(fmt, load1, load5, 
load15);
-    }
+class SqlTraceInfo {
+    String exchangeId;
+    String routeId;
+    String nodeId;
+    String location;
+    String query;
+    String category;
+    String endpoint;
+    long timestamp;
+    long duration;
+    int rowCount;
+    int updateCount;
+    boolean failed;
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SqlTraceTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SqlTraceTab.java
new file mode 100644
index 000000000000..6dd5b4d96da3
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SqlTraceTab.java
@@ -0,0 +1,531 @@
+/*
+ * 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.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+import dev.tamboui.layout.Constraint;
+import dev.tamboui.layout.Layout;
+import dev.tamboui.layout.Rect;
+import dev.tamboui.style.Color;
+import dev.tamboui.style.Style;
+import dev.tamboui.terminal.Frame;
+import dev.tamboui.text.Line;
+import dev.tamboui.text.Span;
+import dev.tamboui.text.Text;
+import dev.tamboui.tui.event.KeyEvent;
+import dev.tamboui.widgets.block.Block;
+import dev.tamboui.widgets.block.BorderType;
+import dev.tamboui.widgets.block.Borders;
+import dev.tamboui.widgets.paragraph.Paragraph;
+import dev.tamboui.widgets.scrollbar.ScrollbarState;
+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 SqlTraceTab implements MonitorTab {
+
+    private static final String[] SORT_COLUMNS = { "time", "type", "sql", 
"route", "duration", "rows" };
+    private static final DateTimeFormatter TIME_FMT = 
DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
+
+    private final MonitorContext ctx;
+    private final TableState tableState = new TableState();
+    private final ScrollbarState detailScrollState = new ScrollbarState();
+    private String sort = "time";
+    private int sortIndex;
+    private boolean sortReversed;
+    private int detailScroll;
+    private boolean wordWrap = true;
+    private String selectedKey;
+    private Consumer<String> editSqlAction;
+
+    SqlTraceTab(MonitorContext ctx) {
+        this.ctx = ctx;
+    }
+
+    void setEditSqlAction(Consumer<String> editSqlAction) {
+        this.editSqlAction = editSqlAction;
+    }
+
+    @Override
+    public void onTabSelected() {
+        IntegrationInfo info = ctx.findSelectedIntegration();
+        if (info != null && !info.sqlTraceStatements.isEmpty() && 
tableState.selected() == null) {
+            tableState.select(0);
+        }
+    }
+
+    @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;
+        }
+        if (ke.isCharIgnoreCase('w')) {
+            wordWrap = !wordWrap;
+            return true;
+        }
+        if (ke.isCharIgnoreCase('e') && editSqlAction != null) {
+            String sql = getSelectedQuery();
+            if (sql != null) {
+                editSqlAction.accept(sql);
+                return true;
+            }
+        }
+        if (ke.isHome()) {
+            detailScroll = 0;
+            selectedKey = null;
+            tableState.select(0);
+            return true;
+        }
+        if (ke.isEnd()) {
+            detailScroll = 0;
+            selectedKey = null;
+            IntegrationInfo ei = ctx.findSelectedIntegration();
+            if (ei != null && !ei.sqlTraceStatements.isEmpty()) {
+                tableState.select(ei.sqlTraceStatements.size() - 1);
+            }
+            return true;
+        }
+        if (ke.isPageUp()) {
+            detailScroll = Math.max(0, detailScroll - 5);
+            return true;
+        }
+        if (ke.isPageDown()) {
+            detailScroll += 5;
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean handleEscape() {
+        return false;
+    }
+
+    @Override
+    public void navigateUp() {
+        detailScroll = 0;
+        selectedKey = null;
+        tableState.selectPrevious();
+    }
+
+    @Override
+    public void navigateDown() {
+        detailScroll = 0;
+        selectedKey = null;
+        IntegrationInfo info = ctx.findSelectedIntegration();
+        if (info != null) {
+            tableState.selectNext(info.sqlTraceStatements.size());
+        }
+    }
+
+    @Override
+    public void render(Frame frame, Rect area) {
+        IntegrationInfo info = ctx.findSelectedIntegration();
+        if (info == null) {
+            renderNoSelection(frame, area);
+            return;
+        }
+
+        List<Rect> layout = Layout.vertical()
+                .constraints(Constraint.length(3), Constraint.fill())
+                .split(area);
+
+        renderKpiStrip(frame, layout.get(0), info);
+        renderMasterDetail(frame, layout.get(1), info);
+    }
+
+    private void renderKpiStrip(Frame frame, Rect area, IntegrationInfo info) {
+        Style labelStyle = Style.EMPTY.dim();
+        Style valueStyle = Style.EMPTY.fg(Color.CYAN).bold();
+        Style warnStyle = Style.EMPTY.fg(Color.YELLOW).bold();
+        Style errorStyle = Style.EMPTY.fg(Color.LIGHT_RED).bold();
+
+        List<Span> spans = new ArrayList<>();
+        spans.add(Span.styled("  Total: ", labelStyle));
+        spans.add(Span.styled(String.valueOf(info.sqlTraceTotal), valueStyle));
+        spans.add(Span.styled("  Avg: ", labelStyle));
+        spans.add(Span.styled(info.sqlTraceAvgTime + " ms", valueStyle));
+        spans.add(Span.styled("  Slowest: ", labelStyle));
+        Style slowestStyle = info.sqlTraceSlowestTime >= 100 ? warnStyle : 
valueStyle;
+        spans.add(Span.styled(info.sqlTraceSlowestTime + " ms", slowestStyle));
+        spans.add(Span.styled("  Slow(>=100ms): ", labelStyle));
+        Style slowStyle = info.sqlTraceSlowCount > 0 ? warnStyle : valueStyle;
+        spans.add(Span.styled(String.valueOf(info.sqlTraceSlowCount), 
slowStyle));
+        spans.add(Span.styled("  Failed: ", labelStyle));
+        Style failStyle = info.sqlTraceFailedCount > 0 ? errorStyle : 
valueStyle;
+        spans.add(Span.styled(String.valueOf(info.sqlTraceFailedCount), 
failStyle));
+
+        Paragraph kpi = Paragraph.builder()
+                .text(Text.from(Line.from(spans)))
+                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                        .title(" SQL Trace ").build())
+                .build();
+        frame.renderWidget(kpi, area);
+    }
+
+    private String getSelectedQuery() {
+        IntegrationInfo info = ctx.findSelectedIntegration();
+        if (info == null) {
+            return null;
+        }
+        List<SqlTraceInfo> sorted = new ArrayList<>(info.sqlTraceStatements);
+        sorted.sort(this::sortTrace);
+        Integer sel = tableState.selected();
+        if (sel != null && sel >= 0 && sel < sorted.size()) {
+            return sorted.get(sel).query;
+        }
+        return null;
+    }
+
+    private static String traceKey(SqlTraceInfo si) {
+        return si.exchangeId + "@" + si.timestamp;
+    }
+
+    private void renderMasterDetail(Frame frame, Rect area, IntegrationInfo 
info) {
+        List<SqlTraceInfo> sorted = new ArrayList<>(info.sqlTraceStatements);
+        sorted.sort(this::sortTrace);
+
+        // follow the previously selected row when new data shifts indices
+        if (selectedKey != null) {
+            for (int i = 0; i < sorted.size(); i++) {
+                if (selectedKey.equals(traceKey(sorted.get(i)))) {
+                    tableState.select(i);
+                    break;
+                }
+            }
+        }
+
+        // auto-select first row when data arrives
+        if (!sorted.isEmpty() && tableState.selected() == null) {
+            tableState.select(0);
+        }
+
+        SqlTraceInfo selected = null;
+        Integer sel = tableState.selected();
+        if (sel != null && sel >= 0 && sel < sorted.size()) {
+            selected = sorted.get(sel);
+            selectedKey = traceKey(selected);
+        }
+        boolean showDetail = selected != null;
+
+        List<Rect> chunks = showDetail
+                ? Layout.vertical()
+                        .constraints(Constraint.length(13), 
Constraint.length(1), Constraint.fill())
+                        .split(area)
+                : List.of(area);
+
+        renderTable(frame, chunks.get(0), sorted);
+
+        if (showDetail) {
+            renderDetail(frame, chunks.get(2), selected);
+        }
+    }
+
+    private void renderTable(Frame frame, Rect area, List<SqlTraceInfo> 
sorted) {
+        List<Row> rows = new ArrayList<>();
+        for (SqlTraceInfo si : sorted) {
+            Style durStyle = si.duration >= 100 ? Style.EMPTY.fg(Color.YELLOW) 
: Style.EMPTY;
+            Style statusStyle = si.failed ? Style.EMPTY.fg(Color.LIGHT_RED) : 
Style.EMPTY.fg(Color.GREEN);
+            String status = si.failed ? "FAIL" : "OK";
+
+            String time = "";
+            if (si.timestamp > 0) {
+                time = 
LocalDateTime.ofInstant(Instant.ofEpochMilli(si.timestamp), 
ZoneId.systemDefault())
+                        .format(TIME_FMT);
+            }
+
+            String rowsStr = "";
+            if (si.rowCount > 0) {
+                rowsStr = String.valueOf(si.rowCount);
+            } else if (si.updateCount > 0) {
+                rowsStr = String.valueOf(si.updateCount);
+            }
+
+            rows.add(Row.from(
+                    Cell.from(Span.styled(time, Style.EMPTY.dim())),
+                    Cell.from(Span.styled(si.category != null ? si.category : 
"", categoryStyle(si.category))),
+                    Cell.from(si.query != null ? si.query : ""),
+                    Cell.from(Span.styled(si.routeId != null ? si.routeId : 
"", Style.EMPTY.fg(Color.CYAN))),
+                    rightCell(String.valueOf(si.duration), 10, durStyle),
+                    rightCell(rowsStr, 8),
+                    Cell.from(Span.styled(status, statusStyle))));
+        }
+
+        if (rows.isEmpty()) {
+            rows.add(Row.from(
+                    Cell.from(""), Cell.from(""),
+                    Cell.from(Span.styled("No SQL statements traced", 
Style.EMPTY.dim())),
+                    Cell.from(""), Cell.from(""), Cell.from(""), 
Cell.from("")));
+        }
+
+        Table table = Table.builder()
+                .rows(rows)
+                .header(Row.from(
+                        Cell.from(Span.styled(sortLabel("TIME", "time"), 
sortStyle("time"))),
+                        Cell.from(Span.styled(sortLabel("TYPE", "type"), 
sortStyle("type"))),
+                        Cell.from(Span.styled(sortLabel("SQL", "sql"), 
sortStyle("sql"))),
+                        Cell.from(Span.styled(sortLabel("ROUTE", "route"), 
sortStyle("route"))),
+                        rightCell(sortLabel("DURATION", "duration"), 10, 
sortStyle("duration")),
+                        rightCell(sortLabel("ROWS", "rows"), 8, 
sortStyle("rows")),
+                        Cell.from(Span.styled("STATUS", Style.EMPTY.bold()))))
+                .widths(
+                        Constraint.length(14),
+                        Constraint.length(8),
+                        Constraint.fill(),
+                        Constraint.length(20),
+                        Constraint.length(10),
+                        Constraint.length(8),
+                        Constraint.length(8))
+                .highlightStyle(Style.EMPTY.fg(Color.WHITE).bold().onBlue())
+                .highlightSpacing(Table.HighlightSpacing.ALWAYS)
+                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                        .title(" Statements sort:" + sort + " ").build())
+                .build();
+
+        frame.renderStatefulWidget(table, area, tableState);
+    }
+
+    private void renderDetail(Frame frame, Rect area, SqlTraceInfo si) {
+        List<Line> lines = new ArrayList<>();
+        Style labelStyle = Style.EMPTY.fg(Color.CYAN).bold();
+        Style valueStyle = Style.EMPTY;
+
+        lines.add(Line.from(
+                Span.styled(" Category: ", labelStyle),
+                Span.styled(si.category != null ? si.category : "", 
categoryStyle(si.category))));
+        lines.add(Line.from(
+                Span.styled(" Route: ", labelStyle),
+                Span.styled(si.routeId != null ? si.routeId : "", 
valueStyle)));
+        if (si.nodeId != null) {
+            List<Span> nodeSpans = new ArrayList<>();
+            nodeSpans.add(Span.styled(" Node: ", labelStyle));
+            nodeSpans.add(Span.styled(si.nodeId, valueStyle));
+            if (si.location != null) {
+                nodeSpans.add(Span.styled("  Source: ", labelStyle));
+                nodeSpans.add(Span.styled(si.location, valueStyle));
+            }
+            lines.add(Line.from(nodeSpans));
+        }
+        lines.add(Line.from(
+                Span.styled(" Exchange: ", labelStyle),
+                Span.styled(si.exchangeId != null ? si.exchangeId : "", 
valueStyle)));
+
+        String time = "";
+        if (si.timestamp > 0) {
+            time = LocalDateTime.ofInstant(Instant.ofEpochMilli(si.timestamp), 
ZoneId.systemDefault())
+                    .format(TIME_FMT);
+        }
+        Style durStyle = si.duration >= 100 ? Style.EMPTY.fg(Color.YELLOW) : 
Style.EMPTY;
+        Style statusStyle = si.failed ? Style.EMPTY.fg(Color.LIGHT_RED) : 
Style.EMPTY.fg(Color.GREEN);
+        lines.add(Line.from(
+                Span.styled(" Time: ", labelStyle),
+                Span.styled(time, valueStyle),
+                Span.styled("  Duration: ", labelStyle),
+                Span.styled(si.duration + " ms", durStyle),
+                Span.styled("  Status: ", labelStyle),
+                Span.styled(si.failed ? "FAILED" : "OK", statusStyle)));
+
+        String rowsStr = "";
+        if (si.rowCount > 0) {
+            rowsStr = si.rowCount + " rows";
+        } else if (si.updateCount > 0) {
+            rowsStr = si.updateCount + " updated";
+        }
+        if (!rowsStr.isEmpty()) {
+            lines.add(Line.from(
+                    Span.styled(" Rows: ", labelStyle),
+                    Span.styled(rowsStr, valueStyle)));
+        }
+
+        lines.add(Line.from(Span.raw("")));
+        lines.add(Line.from(Span.styled(" SQL:", labelStyle)));
+        lines.add(Line.from(Span.styled("   " + (si.query != null ? si.query : 
""), valueStyle)));
+
+        int[] scroll = { detailScroll };
+        int[] hScroll = { 0 };
+        HistoryTab.renderDetailPanel(frame, area, lines, wordWrap, hScroll, 
scroll, detailScrollState);
+        detailScroll = scroll[0];
+    }
+
+    private static Style categoryStyle(String category) {
+        if (category == null) {
+            return Style.EMPTY;
+        }
+        return switch (category) {
+            case "SELECT" -> Style.EMPTY.fg(Color.CYAN);
+            case "INSERT" -> Style.EMPTY.fg(Color.GREEN);
+            case "UPDATE" -> Style.EMPTY.fg(Color.YELLOW);
+            case "DELETE" -> Style.EMPTY.fg(Color.LIGHT_RED);
+            default -> Style.EMPTY;
+        };
+    }
+
+    @Override
+    public void renderFooter(List<Span> spans) {
+        hint(spans, "Esc", "back");
+        hint(spans, "↑↓", "navigate");
+        hint(spans, "Home/End", "top/end");
+        hint(spans, "PgUp/Dn", "scroll detail");
+        hint(spans, "e", "edit SQL");
+        hint(spans, "s", "sort");
+        hint(spans, "w", "wrap [" + (wordWrap ? "on" : "off") + "]");
+    }
+
+    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 sortTrace(SqlTraceInfo a, SqlTraceInfo b) {
+        int result = switch (sort) {
+            case "type" -> {
+                String ca = a.category != null ? a.category : "";
+                String cb = b.category != null ? b.category : "";
+                yield ca.compareToIgnoreCase(cb);
+            }
+            case "sql" -> {
+                String qa = a.query != null ? a.query : "";
+                String qb = b.query != null ? b.query : "";
+                yield qa.compareToIgnoreCase(qb);
+            }
+            case "route" -> {
+                String ra = a.routeId != null ? a.routeId : "";
+                String rb = b.routeId != null ? b.routeId : "";
+                yield ra.compareToIgnoreCase(rb);
+            }
+            case "duration" -> Long.compare(b.duration, a.duration);
+            case "rows" -> {
+                int ra = a.rowCount > 0 ? a.rowCount : a.updateCount;
+                int rb = b.rowCount > 0 ? b.rowCount : b.updateCount;
+                yield Integer.compare(rb, ra);
+            }
+            default -> Long.compare(b.timestamp, a.timestamp); // "time" — 
newest first
+        };
+        return sortReversed ? -result : result;
+    }
+
+    @Override
+    public SelectionContext getSelectionContext() {
+        IntegrationInfo info = ctx.findSelectedIntegration();
+        if (info == null || info.sqlTraceStatements.isEmpty()) {
+            return null;
+        }
+        List<SqlTraceInfo> sorted = new ArrayList<>(info.sqlTraceStatements);
+        sorted.sort(this::sortTrace);
+        List<String> items = sorted.stream()
+                .map(s -> s.query != null ? s.query : s.endpoint)
+                .toList();
+        Integer sel = tableState.selected();
+        return new SelectionContext("table", items, sel != null ? sel : -1, 
items.size(), "SQL Trace");
+    }
+
+    @Override
+    public String getHelpText() {
+        return """
+                # SQL Trace
+
+                Traces SQL query executions flowing through `camel-sql` and 
`camel-jdbc`
+                components. Captures individual executions with timing, row 
counts,
+                and failure status.
+
+                ## KPI Strip
+
+                The top bar shows aggregate statistics:
+                - **Total** — Total number of SQL statements traced
+                - **Avg** — Average execution time in milliseconds
+                - **Slowest** — Longest single execution (yellow when >= 100ms)
+                - **Slow(>=100ms)** — Count of slow queries (yellow when > 0)
+                - **Failed** — Count of failed executions (red when > 0)
+
+                ## Table Columns
+
+                - **TIME** — Timestamp of the execution
+                - **TYPE** — SQL type: SELECT, INSERT, UPDATE, DELETE, CALL, 
or OTHER
+                - **SQL** — The SQL query text
+                - **ROUTE** — The Camel route ID that executed the query
+                - **DURATION** — Execution time in ms (yellow when >= 100ms)
+                - **ROWS** — Row count (for SELECT) or update count (for 
INSERT/UPDATE/DELETE)
+                - **STATUS** — OK (green) or FAIL (red)
+
+                ## Detail Panel
+
+                Select a statement with Up/Down to see full details below the 
table:
+                SQL text, endpoint URI, route, exchange ID, timing, and row 
counts.
+
+                ## Keys
+
+                - `Up/Down` — select statement
+                - `PgUp/PgDn` — scroll detail panel
+                - `Home/End` — jump to top/end of detail
+                - `s` — cycle sort column
+                - `S` — reverse sort order
+                - `w` — toggle word wrap
+                """;
+    }
+
+    @Override
+    public JsonObject getTableDataAsJson() {
+        IntegrationInfo info = ctx.findSelectedIntegration();
+        if (info == null) {
+            return null;
+        }
+        JsonObject result = new JsonObject();
+        result.put("tab", "SQL Trace");
+        JsonArray rows = new JsonArray();
+        for (SqlTraceInfo si : info.sqlTraceStatements) {
+            JsonObject row = new JsonObject();
+            row.put("timestamp", si.timestamp);
+            row.put("exchangeId", si.exchangeId);
+            row.put("routeId", si.routeId);
+            row.put("query", si.query);
+            row.put("type", si.category);
+            row.put("endpoint", si.endpoint);
+            row.put("duration", si.duration);
+            row.put("rowCount", si.rowCount);
+            row.put("updateCount", si.updateCount);
+            row.put("failed", si.failed);
+            rows.add(row);
+        }
+        result.put("rows", rows);
+        result.put("totalRows", info.sqlTraceStatements.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/StatusParser.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
index 515c080554fb..4b911df11bb2 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
@@ -522,6 +522,39 @@ final class StatusParser {
             }
         }
 
+        // Parse sqlTrace
+        JsonObject sqlTraceObj = (JsonObject) root.get("sqlTrace");
+        if (sqlTraceObj != null) {
+            JsonObject summary = (JsonObject) sqlTraceObj.get("summary");
+            if (summary != null) {
+                info.sqlTraceTotal = summary.getLongOrDefault("totalQueries", 
0);
+                info.sqlTraceAvgTime = summary.getLongOrDefault("avgTime", 0);
+                info.sqlTraceSlowestTime = 
summary.getLongOrDefault("slowestTime", 0);
+                info.sqlTraceSlowCount = summary.getLongOrDefault("slowCount", 
0);
+                info.sqlTraceFailedCount = 
summary.getLongOrDefault("failedCount", 0);
+            }
+            JsonArray stmts = (JsonArray) sqlTraceObj.get("statements");
+            if (stmts != null) {
+                for (Object s : stmts) {
+                    JsonObject sj = (JsonObject) s;
+                    SqlTraceInfo si = new SqlTraceInfo();
+                    si.exchangeId = sj.getString("exchangeId");
+                    si.routeId = sj.getString("routeId");
+                    si.nodeId = sj.getString("nodeId");
+                    si.location = sj.getString("location");
+                    si.query = sj.getString("query");
+                    si.category = sj.getString("category");
+                    si.endpoint = sj.getString("endpoint");
+                    si.timestamp = sj.getLongOrDefault("timestamp", 0);
+                    si.duration = sj.getLongOrDefault("duration", 0);
+                    si.rowCount = sj.getIntegerOrDefault("rowCount", 0);
+                    si.updateCount = sj.getIntegerOrDefault("updateCount", 0);
+                    si.failed = sj.getBooleanOrDefault("failed", false);
+                    info.sqlTraceStatements.add(si);
+                }
+            }
+        }
+
         return info;
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java
index 68a948ed51e2..4fb2bf453383 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java
@@ -87,6 +87,7 @@ class TabRegistry {
     private OverviewTab overviewTab;
     private DataSourceTab dataSourceTab;
     private SqlQueryTab sqlQueryTab;
+    private SqlTraceTab sqlTraceTab;
 
     private MonitorTab activeMoreTab;
 
@@ -105,6 +106,7 @@ class TabRegistry {
         consumersTab = new ConsumersTab(ctx);
         dataSourceTab = new DataSourceTab(ctx);
         sqlQueryTab = new SqlQueryTab(ctx);
+        sqlTraceTab = new SqlTraceTab(ctx);
         endpointsTab = new EndpointsTab(ctx, dataService.metrics());
         httpTab = new HttpTab(ctx);
         healthTab = new HealthTab(ctx);
@@ -125,6 +127,11 @@ class TabRegistry {
         overviewTab = new OverviewTab(
                 ctx, dataService.metrics(), dataService.stoppingPids(),
                 resetIntegrationTabState);
+
+        sqlTraceTab.setEditSqlAction(sql -> {
+            selectMoreTab(10); // switch to SQL Query tab
+            sqlQueryTab.setInputValue("sql", sql);
+        });
     }
 
     // ---- Tab access ----
@@ -213,10 +220,11 @@ class TabRegistry {
             case 8 -> memoryTab;
             case 9 -> metricsTab;
             case 10 -> sqlQueryTab;
-            case 11 -> spansTab;
-            case 12 -> processTab;
-            case 13 -> startupTab;
-            case 14 -> threadsTab;
+            case 11 -> sqlTraceTab;
+            case 12 -> spansTab;
+            case 13 -> processTab;
+            case 14 -> startupTab;
+            case 15 -> threadsTab;
             default -> null;
         };
         if (activeMoreTab != null) {
@@ -240,6 +248,7 @@ class TabRegistry {
         consumersTab.onIntegrationChanged();
         dataSourceTab.onIntegrationChanged();
         sqlQueryTab.onIntegrationChanged();
+        sqlTraceTab.onIntegrationChanged();
         circuitBreakerTab.onIntegrationChanged();
         inflightTab.onIntegrationChanged();
         spansTab.onIntegrationChanged();
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiHelper.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiHelper.java
index d7335aab1f9e..586d04ec5a0c 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiHelper.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiHelper.java
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.dsl.jbang.core.commands.tui;
 
+import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -28,6 +29,7 @@ import dev.tamboui.style.Style;
 import dev.tamboui.text.CharWidth;
 import dev.tamboui.text.Line;
 import dev.tamboui.text.Span;
+import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
 import org.apache.camel.dsl.jbang.core.common.ProcessHelper;
 import org.apache.camel.support.PatternHelper;
 import org.apache.camel.util.FileUtil;
@@ -43,7 +45,8 @@ final class TuiHelper {
     }
 
     /**
-     * Find PIDs of running Camel integrations matching the given name pattern.
+     * Find PIDs of running Camel integrations matching the given name 
pattern. Scans the {@code ~/.camel/} directory
+     * for status files rather than iterating all OS processes, which is 
significantly faster.
      */
     static List<Long> findPids(String name, Function<String, Path> 
statusFileResolver) {
         List<Long> pids = new ArrayList<>();
@@ -53,29 +56,65 @@ final class TuiHelper {
             pattern = pattern + "*";
         }
         final String pat = pattern;
-        ProcessHandle.allProcesses()
-                .filter(ph -> ph.pid() != cur)
-                .forEach(ph -> {
-                    JsonObject root = loadStatus(ph.pid(), statusFileResolver);
-                    if (root != null) {
-                        String pName = ProcessHelper.extractName(root, ph);
-                        pName = FileUtil.onlyName(pName);
+
+        for (long pid : findCandidatePids()) {
+            if (pid == cur) {
+                continue;
+            }
+            // check process is still alive
+            if 
(!ProcessHandle.of(pid).map(ProcessHandle::isAlive).orElse(false)) {
+                continue;
+            }
+            JsonObject root = loadStatus(pid, statusFileResolver);
+            if (root != null) {
+                ProcessHandle ph = ProcessHandle.of(pid).orElse(null);
+                String pName = ProcessHelper.extractName(root, ph);
+                pName = FileUtil.onlyName(pName);
+                if (pName != null && !pName.isEmpty() && 
PatternHelper.matchPattern(pName, pat)) {
+                    pids.add(pid);
+                } else {
+                    JsonObject context = (JsonObject) root.get("context");
+                    if (context != null) {
+                        pName = context.getString("name");
+                        if ("CamelJBang".equals(pName)) {
+                            pName = null;
+                        }
                         if (pName != null && !pName.isEmpty() && 
PatternHelper.matchPattern(pName, pat)) {
-                            pids.add(ph.pid());
-                        } else {
-                            JsonObject context = (JsonObject) 
root.get("context");
-                            if (context != null) {
-                                pName = context.getString("name");
-                                if ("CamelJBang".equals(pName)) {
-                                    pName = null;
-                                }
-                                if (pName != null && !pName.isEmpty() && 
PatternHelper.matchPattern(pName, pat)) {
-                                    pids.add(ph.pid());
-                                }
-                            }
+                            pids.add(pid);
                         }
                     }
-                });
+                }
+            }
+        }
+        return pids;
+    }
+
+    /**
+     * List candidate PIDs by scanning {@code ~/.camel/} for {@code 
*-status.json} files. This is O(number of status
+     * files) rather than O(total OS processes).
+     */
+    static List<Long> findCandidatePids() {
+        List<Long> pids = new ArrayList<>();
+        try {
+            Path camelDir = CommandLineHelper.getCamelDir();
+            if (Files.isDirectory(camelDir)) {
+                try (var files = Files.list(camelDir)) {
+                    files.forEach(p -> {
+                        String fn = p.getFileName().toString();
+                        if (fn.endsWith("-status.json")) {
+                            try {
+                                long pid = Long.parseLong(fn.substring(0, 
fn.length() - "-status.json".length()));
+                                pids.add(pid);
+                            } catch (NumberFormatException e) {
+                                // not a PID-based status file
+                            }
+                        }
+                    });
+                }
+            }
+        } catch (IOException e) {
+            // ignore
+        }
         return pids;
     }
 
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiMcpServer.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiMcpServer.java
index 0a3b71cbf7e7..86e560387641 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiMcpServer.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiMcpServer.java
@@ -922,7 +922,17 @@ class TuiMcpServer {
 
     private String callGetOptions() {
         JsonObject result = new JsonObject();
-        result.put("tabs", toJsonArray(facade.getTabNames()));
+        JsonArray tabsArray = new JsonArray();
+        for (String name : facade.getTabNames()) {
+            JsonObject tab = new JsonObject();
+            tab.put("name", name);
+            String desc = McpFacade.TAB_DESCRIPTIONS.get(name);
+            if (desc != null) {
+                tab.put("description", desc);
+            }
+            tabsArray.add(tab);
+        }
+        result.put("tabs", tabsArray);
         result.put("activeTab", facade.getActiveTabName());
         result.put("activeTabIndex", facade.getActiveTabIndex());
         result.put("integrations", toJsonArray(facade.getIntegrationNames()));
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SqlTraceTabRenderTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SqlTraceTabRenderTest.java
new file mode 100644
index 000000000000..f76578626ae5
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/SqlTraceTabRenderTest.java
@@ -0,0 +1,196 @@
+/*
+ * 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 java.util.concurrent.atomic.AtomicReference;
+
+import dev.tamboui.buffer.Buffer;
+import dev.tamboui.layout.Rect;
+import dev.tamboui.style.Color;
+import dev.tamboui.terminal.Frame;
+import dev.tamboui.text.Span;
+import dev.tamboui.tui.event.KeyEvent;
+import dev.tamboui.tui.event.KeyModifiers;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class SqlTraceTabRenderTest {
+
+    private MonitorContext ctx;
+    private IntegrationInfo info;
+
+    @BeforeEach
+    void setUp() {
+        info = new IntegrationInfo();
+        info.pid = "1234";
+        info.name = "test-app";
+
+        AtomicReference<List<IntegrationInfo>> data = new 
AtomicReference<>(List.of(info));
+        AtomicReference<List<InfraInfo>> infraData = new 
AtomicReference<>(List.of());
+        ctx = new MonitorContext(data, infraData);
+        ctx.selectedPid = "1234";
+    }
+
+    @Test
+    void renderShowsKpiStrip() {
+        info.sqlTraceTotal = 42;
+        info.sqlTraceAvgTime = 15;
+        info.sqlTraceSlowestTime = 120;
+        info.sqlTraceSlowCount = 3;
+        info.sqlTraceFailedCount = 1;
+
+        SqlTraceTab tab = new SqlTraceTab(ctx);
+        String rendered = TuiTestHelper.renderToString(tab, 140, 25);
+
+        assertTrue(rendered.contains("SQL Trace"), "Should show SQL Trace 
title");
+        assertTrue(rendered.contains("Total:"), "Should show Total KPI");
+        assertTrue(rendered.contains("42"), "Should show total count");
+        assertTrue(rendered.contains("Avg:"), "Should show Avg KPI");
+    }
+
+    @Test
+    void renderShowsTableHeaders() {
+        addSqlTrace("SELECT * FROM users", "SELECT", "route1", 25, 10, false);
+
+        SqlTraceTab tab = new SqlTraceTab(ctx);
+        String rendered = TuiTestHelper.renderToString(tab, 140, 25);
+
+        assertTrue(rendered.contains("TIME"), "Should show TIME header");
+        assertTrue(rendered.contains("TYPE"), "Should show TYPE header");
+        assertTrue(rendered.contains("SQL"), "Should show SQL header");
+        assertTrue(rendered.contains("ROUTE"), "Should show ROUTE header");
+        assertTrue(rendered.contains("DURATION"), "Should show DURATION 
header");
+    }
+
+    @Test
+    void renderShowsSqlData() {
+        addSqlTrace("SELECT * FROM orders", "SELECT", "orderRoute", 15, 5, 
false);
+
+        SqlTraceTab tab = new SqlTraceTab(ctx);
+        String rendered = TuiTestHelper.renderToString(tab, 140, 25);
+
+        assertTrue(rendered.contains("SELECT * FROM orders"), "Should render 
SQL query text");
+        assertTrue(rendered.contains("orderRoute"), "Should render route ID");
+    }
+
+    @Test
+    void renderSlowQueryHighlighted() {
+        addSqlTrace("SELECT * FROM big_table", "SELECT", "route1", 150, 1000, 
false);
+
+        SqlTraceTab tab = new SqlTraceTab(ctx);
+
+        Rect area = new Rect(0, 0, 140, 25);
+        Buffer buffer = Buffer.empty(area);
+        Frame frame = Frame.forTesting(buffer);
+        tab.render(frame, area);
+
+        boolean foundYellow = TuiTestHelper.findCellWithColor(buffer, "1", 
Color.YELLOW);
+        assertTrue(foundYellow, "Slow query duration should be highlighted in 
YELLOW");
+    }
+
+    @Test
+    void renderFailedQueryInRed() {
+        addSqlTrace("INSERT INTO bad_table", "INSERT", "route1", 5, 0, true);
+
+        SqlTraceTab tab = new SqlTraceTab(ctx);
+
+        Rect area = new Rect(0, 0, 140, 25);
+        Buffer buffer = Buffer.empty(area);
+        Frame frame = Frame.forTesting(buffer);
+        tab.render(frame, area);
+
+        boolean foundRed = TuiTestHelper.findCellWithColor(buffer, "F", 
Color.LIGHT_RED);
+        assertTrue(foundRed, "Failed status should be rendered in LIGHT_RED");
+    }
+
+    @Test
+    void renderEmptyShowsPlaceholder() {
+        SqlTraceTab tab = new SqlTraceTab(ctx);
+        String rendered = TuiTestHelper.renderToString(tab, 140, 25);
+
+        assertTrue(rendered.contains("No SQL statements"), "Should show 
placeholder when no traces exist");
+    }
+
+    @Test
+    void renderNoSelectionShowsPrompt() {
+        ctx.selectedPid = null;
+
+        SqlTraceTab tab = new SqlTraceTab(ctx);
+        String rendered = TuiTestHelper.renderToString(tab, 100, 25);
+
+        assertTrue(rendered.contains("No integration selected") || 
rendered.contains("Select an integration"),
+                "Should show selection prompt when no integration selected");
+    }
+
+    @Test
+    void sortCycleChangesSortIndicator() {
+        addSqlTrace("SELECT 1", "SELECT", "route1", 10, 1, false);
+
+        SqlTraceTab tab = new SqlTraceTab(ctx);
+
+        tab.handleKeyEvent(KeyEvent.ofChar('s', KeyModifiers.NONE));
+        String rendered = TuiTestHelper.renderToString(tab, 140, 25);
+
+        assertTrue(rendered.contains("sort:type"), "Sort should cycle to 
'type' after pressing 's'");
+    }
+
+    @Test
+    void renderFooterHints() {
+        SqlTraceTab tab = new SqlTraceTab(ctx);
+        List<Span> footerSpans = new ArrayList<>();
+        tab.renderFooter(footerSpans);
+
+        String footer = footerSpans.stream()
+                .map(Span::content)
+                .reduce("", String::concat);
+
+        assertTrue(footer.contains("Esc"), "Footer should contain Esc hint");
+        assertTrue(footer.contains("sort"), "Footer should contain sort hint");
+    }
+
+    @Test
+    void helpTextIsAvailable() {
+        SqlTraceTab tab = new SqlTraceTab(ctx);
+        String help = tab.getHelpText();
+        assertNotNull(help, "Help text should not be null");
+        assertTrue(help.contains("SQL Trace"), "Help text should mention SQL 
Trace");
+        assertTrue(help.contains("KPI Strip"), "Help text should describe KPI 
strip");
+    }
+
+    // ---- Helper methods ----
+
+    private void addSqlTrace(
+            String query, String category, String routeId, long duration, int 
rowCount,
+            boolean failed) {
+        SqlTraceInfo si = new SqlTraceInfo();
+        si.query = query;
+        si.category = category;
+        si.routeId = routeId;
+        si.duration = duration;
+        si.rowCount = rowCount;
+        si.failed = failed;
+        si.exchangeId = "ID-test-1234";
+        si.endpoint = "sql:" + query;
+        si.timestamp = System.currentTimeMillis();
+        info.sqlTraceStatements.add(si);
+    }
+}

Reply via email to