Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Supplier;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -66,8 +67,9 @@ class ActionsPopup {
private static final int ACTION_CLASSPATH = 8;
private static final int ACTION_MCP_INFO = 9;
private static final int ACTION_MCP_LOG = 10;
private static final int ACTION_RESET_STATS = 11;
private static final int ACTION_STOP_ALL = 12;
private static final int ACTION_SEND_MESSAGE = 11;
private static final int ACTION_RESET_STATS = 12;
private static final int ACTION_STOP_ALL = 13;

private final Supplier<Set<String>> runningNames;
private final Supplier<List<IntegrationInfo>> integrations;
Expand Down Expand Up @@ -107,14 +109,17 @@ class ActionsPopup {

private final DoctorPopup doctorPopup = new DoctorPopup();
private final ClasspathPopup classpathPopup = new ClasspathPopup();
private final SendMessagePopup sendMessagePopup = new SendMessagePopup();
private final StopAllPopup stopAllPopup;
private final CaptionOverlay captionOverlay;
private ScheduledExecutorService scheduler;

private final List<PendingLaunch> pendingLaunches = new ArrayList<>();
private String launchNotification;
private boolean launchNotificationError;
private long launchNotificationExpiry;
private volatile String pendingAutoSelect;
private String preSelectedRouteId;

ActionsPopup(Supplier<Set<String>> runningNames, Supplier<List<IntegrationInfo>> integrations,
Supplier<List<InfraInfo>> infraServices, CaptionOverlay captionOverlay,
Expand All @@ -135,6 +140,14 @@ void setContext(MonitorContext ctx) {
this.ctx = ctx;
}

void setScheduler(ScheduledExecutorService scheduler) {
this.scheduler = scheduler;
}

void setPreSelectedRouteId(String routeId) {
this.preSelectedRouteId = routeId;
}

void setResetStatsAction(Runnable resetStatsAction) {
this.resetStatsAction = resetStatsAction;
}
Expand All @@ -149,13 +162,13 @@ void setMcpEnabled(
}

private int actionCount() {
return mcpEnabled ? 13 : 11;
return mcpEnabled ? 14 : 12;
}

boolean isVisible() {
return showActionsMenu || showExampleBrowser || runOptionsForm.isVisible() || showDocPicker || showDocViewer
|| mcpLogPopup.isVisible() || doctorPopup.isVisible() || classpathPopup.isVisible()
|| stopAllPopup.isVisible() || captionOverlay.isInlineMode();
|| sendMessagePopup.isVisible() || stopAllPopup.isVisible() || captionOverlay.isInlineMode();
}

SelectionContext getSelectionContext() {
Expand Down Expand Up @@ -201,11 +214,12 @@ List<String> getActionLabels() {
labels.add("Tape Recording Guide");
labels.add("Run Doctor");
labels.add("Show Classpath");
labels.add("Reset Stats");
if (mcpEnabled) {
labels.add("MCP Info");
labels.add("MCP Log");
}
labels.add("Send Message");
labels.add("Reset Stats");
labels.add("Stop All");
return labels;
}
Expand All @@ -224,6 +238,7 @@ void close() {
mcpLogPopup.close();
doctorPopup.close();
classpathPopup.close();
sendMessagePopup.close();
stopAllPopup.close();
captionOverlay.close();
}
Expand All @@ -236,7 +251,21 @@ boolean notificationError() {
return launchNotificationError;
}

void handlePaste(String text) {
if (sendMessagePopup.isVisible()) {
sendMessagePopup.handlePaste(text);
}
}

boolean handleKeyEvent(KeyEvent ke) {
if (sendMessagePopup.isVisible()) {
if (ke.isConfirm()) {
sendMessagePopup.doSend(ctx, scheduler);
} else {
sendMessagePopup.handleKeyEvent(ke);
}
return true;
}
if (mcpLogPopup.handleKeyEvent(ke)) {
return true;
}
Expand Down Expand Up @@ -355,6 +384,9 @@ boolean handleKeyEvent(KeyEvent ke) {
} else if (action == ACTION_MCP_LOG) {
showActionsMenu = false;
openMcpLog();
} else if (action == ACTION_SEND_MESSAGE) {
showActionsMenu = false;
openSendMessage();
} else if (action == ACTION_RESET_STATS) {
showActionsMenu = false;
if (resetStatsAction != null) {
Expand Down Expand Up @@ -403,6 +435,9 @@ void render(Frame frame, Rect area) {
if (classpathPopup.isVisible()) {
classpathPopup.render(frame, area);
}
if (sendMessagePopup.isVisible()) {
sendMessagePopup.render(frame, area);
}
if (captionOverlay.isInlineMode()) {
captionOverlay.render(frame, area);
}
Expand Down Expand Up @@ -497,11 +532,12 @@ private void renderActionsMenu(Frame frame, Rect area) {
items.add(ListItem.from(" 📄 Tape Recording Guide"));
items.add(ListItem.from(" 🩺 Run Doctor"));
items.add(ListItem.from(" 📦 Show Classpath"));
items.add(ListItem.from(" 🔄 Reset Stats"));
if (mcpEnabled) {
items.add(ListItem.from(" 🤖 MCP Info"));
items.add(ListItem.from(" 📋 MCP Log"));
}
items.add(ListItem.from(" 📩 Send Message"));
items.add(ListItem.from(" 🔄 Reset Stats"));
items.add(ListItem.from(stopLabel));
ListWidget list = ListWidget.builder()
.items(items.toArray(ListItem[]::new))
Expand Down Expand Up @@ -777,6 +813,40 @@ private int resolveAction(int index) {
return index;
}

private void openSendMessage() {
if (ctx == null) {
return;
}
String pid = ctx.selectedPid;
if (pid == null) {
List<IntegrationInfo> ints = integrations.get();
List<IntegrationInfo> alive = ints.stream().filter(i -> !i.vanishing && i.pid != null).toList();
if (alive.size() == 1) {
pid = alive.get(0).pid;
}
}
if (pid == null) {
setNotification("Select an integration first", true);
return;
}
IntegrationInfo info = findIntegration(pid);
if (info == null || info.routes.isEmpty()) {
setNotification("No routes available", true);
return;
}
sendMessagePopup.open(ctx, pid, info.name, info.routes, preSelectedRouteId);
preSelectedRouteId = null;
}

private IntegrationInfo findIntegration(String pid) {
for (IntegrationInfo i : integrations.get()) {
if (pid.equals(i.pid)) {
return i;
}
}
return null;
}

private void openTapeInstructions() {
docLines = null;
docContent = "# Tape Recording Guide\n\n"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
import dev.tamboui.tui.event.KeyCode;
import dev.tamboui.tui.event.KeyEvent;
import dev.tamboui.tui.event.KeyModifiers;
import dev.tamboui.tui.event.PasteEvent;
import dev.tamboui.tui.event.TickEvent;
import dev.tamboui.widgets.Clear;
import dev.tamboui.widgets.barchart.Bar;
Expand Down Expand Up @@ -343,6 +344,7 @@ public Integer doCall() throws Exception {
try (var tui = TuiBackendHelper.createTuiRunner()) {
this.runner = tui;
ctx.runner = tui;
actionsPopup.setScheduler(tui.scheduler());
// Intercept Ctrl+C: quit the TUI cleanly instead of letting
// the JVM tear down the classloader while we're still running
Signal.handle(new Signal("INT"), sig -> tui.quit());
Expand Down Expand Up @@ -511,6 +513,9 @@ private boolean handleEvent(Event event, TuiRunner runner) {

// F2 opens actions menu (global)
if (ke.isKey(KeyCode.F2)) {
if (tabsState.selected() == TAB_ROUTES && routesTab != null) {
actionsPopup.setPreSelectedRouteId(routesTab.selectedRouteId());
}
actionsPopup.open();
return true;
}
Expand Down Expand Up @@ -638,6 +643,12 @@ private boolean handleEvent(Event event, TuiRunner runner) {
return true;
}
}
if (event instanceof PasteEvent pe) {
if (actionsPopup.isVisible()) {
actionsPopup.handlePaste(pe.text());
return true;
}
}
if (event instanceof TickEvent) {
long now = System.currentTimeMillis();
boolean keyProcessed = false;
Expand Down Expand Up @@ -2188,8 +2199,12 @@ private void refreshDataSync() {
refreshErrorData(pids);
}

// Refresh trace data only when the Inspect tab is visible
// Refresh trace data only when the History tab is visible
if (tabsState.selected() == TAB_HISTORY) {
if (historyTab.historyRefreshRequested) {
historyTab.historyRefreshRequested = false;
refreshHistoryData(pids);
}
refreshTraceData(pids);
}
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ class HistoryTab implements MonitorTab {
private boolean historyWordWrap = true;
private int historyDetailScroll;
private int historyDetailHScroll;
volatile boolean historyRefreshRequested;

HistoryTab(MonitorContext ctx,
AtomicReference<List<TraceEntry>> traces,
Expand Down Expand Up @@ -226,6 +227,10 @@ public boolean handleKeyEvent(KeyEvent ke) {
return true;
}
if (ke.isKey(KeyCode.F5)) {
historyEntries = Collections.emptyList();
historyDetailScroll = 0;
historyDetailHScroll = 0;
historyRefreshRequested = true;
return true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,19 @@ private Style routeTopSortStyle(String column) {

// ---- Route actions ----

String selectedRouteId() {
IntegrationInfo info = ctx.findSelectedIntegration();
if (info == null || info.routes.isEmpty()) {
return null;
}
List<RouteInfo> sortedRoutes = new ArrayList<>(info.routes);
sortedRoutes.sort(this::sortRoute);
Integer sel = routeTableState.selected();
RouteInfo route = (sel != null && sel >= 0 && sel < sortedRoutes.size())
? sortedRoutes.get(sel) : sortedRoutes.get(0);
return route.routeId;
}

private String selectedRouteState() {
IntegrationInfo info = ctx.findSelectedIntegration();
if (info == null || info.routes.isEmpty()) {
Expand Down
Loading