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 <[email protected]> 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 <[email protected]> --- .../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) {
