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

36
frontend/src/api.js Normal file
View File

@@ -0,0 +1,36 @@
const base = '/api'
async function req(path, opts = {}) {
const res = await fetch(base + path, {
headers: { 'Content-Type': 'application/json' },
...opts,
})
if (!res.ok) {
const text = await res.text().catch(() => '')
throw new Error(`${res.status}: ${text || res.statusText}`)
}
if (res.status === 204) return null
return res.json()
}
export const api = {
status: () => req('/status'),
config: () => req('/config'),
putConfig: (cfg) => req('/config', { method: 'PUT', body: JSON.stringify(cfg) }),
domains: () => req('/domains'),
addDomain: (d) => req('/domains', { method: 'POST', body: JSON.stringify(d) }),
deleteDomain: (name) =>
req(`/domains/${encodeURIComponent(name)}`, { method: 'DELETE' }),
patchDomain: (name, body) =>
req(`/domains/${encodeURIComponent(name)}`, {
method: 'PATCH',
body: JSON.stringify(body),
}),
logs: (params = {}) => {
const q = new URLSearchParams(
Object.fromEntries(Object.entries(params).filter(([, v]) => v !== '' && v != null))
).toString()
return req('/logs' + (q ? `?${q}` : ''))
},
restart: () => req('/proxy/restart', { method: 'POST' }),
}