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 459ea9f59b0d CAMEL-24259: camel-tui - Add Secrets tab for vault
providers
459ea9f59b0d is described below
commit 459ea9f59b0d0f1bc233577ecb57da45fcce387c
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Jul 28 11:50:51 2026 +0200
CAMEL-24259: camel-tui - Add Secrets tab for vault providers
Add a Secrets tab to the TUI that shows secrets from all configured
vault providers (AWS, Azure, GCP, HashiCorp, IBM, Kubernetes) in a
single view. Press f to force reload secrets from the vault providers.
Also add PeriodTaskScheduler.getTasks() API to enumerate scheduled
tasks, and a vault-refresh action in LocalCliConnector that triggers
all vault reload tasks using the @PeriodicTask annotation.
CAMEL-24259: camel-tui - Fix SecretsTab import and ConsumersTab render test
width
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../org/apache/camel/spi/PeriodTaskScheduler.java | 13 ++
.../impl/engine/DefaultPeriodTaskScheduler.java | 14 ++
.../camel/cli/connector/LocalCliConnector.java | 17 ++
.../jbang/core/commands/tui/IntegrationInfo.java | 1 +
.../dsl/jbang/core/commands/tui/SecretsTab.java | 235 +++++++++++++++++++++
.../dsl/jbang/core/commands/tui/StatusParser.java | 71 +++++++
.../dsl/jbang/core/commands/tui/TabRegistry.java | 7 +
.../dsl/jbang/core/commands/tui/TuiIcons.java | 1 +
.../jbang/core/commands/tui/VaultSecretInfo.java | 26 +++
.../core/commands/tui/ConsumersTabRenderTest.java | 2 +-
10 files changed, 386 insertions(+), 1 deletion(-)
diff --git
a/core/camel-api/src/main/java/org/apache/camel/spi/PeriodTaskScheduler.java
b/core/camel-api/src/main/java/org/apache/camel/spi/PeriodTaskScheduler.java
index e0b3eb6577b2..82123d1b7981 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/PeriodTaskScheduler.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/PeriodTaskScheduler.java
@@ -16,6 +16,9 @@
*/
package org.apache.camel.spi;
+import java.util.Collection;
+import java.util.Collections;
+
import org.jspecify.annotations.Nullable;
/**
@@ -55,4 +58,14 @@ public interface PeriodTaskScheduler {
*/
<T> @Nullable T getTaskByType(Class<T> type);
+ /**
+ * Gets all scheduled tasks.
+ *
+ * @return the tasks, or an empty collection if no tasks are scheduled
+ * @since 4.22
+ */
+ default Collection<Runnable> getTasks() {
+ return Collections.emptyList();
+ }
+
}
diff --git
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultPeriodTaskScheduler.java
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultPeriodTaskScheduler.java
index 24d5b26c3a75..d2f8ffb9a0fa 100644
---
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultPeriodTaskScheduler.java
+++
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultPeriodTaskScheduler.java
@@ -16,6 +16,9 @@
*/
package org.apache.camel.impl.engine;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
@@ -57,6 +60,17 @@ public final class DefaultPeriodTaskScheduler extends
TimerListenerManager imple
return null;
}
+ @Override
+ public Collection<Runnable> getTasks() {
+ List<Runnable> answer = new ArrayList<>();
+ for (TimerListener listener : getListeners()) {
+ if (listener instanceof TaskWrapper wrapper) {
+ answer.add(wrapper.getTask());
+ }
+ }
+ return answer;
+ }
+
@Override
public void addTimerListener(TimerListener listener) {
if (listener instanceof TaskWrapper) {
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 f25af3e88f69..c3c25b790d5b 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
@@ -75,12 +75,14 @@ import org.apache.camel.spi.CliConnectorFactory;
import org.apache.camel.spi.ContextReloadStrategy;
import org.apache.camel.spi.EndpointUriFactory;
import org.apache.camel.spi.Language;
+import org.apache.camel.spi.PeriodTaskScheduler;
import org.apache.camel.spi.Resource;
import org.apache.camel.spi.ResourceLoader;
import org.apache.camel.spi.ResourceReloadStrategy;
import org.apache.camel.spi.RoutesLoader;
import org.apache.camel.spi.RuntimeEndpointRegistry;
import org.apache.camel.spi.ShutdownPrepared;
+import org.apache.camel.spi.annotations.PeriodicTask;
import org.apache.camel.support.LoadOnDemandReloadStrategy;
import org.apache.camel.support.MessageHelper;
import org.apache.camel.support.PatternHelper;
@@ -396,6 +398,8 @@ public class LocalCliConnector extends ServiceSupport
implements CliConnector, C
doActionTypeConvertersTask();
} else if ("transformers".equals(action)) {
doActionTransformersTask();
+ } else if ("vault-refresh".equals(action)) {
+ doActionVaultRefreshTask();
}
} catch (Exception e) {
LOG.warn("Error executing action: {} due to: {}. This exception is
ignored.", action != null ? action : af,
@@ -1921,6 +1925,19 @@ public class LocalCliConnector extends ServiceSupport
implements CliConnector, C
return null;
}
+ private void doActionVaultRefreshTask() {
+ PeriodTaskScheduler scheduler =
PluginHelper.getPeriodTaskScheduler(camelContext);
+ if (scheduler == null) {
+ return;
+ }
+ for (Runnable task : scheduler.getTasks()) {
+ PeriodicTask pt =
task.getClass().getAnnotation(PeriodicTask.class);
+ if (pt != null && pt.value().endsWith("-refresh")) {
+ task.run();
+ }
+ }
+ }
+
private JsonObject collectVaults() {
JsonObject root = new JsonObject();
// aws-secrets is optional
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 177dc100cb88..9fcc784d5dad 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
@@ -114,4 +114,5 @@ class IntegrationInfo {
Set<String> devConsoles = Set.of();
boolean hasBrowseableEndpoints;
int transformerCount;
+ final List<VaultSecretInfo> vaultSecrets = new ArrayList<>();
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SecretsTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SecretsTab.java
new file mode 100644
index 000000000000..bbb8b9de6604
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/SecretsTab.java
@@ -0,0 +1,235 @@
+/*
+ * 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.util.ArrayList;
+import java.util.List;
+
+import dev.tamboui.layout.Constraint;
+import dev.tamboui.layout.Rect;
+import dev.tamboui.style.Style;
+import dev.tamboui.terminal.Frame;
+import dev.tamboui.text.Span;
+import dev.tamboui.tui.event.KeyEvent;
+import dev.tamboui.widgets.block.Block;
+import dev.tamboui.widgets.block.BorderType;
+import dev.tamboui.widgets.block.Borders;
+import dev.tamboui.widgets.table.Cell;
+import dev.tamboui.widgets.table.Row;
+import dev.tamboui.widgets.table.Table;
+import org.apache.camel.util.TimeUtils;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+
+import static org.apache.camel.dsl.jbang.core.commands.tui.TuiHelper.*;
+
+class SecretsTab extends AbstractTableTab {
+
+ SecretsTab(MonitorContext ctx) {
+ super(ctx, "vault", "secret");
+ }
+
+ @Override
+ protected int getRowCount() {
+ IntegrationInfo info = ctx.findSelectedIntegration();
+ return info != null ? info.vaultSecrets.size() : 0;
+ }
+
+ @Override
+ protected void renderContent(Frame frame, Rect area, IntegrationInfo info)
{
+ List<VaultSecretInfo> sorted = new ArrayList<>(info.vaultSecrets);
+ sorted.sort(this::sortSecret);
+
+ List<Row> rows = new ArrayList<>();
+ for (VaultSecretInfo vi : sorted) {
+ rows.add(Row.from(
+ Cell.from(Span.styled(" " + (vi.vault != null ? vi.vault :
""), Style.EMPTY.fg(Theme.accent()))),
+ Cell.from(vi.region != null ? vi.region : ""),
+ Cell.from(vi.secret != null ? vi.secret : ""),
+ Cell.from(printSince(vi.timestamp)),
+ Cell.from(printSince(vi.lastReload)),
+ Cell.from(printSince(vi.lastCheck))));
+ }
+
+ if (rows.isEmpty()) {
+ rows.add(emptyRow("No secrets", 6));
+ }
+
+ Table table = Table.builder()
+ .rows(rows)
+ .header(Row.from(
+ Cell.from(Span.styled(" " + sortLabel("VAULT",
"vault"), sortStyle("vault"))),
+ Cell.from(Span.styled("REGION", Style.EMPTY.bold())),
+ Cell.from(Span.styled(sortLabel("SECRET", "secret"),
sortStyle("secret"))),
+ Cell.from(Span.styled("AGE", Style.EMPTY.bold())),
+ Cell.from(Span.styled("UPDATE", Style.EMPTY.bold())),
+ Cell.from(Span.styled("CHECK", Style.EMPTY.bold()))))
+ .widths(
+ Constraint.length(16),
+ Constraint.length(16),
+ Constraint.fill(),
+ Constraint.length(10),
+ Constraint.length(10),
+ Constraint.length(10))
+ .highlightStyle(Theme.selectionBg())
+ .highlightSpacing(Table.HighlightSpacing.ALWAYS)
+
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
+ .title(" Secrets ").build())
+ .build();
+
+ lastTableArea = area;
+ frame.renderStatefulWidget(table, area, tableState);
+ renderScrollbar(frame, sorted.size());
+ }
+
+ @Override
+ protected boolean handleTabKeyEvent(KeyEvent ke) {
+ if (ke.isChar('f')) {
+ IntegrationInfo info = ctx.findSelectedIntegration();
+ if (info != null && info.pid != null) {
+ JsonObject root = new JsonObject();
+ root.put("action", "vault-refresh");
+ ctx.fireAction(info.pid, root);
+ if (ctx.notificationCallback != null) {
+ ctx.notificationCallback.accept("Secrets reload in
progress", false);
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private int sortSecret(VaultSecretInfo a, VaultSecretInfo b) {
+ int result = switch (sort) {
+ case "secret" -> {
+ String sa = a.secret != null ? a.secret : "";
+ String sb = b.secret != null ? b.secret : "";
+ yield sa.compareToIgnoreCase(sb);
+ }
+ default -> { // "vault"
+ String va = a.vault != null ? a.vault : "";
+ String vb = b.vault != null ? b.vault : "";
+ int c = va.compareToIgnoreCase(vb);
+ if (c == 0) {
+ String sa = a.secret != null ? a.secret : "";
+ String sb = b.secret != null ? b.secret : "";
+ c = sa.compareToIgnoreCase(sb);
+ }
+ yield c;
+ }
+ };
+ return sortReversed ? -result : result;
+ }
+
+ private static String printSince(long timestamp) {
+ if (timestamp == 0) {
+ return "";
+ }
+ return TimeUtils.printSince(timestamp);
+ }
+
+ @Override
+ public SelectionContext getSelectionContext() {
+ IntegrationInfo info = ctx.findSelectedIntegration();
+ if (info == null || info.vaultSecrets.isEmpty()) {
+ return null;
+ }
+ List<VaultSecretInfo> sorted = new ArrayList<>(info.vaultSecrets);
+ sorted.sort(this::sortSecret);
+ List<String> items = sorted.stream()
+ .map(v -> (v.vault != null ? v.vault : "") + "/" + (v.secret
!= null ? v.secret : ""))
+ .toList();
+ Integer sel = tableState.selected();
+ return new SelectionContext("table", items, sel != null ? sel : -1,
items.size(), "Secrets");
+ }
+
+ @Override
+ public String description() {
+ return "Secrets from cloud vault providers (AWS, Azure, GCP,
Kubernetes, HashiCorp, IBM)";
+ }
+
+ @Override
+ public String getHelpText() {
+ return """
+ # Secrets
+
+ Shows secrets resolved from cloud vault providers. Camel can
load
+ configuration values from external secret managers instead of
+ hardcoding them in properties files.
+
+ Supported providers: AWS Secrets Manager, Azure Key Vault,
+ GCP Secret Manager, HashiCorp Vault, IBM Secrets Manager,
+ Kubernetes Secrets, and Kubernetes ConfigMaps.
+
+ ## Table Columns
+
+ - **VAULT** — The cloud provider name (AWS, Azure, GCP,
Hashicorp, IBM, Kubernetes, Kubernetes-cm)
+ - **REGION** — Cloud region (AWS only, blank for other
providers)
+ - **SECRET** — The secret name/key as referenced in the route
configuration
+ - **AGE** — How long ago the secret value was last updated
+ - **UPDATE** — How long ago the last automatic reload of
secrets occurred
+ - **CHECK** — How long ago the last check for secret changes
was performed
+
+ ## Automatic Refresh
+
+ Camel can automatically detect secret changes and reload them:
+ - **AWS** — monitors via CloudTrail events
+ - **Azure** — monitors via Event Hubs
+ - **GCP** — monitors via Pub/Sub
+ - **IBM** — monitors via Event Streams (Kafka)
+ - **Kubernetes** — watches the Kubernetes API
+
+ When refresh is enabled, the UPDATE and CHECK columns show the
+ timing of these automatic operations.
+
+ ## Keys
+
+ - `Up/Down` — select secret
+ - `f` — force reload secrets from vault providers
+ - `s` — cycle sort column
+ - `S` — reverse sort order
+ """;
+ }
+
+ @Override
+ public JsonObject getTableDataAsJson() {
+ IntegrationInfo info = ctx.findSelectedIntegration();
+ if (info == null) {
+ return null;
+ }
+ JsonObject result = new JsonObject();
+ result.put("tab", "Secrets");
+ JsonArray rows = new JsonArray();
+ List<VaultSecretInfo> sorted = new ArrayList<>(info.vaultSecrets);
+ sorted.sort(this::sortSecret);
+ for (VaultSecretInfo vi : sorted) {
+ JsonObject row = new JsonObject();
+ row.put("vault", vi.vault);
+ row.put("region", vi.region);
+ row.put("secret", vi.secret);
+ row.put("age", printSince(vi.timestamp));
+ row.put("update", printSince(vi.lastReload));
+ row.put("check", printSince(vi.lastCheck));
+ rows.add(row);
+ }
+ result.put("rows", rows);
+ result.put("totalRows", info.vaultSecrets.size());
+ Integer sel = tableState.selected();
+ result.put("selectedIndex", sel != null ? sel : -1);
+ return result;
+ }
+}
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 493a8f556f58..eed18d345032 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
@@ -719,6 +719,18 @@ final class StatusParser {
info.transformerCount = trObj.getIntegerOrDefault("size", 0);
}
+ // Parse vaults / secrets
+ JsonObject vaults = (JsonObject) root.get("vaults");
+ if (vaults != null) {
+ parseVaultSecrets(vaults, "aws-secrets", "AWS", info, true);
+ parseVaultSecrets(vaults, "gcp-secrets", "GCP", info, false);
+ parseVaultSecrets(vaults, "azure-secrets", "Azure", info, false);
+ parseVaultSecrets(vaults, "ibm-secrets", "IBM", info, false);
+ parseVaultSecrets(vaults, "kubernetes-secrets", "Kubernetes",
info, false);
+ parseVaultConfigmaps(vaults, info);
+ parseVaultHashicorp(vaults, info);
+ }
+
return info;
}
@@ -1367,6 +1379,65 @@ final class StatusParser {
return TuiHelper.objToLong(o);
}
+ private static void parseVaultSecrets(
+ JsonObject vaults, String key, String vaultName, IntegrationInfo
info, boolean hasRegion) {
+ JsonObject vo = (JsonObject) vaults.get(key);
+ if (vo == null) {
+ return;
+ }
+ long lastCheck = vo.getLongOrDefault("lastCheckTimestamp", 0);
+ long lastReload = vo.getLongOrDefault("lastReloadTimestamp", 0);
+ String region = hasRegion ? vo.getString("region") : null;
+ JsonArray arr = (JsonArray) vo.get("secrets");
+ if (arr != null) {
+ for (int i = 0; i < arr.size(); i++) {
+ JsonObject jo = (JsonObject) arr.get(i);
+ VaultSecretInfo vi = new VaultSecretInfo();
+ vi.vault = vaultName;
+ vi.region = region;
+ vi.secret = jo.getString("name");
+ vi.timestamp = jo.getLongOrDefault("timestamp", 0);
+ vi.lastCheck = lastCheck;
+ vi.lastReload = lastReload;
+ info.vaultSecrets.add(vi);
+ }
+ }
+ }
+
+ private static void parseVaultConfigmaps(JsonObject vaults,
IntegrationInfo info) {
+ JsonObject vo = (JsonObject) vaults.get("kubernetes-configmaps");
+ if (vo == null) {
+ return;
+ }
+ long lastCheck = vo.getLongOrDefault("startCheckTimestamp", 0);
+ long lastReload = vo.getLongOrDefault("lastReloadTimestamp", 0);
+ JsonArray arr = (JsonArray) vo.get("configmap");
+ if (arr != null) {
+ for (int i = 0; i < arr.size(); i++) {
+ JsonObject jo = (JsonObject) arr.get(i);
+ VaultSecretInfo vi = new VaultSecretInfo();
+ vi.vault = "Kubernetes-cm";
+ vi.secret = jo.getString("name");
+ vi.timestamp = jo.getLongOrDefault("timestamp", 0);
+ vi.lastCheck = lastCheck;
+ vi.lastReload = lastReload;
+ info.vaultSecrets.add(vi);
+ }
+ }
+ }
+
+ private static void parseVaultHashicorp(JsonObject vaults, IntegrationInfo
info) {
+ JsonObject vo = (JsonObject) vaults.get("hashicorp-secrets");
+ if (vo == null) {
+ return;
+ }
+ VaultSecretInfo vi = new VaultSecretInfo();
+ vi.vault = "Hashicorp";
+ vi.lastCheck = vo.getLongOrDefault("startCheckTimestamp", 0);
+ vi.lastReload = vo.getLongOrDefault("lastReloadTimestamp", 0);
+ info.vaultSecrets.add(vi);
+ }
+
private static void parseEventArray(JsonObject eventsObj, String arrayKey,
String category, IntegrationInfo info) {
JsonArray arr = (JsonArray) eventsObj.get(arrayKey);
if (arr != null) {
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 0e930a47ef7a..c7a53b6c8d7a 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
@@ -107,6 +107,7 @@ class TabRegistry {
private SqlTraceTab sqlTraceTab;
private TypeConvertersTab typeConvertersTab;
private TransformersTab transformersTab;
+ private SecretsTab secretsTab;
private MonitorTab activeMoreTab;
private List<MoreTab> moreTabs;
@@ -136,6 +137,7 @@ class TabRegistry {
sqlTraceTab = new SqlTraceTab(ctx);
typeConvertersTab = new TypeConvertersTab(ctx);
transformersTab = new TransformersTab(ctx);
+ secretsTab = new SecretsTab(ctx);
endpointsTab = new EndpointsTab(ctx, dataService.metrics());
networkTab = new NetworkTab(ctx, dataService.metrics());
httpTab = new HttpTab(ctx);
@@ -215,6 +217,11 @@ class TabRegistry {
new MoreTab(
TuiIcons.TAB_KAFKA, "Kafka", "&Kafka", kafkaTab,
"Data",
List.of("kafka")),
+ new MoreTab(
+ TuiIcons.TAB_SECRETS, "Secrets", "&Secrets",
secretsTab, "Data",
+ List.of("aws-secrets", "azure-secrets", "gcp-secrets",
"hashicorp-secrets",
+ "ibm-secrets", "kubernetes-secrets",
"kubernetes-configmaps"),
+ info -> !info.vaultSecrets.isEmpty()),
new MoreTab(
TuiIcons.TAB_SQL_QUERY, "SQL Query", "S&QL Query",
sqlQueryTab, "Data",
List.of(), info -> !info.dataSources.isEmpty()),
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiIcons.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiIcons.java
index 4da9429eebb7..a57aacbae276 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiIcons.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/TuiIcons.java
@@ -153,6 +153,7 @@ final class TuiIcons {
static final String TAB_RECOVERY_TASKS = "🔁";
static final String TAB_ROUTE_CONTROLLER = "🚦";
static final String TAB_TYPE_CONVERTERS = "🔄";
+ static final String TAB_SECRETS = "🔑";
static final String TAB_TRANSFORMERS = "🔀";
/** Icons for {@link TabRegistry#TAB_OVERVIEW}..{@link
TabRegistry#TAB_MORE} (in order). */
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/VaultSecretInfo.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/VaultSecretInfo.java
new file mode 100644
index 000000000000..ec093801eb2f
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/VaultSecretInfo.java
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+class VaultSecretInfo {
+ String vault;
+ String region;
+ String secret;
+ long timestamp;
+ long lastCheck;
+ long lastReload;
+}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTabRenderTest.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTabRenderTest.java
index c5bfc716773a..64d7836740d2 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTabRenderTest.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/test/java/org/apache/camel/dsl/jbang/core/commands/tui/ConsumersTabRenderTest.java
@@ -59,7 +59,7 @@ class ConsumersTabRenderTest {
addConsumer("route1", "timer://hello", "Started",
"org.apache.camel.component.timer.TimerConsumer");
ConsumersTab tab = new ConsumersTab(ctx);
- String rendered = TuiTestHelper.renderToString(tab, 120, 20);
+ String rendered = TuiTestHelper.renderToString(tab, 160, 20);
assertTrue(rendered.contains("ROUTE"), "Should show ROUTE header");
assertTrue(rendered.contains("STATUS"), "Should show STATUS header");