feat: implement MC domain filter proxy, API, dashboard

- proxy: asyncio TCP proxy with handshake parser, domain whitelist,
  transparent backend tunneling, SQLite logging, mtime hot reload
- api: FastAPI routes for config/domains/logs/status + restart trigger
- frontend: React + Vite NPM-style dashboard (dashboard/domains/logs/settings)
- nginx: reverse proxy for /api -> api:8000 and / -> frontend:3000
- docker-compose: full stack with shared data volume
- replace spec mc-domain-filter.md with README.md
This commit is contained in:
2026-05-20 16:39:18 +09:00
parent b45e884633
commit d10dae5cb9
33 changed files with 1872 additions and 223 deletions

38
api/routes/logs.py Normal file
View File

@@ -0,0 +1,38 @@
"""접속 로그 조회."""
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),
) -> dict:
if not LOG_DB.exists():
return {"total": 0, "items": []}
con = sqlite3.connect(LOG_DB)
try:
con.row_factory = sqlite3.Row
where, params = "", []
if action:
where = "WHERE action = ?"
params.append(action)
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]}