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 1bfe091dec49 camel-jbang-tui: Extract HTTP probe into its own class
1bfe091dec49 is described below
commit 1bfe091dec492bc1c7cd2463dd8a66846c22eec5
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Jul 28 16:04:05 2026 +0200
camel-jbang-tui: Extract HTTP probe into its own class
Refactor HttpTab by extracting the HTTP probe (lightweight Postman)
into a new HttpProbe class, reducing HttpTab from 2512 to 811 lines.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../commands/tui/{HttpTab.java => HttpProbe.java} | 1175 +++---------
.../camel/dsl/jbang/core/commands/tui/HttpTab.java | 2029 ++------------------
2 files changed, 392 insertions(+), 2812 deletions(-)
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/HttpProbe.java
similarity index 65%
copy from
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
copy to
dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpProbe.java
index fe0f50517c21..df41c549381c 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/HttpProbe.java
@@ -23,10 +23,8 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Locale;
-import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -53,20 +51,15 @@ import dev.tamboui.widgets.input.TextAreaState;
import dev.tamboui.widgets.input.TextInput;
import dev.tamboui.widgets.input.TextInputState;
import dev.tamboui.widgets.paragraph.Paragraph;
-import dev.tamboui.widgets.table.Cell;
-import dev.tamboui.widgets.table.Row;
-import dev.tamboui.widgets.table.Table;
import org.apache.camel.dsl.jbang.core.common.CamelCommandHelper;
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.*;
+import static org.apache.camel.dsl.jbang.core.commands.tui.HttpTab.methodStyle;
-class HttpTab extends AbstractTableTab {
+class HttpProbe {
private static final int MOUSE_SCROLL_LINES = 3;
- private static final Set<String> OPENAPI_HTTP_VERBS
- = Set.of("get", "post", "put", "delete", "patch", "options",
"head", "trace");
private static final Pattern PATH_PARAM_PATTERN =
Pattern.compile("\\{([^}]+)}");
// Probe field constants
@@ -80,7 +73,7 @@ class HttpTab extends AbstractTableTab {
private static final int PROBE_BODY = 7;
private static final int PROBE_HISTORY = 8;
- private static final String[] PROBE_METHODS = { "GET", "POST", "PUT",
"DELETE", "PATCH", "HEAD", "OPTIONS" };
+ static final String[] PROBE_METHODS = { "GET", "POST", "PUT", "DELETE",
"PATCH", "HEAD", "OPTIONS" };
private static final String[] CONTENT_TYPES = {
"", "application/json", "application/xml", "text/plain",
"text/xml", "application/x-www-form-urlencoded",
"multipart/form-data"
@@ -90,18 +83,10 @@ class HttpTab extends AbstractTableTab {
};
private static final int MAX_PROBE_HISTORY = 20;
- private final AtomicBoolean specLoading = new AtomicBoolean(false);
-
- private int filter;
- private boolean showManagement = true;
-
- private boolean showSpec;
- private List<String> specLines = Collections.emptyList();
- private String specTitle;
- private int specScroll;
+ private final MonitorContext ctx;
// Probe mode state
- private boolean probeMode;
+ private boolean active;
private String probeRouteId;
private String probeBaseUrl;
private int probeField = PROBE_PATH;
@@ -131,235 +116,20 @@ class HttpTab extends AbstractTableTab {
private String probeResponseRawBody;
private List<String> probeResponseHeaderLines;
- private int detailPanelHeight = 10;
- private final DragSplit vSplit = new DragSplit();
-
- HttpTab(MonitorContext ctx) {
- super(ctx, "method", "path", "total", "consumes", "produces",
"source");
- }
-
- @Override
- protected int getRowCount() {
- return sortedVisibleEndpoints(ctx.findSelectedIntegration()).size();
- }
-
- boolean isProbeMode() {
- return probeMode;
- }
-
- @Override
- public boolean handleKeyEvent(KeyEvent ke) {
- if (probeMode) {
- return handleProbeKeyEvent(ke);
- }
-
- if (showSpec) {
- if (ke.isChar('c') || ke.isCancel()) {
- showSpec = false;
- } else if (ke.isUp()) {
- specScroll = Math.max(0, specScroll - 1);
- } else if (ke.isDown()) {
- specScroll++;
- } else if (ke.isPageUp() || ke.isKey(KeyCode.PAGE_UP)) {
- specScroll = Math.max(0, specScroll - 20);
- } else if (ke.isPageDown() || ke.isKey(KeyCode.PAGE_DOWN)) {
- specScroll += 20;
- } else if (ke.isHome()) {
- specScroll = 0;
- } else if (ke.isEnd()) {
- specScroll = Integer.MAX_VALUE;
- } else {
- return false;
- }
- return true;
- }
-
- return super.handleKeyEvent(ke);
- }
-
- @Override
- protected boolean handleTabKeyEvent(KeyEvent ke) {
- if (ke.isConfirm()) {
- enterProbeModeFromTable();
- return true;
- }
- if (ke.isCharIgnoreCase('f')) {
- filter = (filter + 1) % 3;
- return true;
- }
- if (ke.isCharIgnoreCase('m')) {
- showManagement = !showManagement;
- return true;
- }
- if (ke.isChar('c')) {
- loadSpecForSelectedEndpoint();
- return true;
- }
- return false;
- }
-
- @Override
- public boolean handleMouseEvent(MouseEvent me, Rect area) {
- if (!probeMode && !showSpec && vSplit.handleMouse(me, me.y())) {
- if (vSplit.isDragging() && me.kind() == MouseEventKind.DRAG) {
- detailPanelHeight = Math.max(3, Math.min(area.y() +
area.height() - me.y(), area.height() - 5));
- }
- return true;
- }
- if (!probeMode && !showSpec) {
- List<HttpEndpointInfo> visible =
sortedVisibleEndpoints(ctx.findSelectedIntegration());
- if (handleTableClick(me, lastTableArea, tableState,
visible.size())) {
- return true;
- }
- }
- if (showSpec) {
- if (me.kind() == MouseEventKind.SCROLL_UP) {
- specScroll = Math.max(0, specScroll - MOUSE_SCROLL_LINES);
- return true;
- }
- if (me.kind() == MouseEventKind.SCROLL_DOWN) {
- specScroll += MOUSE_SCROLL_LINES;
- return true;
- }
- }
- if (probeMode) {
- if (me.kind() == MouseEventKind.SCROLL_UP) {
- probeResponseScroll = Math.max(0, probeResponseScroll -
MOUSE_SCROLL_LINES);
- return true;
- }
- if (me.kind() == MouseEventKind.SCROLL_DOWN) {
- probeResponseScroll += MOUSE_SCROLL_LINES;
- return true;
- }
- }
- return false;
- }
-
- @Override
- public boolean handleEscape() {
- if (probeMode) {
- probeMode = false;
- return true;
- }
- if (showSpec) {
- showSpec = false;
- return true;
- }
- return false;
- }
-
- @Override
- public void navigateUp() {
- if (probeMode) {
- // Arrow up is handled by handleProbeKeyEvent for all fields
- return;
- }
- tableState.selectPrevious();
- }
-
- @Override
- public void navigateDown() {
- if (probeMode) {
- // Arrow down is handled by handleProbeKeyEvent for all fields
- return;
- }
- List<HttpEndpointInfo> visible =
sortedVisibleEndpoints(ctx.findSelectedIntegration());
- tableState.selectNext(visible.size());
- }
-
- @Override
- public void onIntegrationChanged() {
- showSpec = false;
- specLines = Collections.emptyList();
- specTitle = null;
- specScroll = 0;
- probeMode = false;
+ HttpProbe(MonitorContext ctx) {
+ this.ctx = ctx;
}
- @Override
- protected void renderContent(Frame frame, Rect area, IntegrationInfo info)
{
- if (probeMode) {
- renderProbe(frame, area);
- return;
- }
-
- if (showSpec) {
- renderSpec(frame, area);
- return;
- }
-
- List<HttpEndpointInfo> visible = sortedVisibleEndpoints(info);
-
- detailPanelHeight = Math.max(3, Math.min(detailPanelHeight,
area.height() - 5));
- List<Rect> chunks = Layout.vertical()
- .constraints(Constraint.fill(),
Constraint.length(detailPanelHeight))
- .split(area);
-
- renderTable(frame, chunks.get(0), visible, info);
- vSplit.setBorderPos(chunks.get(1).y());
- renderDetail(frame, chunks.get(1), visible);
+ boolean isActive() {
+ return active;
}
- @Override
- public void renderFooter(List<Span> spans) {
- if (probeMode) {
- hint(spans, "Esc", "back");
- hint(spans, "Tab", "fields");
- hint(spans, "F5", "send");
- hint(spans, "F4", probeViewRequest ? "response" : "request");
- hint(spans, "+", "param/header");
- hint(spans, "p", "pretty" + (probePrettyPrint ? " [on]" : ""));
- if (!probeHistory.isEmpty()) {
- hintLast(spans, TuiIcons.HINT_SCROLL, "history");
- }
- return;
- }
- if (showSpec) {
- hint(spans, "c/Esc", "close");
- hint(spans, TuiIcons.HINT_SCROLL, "scroll");
- hintLast(spans, "PgUp/PgDn", "page");
- return;
- }
- hint(spans, "Esc", "back");
- hint(spans, "Enter", "probe");
- hint(spans, "s", "sort");
- String[] filterLabels = { "all", "rest", "http" };
- hint(spans, "f", "filter [" + filterLabels[filter] + "]");
- hint(spans, "m", "management" + (showManagement ? " [on]" : " [off]"));
- List<HttpEndpointInfo> hVisible =
sortedVisibleEndpoints(ctx.findSelectedIntegration());
- Integer hSel = tableState.selected();
- if (hSel != null && hSel >= 0 && hSel < hVisible.size() &&
hVisible.get(hSel).specificationUri != null) {
- hintLast(spans, "c", "spec");
- }
+ boolean isSending() {
+ return probeSending.get();
}
- void handlePaste(String text) {
- if (!probeMode || probeSending.get()) {
- return;
- }
- if (probeField == PROBE_BODY) {
- probeBodyState.insert(text);
- } else {
- TextInputState target = probeActiveTextInput();
- if (target != null) {
- FormHelper.handlePaste(text, target);
- }
- }
- }
-
- // ---- Probe mode ----
-
- private void enterProbeModeFromTable() {
- List<HttpEndpointInfo> visible =
sortedVisibleEndpoints(ctx.findSelectedIntegration());
- Integer sel = tableState.selected();
- if (sel == null || sel < 0 || sel >= visible.size()) {
- return;
- }
- enterProbeMode(visible.get(sel));
- }
-
- private void enterProbeMode(HttpEndpointInfo ep) {
- probeMode = true;
+ void enter(HttpEndpointInfo ep) {
+ active = true;
probeField = PROBE_PATH;
probeRouteId = ep.routeId;
probeBaseUrl = extractBaseUrl(ep.url);
@@ -413,22 +183,13 @@ class HttpTab extends AbstractTableTab {
probeHistoryIndex = 0;
}
- private void addProbeHeader(String key, String value) {
- if (probeHeaders == null) {
- probeHeaders = new ArrayList<>();
- }
- TextInputState keyState = new TextInputState("");
- TextInputState valState = new TextInputState("");
- for (int i = 0; i < key.length(); i++) {
- keyState.insert(key.charAt(i));
- }
- for (int i = 0; i < value.length(); i++) {
- valState.insert(value.charAt(i));
- }
- probeHeaders.add(new FormHelper.HeaderEntry(keyState, valState));
+ void exit() {
+ active = false;
}
- private boolean handleProbeKeyEvent(KeyEvent ke) {
+ // ---- Key handling ----
+
+ boolean handleKeyEvent(KeyEvent ke) {
if (probeSending.get()) {
return true;
}
@@ -510,6 +271,66 @@ class HttpTab extends AbstractTableTab {
return true;
}
+ boolean handleMouseScroll(MouseEvent me) {
+ if (me.kind() == MouseEventKind.SCROLL_UP) {
+ probeResponseScroll = Math.max(0, probeResponseScroll -
MOUSE_SCROLL_LINES);
+ return true;
+ }
+ if (me.kind() == MouseEventKind.SCROLL_DOWN) {
+ probeResponseScroll += MOUSE_SCROLL_LINES;
+ return true;
+ }
+ return false;
+ }
+
+ void handlePaste(String text) {
+ if (!active || probeSending.get()) {
+ return;
+ }
+ if (probeField == PROBE_BODY) {
+ probeBodyState.insert(text);
+ } else {
+ TextInputState target = probeActiveTextInput();
+ if (target != null) {
+ FormHelper.handlePaste(text, target);
+ }
+ }
+ }
+
+ boolean setInputValue(String field, String value) {
+ String v = value != null ? value : "";
+ if ("path".equals(field)) {
+ probePathState.setText(v);
+ rescanPathParams();
+ return true;
+ }
+ if ("body".equals(field)) {
+ probeBodyState.setText(v);
+ return true;
+ }
+ if ("method".equals(field)) {
+ String upper = v.toUpperCase(Locale.ENGLISH);
+ for (int i = 0; i < PROBE_METHODS.length; i++) {
+ if (PROBE_METHODS[i].equals(upper)) {
+ probeMethodIndex = i;
+ return true;
+ }
+ }
+ return false;
+ }
+ if ("content-type".equals(field)) {
+ probeContentTypeIndex = findPresetIndex(CONTENT_TYPES, v);
+ return true;
+ }
+ if ("accept".equals(field)) {
+ probeAcceptIndex = findPresetIndex(ACCEPT_TYPES, v);
+ return true;
+ }
+ return false;
+ }
+
+ // ---- Field-specific key handlers ----
+
private boolean handleProbeMethodKeyEvent(KeyEvent ke) {
if (ke.isKey(KeyCode.TAB) || ke.isDown()) {
probeField = PROBE_PATH;
@@ -855,6 +676,23 @@ class HttpTab extends AbstractTableTab {
return true;
}
+ // ---- Header and param helpers ----
+
+ private void addProbeHeader(String key, String value) {
+ if (probeHeaders == null) {
+ probeHeaders = new ArrayList<>();
+ }
+ TextInputState keyState = new TextInputState("");
+ TextInputState valState = new TextInputState("");
+ for (int i = 0; i < key.length(); i++) {
+ keyState.insert(key.charAt(i));
+ }
+ for (int i = 0; i < value.length(); i++) {
+ valState.insert(value.charAt(i));
+ }
+ probeHeaders.add(new FormHelper.HeaderEntry(keyState, valState));
+ }
+
private void addProbeHeaderEmpty() {
if (probeHeaders == null) {
probeHeaders = new ArrayList<>();
@@ -865,6 +703,16 @@ class HttpTab extends AbstractTableTab {
probeEditingHeaderKey = true;
}
+ private void addProbeQueryParamEmpty() {
+ if (probeQueryParams == null) {
+ probeQueryParams = new ArrayList<>();
+ }
+ probeQueryParams.add(new FormHelper.HeaderEntry(new
TextInputState(""), new TextInputState("")));
+ probeField = PROBE_QUERY_PARAMS;
+ probeSelectedQueryParam = probeQueryParams.size() - 1;
+ probeEditingQueryKey = true;
+ }
+
private boolean hasProbeHeaders() {
return probeHeaders != null && !probeHeaders.isEmpty();
}
@@ -895,6 +743,8 @@ class HttpTab extends AbstractTableTab {
return null;
}
+ // ---- Field navigation ----
+
private void advanceProbeField() {
goToNextProbeFieldFrom(probeField);
}
@@ -1019,6 +869,8 @@ class HttpTab extends AbstractTableTab {
}
}
+ // ---- Path param scanning ----
+
private List<PathParam> scanPathParams(String path) {
if (path == null || path.isEmpty()) {
return null;
@@ -1041,7 +893,6 @@ class HttpTab extends AbstractTableTab {
probePathParams = null;
return;
}
- // Preserve existing values for matching param names
if (probePathParams != null) {
for (PathParam np : newParams) {
for (PathParam op : probePathParams) {
@@ -1055,44 +906,20 @@ class HttpTab extends AbstractTableTab {
probePathParams = newParams;
}
- private void addProbeQueryParamEmpty() {
- if (probeQueryParams == null) {
- probeQueryParams = new ArrayList<>();
- }
- probeQueryParams.add(new FormHelper.HeaderEntry(new
TextInputState(""), new TextInputState("")));
- probeField = PROBE_QUERY_PARAMS;
- probeSelectedQueryParam = probeQueryParams.size() - 1;
- probeEditingQueryKey = true;
- }
-
- private static int findPresetIndex(String[] presets, String value) {
- if (value == null || value.isEmpty()) {
- return 0;
- }
- for (int i = 1; i < presets.length; i++) {
- if (presets[i].equalsIgnoreCase(value) ||
value.contains(presets[i])) {
- return i;
- }
- }
- return 0;
- }
+ // ---- History replay ----
private void replayHistoryEntry(ProbeHistoryEntry entry) {
- // Method
for (int i = 0; i < PROBE_METHODS.length; i++) {
if (PROBE_METHODS[i].equals(entry.method)) {
probeMethodIndex = i;
break;
}
}
- // Path
probePathState.setText(entry.path);
- // Path params: scan template then extract values from resolved path
rescanPathParams();
if (probePathParams != null && entry.resolvedPath != null) {
extractPathParamValues(entry.path, entry.resolvedPath);
}
- // Query params
probeQueryParams = null;
if (entry.queryParams != null) {
probeQueryParams = new ArrayList<>();
@@ -1104,10 +931,8 @@ class HttpTab extends AbstractTableTab {
}
probeSelectedQueryParam = 0;
probeEditingQueryKey = true;
- // Content-Type and Accept
probeContentTypeIndex = entry.contentTypeIndex;
probeAcceptIndex = entry.acceptIndex;
- // Headers
probeHeaders = null;
probeSelectedHeader = 0;
probeEditingHeaderKey = true;
@@ -1116,9 +941,7 @@ class HttpTab extends AbstractTableTab {
addProbeHeader(he.keyInput().text(), he.valueInput().text());
}
}
- // Body
probeBodyState.setText(entry.body != null ? entry.body : "");
- // Focus on method so user can review before sending
probeField = PROBE_METHOD;
probeViewRequest = true;
}
@@ -1127,7 +950,6 @@ class HttpTab extends AbstractTableTab {
if (probePathParams == null || probePathParams.isEmpty()) {
return;
}
- // Build a regex from the template, capturing each {xxx} group
StringBuilder regex = new StringBuilder();
Matcher m = PATH_PARAM_PATTERN.matcher(templatePath);
int last = 0;
@@ -1158,6 +980,8 @@ class HttpTab extends AbstractTableTab {
}
}
+ // ---- Request execution ----
+
private void doProbeRequest() {
if (ctx.runner == null || probeBaseUrl == null) {
return;
@@ -1175,7 +999,6 @@ class HttpTab extends AbstractTableTab {
String method = PROBE_METHODS[probeMethodIndex];
String path = probePathState.text();
- // Replace {xxx} placeholders with param values
if (hasPathParams()) {
for (PathParam pp : probePathParams) {
String val = pp.input.text();
@@ -1185,7 +1008,6 @@ class HttpTab extends AbstractTableTab {
}
}
- // Append query params
if (hasQueryParams()) {
StringBuilder sb = new StringBuilder(path);
char sep = path.contains("?") ? '&' : '?';
@@ -1217,7 +1039,6 @@ class HttpTab extends AbstractTableTab {
String sendBody = body;
String baseUrl = probeBaseUrl;
- // Build headers: Content-Type + Accept presets, then manual headers
List<FormHelper.HeaderEntry> allHeaders = new ArrayList<>();
if (probeContentTypeIndex > 0) {
allHeaders.add(new FormHelper.HeaderEntry(
@@ -1238,7 +1059,6 @@ class HttpTab extends AbstractTableTab {
}
List<FormHelper.HeaderEntry> hdrs = allHeaders.isEmpty() ? null :
allHeaders;
- // Snapshot manual headers, query params, and indices for history
List<FormHelper.HeaderEntry> manualHeaderSnapshot =
snapshotEntries(probeHeaders);
List<FormHelper.HeaderEntry> qpSnapshot =
snapshotEntries(probeQueryParams);
int ctIdx = probeContentTypeIndex;
@@ -1256,19 +1076,6 @@ class HttpTab extends AbstractTableTab {
});
}
- private static List<FormHelper.HeaderEntry>
snapshotEntries(List<FormHelper.HeaderEntry> entries) {
- if (entries == null || entries.isEmpty()) {
- return null;
- }
- List<FormHelper.HeaderEntry> snap = new ArrayList<>();
- for (FormHelper.HeaderEntry he : entries) {
- snap.add(new FormHelper.HeaderEntry(
- new TextInputState(he.keyInput().text()),
- new TextInputState(he.valueInput().text())));
- }
- return snap;
- }
-
private void doProbeRequestInBackground(
String baseUrl, String method, String resolvedPath, String
originalPath,
String body, List<FormHelper.HeaderEntry> allHeaders,
@@ -1301,7 +1108,6 @@ class HttpTab extends AbstractTableTab {
queryParams, contentTypeIdx, acceptIdx,
httpStatus, result.elapsed(), statusText, error);
- // Apply results on render thread
String finalRawBody = result.body();
if (ctx.runner != null) {
@@ -1314,7 +1120,6 @@ class HttpTab extends AbstractTableTab {
probeResponseScroll = 0;
rebuildResponseLines();
- // Add to history (most recent first)
probeHistory.add(0, histEntry);
if (probeHistory.size() > MAX_PROBE_HISTORY) {
probeHistory.remove(probeHistory.size() - 1);
@@ -1324,11 +1129,12 @@ class HttpTab extends AbstractTableTab {
}
}
+ // ---- Helpers ----
+
private static String extractBaseUrl(String url) {
if (url == null) {
return "http://localhost:8080";
}
- // Extract scheme + host + port from full URL like
http://0.0.0.0:8080/api/hello
try {
URI uri = URI.create(url);
String host = uri.getHost();
@@ -1346,6 +1152,45 @@ class HttpTab extends AbstractTableTab {
}
}
+ private static int findPresetIndex(String[] presets, String value) {
+ if (value == null || value.isEmpty()) {
+ return 0;
+ }
+ for (int i = 1; i < presets.length; i++) {
+ if (presets[i].equalsIgnoreCase(value) ||
value.contains(presets[i])) {
+ return i;
+ }
+ }
+ return 0;
+ }
+
+ private static List<FormHelper.HeaderEntry>
snapshotEntries(List<FormHelper.HeaderEntry> entries) {
+ if (entries == null || entries.isEmpty()) {
+ return null;
+ }
+ List<FormHelper.HeaderEntry> snap = new ArrayList<>();
+ for (FormHelper.HeaderEntry he : entries) {
+ snap.add(new FormHelper.HeaderEntry(
+ new TextInputState(he.keyInput().text()),
+ new TextInputState(he.valueInput().text())));
+ }
+ return snap;
+ }
+
+ private String buildFullUrl() {
+ String base = probeBaseUrl != null ? probeBaseUrl : "";
+ String path = probePathState.text();
+ if (hasPathParams()) {
+ for (PathParam pp : probePathParams) {
+ String val = pp.input.text();
+ if (!val.isEmpty()) {
+ path = path.replace("{" + pp.name + "}", val);
+ }
+ }
+ }
+ return base + path;
+ }
+
private void rebuildResponseLines() {
List<String> lines = new ArrayList<>();
if (probeResponseHeaderLines != null) {
@@ -1376,9 +1221,9 @@ class HttpTab extends AbstractTableTab {
return CamelCommandHelper.valueAsStringPretty(text, false);
}
- // ---- Probe rendering ----
+ // ---- Rendering ----
- private void renderProbe(Frame frame, Rect area) {
+ void render(Frame frame, Rect area) {
frame.renderWidget(Clear.INSTANCE, area);
Rect inner = area;
@@ -1408,6 +1253,18 @@ class HttpTab extends AbstractTableTab {
renderProbeHistory(frame, chunks.get(2));
}
+ void renderFooter(List<Span> spans) {
+ TuiHelper.hint(spans, "Esc", "back");
+ TuiHelper.hint(spans, "Tab", "fields");
+ TuiHelper.hint(spans, "F5", "send");
+ TuiHelper.hint(spans, "F4", probeViewRequest ? "response" : "request");
+ TuiHelper.hint(spans, "+", "param/header");
+ TuiHelper.hint(spans, "p", "pretty" + (probePrettyPrint ? " [on]" :
""));
+ if (!probeHistory.isEmpty()) {
+ TuiHelper.hintLast(spans, TuiIcons.HINT_SCROLL, "history");
+ }
+ }
+
private void renderProbeRequest(Frame frame, Rect area) {
String method = PROBE_METHODS[probeMethodIndex];
Title title = Title.from(Line.from(
@@ -1472,7 +1329,6 @@ class HttpTab extends AbstractTableTab {
isSelected || (i == 0 && probeField ==
PROBE_PATH_PARAMS));
int fieldX = innerX + labelW;
- // Show param name as prefix
String paramLabel = "{" + pp.name + "}: ";
int paramLabelW = Math.min(paramLabel.length(), 15);
Rect paramLabelArea = new Rect(fieldX, row, paramLabelW, 1);
@@ -1521,9 +1377,9 @@ class HttpTab extends AbstractTableTab {
// Body input (multi-line TextArea)
row++;
- int bodyHeight = 6;
+ int bodyH = 6;
FormHelper.renderLabel(frame, innerX, row, labelW, "Body:", probeField
== PROBE_BODY);
- Rect bodyArea = new Rect(innerX + labelW, row, fieldW, bodyHeight);
+ Rect bodyArea = new Rect(innerX + labelW, row, fieldW, bodyH);
Style bodyBorderStyle = probeField == PROBE_BODY ?
Style.EMPTY.fg(Theme.accent()) : Theme.muted();
Block bodyBlock = Block.builder()
.borders(Borders.ALL)
@@ -1543,20 +1399,6 @@ class HttpTab extends AbstractTableTab {
}
}
- private String buildFullUrl() {
- String base = probeBaseUrl != null ? probeBaseUrl : "";
- String path = probePathState.text();
- if (hasPathParams()) {
- for (PathParam pp : probePathParams) {
- String val = pp.input.text();
- if (!val.isEmpty()) {
- path = path.replace("{" + pp.name + "}", val);
- }
- }
- }
- return base + path;
- }
-
private void renderCycler(
Frame frame, int x, int row, int labelW, int fieldW,
String label, String[] options, int activeIndex, boolean selected)
{
@@ -1625,7 +1467,6 @@ class HttpTab extends AbstractTableTab {
private void renderProbeResponse(Frame frame, Rect area) {
boolean hasResponse = probeResponseStatus != null;
- // Build tab header title
Title title;
if (!hasResponse) {
title = Title.from(Line.from(Span.styled(" Request ",
Style.EMPTY.bold())));
@@ -1656,7 +1497,6 @@ class HttpTab extends AbstractTableTab {
Span.styled(statusSuffix + " ", statusColor.bold())));
}
- // Show request preview (before first send, or when request tab is
active)
if (!hasResponse || probeViewRequest) {
List<Line> preview = buildRequestPreview();
frame.renderWidget(
@@ -1668,7 +1508,6 @@ class HttpTab extends AbstractTableTab {
return;
}
- // Show response
if (probeResponseLines == null || probeResponseLines.isEmpty()) {
frame.renderWidget(
Paragraph.builder()
@@ -1724,7 +1563,6 @@ class HttpTab extends AbstractTableTab {
String method = PROBE_METHODS[probeMethodIndex];
String fullUrl = buildFullUrl();
- // Add query params to the preview URL
if (hasQueryParams()) {
StringBuilder sb = new StringBuilder(fullUrl);
char sep = fullUrl.contains("?") ? '&' : '?';
@@ -1744,7 +1582,6 @@ class HttpTab extends AbstractTableTab {
Span.raw(" " + fullUrl)));
lines.add(Line.from(Span.raw("")));
- // Headers
String ct = CONTENT_TYPES[probeContentTypeIndex];
if (!ct.isEmpty()) {
lines.add(Line.from(
@@ -1769,7 +1606,6 @@ class HttpTab extends AbstractTableTab {
}
}
- // Body
String body = probeBodyState.text();
if (body != null && !body.isEmpty()) {
lines.add(Line.from(Span.raw("")));
@@ -1806,7 +1642,6 @@ class HttpTab extends AbstractTableTab {
visibleLines = 1;
}
- // Ensure selected index is visible
int start = 0;
if (probeHistoryIndex >= visibleLines) {
start = probeHistoryIndex - visibleLines + 1;
@@ -1868,416 +1703,81 @@ class HttpTab extends AbstractTableTab {
return Theme.error().bold();
}
- // ---- Existing table/spec methods ----
-
- List<HttpEndpointInfo> sortedVisibleEndpoints(IntegrationInfo info) {
- List<HttpEndpointInfo> visible = visibleEndpoints(info);
- visible.sort((a, b) -> {
- int result = switch (sort) {
- case "path" -> compareStr(a.path, b.path);
- case "total" -> Long.compare(b.hits, a.hits);
- case "source" -> Boolean.compare(b.fromRest, a.fromRest);
- case "consumes" -> compareStr(a.consumes, b.consumes);
- case "produces" -> compareStr(a.produces, b.produces);
- default -> compareStr(a.method, b.method);
- };
- return sortReversed ? -result : result;
- });
- return visible;
- }
+ // ---- JSON serialization ----
- private List<HttpEndpointInfo> visibleEndpoints(IntegrationInfo info) {
- if (info == null) {
- return Collections.emptyList();
+ JsonObject toJson() {
+ JsonObject result = new JsonObject();
+ result.put("tab", "HTTP");
+ result.put("probeMode", true);
+ result.put("method", PROBE_METHODS[probeMethodIndex]);
+ result.put("path", probePathState.text());
+ String ct = CONTENT_TYPES[probeContentTypeIndex];
+ if (!ct.isEmpty()) {
+ result.put("contentType", ct);
}
- List<HttpEndpointInfo> result = new ArrayList<>();
- for (HttpEndpointInfo ep : info.httpEndpoints) {
- if (ep.management && !showManagement) {
- continue;
- }
- if (filter == 1 && !ep.fromRest) {
- continue;
- }
- if (filter == 2 && ep.fromRest) {
- continue;
- }
- result.add(ep);
+ String acc = ACCEPT_TYPES[probeAcceptIndex];
+ if (!acc.isEmpty()) {
+ result.put("accept", acc);
}
- return result;
- }
-
- static Style methodStyle(String method) {
- if (method == null) {
- return Style.EMPTY;
- }
- String m = method.split(",")[0].trim().toUpperCase(Locale.ENGLISH);
- return switch (m) {
- case "GET" -> Theme.success();
- case "POST" -> Theme.label();
- case "PUT" -> Style.EMPTY.fg(Theme.accent());
- case "DELETE" -> Theme.error();
- case "PATCH" -> Theme.warning();
- default -> Style.EMPTY.dim();
- };
- }
-
- private Title buildTableTitle(IntegrationInfo info, List<HttpEndpointInfo>
visible) {
- List<Span> spans = new ArrayList<>();
- spans.add(Span.raw(" HTTP Services [" + visible.size() + "]"));
- if (info.httpServer != null) {
- spans.add(Span.raw(" "));
- spans.add(Span.styled("Server: ", Theme.muted()));
- spans.add(Span.styled(info.httpServer,
Style.EMPTY.fg(Theme.accent())));
- }
- long restCount = info.httpEndpoints.stream().filter(e -> e.fromRest &&
!e.specification).count();
- long specCount = info.httpEndpoints.stream().filter(e ->
e.specification).count();
- long httpCount = info.httpEndpoints.stream().filter(e -> !e.fromRest
&& !e.management).count();
- long mgmtCount = info.httpEndpoints.stream().filter(e ->
e.management).count();
- if (restCount > 0) {
- spans.add(Span.raw(" "));
- spans.add(Span.styled("REST: ", Theme.success()));
- spans.add(Span.raw(restCount + ""));
- }
- if (specCount > 0) {
- spans.add(Span.raw(" "));
- spans.add(Span.styled("Spec: ", Theme.notice()));
- spans.add(Span.raw(specCount + ""));
- }
- if (httpCount > 0) {
- spans.add(Span.raw(" "));
- spans.add(Span.styled("HTTP: ", Style.EMPTY.fg(Theme.accent())));
- spans.add(Span.raw(httpCount + ""));
- }
- if (mgmtCount > 0) {
- spans.add(Span.raw(" "));
- spans.add(Span.styled("Management: ", Theme.label().dim()));
- spans.add(Span.raw(mgmtCount + ""));
- }
- spans.add(Span.raw(" "));
- return Title.from(Line.from(spans));
- }
-
- private void renderTable(Frame frame, Rect area, List<HttpEndpointInfo>
visible, IntegrationInfo info) {
- List<Row> rows = new ArrayList<>();
- for (HttpEndpointInfo ep : visible) {
- String method = ep.method != null ? ep.method : "";
- String path = ep.path != null ? ep.path : (ep.url != null ? ep.url
: "");
- String consumes = ep.consumes != null ? ep.consumes : "";
- String produces = ep.produces != null ? ep.produces : "";
- String source;
- if (ep.management) {
- source = "Management";
- } else if (ep.specification) {
- source = "API Spec";
- } else if (ep.fromRest) {
- source = ep.contractFirst ? "REST(contract)" : "REST(code)";
- } else {
- source = "HTTP";
+ if (hasPathParams()) {
+ JsonObject pp = new JsonObject();
+ for (PathParam p : probePathParams) {
+ pp.put(p.name, p.input.text());
}
- String state = ep.state != null ? ep.state : "";
- String hitsStr = ep.hits > 0 ? String.valueOf(ep.hits) : "";
- rows.add(Row.from(
- Cell.from(Span.styled(method, methodStyle(method))),
- Cell.from(Span.styled(path,
- ep.url != null ? Theme.info().hyperlink(ep.url) :
Style.EMPTY)),
- rightCell(hitsStr, 8),
- Cell.from(consumes),
- Cell.from(produces),
- Cell.from(Span.styled(source,
- ep.specification ? Theme.notice()
- : ep.fromRest ? Theme.success()
- : Style.EMPTY.fg(Theme.accent()))),
- Cell.from(Span.styled(state,
- "Stopped".equals(state) ? Theme.error() :
Style.EMPTY))));
- }
-
- Title title = buildTableTitle(info, visible);
-
- Row header = Row.from(
- Cell.from(Span.styled(sortLabel("METHOD", "method"),
sortStyle("method"))),
- Cell.from(Span.styled(sortLabel("PATH", "path"),
sortStyle("path"))),
- rightCell(sortLabel("TOTAL", "total"), 8, sortStyle("total")),
- Cell.from(Span.styled(sortLabel("CONSUMES", "consumes"),
sortStyle("consumes"))),
- Cell.from(Span.styled(sortLabel("PRODUCES", "produces"),
sortStyle("produces"))),
- Cell.from(Span.styled(sortLabel("SOURCE", "source"),
sortStyle("source"))),
- Cell.from(Span.styled("STATE", Style.EMPTY.bold())));
-
- Table table = Table.builder()
- .rows(rows)
- .header(header)
- .widths(
- Constraint.length(12),
- Constraint.fill(),
- Constraint.length(8),
- Constraint.length(30),
- Constraint.length(30),
- Constraint.length(15),
- Constraint.length(9))
- .highlightStyle(Theme.selectionBg())
- .highlightSpacing(Table.HighlightSpacing.ALWAYS)
-
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).build())
- .build();
-
- lastTableArea = area;
- frame.renderStatefulWidget(table, area, tableState);
- renderScrollbar(frame, visible.size());
- }
-
- private void renderDetail(Frame frame, Rect area, List<HttpEndpointInfo>
visible) {
- Integer sel = tableState.selected();
- if (sel == null || sel < 0 || sel >= visible.size()) {
- frame.renderWidget(
- Paragraph.builder()
- .text(Text.from(Line.from(
- Span.styled(" Select an endpoint to view
details",
- Style.EMPTY.dim()))))
-
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
- .title(" Detail ").build())
- .build(),
- area);
- return;
- }
-
- HttpEndpointInfo ep = visible.get(sel);
- List<Span> titleSpans = new ArrayList<>();
- if (ep.method != null) {
- titleSpans.add(Span.raw(" "));
- titleSpans.add(Span.styled(ep.method,
methodStyle(ep.method).bold()));
- titleSpans.add(Span.raw(" "));
+ result.put("pathParams", pp);
}
- if (ep.path != null) {
- titleSpans.add(Span.raw(ep.path + " "));
- }
- Title detailTitle = Title.from(Line.from(titleSpans));
-
- List<Line> lines = new ArrayList<>();
- if (ep.url != null && !ep.url.isEmpty()) {
- lines.add(Line.from(
- Span.styled(String.format(" %-10s ", "URL:"),
Theme.muted()),
- Span.styled(ep.url, Theme.info().hyperlink(ep.url))));
- }
- addDetailLine(lines, "Consumes", ep.consumes);
- addDetailLine(lines, "Produces", ep.produces);
- String sourceStr;
- if (ep.management) {
- sourceStr = "Platform-HTTP (management)";
- } else if (ep.specification) {
- sourceStr = "REST DSL (API specification - " + (ep.contractFirst ?
"contract-first" : "code-first") + ")";
- } else if (ep.fromRest) {
- sourceStr = "REST DSL (" + (ep.contractFirst ? "contract-first" :
"code-first") + ")";
- } else {
- sourceStr = "Platform-HTTP";
- }
- addDetailLine(lines, "Source", sourceStr);
- if (ep.routeId != null) {
- addDetailLine(lines, "Route", ep.routeId);
- }
- if (ep.operationId != null) {
- addDetailLine(lines, "Operation", ep.operationId);
- }
- if (ep.specificationUri != null) {
- addDetailLine(lines, "Spec", ep.specificationUri);
- }
- if (ep.state != null) {
- addDetailLine(lines, "State", ep.state);
- }
- if (ep.inType != null) {
- addDetailLine(lines, "In type", ep.inType);
- }
- if (ep.outType != null) {
- addDetailLine(lines, "Out type", ep.outType);
- }
- if (ep.description != null) {
- addDetailLine(lines, "Desc", ep.description);
- }
-
- frame.renderWidget(
- Paragraph.builder()
- .text(Text.from(lines))
-
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(detailTitle).build())
- .build(),
- area);
- }
-
- private static void addDetailLine(List<Line> lines, String label, String
value) {
- if (value == null || value.isEmpty()) {
- return;
- }
- lines.add(Line.from(
- Span.styled(String.format(" %-10s ", label + ":"),
Theme.muted()),
- Span.raw(value)));
- }
-
- private void loadSpecForSelectedEndpoint() {
- if (ctx.selectedPid == null || ctx.runner == null) {
- return;
- }
- List<HttpEndpointInfo> visible =
sortedVisibleEndpoints(ctx.findSelectedIntegration());
- Integer sel = tableState.selected();
- if (sel == null || sel < 0 || sel >= visible.size()) {
- return;
- }
- HttpEndpointInfo ep = visible.get(sel);
- if (ep.specificationUri == null) {
- return;
- }
- if (!specLoading.compareAndSet(false, true)) {
- return;
- }
-
- specLines = List.of("(Loading spec...)");
- specTitle = ep.specificationUri;
- specScroll = 0;
- showSpec = true;
-
- String pid = ctx.selectedPid;
- String specUri = ep.specificationUri;
- String operationId = ep.operationId;
-
- ctx.backgroundExecutor.execute(() -> {
- try {
- loadSpecInBackground(pid, specUri, operationId);
- } finally {
- specLoading.set(false);
+ if (hasQueryParams()) {
+ JsonArray qp = new JsonArray();
+ for (FormHelper.HeaderEntry e : probeQueryParams) {
+ JsonObject entry = new JsonObject();
+ entry.put("key", e.keyInput().text());
+ entry.put("value", e.valueInput().text());
+ qp.add(entry);
}
- });
- }
-
- private void loadSpecInBackground(String pid, String specUri, String
operationId) {
- JsonObject root = new JsonObject();
- root.put("action", "rest-spec");
- root.put("filter", specUri);
-
- JsonObject jo = ctx.executeAction(pid, root, 5000);
-
- if (jo == null) {
- applySpecResult(specUri, List.of("(No response from
integration)"), 0);
- return;
- }
-
- JsonArray specs = (JsonArray) jo.get("specs");
- if (specs == null || specs.isEmpty()) {
- applySpecResult(specUri, List.of("(No spec content available for:
" + specUri + ")"), 0);
- return;
- }
-
- JsonObject specObj = (JsonObject) specs.get(0);
- String content = specObj.getString("content");
- if (content == null || content.isBlank()) {
- applySpecResult(specUri, List.of("(Empty spec content for: " +
specUri + ")"), 0);
- return;
+ result.put("queryParams", qp);
}
-
- List<String> lines = List.of(content.split("\n", -1));
-
- int scrollTo = 0;
- if (operationId != null) {
- int opIdLine = -1;
- for (int i = 0; i < lines.size(); i++) {
- String line = lines.get(i);
- if (line.contains("operationId") &&
line.contains(operationId)) {
- opIdLine = i;
- break;
- }
- }
- if (opIdLine >= 0) {
- scrollTo = findOperationDeclarationLine(lines, opIdLine);
+ if (hasProbeHeaders()) {
+ JsonArray hd = new JsonArray();
+ for (FormHelper.HeaderEntry e : probeHeaders) {
+ JsonObject entry = new JsonObject();
+ entry.put("key", e.keyInput().text());
+ entry.put("value", e.valueInput().text());
+ hd.add(entry);
}
+ result.put("headers", hd);
}
-
- applySpecResult(specUri, lines, scrollTo);
- }
-
- private static int findOperationDeclarationLine(List<String> lines, int
opIdLine) {
- int opIdIndent = leadingSpaces(lines.get(opIdLine));
- for (int i = opIdLine - 1; i >= 0; i--) {
- String raw = lines.get(i);
- String trimmed = raw.trim();
- if (trimmed.isEmpty()) {
- continue;
- }
- int indent = leadingSpaces(raw);
- if (indent >= opIdIndent) {
- continue;
- }
- String lower = trimmed.toLowerCase(Locale.ENGLISH);
- for (String verb : OPENAPI_HTTP_VERBS) {
- if (lower.equals(verb + ":") || lower.startsWith(verb + ": ")
- || lower.equals("\"" + verb + "\":") ||
lower.startsWith("\"" + verb + "\": ")) {
- return Math.max(0, i - 1);
- }
- }
- break;
+ String body = probeBodyState.text();
+ if (body != null && !body.isEmpty()) {
+ result.put("body", body);
}
- return Math.max(0, opIdLine - 2);
- }
-
- private static int leadingSpaces(String line) {
- int count = 0;
- for (int i = 0; i < line.length(); i++) {
- char c = line.charAt(i);
- if (c == ' ') {
- count++;
- } else if (c == '\t') {
- count += 2;
- } else {
- break;
- }
+ if (probeResponseStatus != null) {
+ result.put("responseStatus", probeResponseStatus);
}
- return count;
- }
-
- private void applySpecResult(String specUri, List<String> lines, int
scrollTo) {
- if (ctx.runner == null) {
- return;
+ if (probeResponseElapsed > 0) {
+ result.put("responseElapsed", probeResponseElapsed);
}
- ctx.runner.runOnRenderThread(() -> {
- if (!showSpec) {
- return;
- }
- specTitle = specUri;
- specLines = lines;
- specScroll = scrollTo;
- });
- }
-
- private void renderSpec(Frame frame, Rect area) {
- String title = " Spec [" + (specTitle != null ? specTitle : "") + "] ";
-
- int visibleLines = area.height() - 2;
- if (visibleLines < 1) {
- visibleLines = 1;
+ if (probeResponseRawBody != null && !probeResponseRawBody.isEmpty()) {
+ result.put("responseBody", probeResponseRawBody);
}
- int maxScroll = Math.max(0, specLines.size() - visibleLines);
- specScroll = Math.min(specScroll, maxScroll);
-
- int end = Math.min(specScroll + visibleLines, specLines.size());
- List<Line> visible = new ArrayList<>();
- for (int i = specScroll; i < end; i++) {
- visible.add(Line.from(Span.raw(specLines.get(i))));
+ if (!probeHistory.isEmpty()) {
+ JsonArray hist = new JsonArray();
+ for (ProbeHistoryEntry he : probeHistory) {
+ JsonObject entry = new JsonObject();
+ entry.put("method", he.method);
+ entry.put("path", he.path);
+ entry.put("statusCode", he.statusCode);
+ entry.put("elapsed", he.elapsed);
+ if (he.error) {
+ entry.put("error", true);
+ }
+ hist.add(entry);
+ }
+ result.put("history", hist);
}
-
- frame.renderWidget(
- Paragraph.builder()
- .text(Text.from(visible))
-
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).build())
- .build(),
- area);
+ return result;
}
- @Override
- public SelectionContext getSelectionContext() {
- IntegrationInfo info = ctx.findSelectedIntegration();
- List<HttpEndpointInfo> visible = sortedVisibleEndpoints(info);
- if (visible.isEmpty()) {
- return null;
- }
- List<String> items = visible.stream()
- .map(ep -> (ep.method != null ? ep.method : "") + " " +
(ep.path != null ? ep.path : ""))
- .toList();
- Integer sel = tableState.selected();
- return new SelectionContext("table", items, sel != null ? sel : -1,
items.size(), "HTTP");
- }
+ // ---- Inner records ----
record PathParam(String name, TextInputState input) {
}
@@ -2290,223 +1790,4 @@ class HttpTab extends AbstractTableTab {
int statusCode, long elapsed,
String statusText, boolean error) {
}
-
- @Override
- public String description() {
- return "HTTP endpoint probe — lightweight Postman for testing
REST/HTTP endpoints";
- }
-
- @Override
- public String getHelpText() {
- return """
- # HTTP
-
- The HTTP tab shows all HTTP endpoints exposed by this
integration and
- lets you send test requests interactively — a lightweight
Postman built
- into the terminal. This includes REST API endpoints,
management endpoints
- (health, metrics), and any other HTTP routes.
-
- ## Endpoint List
-
- - **METHOD** — HTTP method: `GET`, `POST`, `PUT`, `DELETE`,
`PATCH`, etc.
- - **PATH** — URL path for this endpoint
- - **TOTAL** — Number of HTTP requests received since startup
- - **CONSUMES** — Content-Type this endpoint accepts
- - **PRODUCES** — Content-Type this endpoint returns
- - **SOURCE** — How the endpoint was registered: REST(code),
REST(contract), HTTP, Management
- - **STATE** — Endpoint state: `Started` or `Stopped`
-
- ## HTTP Probe
-
- Press `Enter` on an endpoint to open the interactive HTTP
probe.
-
- The probe has these sections:
-
- - **Method**: `Left/Right` arrows to cycle through HTTP methods
- - **URL**: Read-only full URL (with resolved placeholders).
Clickable as hyperlink
- - **Path**: Editable URL path template (e.g.,
`/api/users/{id}`)
- - **Path Params**: Auto-detected from `{xxx}` placeholders in
the path. Fill in values and they get substituted in the URL
- - **Query Params**: Key-value pairs appended to the URL as
`?key=value`. Press `+` to add
- - **Content-Type**: Cycle through common content types with
`Left/Right` arrows
- - **Accept**: Cycle through common accept types with
`Left/Right` arrows
- - **Headers**: Custom request headers. Press `+` to add
- - **Body**: Multi-line request body (Enter for newline).
Supports `file:payload.json` to load from disk
- - **Response**: HTTP status code, elapsed time, response
headers, and body
- - **History**: Recent requests with replay support
-
- Press `F5` to send the request. Press `p` to toggle
pretty-print for JSON responses.
-
- ## Keys
-
- ### Endpoint List
- - `Up/Down` — select endpoint
- - `Enter` — open HTTP probe for selected endpoint
- - `s` — cycle sort column
- - `S` — reverse sort order
- - `f` — cycle filter (all / rest / http)
- - `m` — toggle management endpoints
- - `c` — view OpenAPI spec (when available)
-
- ### HTTP Probe
- - `F5` — send request
- - `Tab/Down` — next field
- - `Up` — previous field
- - `Enter` — newline in body, advance in other fields
- - `Left/Right` — cycle method, content-type, accept; cursor in
text fields
- - `+` — add query param or header
- - `p` — toggle pretty-print
- - `PgUp/PgDn` — scroll response
- - `Esc` — close probe
- """;
- }
-
- @Override
- public boolean setInputValue(String field, String value) {
- if (!probeMode) {
- return false;
- }
- String v = value != null ? value : "";
- if ("path".equals(field)) {
- probePathState.setText(v);
- rescanPathParams();
- return true;
- }
- if ("body".equals(field)) {
- probeBodyState.setText(v);
- return true;
- }
- if ("method".equals(field)) {
- String upper = v.toUpperCase(Locale.ENGLISH);
- for (int i = 0; i < PROBE_METHODS.length; i++) {
- if (PROBE_METHODS[i].equals(upper)) {
- probeMethodIndex = i;
- return true;
- }
- }
- return false;
- }
- if ("content-type".equals(field)) {
- probeContentTypeIndex = findPresetIndex(CONTENT_TYPES, v);
- return true;
- }
- if ("accept".equals(field)) {
- probeAcceptIndex = findPresetIndex(ACCEPT_TYPES, v);
- return true;
- }
- return false;
- }
-
- @Override
- public JsonObject getTableDataAsJson() {
- IntegrationInfo info = ctx.findSelectedIntegration();
- if (info == null) {
- return null;
- }
- JsonObject result = new JsonObject();
- result.put("tab", "HTTP");
-
- if (probeMode) {
- result.put("probeMode", true);
- result.put("method", PROBE_METHODS[probeMethodIndex]);
- result.put("path", probePathState.text());
- String ct = CONTENT_TYPES[probeContentTypeIndex];
- if (!ct.isEmpty()) {
- result.put("contentType", ct);
- }
- String acc = ACCEPT_TYPES[probeAcceptIndex];
- if (!acc.isEmpty()) {
- result.put("accept", acc);
- }
- if (hasPathParams()) {
- JsonObject pp = new JsonObject();
- for (PathParam p : probePathParams) {
- pp.put(p.name, p.input.text());
- }
- result.put("pathParams", pp);
- }
- if (hasQueryParams()) {
- JsonArray qp = new JsonArray();
- for (FormHelper.HeaderEntry e : probeQueryParams) {
- JsonObject entry = new JsonObject();
- entry.put("key", e.keyInput().text());
- entry.put("value", e.valueInput().text());
- qp.add(entry);
- }
- result.put("queryParams", qp);
- }
- if (hasProbeHeaders()) {
- JsonArray hd = new JsonArray();
- for (FormHelper.HeaderEntry e : probeHeaders) {
- JsonObject entry = new JsonObject();
- entry.put("key", e.keyInput().text());
- entry.put("value", e.valueInput().text());
- hd.add(entry);
- }
- result.put("headers", hd);
- }
- String body = probeBodyState.text();
- if (body != null && !body.isEmpty()) {
- result.put("body", body);
- }
- if (probeResponseStatus != null) {
- result.put("responseStatus", probeResponseStatus);
- }
- if (probeResponseElapsed > 0) {
- result.put("responseElapsed", probeResponseElapsed);
- }
- if (probeResponseRawBody != null &&
!probeResponseRawBody.isEmpty()) {
- result.put("responseBody", probeResponseRawBody);
- }
- if (!probeHistory.isEmpty()) {
- JsonArray hist = new JsonArray();
- for (ProbeHistoryEntry he : probeHistory) {
- JsonObject entry = new JsonObject();
- entry.put("method", he.method);
- entry.put("path", he.path);
- entry.put("statusCode", he.statusCode);
- entry.put("elapsed", he.elapsed);
- if (he.error) {
- entry.put("error", true);
- }
- hist.add(entry);
- }
- result.put("history", hist);
- }
- return result;
- }
-
- JsonArray rows = new JsonArray();
- for (HttpEndpointInfo hi : info.httpEndpoints) {
- JsonObject row = new JsonObject();
- row.put("method", hi.method);
- row.put("path", hi.path);
- row.put("url", hi.url);
- row.put("hits", hi.hits);
- row.put("routeId", hi.routeId);
- row.put("state", hi.state);
- row.put("fromRest", hi.fromRest);
- row.put("contractFirst", hi.contractFirst);
- if (hi.consumes != null) {
- row.put("consumes", hi.consumes);
- }
- if (hi.produces != null) {
- row.put("produces", hi.produces);
- }
- if (hi.description != null) {
- row.put("description", hi.description);
- }
- if (hi.operationId != null) {
- row.put("operationId", hi.operationId);
- }
- rows.add(row);
- }
- result.put("rows", rows);
- result.put("totalRows", info.httpEndpoints.size());
- if (info.httpServer != null) {
- result.put("httpServer", info.httpServer);
- }
- 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/HttpTab.java
b/dsl/camel-jbang/camel-jbang-plugin-tui/src/main/java/org/apache/camel/dsl/jbang/core/commands/tui/HttpTab.java
index fe0f50517c21..4b60ca179152 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
@@ -16,20 +16,12 @@
*/
package org.apache.camel.dsl.jbang.core.commands.tui;
-import java.io.IOException;
-import java.net.URI;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import dev.tamboui.layout.Constraint;
import dev.tamboui.layout.Layout;
@@ -43,20 +35,14 @@ import dev.tamboui.tui.event.KeyCode;
import dev.tamboui.tui.event.KeyEvent;
import dev.tamboui.tui.event.MouseEvent;
import dev.tamboui.tui.event.MouseEventKind;
-import dev.tamboui.widgets.Clear;
import dev.tamboui.widgets.block.Block;
import dev.tamboui.widgets.block.BorderType;
import dev.tamboui.widgets.block.Borders;
import dev.tamboui.widgets.block.Title;
-import dev.tamboui.widgets.input.TextArea;
-import dev.tamboui.widgets.input.TextAreaState;
-import dev.tamboui.widgets.input.TextInput;
-import dev.tamboui.widgets.input.TextInputState;
import dev.tamboui.widgets.paragraph.Paragraph;
import dev.tamboui.widgets.table.Cell;
import dev.tamboui.widgets.table.Row;
import dev.tamboui.widgets.table.Table;
-import org.apache.camel.dsl.jbang.core.common.CamelCommandHelper;
import org.apache.camel.util.json.JsonArray;
import org.apache.camel.util.json.JsonObject;
@@ -67,29 +53,8 @@ class HttpTab extends AbstractTableTab {
private static final int MOUSE_SCROLL_LINES = 3;
private static final Set<String> OPENAPI_HTTP_VERBS
= Set.of("get", "post", "put", "delete", "patch", "options",
"head", "trace");
- private static final Pattern PATH_PARAM_PATTERN =
Pattern.compile("\\{([^}]+)}");
-
- // Probe field constants
- private static final int PROBE_METHOD = 0;
- private static final int PROBE_PATH = 1;
- private static final int PROBE_PATH_PARAMS = 2;
- private static final int PROBE_QUERY_PARAMS = 3;
- private static final int PROBE_CONTENT_TYPE = 4;
- private static final int PROBE_ACCEPT = 5;
- private static final int PROBE_HEADERS = 6;
- private static final int PROBE_BODY = 7;
- private static final int PROBE_HISTORY = 8;
-
- private static final String[] PROBE_METHODS = { "GET", "POST", "PUT",
"DELETE", "PATCH", "HEAD", "OPTIONS" };
- private static final String[] CONTENT_TYPES = {
- "", "application/json", "application/xml", "text/plain",
- "text/xml", "application/x-www-form-urlencoded",
"multipart/form-data"
- };
- private static final String[] ACCEPT_TYPES = {
- "", "application/json", "application/xml", "text/plain", "*/*"
- };
- private static final int MAX_PROBE_HISTORY = 20;
+ private final HttpProbe probe;
private final AtomicBoolean specLoading = new AtomicBoolean(false);
private int filter;
@@ -100,1775 +65,208 @@ class HttpTab extends AbstractTableTab {
private String specTitle;
private int specScroll;
- // Probe mode state
- private boolean probeMode;
- private String probeRouteId;
- private String probeBaseUrl;
- private int probeField = PROBE_PATH;
- private int probeMethodIndex;
- private final TextInputState probePathState = new TextInputState("");
- private final TextAreaState probeBodyState = new TextAreaState("");
- private List<FormHelper.HeaderEntry> probeHeaders;
- private int probeSelectedHeader;
- private boolean probeEditingHeaderKey;
- private List<PathParam> probePathParams;
- private int probeSelectedPathParam;
- private List<FormHelper.HeaderEntry> probeQueryParams;
- private int probeSelectedQueryParam;
- private boolean probeEditingQueryKey;
- private int probeContentTypeIndex;
- private int probeAcceptIndex;
- private final AtomicBoolean probeSending = new AtomicBoolean(false);
- private String probeResponseStatus;
- private long probeResponseElapsed;
- private List<String> probeResponseLines;
- private boolean probeResponseError;
- private int probeResponseScroll;
- private boolean probeViewRequest;
- private final List<ProbeHistoryEntry> probeHistory = new ArrayList<>();
- private int probeHistoryIndex;
- private boolean probePrettyPrint;
- private String probeResponseRawBody;
- private List<String> probeResponseHeaderLines;
-
private int detailPanelHeight = 10;
private final DragSplit vSplit = new DragSplit();
- HttpTab(MonitorContext ctx) {
- super(ctx, "method", "path", "total", "consumes", "produces",
"source");
- }
-
- @Override
- protected int getRowCount() {
- return sortedVisibleEndpoints(ctx.findSelectedIntegration()).size();
- }
-
- boolean isProbeMode() {
- return probeMode;
- }
-
- @Override
- public boolean handleKeyEvent(KeyEvent ke) {
- if (probeMode) {
- return handleProbeKeyEvent(ke);
- }
-
- if (showSpec) {
- if (ke.isChar('c') || ke.isCancel()) {
- showSpec = false;
- } else if (ke.isUp()) {
- specScroll = Math.max(0, specScroll - 1);
- } else if (ke.isDown()) {
- specScroll++;
- } else if (ke.isPageUp() || ke.isKey(KeyCode.PAGE_UP)) {
- specScroll = Math.max(0, specScroll - 20);
- } else if (ke.isPageDown() || ke.isKey(KeyCode.PAGE_DOWN)) {
- specScroll += 20;
- } else if (ke.isHome()) {
- specScroll = 0;
- } else if (ke.isEnd()) {
- specScroll = Integer.MAX_VALUE;
- } else {
- return false;
- }
- return true;
- }
-
- return super.handleKeyEvent(ke);
- }
-
- @Override
- protected boolean handleTabKeyEvent(KeyEvent ke) {
- if (ke.isConfirm()) {
- enterProbeModeFromTable();
- return true;
- }
- if (ke.isCharIgnoreCase('f')) {
- filter = (filter + 1) % 3;
- return true;
- }
- if (ke.isCharIgnoreCase('m')) {
- showManagement = !showManagement;
- return true;
- }
- if (ke.isChar('c')) {
- loadSpecForSelectedEndpoint();
- return true;
- }
- return false;
- }
-
- @Override
- public boolean handleMouseEvent(MouseEvent me, Rect area) {
- if (!probeMode && !showSpec && vSplit.handleMouse(me, me.y())) {
- if (vSplit.isDragging() && me.kind() == MouseEventKind.DRAG) {
- detailPanelHeight = Math.max(3, Math.min(area.y() +
area.height() - me.y(), area.height() - 5));
- }
- return true;
- }
- if (!probeMode && !showSpec) {
- List<HttpEndpointInfo> visible =
sortedVisibleEndpoints(ctx.findSelectedIntegration());
- if (handleTableClick(me, lastTableArea, tableState,
visible.size())) {
- return true;
- }
- }
- if (showSpec) {
- if (me.kind() == MouseEventKind.SCROLL_UP) {
- specScroll = Math.max(0, specScroll - MOUSE_SCROLL_LINES);
- return true;
- }
- if (me.kind() == MouseEventKind.SCROLL_DOWN) {
- specScroll += MOUSE_SCROLL_LINES;
- return true;
- }
- }
- if (probeMode) {
- if (me.kind() == MouseEventKind.SCROLL_UP) {
- probeResponseScroll = Math.max(0, probeResponseScroll -
MOUSE_SCROLL_LINES);
- return true;
- }
- if (me.kind() == MouseEventKind.SCROLL_DOWN) {
- probeResponseScroll += MOUSE_SCROLL_LINES;
- return true;
- }
- }
- return false;
- }
-
- @Override
- public boolean handleEscape() {
- if (probeMode) {
- probeMode = false;
- return true;
- }
- if (showSpec) {
- showSpec = false;
- return true;
- }
- return false;
- }
-
- @Override
- public void navigateUp() {
- if (probeMode) {
- // Arrow up is handled by handleProbeKeyEvent for all fields
- return;
- }
- tableState.selectPrevious();
- }
-
- @Override
- public void navigateDown() {
- if (probeMode) {
- // Arrow down is handled by handleProbeKeyEvent for all fields
- return;
- }
- List<HttpEndpointInfo> visible =
sortedVisibleEndpoints(ctx.findSelectedIntegration());
- tableState.selectNext(visible.size());
- }
-
- @Override
- public void onIntegrationChanged() {
- showSpec = false;
- specLines = Collections.emptyList();
- specTitle = null;
- specScroll = 0;
- probeMode = false;
- }
-
- @Override
- protected void renderContent(Frame frame, Rect area, IntegrationInfo info)
{
- if (probeMode) {
- renderProbe(frame, area);
- return;
- }
-
- if (showSpec) {
- renderSpec(frame, area);
- return;
- }
-
- List<HttpEndpointInfo> visible = sortedVisibleEndpoints(info);
-
- detailPanelHeight = Math.max(3, Math.min(detailPanelHeight,
area.height() - 5));
- List<Rect> chunks = Layout.vertical()
- .constraints(Constraint.fill(),
Constraint.length(detailPanelHeight))
- .split(area);
-
- renderTable(frame, chunks.get(0), visible, info);
- vSplit.setBorderPos(chunks.get(1).y());
- renderDetail(frame, chunks.get(1), visible);
- }
-
- @Override
- public void renderFooter(List<Span> spans) {
- if (probeMode) {
- hint(spans, "Esc", "back");
- hint(spans, "Tab", "fields");
- hint(spans, "F5", "send");
- hint(spans, "F4", probeViewRequest ? "response" : "request");
- hint(spans, "+", "param/header");
- hint(spans, "p", "pretty" + (probePrettyPrint ? " [on]" : ""));
- if (!probeHistory.isEmpty()) {
- hintLast(spans, TuiIcons.HINT_SCROLL, "history");
- }
- return;
- }
- if (showSpec) {
- hint(spans, "c/Esc", "close");
- hint(spans, TuiIcons.HINT_SCROLL, "scroll");
- hintLast(spans, "PgUp/PgDn", "page");
- return;
- }
- hint(spans, "Esc", "back");
- hint(spans, "Enter", "probe");
- hint(spans, "s", "sort");
- String[] filterLabels = { "all", "rest", "http" };
- hint(spans, "f", "filter [" + filterLabels[filter] + "]");
- hint(spans, "m", "management" + (showManagement ? " [on]" : " [off]"));
- List<HttpEndpointInfo> hVisible =
sortedVisibleEndpoints(ctx.findSelectedIntegration());
- Integer hSel = tableState.selected();
- if (hSel != null && hSel >= 0 && hSel < hVisible.size() &&
hVisible.get(hSel).specificationUri != null) {
- hintLast(spans, "c", "spec");
- }
- }
-
- void handlePaste(String text) {
- if (!probeMode || probeSending.get()) {
- return;
- }
- if (probeField == PROBE_BODY) {
- probeBodyState.insert(text);
- } else {
- TextInputState target = probeActiveTextInput();
- if (target != null) {
- FormHelper.handlePaste(text, target);
- }
- }
- }
-
- // ---- Probe mode ----
-
- private void enterProbeModeFromTable() {
- List<HttpEndpointInfo> visible =
sortedVisibleEndpoints(ctx.findSelectedIntegration());
- Integer sel = tableState.selected();
- if (sel == null || sel < 0 || sel >= visible.size()) {
- return;
- }
- enterProbeMode(visible.get(sel));
- }
-
- private void enterProbeMode(HttpEndpointInfo ep) {
- probeMode = true;
- probeField = PROBE_PATH;
- probeRouteId = ep.routeId;
- probeBaseUrl = extractBaseUrl(ep.url);
-
- // Pre-fill method
- probeMethodIndex = 0;
- if (ep.method != null) {
- String m =
ep.method.split(",")[0].trim().toUpperCase(Locale.ENGLISH);
- for (int i = 0; i < PROBE_METHODS.length; i++) {
- if (PROBE_METHODS[i].equals(m)) {
- probeMethodIndex = i;
- break;
- }
- }
- }
-
- // Pre-fill path
- String path = ep.path != null ? ep.path : (ep.url != null ? ep.url :
"/");
- probePathState.clear();
- for (int i = 0; i < path.length(); i++) {
- probePathState.insert(path.charAt(i));
- }
-
- // Scan for {xxx} placeholders in path
- probePathParams = scanPathParams(path);
- probeSelectedPathParam = 0;
-
- // Pre-fill Content-Type and Accept from endpoint metadata
- probeContentTypeIndex = findPresetIndex(CONTENT_TYPES, ep.consumes);
- probeAcceptIndex = findPresetIndex(ACCEPT_TYPES, ep.produces);
-
- // Clear manual headers (Content-Type/Accept are handled by cyclers)
- probeHeaders = null;
- probeSelectedHeader = 0;
- probeEditingHeaderKey = true;
-
- // Clear query params
- probeQueryParams = null;
- probeSelectedQueryParam = 0;
- probeEditingQueryKey = true;
-
- // Clear body and response
- probeBodyState.clear();
- probeResponseStatus = null;
- probeResponseElapsed = 0;
- probeResponseLines = null;
- probeResponseHeaderLines = null;
- probeResponseRawBody = null;
- probeResponseError = false;
- probeResponseScroll = 0;
- probeHistoryIndex = 0;
- }
-
- private void addProbeHeader(String key, String value) {
- if (probeHeaders == null) {
- probeHeaders = new ArrayList<>();
- }
- TextInputState keyState = new TextInputState("");
- TextInputState valState = new TextInputState("");
- for (int i = 0; i < key.length(); i++) {
- keyState.insert(key.charAt(i));
- }
- for (int i = 0; i < value.length(); i++) {
- valState.insert(value.charAt(i));
- }
- probeHeaders.add(new FormHelper.HeaderEntry(keyState, valState));
- }
-
- private boolean handleProbeKeyEvent(KeyEvent ke) {
- if (probeSending.get()) {
- return true;
- }
-
- // F5 to send request
- if (ke.code() == KeyCode.F5) {
- doProbeRequest();
- probeViewRequest = false;
- return true;
- }
-
- // F4 to toggle request/response view
- if (ke.code() == KeyCode.F4) {
- probeViewRequest = !probeViewRequest;
- return true;
- }
-
- // Enter: newline in body, replay in history, advance field otherwise
- if (ke.isConfirm()) {
- if (probeField == PROBE_BODY) {
- probeBodyState.insert('\n');
- } else if (probeField == PROBE_HISTORY && !probeHistory.isEmpty())
{
- replayHistoryEntry(probeHistory.get(probeHistoryIndex));
- } else {
- advanceProbeField();
- }
- return true;
- }
-
- // PgUp/PgDn scroll response
- if (ke.isPageUp() || ke.isKey(KeyCode.PAGE_UP)) {
- probeResponseScroll = Math.max(0, probeResponseScroll - 10);
- return true;
- }
- if (ke.isPageDown() || ke.isKey(KeyCode.PAGE_DOWN)) {
- probeResponseScroll += 10;
- return true;
- }
-
- // Toggle pretty print for response body
- if (ke.isChar('p') && probeField != PROBE_PATH && probeField !=
PROBE_BODY
- && probeField != PROBE_HEADERS && probeField !=
PROBE_PATH_PARAMS
- && probeField != PROBE_QUERY_PARAMS) {
- probePrettyPrint = !probePrettyPrint;
- if (probeResponseLines != null) {
- reformatResponseBody();
- }
- return true;
- }
-
- // Field-specific handling
- if (probeField == PROBE_METHOD) {
- return handleProbeMethodKeyEvent(ke);
- }
- if (probeField == PROBE_PATH) {
- return handleProbePathKeyEvent(ke);
- }
- if (probeField == PROBE_PATH_PARAMS) {
- return handleProbePathParamKeyEvent(ke);
- }
- if (probeField == PROBE_QUERY_PARAMS) {
- return handleProbeQueryParamKeyEvent(ke);
- }
- if (probeField == PROBE_CONTENT_TYPE) {
- return handleProbeCyclerKeyEvent(ke, CONTENT_TYPES,
PROBE_CONTENT_TYPE);
- }
- if (probeField == PROBE_ACCEPT) {
- return handleProbeCyclerKeyEvent(ke, ACCEPT_TYPES, PROBE_ACCEPT);
- }
- if (probeField == PROBE_HEADERS) {
- return handleProbeHeaderKeyEvent(ke);
- }
- if (probeField == PROBE_BODY) {
- return handleProbeBodyKeyEvent(ke);
- }
- if (probeField == PROBE_HISTORY) {
- return handleProbeHistoryKeyEvent(ke);
- }
- return true;
- }
-
- private boolean handleProbeMethodKeyEvent(KeyEvent ke) {
- if (ke.isKey(KeyCode.TAB) || ke.isDown()) {
- probeField = PROBE_PATH;
- return true;
- }
- if (ke.isUp()) {
- goToLastProbeField();
- return true;
- }
- if (ke.isLeft()) {
- probeMethodIndex = (probeMethodIndex - 1 + PROBE_METHODS.length) %
PROBE_METHODS.length;
- return true;
- }
- if (ke.isRight()) {
- probeMethodIndex = (probeMethodIndex + 1) % PROBE_METHODS.length;
- return true;
- }
- if (ke.isChar('+')) {
- addProbeQueryParamEmpty();
- return true;
- }
- return true;
- }
-
- private boolean handleProbePathKeyEvent(KeyEvent ke) {
- if (ke.isKey(KeyCode.TAB) || ke.isDown()) {
- goToNextProbeFieldFrom(PROBE_PATH);
- return true;
- }
- if (ke.isUp()) {
- probeField = PROBE_METHOD;
- return true;
- }
- if (ke.isChar('+')) {
- addProbeQueryParamEmpty();
- return true;
- }
- FormHelper.handleTextInput(ke, probePathState);
- rescanPathParams();
- return true;
- }
-
- private boolean handleProbePathParamKeyEvent(KeyEvent ke) {
- if (probePathParams == null || probePathParams.isEmpty()) {
- goToNextProbeFieldFrom(PROBE_PATH_PARAMS);
- return true;
- }
- PathParam current = probePathParams.get(probeSelectedPathParam);
-
- if (ke.isKey(KeyCode.TAB) || ke.isDown()) {
- if (probeSelectedPathParam < probePathParams.size() - 1) {
- probeSelectedPathParam++;
- } else {
- goToNextProbeFieldFrom(PROBE_PATH_PARAMS);
- }
- return true;
- }
- if (ke.isUp()) {
- if (probeSelectedPathParam > 0) {
- probeSelectedPathParam--;
- } else {
- probeField = PROBE_PATH;
- }
- return true;
- }
- FormHelper.handleTextInput(ke, current.input);
- return true;
- }
-
- private boolean handleProbeQueryParamKeyEvent(KeyEvent ke) {
- if (probeQueryParams == null || probeQueryParams.isEmpty()) {
- goToNextProbeFieldFrom(PROBE_QUERY_PARAMS);
- return true;
- }
- FormHelper.HeaderEntry current =
probeQueryParams.get(probeSelectedQueryParam);
- TextInputState activeInput = probeEditingQueryKey ? current.keyInput()
: current.valueInput();
-
- if (ke.isChar('+')) {
- addProbeQueryParamEmpty();
- return true;
- }
- if (ke.isKey(KeyCode.TAB) || ke.isDown()) {
- if (probeEditingQueryKey) {
- probeEditingQueryKey = false;
- } else if (probeSelectedQueryParam < probeQueryParams.size() - 1) {
- probeSelectedQueryParam++;
- probeEditingQueryKey = true;
- } else {
- probeField = PROBE_CONTENT_TYPE;
- }
- return true;
- }
- if (ke.isUp()) {
- if (probeEditingQueryKey) {
- if (probeSelectedQueryParam > 0) {
- probeSelectedQueryParam--;
- probeEditingQueryKey = false;
- } else {
- goToPrevProbeFieldFrom(PROBE_QUERY_PARAMS);
- }
- } else {
- probeEditingQueryKey = true;
- }
- return true;
- }
- if (ke.isDeleteBackward()) {
- if (probeEditingQueryKey && current.keyInput().text().isEmpty()) {
- probeQueryParams.remove(probeSelectedQueryParam);
- if (probeQueryParams.isEmpty()) {
- probeQueryParams = null;
- goToPrevProbeFieldFrom(PROBE_QUERY_PARAMS);
- } else if (probeSelectedQueryParam >= probeQueryParams.size())
{
- probeSelectedQueryParam = probeQueryParams.size() - 1;
- }
- return true;
- }
- activeInput.deleteBackward();
- return true;
- }
- if (ke.isDeleteForward()) {
- activeInput.deleteForward();
- return true;
- }
- if (ke.isLeft()) {
- if (!probeEditingQueryKey && activeInput.cursorPosition() == 0) {
- probeEditingQueryKey = true;
- } else {
- activeInput.moveCursorLeft();
- }
- return true;
- }
- if (ke.isRight()) {
- if (probeEditingQueryKey && activeInput.cursorPosition() ==
activeInput.text().length()) {
- probeEditingQueryKey = false;
- } else {
- activeInput.moveCursorRight();
- }
- return true;
- }
- if (ke.isHome()) {
- activeInput.moveCursorToStart();
- return true;
- }
- if (ke.isEnd()) {
- activeInput.moveCursorToEnd();
- return true;
- }
- if (ke.code() == KeyCode.CHAR) {
- activeInput.insert(ke.string().charAt(0));
- return true;
- }
- return true;
- }
-
- private boolean handleProbeCyclerKeyEvent(KeyEvent ke, String[] options,
int field) {
- if (ke.isKey(KeyCode.TAB) || ke.isDown()) {
- goToNextProbeFieldFrom(field);
- return true;
- }
- if (ke.isUp()) {
- goToPrevProbeFieldFrom(field);
- return true;
- }
- if (ke.isLeft()) {
- if (field == PROBE_CONTENT_TYPE) {
- probeContentTypeIndex = (probeContentTypeIndex - 1 +
options.length) % options.length;
- } else {
- probeAcceptIndex = (probeAcceptIndex - 1 + options.length) %
options.length;
- }
- return true;
- }
- if (ke.isRight() || ke.isChar(' ')) {
- if (field == PROBE_CONTENT_TYPE) {
- probeContentTypeIndex = (probeContentTypeIndex + 1) %
options.length;
- } else {
- probeAcceptIndex = (probeAcceptIndex + 1) % options.length;
- }
- return true;
- }
- if (ke.isChar('+')) {
- addProbeHeaderEmpty();
- return true;
- }
- return true;
- }
-
- private boolean handleProbeBodyKeyEvent(KeyEvent ke) {
- if (ke.isKey(KeyCode.TAB)) {
- goToNextProbeFieldFrom(PROBE_BODY);
- return true;
- }
- if (ke.isUp()) {
- if (probeBodyState.cursorRow() == 0) {
- goToPrevProbeFieldFrom(PROBE_BODY);
- } else {
- probeBodyState.moveCursorUp();
- }
- return true;
- }
- if (ke.isDown()) {
- if (probeBodyState.cursorRow() >= probeBodyState.lineCount() - 1) {
- goToNextProbeFieldFrom(PROBE_BODY);
- } else {
- probeBodyState.moveCursorDown();
- }
- return true;
- }
- if (ke.isLeft()) {
- probeBodyState.moveCursorLeft();
- return true;
- }
- if (ke.isRight()) {
- probeBodyState.moveCursorRight();
- return true;
- }
- if (ke.isHome()) {
- probeBodyState.moveCursorToLineStart();
- return true;
- }
- if (ke.isEnd()) {
- probeBodyState.moveCursorToLineEnd();
- return true;
- }
- if (ke.isDeleteBackward()) {
- probeBodyState.deleteBackward();
- return true;
- }
- if (ke.isDeleteForward()) {
- probeBodyState.deleteForward();
- return true;
- }
- if (ke.code() == KeyCode.CHAR) {
- probeBodyState.insert(ke.character());
- return true;
- }
- return true;
- }
-
- private boolean handleProbeHistoryKeyEvent(KeyEvent ke) {
- if (ke.isKey(KeyCode.TAB)) {
- probeField = PROBE_METHOD;
- return true;
- }
- if (ke.isUp()) {
- if (probeHistoryIndex > 0) {
- probeHistoryIndex--;
- } else {
- probeField = PROBE_BODY;
- }
- return true;
- }
- if (ke.isDown()) {
- if (probeHistoryIndex < probeHistory.size() - 1) {
- probeHistoryIndex++;
- }
- return true;
- }
- if (ke.isChar('+')) {
- addProbeQueryParamEmpty();
- return true;
- }
- return true;
- }
-
- private boolean handleProbeHeaderKeyEvent(KeyEvent ke) {
- FormHelper.HeaderEntry current = probeHeaders.get(probeSelectedHeader);
- TextInputState activeInput = probeEditingHeaderKey ?
current.keyInput() : current.valueInput();
-
- if (ke.isChar('+')) {
- addProbeHeaderEmpty();
- return true;
- }
- if (ke.isKey(KeyCode.TAB) || ke.isDown()) {
- if (probeEditingHeaderKey) {
- probeEditingHeaderKey = false;
- } else if (probeSelectedHeader < probeHeaders.size() - 1) {
- probeSelectedHeader++;
- probeEditingHeaderKey = true;
- } else {
- probeField = PROBE_BODY;
- }
- return true;
- }
- if (ke.isUp()) {
- if (probeEditingHeaderKey) {
- if (probeSelectedHeader > 0) {
- probeSelectedHeader--;
- probeEditingHeaderKey = false;
- } else {
- probeField = PROBE_ACCEPT;
- }
- } else {
- probeEditingHeaderKey = true;
- }
- return true;
- }
- if (ke.isDeleteBackward()) {
- if (probeEditingHeaderKey && current.keyInput().text().isEmpty()) {
- probeHeaders.remove(probeSelectedHeader);
- if (probeHeaders.isEmpty()) {
- probeHeaders = null;
- probeField = PROBE_ACCEPT;
- } else if (probeSelectedHeader >= probeHeaders.size()) {
- probeSelectedHeader = probeHeaders.size() - 1;
- }
- return true;
- }
- activeInput.deleteBackward();
- return true;
- }
- if (ke.isDeleteForward()) {
- activeInput.deleteForward();
- return true;
- }
- if (ke.isLeft()) {
- if (!probeEditingHeaderKey && activeInput.cursorPosition() == 0) {
- probeEditingHeaderKey = true;
- } else {
- activeInput.moveCursorLeft();
- }
- return true;
- }
- if (ke.isRight()) {
- if (probeEditingHeaderKey && activeInput.cursorPosition() ==
activeInput.text().length()) {
- probeEditingHeaderKey = false;
- } else {
- activeInput.moveCursorRight();
- }
- return true;
- }
- if (ke.isHome()) {
- activeInput.moveCursorToStart();
- return true;
- }
- if (ke.isEnd()) {
- activeInput.moveCursorToEnd();
- return true;
- }
- if (ke.code() == KeyCode.CHAR) {
- activeInput.insert(ke.string().charAt(0));
- return true;
- }
- return true;
- }
-
- private void addProbeHeaderEmpty() {
- if (probeHeaders == null) {
- probeHeaders = new ArrayList<>();
- }
- probeHeaders.add(new FormHelper.HeaderEntry(new TextInputState(""),
new TextInputState("")));
- probeField = PROBE_HEADERS;
- probeSelectedHeader = probeHeaders.size() - 1;
- probeEditingHeaderKey = true;
- }
-
- private boolean hasProbeHeaders() {
- return probeHeaders != null && !probeHeaders.isEmpty();
- }
-
- private boolean hasPathParams() {
- return probePathParams != null && !probePathParams.isEmpty();
- }
-
- private boolean hasQueryParams() {
- return probeQueryParams != null && !probeQueryParams.isEmpty();
- }
-
- private TextInputState probeActiveTextInput() {
- if (probeField == PROBE_PATH) {
- return probePathState;
- }
- if (probeField == PROBE_PATH_PARAMS && hasPathParams()) {
- return probePathParams.get(probeSelectedPathParam).input;
- }
- if (probeField == PROBE_QUERY_PARAMS && hasQueryParams()) {
- FormHelper.HeaderEntry qp =
probeQueryParams.get(probeSelectedQueryParam);
- return probeEditingQueryKey ? qp.keyInput() : qp.valueInput();
- }
- if (probeField == PROBE_HEADERS && hasProbeHeaders()) {
- FormHelper.HeaderEntry he = probeHeaders.get(probeSelectedHeader);
- return probeEditingHeaderKey ? he.keyInput() : he.valueInput();
- }
- return null;
- }
-
- private void advanceProbeField() {
- goToNextProbeFieldFrom(probeField);
- }
-
- private void goToNextProbeFieldFrom(int from) {
- switch (from) {
- case PROBE_METHOD:
- probeField = PROBE_PATH;
- break;
- case PROBE_PATH:
- if (hasPathParams()) {
- probeField = PROBE_PATH_PARAMS;
- probeSelectedPathParam = 0;
- } else if (hasQueryParams()) {
- probeField = PROBE_QUERY_PARAMS;
- probeSelectedQueryParam = 0;
- probeEditingQueryKey = true;
- } else {
- probeField = PROBE_CONTENT_TYPE;
- }
- break;
- case PROBE_PATH_PARAMS:
- if (hasQueryParams()) {
- probeField = PROBE_QUERY_PARAMS;
- probeSelectedQueryParam = 0;
- probeEditingQueryKey = true;
- } else {
- probeField = PROBE_CONTENT_TYPE;
- }
- break;
- case PROBE_QUERY_PARAMS:
- probeField = PROBE_CONTENT_TYPE;
- break;
- case PROBE_CONTENT_TYPE:
- probeField = PROBE_ACCEPT;
- break;
- case PROBE_ACCEPT:
- if (hasProbeHeaders()) {
- probeField = PROBE_HEADERS;
- probeSelectedHeader = 0;
- probeEditingHeaderKey = true;
- } else {
- probeField = PROBE_BODY;
- }
- break;
- case PROBE_HEADERS:
- probeField = PROBE_BODY;
- break;
- case PROBE_BODY:
- if (!probeHistory.isEmpty()) {
- probeField = PROBE_HISTORY;
- probeHistoryIndex = 0;
- } else {
- probeField = PROBE_METHOD;
- }
- break;
- case PROBE_HISTORY:
- probeField = PROBE_METHOD;
- break;
- default:
- probeField = PROBE_METHOD;
- }
- }
-
- private void goToPrevProbeFieldFrom(int from) {
- switch (from) {
- case PROBE_PATH:
- probeField = PROBE_METHOD;
- break;
- case PROBE_PATH_PARAMS:
- probeField = PROBE_PATH;
- break;
- case PROBE_QUERY_PARAMS:
- if (hasPathParams()) {
- probeField = PROBE_PATH_PARAMS;
- probeSelectedPathParam = probePathParams.size() - 1;
- } else {
- probeField = PROBE_PATH;
- }
- break;
- case PROBE_CONTENT_TYPE:
- if (hasQueryParams()) {
- probeField = PROBE_QUERY_PARAMS;
- probeSelectedQueryParam = probeQueryParams.size() - 1;
- probeEditingQueryKey = false;
- } else if (hasPathParams()) {
- probeField = PROBE_PATH_PARAMS;
- probeSelectedPathParam = probePathParams.size() - 1;
- } else {
- probeField = PROBE_PATH;
- }
- break;
- case PROBE_ACCEPT:
- probeField = PROBE_CONTENT_TYPE;
- break;
- case PROBE_HEADERS:
- probeField = PROBE_ACCEPT;
- break;
- case PROBE_BODY:
- if (hasProbeHeaders()) {
- probeField = PROBE_HEADERS;
- probeSelectedHeader = probeHeaders.size() - 1;
- probeEditingHeaderKey = false;
- } else {
- probeField = PROBE_ACCEPT;
- }
- break;
- case PROBE_HISTORY:
- probeField = PROBE_BODY;
- break;
- default:
- probeField = PROBE_METHOD;
- }
- }
-
- private void goToLastProbeField() {
- if (!probeHistory.isEmpty()) {
- probeField = PROBE_HISTORY;
- probeHistoryIndex = 0;
- } else {
- probeField = PROBE_BODY;
- }
- }
-
- private List<PathParam> scanPathParams(String path) {
- if (path == null || path.isEmpty()) {
- return null;
- }
- Matcher m = PATH_PARAM_PATTERN.matcher(path);
- List<PathParam> params = null;
- while (m.find()) {
- if (params == null) {
- params = new ArrayList<>();
- }
- params.add(new PathParam(m.group(1), new TextInputState("")));
- }
- return params;
- }
-
- private void rescanPathParams() {
- String path = probePathState.text();
- List<PathParam> newParams = scanPathParams(path);
- if (newParams == null) {
- probePathParams = null;
- return;
- }
- // Preserve existing values for matching param names
- if (probePathParams != null) {
- for (PathParam np : newParams) {
- for (PathParam op : probePathParams) {
- if (np.name.equals(op.name) && !op.input.text().isEmpty())
{
- np.input.setText(op.input.text());
- break;
- }
- }
- }
- }
- probePathParams = newParams;
- }
-
- private void addProbeQueryParamEmpty() {
- if (probeQueryParams == null) {
- probeQueryParams = new ArrayList<>();
- }
- probeQueryParams.add(new FormHelper.HeaderEntry(new
TextInputState(""), new TextInputState("")));
- probeField = PROBE_QUERY_PARAMS;
- probeSelectedQueryParam = probeQueryParams.size() - 1;
- probeEditingQueryKey = true;
- }
-
- private static int findPresetIndex(String[] presets, String value) {
- if (value == null || value.isEmpty()) {
- return 0;
- }
- for (int i = 1; i < presets.length; i++) {
- if (presets[i].equalsIgnoreCase(value) ||
value.contains(presets[i])) {
- return i;
- }
- }
- return 0;
- }
-
- private void replayHistoryEntry(ProbeHistoryEntry entry) {
- // Method
- for (int i = 0; i < PROBE_METHODS.length; i++) {
- if (PROBE_METHODS[i].equals(entry.method)) {
- probeMethodIndex = i;
- break;
- }
- }
- // Path
- probePathState.setText(entry.path);
- // Path params: scan template then extract values from resolved path
- rescanPathParams();
- if (probePathParams != null && entry.resolvedPath != null) {
- extractPathParamValues(entry.path, entry.resolvedPath);
- }
- // Query params
- probeQueryParams = null;
- if (entry.queryParams != null) {
- probeQueryParams = new ArrayList<>();
- for (FormHelper.HeaderEntry qp : entry.queryParams) {
- probeQueryParams.add(new FormHelper.HeaderEntry(
- new TextInputState(qp.keyInput().text()),
- new TextInputState(qp.valueInput().text())));
- }
- }
- probeSelectedQueryParam = 0;
- probeEditingQueryKey = true;
- // Content-Type and Accept
- probeContentTypeIndex = entry.contentTypeIndex;
- probeAcceptIndex = entry.acceptIndex;
- // Headers
- probeHeaders = null;
- probeSelectedHeader = 0;
- probeEditingHeaderKey = true;
- if (entry.headers != null) {
- for (FormHelper.HeaderEntry he : entry.headers) {
- addProbeHeader(he.keyInput().text(), he.valueInput().text());
- }
- }
- // Body
- probeBodyState.setText(entry.body != null ? entry.body : "");
- // Focus on method so user can review before sending
- probeField = PROBE_METHOD;
- probeViewRequest = true;
- }
-
- private void extractPathParamValues(String templatePath, String
resolvedPath) {
- if (probePathParams == null || probePathParams.isEmpty()) {
- return;
- }
- // Build a regex from the template, capturing each {xxx} group
- StringBuilder regex = new StringBuilder();
- Matcher m = PATH_PARAM_PATTERN.matcher(templatePath);
- int last = 0;
- List<String> paramOrder = new ArrayList<>();
- while (m.find()) {
- regex.append(Pattern.quote(templatePath.substring(last,
m.start())));
- regex.append("([^/]*)");
- paramOrder.add(m.group(1));
- last = m.end();
- }
- regex.append(Pattern.quote(templatePath.substring(last)));
- try {
- Matcher rm =
Pattern.compile(regex.toString()).matcher(resolvedPath);
- if (rm.matches()) {
- for (int i = 0; i < paramOrder.size() && i < rm.groupCount();
i++) {
- String name = paramOrder.get(i);
- String value = rm.group(i + 1);
- for (PathParam pp : probePathParams) {
- if (pp.name.equals(name)) {
- pp.input.setText(value);
- break;
- }
- }
- }
- }
- } catch (Exception e) {
- // ignore regex errors
- }
- }
-
- private void doProbeRequest() {
- if (ctx.runner == null || probeBaseUrl == null) {
- return;
- }
- if (!probeSending.compareAndSet(false, true)) {
- return;
- }
-
- probeResponseStatus = "Sending...";
- probeResponseElapsed = 0;
- probeResponseLines = null;
- probeResponseError = false;
- probeResponseScroll = 0;
-
- String method = PROBE_METHODS[probeMethodIndex];
- String path = probePathState.text();
-
- // Replace {xxx} placeholders with param values
- if (hasPathParams()) {
- for (PathParam pp : probePathParams) {
- String val = pp.input.text();
- if (!val.isEmpty()) {
- path = path.replace("{" + pp.name + "}", val);
- }
- }
- }
-
- // Append query params
- if (hasQueryParams()) {
- StringBuilder sb = new StringBuilder(path);
- char sep = path.contains("?") ? '&' : '?';
- for (FormHelper.HeaderEntry qp : probeQueryParams) {
- String k = qp.keyInput().text().trim();
- String v = qp.valueInput().text();
- if (!k.isEmpty()) {
- sb.append(sep)
- .append(URLEncoder.encode(k,
StandardCharsets.UTF_8))
- .append('=')
- .append(URLEncoder.encode(v,
StandardCharsets.UTF_8));
- sep = '&';
- }
- }
- path = sb.toString();
- }
-
- String body = probeBodyState.text();
- if (body != null && body.trim().startsWith("file:")) {
- try {
- body = Files.readString(Path.of(body.trim().substring(5)));
- } catch (IOException e) {
- probeResponseStatus = "Error reading file: " + e.getMessage();
- probeResponseError = true;
- probeSending.set(false);
- return;
- }
- }
- String sendBody = body;
- String baseUrl = probeBaseUrl;
-
- // Build headers: Content-Type + Accept presets, then manual headers
- List<FormHelper.HeaderEntry> allHeaders = new ArrayList<>();
- if (probeContentTypeIndex > 0) {
- allHeaders.add(new FormHelper.HeaderEntry(
- new TextInputState("Content-Type"),
- new TextInputState(CONTENT_TYPES[probeContentTypeIndex])));
- }
- if (probeAcceptIndex > 0) {
- allHeaders.add(new FormHelper.HeaderEntry(
- new TextInputState("Accept"),
- new TextInputState(ACCEPT_TYPES[probeAcceptIndex])));
- }
- if (hasProbeHeaders()) {
- for (FormHelper.HeaderEntry he : probeHeaders) {
- allHeaders.add(new FormHelper.HeaderEntry(
- new TextInputState(he.keyInput().text()),
- new TextInputState(he.valueInput().text())));
- }
- }
- List<FormHelper.HeaderEntry> hdrs = allHeaders.isEmpty() ? null :
allHeaders;
-
- // Snapshot manual headers, query params, and indices for history
- List<FormHelper.HeaderEntry> manualHeaderSnapshot =
snapshotEntries(probeHeaders);
- List<FormHelper.HeaderEntry> qpSnapshot =
snapshotEntries(probeQueryParams);
- int ctIdx = probeContentTypeIndex;
- int accIdx = probeAcceptIndex;
- String originalPath = probePathState.text();
- String resolvedPath = path;
-
- ctx.backgroundExecutor.execute(() -> {
- try {
- doProbeRequestInBackground(baseUrl, method, resolvedPath,
originalPath, sendBody,
- hdrs, manualHeaderSnapshot, qpSnapshot, ctIdx, accIdx);
- } finally {
- probeSending.set(false);
- }
- });
- }
-
- private static List<FormHelper.HeaderEntry>
snapshotEntries(List<FormHelper.HeaderEntry> entries) {
- if (entries == null || entries.isEmpty()) {
- return null;
- }
- List<FormHelper.HeaderEntry> snap = new ArrayList<>();
- for (FormHelper.HeaderEntry he : entries) {
- snap.add(new FormHelper.HeaderEntry(
- new TextInputState(he.keyInput().text()),
- new TextInputState(he.valueInput().text())));
- }
- return snap;
- }
-
- private void doProbeRequestInBackground(
- String baseUrl, String method, String resolvedPath, String
originalPath,
- String body, List<FormHelper.HeaderEntry> allHeaders,
- List<FormHelper.HeaderEntry> manualHeaders,
- List<FormHelper.HeaderEntry> queryParams, int contentTypeIdx, int
acceptIdx) {
-
- String url = baseUrl + resolvedPath;
- HttpHelper.HttpResult result = HttpHelper.sendRequest(url, method,
body, allHeaders);
-
- String statusText;
- boolean error;
- List<String> headerLines;
- int httpStatus;
-
- if (result.error() != null) {
- statusText = "Error";
- error = true;
- httpStatus = 0;
- headerLines = new ArrayList<>();
- headerLines.add(result.error());
- } else {
- httpStatus = result.statusCode();
- statusText = String.valueOf(httpStatus);
- error = httpStatus >= 400;
- headerLines = result.headerLines();
- }
-
- ProbeHistoryEntry histEntry = new ProbeHistoryEntry(
- method, originalPath, resolvedPath, manualHeaders, body,
- queryParams, contentTypeIdx, acceptIdx,
- httpStatus, result.elapsed(), statusText, error);
-
- // Apply results on render thread
- String finalRawBody = result.body();
-
- if (ctx.runner != null) {
- ctx.runner.runOnRenderThread(() -> {
- probeResponseStatus = statusText;
- probeResponseElapsed = result.elapsed();
- probeResponseError = error;
- probeResponseHeaderLines = headerLines;
- probeResponseRawBody = finalRawBody;
- probeResponseScroll = 0;
- rebuildResponseLines();
-
- // Add to history (most recent first)
- probeHistory.add(0, histEntry);
- if (probeHistory.size() > MAX_PROBE_HISTORY) {
- probeHistory.remove(probeHistory.size() - 1);
- }
- probeHistoryIndex = 0;
- });
- }
- }
-
- private static String extractBaseUrl(String url) {
- if (url == null) {
- return "http://localhost:8080";
- }
- // Extract scheme + host + port from full URL like
http://0.0.0.0:8080/api/hello
- try {
- URI uri = URI.create(url);
- String host = uri.getHost();
- if ("0.0.0.0".equals(host)) {
- host = "localhost";
- }
- int port = uri.getPort();
- String scheme = uri.getScheme() != null ? uri.getScheme() : "http";
- if (port > 0) {
- return scheme + "://" + host + ":" + port;
- }
- return scheme + "://" + host;
- } catch (Exception e) {
- return "http://localhost:8080";
- }
- }
-
- private void rebuildResponseLines() {
- List<String> lines = new ArrayList<>();
- if (probeResponseHeaderLines != null) {
- lines.addAll(probeResponseHeaderLines);
- }
- if (probeResponseRawBody != null && !probeResponseRawBody.isEmpty()) {
- lines.add("");
- String body = probeResponseRawBody;
- if (probePrettyPrint) {
- body = prettyFormat(body);
- }
- for (String line : body.split("\n", -1)) {
- lines.add(line);
- }
- }
- probeResponseLines = lines;
- }
-
- private void reformatResponseBody() {
- rebuildResponseLines();
- probeResponseScroll = 0;
- }
-
- private static String prettyFormat(String text) {
- if (text == null || text.isEmpty()) {
- return text;
- }
- return CamelCommandHelper.valueAsStringPretty(text, false);
- }
-
- // ---- Probe rendering ----
-
- private void renderProbe(Frame frame, Rect area) {
- frame.renderWidget(Clear.INSTANCE, area);
-
- Rect inner = area;
-
- int pathParamCount = hasPathParams() ? probePathParams.size() : 0;
- int queryParamCount = hasQueryParams() ? probeQueryParams.size() : 0;
- int headerCount = hasProbeHeaders() ? probeHeaders.size() : 0;
- int bodyHeight = 6;
- int requestHeight = 3 // method + URL + path
- + pathParamCount
- + queryParamCount + (queryParamCount > 0 ? 1 : 0)
- + 2 // content-type + accept
- + headerCount + (headerCount > 0 ? 1 : 0)
- + bodyHeight
- + 3; // borders and padding
- int historyHeight = probeHistory.isEmpty() ? 3 : Math.min(4,
probeHistory.size()) + 2;
+ HttpTab(MonitorContext ctx) {
+ super(ctx, "method", "path", "total", "consumes", "produces",
"source");
+ this.probe = new HttpProbe(ctx);
+ }
- List<Rect> chunks = Layout.vertical()
- .constraints(
- Constraint.length(requestHeight),
- Constraint.fill(),
- Constraint.length(historyHeight))
- .split(inner);
+ @Override
+ protected int getRowCount() {
+ return sortedVisibleEndpoints(ctx.findSelectedIntegration()).size();
+ }
- renderProbeRequest(frame, chunks.get(0));
- renderProbeResponse(frame, chunks.get(1));
- renderProbeHistory(frame, chunks.get(2));
+ boolean isProbeMode() {
+ return probe.isActive();
}
- private void renderProbeRequest(Frame frame, Rect area) {
- String method = PROBE_METHODS[probeMethodIndex];
- Title title = Title.from(Line.from(
- Span.styled(" HTTP Probe ", Style.EMPTY.bold()),
- Span.styled(method, methodStyle(method).bold()),
- Span.raw(" " + probePathState.text() + " ")));
-
- boolean requestFocused = probeField != PROBE_HISTORY;
- Style borderStyle = requestFocused ? Style.EMPTY.fg(Theme.accent()) :
Style.EMPTY;
- Block block =
Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
- .borderStyle(borderStyle).title(title).build();
- frame.renderWidget(block, area);
-
- int innerX = area.left() + 2;
- int innerW = area.width() - 4;
- int labelW = 10;
- int fieldW = innerW - labelW;
- int row = area.top() + 1;
-
- // Method selector
- FormHelper.renderLabel(frame, innerX, row, labelW, "Method:",
probeField == PROBE_METHOD);
- Rect methodArea = new Rect(innerX + labelW, row, fieldW, 1);
- Style methodSt = methodStyle(method);
- String leftArr = probeField == PROBE_METHOD ? TuiIcons.ARROW_LEFT + "
" : " ";
- String rightArr = probeField == PROBE_METHOD ? " " +
TuiIcons.ARROW_RIGHT : "";
- frame.renderWidget(Paragraph.from(Line.from(
- Span.styled(leftArr, methodSt),
- Span.styled(method, methodSt.bold()),
- Span.styled(rightArr, methodSt))), methodArea);
-
- // Full URL (read-only)
- row++;
- FormHelper.renderLabel(frame, innerX, row, labelW, "URL:", false);
- String fullUrl = buildFullUrl();
- Rect urlArea = new Rect(innerX + labelW, row, fieldW, 1);
- frame.renderWidget(Paragraph.from(Line.from(
- Span.styled(fullUrl, Theme.info().dim().hyperlink(fullUrl)))),
urlArea);
-
- // Path input
- row++;
- FormHelper.renderLabel(frame, innerX, row, labelW, "Path:", probeField
== PROBE_PATH);
- Rect pathArea = new Rect(innerX + labelW, row, fieldW, 1);
- if (probeField == PROBE_PATH && !probeSending.get()) {
- TextInput textInput =
TextInput.builder().cursorStyle(Style.EMPTY.reversed()).build();
- frame.renderStatefulWidget(textInput, pathArea, probePathState);
- } else {
- String pathText = probePathState.text();
- frame.renderWidget(Paragraph.from(Line.from(
- Span.styled(pathText.isEmpty() ? "/" : pathText,
- pathText.isEmpty() ? Style.EMPTY.dim() :
Style.EMPTY))),
- pathArea);
+ @Override
+ public boolean handleKeyEvent(KeyEvent ke) {
+ if (probe.isActive()) {
+ return probe.handleKeyEvent(ke);
}
- // Path parameters ({xxx} placeholders)
- if (hasPathParams()) {
- for (int i = 0; i < probePathParams.size(); i++) {
- row++;
- PathParam pp = probePathParams.get(i);
- boolean isSelected = probeField == PROBE_PATH_PARAMS &&
probeSelectedPathParam == i;
- String label = i == 0 ? "Params:" : "";
- FormHelper.renderLabel(frame, innerX, row, labelW, label,
- isSelected || (i == 0 && probeField ==
PROBE_PATH_PARAMS));
-
- int fieldX = innerX + labelW;
- // Show param name as prefix
- String paramLabel = "{" + pp.name + "}: ";
- int paramLabelW = Math.min(paramLabel.length(), 15);
- Rect paramLabelArea = new Rect(fieldX, row, paramLabelW, 1);
- frame.renderWidget(Paragraph.from(Line.from(
- Span.styled(paramLabel, isSelected ?
Theme.label().bold() : Theme.label()))),
- paramLabelArea);
-
- Rect paramInputArea = new Rect(fieldX + paramLabelW, row,
fieldW - paramLabelW, 1);
- if (isSelected && !probeSending.get()) {
- TextInput textInput = TextInput.builder()
- .cursorStyle(Style.EMPTY.reversed())
- .placeholder("value for {" + pp.name + "}")
- .build();
- frame.renderStatefulWidget(textInput, paramInputArea,
pp.input);
- } else {
- String val = pp.input.text();
- frame.renderWidget(Paragraph.from(Line.from(
- Span.styled(val.isEmpty() ? "—" : val,
- val.isEmpty() ? Style.EMPTY.dim() :
Style.EMPTY))),
- paramInputArea);
- }
+ if (showSpec) {
+ if (ke.isChar('c') || ke.isCancel()) {
+ showSpec = false;
+ } else if (ke.isUp()) {
+ specScroll = Math.max(0, specScroll - 1);
+ } else if (ke.isDown()) {
+ specScroll++;
+ } else if (ke.isPageUp() || ke.isKey(KeyCode.PAGE_UP)) {
+ specScroll = Math.max(0, specScroll - 20);
+ } else if (ke.isPageDown() || ke.isKey(KeyCode.PAGE_DOWN)) {
+ specScroll += 20;
+ } else if (ke.isHome()) {
+ specScroll = 0;
+ } else if (ke.isEnd()) {
+ specScroll = Integer.MAX_VALUE;
+ } else {
+ return false;
}
+ return true;
}
- // Query parameters
- if (hasQueryParams()) {
- row = renderKeyValueSection(frame, row, innerX, labelW, fieldW,
"Query:",
- probeQueryParams, PROBE_QUERY_PARAMS,
probeSelectedQueryParam, probeEditingQueryKey);
- }
-
- // Content-Type cycler
- row++;
- renderCycler(frame, innerX, row, labelW, fieldW, "Content:",
- CONTENT_TYPES, probeContentTypeIndex, probeField ==
PROBE_CONTENT_TYPE);
-
- // Accept cycler
- row++;
- renderCycler(frame, innerX, row, labelW, fieldW, "Accept:",
- ACCEPT_TYPES, probeAcceptIndex, probeField == PROBE_ACCEPT);
+ return super.handleKeyEvent(ke);
+ }
- // Headers
- if (hasProbeHeaders()) {
- row = renderKeyValueSection(frame, row, innerX, labelW, fieldW,
"Headers:",
- probeHeaders, PROBE_HEADERS, probeSelectedHeader,
probeEditingHeaderKey);
+ @Override
+ protected boolean handleTabKeyEvent(KeyEvent ke) {
+ if (ke.isConfirm()) {
+ enterProbeModeFromTable();
+ return true;
}
-
- // Body input (multi-line TextArea)
- row++;
- int bodyHeight = 6;
- FormHelper.renderLabel(frame, innerX, row, labelW, "Body:", probeField
== PROBE_BODY);
- Rect bodyArea = new Rect(innerX + labelW, row, fieldW, bodyHeight);
- Style bodyBorderStyle = probeField == PROBE_BODY ?
Style.EMPTY.fg(Theme.accent()) : Theme.muted();
- Block bodyBlock = Block.builder()
- .borders(Borders.ALL)
- .borderType(BorderType.ROUNDED)
- .borderStyle(bodyBorderStyle)
- .build();
- Rect bodyInner = bodyBlock.inner(bodyArea);
- frame.renderWidget(bodyBlock, bodyArea);
- TextArea textArea = TextArea.builder()
- .cursorStyle(Style.EMPTY.reversed())
- .placeholder("body text, JSON, XML, or file:payload.json")
- .build();
- if (probeField == PROBE_BODY && !probeSending.get()) {
- textArea.renderWithCursor(bodyInner, frame.buffer(),
probeBodyState, frame);
- } else {
- textArea.render(bodyInner, frame.buffer(), probeBodyState);
+ if (ke.isCharIgnoreCase('f')) {
+ filter = (filter + 1) % 3;
+ return true;
}
- }
-
- private String buildFullUrl() {
- String base = probeBaseUrl != null ? probeBaseUrl : "";
- String path = probePathState.text();
- if (hasPathParams()) {
- for (PathParam pp : probePathParams) {
- String val = pp.input.text();
- if (!val.isEmpty()) {
- path = path.replace("{" + pp.name + "}", val);
- }
- }
+ if (ke.isCharIgnoreCase('m')) {
+ showManagement = !showManagement;
+ return true;
}
- return base + path;
- }
-
- private void renderCycler(
- Frame frame, int x, int row, int labelW, int fieldW,
- String label, String[] options, int activeIndex, boolean selected)
{
-
- FormHelper.renderLabel(frame, x, row, labelW, label, selected);
- Rect fieldArea = new Rect(x + labelW, row, fieldW, 1);
- String display = activeIndex == 0 ? "(none)" : options[activeIndex];
- String leftArr = selected ? TuiIcons.ARROW_LEFT + " " : " ";
- String rightArr = selected ? " " + TuiIcons.ARROW_RIGHT : "";
- Style style = activeIndex == 0 ? Style.EMPTY.dim() : Style.EMPTY;
- frame.renderWidget(Paragraph.from(Line.from(
- Span.styled(leftArr, style),
- Span.styled(display, selected ? style.bold() : style),
- Span.styled(rightArr, style))), fieldArea);
+ if (ke.isChar('c')) {
+ loadSpecForSelectedEndpoint();
+ return true;
+ }
+ return false;
}
- private int renderKeyValueSection(
- Frame frame, int startRow, int innerX, int labelW, int fieldW,
- String sectionLabel, List<FormHelper.HeaderEntry> entries,
- int fieldId, int selectedIdx, boolean editingKey) {
-
- int keyW = Math.min(20, fieldW / 3);
- int valW = fieldW - keyW - 3;
- int row = startRow;
- for (int i = 0; i < entries.size(); i++) {
- row++;
- boolean isSelected = probeField == fieldId && selectedIdx == i;
- String label = i == 0 ? sectionLabel : "";
- FormHelper.renderLabel(frame, innerX, row, labelW, label,
- isSelected || (i == 0 && probeField == fieldId));
-
- FormHelper.HeaderEntry he = entries.get(i);
- int fieldX = innerX + labelW;
-
- Rect keyArea = new Rect(fieldX, row, keyW, 1);
- if (isSelected && editingKey && !probeSending.get()) {
- TextInput keyInput =
TextInput.builder().cursorStyle(Style.EMPTY.reversed()).build();
- frame.renderStatefulWidget(keyInput, keyArea, he.keyInput());
- } else {
- String keyText = he.keyInput().text();
- Style keyStyle = keyText.isEmpty() ? Style.EMPTY.dim()
- : isSelected ? Style.EMPTY.bold() : Style.EMPTY;
- frame.renderWidget(Paragraph.from(Line.from(
- Span.styled(keyText.isEmpty() ? "<key>" : keyText,
keyStyle))), keyArea);
+ @Override
+ public boolean handleMouseEvent(MouseEvent me, Rect area) {
+ if (!probe.isActive() && !showSpec && vSplit.handleMouse(me, me.y())) {
+ if (vSplit.isDragging() && me.kind() == MouseEventKind.DRAG) {
+ detailPanelHeight = Math.max(3, Math.min(area.y() +
area.height() - me.y(), area.height() - 5));
}
-
- Rect sepArea = new Rect(fieldX + keyW, row, 3, 1);
- frame.renderWidget(Paragraph.from(Line.from(
- Span.styled(" : ", Style.EMPTY.dim()))), sepArea);
-
- Rect valArea = new Rect(fieldX + keyW + 3, row, valW, 1);
- if (isSelected && !editingKey && !probeSending.get()) {
- TextInput valInput =
TextInput.builder().cursorStyle(Style.EMPTY.reversed()).build();
- frame.renderStatefulWidget(valInput, valArea, he.valueInput());
- } else {
- String valText = he.valueInput().text();
- Style valStyle = valText.isEmpty() ? Style.EMPTY.dim()
- : isSelected ? Style.EMPTY.bold() : Style.EMPTY;
- frame.renderWidget(Paragraph.from(Line.from(
- Span.styled(valText.isEmpty() ? "<value>" : valText,
valStyle))), valArea);
+ return true;
+ }
+ if (!probe.isActive() && !showSpec) {
+ List<HttpEndpointInfo> visible =
sortedVisibleEndpoints(ctx.findSelectedIntegration());
+ if (handleTableClick(me, lastTableArea, tableState,
visible.size())) {
+ return true;
}
}
- return row;
- }
-
- private void renderProbeResponse(Frame frame, Rect area) {
- boolean hasResponse = probeResponseStatus != null;
-
- // Build tab header title
- Title title;
- if (!hasResponse) {
- title = Title.from(Line.from(Span.styled(" Request ",
Style.EMPTY.bold())));
- } else {
- Style reqStyle = probeViewRequest ? Style.EMPTY.bold() :
Style.EMPTY.dim();
- Style resStyle = probeViewRequest ? Style.EMPTY.dim() :
Style.EMPTY.bold();
-
- String statusSuffix = "";
- Style statusColor = Style.EMPTY;
- if (probeResponseError) {
- statusSuffix = " " + probeResponseStatus;
- statusColor = Theme.error();
- } else if ("Sending...".equals(probeResponseStatus)) {
- statusSuffix = " Sending...";
- statusColor = Theme.warning();
- } else {
- statusSuffix = " " + probeResponseStatus;
- if (probeResponseElapsed > 0) {
- statusSuffix += " (" + probeResponseElapsed + "ms)";
- }
- statusColor = statusStyle(probeResponseStatus);
+ if (showSpec) {
+ if (me.kind() == MouseEventKind.SCROLL_UP) {
+ specScroll = Math.max(0, specScroll - MOUSE_SCROLL_LINES);
+ return true;
+ }
+ if (me.kind() == MouseEventKind.SCROLL_DOWN) {
+ specScroll += MOUSE_SCROLL_LINES;
+ return true;
}
-
- title = Title.from(Line.from(
- Span.styled(" Request ", reqStyle),
- Span.styled("|", Style.EMPTY.dim()),
- Span.styled(" Response", resStyle),
- Span.styled(statusSuffix + " ", statusColor.bold())));
}
-
- // Show request preview (before first send, or when request tab is
active)
- if (!hasResponse || probeViewRequest) {
- List<Line> preview = buildRequestPreview();
- frame.renderWidget(
- Paragraph.builder()
- .text(Text.from(preview))
-
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).build())
- .build(),
- area);
- return;
+ if (probe.isActive()) {
+ return probe.handleMouseScroll(me);
}
+ return false;
+ }
- // Show response
- if (probeResponseLines == null || probeResponseLines.isEmpty()) {
- frame.renderWidget(
- Paragraph.builder()
- .text(Text.from(Line.from(Span.styled(" No
response content", Style.EMPTY.dim()))))
-
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title).build())
- .build(),
- area);
- return;
+ @Override
+ public boolean handleEscape() {
+ if (probe.isActive()) {
+ probe.exit();
+ return true;
}
-
- int visibleLines = area.height() - 2;
- if (visibleLines < 1) {
- visibleLines = 1;
+ if (showSpec) {
+ showSpec = false;
+ return true;
}
- int totalLines = probeResponseLines.size();
- int maxScroll = Math.max(0, totalLines - visibleLines);
- probeResponseScroll = Math.min(probeResponseScroll, maxScroll);
+ return false;
+ }
- int end = Math.min(probeResponseScroll + visibleLines, totalLines);
- List<Line> lines = new ArrayList<>();
- for (int i = probeResponseScroll; i < end; i++) {
- String line = probeResponseLines.get(i);
- if (line.isEmpty()) {
- lines.add(Line.from(Span.raw("")));
- } else if (line.contains(": ") && !line.startsWith(" ") &&
!line.startsWith("{")
- && !line.startsWith("[") && !line.startsWith("\"")) {
- int colon = line.indexOf(": ");
- lines.add(Line.from(
- Span.styled(line.substring(0, colon + 1),
Theme.label()),
- Span.raw(line.substring(colon + 1))));
- } else {
- lines.add(Line.from(Span.raw(line)));
- }
+ @Override
+ public void navigateUp() {
+ if (probe.isActive()) {
+ return;
}
+ tableState.selectPrevious();
+ }
- Block.Builder blockBuilder =
Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL).title(title);
- if (totalLines > visibleLines) {
- int pct = (int) ((probeResponseScroll + visibleLines) * 100L /
totalLines);
- String scrollInfo = " " + pct + "% (" + totalLines + " lines) ";
-
blockBuilder.titleBottom(Title.from(Line.from(Span.styled(scrollInfo,
Style.EMPTY.dim()))));
+ @Override
+ public void navigateDown() {
+ if (probe.isActive()) {
+ return;
}
+ List<HttpEndpointInfo> visible =
sortedVisibleEndpoints(ctx.findSelectedIntegration());
+ tableState.selectNext(visible.size());
+ }
- frame.renderWidget(
- Paragraph.builder()
- .text(Text.from(lines))
- .block(blockBuilder.build())
- .build(),
- area);
+ @Override
+ public void onIntegrationChanged() {
+ showSpec = false;
+ specLines = Collections.emptyList();
+ specTitle = null;
+ specScroll = 0;
+ probe.exit();
}
- private List<Line> buildRequestPreview() {
- List<Line> lines = new ArrayList<>();
- String method = PROBE_METHODS[probeMethodIndex];
- String fullUrl = buildFullUrl();
-
- // Add query params to the preview URL
- if (hasQueryParams()) {
- StringBuilder sb = new StringBuilder(fullUrl);
- char sep = fullUrl.contains("?") ? '&' : '?';
- for (FormHelper.HeaderEntry qp : probeQueryParams) {
- String k = qp.keyInput().text().trim();
- String v = qp.valueInput().text();
- if (!k.isEmpty()) {
- sb.append(sep).append(k).append('=').append(v);
- sep = '&';
- }
- }
- fullUrl = sb.toString();
+ @Override
+ protected void renderContent(Frame frame, Rect area, IntegrationInfo info)
{
+ if (probe.isActive()) {
+ probe.render(frame, area);
+ return;
}
- lines.add(Line.from(
- Span.styled(" " + method, methodStyle(method).bold()),
- Span.raw(" " + fullUrl)));
- lines.add(Line.from(Span.raw("")));
-
- // Headers
- String ct = CONTENT_TYPES[probeContentTypeIndex];
- if (!ct.isEmpty()) {
- lines.add(Line.from(
- Span.styled(" Content-Type: ", Theme.label()),
- Span.raw(ct)));
- }
- String acc = ACCEPT_TYPES[probeAcceptIndex];
- if (!acc.isEmpty()) {
- lines.add(Line.from(
- Span.styled(" Accept: ", Theme.label()),
- Span.raw(acc)));
- }
- if (hasProbeHeaders()) {
- for (FormHelper.HeaderEntry h : probeHeaders) {
- String k = h.keyInput().text().trim();
- String v = h.valueInput().text();
- if (!k.isEmpty()) {
- lines.add(Line.from(
- Span.styled(" " + k + ": ", Theme.label()),
- Span.raw(v)));
- }
- }
+ if (showSpec) {
+ renderSpec(frame, area);
+ return;
}
- // Body
- String body = probeBodyState.text();
- if (body != null && !body.isEmpty()) {
- lines.add(Line.from(Span.raw("")));
- for (String bl : body.split("\n", -1)) {
- lines.add(Line.from(Span.raw(" " + bl)));
- }
- }
+ List<HttpEndpointInfo> visible = sortedVisibleEndpoints(info);
- if (lines.size() <= 1) {
- lines.add(Line.from(Span.styled(" Press F5 to send",
Style.EMPTY.dim())));
- }
- return lines;
- }
+ detailPanelHeight = Math.max(3, Math.min(detailPanelHeight,
area.height() - 5));
+ List<Rect> chunks = Layout.vertical()
+ .constraints(Constraint.fill(),
Constraint.length(detailPanelHeight))
+ .split(area);
- private void renderProbeHistory(Frame frame, Rect area) {
- String title = " History [" + probeHistory.size() + "] ";
- boolean historyFocused = probeField == PROBE_HISTORY;
- Style histBorderStyle = historyFocused ?
Style.EMPTY.fg(Theme.accent()) : Style.EMPTY;
+ renderTable(frame, chunks.get(0), visible, info);
+ vSplit.setBorderPos(chunks.get(1).y());
+ renderDetail(frame, chunks.get(1), visible);
+ }
- if (probeHistory.isEmpty()) {
- frame.renderWidget(
- Paragraph.builder()
- .text(Text.from(Line.from(
- Span.styled(" No requests yet",
Style.EMPTY.dim()))))
-
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
-
.borderStyle(histBorderStyle).title(title).build())
- .build(),
- area);
+ @Override
+ public void renderFooter(List<Span> spans) {
+ if (probe.isActive()) {
+ probe.renderFooter(spans);
return;
}
-
- int visibleLines = area.height() - 2;
- if (visibleLines < 1) {
- visibleLines = 1;
+ if (showSpec) {
+ hint(spans, "c/Esc", "close");
+ hint(spans, TuiIcons.HINT_SCROLL, "scroll");
+ hintLast(spans, "PgUp/PgDn", "page");
+ return;
}
-
- // Ensure selected index is visible
- int start = 0;
- if (probeHistoryIndex >= visibleLines) {
- start = probeHistoryIndex - visibleLines + 1;
+ hint(spans, "Esc", "back");
+ hint(spans, "Enter", "probe");
+ hint(spans, "s", "sort");
+ String[] filterLabels = { "all", "rest", "http" };
+ hint(spans, "f", "filter [" + filterLabels[filter] + "]");
+ hint(spans, "m", "management" + (showManagement ? " [on]" : " [off]"));
+ List<HttpEndpointInfo> hVisible =
sortedVisibleEndpoints(ctx.findSelectedIntegration());
+ Integer hSel = tableState.selected();
+ if (hSel != null && hSel >= 0 && hSel < hVisible.size() &&
hVisible.get(hSel).specificationUri != null) {
+ hintLast(spans, "c", "spec");
}
- int end = Math.min(start + visibleLines, probeHistory.size());
+ }
- List<Line> lines = new ArrayList<>();
- for (int i = start; i < end; i++) {
- ProbeHistoryEntry entry = probeHistory.get(i);
- boolean selected = historyFocused && i == probeHistoryIndex;
- String pointer = selected ? TuiIcons.POINTER + " " : " ";
- String methodStr = String.format("%-8s", entry.method);
- String statusStr = entry.error ? "ERR" : entry.statusText;
- String elapsedStr = entry.elapsed > 0 ? entry.elapsed + "ms" : "";
- String bodySnippet = entry.body != null && !entry.body.isEmpty()
- ? " " + TuiHelper.truncate(entry.body, 30)
- : "";
-
- Style lineStyle = selected ? Style.EMPTY.bold() : Style.EMPTY;
- lines.add(Line.from(
- Span.styled(pointer, lineStyle),
- Span.styled(methodStr, methodStyle(entry.method)),
- Span.styled(entry.resolvedPath + " ", lineStyle),
- Span.styled(statusStr, selected ? statusStyle(statusStr) :
statusStyle(statusStr).dim()),
- Span.styled(" " + elapsedStr, Style.EMPTY.dim()),
- Span.styled(bodySnippet, Style.EMPTY.dim())));
+ void handlePaste(String text) {
+ if (probe.isActive()) {
+ probe.handlePaste(text);
}
-
- frame.renderWidget(
- Paragraph.builder()
- .text(Text.from(lines))
-
.block(Block.builder().borderType(BorderType.ROUNDED).borders(Borders.ALL)
-
.borderStyle(histBorderStyle).title(title).build())
- .build(),
- area);
}
- private static Style statusStyle(String status) {
- if (status == null) {
- return Style.EMPTY.bold();
- }
- try {
- int code = Integer.parseInt(status);
- if (code >= 200 && code < 300) {
- return Theme.success().bold();
- }
- if (code >= 300 && code < 400) {
- return Style.EMPTY.fg(Theme.accent()).bold();
- }
- if (code >= 400 && code < 500) {
- return Theme.warning().bold();
- }
- if (code >= 500) {
- return Theme.error().bold();
- }
- } catch (NumberFormatException e) {
- // not a number — treat as error
+ private void enterProbeModeFromTable() {
+ List<HttpEndpointInfo> visible =
sortedVisibleEndpoints(ctx.findSelectedIntegration());
+ Integer sel = tableState.selected();
+ if (sel == null || sel < 0 || sel >= visible.size()) {
+ return;
}
- return Theme.error().bold();
+ probe.enter(visible.get(sel));
}
- // ---- Existing table/spec methods ----
+ // ---- Table rendering ----
List<HttpEndpointInfo> sortedVisibleEndpoints(IntegrationInfo info) {
List<HttpEndpointInfo> visible = visibleEndpoints(info);
@@ -2023,6 +421,8 @@ class HttpTab extends AbstractTableTab {
renderScrollbar(frame, visible.size());
}
+ // ---- Detail panel ----
+
private void renderDetail(Frame frame, Rect area, List<HttpEndpointInfo>
visible) {
Integer sel = tableState.selected();
if (sel == null || sel < 0 || sel >= visible.size()) {
@@ -2108,6 +508,8 @@ class HttpTab extends AbstractTableTab {
Span.raw(value)));
}
+ // ---- Spec viewer ----
+
private void loadSpecForSelectedEndpoint() {
if (ctx.selectedPid == null || ctx.runner == null) {
return;
@@ -2265,6 +667,8 @@ class HttpTab extends AbstractTableTab {
area);
}
+ // ---- Selection / MCP ----
+
@Override
public SelectionContext getSelectionContext() {
IntegrationInfo info = ctx.findSelectedIntegration();
@@ -2279,18 +683,6 @@ class HttpTab extends AbstractTableTab {
return new SelectionContext("table", items, sel != null ? sel : -1,
items.size(), "HTTP");
}
- record PathParam(String name, TextInputState input) {
- }
-
- record ProbeHistoryEntry(
- String method, String path, String resolvedPath,
- List<FormHelper.HeaderEntry> headers, String body,
- List<FormHelper.HeaderEntry> queryParams,
- int contentTypeIndex, int acceptIndex,
- int statusCode, long elapsed,
- String statusText, boolean error) {
- }
-
@Override
public String description() {
return "HTTP endpoint probe — lightweight Postman for testing
REST/HTTP endpoints";
@@ -2362,36 +754,8 @@ class HttpTab extends AbstractTableTab {
@Override
public boolean setInputValue(String field, String value) {
- if (!probeMode) {
- return false;
- }
- String v = value != null ? value : "";
- if ("path".equals(field)) {
- probePathState.setText(v);
- rescanPathParams();
- return true;
- }
- if ("body".equals(field)) {
- probeBodyState.setText(v);
- return true;
- }
- if ("method".equals(field)) {
- String upper = v.toUpperCase(Locale.ENGLISH);
- for (int i = 0; i < PROBE_METHODS.length; i++) {
- if (PROBE_METHODS[i].equals(upper)) {
- probeMethodIndex = i;
- return true;
- }
- }
- return false;
- }
- if ("content-type".equals(field)) {
- probeContentTypeIndex = findPresetIndex(CONTENT_TYPES, v);
- return true;
- }
- if ("accept".equals(field)) {
- probeAcceptIndex = findPresetIndex(ACCEPT_TYPES, v);
- return true;
+ if (probe.isActive()) {
+ return probe.setInputValue(field, value);
}
return false;
}
@@ -2402,79 +766,14 @@ class HttpTab extends AbstractTableTab {
if (info == null) {
return null;
}
- JsonObject result = new JsonObject();
- result.put("tab", "HTTP");
- if (probeMode) {
- result.put("probeMode", true);
- result.put("method", PROBE_METHODS[probeMethodIndex]);
- result.put("path", probePathState.text());
- String ct = CONTENT_TYPES[probeContentTypeIndex];
- if (!ct.isEmpty()) {
- result.put("contentType", ct);
- }
- String acc = ACCEPT_TYPES[probeAcceptIndex];
- if (!acc.isEmpty()) {
- result.put("accept", acc);
- }
- if (hasPathParams()) {
- JsonObject pp = new JsonObject();
- for (PathParam p : probePathParams) {
- pp.put(p.name, p.input.text());
- }
- result.put("pathParams", pp);
- }
- if (hasQueryParams()) {
- JsonArray qp = new JsonArray();
- for (FormHelper.HeaderEntry e : probeQueryParams) {
- JsonObject entry = new JsonObject();
- entry.put("key", e.keyInput().text());
- entry.put("value", e.valueInput().text());
- qp.add(entry);
- }
- result.put("queryParams", qp);
- }
- if (hasProbeHeaders()) {
- JsonArray hd = new JsonArray();
- for (FormHelper.HeaderEntry e : probeHeaders) {
- JsonObject entry = new JsonObject();
- entry.put("key", e.keyInput().text());
- entry.put("value", e.valueInput().text());
- hd.add(entry);
- }
- result.put("headers", hd);
- }
- String body = probeBodyState.text();
- if (body != null && !body.isEmpty()) {
- result.put("body", body);
- }
- if (probeResponseStatus != null) {
- result.put("responseStatus", probeResponseStatus);
- }
- if (probeResponseElapsed > 0) {
- result.put("responseElapsed", probeResponseElapsed);
- }
- if (probeResponseRawBody != null &&
!probeResponseRawBody.isEmpty()) {
- result.put("responseBody", probeResponseRawBody);
- }
- if (!probeHistory.isEmpty()) {
- JsonArray hist = new JsonArray();
- for (ProbeHistoryEntry he : probeHistory) {
- JsonObject entry = new JsonObject();
- entry.put("method", he.method);
- entry.put("path", he.path);
- entry.put("statusCode", he.statusCode);
- entry.put("elapsed", he.elapsed);
- if (he.error) {
- entry.put("error", true);
- }
- hist.add(entry);
- }
- result.put("history", hist);
- }
- return result;
+ if (probe.isActive()) {
+ return probe.toJson();
}
+ JsonObject result = new JsonObject();
+ result.put("tab", "HTTP");
+
JsonArray rows = new JsonArray();
for (HttpEndpointInfo hi : info.httpEndpoints) {
JsonObject row = new JsonObject();