fix: tighten tui layout and request progress
This commit is contained in:
@@ -359,6 +359,24 @@ def age_since(value: Any) -> str:
|
|||||||
return format_elapsed_seconds(seconds)
|
return format_elapsed_seconds(seconds)
|
||||||
|
|
||||||
|
|
||||||
|
def request_updated_at(row: dict[str, Any]) -> Any:
|
||||||
|
for key in ("last_activity_at", "usage_last_updated_at", "finished_at"):
|
||||||
|
value = row.get(key)
|
||||||
|
if parse_datetime(value):
|
||||||
|
return value
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def request_updated_elapsed(row: dict[str, Any]) -> str:
|
||||||
|
return elapsed_between(row.get("started_at"), request_updated_at(row))
|
||||||
|
|
||||||
|
|
||||||
|
def request_chunk_progress(row: dict[str, Any]) -> str:
|
||||||
|
if not row.get("response_stream"):
|
||||||
|
return "-"
|
||||||
|
return f"{row.get('stream_chunk_count') or 0} / {format_bytes(row.get('response_bytes_received'))}"
|
||||||
|
|
||||||
|
|
||||||
def status_kind(value: Any) -> str:
|
def status_kind(value: Any) -> str:
|
||||||
text = str(value or "").strip().lower()
|
text = str(value or "").strip().lower()
|
||||||
if text in {"ok", "operational", "success"}:
|
if text in {"ok", "operational", "success"}:
|
||||||
@@ -558,7 +576,7 @@ def render_request_detail(row: dict[str, Any]) -> str:
|
|||||||
f"life {request_lifecycle_label(row)}",
|
f"life {request_lifecycle_label(row)}",
|
||||||
f"started {short_time(row['started_at'])}",
|
f"started {short_time(row['started_at'])}",
|
||||||
f"finished {short_time(row['finished_at'])}",
|
f"finished {short_time(row['finished_at'])}",
|
||||||
f"updated {age_since(row['usage_last_updated_at'])}",
|
f"updated {request_updated_elapsed(row)}",
|
||||||
]
|
]
|
||||||
if row.get("model"):
|
if row.get("model"):
|
||||||
bits.append(f"model {row['model']}")
|
bits.append(f"model {row['model']}")
|
||||||
@@ -753,11 +771,11 @@ def run_textual(api_url: str, status_url: str, refresh_seconds: int, timeout: in
|
|||||||
Screen { layout: vertical; }
|
Screen { layout: vertical; }
|
||||||
#summary { height: 1; padding: 0 1; color: $accent; }
|
#summary { height: 1; padding: 0 1; color: $accent; }
|
||||||
#paths { height: 1; padding: 0 1; color: $text-muted; }
|
#paths { height: 1; padding: 0 1; color: $text-muted; }
|
||||||
#controls { height: 2; }
|
#controls { height: 1; padding: 0 1; color: $text-muted; }
|
||||||
#tables { height: 1fr; }
|
#tables { height: 1; padding: 0 1; color: $text-muted; }
|
||||||
#requests_table, #logs_table, #profiles_table { height: 1fr; }
|
#requests_table, #logs_table, #profiles_table { height: 1fr; }
|
||||||
#detail { height: 5; padding: 0 1; border-top: solid $panel; }
|
#detail { height: 3; padding: 0 1; border-top: solid $panel; }
|
||||||
#status { height: 2; padding: 0 1; color: $text-muted; }
|
#status { height: 1; padding: 0 1; color: $text-muted; }
|
||||||
"""
|
"""
|
||||||
|
|
||||||
BINDINGS = [
|
BINDINGS = [
|
||||||
@@ -813,7 +831,7 @@ def run_textual(api_url: str, status_url: str, refresh_seconds: int, timeout: in
|
|||||||
requests = self.query_one("#requests_table", DataTable)
|
requests = self.query_one("#requests_table", DataTable)
|
||||||
requests.cursor_type = "row"
|
requests.cursor_type = "row"
|
||||||
requests.zebra_stripes = True
|
requests.zebra_stripes = True
|
||||||
requests.add_columns("Seq", "ID", "Status", "Path", "Model", "Reasoning", "Req", "Resp", "First", "Duration", "Updated", "Note")
|
requests.add_columns("Seq", "ID", "Status", "Path", "Model", "Reasoning", "Req", "Resp", "Chunks", "First", "Duration", "Updated", "Note")
|
||||||
|
|
||||||
logs = self.query_one("#logs_table", DataTable)
|
logs = self.query_one("#logs_table", DataTable)
|
||||||
logs.cursor_type = "row"
|
logs.cursor_type = "row"
|
||||||
@@ -994,9 +1012,10 @@ def run_textual(api_url: str, status_url: str, refresh_seconds: int, timeout: in
|
|||||||
row["reasoning_tokens"] if row["reasoning_tokens"] is not None else "-",
|
row["reasoning_tokens"] if row["reasoning_tokens"] is not None else "-",
|
||||||
format_bytes(row["request_body_bytes"]),
|
format_bytes(row["request_body_bytes"]),
|
||||||
format_bytes(row["response_bytes_received"]),
|
format_bytes(row["response_bytes_received"]),
|
||||||
|
request_chunk_progress(row),
|
||||||
format_ms(row["first_response_delay_ms"]),
|
format_ms(row["first_response_delay_ms"]),
|
||||||
format_ms(row["duration_ms"]),
|
format_ms(row["duration_ms"]),
|
||||||
age_since(row.get("usage_last_updated_at")),
|
request_updated_elapsed(row),
|
||||||
f"{row['upstream_attempt_count'] or 0}x {short_text(row['error'], 18)}",
|
f"{row['upstream_attempt_count'] or 0}x {short_text(row['error'], 18)}",
|
||||||
key=key,
|
key=key,
|
||||||
)
|
)
|
||||||
@@ -1236,7 +1255,7 @@ def print_once(snapshot: dict[str, Any], filter_text: str = "") -> None:
|
|||||||
print(
|
print(
|
||||||
f"{row['seq']:>6} {short_text(row['request_id'], 16):<16} {status_symbol(row)} {row.get('status_code') or '-':<4} "
|
f"{row['seq']:>6} {short_text(row['request_id'], 16):<16} {status_symbol(row)} {row.get('status_code') or '-':<4} "
|
||||||
f"{short_text(row['path'], 20):<20} {short_text(row['model'] or row['requested_model'] or row['forwarded_model'], 16):<16} "
|
f"{short_text(row['path'], 20):<20} {short_text(row['model'] or row['requested_model'] or row['forwarded_model'], 16):<16} "
|
||||||
f"{format_ms(row['duration_ms']):<8} {age_since(row.get('usage_last_updated_at')):<8} "
|
f"{request_chunk_progress(row):<14} {format_ms(row['duration_ms']):<8} {request_updated_elapsed(row):<8} "
|
||||||
f"{format_count(row['upstream_attempt_count'] or 0):<4} {short_text(row['error'], 30)}"
|
f"{format_count(row['upstream_attempt_count'] or 0):<4} {short_text(row['error'], 30)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "codex-retry-gateway-tui"
|
name = "codex-retry-gateway-tui"
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
description = "Terminal UI for codex-retry-gateway monitoring and control"
|
description = "Terminal UI for codex-retry-gateway monitoring and control"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
|
|||||||
+21
-1
@@ -72,7 +72,27 @@ class CodexRetryGatewayTUITests(unittest.TestCase):
|
|||||||
mod.gateway_status_url("http://127.0.0.1:4610/__codex_retry_gateway"),
|
mod.gateway_status_url("http://127.0.0.1:4610/__codex_retry_gateway"),
|
||||||
"http://127.0.0.1:4610/__codex_retry_gateway/api/status",
|
"http://127.0.0.1:4610/__codex_retry_gateway/api/status",
|
||||||
)
|
)
|
||||||
self.assertIn("updated", mod.render_request_detail({"seq": 1, "request_id": "r", "method": "POST", "path": "/responses", "status_code": 200, "upstream_status_code": 200, "upstream_attempt_count": 1, "first_response_delay_ms": 10, "duration_ms": 20, "request_body_bytes": 3, "response_bytes_received": 4, "stream_chunk_count": 1, "started_at": "2026-06-30T00:00:00Z", "finished_at": "2026-06-30T00:00:01Z", "usage_last_updated_at": "2026-06-30T00:00:01Z"}))
|
row = {
|
||||||
|
"seq": 1,
|
||||||
|
"request_id": "r",
|
||||||
|
"method": "POST",
|
||||||
|
"path": "/responses",
|
||||||
|
"status_code": 200,
|
||||||
|
"upstream_status_code": 200,
|
||||||
|
"upstream_attempt_count": 1,
|
||||||
|
"first_response_delay_ms": 10,
|
||||||
|
"duration_ms": 20,
|
||||||
|
"request_body_bytes": 3,
|
||||||
|
"response_bytes_received": 4096,
|
||||||
|
"stream_chunk_count": 7,
|
||||||
|
"response_stream": True,
|
||||||
|
"started_at": "2026-06-30T00:00:00Z",
|
||||||
|
"finished_at": "2026-06-30T00:00:01Z",
|
||||||
|
"usage_last_updated_at": "2026-06-30T00:00:01Z",
|
||||||
|
}
|
||||||
|
self.assertIn("updated 1.0s", mod.render_request_detail(row))
|
||||||
|
self.assertEqual(mod.request_updated_elapsed(row), "1.0s")
|
||||||
|
self.assertEqual(mod.request_chunk_progress(row), "7 / 4.1KB")
|
||||||
|
|
||||||
def test_default_api_url_discovers_gateway_state(self) -> None:
|
def test_default_api_url_discovers_gateway_state(self) -> None:
|
||||||
mod = load_module()
|
mod = load_module()
|
||||||
|
|||||||
Reference in New Issue
Block a user