27916bbe61
Tests · Go / test (push) Successful in 2m0s
Add the server-rendered operator console at /_gm, exposed publicly through the gateway behind the existing admin_accounts Basic Auth. Backend: - new internal/adminconsole package (html/template Renderer, stateless HMAC CSRF signer, embedded stylesheet) - /_gm route group reusing basicauth.Middleware(admin.Service) + a CSRF guard (per-operator token + same-origin check); dashboard landing page - BACKEND_ADMIN_CONSOLE_CSRF_KEY config (per-process random fallback) Gateway: - new "admin" public route class (per-IP rate limit, body + GET/HEAD/POST method limits) classifying /_gm traffic - reverse proxy to the backend /_gm surface, preserving Host and relaying the backend 401 Basic Auth challenge; 502 when the backend is unreachable - GATEWAY_PUBLIC_HTTP_ANTI_ABUSE_ADMIN_* config dev-deploy: - Caddy routes /_gm/* to the gateway - bootstrap admin + stable CSRF key; enable Prometheus /metrics exporters on backend and gateway (forward-compat for a future Prometheus/Grafana stack) Docs: ARCHITECTURE 14.1/16, FUNCTIONAL 10.2.1 (+ru mirror), backend and gateway READMEs, new backend/docs/admin-console.md. Tests: renderer + CSRF unit tests; backend router auth/render/asset/CSRF; gateway classifier, proxy forwarding/Host/401/405/413/429/502.
50 lines
1.6 KiB
CSS
50 lines
1.6 KiB
CSS
/* Admin console stylesheet. Deliberately small and dependency-free: the
|
|
console is an internal operator tool, not a public surface. */
|
|
:root {
|
|
--bg: #11151c;
|
|
--panel: #1b2230;
|
|
--panel-hi: #232c3d;
|
|
--ink: #e6ebf2;
|
|
--ink-dim: #9aa7ba;
|
|
--line: #2c3850;
|
|
--accent: #5aa9ff;
|
|
--danger: #ff6b6b;
|
|
--ok: #4ecb8d;
|
|
}
|
|
* { box-sizing: border-box; }
|
|
body {
|
|
margin: 0;
|
|
background: var(--bg);
|
|
color: var(--ink);
|
|
font: 15px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
}
|
|
a { color: var(--accent); text-decoration: none; }
|
|
a:hover { text-decoration: underline; }
|
|
.topbar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1.5rem;
|
|
padding: 0.6rem 1.2rem;
|
|
background: var(--panel);
|
|
border-bottom: 1px solid var(--line);
|
|
}
|
|
.topbar .brand { font-weight: 700; letter-spacing: 0.04em; }
|
|
.topbar .mainnav { display: flex; gap: 1rem; flex: 1; }
|
|
.topbar .mainnav a.active { color: var(--ink); border-bottom: 2px solid var(--accent); }
|
|
.topbar .who { color: var(--ink-dim); }
|
|
.content { padding: 1.5rem; max-width: 1100px; margin: 0 auto; }
|
|
h1 { font-size: 1.4rem; margin: 0 0 0.4rem; }
|
|
.lede { color: var(--ink-dim); margin-top: 0; }
|
|
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem; margin-top: 1.5rem; }
|
|
.card {
|
|
display: block;
|
|
padding: 1rem 1.2rem;
|
|
background: var(--panel);
|
|
border: 1px solid var(--line);
|
|
border-radius: 8px;
|
|
color: var(--ink);
|
|
}
|
|
.card:hover { background: var(--panel-hi); text-decoration: none; }
|
|
.card h2 { font-size: 1.05rem; margin: 0 0 0.3rem; color: var(--accent); }
|
|
.card p { margin: 0; color: var(--ink-dim); font-size: 0.9rem; }
|