feat: show token usage and cost tracker in gateway status popover (#284)

* feat: show token usage, cost tracker, and uptime in gateway status popover

The "Connected" hover popover in the web gateway now displays three
sections: connection info (SSE/WS counts, uptime), daily cost tracker
(spend + actions/hr), and per-model token usage (input/output counts
with cost per model). Also fixes the field name mismatch between the
backend response and JS rendering that prevented the popover from
showing correct data.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix: address PR review — escape HTML in popover, add model_usage test

- Escape model name and cost strings with escapeHtml() before inserting
  into innerHTML to prevent XSS via crafted model names
- Add test_model_usage_per_model_tracking test covering multi-model
  token/cost accumulation in CostGuard

Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: Claude Opus 4.6 <[email protected]>
This commit is contained in:
Illia Polosukhin
2026-02-21 03:45:04 +00:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 493e4578d0
commit b68d67bd35
9 changed files with 241 additions and 14 deletions
+4
View File
@@ -198,6 +198,8 @@ async fn start_test_server_with_provider(
skill_registry: None,
skill_catalog: None,
chat_rate_limiter: ironclaw::channels::web::server::RateLimiter::new(30, 60),
cost_guard: None,
startup_time: std::time::Instant::now(),
});
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
@@ -683,6 +685,8 @@ async fn test_no_llm_provider_returns_503() {
skill_registry: None,
skill_catalog: None,
chat_rate_limiter: ironclaw::channels::web::server::RateLimiter::new(30, 60),
cost_guard: None,
startup_time: std::time::Instant::now(),
});
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
+2
View File
@@ -56,6 +56,8 @@ async fn start_test_server() -> (
skill_registry: None,
skill_catalog: None,
chat_rate_limiter: ironclaw::channels::web::server::RateLimiter::new(30, 60),
cost_guard: None,
startup_time: std::time::Instant::now(),
});
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();