mirror of
https://github.com/taylanbakircioglu/haproxy-openmanager.git
synced 2026-09-12 05:48:58 +00:00
ef26860df9
Until now the only record of what happened was `user_activity_logs`, which stores non-GET 2xx operations with no bodies. When something failed you could see that a counter went up, never what was sent or what came back. This adds one queryable timeline covering both directions: - inbound: every API call, including GETs and including 4xx/5xx, with the user, client IP, status, duration and — redacted, size-capped — the request and response bodies. - outbound: every HTTP call the backend makes, tagged with who it went to (ACME/Let's Encrypt, Cloudflare, GoDaddy, HAProxy stats, agents, the ACME diagnostics probe). Outbound rows inherit the inbound request's id, so one operator action and the CA/DNS calls it triggered read as a single trace: opening a failed "Request Certificate" shows the exact POST /acme/new-order and the CA's 429 underneath. Implementation notes: - Capture is a pure-ASGI middleware that TEES the request and response streams rather than draining them. `await request.body()` inside a BaseHTTPMiddleware would consume the receive channel and break the raw-body agent heartbeat handler. Registered last so it is outermost: it then sees the final client-visible response and seeds correlation_id_context before the error handler reads it. - Rows are written by a batching background writer with a bounded queue, so the request path never awaits the database and a saturated logger drops rows visibly (surfaced on the page) instead of blocking. Redaction runs on the writer, off the request coroutine. - Secrets never land: headers are an allowlist with Authorization/Cookie kept only as a presence marker; body keys and value shapes are redacted (passwords, tokens, api_token, API keys, private-key PEMs, JWTs); the ACME JWS request body is never stored, because a stored protected+signature pair is a replayable credential — a summary is logged instead; DNS-provider errors record only the exception type; the ACME HTTP-01 challenge endpoint is excluded so key_authorization is never captured. - Retention is operator-configurable in Settings -> Request Log: separate day counts for successful and failed rows (7 / 30) plus a hard row cap (500k), whichever is reached first. Pruned in batches under a Postgres advisory lock, with the day counts bound as parameters, never interpolated. - New permissions requestlog.read / requestlog.manage. super_admin and security_admin get both, operator gets read, viewer gets neither. Schema: one new table (request_logs) plus its settings seed, SCHEMA_VERSION 10 -> 11, auto-migrated. No existing table altered, no agent or rendered-config change. Kill switches: REQUEST_LOG_ENABLED=false (middleware never registered) or the `enabled` toggle in Settings. Tests: 245 new (7 backend files + 1 frontend), full suite 1655 backend + 17 frontend passing.
74 lines
1.9 KiB
JSON
74 lines
1.9 KiB
JSON
{
|
|
"name": "haproxy-openmanager-frontend",
|
|
"version": "1.11.0",
|
|
"description": "HAProxy Load Balancer Management UI",
|
|
"license": "AGPL-3.0-or-later",
|
|
"dependencies": {
|
|
"react": "^18.2.0",
|
|
"react-dom": "^18.2.0",
|
|
"react-router-dom": "^6.30.4",
|
|
"axios": "^1.16.0",
|
|
"antd": "^5.2.0",
|
|
"@ant-design/icons": "^5.0.0",
|
|
"recharts": "^2.5.0",
|
|
"moment": "^2.29.0",
|
|
"react18-json-view": "^0.2.9",
|
|
"monaco-editor": "^0.36.0",
|
|
"@monaco-editor/react": "^4.6.0",
|
|
"react-ace": "^10.1.0",
|
|
"ace-builds": "^1.23.4",
|
|
"react-window": "^1.8.10",
|
|
"qrcode.react": "^4.0.0"
|
|
},
|
|
"devDependencies": {
|
|
"@types/react": "^18.0.0",
|
|
"@types/react-dom": "^18.0.0",
|
|
"react-scripts": "5.0.1",
|
|
"typescript": "^4.9.0",
|
|
"@testing-library/react": "^13.4.0",
|
|
"@testing-library/jest-dom": "^5.16.4",
|
|
"@testing-library/user-event": "^14.4.3",
|
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.0"
|
|
},
|
|
"overrides": {
|
|
"ws": "7.5.11",
|
|
"webpack-dev-server": { "ws": "8.21.0" },
|
|
"form-data": "4.0.6",
|
|
"jsdom": { "form-data": "3.0.5" },
|
|
"js-yaml": "3.15.0",
|
|
"eslint": { "js-yaml": "4.2.0" },
|
|
"@eslint/eslintrc": { "js-yaml": "4.2.0" },
|
|
"http-proxy-middleware": "2.0.10",
|
|
"launch-editor": "2.14.1",
|
|
"postcss": "8.5.10",
|
|
"resolve-url-loader": "5.0.0",
|
|
"@babel/core": "7.29.6",
|
|
"websocket-driver": "0.7.5"
|
|
},
|
|
"scripts": {
|
|
"start": "react-scripts start",
|
|
"build": "react-scripts build",
|
|
"test": "react-scripts test",
|
|
"eject": "react-scripts eject"
|
|
},
|
|
|
|
"eslintConfig": {
|
|
"extends": [
|
|
"react-app",
|
|
"react-app/jest"
|
|
]
|
|
},
|
|
"browserslist": {
|
|
"production": [
|
|
">0.2%",
|
|
"not dead",
|
|
"not op_mini all"
|
|
],
|
|
"development": [
|
|
"last 1 chrome version",
|
|
"last 1 firefox version",
|
|
"last 1 safari version"
|
|
]
|
|
},
|
|
"proxy": "http://backend:8000"
|
|
} |