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

davsclaus pushed a commit to branch fix/CAMEL-23862
in repository https://gitbox.apache.org/repos/asf/camel.git

commit ad0718430ff50eef4fe32068df2151ed55cfa449
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Jun 30 19:25:07 2026 +0200

    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/impl/console/SqlTraceDevConsole.java     |  17 +++
 .../dsl/jbang/core/commands/tui/SqlTraceTab.java   | 151 ++++++++++++++++++++-
 2 files changed, 164 insertions(+), 4 deletions(-)

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
index b20ff8fd9929..63809d27570d 100644
--- 
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
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.impl.console;
 
+import java.io.InputStream;
 import java.net.URLDecoder;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
@@ -31,6 +32,7 @@ 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;
@@ -204,6 +206,17 @@ public class SqlTraceDevConsole extends AbstractDevConsole 
{
         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);
@@ -252,6 +265,10 @@ public class SqlTraceDevConsole extends AbstractDevConsole 
{
                     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());
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
index 37fe1c062a63..b68b8acf49af 100644
--- 
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
@@ -35,7 +35,9 @@ 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;
@@ -52,14 +54,26 @@ class SqlTraceTab implements MonitorTab {
 
     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;
 
     SqlTraceTab(MonitorContext ctx) {
         this.ctx = ctx;
     }
 
+    @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')) {
@@ -72,6 +86,26 @@ class SqlTraceTab implements MonitorTab {
             sortReversed = !sortReversed;
             return true;
         }
+        if (ke.isCharIgnoreCase('w')) {
+            wordWrap = !wordWrap;
+            return true;
+        }
+        if (ke.isHome()) {
+            detailScroll = 0;
+            return true;
+        }
+        if (ke.isEnd()) {
+            detailScroll = Integer.MAX_VALUE;
+            return true;
+        }
+        if (ke.isPageUp()) {
+            detailScroll = Math.max(0, detailScroll - 5);
+            return true;
+        }
+        if (ke.isPageDown()) {
+            detailScroll += 5;
+            return true;
+        }
         return false;
     }
 
@@ -82,10 +116,17 @@ class SqlTraceTab implements MonitorTab {
 
     @Override
     public void navigateUp() {
+        detailScroll = 0;
+        tableState.selectPrevious();
     }
 
     @Override
     public void navigateDown() {
+        detailScroll = 0;
+        IntegrationInfo info = ctx.findSelectedIntegration();
+        if (info != null) {
+            tableState.selectNext(info.sqlTraceStatements.size());
+        }
     }
 
     @Override
@@ -101,7 +142,7 @@ class SqlTraceTab implements MonitorTab {
                 .split(area);
 
         renderKpiStrip(frame, layout.get(0), info);
-        renderTable(frame, layout.get(1), info);
+        renderMasterDetail(frame, layout.get(1), info);
     }
 
     private void renderKpiStrip(Frame frame, Rect area, IntegrationInfo info) {
@@ -127,16 +168,52 @@ class SqlTraceTab implements MonitorTab {
 
         Paragraph kpi = Paragraph.builder()
                 .text(Text.from(Line.from(spans)))
-                .block(Block.builder().borderType(BorderType.ROUNDED)
+                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
                         .title(" SQL Trace ").build())
                 .build();
         frame.renderWidget(kpi, area);
     }
 
-    private void renderTable(Frame frame, Rect area, IntegrationInfo info) {
+    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);
 
+        // restore selection by identity so cursor follows the same row when 
new data arrives
+        if (selectedKey != null) {
+            for (int i = 0; i < sorted.size(); i++) {
+                if (selectedKey.equals(traceKey(sorted.get(i)))) {
+                    tableState.select(i);
+                    break;
+                }
+            }
+        }
+
+        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;
@@ -191,13 +268,67 @@ class SqlTraceTab implements MonitorTab {
                         Constraint.length(10),
                         Constraint.length(8),
                         Constraint.length(8))
-                .block(Block.builder().borderType(BorderType.ROUNDED)
+                .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)));
+        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;
@@ -214,7 +345,11 @@ class SqlTraceTab implements MonitorTab {
     @Override
     public void renderFooter(List<Span> spans) {
         hint(spans, "Esc", "back");
+        hint(spans, "↑↓", "navigate");
+        hint(spans, "PgUp/Dn", "scroll detail");
+        hint(spans, "Home/End", "top/end");
         hint(spans, "s", "sort");
+        hint(spans, "w", "wrap [" + (wordWrap ? "on" : "off") + "]");
     }
 
     private String sortLabel(String label, String column) {
@@ -296,11 +431,19 @@ class SqlTraceTab implements MonitorTab {
                 - **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
                 """;
     }
 

Reply via email to