- 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 "<날짜> 하루치 삭제".
86 lines
2.8 KiB
Python
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}
|