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

commit b2da8ea93aa2ce249c8ebd3dacce3a7ba87c157a
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Jul 27 20:04:48 2026 +0200

    CAMEL-24259: camel-tui - Add Data Type Transformers tab
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../camel/cli/connector/LocalCliConnector.java     |  14 ++
 .../jbang/core/commands/tui/IntegrationInfo.java   |   1 +
 .../dsl/jbang/core/commands/tui/StatusParser.java  |   4 +
 .../dsl/jbang/core/commands/tui/TabRegistry.java   |   8 +-
 .../jbang/core/commands/tui/TransformersTab.java   | 275 +++++++++++++++++++++
 .../dsl/jbang/core/commands/tui/TuiIcons.java      |   1 +
 6 files changed, 302 insertions(+), 1 deletion(-)

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 4da6c850f7b3..f25af3e88f69 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
@@ -394,6 +394,8 @@ public class LocalCliConnector extends ServiceSupport 
implements CliConnector, C
                 doActionSpringBootConfigurationTask(root);
             } else if ("type-converters".equals(action)) {
                 doActionTypeConvertersTask();
+            } else if ("transformers".equals(action)) {
+                doActionTransformersTask();
             }
         } catch (Exception e) {
             LOG.warn("Error executing action: {} due to: {}. This exception is 
ignored.", action != null ? action : af,
@@ -868,6 +870,18 @@ public class LocalCliConnector extends ServiceSupport 
implements CliConnector, C
         }
     }
 
