Multi-tenant Claude Code monitoring dashboard. FastAPI + PostgreSQL + Docker + SSE real-time updates. Montserrat font, black/#FFC407 color scheme. Apache reverse proxy config at /cc-dashboard/. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
194 lines
4.8 KiB
Python
194 lines
4.8 KiB
Python
from datetime import date, datetime
|
|
from typing import Any
|
|
|
|
from pydantic import BaseModel, EmailStr, Field
|
|
|
|
|
|
# ── Auth ──────────────────────────────────────────────────────────────────────
|
|
|
|
class LoginRequest(BaseModel):
|
|
email: EmailStr
|
|
password: str
|
|
|
|
|
|
class TokenResponse(BaseModel):
|
|
access_token: str
|
|
refresh_token: str
|
|
token_type: str = "bearer"
|
|
|
|
|
|
class RefreshRequest(BaseModel):
|
|
refresh_token: str
|
|
|
|
|
|
class ChangePasswordRequest(BaseModel):
|
|
current_password: str
|
|
new_password: str = Field(min_length=8)
|
|
|
|
|
|
# ── Users ─────────────────────────────────────────────────────────────────────
|
|
|
|
class UserOut(BaseModel):
|
|
id: str
|
|
email: str
|
|
username: str
|
|
role: str
|
|
is_active: bool
|
|
daily_overhead_hours: float
|
|
created_at: datetime
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
class UserCreate(BaseModel):
|
|
email: EmailStr
|
|
username: str = Field(min_length=2, max_length=100)
|
|
password: str = Field(min_length=8)
|
|
role: str = Field(default="user", pattern="^(admin|user)$")
|
|
|
|
|
|
class UserUpdate(BaseModel):
|
|
username: str | None = None
|
|
role: str | None = Field(default=None, pattern="^(admin|user)$")
|
|
is_active: bool | None = None
|
|
daily_overhead_hours: float | None = Field(default=None, ge=0, le=12)
|
|
|
|
|
|
# ── API Keys ──────────────────────────────────────────────────────────────────
|
|
|
|
class ApiKeyOut(BaseModel):
|
|
id: str
|
|
label: str
|
|
key_prefix: str
|
|
is_active: bool
|
|
last_used_at: datetime | None
|
|
created_at: datetime
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
class ApiKeyCreate(BaseModel):
|
|
label: str = Field(default="My Machine", max_length=100)
|
|
|
|
|
|
class ApiKeyCreated(ApiKeyOut):
|
|
raw_key: str # shown once
|
|
|
|
|
|
# ── Ingestion ─────────────────────────────────────────────────────────────────
|
|
|
|
class SessionPayload(BaseModel):
|
|
session_id: str
|
|
project_slug: str
|
|
date: date
|
|
start_at: datetime
|
|
end_at: datetime
|
|
message_count: int = 0
|
|
active_hours: float = 0.0
|
|
work_summary: str = ""
|
|
commits: list[str] = []
|
|
tools_used: dict[str, int] = {}
|
|
files_changed: list[str] = []
|
|
raw_stats: dict[str, Any] = {}
|
|
|
|
|
|
class IngestPayload(BaseModel):
|
|
root_path: str = ""
|
|
sessions: list[SessionPayload]
|
|
|
|
|
|
class IngestResponse(BaseModel):
|
|
accepted: int
|
|
skipped: int
|
|
|
|
|
|
# ── Dashboard ─────────────────────────────────────────────────────────────────
|
|
|
|
class KpiSummary(BaseModel):
|
|
total_hours: float
|
|
total_projects: int
|
|
working_days: int
|
|
total_sessions: int
|
|
avg_hours_per_day: float
|
|
top_project: str
|
|
total_commits: int
|
|
total_files_changed: int
|
|
period_from: date | None
|
|
period_to: date | None
|
|
|
|
|
|
class ProjectHours(BaseModel):
|
|
project_id: str
|
|
display_name: str
|
|
total_hours: float
|
|
session_count: int
|
|
working_days: int
|
|
last_active: date | None
|
|
|
|
|
|
class DailyPoint(BaseModel):
|
|
date: date
|
|
hours: float
|
|
sessions: int
|
|
|
|
|
|
class MonthlyPoint(BaseModel):
|
|
month: str # "2026-03"
|
|
hours: float
|
|
|
|
|
|
class DowPoint(BaseModel):
|
|
dow: int # 0=Mon … 6=Sun
|
|
label: str
|
|
hours: float
|
|
|
|
|
|
class ToolUsage(BaseModel):
|
|
tool: str
|
|
count: int
|
|
|
|
|
|
class SessionOut(BaseModel):
|
|
id: str
|
|
session_id: str
|
|
project_id: str
|
|
project_name: str
|
|
date: date
|
|
start_at: datetime
|
|
end_at: datetime
|
|
active_hours: float
|
|
message_count: int
|
|
work_summary: str
|
|
commits: list[str]
|
|
tools_used: dict[str, int]
|
|
files_changed: list[str]
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
class ProjectDetail(BaseModel):
|
|
project: "ProjectOut"
|
|
daily: list[DailyPoint]
|
|
sessions: list[SessionOut]
|
|
top_files: list[dict]
|
|
top_tools: list[ToolUsage]
|
|
|
|
|
|
class ProjectOut(BaseModel):
|
|
id: str
|
|
slug: str
|
|
display_name: str
|
|
root_path: str
|
|
created_at: datetime
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
# ── Admin ─────────────────────────────────────────────────────────────────────
|
|
|
|
class AdminStats(BaseModel):
|
|
total_users: int
|
|
active_users: int
|
|
total_sessions: int
|
|
total_hours: float
|
|
users: list[UserOut]
|