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 e419a5d821d5 CAMEL-23615: camel-jbang - TUI cold restart for
integrations (#23595)
e419a5d821d5 is described below
commit e419a5d821d5b70dbd03ffac0bc95c209a311636
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu May 28 11:26:50 2026 +0200
CAMEL-23615: camel-jbang - TUI cold restart for integrations (#23595)
Co-authored-by: Claude Opus 4.6 <[email protected]>
---
.../dsl/jbang/core/commands/tui/CamelMonitor.java | 141 +++++++++++++++++++++
.../jbang/core/commands/tui/IntegrationInfo.java | 1 +
2 files changed, 142 insertions(+)
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 d24b31eb7f5a..ba7e66f43ab0 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
@@ -16,6 +16,7 @@
*/
package org.apache.camel.dsl.jbang.core.commands.tui;
+import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;
@@ -37,8 +38,10 @@ import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
@@ -220,6 +223,9 @@ public class CamelMonitor extends CamelCommand {
private volatile long lastRefresh;
private boolean showKillConfirm;
+ private String monitorNotification;
+ private boolean monitorNotificationError;
+ private long monitorNotificationExpiry;
private volatile Buffer lastBuffer;
private volatile long renderGeneration;
private volatile String screenshotMessage;
@@ -622,6 +628,11 @@ public class CamelMonitor extends CamelCommand {
showKillConfirm = true;
return true;
}
+ // Overview tab: cold restart (stop + re-launch) for selected
integration
+ if (tab == TAB_OVERVIEW && ke.isChar('r') && ctx.selectedPid !=
null && !ctx.infraTableFocused) {
+ restartSelectedProcess();
+ return true;
+ }
// Delegate remaining keys to active tab
if (activeTab != null && activeTab.handleKeyEvent(ke)) {
return true;
@@ -918,6 +929,15 @@ public class CamelMonitor extends CamelCommand {
Style style = actionsPopup.notificationError() ?
Style.EMPTY.fg(Color.RED) : Style.EMPTY.fg(Color.GREEN);
titleSpans.add(Span.styled(actionsPopup.notification(), style));
}
+ if (monitorNotification != null) {
+ if (System.currentTimeMillis() > monitorNotificationExpiry) {
+ monitorNotification = null;
+ } else {
+ titleSpans.add(Span.raw(" "));
+ Style style = monitorNotificationError ?
Style.EMPTY.fg(Color.RED) : Style.EMPTY.fg(Color.GREEN);
+ titleSpans.add(Span.styled(monitorNotification, style));
+ }
+ }
Line titleLine = Line.from(titleSpans);
frame.renderWidget(
@@ -1654,6 +1674,123 @@ public class CamelMonitor extends CamelCommand {
}
}
+ private void restartSelectedProcess() {
+ if (ctx.selectedPid == null || ctx.infraTableFocused) {
+ return;
+ }
+ long pid;
+ try {
+ pid = Long.parseLong(ctx.selectedPid);
+ } catch (NumberFormatException e) {
+ return;
+ }
+ IntegrationInfo info = findSelectedIntegration();
+ if (info == null) {
+ return;
+ }
+ ProcessHandle ph = ProcessHandle.of(pid).orElse(null);
+ if (ph == null) {
+ return;
+ }
+
+ // capture command line before stopping
+ Optional<String> cmdOpt = ph.info().command();
+ Optional<String[]> argsOpt = ph.info().arguments();
+ Optional<String> cmdLineOpt = ph.info().commandLine();
+
+ String name = info.name;
+ String directory = info.directory;
+
+ // remember name so the restarted process gets auto-selected
+ ctx.lastSelectedName = name;
+
+ // stop gracefully
+ ph.destroy();
+ setNotification("Restarting: " + name, false);
+
+ // re-launch in background after process terminates
+ if (runner != null) {
+ runner.scheduler().execute(() -> {
+ try {
+ // wait for termination (max 10 seconds, then force kill)
+ CompletableFuture<ProcessHandle> exitFuture =
ph.onExit().toCompletableFuture();
+ try {
+ exitFuture.get(10, TimeUnit.SECONDS);
+ } catch (Exception e) {
+ ph.destroyForcibly();
+ Thread.sleep(500);
+ }
+
+ // build command
+ List<String> cmd = new ArrayList<>();
+ if (cmdOpt.isPresent() && argsOpt.isPresent() &&
argsOpt.get().length > 0) {
+ cmd.add(cmdOpt.get());
+ Collections.addAll(cmd, argsOpt.get());
+ } else if (cmdLineOpt.isPresent()) {
+ cmd.addAll(parseCommandLine(cmdLineOpt.get()));
+ }
+
+ if (cmd.isEmpty()) {
+ runner.runOnRenderThread(
+ () -> setNotification("Cannot restart: command
line not available", true));
+ return;
+ }
+
+ ProcessBuilder pb = new ProcessBuilder(cmd);
+ if (directory != null) {
+ pb.directory(new File(directory));
+ }
+ pb.redirectErrorStream(true);
+ Path outputFile = Files.createTempFile("camel-restart-",
".log");
+ outputFile.toFile().deleteOnExit();
+ pb.redirectOutput(outputFile.toFile());
+ pb.start();
+
+ runner.runOnRenderThread(() -> setNotification("Restarted:
" + name, false));
+ } catch (Exception e) {
+ runner.runOnRenderThread(
+ () -> setNotification("Restart failed: " +
e.getMessage(), true));
+ }
+ });
+ }
+ }
+
+ private void setNotification(String message, boolean error) {
+ monitorNotification = message;
+ monitorNotificationError = error;
+ monitorNotificationExpiry = System.currentTimeMillis() + 5000;
+ }
+
+ static List<String> parseCommandLine(String commandLine) {
+ List<String> tokens = new ArrayList<>();
+ StringBuilder current = new StringBuilder();
+ boolean inQuotes = false;
+ boolean escaped = false;
+
+ for (int i = 0; i < commandLine.length(); i++) {
+ char c = commandLine.charAt(i);
+ if (escaped) {
+ current.append(c);
+ escaped = false;
+ } else if (c == '\\') {
+ escaped = true;
+ } else if (c == '"') {
+ inQuotes = !inQuotes;
+ } else if (c == ' ' && !inQuotes) {
+ if (!current.isEmpty()) {
+ tokens.add(current.toString());
+ current.setLength(0);
+ }
+ } else {
+ current.append(c);
+ }
+ }
+ if (!current.isEmpty()) {
+ tokens.add(current.toString());
+ }
+ return tokens;
+ }
+
private void resetStats() {
IntegrationInfo info = ctx.findSelectedIntegration();
if (info == null) {
@@ -1845,6 +1982,9 @@ public class CamelMonitor extends CamelCommand {
}
}
if (ctx.selectedPid != null) {
+ if (!ctx.infraTableFocused) {
+ hint(spans, "r", "restart");
+ }
hint(spans, "x", "stop");
hint(spans, "X", "kill");
}
@@ -2779,6 +2919,7 @@ public class CamelMonitor extends CamelCommand {
}
}
}
+ info.directory = runtime != null ? runtime.getString("directory") :
null;
info.javaVersion = runtime != null ? runtime.getString("javaVersion")
: null;
info.javaVendor = runtime != null ? runtime.getString("javaVendor") :
null;
info.javaVmName = runtime != null ? runtime.getString("javaVmName") :
null;
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 099e7b2294ca..018fabc1a6f1 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
@@ -28,6 +28,7 @@ class IntegrationInfo {
String javaVersion;
String javaVendor;
String javaVmName;
+ String directory;
String profile;
boolean devMode;
String ready;