status: consolidate the pill to one dot + one word; upgrade the panel

- pill: two host dots -> ONE consolidated state (down > slow > up) —
  the at-a-glance question is 'can I watch?', which is one state;
  per-host detail belongs in the tap panel
- panel: dashed slow-threshold line in the CDN sparkline (drawn at the
  server's real threshold, not a client copy) + origin up/down step
  sparkline so 'how long has the origin been down' is visible
- server: origin probe keeps a 20-sample hist like the CDN probe;
  DEGRADED threshold is now ANIWEB_CDN_KBPS (default 40) and is
  returned in /api/cdn so the UI draws the line at the live value
- tests: pill assertions switched to consolidated states; fixtures
  carry origin hist + threshold; assert both sparklines + threshold line
- docs: README + SPEC synced; preview on :8322 restarted (pid 44382)
This commit is contained in:
jozamudi committed 2026-09-21 00:10:10 +00:00
1 parent 778bf2bfc1
commit 5f7aa010f1
8 files changed
+105 -56

No files matched your search

+8 -3
View File
@@ -48,7 +48,10 @@ VERSION = _version()
_CDN = {"state": "unknown", "kbps": 0.0, "ts": 0.0, "hist": []}
# origin (primary provider pages/API) health — separate host, separate failure
# mode (observed 2026-09-03: CDN fine while origin 503'd "Under Maintenance")
_ORIGIN = {"state": "unknown", "msg": "", "ts": 0.0}
_ORIGIN = {"state": "unknown", "msg": "", "ts": 0.0, "hist": []}
# ponytail: fixed threshold; if the CDN's normal throughput drifts below it
# the pill sits on "slow" — tune via ANIWEB_CDN_KBPS, not a percentile.
_CDN_THRESHOLD = float(os.environ.get("ANIWEB_CDN_KBPS", "40"))
def _cdn_probe():
@@ -81,7 +84,7 @@ def _cdn_probe():
kbps = len(r.content) / 1024 / max(time.time() - t, 0.01)
except Exception:
kbps = 0.0
now = "OK" if kbps >= 40 else "DEGRADED"
now = "OK" if kbps >= _CDN_THRESHOLD else "DEGRADED"
if now != state:
print(f"[probe] CDN {now} ({kbps:.0f} KB/s)", flush=True)
state = now
@@ -103,6 +106,8 @@ def _origin_probe():
state, msg = "DOWN", str(e)
prev = _ORIGIN["state"]
_ORIGIN.update(state=state, msg=msg, ts=time.time())
_ORIGIN["hist"].append({"t": time.time(), "state": state})
del _ORIGIN["hist"][:-20]
if state != prev:
print(f"[probe] ORIGIN {state} ({msg})", flush=True)
@@ -114,7 +119,7 @@ threading.Thread(target=_cdn_probe, daemon=True).start()
async def cdn_status():
"""CDN + origin health (read-only, public). Top-level fields are the CDN
probe (existing shape); .origin is the anidb.app pages/API probe."""
return {**_CDN, "origin": _ORIGIN}
return {**_CDN, "threshold": _CDN_THRESHOLD, "origin": _ORIGIN}
# ---------- auth: multi-user, signed cookies ----------
users.migrate_legacy() # file-based legacy import; db.init() absorbs its output