Files
mc_domain_proxy/api/routes/logs.py
claude-bot 7362b45846 feat(logs): date filter + clear log endpoints
- API GET /api/logs now accepts from_ts / to_ts (unix epoch, half-open
  [from, to)) so callers can scope by arbitrary time range.
- API DELETE /api/logs added. Same from_ts / to_ts semantics. No params
  = wipe everything and reset the AUTOINCREMENT counter.
- Dashboard Logs page: date picker that scopes both the view and the
  delete button to the selected day in the user's local timezone. The
  clear button is red and confirms before deleting; label switches
  between "전체 로그 초기화" and "<날짜> 하루치 삭제".
2026-05-23 17:55:58 +09:00

86 lines
2.8 KiB
Python

"""접속 로그 조회 및 초기화."""
from __future__ import annotations
import sqlite3
from fastapi import APIRouter, Query
from config_io import LOG_DB
router = APIRouter()
@router.get("/logs")
def list_logs(
limit: int = Query(50, ge=1, le=500),
offset: int = Query(0, ge=0),
action: str | None = Query(None),
from_ts: float | None = Query(None, description="unix epoch seconds, inclusive"),
to_ts: float | None = Query(None, description="unix epoch seconds, exclusive"),
) -> dict:
if not LOG_DB.exists():
return {"total": 0, "items": []}
con = sqlite3.connect(LOG_DB)
try:
con.row_factory = sqlite3.Row
conds: list[str] = []
params: list = []
if action:
conds.append("action = ?")
params.append(action)
if from_ts is not None:
conds.append("ts >= ?")
params.append(from_ts)
if to_ts is not None:
conds.append("ts < ?")
params.append(to_ts)
where = ("WHERE " + " AND ".join(conds)) if conds else ""
total = con.execute(
f"SELECT COUNT(*) FROM connections {where}", params
).fetchone()[0]
rows = con.execute(
f"SELECT id, ts, client_ip, domain, next_state, action, reason "
f"FROM connections {where} ORDER BY id DESC LIMIT ? OFFSET ?",
[*params, limit, offset],
).fetchall()
finally:
con.close()
return {"total": total, "items": [dict(r) for r in rows]}
@router.delete("/logs")
def clear_logs(
from_ts: float | None = Query(None, description="ts >= from_ts 만 삭제"),
to_ts: float | None = Query(None, description="ts < to_ts 만 삭제"),
) -> dict:
"""접속 로그를 삭제한다.
- from_ts/to_ts 둘 다 없으면 전체 삭제 (AUTOINCREMENT 카운터까지 리셋)
- 둘 중 하나만 있으면 그 조건만 적용
- 둘 다 있으면 [from_ts, to_ts) 범위 삭제 — 날짜 선택 삭제 용도
"""
if not LOG_DB.exists():
return {"deleted": 0}
con = sqlite3.connect(LOG_DB, timeout=5)
try:
conds: list[str] = []
params: list = []
if from_ts is not None:
conds.append("ts >= ?")
params.append(from_ts)
if to_ts is not None:
conds.append("ts < ?")
params.append(to_ts)
if conds:
where = "WHERE " + " AND ".join(conds)
cur = con.execute(f"DELETE FROM connections {where}", params)
deleted = cur.rowcount
else:
cur = con.execute("DELETE FROM connections")
deleted = cur.rowcount
con.execute("DELETE FROM sqlite_sequence WHERE name='connections'")
con.commit()
finally:
con.close()
return {"deleted": deleted}