You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2026/05/19 18:02:38 UTC
(camel) 04/07: camel-jbang - Add circuit breaker state machine diagram to TUI
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch worktree-more-tui-2
in repository https://gitbox.apache.org/repos/asf/camel.git
commit 45c9ff33120f10d25b7a5b60cddeeddf97597685
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Tue May 19 17:22:11 2026 +0200
camel-jbang - Add circuit breaker state machine diagram to TUI
Shows an ASCII art state diagram below the circuit breaker table for the
selected entry, illustrating the three states (CLOSED/OPEN/HALF_OPEN),
their transitions, and what each state means. The active state is
highlighted in color. Metrics are shown below the diagram.
Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
.../dsl/jbang/core/commands/tui/CamelMonitor.java | 125 ++++++++++++++++++++-
1 file changed, 124 insertions(+), 1 deletion(-)
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 0defa6007812..cc05d09012a1 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
@@ -2914,6 +2914,19 @@ public class CamelMonitor extends CamelCommand {
Cell.from(""), Cell.from(""), Cell.from(""), Cell.from(""), Cell.from("")));
}
+ // Split area: table on top, state diagram below for the selected entry
+ CircuitBreakerInfo selectedCb = null;
+ Integer sel = cbTableState.selected();
+ if (sel != null && sel >= 0 && sel < sorted.size()) {
+ selectedCb = sorted.get(sel);
+ }
+ boolean showDiagram = selectedCb != null;
+ List<Rect> chunks = showDiagram
+ ? Layout.vertical()
+ .constraints(Constraint.fill(), Constraint.length(15))
+ .split(area)
+ : List.of(area);
+
Table table = Table.builder()
.rows(rows)
.header(Row.from(
@@ -2941,7 +2954,117 @@ public class CamelMonitor extends CamelCommand {
.block(Block.builder().borderType(BorderType.ROUNDED).title(" Circuit Breaker ").build())
.build();
- frame.renderStatefulWidget(table, area, cbTableState);
+ frame.renderStatefulWidget(table, chunks.get(0), cbTableState);
+
+ if (showDiagram) {
+ renderCircuitBreakerDiagram(frame, chunks.get(1), selectedCb);
+ }
+ }
+
+ private void renderCircuitBreakerDiagram(Frame frame, Rect area, CircuitBreakerInfo cb) {
+ String state = cb.state != null ? cb.state.toLowerCase() : "";
+ boolean isClosed = state.equals("closed");
+ boolean isOpen = state.equals("open") || state.equals("forced_open");
+
+ Style closedBox = isClosed ? Style.EMPTY.fg(Color.GREEN).bold() : Style.EMPTY;
+ Style openBox = isOpen ? Style.EMPTY.fg(Color.LIGHT_RED).bold() : Style.EMPTY;
+ Style halfOpenBox = !isClosed && !isOpen ? Style.EMPTY.fg(Color.YELLOW).bold() : Style.EMPTY;
+ Style lbl = Style.EMPTY.dim();
+
+ List<Line> lines = new ArrayList<>();
+ // ┌──────────────┐ ┌──────────────┐
+ lines.add(Line.from(
+ Span.raw(" "),
+ Span.styled("┌──────────────┐", closedBox),
+ Span.raw(" "),
+ Span.styled("┌──────────────┐", openBox)));
+ // │ CLOSED │─────────────►│ OPEN │◄─┐
+ lines.add(Line.from(
+ Span.raw(" "),
+ Span.styled("│ CLOSED │", closedBox),
+ Span.raw("─────────────►"),
+ Span.styled("│ OPEN │", openBox),
+ Span.raw("◄─┐")));
+ // │ (flowing) │ failure rate │ (blocked) │ │
+ lines.add(Line.from(
+ Span.raw(" "),
+ Span.styled("│ (flowing) │", closedBox),
+ Span.styled(" failure rate ", lbl),
+ Span.styled("│ (blocked) │", openBox),
+ Span.raw(" │")));
+ // └──────▲───────┘ └───────┬──────┘ │
+ lines.add(Line.from(
+ Span.raw(" "),
+ Span.styled("└──────", closedBox),
+ Span.raw("▲"),
+ Span.styled("───────┘", closedBox),
+ Span.raw(" "),
+ Span.styled("└───────", openBox),
+ Span.raw("┬"),
+ Span.styled("──────┘", openBox),
+ Span.raw(" │")));
+ // │ │ │
+ lines.add(Line.from(Span.raw(
+ " │ │ │")));
+ // │ success wait timeout │ fail
+ lines.add(Line.from(
+ Span.raw(" │"),
+ Span.styled(" success", lbl),
+ Span.raw(" "),
+ Span.styled("wait timeout", lbl),
+ Span.raw(" │"),
+ Span.styled(" fail", lbl)));
+ // │ │ │
+ lines.add(Line.from(Span.raw(
+ " │ │ │")));
+ // │ ┌───────▼──────┐ │
+ lines.add(Line.from(
+ Span.raw(" │ "),
+ Span.styled("┌───────", halfOpenBox),
+ Span.raw("▼"),
+ Span.styled("──────┐", halfOpenBox),
+ Span.raw(" │")));
+ // └──────────────────────┤ HALF_OPEN ├──┘
+ lines.add(Line.from(
+ Span.raw(" └──────────────────────"),
+ Span.styled("┤ HALF_OPEN ├", halfOpenBox),
+ Span.raw("──┘")));
+ // │ (probe) │
+ lines.add(Line.from(
+ Span.raw(" "),
+ Span.styled("│ (probe) │", halfOpenBox)));
+ // └──────────────┘
+ lines.add(Line.from(
+ Span.raw(" "),
+ Span.styled("└──────────────┘", halfOpenBox)));
+ // Metrics
+ lines.add(Line.from(Span.raw("")));
+ lines.add(Line.from(
+ Span.raw(" "),
+ Span.styled("success:", lbl),
+ Span.raw(cb.successfulCalls + " "),
+ Span.styled("fail:", lbl),
+ Span.raw(cb.failedCalls + " "),
+ Span.styled("rate:", lbl),
+ Span.raw((cb.failureRate >= 0 ? String.format("%.0f%%", cb.failureRate) : "n/a") + " "),
+ Span.styled("pending:", lbl),
+ Span.raw(cb.bufferedCalls + " "),
+ Span.styled("rejected:", lbl),
+ Span.raw(String.valueOf(cb.notPermittedCalls))));
+
+ String title = " ";
+ if (cb.id != null && !cb.id.isEmpty()) {
+ title += cb.id;
+ }
+ if (cb.component != null) {
+ title += " (" + cb.component + ")";
+ }
+ title += " ";
+
+ frame.renderWidget(Paragraph.builder()
+ .text(Text.from(lines))
+ .block(Block.builder().borderType(BorderType.ROUNDED).title(title).build())
+ .build(), area);
}
private String cbSortLabel(String label, String column) {