+    private void doActionTransformersTask() throws IOException {
+        DevConsole dc = 
camelContext.getCamelContextExtension().getContextPlugin(DevConsoleRegistry.class)
+                .resolveById("transformers");
+        if (dc != null) {
+            JsonObject json = (JsonObject) dc.call(DevConsole.MediaType.JSON);
+            LOG.trace("Updating output file: {}", outputFile);
+            IOHelper.writeText(json.toJson(), outputFile);
+        } else {
+            IOHelper.writeText("{}", outputFile);
+        }
+    }
+
     private void doActionJvmTask() throws IOException {
         DevConsole dc = 
camelContext.getCamelContextExtension().getContextPlugin(DevConsoleRegistry.class)
                 .resolveById("jvm");
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 622952024ac7..177dc100cb88 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
@@ -113,4 +113,5 @@ class IntegrationInfo {
     String readmeFiles;
     Set<String> devConsoles = Set.of();
     boolean hasBrowseableEndpoints;
+    int transformerCount;
 }
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 37ce90e54c47..aa9883bf048d 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
@@ -712,6 +712,10 @@ final class StatusParser {
         if (root.containsKey("hasBrowseableEndpoints")) {
             info.hasBrowseableEndpoints = 
root.getBooleanOrDefault("hasBrowseableEndpoints", false);
         }
+        JsonObject trObj = (JsonObject) root.get("transformers");
+        if (trObj != null) {
+            info.transformerCount = trObj.getIntegerOrDefault("size", 0);
+        }
 
         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 19501cef973e..9bd91d6def4b 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
@@ -106,6 +106,7 @@ class TabRegistry {
     private InternalTasksTab internalTasksTab;
     private SqlTraceTab sqlTraceTab;
     private TypeConvertersTab typeConvertersTab;
+    private TransformersTab transformersTab;
 
     private MonitorTab activeMoreTab;
     private List<MoreTab> moreTabs;
@@ -134,6 +135,7 @@ class TabRegistry {
         internalTasksTab = new InternalTasksTab(ctx);
         sqlTraceTab = new SqlTraceTab(ctx);
         typeConvertersTab = new TypeConvertersTab(ctx);
+        transformersTab = new TransformersTab(ctx);
         endpointsTab = new EndpointsTab(ctx, dataService.metrics());
         networkTab = new NetworkTab(ctx, dataService.metrics());
         httpTab = new HttpTab(ctx);
@@ -238,7 +240,11 @@ class TabRegistry {
                         "Project"),
                 new MoreTab(
                         TuiIcons.TAB_TYPE_CONVERTERS, "Type Converters", 
"T&ype Converters",
-                        typeConvertersTab, "Project"));
+                        typeConvertersTab, "Project"),
+                new MoreTab(
+                        TuiIcons.TAB_TRANSFORMERS, "Transformers", 
"Trans&formers",
+                        transformersTab, "Project",
+                        List.of(), info -> info.transformerCount > 0));
     }
 
     // ---- Tab access ----
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TransformersTab.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TransformersTab.java
new file mode 100644
index 000000000000..4ab4369e7e89
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TransformersTab.java
@@ -0,0 +1,275 @@
+/*
+ * 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.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import dev.tamboui.layout.Constraint;
+import dev.tamboui.layout.Rect;
+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.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.table.Cell;
+import dev.tamboui.widgets.table.Row;
+import dev.tamboui.widgets.table.Table;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+
+import static org.apache.camel.dsl.jbang.core.commands.tui.TuiHelper.*;
+
+class TransformersTab extends AbstractTableTab {
+
+    private final AtomicBoolean loading = new AtomicBoolean(false);
+
+    private List<TransformerData> allTransformers = Collections.emptyList();
+    private String lastPid;
+
+    TransformersTab(MonitorContext ctx) {
+        super(ctx, "name", "from", "to");
+    }
+
+    @Override
+    protected int getRowCount() {
+        return sortedTransformers().size();
+    }
+
+    @Override
+    public void onTabSelected() {
+        String pid = ctx.selectedPid;
+        if (pid != null && !pid.equals(lastPid)) {
+            lastPid = pid;
+            allTransformers = Collections.emptyList();
+        }
+        if (allTransformers.isEmpty()) {
+            loadTransformers();
+        }
+    }
+
+    @Override
+    public void onIntegrationChanged() {
+        allTransformers = Collections.emptyList();
+        lastPid = null;
+    }
+
+    @Override
+    protected void renderContent(Frame frame, Rect area, IntegrationInfo info) 
{
+        if (loading.get() && allTransformers.isEmpty()) {
+            frame.renderWidget(
+                    Paragraph.builder()
+                            .text(Text.from(Line.from(Span.styled(" Loading 
transformers...", Style.EMPTY.dim()))))
+                            
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+                                    .title(" Data Type Transformers ").build())
+                            .build(),
+                    area);
+            return;
+        }
+
+        List<TransformerData> visible = sortedTransformers();
+        renderTable(frame, area, visible);
+    }
+
+    private void renderTable(Frame frame, Rect area, List<TransformerData> 
visible) {
+        List<Row> rows = new ArrayList<>();
+        for (TransformerData t : visible) {
+            rows.add(Row.from(
+                    Cell.from(Span.styled(t.name != null ? t.name : "", 
Style.EMPTY.fg(Theme.accent()))),
+                    Cell.from(Span.styled(t.from != null ? t.from : "*", 
Style.EMPTY)),
+                    Cell.from(Span.styled(t.to != null ? t.to : "*", 
Style.EMPTY))));
+        }
+
+        if (rows.isEmpty()) {
+            rows.add(emptyRow("No data type transformers", 3));
+        }
+
+        String title = String.format(" Data Type Transformers [%d] ", 
visible.size());
+
+        Table table = Table.builder()
+                .rows(rows)
+                .header(Row.from(
+                        Cell.from(Span.styled(sortLabel("NAME", "name"), 
sortStyle("name"))),
+                        Cell.from(Span.styled(sortLabel("FROM", "from"), 
sortStyle("from"))),
+                        Cell.from(Span.styled(sortLabel("TO", "to"), 
sortStyle("to")))))
+                .widths(
+                        Constraint.percentage(40),
+                        Constraint.percentage(30),
+                        Constraint.fill())
+                .highlightStyle(Theme.selectionBg())
+                .highlightSpacing(Table.HighlightSpacing.ALWAYS)
+                
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).build())
+                .build();
+
+        lastTableArea = area;
+        frame.renderStatefulWidget(table, area, tableState);
+        renderScrollbar(frame, visible.size());
+    }
+
+    @Override
+    public void renderFooter(List<Span> spans) {
+        hint(spans, "Esc", "back");
+        hint(spans, TuiIcons.HINT_SCROLL, "navigate");
+        hintLast(spans, "s", "sort");
+    }
+
+    @Override
+    public SelectionContext getSelectionContext() {
+        List<TransformerData> visible = sortedTransformers();
+        if (visible.isEmpty()) {
+            return null;
+        }
+        List<String> items = visible.stream().map(t -> t.name != null ? t.name 
: "?").toList();
+        Integer sel = tableState.selected();
+        return new SelectionContext("table", items, sel != null ? sel : -1, 
items.size(), "Data Type Transformers");
+    }
+
+    private List<TransformerData> sortedTransformers() {
+        List<TransformerData> result = new ArrayList<>(allTransformers);
+        result.sort((a, b) -> {
+            int cmp = switch (sort) {
+                case "from" -> compareStr(a.from, b.from);
+                case "to" -> compareStr(a.to, b.to);
+                default -> compareStr(a.name, b.name);
+            };
+            return sortReversed ? -cmp : cmp;
+        });
+        return result;
+    }
+
+    private void loadTransformers() {
+        if (ctx.selectedPid == null || ctx.runner == null) {
+            return;
+        }
+        if (!loading.compareAndSet(false, true)) {
+            return;
+        }
+        String pid = ctx.selectedPid;
+        ctx.backgroundExecutor.execute(() -> {
+            try {
+                loadTransformersInBackground(pid);
+            } finally {
+                loading.set(false);
+            }
+        });
+    }
+
+    private void loadTransformersInBackground(String pid) {
+        JsonObject root = new JsonObject();
+        root.put("action", "transformers");
+
+        JsonObject jo = ctx.executeAction(pid, root, 5000);
+        if (jo == null) {
+            return;
+        }
+
+        JsonArray arr = jo.getCollection("transformers");
+        if (arr == null) {
+            return;
+        }
+
+        List<TransformerData> result = new ArrayList<>();
+        for (int i = 0; i < arr.size(); i++) {
+            JsonObject tj = (JsonObject) arr.get(i);
+            TransformerData td = new TransformerData();
+            td.name = tj.getString("name");
+            td.from = tj.getString("from");
+            td.to = tj.getString("to");
+            result.add(td);
+        }
+
+        if (ctx.runner != null) {
+            ctx.runner.runOnRenderThread(() -> {
+                allTransformers = result;
+                lastPid = pid;
+            });
+        }
+    }
+
+    static class TransformerData {
+        String name;
+        String from;
+        String to;
+    }
+
+    @Override
+    public String description() {
+        return "Registered data type transformers in the Camel context";
+    }
+
+    @Override
+    public String getHelpText() {
+        return """
+                # Data Type Transformers
+
+                The Data Type Transformers tab shows all transformers 
registered in the
+                Camel transformer registry. Data type transformers are used to 
convert
+                message bodies between declared data types as part of Camel's 
contract
+                mechanism (inputType/outputType on routes).
+
+                When a route declares an inputType or outputType, Camel 
automatically
+                looks up a matching transformer and applies it to convert the 
message
+                body between the source and target data types.
+
+                ## Table Columns
+
+                - **NAME** — Transformer name (e.g. `json:jackson`, `xml:jaxb`)
+                - **FROM** — Source data type (`*` means any input type)
+                - **TO** — Target data type (`*` means any output type)
+
+                ## Keys
+
+                - `Up/Down` — select transformer
+                - `s` — cycle sort column
+                - `S` — reverse sort order
+                - `Esc` — back
+                """;
+    }
+
+    @Override
+    public JsonObject getTableDataAsJson() {
+        List<TransformerData> transformers = sortedTransformers();
+        if (transformers.isEmpty()) {
+            return null;
+        }
+        JsonObject result = new JsonObject();
+        result.put("tab", "Data Type Transformers");
+        JsonArray rows = new JsonArray();
+        for (TransformerData t : transformers) {
+            JsonObject row = new JsonObject();
+            row.put("name", t.name);
+            if (t.from != null) {
+                row.put("from", t.from);
+            }
+            if (t.to != null) {
+                row.put("to", t.to);
+            }
+            rows.add(row);
+        }
+        result.put("rows", rows);
+        result.put("totalRows", transformers.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/TuiIcons.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiIcons.java
index 55e6e4f60db0..9858818d9a76 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiIcons.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiIcons.java
@@ -153,6 +153,7 @@ final class TuiIcons {
     static final String TAB_INTERNAL_TASKS = "🔩";
     static final String TAB_ROUTE_CONTROLLER = "🚦";
     static final String TAB_TYPE_CONVERTERS = "🔄";
+    static final String TAB_TRANSFORMERS = "🔀";
 
     /** Icons for {@link TabRegistry#TAB_OVERVIEW}..{@link 
TabRegistry#TAB_MORE} (in order). */
     static final List<String> PRIMARY_TAB_ICONS = List.of(

Reply via email to