Files
haproxy-openmanager/frontend/package.json
T
mustafa.ulukaya ef26860df9 feat(logging): unified request/response log with configurable retention (v1.11.0)
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.
2026-08-11 02:36:03 +03:00

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"
}