feat: add admin panel

This commit is contained in:
hh committed 2026-05-20 13:00:08 +02:00
1 parent 7970d4be9b
commit 0128191ac3
26 files changed
+2985 -115

No files matched your search

+80
View File
@@ -0,0 +1,80 @@
"""SQLModel tables — see PRD §9.
Three tables, all flat, no relationships modelled yet (Phase 4 talks
about ``actor`` and ``agent_name`` as strings — joining audit→token by
name is fine at this volume; we'll introduce FKs when the admin UI
actually demands them).
Datetimes are stored UTC; we set ``default_factory`` rather than relying
on DB defaults so SQLite + Postgres behave identically. Every row that
needs an id uses ``Optional[int]`` so SQLAlchemy can autoincrement.
"""
from __future__ import annotations
from datetime import UTC, datetime
from sqlmodel import Field, SQLModel
def _utcnow() -> datetime:
return datetime.now(UTC)
class Token(SQLModel, table=True):
"""Bearer token issued to an external caller.
``hashed_value`` holds the Argon2 hash (Phase 4.2 — until then,
rows are written by tests / the admin UI, not by ``TokenStore``).
Plaintext is shown to the user **once** at creation and then
discarded.
"""
id: int | None = Field(default=None, primary_key=True)
name: str = Field(index=True, unique=True)
scope: str = Field(default="*")
hashed_value: str
created_at: datetime = Field(default_factory=_utcnow)
last_used_at: datetime | None = Field(default=None)
revoked_at: datetime | None = Field(default=None)
class Session(SQLModel, table=True):
"""Mirror of one live ``claude-code-api`` session.
The id is the ``session_id`` claude itself assigns on the first
turn; we don't generate it. Rows here are for admin observability
(live count, last activity) — the actual pool lives in
``claude_code_api.ClaudeCodeBackend`` and is the source of truth.
"""
id: str = Field(primary_key=True)
agent_name: str = Field(index=True)
fingerprint: str = Field(index=True)
created_at: datetime = Field(default_factory=_utcnow)
last_active_at: datetime = Field(default_factory=_utcnow)
closed_at: datetime | None = Field(default=None)
class AuditLog(SQLModel, table=True):
"""Append-only record of who-did-what.
``actor`` is ``"token:<name>"`` for inbound traffic or
``"admin:<user>"`` for admin-UI actions. ``kind`` is a short tag
(``"messages"`` / ``"mcp_call"`` / ``"token_create"`` / …);
free-form rather than enum so we can add new kinds without a
schema migration. ``detail_json`` is a JSON-encoded blob — keep
it small (paths, method, status), not full bodies.
"""
__tablename__ = "audit_log"
id: int | None = Field(default=None, primary_key=True)
ts: datetime = Field(default_factory=_utcnow, index=True)
actor: str = Field(index=True)
kind: str = Field(index=True)
agent_name: str | None = Field(default=None, index=True)
detail_json: str = Field(default="{}")
__all__ = ["AuditLog", "Session", "Token"]