You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2026/06/30 17:45:10 UTC
(camel) 04/08: CAMEL-23862: SQL Trace - master/detail panel, borders, and resource file resolution
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 <cl...@gmail.com>
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 <no...@anthropic.com>
Signed-off-by: Claus Ibsen <cl...@gmail.com>
---
.../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
""";
}