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 51a65ad835f1 CAMEL-23637: camel-jbang - TUI add Run Infra Service 
action to F2 menu (#23607)
51a65ad835f1 is described below

commit 51a65ad835f17e42833eb8142db52c269f940db5
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu May 28 20:42:05 2026 +0200

    CAMEL-23637: camel-jbang - TUI add Run Infra Service action to F2 menu 
(#23607)
    
    Co-authored-by: Claude <[email protected]>
---
 .../dsl/jbang/core/commands/tui/ActionsPopup.java  | 435 ++++++++++++++++++++-
 1 file changed, 418 insertions(+), 17 deletions(-)

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 e569c49861fc..e448c3cc73f5 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
@@ -17,11 +17,15 @@
 package org.apache.camel.dsl.jbang.core.commands.tui;
 
 import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.function.Supplier;
@@ -41,15 +45,21 @@ import dev.tamboui.widgets.Clear;
 import dev.tamboui.widgets.block.Block;
 import dev.tamboui.widgets.block.BorderType;
 import dev.tamboui.widgets.block.Title;
+import dev.tamboui.widgets.input.TextInput;
+import dev.tamboui.widgets.input.TextInputState;
 import dev.tamboui.widgets.list.ListItem;
 import dev.tamboui.widgets.list.ListState;
 import dev.tamboui.widgets.list.ListWidget;
 import dev.tamboui.widgets.list.ScrollMode;
 import dev.tamboui.widgets.paragraph.Paragraph;
+import org.apache.camel.catalog.CamelCatalog;
+import org.apache.camel.catalog.DefaultCamelCatalog;
 import org.apache.camel.dsl.jbang.core.common.ExampleHelper;
 import org.apache.camel.dsl.jbang.core.common.LauncherHelper;
 import org.apache.camel.dsl.jbang.core.common.PathUtils;
+import org.apache.camel.util.json.JsonArray;
 import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
 
 import static org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.hint;
 import static 
org.apache.camel.dsl.jbang.core.commands.tui.MonitorContext.hintLast;
@@ -59,27 +69,29 @@ class ActionsPopup {
     // Group 1: User Actions
     private static final int ACTION_SEND_MESSAGE = 0;
     private static final int ACTION_RUN_EXAMPLE = 1;
-    private static final int ACTION_SHOW_DOCS = 2;
+    private static final int ACTION_RUN_INFRA = 2;
+    private static final int ACTION_SHOW_DOCS = 3;
     // Group 2: Diagnostics
-    private static final int ACTION_DOCTOR = 3;
-    private static final int ACTION_CLASSPATH = 4;
-    private static final int ACTION_RESET_STATS = 5;
-    private static final int ACTION_STOP_ALL = 6;
+    private static final int ACTION_DOCTOR = 4;
+    private static final int ACTION_CLASSPATH = 5;
+    private static final int ACTION_RESET_STATS = 6;
+    private static final int ACTION_STOP_ALL = 7;
     // Group 3: Recording & Presentation
-    private static final int ACTION_SCREENSHOT = 7;
-    private static final int ACTION_TAPE_RECORDING = 8;
-    private static final int ACTION_TAPE_INSTRUCTIONS = 9;
-    private static final int ACTION_CAPTION = 10;
-    private static final int ACTION_SHOW_KEYSTROKES = 11;
+    private static final int ACTION_SCREENSHOT = 8;
+    private static final int ACTION_TAPE_RECORDING = 9;
+    private static final int ACTION_TAPE_INSTRUCTIONS = 10;
+    private static final int ACTION_CAPTION = 11;
+    private static final int ACTION_SHOW_KEYSTROKES = 12;
     // Group 4: MCP
-    private static final int ACTION_MCP_INFO = 12;
-    private static final int ACTION_MCP_LOG = 13;
+    private static final int ACTION_MCP_INFO = 13;
+    private static final int ACTION_MCP_LOG = 14;
 
-    private static final int[] GROUP_SIZES = { 3, 4, 5 };
+    private static final int[] GROUP_SIZES = { 4, 4, 5 };
     private static final int MCP_GROUP_SIZE = 2;
 
     private final Supplier<Set<String>> runningNames;
     private final Supplier<List<IntegrationInfo>> integrations;
+    private final Supplier<List<InfraInfo>> infraServices;
     private final Runnable screenshotAction;
     private final Runnable toggleKeystrokes;
     private final Supplier<Boolean> keystrokesEnabled;
@@ -112,6 +124,14 @@ class ActionsPopup {
     private String docTitle;
     private int docScroll;
 
+    private boolean showInfraBrowser;
+    private final ListState infraBrowserState = new ListState();
+    private List<InfraServiceEntry> infraCatalog;
+    private boolean showInfraPortDialog;
+    private InfraServiceEntry selectedInfraService;
+    private int infraImplIndex;
+    private TextInputState infraPortState;
+
     private final McpLogPopup mcpLogPopup = new McpLogPopup();
 
     private final DoctorPopup doctorPopup = new DoctorPopup();
@@ -134,6 +154,7 @@ class ActionsPopup {
                  Runnable toggleTapeRecording, Supplier<Boolean> 
tapeRecordingActive) {
         this.runningNames = runningNames;
         this.integrations = integrations;
+        this.infraServices = infraServices;
         this.captionOverlay = captionOverlay;
         this.screenshotAction = screenshotAction;
         this.toggleKeystrokes = toggleKeystrokes;
@@ -170,11 +191,11 @@ class ActionsPopup {
 
     private int visualActionCount() {
         if (mcpEnabled) {
-            // 3 + 4 + 5 + 2 actions = 14, plus 3 dividers = 17
-            return 14 + 3;
+            // 4 + 4 + 5 + 2 actions = 15, plus 3 dividers = 18
+            return 15 + 3;
         } else {
-            // 3 + 4 + 5 actions = 12, plus 2 dividers = 14
-            return 12 + 2;
+            // 4 + 4 + 5 actions = 13, plus 2 dividers = 15
+            return 13 + 2;
         }
     }
 
@@ -227,11 +248,17 @@ class ActionsPopup {
 
     boolean isVisible() {
         return showActionsMenu || showExampleBrowser || 
runOptionsForm.isVisible() || showDocPicker || showDocViewer
+                || showInfraBrowser || showInfraPortDialog
                 || mcpLogPopup.isVisible() || doctorPopup.isVisible() || 
classpathPopup.isVisible()
                 || sendMessagePopup.isVisible() || stopAllPopup.isVisible() || 
captionOverlay.isInlineMode();
     }
 
     SelectionContext getSelectionContext() {
+        if (showInfraBrowser && infraCatalog != null) {
+            List<String> items = infraCatalog.stream().map(e -> 
e.alias).collect(Collectors.toList());
+            Integer sel = infraBrowserState.selected();
+            return new SelectionContext("list", items, sel != null ? sel : -1, 
infraCatalog.size(), "Infra Services");
+        }
         if (showExampleBrowser && exampleCatalog != null) {
             List<String> items = new ArrayList<>();
             String currentLevel = null;
@@ -268,6 +295,7 @@ class ActionsPopup {
         // Group 1: User Actions
         labels.add("Send Message");
         labels.add("Run an example...");
+        labels.add("Run Infra Service...");
         labels.add("Show Documentation");
         labels.add("───");
         // Group 2: Diagnostics
@@ -302,6 +330,8 @@ class ActionsPopup {
         runOptionsForm.close();
         showDocPicker = false;
         showDocViewer = false;
+        showInfraBrowser = false;
+        showInfraPortDialog = false;
         mcpLogPopup.close();
         doctorPopup.close();
         classpathPopup.close();
@@ -367,6 +397,44 @@ class ActionsPopup {
             }
             return true;
         }
+        if (showInfraPortDialog) {
+            if (ke.isCancel()) {
+                showInfraPortDialog = false;
+                showInfraBrowser = true;
+            } else if (ke.isConfirm()) {
+                launchInfraService();
+            } else if (selectedInfraService != null && 
selectedInfraService.implementations.size() > 1) {
+                if (ke.isLeft()) {
+                    infraImplIndex = (infraImplIndex - 1 + 
selectedInfraService.implementations.size())
+                                     % 
selectedInfraService.implementations.size();
+                    return true;
+                } else if (ke.isRight()) {
+                    infraImplIndex = (infraImplIndex + 1) % 
selectedInfraService.implementations.size();
+                    return true;
+                }
+            }
+            handlePortInput(ke);
+            return true;
+        }
+        if (showInfraBrowser) {
+            if (ke.isCancel()) {
+                showInfraBrowser = false;
+                showActionsMenu = true;
+            } else if (ke.isUp()) {
+                navigateInfraBrowser(-1);
+            } else if (ke.isDown()) {
+                navigateInfraBrowser(1);
+            } else if (ke.isPageUp() || ke.isKey(KeyCode.PAGE_UP)) {
+                navigateInfraBrowser(-10);
+            } else if (ke.isPageDown() || ke.isKey(KeyCode.PAGE_DOWN)) {
+                navigateInfraBrowser(10);
+            } else if (ke.isConfirm()) {
+                selectInfraService();
+            } else if (ke.code() == KeyCode.CHAR) {
+                jumpToInfraService(ke.character());
+            }
+            return true;
+        }
         if (runOptionsForm.isVisible()) {
             if (ke.isCancel()) {
                 runOptionsForm.close();
@@ -445,6 +513,8 @@ class ActionsPopup {
                     } else if (action == ACTION_CLASSPATH) {
                         showActionsMenu = false;
                         openClasspath();
+                    } else if (action == ACTION_RUN_INFRA) {
+                        openInfraBrowser();
                     } else if (action == ACTION_MCP_INFO) {
                         showActionsMenu = false;
                         openMcpInfo();
@@ -478,6 +548,12 @@ class ActionsPopup {
         if (showActionsMenu) {
             renderActionsMenu(frame, area);
         }
+        if (showInfraBrowser) {
+            renderInfraBrowser(frame, area);
+        }
+        if (showInfraPortDialog) {
+            renderInfraPortDialog(frame, area);
+        }
         if (showExampleBrowser) {
             renderExampleBrowser(frame, area);
         }
@@ -546,6 +622,17 @@ class ActionsPopup {
             runOptionsForm.renderFooter(spans);
             return;
         }
+        if (showInfraPortDialog) {
+            hint(spans, "Enter", "run");
+            hintLast(spans, "Esc", "back");
+            return;
+        }
+        if (showInfraBrowser) {
+            hint(spans, "↑↓", "navigate");
+            hint(spans, "Enter", "select");
+            hintLast(spans, "Esc", "back");
+            return;
+        }
         if (showExampleBrowser) {
             hint(spans, "↑↓", "navigate");
             hint(spans, "Enter", "run");
@@ -595,6 +682,7 @@ class ActionsPopup {
         // Group 1: User Actions
         items.add(ListItem.from("  📩 Send Message"));
         items.add(ListItem.from("  🐪 Run an example..."));
+        items.add(ListItem.from("  🔧 Run Infra Service..."));
         items.add(ListItem.from("  📖 Show Documentation"));
         items.add(ListItem.from(divider).style(Style.EMPTY.dim()));
         // Group 2: Diagnostics
@@ -1225,6 +1313,316 @@ class ActionsPopup {
         return null;
     }
 
+    // ---- Infra Browser ----
+
+    private void openInfraBrowser() {
+        showActionsMenu = false;
+        if (infraCatalog == null) {
+            infraCatalog = loadInfraCatalog();
+        }
+        if (infraCatalog.isEmpty()) {
+            setNotification("No infra services found", true);
+            return;
+        }
+        showInfraBrowser = true;
+        infraBrowserState.select(0);
+        // skip to first non-running service
+        if (!infraCatalog.isEmpty() && infraCatalog.get(0).running) {
+            navigateInfraBrowser(1);
+        }
+    }
+
+    private List<InfraServiceEntry> loadInfraCatalog() {
+        try {
+            CamelCatalog catalog = new DefaultCamelCatalog();
+            try (InputStream is = catalog.loadResource("test-infra", 
"metadata.json")) {
+                if (is == null) {
+                    return List.of();
+                }
+                String json = new String(is.readAllBytes(), 
StandardCharsets.UTF_8);
+                JsonArray arr = (JsonArray) Jsoner.deserialize(json);
+                Map<String, InfraServiceEntry> byAlias = new LinkedHashMap<>();
+                for (Object obj : arr) {
+                    JsonObject svc = (JsonObject) obj;
+                    String desc = (String) svc.getOrDefault("description", "");
+                    JsonArray aliases = (JsonArray) svc.get("alias");
+                    JsonArray impls = (JsonArray) 
svc.get("aliasImplementation");
+                    if (aliases != null) {
+                        for (Object a : aliases) {
+                            String alias = a.toString();
+                            InfraServiceEntry entry = byAlias.get(alias);
+                            if (entry == null) {
+                                entry = new InfraServiceEntry(alias, desc, new 
ArrayList<>(), false);
+                                byAlias.put(alias, entry);
+                            }
+                            if (impls != null) {
+                                for (Object impl : impls) {
+                                    String implStr = impl.toString();
+                                    if 
(!entry.implementations.contains(implStr)) {
+                                        entry.implementations.add(implStr);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+                // Mark running services
+                Set<String> runningAliases = infraServices.get().stream()
+                        .filter(i -> i.alive)
+                        .map(i -> i.alias)
+                        .collect(Collectors.toSet());
+                List<InfraServiceEntry> result = new ArrayList<>();
+                for (InfraServiceEntry entry : byAlias.values()) {
+                    boolean running = runningAliases.contains(entry.alias);
+                    result.add(new InfraServiceEntry(entry.alias, 
entry.description, entry.implementations, running));
+                }
+                result.sort((a, b) -> a.alias.compareToIgnoreCase(b.alias));
+                return result;
+            }
+        } catch (Exception e) {
+            return List.of();
+        }
+    }
+
+    private void refreshInfraRunningState() {
+        if (infraCatalog == null) {
+            return;
+        }
+        Set<String> runningAliases = infraServices.get().stream()
+                .filter(i -> i.alive)
+                .map(i -> i.alias)
+                .collect(Collectors.toSet());
+        List<InfraServiceEntry> refreshed = new ArrayList<>();
+        for (InfraServiceEntry entry : infraCatalog) {
+            boolean running = runningAliases.contains(entry.alias);
+            refreshed.add(new InfraServiceEntry(entry.alias, 
entry.description, entry.implementations, running));
+        }
+        infraCatalog = refreshed;
+    }
+
+    private void navigateInfraBrowser(int direction) {
+        if (infraCatalog == null || infraCatalog.isEmpty()) {
+            return;
+        }
+        int total = infraCatalog.size();
+        Integer current = infraBrowserState.selected();
+        int next = (current != null ? current : 0) + direction;
+        next = Math.max(0, Math.min(next, total - 1));
+        // skip running services
+        while (next >= 0 && next < total && infraCatalog.get(next).running) {
+            next += direction;
+        }
+        next = Math.max(0, Math.min(next, total - 1));
+        if (!infraCatalog.get(next).running) {
+            infraBrowserState.select(next);
+        }
+    }
+
+    private void selectInfraService() {
+        Integer sel = infraBrowserState.selected();
+        if (sel == null || sel >= infraCatalog.size()) {
+            return;
+        }
+        InfraServiceEntry entry = infraCatalog.get(sel);
+        if (entry.running) {
+            return;
+        }
+        selectedInfraService = entry;
+        infraImplIndex = 0;
+        infraPortState = new TextInputState("");
+        showInfraBrowser = false;
+        showInfraPortDialog = true;
+    }
+
+    private void jumpToInfraService(char ch) {
+        if (infraCatalog == null) {
+            return;
+        }
+        char lower = Character.toLowerCase(ch);
+        for (int i = 0; i < infraCatalog.size(); i++) {
+            if 
(infraCatalog.get(i).alias.toLowerCase().startsWith(String.valueOf(lower))) {
+                infraBrowserState.select(i);
+                infraBrowserState.setOffset(Math.max(0, i - 2));
+                return;
+            }
+        }
+    }
+
+    private void renderInfraBrowser(Frame frame, Rect area) {
+        if (infraCatalog == null || infraCatalog.isEmpty()) {
+            return;
+        }
+        refreshInfraRunningState();
+        int popupW = Math.min(100, area.width() - 4);
+        int popupH = Math.min(infraCatalog.size() + 2, Math.min(22, 
area.height() - 6));
+        int x = area.left() + Math.max(0, (area.width() - popupW) / 2);
+        int y = area.top() + Math.max(0, (area.height() - popupH) / 2);
+        Rect popup = new Rect(x, y, Math.min(popupW, area.width()), 
Math.min(popupH, area.height()));
+
+        frame.renderWidget(Clear.INSTANCE, popup);
+
+        int nameCol = 22;
+        List<ListItem> items = new ArrayList<>();
+        for (InfraServiceEntry entry : infraCatalog) {
+            String padded = String.format("%-" + nameCol + "s", 
TuiHelper.truncate(entry.alias, nameCol));
+            String prefix = "  🔧 " + padded + " ";
+            if (entry.running) {
+                items.add(ListItem.from(prefix + 
"(running)").style(Style.EMPTY.dim()));
+            } else {
+                String implStr = entry.implementations.isEmpty() ? "" : 
String.join(", ", entry.implementations);
+                String desc = entry.description;
+                if (!implStr.isEmpty()) {
+                    desc = desc + " [" + implStr + "]";
+                }
+                int descW = Math.max(10, popupW - prefix.length() - 2);
+                if (desc.length() <= descW) {
+                    items.add(ListItem.from(prefix + desc));
+                } else {
+                    String indent = " ".repeat(prefix.length());
+                    List<Line> lines = new ArrayList<>();
+                    List<String> wrapped = wrapWords(desc, descW);
+                    lines.add(Line.from(prefix + wrapped.get(0)));
+                    for (int w = 1; w < wrapped.size(); w++) {
+                        lines.add(Line.from(indent + wrapped.get(w)));
+                    }
+                    
items.add(ListItem.from(Text.from(lines.toArray(Line[]::new))));
+                }
+            }
+        }
+
+        long available = infraCatalog.stream().filter(e -> !e.running).count();
+        ListWidget list = ListWidget.builder()
+                .items(items.toArray(ListItem[]::new))
+                .highlightStyle(Style.EMPTY.fg(Color.WHITE).bold().onBlue())
+                .highlightSymbol("")
+                .scrollMode(ScrollMode.AUTO_SCROLL)
+                .block(Block.builder()
+                        .borderType(BorderType.ROUNDED)
+                        .title(" Run Infra Service (" + available + "/" + 
infraCatalog.size() + ") ")
+                        .titleBottom(Title.from(Line.from(
+                                Span.styled(" Enter", 
MonitorContext.HINT_KEY_STYLE), Span.raw(" select │"),
+                                Span.styled(" ↑↓", 
MonitorContext.HINT_KEY_STYLE), Span.raw(" navigate │"),
+                                Span.styled(" Esc", 
MonitorContext.HINT_KEY_STYLE), Span.raw(" back "))))
+                        .build())
+                .build();
+        frame.renderStatefulWidget(list, popup, infraBrowserState);
+    }
+
+    // ---- Infra Port Dialog ----
+
+    private void renderInfraPortDialog(Frame frame, Rect area) {
+        if (selectedInfraService == null) {
+            return;
+        }
+        boolean hasMultiImpl = selectedInfraService.implementations.size() > 1;
+        int popupW = 42;
+        int popupH = hasMultiImpl ? 8 : 6;
+        int x = area.left() + Math.max(0, (area.width() - popupW) / 2);
+        int y = area.top() + Math.max(0, (area.height() - popupH) / 2);
+        Rect popup = new Rect(x, y, Math.min(popupW, area.width()), 
Math.min(popupH, area.height()));
+
+        frame.renderWidget(Clear.INSTANCE, popup);
+
+        Block block = Block.builder()
+                .borderType(BorderType.ROUNDED)
+                .title(" Run " + selectedInfraService.alias + " ")
+                .titleBottom(Title.from(Line.from(
+                        Span.styled(" Enter", MonitorContext.HINT_KEY_STYLE), 
Span.raw(" run │"),
+                        Span.styled(" Esc", MonitorContext.HINT_KEY_STYLE), 
Span.raw(" back "))))
+                .build();
+        frame.renderWidget(block, popup);
+        Rect inner = block.inner(popup);
+
+        int labelW = 8;
+        int fieldW = inner.width() - labelW;
+        int row = inner.top();
+        int ix = inner.left();
+
+        // Implementation selector (if multiple)
+        if (hasMultiImpl) {
+            row++;
+            Rect labelArea = new Rect(ix, row, labelW, 1);
+            frame.renderWidget(Paragraph.from(Line.from(Span.styled("Impl:", 
Style.EMPTY.bold()))), labelArea);
+            String impl = 
selectedInfraService.implementations.get(infraImplIndex);
+            Rect implArea = new Rect(ix + labelW, row, fieldW, 1);
+            frame.renderWidget(Paragraph.from(Line.from(
+                    Span.styled("◀ ", MonitorContext.HINT_KEY_STYLE),
+                    Span.raw(impl),
+                    Span.styled(" ▶", MonitorContext.HINT_KEY_STYLE))), 
implArea);
+            row++;
+        }
+
+        // Port input
+        row++;
+        Rect labelArea = new Rect(ix, row, labelW, 1);
+        frame.renderWidget(Paragraph.from(Line.from(Span.styled("Port:", 
Style.EMPTY.bold()))), labelArea);
+        Rect portArea = new Rect(ix + labelW, row, fieldW, 1);
+        TextInput textInput = TextInput.builder()
+                .cursorStyle(Style.EMPTY.reversed())
+                .placeholder("default")
+                .build();
+        frame.renderStatefulWidget(textInput, portArea, infraPortState);
+    }
+
+    private void handlePortInput(KeyEvent ke) {
+        if (infraPortState == null) {
+            return;
+        }
+        if (ke.isDeleteBackward()) {
+            infraPortState.deleteBackward();
+        } else if (ke.isDeleteForward()) {
+            infraPortState.deleteForward();
+        } else if (ke.isLeft()) {
+            infraPortState.moveCursorLeft();
+        } else if (ke.isRight()) {
+            infraPortState.moveCursorRight();
+        } else if (ke.isHome()) {
+            infraPortState.moveCursorToStart();
+        } else if (ke.isEnd()) {
+            infraPortState.moveCursorToEnd();
+        } else if (ke.code() == KeyCode.CHAR && 
Character.isDigit(ke.character())) {
+            infraPortState.insert(ke.character());
+        }
+    }
+
+    private void launchInfraService() {
+        if (selectedInfraService == null) {
+            return;
+        }
+        String alias = selectedInfraService.alias;
+        String impl = null;
+        if (!selectedInfraService.implementations.isEmpty()) {
+            impl = selectedInfraService.implementations.get(infraImplIndex);
+        }
+        String portStr = infraPortState != null ? infraPortState.text().trim() 
: "";
+        showInfraPortDialog = false;
+        try {
+            List<String> cmd = new 
ArrayList<>(LauncherHelper.getCamelCommand());
+            cmd.add("infra");
+            cmd.add("run");
+            cmd.add(alias);
+            if (impl != null) {
+                cmd.add(impl);
+            }
+            cmd.add("--background");
+            if (!portStr.isEmpty()) {
+                cmd.add("--port=" + portStr);
+            }
+            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()));
+            setNotification("Starting infra: " + alias, false);
+            // force reload next time browser opens
+            infraCatalog = null;
+        } catch (Exception e) {
+            setNotification("Failed to start infra: " + alias + " - " + 
e.getMessage(), true);
+        }
+    }
+
     // ---- Process Launch & Monitoring ----
 
     private void launchSelectedExample() {
@@ -1332,6 +1730,9 @@ class ActionsPopup {
         return lines;
     }
 
+    record InfraServiceEntry(String alias, String description, List<String> 
implementations, boolean running) {
+    }
+
     private record PendingLaunch(String name, Process process, Path 
outputFile, long startTime) {
     }
 }

Reply via email to