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 0fb6d6c47ffc camel-tui: Dynamic tab visibility based on integration
features
0fb6d6c47ffc is described below
commit 0fb6d6c47ffc8373e24bf9d30864ce3291c0207d
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Jul 27 13:45:37 2026 +0200
camel-tui: Dynamic tab visibility based on integration features
Hide More tabs that are not relevant for the selected integration.
Tier 1 checks dev console IDs on classpath (kafka, resilience4j, micrometer,
opentelemetry). Tier 2 checks runtime data (browseable endpoints,
supervised route controller, datasources, HTTP endpoints, network
services, producers). The connector reports devConsoles IDs and
hasBrowseableEndpoints in the status JSON. The More popup, F2 Go-to
popup, MCP tui_get_options, and keyboard shortcuts all filter inactive
tabs. Switching integrations via F3 falls back to Overview if the
current tab becomes inactive. Old Camel versions without devConsoles
show all tabs. Also: show management endpoints by default in HTTP tab,
expand Mgmt to Management, show group label for first More group,
increase Page Up/Down jump to 10 entries.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../camel/cli/connector/LocalCliConnector.java | 13 +++
.../dsl/jbang/core/commands/tui/ActionsPopup.java | 6 ++
.../dsl/jbang/core/commands/tui/CamelMonitor.java | 17 ++++
.../dsl/jbang/core/commands/tui/GotoTabPopup.java | 41 +++++++--
.../camel/dsl/jbang/core/commands/tui/HttpTab.java | 12 +--
.../jbang/core/commands/tui/IntegrationInfo.java | 3 +
.../dsl/jbang/core/commands/tui/McpFacade.java | 2 +-
.../dsl/jbang/core/commands/tui/PopupManager.java | 50 ++++++-----
.../dsl/jbang/core/commands/tui/StatusParser.java | 15 ++++
.../dsl/jbang/core/commands/tui/TabRegistry.java | 97 ++++++++++++++++++----
.../jbang/core/commands/tui/PopupManagerTest.java | 4 +
11 files changed, 215 insertions(+), 45 deletions(-)
diff --git
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
index 9dd068726e57..5b7d11c61dd4 100644
---
a/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
+++
b/dsl/camel-cli-connector/src/main/java/org/apache/camel/cli/connector/LocalCliConnector.java
@@ -49,6 +49,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.camel.CamelContext;
import org.apache.camel.CamelContextAware;
import org.apache.camel.ConsumerTemplate;
+import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
import org.apache.camel.ExchangePattern;
import org.apache.camel.Expression;
@@ -68,6 +69,7 @@ import org.apache.camel.model.RouteDefinition;
import org.apache.camel.model.language.ExpressionDefinition;
import org.apache.camel.spi.BacklogDebugger;
import org.apache.camel.spi.BacklogTracer;
+import org.apache.camel.spi.BrowsableEndpoint;
import org.apache.camel.spi.CliConnector;
import org.apache.camel.spi.CliConnectorFactory;
import org.apache.camel.spi.ContextReloadStrategy;
@@ -1661,7 +1663,18 @@ public class LocalCliConnector extends ServiceSupport
implements CliConnector, C
root.put("sqlTrace", json);
}
}
+ JsonArray consoleIds = new JsonArray();
+ consoleIds.addAll(dcr.getConsoleIDs());
+ root.put("devConsoles", consoleIds);
+ }
+ boolean hasBrowseable = false;
+ for (Endpoint ep : getCamelContext().getEndpoints()) {
+ if (ep instanceof BrowsableEndpoint) {
+ hasBrowseable = true;
+ break;
+ }
}
+ root.put("hasBrowseableEndpoints", hasBrowseable);
// various details
JsonObject mem = collectMemory();
if (mem != null) {
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 65905a55fbf6..c5aff66eb649 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
@@ -216,6 +216,12 @@ class ActionsPopup {
settingsPopup.setTabEntries(entries);
}
+ void setActiveTabFilter(
+ java.util.function.Supplier<IntegrationInfo> integrationSupplier,
+ java.util.function.Supplier<List<TabRegistry.MoreTab>>
moreTabsSupplier) {
+ gotoTabPopup.setActiveTabFilter(integrationSupplier, moreTabsSupplier);
+ }
+
void setClearScreenAction(Runnable clearScreen) {
settingsPopup.setClearScreen(clearScreen);
}
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 14f4418c8838..e24dde92c837 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
@@ -346,6 +346,7 @@ public class CamelMonitor extends CamelCommand {
}
}
});
+ actionsPopup.setActiveTabFilter(ctx::findSelectedIntegration,
tabRegistry::moreTabs);
popupManager = new PopupManager(
ctx, this::getNonVanishingIntegrations, tabRegistry::moreTabs,
filesBrowser,
@@ -369,6 +370,22 @@ public class CamelMonitor extends CamelCommand {
public void stopSelectedProcess(boolean forceKill) {
CamelMonitor.this.stopSelectedProcess(forceKill);
}
+
+ @Override
+ public void fallbackToOverviewIfTabInactive() {
+ if (tabRegistry.selectedTabIndex() ==
TabRegistry.TAB_MORE) {
+ MonitorTab active = tabRegistry.getActiveMoreTab();
+ if (active != null) {
+ IntegrationInfo info =
ctx.findSelectedIntegration();
+ for (TabRegistry.MoreTab mt :
tabRegistry.moreTabs()) {
+ if (mt.tab() == active &&
!TabRegistry.isMoreTabActive(mt, info)) {
+
tabsState.select(TabRegistry.TAB_OVERVIEW);
+ break;
+ }
+ }
+ }
+ }
+ }
});
tabRegistry.overviewTab().setActions(new OverviewTab.OverviewActions()
{
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/GotoTabPopup.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/GotoTabPopup.java
index 633ffbf47737..ae28fa7aa250 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/GotoTabPopup.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/GotoTabPopup.java
@@ -46,16 +46,26 @@ class GotoTabPopup {
private final ListState listState = new ListState();
private final ScrollbarState scrollbarState = new ScrollbarState();
private List<TabRegistry.TabEntry> allEntries;
+ private List<TabRegistry.TabEntry> activeEntries;
private List<TabRegistry.TabEntry> filteredEntries;
private Rect popupRect;
private Runnable callback;
private TabRegistry.TabEntry pendingEntry;
+ private java.util.function.Supplier<IntegrationInfo> integrationSupplier;
+ private java.util.function.Supplier<List<TabRegistry.MoreTab>>
moreTabsSupplier;
void setTabEntries(List<TabRegistry.TabEntry> entries, Runnable callback) {
this.allEntries = entries;
this.callback = callback;
}
+ void setActiveTabFilter(
+ java.util.function.Supplier<IntegrationInfo> integrationSupplier,
+ java.util.function.Supplier<List<TabRegistry.MoreTab>>
moreTabsSupplier) {
+ this.integrationSupplier = integrationSupplier;
+ this.moreTabsSupplier = moreTabsSupplier;
+ }
+
boolean isVisible() {
return visible;
}
@@ -92,13 +102,13 @@ class GotoTabPopup {
return true;
}
if (ke.isPageUp() || ke.isKey(KeyCode.PAGE_UP)) {
- for (int i = 0; i < 5; i++) {
+ for (int i = 0; i < 10; i++) {
listState.selectPrevious();
}
return true;
}
if (ke.isPageDown() || ke.isKey(KeyCode.PAGE_DOWN)) {
- for (int i = 0; i < 5; i++) {
+ for (int i = 0; i < 10; i++) {
listState.selectNext(size);
}
return true;
@@ -247,7 +257,7 @@ class GotoTabPopup {
renderState.select(sel + 2);
}
- int total = allEntries != null ? allEntries.size() : 0;
+ int total = activeEntries != null ? activeEntries.size() : 0;
int shown = filteredEntries.size();
String title = shown == total
? " Go to Tab (" + total + ") "
@@ -289,12 +299,14 @@ class GotoTabPopup {
filteredEntries = List.of();
return;
}
+ activeEntries = filterActiveEntries(allEntries);
+ List<TabRegistry.TabEntry> active = activeEntries;
if (!filter.hasFilter()) {
- filteredEntries = new ArrayList<>(allEntries);
+ filteredEntries = new ArrayList<>(active);
} else {
filteredEntries = new ArrayList<>();
String f = filter.filter();
- for (TabRegistry.TabEntry entry : allEntries) {
+ for (TabRegistry.TabEntry entry : active) {
boolean nameMatch;
if (f.length() <= 2) {
nameMatch = entry.name().toLowerCase().contains(f);
@@ -311,6 +323,25 @@ class GotoTabPopup {
listState.select(filteredEntries.isEmpty() ? null : 0);
}
+ private List<TabRegistry.TabEntry>
filterActiveEntries(List<TabRegistry.TabEntry> entries) {
+ if (integrationSupplier == null || moreTabsSupplier == null) {
+ return entries;
+ }
+ IntegrationInfo info = integrationSupplier.get();
+ List<TabRegistry.MoreTab> moreTabs = moreTabsSupplier.get();
+ List<TabRegistry.TabEntry> active = new ArrayList<>();
+ for (TabRegistry.TabEntry entry : entries) {
+ if (entry.moreIndex() >= 0 && entry.moreIndex() < moreTabs.size())
{
+ TabRegistry.MoreTab mt = moreTabs.get(entry.moreIndex());
+ if (!TabRegistry.isMoreTabActive(mt, info)) {
+ continue;
+ }
+ }
+ active.add(entry);
+ }
+ return active;
+ }
+
private void navigateToEntry(TabRegistry.TabEntry entry) {
if (callback != null) {
pendingEntry = entry;
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
index d6327c8d8b1d..a450d58120ee 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
@@ -80,7 +80,7 @@ class HttpTab extends AbstractTableTab {
private final AtomicBoolean specLoading = new AtomicBoolean(false);
private int filter;
- private boolean showManagement;
+ private boolean showManagement = true;
private boolean showSpec;
private List<String> specLines = Collections.emptyList();
@@ -1249,7 +1249,7 @@ class HttpTab extends AbstractTableTab {
}
if (mgmtCount > 0) {
spans.add(Span.raw(" "));
- spans.add(Span.styled("Mgmt: ", Theme.label().dim()));
+ spans.add(Span.styled("Management: ", Theme.label().dim()));
spans.add(Span.raw(mgmtCount + ""));
}
spans.add(Span.raw(" "));
@@ -1265,7 +1265,7 @@ class HttpTab extends AbstractTableTab {
String produces = ep.produces != null ? ep.produces : "";
String source;
if (ep.management) {
- source = "Mgmt";
+ source = "Management";
} else if (ep.specification) {
source = "API Spec";
} else if (ep.fromRest) {
@@ -1611,8 +1611,8 @@ class HttpTab extends AbstractTableTab {
METHOD PATH TOTAL CONSUMES
PRODUCES SOURCE STATE
GET /api/users 142 application/json
application/json REST(code) Started
POST /api/users 38 application/json
application/json REST(code) Started
- GET /observe/health 97
Mgmt Started
- GET /observe/metrics 52
text/plain Mgmt Started
+ GET /observe/health 97
Management Started
+ GET /observe/metrics 52
text/plain Management Started
GET /q/openapi 15
application/json REST(contract) Started
```
@@ -1623,7 +1623,7 @@ class HttpTab extends AbstractTableTab {
- **REST(code)** — Defined in Camel REST DSL code. The
developer wrote `rest("/api").get("/users").to("direct:getUsers")` in the route
definition
- **REST(contract)** — Generated from an OpenAPI/Swagger
specification file (contract-first approach). The API structure comes from a
`.json` or `.yaml` spec file
- **HTTP** — Registered directly via the `platform-http`
component using `from("platform-http:/path")`
- - **Mgmt** — Management endpoint added automatically by Camel
for observability: health checks, metrics, OpenAPI specs, developer console
+ - **Management** — Management endpoint added automatically by
Camel for observability: health checks, metrics, OpenAPI specs, developer
console
## HTTP Probe
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 6f3b6945be2e..2cb345b043d7 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
@@ -18,6 +18,7 @@ package org.apache.camel.dsl.jbang.core.commands.tui;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
class IntegrationInfo {
String pid;
@@ -109,4 +110,6 @@ class IntegrationInfo {
long sqlTraceFailedCount;
String httpServer;
String readmeFiles;
+ Set<String> devConsoles = Set.of();
+ boolean hasBrowseableEndpoints;
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
index 3f7151ea6c89..5130e4767da6 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/McpFacade.java
@@ -313,7 +313,7 @@ class McpFacade {
}
List<TabRegistry.TabEntry> getTabEntries() {
- return tabRegistry.allTabEntries();
+ return tabRegistry.allTabEntries(ctx != null ?
ctx.findSelectedIntegration() : null);
}
List<String> getActionLabels() {
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/PopupManager.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/PopupManager.java
index 4d230b6c1497..f425cb17aebc 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/PopupManager.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/PopupManager.java
@@ -60,6 +60,8 @@ class PopupManager {
void refreshLogData();
void stopSelectedProcess(boolean forceKill);
+
+ void fallbackToOverviewIfTabInactive();
}
private final MonitorContext ctx;
@@ -100,14 +102,16 @@ class PopupManager {
private List<TabRegistry.MoreTab> buildMoreVisualList() {
List<TabRegistry.MoreTab> tabs = moreTabsSupplier.get();
+ IntegrationInfo info = ctx.findSelectedIntegration();
List<TabRegistry.MoreTab> visual = new ArrayList<>();
String currentGroup = null;
for (TabRegistry.MoreTab tab : tabs) {
+ if (!TabRegistry.isMoreTabActive(tab, info)) {
+ continue;
+ }
String group = tab.group();
if (group != null && !group.equals(currentGroup)) {
- if (currentGroup != null) {
- visual.add(null);
- }
+ visual.add(null);
currentGroup = group;
}
visual.add(tab);
@@ -129,24 +133,21 @@ class PopupManager {
if (visualIndex < 0 || visualIndex >= visual.size() ||
visual.get(visualIndex) == null) {
return -1;
}
- int moreIndex = 0;
- for (int i = 0; i < visualIndex; i++) {
- if (visual.get(i) != null) {
- moreIndex++;
- }
- }
- return moreIndex;
+ TabRegistry.MoreTab selected = visual.get(visualIndex);
+ List<TabRegistry.MoreTab> fullList = moreTabsSupplier.get();
+ return fullList.indexOf(selected);
}
private int moreToVisualIndex(int moreIndex) {
+ List<TabRegistry.MoreTab> fullList = moreTabsSupplier.get();
+ if (moreIndex < 0 || moreIndex >= fullList.size()) {
+ return 0;
+ }
+ TabRegistry.MoreTab target = fullList.get(moreIndex);
List<TabRegistry.MoreTab> visual = buildMoreVisualList();
- int count = 0;
for (int i = 0; i < visual.size(); i++) {
- if (visual.get(i) != null) {
- if (count == moreIndex) {
- return i;
- }
- count++;
+ if (target.equals(visual.get(i))) {
+ return i;
}
}
return 0;
@@ -199,7 +200,11 @@ class PopupManager {
void openMorePopup() {
showMorePopup = !showMorePopup;
if (showMorePopup) {
- morePopupState.select(lastMoreSelection);
+ int sel = lastMoreSelection;
+ if (isMoreDividerIndex(sel)) {
+ sel++;
+ }
+ morePopupState.select(sel);
}
}
@@ -373,6 +378,7 @@ class PopupManager {
ctx.selectedPid = chosen.pid;
ctx.lastSelectedName = chosen.name;
callbacks.resetIntegrationTabState();
+ callbacks.fallbackToOverviewIfTabInactive();
if (selectedTab == tabLog) {
callbacks.refreshLogData();
}
@@ -480,6 +486,7 @@ class PopupManager {
ctx.selectedPid = chosen.pid;
ctx.lastSelectedName = chosen.name;
callbacks.resetIntegrationTabState();
+ callbacks.fallbackToOverviewIfTabInactive();
if (selectedTab == tabLog) {
callbacks.refreshLogData();
}
@@ -685,15 +692,20 @@ class PopupManager {
int[] morePopupShortcut(KeyEvent ke) {
List<TabRegistry.MoreTab> tabs = moreTabsSupplier.get();
+ IntegrationInfo info = ctx.findSelectedIntegration();
List<Integer> matches = new ArrayList<>();
char pressedUpper = 0;
for (int i = 0; i < tabs.size(); i++) {
- int idx = tabs.get(i).mnemonicIndex();
+ TabRegistry.MoreTab tab = tabs.get(i);
+ if (!TabRegistry.isMoreTabActive(tab, info)) {
+ continue;
+ }
+ int idx = tab.mnemonicIndex();
if (idx < 0) {
continue;
}
- char letter = tabs.get(i).displayName().charAt(idx);
+ char letter = tab.displayName().charAt(idx);
if (ke.isChar(Character.toLowerCase(letter)) ||
ke.isChar(Character.toUpperCase(letter))) {
if (pressedUpper == 0) {
pressedUpper = Character.toUpperCase(letter);
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
index 0f6ab05993cc..e7a725f2049a 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/StatusParser.java
@@ -21,9 +21,11 @@ import java.time.Instant;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Set;
import org.apache.camel.dsl.jbang.core.common.ProcessHelper;
import org.apache.camel.dsl.jbang.core.common.VersionHelper;
@@ -676,6 +678,19 @@ final class StatusParser {
}
}
+ // Parse registered dev console IDs
+ JsonArray devConsolesArr = (JsonArray) root.get("devConsoles");
+ if (devConsolesArr != null) {
+ Set<String> ids = new LinkedHashSet<>();
+ for (Object o : devConsolesArr) {
+ ids.add(o.toString());
+ }
+ info.devConsoles = Set.copyOf(ids);
+ }
+ if (root.containsKey("hasBrowseableEndpoints")) {
+ info.hasBrowseableEndpoints =
root.getBooleanOrDefault("hasBrowseableEndpoints", false);
+ }
+
return info;
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java
index a5715381a39e..3d667e06667a 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TabRegistry.java
@@ -18,6 +18,7 @@ package org.apache.camel.dsl.jbang.core.commands.tui;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Predicate;
import dev.tamboui.widgets.tabs.TabsState;
@@ -166,28 +167,50 @@ class TabRegistry {
// The group field drives divider rendering in the More popup.
moreTabs = List.of(
// Routing
- new MoreTab(TuiIcons.TAB_BROWSE, "Browse Endpoints", "&Browse
Endpoints", browseTab, "Routing"),
+ new MoreTab(
+ TuiIcons.TAB_BROWSE, "Browse Endpoints", "&Browse
Endpoints", browseTab, "Routing",
+ List.of(), info -> info.hasBrowseableEndpoints),
new MoreTab(TuiIcons.TAB_CONSUMERS, "Consumers", "&Consumers",
consumersTab, "Routing"),
- new MoreTab(TuiIcons.TAB_HTTP, "HTTP", "&HTTP", httpTab,
"Routing"),
+ new MoreTab(
+ TuiIcons.TAB_HTTP, "HTTP", "&HTTP", httpTab, "Routing",
+ List.of("platform-http"), info ->
!info.httpEndpoints.isEmpty()),
new MoreTab(TuiIcons.TAB_INFLIGHT, "Inflight", "&Inflight",
inflightTab, "Routing"),
- new MoreTab(TuiIcons.TAB_PRODUCERS, "Producers", "&Producers",
producersTab, "Routing"),
+ new MoreTab(
+ TuiIcons.TAB_PRODUCERS, "Producers", "&Producers",
producersTab, "Routing",
+ List.of(), info -> !info.producers.isEmpty()),
new MoreTab(
TuiIcons.TAB_ROUTE_CONTROLLER, "Route Controller",
"&Route Controller", routeControllerTab,
- "Routing"),
+ "Routing",
+ List.of(), info ->
"SupervisingRouteController".equals(info.routeControllerType)),
// Observability
new MoreTab(
TuiIcons.TAB_CIRCUIT_BREAKER, "Circuit Breaker",
"&Circuit Breaker", circuitBreakerTab,
- "Observability"),
+ "Observability",
+ List.of("resilience4j", "fault-tolerance")),
new MoreTab(TuiIcons.TAB_HEALTH, "Health", "&Health",
healthTab, "Observability"),
- new MoreTab(TuiIcons.TAB_METRICS, "Metrics", "&Metrics",
metricsTab, "Observability"),
- new MoreTab(TuiIcons.TAB_NETWORK, "Network Services",
"&Network Services", networkTab, "Observability"),
+ new MoreTab(
+ TuiIcons.TAB_METRICS, "Metrics", "&Metrics",
metricsTab, "Observability",
+ List.of("micrometer")),
+ new MoreTab(
+ TuiIcons.TAB_NETWORK, "Network Services", "&Network
Services", networkTab, "Observability",
+ List.of(), info -> !info.services.isEmpty()),
new MoreTab(TuiIcons.TAB_EVENTS, "Events", "&Exchange Events",
eventTab, "Observability"),
- new MoreTab(TuiIcons.TAB_SPANS, "Spans", "&OTel Spans",
spansTab, "Observability"),
+ new MoreTab(
+ TuiIcons.TAB_SPANS, "Spans", "&OTel Spans", spansTab,
"Observability",
+ List.of("opentelemetry")),
// Data
- new MoreTab(TuiIcons.TAB_DATASOURCE, "JDBC DataSource", "&JDBC
DataSource", dataSourceTab, "Data"),
- new MoreTab(TuiIcons.TAB_KAFKA, "Kafka", "&Kafka", kafkaTab,
"Data"),
- new MoreTab(TuiIcons.TAB_SQL_QUERY, "SQL Query", "S&QL Query",
sqlQueryTab, "Data"),
- new MoreTab(TuiIcons.TAB_SQL_TRACE, "SQL Trace", "S&QL Trace",
sqlTraceTab, "Data"),
+ new MoreTab(
+ TuiIcons.TAB_DATASOURCE, "JDBC DataSource", "&JDBC
DataSource", dataSourceTab, "Data",
+ List.of(), info -> !info.dataSources.isEmpty()),
+ new MoreTab(
+ TuiIcons.TAB_KAFKA, "Kafka", "&Kafka", kafkaTab,
"Data",
+ List.of("kafka")),
+ new MoreTab(
+ TuiIcons.TAB_SQL_QUERY, "SQL Query", "S&QL Query",
sqlQueryTab, "Data",
+ List.of(), info -> !info.dataSources.isEmpty()),
+ new MoreTab(
+ TuiIcons.TAB_SQL_TRACE, "SQL Trace", "S&QL Trace",
sqlTraceTab, "Data",
+ List.of(), info -> !info.dataSources.isEmpty()),
// JVM
new MoreTab(TuiIcons.TAB_CLASSPATH, "Classpath", "&Classpath",
classpathTab, "JVM"),
new MoreTab(TuiIcons.TAB_HEAP, "Heap Memory Histogram", "Heap
&Memory Histogram", heapHistogramTab, "JVM"),
@@ -415,10 +438,25 @@ class TabRegistry {
* shortcut letter and its highlight offset are derived from {@code label}
via
* {@link TuiIcons#mnemonicIndex(String)}, so there is no separate index
or shortcut list to keep aligned.
*/
- record MoreTab(String icon, String name, String label, MonitorTab tab,
String group) {
+ /**
+ * @param requiredConsoles dev console IDs that must be present (any-of)
for this tab to be active; empty = always
+ * active
+ * @param activeWhen runtime predicate on IntegrationInfo; null =
always active (after console check)
+ */
+ record MoreTab(String icon, String name, String label, MonitorTab tab,
String group,
+ List<String> requiredConsoles, Predicate<IntegrationInfo>
activeWhen) {
+
+ MoreTab(String icon, String name, String label, MonitorTab tab, String
group,
+ List<String> requiredConsoles) {
+ this(icon, name, label, tab, group, requiredConsoles, null);
+ }
+
+ MoreTab(String icon, String name, String label, MonitorTab tab, String
group) {
+ this(icon, name, label, tab, group, List.of(), null);
+ }
MoreTab(String icon, String name, String label, MonitorTab tab) {
- this(icon, name, label, tab, null);
+ this(icon, name, label, tab, null, List.of(), null);
}
MoreTab {
@@ -446,6 +484,30 @@ class TabRegistry {
return moreTabs;
}
+ static boolean isMoreTabActive(MoreTab mt, IntegrationInfo info) {
+ if (mt.requiredConsoles().isEmpty() && mt.activeWhen() == null) {
+ return true;
+ }
+ if (info == null) {
+ return true;
+ }
+ if (!mt.requiredConsoles().isEmpty()) {
+ if (info.devConsoles.isEmpty()) {
+ // old Camel without devConsoles reporting — show all
+ } else if
(mt.requiredConsoles().stream().noneMatch(info.devConsoles::contains)) {
+ return false;
+ }
+ }
+ if (mt.activeWhen() != null && !mt.activeWhen().test(info)) {
+ return false;
+ }
+ return true;
+ }
+
+ List<MoreTab> activeMoreTabs(IntegrationInfo info) {
+ return moreTabs.stream().filter(mt -> isMoreTabActive(mt,
info)).toList();
+ }
+
/** Position of the More tab with the given programmatic {@link
MoreTab#name() name}, or -1 when absent. */
int moreTabIndex(String name) {
for (int i = 0; i < moreTabs.size(); i++) {
@@ -457,6 +519,10 @@ class TabRegistry {
}
List<TabEntry> allTabEntries() {
+ return allTabEntries(null);
+ }
+
+ List<TabEntry> allTabEntries(IntegrationInfo info) {
List<TabEntry> entries = new ArrayList<>();
entries.add(
new TabEntry(icon(TAB_OVERVIEW), "Overview",
overviewTab.description(), "1", TAB_OVERVIEW, -1, overviewTab));
@@ -472,6 +538,9 @@ class TabRegistry {
entries.add(new TabEntry(icon(TAB_ERRORS), "Errors",
errorsTab.description(), "9", TAB_ERRORS, -1, errorsTab));
for (int i = 0; i < moreTabs.size(); i++) {
MoreTab mt = moreTabs.get(i);
+ if (!isMoreTabActive(mt, info)) {
+ continue;
+ }
entries.add(new TabEntry(
mt.icon(), mt.name(), mt.tab().description(),
String.valueOf(mt.shortcut()),
TAB_MORE, i, mt.tab()));
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/PopupManagerTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/PopupManagerTest.java
index 9a8e7c4b9e32..c95c96e6008f 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/PopupManagerTest.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/PopupManagerTest.java
@@ -69,6 +69,10 @@ class PopupManagerTest {
@Override
public void stopSelectedProcess(boolean forceKill) {
}
+
+ @Override
+ public void fallbackToOverviewIfTabInactive() {
+ }
};
popupManager = new PopupManager(