forked from retoor/devplacepy
Delete four stale SEO and style agent report JSON/MD files from agents/reports/ that had zero findings or were superseded. Export PYTHONDONTWRITEBYTECODE=1 in the Makefile so all make targets suppress bytecode generation, and add a `tree` target that lists the repository file tree via git ls-tree. Update AGENTS.md and CLAUDE.md documentation to reflect that routers now form a directory tree mirroring URL paths and that bytecode writing is disabled project-wide.
139 lines
4.4 KiB
Python
139 lines
4.4 KiB
Python
# retoor <retoor@molodetz.nl>
|
|
|
|
import logging
|
|
from urllib.parse import urlencode
|
|
from fastapi import APIRouter, Request
|
|
from fastapi.responses import HTMLResponse
|
|
from devplacepy.database import get_users_by_uids
|
|
from devplacepy.utils import require_admin, time_ago, not_found, pretty_json
|
|
from devplacepy.seo import base_seo_context, site_url, website_schema
|
|
from devplacepy.responses import respond
|
|
from devplacepy.schemas import AuditLogOut, AuditEventOut
|
|
from devplacepy.services.audit import query as audit_query
|
|
|
|
from devplacepy.routers.admin._shared import parse_metadata
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/audit-log", response_class=HTMLResponse)
|
|
async def admin_audit_log(
|
|
request: Request,
|
|
page: int = 1,
|
|
event_key: str = "",
|
|
category: str = "",
|
|
actor_role: str = "",
|
|
actor_uid: str = "",
|
|
origin: str = "",
|
|
result: str = "",
|
|
q: str = "",
|
|
date_from: str = "",
|
|
date_to: str = "",
|
|
):
|
|
admin = require_admin(request)
|
|
filters = {
|
|
"event_key": event_key,
|
|
"category": category,
|
|
"actor_role": actor_role,
|
|
"actor_uid": actor_uid,
|
|
"origin": origin,
|
|
"result": result,
|
|
"q": q,
|
|
"date_from": date_from,
|
|
"date_to": date_to,
|
|
}
|
|
active = {key: value for key, value in filters.items() if value}
|
|
rows, pagination = audit_query.list_events(filters, page)
|
|
user_uids = [
|
|
row["actor_uid"]
|
|
for row in rows
|
|
if row.get("actor_kind") == "user" and row.get("actor_uid")
|
|
]
|
|
users_by_uid = get_users_by_uids(user_uids)
|
|
entries = []
|
|
for row in rows:
|
|
entry = dict(row)
|
|
entry["actor_user"] = users_by_uid.get(row.get("actor_uid"))
|
|
entry["time_ago"] = time_ago(row["created_at"]) if row.get("created_at") else ""
|
|
entry["metadata"] = parse_metadata(row.get("metadata"))
|
|
entries.append(entry)
|
|
filter_qs = urlencode(active)
|
|
pagination_query = (filter_qs + "&") if filter_qs else ""
|
|
base = site_url(request)
|
|
seo_ctx = base_seo_context(
|
|
request,
|
|
title="Audit Log - Admin",
|
|
description="Platform audit trail of every state-changing action.",
|
|
robots="noindex,nofollow",
|
|
breadcrumbs=[
|
|
{"name": "Home", "url": "/feed"},
|
|
{"name": "Admin", "url": "/admin"},
|
|
{"name": "Audit Log", "url": "/admin/audit-log"},
|
|
],
|
|
schemas=[website_schema(base)],
|
|
)
|
|
return respond(
|
|
request,
|
|
"admin_audit_log.html",
|
|
{
|
|
**seo_ctx,
|
|
"request": request,
|
|
"user": admin,
|
|
"entries": entries,
|
|
"pagination": pagination,
|
|
"pagination_query": pagination_query,
|
|
"filters": active,
|
|
"options": audit_query.filter_options(),
|
|
"admin_section": "audit-log",
|
|
},
|
|
model=AuditLogOut,
|
|
)
|
|
|
|
|
|
@router.get("/audit-log/{uid}", response_class=HTMLResponse)
|
|
async def admin_audit_event(request: Request, uid: str):
|
|
admin = require_admin(request)
|
|
data = audit_query.get_event_with_links(uid)
|
|
if data is None:
|
|
raise not_found("Audit event not found")
|
|
event = dict(data["event"])
|
|
if event.get("actor_kind") == "user" and event.get("actor_uid"):
|
|
event["actor_user"] = get_users_by_uids([event["actor_uid"]]).get(
|
|
event["actor_uid"]
|
|
)
|
|
event["metadata"] = parse_metadata(event.get("metadata"))
|
|
event["metadata_pretty"] = (
|
|
pretty_json(event["metadata"]) if event["metadata"] else ""
|
|
)
|
|
event["time_ago"] = (
|
|
time_ago(event["created_at"]) if event.get("created_at") else ""
|
|
)
|
|
base = site_url(request)
|
|
seo_ctx = base_seo_context(
|
|
request,
|
|
title="Audit Event - Admin",
|
|
description="A single audit event with related objects.",
|
|
robots="noindex,nofollow",
|
|
breadcrumbs=[
|
|
{"name": "Home", "url": "/feed"},
|
|
{"name": "Admin", "url": "/admin"},
|
|
{"name": "Audit Log", "url": "/admin/audit-log"},
|
|
{"name": "Event", "url": f"/admin/audit-log/{uid}"},
|
|
],
|
|
schemas=[website_schema(base)],
|
|
)
|
|
return respond(
|
|
request,
|
|
"admin_audit_event.html",
|
|
{
|
|
**seo_ctx,
|
|
"request": request,
|
|
"user": admin,
|
|
"event": event,
|
|
"links": data["links"],
|
|
"admin_section": "audit-log",
|
|
},
|
|
model=AuditEventOut,
|
|
)
|