ammachado commented on code in PR #25317:
URL: https://github.com/apache/camel/pull/25317#discussion_r3712744679


##########
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiWebServer.java:
##########
@@ -0,0 +1,193 @@
+/*
+ * 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.io.IOException;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.net.BindException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import dev.tamboui.backend.aesh.AeshBackend;
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.MultiThreadIoEventLoopGroup;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.channel.group.ChannelGroup;
+import io.netty.channel.group.DefaultChannelGroup;
+import io.netty.channel.nio.NioIoHandler;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.codec.http.DefaultFullHttpResponse;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http.HttpServerCodec;
+import io.netty.handler.codec.http.HttpVersion;
+import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import io.netty.util.concurrent.ImmediateEventExecutor;
+import org.aesh.terminal.Connection;
+import org.aesh.terminal.http.netty.HttpRequestHandler;
+import org.aesh.terminal.http.netty.TtyWebSocketFrameHandler;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+
+/**
+ * Serves the Camel TUI dashboard to a web browser over WebSocket, using 
Aesh's HTTP/WebSocket terminal bridge.
+ * <p>
+ * Each incoming connection gets its own {@link CamelMonitor} instance 
(running the same live-monitoring logic as a
+ * local terminal session) driven by an {@link AeshBackend} wrapping that 
connection.
+ * <p>
+ * Binds to 127.0.0.1 only for security.
+ */
+class TuiWebServer {
+
+    private static final Logger LOG = 
System.getLogger(TuiWebServer.class.getName());
+    private final int port;
+    private final CamelJBangMain main;
+    private final ClassLoader classLoader;
+    private final String name;
+    private final long refreshInterval;
+    private final String theme;
+    private final ChannelGroup channels = new 
DefaultChannelGroup(ImmediateEventExecutor.INSTANCE);
+    private final EventLoopGroup bossGroup = new 
MultiThreadIoEventLoopGroup(1, NioIoHandler.newFactory());
+    private final EventLoopGroup workerGroup = new 
MultiThreadIoEventLoopGroup(NioIoHandler.newFactory());
+    private final ExecutorService sessionExecutor = 
Executors.newCachedThreadPool(r -> {

Review Comment:
   Fixed in 8ee58d6d6957 — switched to `newFixedThreadPool(Math.max(4, 
availableProcessors() * 2))`.
   
   _Claude Code on behalf of ammachado_



##########
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiWebServer.java:
##########
@@ -0,0 +1,193 @@
+/*
+ * 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.io.IOException;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.net.BindException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import dev.tamboui.backend.aesh.AeshBackend;
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.MultiThreadIoEventLoopGroup;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.channel.group.ChannelGroup;
+import io.netty.channel.group.DefaultChannelGroup;
+import io.netty.channel.nio.NioIoHandler;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.codec.http.DefaultFullHttpResponse;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http.HttpServerCodec;
+import io.netty.handler.codec.http.HttpVersion;
+import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import io.netty.util.concurrent.ImmediateEventExecutor;
+import org.aesh.terminal.Connection;
+import org.aesh.terminal.http.netty.HttpRequestHandler;
+import org.aesh.terminal.http.netty.TtyWebSocketFrameHandler;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+
+/**
+ * Serves the Camel TUI dashboard to a web browser over WebSocket, using 
Aesh's HTTP/WebSocket terminal bridge.
+ * <p>
+ * Each incoming connection gets its own {@link CamelMonitor} instance 
(running the same live-monitoring logic as a
+ * local terminal session) driven by an {@link AeshBackend} wrapping that 
connection.
+ * <p>
+ * Binds to 127.0.0.1 only for security.
+ */
+class TuiWebServer {
+
+    private static final Logger LOG = 
System.getLogger(TuiWebServer.class.getName());
+    private final int port;
+    private final CamelJBangMain main;
+    private final ClassLoader classLoader;
+    private final String name;
+    private final long refreshInterval;
+    private final String theme;
+    private final ChannelGroup channels = new 
DefaultChannelGroup(ImmediateEventExecutor.INSTANCE);
+    private final EventLoopGroup bossGroup = new 
MultiThreadIoEventLoopGroup(1, NioIoHandler.newFactory());
+    private final EventLoopGroup workerGroup = new 
MultiThreadIoEventLoopGroup(NioIoHandler.newFactory());
+    private final ExecutorService sessionExecutor = 
Executors.newCachedThreadPool(r -> {
+        Thread t = new Thread(r, "tui-web-session");
+        t.setDaemon(true);
+        return t;
+    });
+    private Channel serverChannel;
+    private boolean stopped;
+
+    TuiWebServer(int port, CamelJBangMain main, ClassLoader classLoader, 
String name, long refreshInterval,
+                 String theme) {
+        this.port = port;
+        this.main = main;
+        this.classLoader = classLoader;
+        this.name = name;
+        this.refreshInterval = refreshInterval;
+        this.theme = theme;
+    }
+
+    void start() throws IOException {
+        try {
+            ServerBootstrap bootstrap = new ServerBootstrap();
+            serverChannel = bootstrap.group(bossGroup, workerGroup)
+                    .channel(NioServerSocketChannel.class)
+                    .childHandler(new WebServerInitializer())
+                    .bind("127.0.0.1", port)
+                    .sync()
+                    .channel();
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            stop();
+            throw new IOException("Interrupted while starting web terminal 
server", e);
+        } catch (Exception e) {
+            stop();
+            Throwable cause = e.getCause();
+            if (cause instanceof BindException bindException) {
+                throw bindException;
+            }
+            if (e instanceof BindException bindException) {
+                throw bindException;
+            }
+            throw new IOException("Failed to start web terminal server", e);
+        }
+    }
+
+    synchronized void stop() {
+        if (stopped) {
+            return;
+        }
+        stopped = true;
+        if (serverChannel != null) {
+            serverChannel.close().syncUninterruptibly();
+        }
+        channels.close().syncUninterruptibly();
+        bossGroup.shutdownGracefully(0, 5, 
TimeUnit.SECONDS).syncUninterruptibly();
+        workerGroup.shutdownGracefully(0, 5, 
TimeUnit.SECONDS).syncUninterruptibly();
+        sessionExecutor.shutdownNow();
+    }
+
+    boolean awaitTermination(long timeout, TimeUnit unit) throws 
InterruptedException {

Review Comment:
   Fixed in 8ee58d6d6957 — `awaitTermination()` now also waits on 
`sessionExecutor` with the remaining deadline after the Netty event loops.
   
   _Claude Code on behalf of ammachado_



##########
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/CamelMonitor.java:
##########
@@ -523,7 +538,21 @@ public void resetIntegrationTabState() {
         }
         aiPanel.setMcpInfo(mcp, mcpPort);
 
-        try (var tui = TuiBackendHelper.createTuiRunner()) {
+        if (web) {
+            webServer = new TuiWebServer(webPort, getMain(), classLoader, 
name, refreshInterval, theme);
+            try {
+                webServer.start();
+            } catch (java.net.BindException e) {

Review Comment:
   Fixed in 8ee58d6d6957 — both the MCP and web server catch blocks now import 
`BindException` instead of using the FQCN.
   
   _Claude Code on behalf of ammachado_



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to