surface rate_limited status as red badge on per-agent page and dashboard

- add rate_limited: Arc<AtomicBool> to Bus; set/cleared by emit_status
- write/remove sentinel file hyperhive-rate-limited in state dir so host-side
  dashboard can detect it without a live socket call
- api_state returns status=rate_limited when flag is set (cold-load accurate)
- ALIVE_LABELS gains rate_limited entry (⊘ red chip) on per-agent page
- ContainerView gains rate_limited: bool read from sentinel file
- dashboard container row shows ⊘ rate limited badge (red) ahead of needs_login

Closes #24
This commit is contained in:
iris 2026-05-20 15:16:00 +02:00
parent 808b9cbe1a
commit 804875d670
7 changed files with 69 additions and 10 deletions

View file

@ -340,7 +340,7 @@ struct StateSnapshot {
seq: u64,
label: String,
dashboard_port: u16,
/// `"online"` | `"needs_login_idle"` | `"needs_login_in_progress"`.
/// `"online"` | `"rate_limited"` | `"needs_login_idle"` | `"needs_login_in_progress"`.
status: &'static str,
/// Present when `status == "needs_login_in_progress"`.
session: Option<SessionView>,
@ -431,6 +431,7 @@ async fn api_state(State(state): State<AppState>) -> axum::Json<StateSnapshot> {
let login = *state.login.lock().unwrap();
let session_snapshot = state.session.lock().unwrap().clone();
let (status, session_view) = match (login, session_snapshot) {
(LoginState::Online, _) if state.bus.is_rate_limited() => ("rate_limited", None),
(LoginState::Online, _) => ("online", None),
(LoginState::NeedsLogin, None) => ("needs_login_idle", None),
(LoginState::NeedsLogin, Some(s)) => (