feat: add admin panel
This commit is contained in:
1 parent
7970d4be9b
commit
0128191ac3
26 files changed
+2985
-115
No files matched your search
@@ -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"]
|
||||
Reference in new issue
Block a user