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 10e6679b4756 CAMEL-23615: camel-jbang - TUI unified Overview table and 
auto-start infra services (#23622)
10e6679b4756 is described below

commit 10e6679b47564c49cc1d4488e250b9ae126bcc97
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri May 29 10:32:33 2026 +0200

    CAMEL-23615: camel-jbang - TUI unified Overview table and auto-start infra 
services (#23622)
    
    * CAMEL-23615: camel-jbang - TUI merge integrations and infra into unified 
Overview table
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * CAMEL-23615: camel-jbang - TUI check Docker/Podman before opening Run 
Infra Service
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    * CAMEL-23615: camel-jbang - TUI auto-start infra services when running 
examples
    
    Co-Authored-By: Claude <[email protected]>
    
    ---------
    
    Co-authored-by: Claude Opus 4.6 <[email protected]>
---
 .../camel/dsl/jbang/core/common/ExampleHelper.java |   9 +
 .../examples/camel-jbang-example-catalog.json      |  29 +-
 .../dsl/jbang/core/commands/tui/ActionsPopup.java  | 161 ++++++++--
 .../dsl/jbang/core/commands/tui/CamelMonitor.java  | 339 ++++++++++-----------
 .../jbang/core/commands/tui/MonitorContext.java    |   3 +-
 5 files changed, 328 insertions(+), 213 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ExampleHelper.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ExampleHelper.java
index faf401146db3..5e934d366d66 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ExampleHelper.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ExampleHelper.java
@@ -129,6 +129,15 @@ public final class ExampleHelper {
         return citrus != null && citrus;
     }
 
+    @SuppressWarnings("unchecked")
+    public static List<String> getInfraServices(JsonObject entry) {
+        Collection<String> services = (Collection<String>) 
entry.get("infraServices");
+        if (services == null) {
+            return List.of();
+        }
+        return new ArrayList<>(services);
+    }
+
     @SuppressWarnings("unchecked")
     public static List<String> getFiles(JsonObject entry) {
         Collection<String> files = (Collection<String>) entry.get("files");
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json
 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json
index d88e50b1dc0f..10c4a6e65692 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/examples/camel-jbang-example-catalog.json
@@ -17,6 +17,9 @@
             "application.properties",
             "consumer.camel.yaml",
             "producer.camel.yaml"
+        ],
+        "infraServices": [
+            "artemis"
         ]
     },
     {
@@ -120,6 +123,10 @@
             "compose.yaml",
             "docling-langchain4j-rag.yaml",
             "sample.md"
+        ],
+        "infraServices": [
+            "docling",
+            "ollama"
         ]
     },
     {
@@ -144,6 +151,10 @@
             "examples/semiconductor-sector-analysis.pdf",
             "examples/tesla-q3-2024.pdf",
             "financial-doc-analyzer.yaml"
+        ],
+        "infraServices": [
+            "docling",
+            "ollama"
         ]
     },
     {
@@ -165,6 +176,10 @@
             "compose.yaml",
             "ftp.camel.yaml",
             "jbang.properties"
+        ],
+        "infraServices": [
+            "artemis",
+            "ftp"
         ]
     },
     {
@@ -204,6 +219,9 @@
             "README.md",
             "application.properties",
             "rest-api.camel.yaml"
+        ],
+        "infraServices": [
+            "keycloak"
         ]
     },
     {
@@ -224,6 +242,9 @@
             "README.md",
             "application.properties",
             "rest-api.camel.yaml"
+        ],
+        "infraServices": [
+            "keycloak"
         ]
     },
     {
@@ -241,7 +262,7 @@
         "hasCitrusTests": false,
         "files": [
             "README.md",
-            "message-size.camel.yaml"
+            "orders.camel.yaml"
         ]
     },
     {
@@ -264,6 +285,9 @@
             "infra/mosquitto.conf",
             "mqtt.camel.yaml",
             "start.sh"
+        ],
+        "infraServices": [
+            "mosquitto"
         ]
     },
     {
@@ -435,6 +459,9 @@
             "application.properties",
             "compose.yaml",
             "sql.camel.yaml"
+        ],
+        "infraServices": [
+            "postgres"
         ]
     },
     {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
index e448c3cc73f5..f737a9af1e73 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/ActionsPopup.java
@@ -18,6 +18,7 @@ package org.apache.camel.dsl.jbang.core.commands.tui;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -142,6 +143,7 @@ class ActionsPopup {
     private ScheduledExecutorService scheduler;
 
     private final List<PendingLaunch> pendingLaunches = new ArrayList<>();
+    private DeferredExampleLaunch deferredLaunch;
     private String launchNotification;
     private boolean launchNotificationError;
     private long launchNotificationExpiry;
@@ -650,6 +652,7 @@ class ActionsPopup {
 
     void tick(long now) {
         monitorPendingLaunches(now);
+        checkDeferredLaunch(now);
         if (launchNotification != null && now > launchNotificationExpiry) {
             launchNotification = null;
         }
@@ -763,8 +766,10 @@ class ActionsPopup {
             boolean docker = ExampleHelper.requiresDocker(ex);
             boolean bundled = ExampleHelper.isBundled(ex);
             boolean citrus = ExampleHelper.hasCitrusTests(ex);
+            boolean infra = !ExampleHelper.getInfraServices(ex).isEmpty();
 
-            String icons = (bundled ? "📦" : "🌐") + (docker ? "🐳" : "  ") + 
(citrus ? "🍋" : "  ");
+            String icons = (bundled ? "📦" : "🌐") + (docker ? "🐳" : "  ")
+                           + (infra ? "🔧" : "  ") + (citrus ? "🍋" : "  ");
             int nameCol = Math.min(30, width / 3);
             String padded = String.format("%-" + nameCol + "s", 
TuiHelper.truncate(name, nameCol));
             String prefix = " " + icons + " " + padded + " ";
@@ -785,7 +790,7 @@ class ActionsPopup {
             }
         }
         items.add(ListItem.from(""));
-        items.add(ListItem.from(" 📦 = bundled (offline)  🌐 = online (GitHub)  
🐳 = Docker  🍋 = Citrus tests")
+        items.add(ListItem.from(" 📦 = bundled  🌐 = online  🐳 = Docker  🔧 = 
infra services  🍋 = Citrus tests")
                 .style(Style.EMPTY.dim()));
         return items;
     }
@@ -1159,28 +1164,18 @@ class ActionsPopup {
         }
         List<String> extraArgs = runOptionsForm.buildArgs();
         runOptionsForm.close();
-        try {
-            List<String> cmd = new 
ArrayList<>(LauncherHelper.getCamelCommand());
-            cmd.add("run");
-            cmd.add("--example=" + exampleName);
-            cmd.add("--logging-color=true");
-            cmd.addAll(extraArgs);
-            Path outputFile = Files.createTempFile("camel-example-", ".log");
-            outputFile.toFile().deleteOnExit();
-            ProcessBuilder pb = new ProcessBuilder(cmd);
-            pb.redirectErrorStream(true);
-            pb.redirectOutput(outputFile.toFile());
-            Process process = pb.start();
-            pendingLaunches.add(new PendingLaunch(displayName, process, 
outputFile, System.currentTimeMillis()));
-            pendingAutoSelect = displayName;
-            launchNotification = "Starting: " + displayName;
-            launchNotificationError = false;
-            launchNotificationExpiry = System.currentTimeMillis() + 5000;
-        } catch (Exception e) {
-            launchNotification = "Failed to start: " + exampleName + " - " + 
e.getMessage();
-            launchNotificationError = true;
-            launchNotificationExpiry = System.currentTimeMillis() + 10000;
+
+        List<String> missing = findMissingInfraServices(selectedExample);
+        if (!missing.isEmpty()) {
+            if (!isContainerRuntimeAvailable()) {
+                setNotification("Docker/Podman required for infra services. 
Run Doctor for details", true);
+                return;
+            }
+            startMissingInfraAndDeferExample(missing, exampleName, 
displayName, extraArgs);
+            return;
         }
+
+        doLaunchExample(exampleName, displayName, extraArgs);
     }
 
     // ---- Example Browser Navigation ----
@@ -1317,6 +1312,10 @@ class ActionsPopup {
 
     private void openInfraBrowser() {
         showActionsMenu = false;
+        if (!isContainerRuntimeAvailable()) {
+            setNotification("Docker or Podman is not running (use F2 → Run 
Doctor to check)", true);
+            return;
+        }
         if (infraCatalog == null) {
             infraCatalog = loadInfraCatalog();
         }
@@ -1636,26 +1635,103 @@ class ActionsPopup {
         }
         String exampleName = example.getStringOrDefault("name", "");
         showExampleBrowser = false;
+
+        List<String> missing = findMissingInfraServices(example);
+        if (!missing.isEmpty()) {
+            if (!isContainerRuntimeAvailable()) {
+                setNotification("Docker/Podman required for infra services. 
Run Doctor for details", true);
+                return;
+            }
+            startMissingInfraAndDeferExample(missing, exampleName, 
exampleName, List.of());
+            return;
+        }
+
+        doLaunchExample(exampleName, exampleName, List.of());
+    }
+
+    private void doLaunchExample(String exampleName, String displayName, 
List<String> extraArgs) {
         try {
             List<String> cmd = new 
ArrayList<>(LauncherHelper.getCamelCommand());
             cmd.add("run");
             cmd.add("--example=" + exampleName);
             cmd.add("--logging-color=true");
+            cmd.addAll(extraArgs);
             Path outputFile = Files.createTempFile("camel-example-", ".log");
             outputFile.toFile().deleteOnExit();
             ProcessBuilder pb = new ProcessBuilder(cmd);
             pb.redirectErrorStream(true);
             pb.redirectOutput(outputFile.toFile());
             Process process = pb.start();
-            pendingLaunches.add(new PendingLaunch(exampleName, process, 
outputFile, System.currentTimeMillis()));
-            pendingAutoSelect = exampleName;
-            launchNotification = "Starting: " + exampleName;
-            launchNotificationError = false;
-            launchNotificationExpiry = System.currentTimeMillis() + 5000;
+            pendingLaunches.add(new PendingLaunch(displayName, process, 
outputFile, System.currentTimeMillis()));
+            pendingAutoSelect = displayName;
+            setNotification("Starting: " + displayName, false);
         } catch (Exception e) {
-            launchNotification = "Failed to start: " + exampleName + " - " + 
e.getMessage();
-            launchNotificationError = true;
-            launchNotificationExpiry = System.currentTimeMillis() + 10000;
+            setNotification("Failed to start: " + exampleName + " - " + 
e.getMessage(), true);
+        }
+    }
+
+    private List<String> findMissingInfraServices(JsonObject example) {
+        List<String> required = ExampleHelper.getInfraServices(example);
+        if (required.isEmpty()) {
+            return List.of();
+        }
+        Set<String> runningAliases = infraServices.get().stream()
+                .filter(i -> i.alive)
+                .map(i -> i.alias)
+                .collect(Collectors.toSet());
+        List<String> missing = new ArrayList<>();
+        for (String alias : required) {
+            if (!runningAliases.contains(alias)) {
+                missing.add(alias);
+            }
+        }
+        return missing;
+    }
+
+    private void startMissingInfraAndDeferExample(
+            List<String> missingInfra, String exampleName, String displayName, 
List<String> extraArgs) {
+        for (String alias : missingInfra) {
+            try {
+                List<String> cmd = new 
ArrayList<>(LauncherHelper.getCamelCommand());
+                cmd.add("infra");
+                cmd.add("run");
+                cmd.add(alias);
+                cmd.add("--background");
+                Path outputFile = Files.createTempFile("camel-infra-", ".log");
+                outputFile.toFile().deleteOnExit();
+                ProcessBuilder pb = new ProcessBuilder(cmd);
+                pb.redirectErrorStream(true);
+                pb.redirectOutput(outputFile.toFile());
+                Process process = pb.start();
+                pendingLaunches.add(new PendingLaunch(alias, process, 
outputFile, System.currentTimeMillis()));
+            } catch (Exception e) {
+                setNotification("Failed to start infra: " + alias + " - " + 
e.getMessage(), true);
+                return;
+            }
+        }
+        deferredLaunch = new DeferredExampleLaunch(
+                exampleName, displayName, extraArgs, missingInfra, 
System.currentTimeMillis());
+        infraCatalog = null;
+        String infraList = String.join(", ", missingInfra);
+        setNotification("Starting infra: " + infraList + " → then: " + 
displayName, false);
+    }
+
+    private void checkDeferredLaunch(long now) {
+        if (deferredLaunch == null) {
+            return;
+        }
+        Set<String> runningAliases = infraServices.get().stream()
+                .filter(i -> i.alive)
+                .map(i -> i.alias)
+                .collect(Collectors.toSet());
+        boolean allReady = 
runningAliases.containsAll(deferredLaunch.requiredInfra);
+        if (allReady) {
+            DeferredExampleLaunch dl = deferredLaunch;
+            deferredLaunch = null;
+            doLaunchExample(dl.exampleName, dl.displayName, dl.extraArgs);
+        } else if (now - deferredLaunch.startTime > 120_000) {
+            deferredLaunch = null;
+            setNotification("Timeout waiting for infra services to start", 
true);
         }
     }
 
@@ -1730,9 +1806,32 @@ class ActionsPopup {
         return lines;
     }
 
+    private static boolean isContainerRuntimeAvailable() {
+        for (String cmd : new String[] { "docker", "podman" }) {
+            try {
+                Process p = new ProcessBuilder(cmd, "info")
+                        .redirectErrorStream(true)
+                        .start();
+                p.getInputStream().transferTo(OutputStream.nullOutputStream());
+                int exit = p.waitFor();
+                if (exit == 0) {
+                    return true;
+                }
+            } catch (Exception e) {
+                // not found, try next
+            }
+        }
+        return false;
+    }
+
     record InfraServiceEntry(String alias, String description, List<String> 
implementations, boolean running) {
     }
 
     private record PendingLaunch(String name, Process process, Path 
outputFile, long startTime) {
     }
+
+    private record DeferredExampleLaunch(
+            String exampleName, String displayName, List<String> extraArgs,
+            List<String> requiredInfra, long startTime) {
+    }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
index 9e5e8f376e0e..76841a3e03f0 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java
@@ -153,8 +153,7 @@ public class CamelMonitor extends CamelCommand {
     private final Map<String, VanishingInfo> vanishing = new 
ConcurrentHashMap<>();
     private final Map<String, VanishingInfraInfo> vanishingInfra = new 
ConcurrentHashMap<>();
     private final TableState overviewTableState = new TableState();
-    private final TableState infraTableState = new TableState();
-    // infraTableFocused is stored on ctx (MonitorContext) so tabs can access 
it
+    private int overviewDividerIndex = -1;
     private final TabsState tabsState = new TabsState(TAB_OVERVIEW);
 
     // Sparkline: throughput history per PID (one point per second)
@@ -424,11 +423,6 @@ public class CamelMonitor extends CamelCommand {
                     tabsState.select(TAB_OVERVIEW);
                     return true;
                 }
-                if (ctx.infraTableFocused) {
-                    ctx.infraTableFocused = false;
-                    syncSelectedPidFromOverview();
-                    return true;
-                }
                 if (ctx.selectedPid != null) {
                     ctx.selectedPid = null;
                     ctx.lastSelectedName = null;
@@ -597,25 +591,8 @@ public class CamelMonitor extends CamelCommand {
                 chartMode = (chartMode + 1) % 3;
                 return true;
             }
-            // Overview tab: toggle focus between integrations and infra tables
-            if (tab == TAB_OVERVIEW && ke.isChar('i') && 
!infraData.get().isEmpty()) {
-                ctx.infraTableFocused = !ctx.infraTableFocused;
-                if (ctx.infraTableFocused) {
-                    if (infraTableState.selected() == null) {
-                        infraTableState.select(0);
-                    }
-                    syncSelectedPidFromInfra();
-                } else {
-                    List<IntegrationInfo> intInfos = sortedOverviewInfos();
-                    if (!intInfos.isEmpty() && overviewTableState.selected() 
== null) {
-                        overviewTableState.select(0);
-                    }
-                    syncSelectedPidFromOverview();
-                }
-                return true;
-            }
             // Overview tab: start/stop all routes for selected integration 
(not infra)
-            if (tab == TAB_OVERVIEW && ke.isChar('p') && ctx.selectedPid != 
null && !ctx.infraTableFocused) {
+            if (tab == TAB_OVERVIEW && ke.isChar('p') && ctx.selectedPid != 
null && !isInfraSelected()) {
                 IntegrationInfo selInfo = findSelectedIntegration();
                 if (selInfo != null) {
                     String cmd = selInfo.routeStarted > 0 ? "stop" : "start";
@@ -634,7 +611,7 @@ public class CamelMonitor extends CamelCommand {
                 return true;
             }
             // Overview tab: cold restart (stop + re-launch) for selected 
integration
-            if (tab == TAB_OVERVIEW && ke.isChar('r') && ctx.selectedPid != 
null && !ctx.infraTableFocused) {
+            if (tab == TAB_OVERVIEW && ke.isChar('r') && ctx.selectedPid != 
null && !isInfraSelected()) {
                 restartSelectedProcess();
                 return true;
             }
@@ -787,30 +764,28 @@ public class CamelMonitor extends CamelCommand {
             }
             ctx.selectedPid = null;
         }
-        if (ctx.infraTableFocused) {
-            List<InfraInfo> infras = infraData.get();
-            Integer sel = infraTableState.selected();
-            if (sel != null && sel >= 0 && sel < infras.size()) {
-                ctx.selectedPid = infras.get(sel).pid;
-            }
-        } else {
-            List<IntegrationInfo> infos = sortedOverviewInfos();
-            Integer sel = overviewTableState.selected();
-            if (sel != null && sel >= 0 && sel < infos.size()) {
-                ctx.selectedPid = infos.get(sel).pid;
-            } else if (infos.size() == 1) {
-                ctx.selectedPid = infos.get(0).pid;
+        List<IntegrationInfo> infos = sortedOverviewInfos();
+        List<InfraInfo> infras = infraData.get();
+        Integer sel = overviewTableState.selected();
+        if (sel != null && sel >= 0) {
+            String pid = overviewIndexToPid(infos, infras, sel);
+            if (pid != null) {
+                ctx.selectedPid = pid;
             }
+        } else if (infos.size() == 1) {
+            ctx.selectedPid = infos.get(0).pid;
         }
     }
 
-    private void syncSelectedPidFromOverview() {
+    private void syncSelectedPid() {
         List<IntegrationInfo> infos = sortedOverviewInfos();
+        List<InfraInfo> infras = infraData.get();
         Integer sel = overviewTableState.selected();
         String newPid = null;
-        if (sel != null && sel >= 0 && sel < infos.size()) {
-            newPid = infos.get(sel).pid;
-        } else if (infos.size() == 1) {
+        if (sel != null && sel >= 0) {
+            newPid = overviewIndexToPid(infos, infras, sel);
+        }
+        if (newPid == null && infos.size() == 1) {
             newPid = infos.get(0).pid;
         }
         if (newPid != null && !newPid.equals(ctx.selectedPid)) {
@@ -820,18 +795,15 @@ public class CamelMonitor extends CamelCommand {
         }
     }
 
-    private void syncSelectedPidFromInfra() {
-        List<InfraInfo> infras = infraData.get();
-        Integer sel = infraTableState.selected();
-        String newPid = null;
-        if (sel != null && sel >= 0 && sel < infras.size()) {
-            newPid = infras.get(sel).pid;
+    private String overviewIndexToPid(List<IntegrationInfo> infos, 
List<InfraInfo> infras, int index) {
+        if (index < infos.size()) {
+            return infos.get(index).pid;
         }
-        if (newPid != null && !newPid.equals(ctx.selectedPid)) {
-            ctx.selectedPid = newPid;
-            ctx.lastSelectedName = null;
-            resetIntegrationTabState();
+        int infraIndex = index - infos.size() - (overviewDividerIndex >= 0 ? 1 
: 0);
+        if (infraIndex >= 0 && infraIndex < infras.size()) {
+            return infras.get(infraIndex).pid;
         }
+        return null;
     }
 
     private void resetIntegrationTabState() {
@@ -846,13 +818,13 @@ public class CamelMonitor extends CamelCommand {
         if (tab != null) {
             tab.navigateUp();
         } else {
-            if (ctx.infraTableFocused) {
-                infraTableState.selectPrevious();
-                syncSelectedPidFromInfra();
-            } else {
+            overviewTableState.selectPrevious();
+            // Skip the divider row
+            Integer sel = overviewTableState.selected();
+            if (sel != null && overviewDividerIndex >= 0 && sel == 
overviewDividerIndex) {
                 overviewTableState.selectPrevious();
-                syncSelectedPidFromOverview();
             }
+            syncSelectedPid();
         }
     }
 
@@ -861,16 +833,23 @@ public class CamelMonitor extends CamelCommand {
         if (tab != null) {
             tab.navigateDown();
         } else {
-            if (ctx.infraTableFocused) {
-                infraTableState.selectNext(infraData.get().size());
-                syncSelectedPidFromInfra();
-            } else {
-                overviewTableState.selectNext(sortedOverviewInfos().size());
-                syncSelectedPidFromOverview();
+            int totalRows = overviewTotalRows();
+            overviewTableState.selectNext(totalRows);
+            // Skip the divider row
+            Integer sel = overviewTableState.selected();
+            if (sel != null && overviewDividerIndex >= 0 && sel == 
overviewDividerIndex) {
+                overviewTableState.selectNext(totalRows);
             }
+            syncSelectedPid();
         }
     }
 
+    private int overviewTotalRows() {
+        int integrations = sortedOverviewInfos().size();
+        int infra = infraData.get().size();
+        return integrations + (infra > 0 ? 1 : 0) + infra;
+    }
+
     // ---- Rendering ----
 
     private void render(Frame frame) {
@@ -1134,27 +1113,31 @@ public class CamelMonitor extends CamelCommand {
         List<IntegrationInfo> infos = sortedOverviewInfos();
         List<InfraInfo> infraInfos = infraData.get();
 
+        // Build unified row list: integrations, then divider, then infra
+        int integrationCount = infos.size();
+        int infraCount = infraInfos.size();
+        overviewDividerIndex = infraCount > 0 ? integrationCount : -1;
+
         // Keep the table selection index tracking the same PID across sort 
changes and data refreshes
-        if (ctx.selectedPid != null && !ctx.infraTableFocused) {
+        if (ctx.selectedPid != null) {
             for (int i = 0; i < infos.size(); i++) {
                 if (ctx.selectedPid.equals(infos.get(i).pid)) {
                     overviewTableState.select(i);
                     break;
                 }
             }
-        }
-        if (ctx.selectedPid != null && ctx.infraTableFocused) {
             for (int i = 0; i < infraInfos.size(); i++) {
                 if (ctx.selectedPid.equals(infraInfos.get(i).pid)) {
-                    infraTableState.select(i);
+                    int tableIndex = integrationCount + (overviewDividerIndex 
>= 0 ? 1 : 0) + i;
+                    overviewTableState.select(tableIndex);
                     break;
                 }
             }
         }
 
-        // Split: one table (integrations or infra, toggled by 'i') + chart or 
info panel
-        boolean hasSparkline = chartMode != CHART_OFF && 
!throughputHistory.isEmpty() && !ctx.infraTableFocused;
-        boolean showInfoPanel = ctx.infraTableFocused && findSelectedInfra() 
!= null && !hasSparkline;
+        // Split: table + chart or info panel
+        boolean hasSparkline = chartMode != CHART_OFF && 
!throughputHistory.isEmpty() && !isInfraSelected();
+        boolean showInfoPanel = isInfraSelected() && findSelectedInfra() != 
null && !hasSparkline;
         List<Constraint> constraints = new ArrayList<>();
         constraints.add(Constraint.fill());
         if (hasSparkline) {
@@ -1175,9 +1158,10 @@ public class CamelMonitor extends CamelCommand {
                 int gray = (int) (100 * fade);
                 Style dimStyle = Style.EMPTY.fg(Color.indexed(232 + 
Math.min(gray / 4, 23)));
 
+                String vanishName = "\ud83d\udc2b " + (info.name != null ? 
info.name : "");
                 rows.add(Row.from(
                         Cell.from(Span.styled(info.pid, dimStyle)),
-                        Cell.from(Span.styled(info.name != null ? info.name : 
"", dimStyle)),
+                        Cell.from(Span.styled(vanishName, dimStyle)),
                         Cell.from(Span.styled("", dimStyle)),
                         Cell.from(Span.styled("", dimStyle)),
                         Cell.from(Span.styled("\u2716 Stopped", 
Style.EMPTY.fg(Color.LIGHT_RED).dim())),
@@ -1199,11 +1183,12 @@ public class CamelMonitor extends CamelCommand {
 
                 String sinceLastDisplay = formatSinceLast(info);
 
+                String nameText = "🐫 " + (info.name != null ? info.name : "");
                 Line nameLine = info.devMode
                         ? Line.from(
-                                Span.styled(info.name != null ? info.name : 
"", Style.EMPTY.fg(Color.CYAN)),
+                                Span.styled(nameText, 
Style.EMPTY.fg(Color.CYAN)),
                                 Span.styled(" [dev]", 
Style.EMPTY.fg(Color.YELLOW).dim()))
-                        : Line.from(Span.styled(info.name != null ? info.name 
: "", Style.EMPTY.fg(Color.CYAN)));
+                        : Line.from(Span.styled(nameText, 
Style.EMPTY.fg(Color.CYAN)));
                 rows.add(Row.from(
                         Cell.from(info.pid),
                         Cell.from(nameLine),
@@ -1234,36 +1219,94 @@ public class CamelMonitor extends CamelCommand {
                 rightCell("INFLIGHT", 8, Style.EMPTY.bold()),
                 Cell.from(Span.styled("SINCE-LAST", Style.EMPTY.bold())));
 
-        if (ctx.infraTableFocused) {
-            // Show infra table only
-            renderInfraTable(frame, chunks.get(0), infraInfos);
-        } else {
-            // Show integrations table only
-            Style integrationHighlight = 
Style.EMPTY.fg(Color.WHITE).bold().onBlue();
-            Table table = Table.builder()
-                    .rows(rows)
-                    .header(header)
-                    .widths(
-                            Constraint.length(8),
-                            Constraint.fill(),
-                            Constraint.length(16),
-                            Constraint.length(5),
-                            Constraint.length(10),
-                            Constraint.length(8),
-                            Constraint.length(7),
-                            Constraint.length(8),
-                            Constraint.length(8),
-                            Constraint.length(6),
-                            Constraint.length(8),
-                            Constraint.length(12))
-                    .highlightStyle(integrationHighlight)
-                    .highlightSpacing(Table.HighlightSpacing.ALWAYS)
-                    
.block(Block.builder().borderType(BorderType.ROUNDED).title(" Integrations 
").build())
-                    .build();
+        // Divider row between integrations and infra services
+        if (overviewDividerIndex >= 0) {
+            rows.add(Row.from(
+                    Cell.from(""),
+                    Cell.from(Span.styled("─── Infra Services ───", 
Style.EMPTY.dim())),
+                    Cell.from(""), Cell.from(""), Cell.from(""), Cell.from(""),
+                    Cell.from(""), Cell.from(""), Cell.from(""), Cell.from(""),
+                    Cell.from(""), Cell.from("")));
+        }
 
-            frame.renderStatefulWidget(table, chunks.get(0), 
overviewTableState);
+        // Infra rows adapted to 12-column layout
+        for (InfraInfo info : infraInfos) {
+            if (info.vanishing) {
+                long elapsed = System.currentTimeMillis() - info.vanishStart;
+                float fade = 1.0f - Math.min(1.0f, (float) elapsed / 
VANISH_DURATION_MS);
+                int gray = (int) (100 * fade);
+                Style dimStyle = Style.EMPTY.fg(Color.indexed(232 + 
Math.min(gray / 4, 23)));
+                String vanishAlias = "🔧  " + info.alias;
+                rows.add(Row.from(
+                        Cell.from(Span.styled(info.pid, dimStyle)),
+                        Cell.from(Span.styled(vanishAlias, dimStyle)),
+                        Cell.from(Span.styled("", dimStyle)),
+                        Cell.from(Span.styled("", dimStyle)),
+                        Cell.from(Span.styled("✖ Stopped", 
Style.EMPTY.fg(Color.LIGHT_RED).dim())),
+                        Cell.from(Span.styled("", dimStyle)),
+                        Cell.from(Span.styled("", dimStyle)),
+                        Cell.from(Span.styled("", dimStyle)),
+                        Cell.from(Span.styled("", dimStyle)),
+                        Cell.from(Span.styled("", dimStyle)),
+                        Cell.from(Span.styled("", dimStyle)),
+                        Cell.from(Span.styled("", dimStyle))));
+            } else {
+                Style statusStyle = info.alive ? Style.EMPTY.fg(Color.GREEN) : 
Style.EMPTY.fg(Color.LIGHT_RED);
+                String statusText = info.alive ? "Running" : "Stopped";
+                String port = objToString(info.properties.get("getPort"));
+                String host = objToString(info.properties.get("getHost"));
+                if (host.isEmpty()) {
+                    host = objToString(info.properties.get("getHostname"));
+                }
+                String portHost = "";
+                if (!port.isEmpty() && !host.isEmpty()) {
+                    portHost = host + ":" + port;
+                } else if (!port.isEmpty()) {
+                    portHost = ":" + port;
+                } else if (!host.isEmpty()) {
+                    portHost = host;
+                }
+                String infraAlias = "🔧  " + info.alias;
+                rows.add(Row.from(
+                        Cell.from(info.pid),
+                        Cell.from(Span.styled(infraAlias, 
Style.EMPTY.fg(Color.MAGENTA))),
+                        Cell.from(""),
+                        Cell.from(""),
+                        Cell.from(Span.styled(statusText, statusStyle)),
+                        Cell.from(""),
+                        Cell.from(""),
+                        Cell.from(""),
+                        Cell.from(""),
+                        Cell.from(""),
+                        Cell.from(""),
+                        Cell.from(portHost)));
+            }
         }
 
+        Style overviewHighlight = Style.EMPTY.fg(Color.WHITE).bold().onBlue();
+        Table table = Table.builder()
+                .rows(rows)
+                .header(header)
+                .widths(
+                        Constraint.length(8),
+                        Constraint.fill(),
+                        Constraint.length(16),
+                        Constraint.length(5),
+                        Constraint.length(10),
+                        Constraint.length(8),
+                        Constraint.length(7),
+                        Constraint.length(8),
+                        Constraint.length(8),
+                        Constraint.length(6),
+                        Constraint.length(8),
+                        Constraint.length(12))
+                .highlightStyle(overviewHighlight)
+                .highlightSpacing(Table.HighlightSpacing.ALWAYS)
+                .block(Block.builder().borderType(BorderType.ROUNDED).title(" 
Overview ").build())
+                .build();
+
+        frame.renderStatefulWidget(table, chunks.get(0), overviewTableState);
+
         // Split green/red throughput bar chart with Y and X axes
         if (hasSparkline && chunks.size() > 1) {
             Rect chartTotalArea = chunks.get(chunks.size() - 1);
@@ -1416,7 +1459,7 @@ public class CamelMonitor extends CamelCommand {
 
     private void renderOverviewInfoPanel(Frame frame, Rect area) {
         // Check if an infra service is selected — show connection details 
instead
-        InfraInfo infraSel = ctx.infraTableFocused ? findSelectedInfra() : 
null;
+        InfraInfo infraSel = findSelectedInfra();
         if (infraSel != null) {
             renderInfraInfoPanel(frame, area, infraSel);
             return;
@@ -1524,64 +1567,6 @@ public class CamelMonitor extends CamelCommand {
         frame.renderWidget(Paragraph.builder().text(Text.from(lines)).build(), 
inner);
     }
 
-    // ---- Infra table (overview sub-section) ----
-
-    private void renderInfraTable(Frame frame, Rect area, List<InfraInfo> 
infraInfos) {
-        List<Row> infraRows = new ArrayList<>();
-        for (InfraInfo info : infraInfos) {
-            if (info.vanishing) {
-                long elapsed = System.currentTimeMillis() - info.vanishStart;
-                float fade = 1.0f - Math.min(1.0f, (float) elapsed / 
VANISH_DURATION_MS);
-                int gray = (int) (100 * fade);
-                Style dimStyle = Style.EMPTY.fg(Color.indexed(232 + 
Math.min(gray / 4, 23)));
-                infraRows.add(Row.from(
-                        Cell.from(Span.styled(info.pid, dimStyle)),
-                        Cell.from(Span.styled(info.alias, dimStyle)),
-                        Cell.from(Span.styled("✖ Stopped", 
Style.EMPTY.fg(Color.LIGHT_RED).dim())),
-                        Cell.from(Span.styled("", dimStyle)),
-                        Cell.from(Span.styled("", dimStyle))));
-            } else {
-                Style statusStyle = info.alive ? Style.EMPTY.fg(Color.GREEN) : 
Style.EMPTY.fg(Color.LIGHT_RED);
-                String statusText = info.alive ? "Running" : "Stopped";
-                String port = objToString(info.properties.get("getPort"));
-                String host = objToString(info.properties.get("getHost"));
-                if (host.isEmpty()) {
-                    host = objToString(info.properties.get("getHostname"));
-                }
-                infraRows.add(Row.from(
-                        Cell.from(info.pid),
-                        Cell.from(Span.styled(info.alias, 
Style.EMPTY.fg(Color.MAGENTA))),
-                        Cell.from(Span.styled(statusText, statusStyle)),
-                        Cell.from(port),
-                        Cell.from(host)));
-            }
-        }
-
-        Row infraHeader = Row.from(
-                Cell.from(Span.styled("PID", Style.EMPTY.bold())),
-                Cell.from(Span.styled("SERVICE", Style.EMPTY.bold())),
-                Cell.from(Span.styled("STATUS", Style.EMPTY.bold())),
-                Cell.from(Span.styled("PORT", Style.EMPTY.bold())),
-                Cell.from(Span.styled("HOST", Style.EMPTY.bold())));
-
-        Style infraHighlight = Style.EMPTY.fg(Color.WHITE).bold().onBlue();
-        Table infraTable = Table.builder()
-                .rows(infraRows)
-                .header(infraHeader)
-                .widths(
-                        Constraint.length(8),
-                        Constraint.fill(),
-                        Constraint.length(10),
-                        Constraint.length(8),
-                        Constraint.length(20))
-                .highlightStyle(infraHighlight)
-                .highlightSpacing(Table.HighlightSpacing.ALWAYS)
-                .block(Block.builder().borderType(BorderType.ROUNDED).title(" 
Infrastructure ").build())
-                .build();
-
-        frame.renderStatefulWidget(infraTable, area, infraTableState);
-    }
-
     private void renderInfraInfoPanel(Frame frame, Rect area, InfraInfo infra) 
{
         Block infoBlock = 
Block.builder().borderType(BorderType.ROUNDED).build();
         frame.renderWidget(infoBlock, area);
@@ -1655,7 +1640,7 @@ public class CamelMonitor extends CamelCommand {
         } catch (NumberFormatException e) {
             return;
         }
-        if (ctx.infraTableFocused) {
+        if (isInfraSelected()) {
             InfraInfo infra = findSelectedInfra();
             if (infra != null) {
                 Path camelDir = CommandLineHelper.getCamelDir();
@@ -1686,7 +1671,7 @@ public class CamelMonitor extends CamelCommand {
     }
 
     private void restartSelectedProcess() {
-        if (ctx.selectedPid == null || ctx.infraTableFocused) {
+        if (ctx.selectedPid == null || isInfraSelected()) {
             return;
         }
         long pid;
@@ -1972,13 +1957,10 @@ public class CamelMonitor extends CamelCommand {
         hint(spans, "q", "quit");
         hint(spans, "F2", "actions");
         if (ctx.selectedPid != null) {
-            hint(spans, "Esc", ctx.infraTableFocused ? "integrations" : 
"unselect");
+            hint(spans, "Esc", "unselect");
         }
         hint(spans, "↑↓", "navigate");
-        if (!infraData.get().isEmpty()) {
-            hint(spans, "i", ctx.infraTableFocused ? "integrations" : "infra");
-        }
-        if (!ctx.infraTableFocused) {
+        if (!isInfraSelected()) {
             hint(spans, "s", "sort");
             hint(spans, "a", "chart " + switch (chartMode) {
                 case CHART_ALL -> "[all]";
@@ -1986,14 +1968,14 @@ public class CamelMonitor extends CamelCommand {
                 default -> "[off]";
             });
         }
-        if (ctx.selectedPid != null && !ctx.infraTableFocused) {
+        if (ctx.selectedPid != null && !isInfraSelected()) {
             IntegrationInfo selInfo = findSelectedIntegration();
             if (selInfo != null) {
                 hint(spans, "p", selInfo.routeStarted > 0 ? "stop routes" : 
"start routes");
             }
         }
         if (ctx.selectedPid != null) {
-            if (!ctx.infraTableFocused) {
+            if (!isInfraSelected()) {
                 hint(spans, "r", "restart");
             }
             hint(spans, "x", "stop");
@@ -2103,7 +2085,7 @@ public class CamelMonitor extends CamelCommand {
             data.set(infos);
 
             // Clear stale selection when the selected integration is gone
-            if (ctx.selectedPid != null && !ctx.infraTableFocused) {
+            if (ctx.selectedPid != null && !isInfraSelected()) {
                 boolean stillAlive = infos.stream()
                         .anyMatch(i -> ctx.selectedPid.equals(i.pid) && 
!i.vanishing);
                 if (!stillAlive) {
@@ -2124,7 +2106,6 @@ public class CamelMonitor extends CamelCommand {
                 for (IntegrationInfo info : infos) {
                     if (!info.vanishing && 
autoSelect.equalsIgnoreCase(info.name)) {
                         ctx.selectedPid = info.pid;
-                        ctx.infraTableFocused = false;
                         ctx.lastSelectedName = null;
                         actionsPopup.clearPendingAutoSelect();
                         break;
@@ -2133,7 +2114,7 @@ public class CamelMonitor extends CamelCommand {
             }
 
             // Auto-reselect by remembered name when the integration restarts
-            if (ctx.selectedPid == null && ctx.lastSelectedName != null && 
!ctx.infraTableFocused) {
+            if (ctx.selectedPid == null && ctx.lastSelectedName != null && 
!isInfraSelected()) {
                 for (IntegrationInfo info : infos) {
                     if (!info.vanishing && 
ctx.lastSelectedName.equalsIgnoreCase(info.name)) {
                         ctx.selectedPid = info.pid;
@@ -2146,14 +2127,15 @@ public class CamelMonitor extends CamelCommand {
             // Discover running infra services
             refreshInfraData();
 
-            // Auto-focus infra table when no active integrations exist
-            if (!ctx.infraTableFocused && !infraData.get().isEmpty()
+            // Auto-select first infra service when no active integrations 
exist
+            if (ctx.selectedPid == null && !infraData.get().isEmpty()
                     && infos.stream().noneMatch(i -> !i.vanishing)) {
-                ctx.infraTableFocused = true;
-                if (infraTableState.selected() == null) {
-                    infraTableState.select(0);
+                List<InfraInfo> infras = infraData.get();
+                if (!infras.isEmpty()) {
+                    int firstInfraIndex = infos.size() + (infras.size() > 0 ? 
1 : 0);
+                    overviewTableState.select(firstInfraIndex);
+                    ctx.selectedPid = infras.get(0).pid;
                 }
-                syncSelectedPidFromInfra();
             }
 
             // Refresh log data only when the Log tab is visible
@@ -3637,7 +3619,6 @@ public class CamelMonitor extends CamelCommand {
             if (nameOrPid.equals(info.pid)
                     || (info.name != null && 
info.name.equalsIgnoreCase(nameOrPid))) {
                 ctx.selectedPid = info.pid;
-                ctx.infraTableFocused = false;
                 return info.name != null ? info.name : info.pid;
             }
         }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorContext.java
 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorContext.java
index 69105cfbff78..0a1c1d437d97 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorContext.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/MonitorContext.java
@@ -50,7 +50,6 @@ class MonitorContext {
 
     String selectedPid;
     String lastSelectedName;
-    boolean infraTableFocused;
 
     MonitorContext(
                    AtomicReference<List<IntegrationInfo>> data,
@@ -78,7 +77,7 @@ class MonitorContext {
     }
 
     boolean isInfraSelected() {
-        return infraTableFocused && findSelectedInfra() != null;
+        return findSelectedInfra() != null;
     }
 
     String selectedName() {


Reply via email to