monitor-app 2.1.0__tar.gz → 2.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. {monitor_app-2.1.0 → monitor_app-2.3.1}/PKG-INFO +55 -7
  2. {monitor_app-2.1.0 → monitor_app-2.3.1}/README.md +50 -3
  3. monitor_app-2.3.1/monitor_app/api/deps.py +140 -0
  4. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/api/errors.py +7 -1
  5. monitor_app-2.3.1/monitor_app/api/routers/auth.py +51 -0
  6. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/api/routers/crud.py +7 -5
  7. monitor_app-2.3.1/monitor_app/api/routers/pages.py +282 -0
  8. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/api/routers/views.py +77 -24
  9. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/api/schemas.py +1 -0
  10. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/cli.py +52 -2
  11. monitor_app-2.3.1/monitor_app/db/ingest_state.py +49 -0
  12. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/db/registry.py +13 -1
  13. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/db/repository.py +10 -2
  14. monitor_app-2.3.1/monitor_app/integrations/__init__.py +53 -0
  15. monitor_app-2.3.1/monitor_app/integrations/netdiag.py +166 -0
  16. monitor_app-2.3.1/monitor_app/integrations/network_checker.py +192 -0
  17. monitor_app-2.3.1/monitor_app/integrations/procdiag.py +281 -0
  18. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/main.py +58 -6
  19. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/scaffold/config.py +40 -3
  20. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/scaffold/env.example +0 -1
  21. monitor_app-2.3.1/monitor_app/services/alert_scheduler.py +73 -0
  22. monitor_app-2.3.1/monitor_app/services/alert_service.py +203 -0
  23. monitor_app-2.3.1/monitor_app/services/connectors.py +439 -0
  24. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/services/crud_service.py +38 -8
  25. monitor_app-2.3.1/monitor_app/services/importer.py +182 -0
  26. monitor_app-2.3.1/monitor_app/services/ingest_watcher.py +138 -0
  27. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/services/kpi_service.py +22 -7
  28. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/services/notifiers/__init__.py +0 -3
  29. monitor_app-2.3.1/monitor_app/services/view_cache.py +39 -0
  30. monitor_app-2.3.1/monitor_app/services/view_service.py +107 -0
  31. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/settings/declarative.py +105 -0
  32. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/settings/runtime.py +8 -1
  33. monitor_app-2.3.1/monitor_app/web/static/css/app.css +571 -0
  34. monitor_app-2.3.1/monitor_app/web/static/css/dashboard.css +147 -0
  35. monitor_app-2.3.1/monitor_app/web/static/css/kiosk.css +189 -0
  36. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/web/static/js/alerts-ui.js +23 -30
  37. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/web/static/js/app.js +50 -63
  38. monitor_app-2.3.1/monitor_app/web/static/js/chart-view.js +195 -0
  39. monitor_app-2.3.1/monitor_app/web/static/js/dashboard.js +193 -0
  40. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/web/static/js/form.js +7 -0
  41. monitor_app-2.3.1/monitor_app/web/static/js/kiosk.js +220 -0
  42. monitor_app-2.3.1/monitor_app/web/static/js/kpis.js +104 -0
  43. monitor_app-2.3.1/monitor_app/web/static/js/table-render.js +87 -0
  44. monitor_app-2.3.1/monitor_app/web/static/js/theme.js +73 -0
  45. monitor_app-2.3.1/monitor_app/web/templates/base.html +110 -0
  46. monitor_app-2.3.1/monitor_app/web/templates/index.html +116 -0
  47. monitor_app-2.3.1/monitor_app/web/templates/kiosk.html +75 -0
  48. monitor_app-2.3.1/monitor_app/web/templates/login.html +49 -0
  49. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/web/templates/table.html +26 -6
  50. {monitor_app-2.1.0 → monitor_app-2.3.1}/pyproject.toml +35 -6
  51. monitor_app-2.1.0/monitor_app/api/deps.py +0 -88
  52. monitor_app-2.1.0/monitor_app/api/routers/pages.py +0 -111
  53. monitor_app-2.1.0/monitor_app/services/alert_service.py +0 -130
  54. monitor_app-2.1.0/monitor_app/services/importer.py +0 -114
  55. monitor_app-2.1.0/monitor_app/services/ingest_watcher.py +0 -74
  56. monitor_app-2.1.0/monitor_app/services/notifiers/line.py +0 -22
  57. monitor_app-2.1.0/monitor_app/services/view_service.py +0 -54
  58. monitor_app-2.1.0/monitor_app/web/static/css/app.css +0 -278
  59. monitor_app-2.1.0/monitor_app/web/static/css/kiosk.css +0 -100
  60. monitor_app-2.1.0/monitor_app/web/static/js/chart-view.js +0 -95
  61. monitor_app-2.1.0/monitor_app/web/static/js/kiosk.js +0 -131
  62. monitor_app-2.1.0/monitor_app/web/static/js/kpis.js +0 -57
  63. monitor_app-2.1.0/monitor_app/web/templates/base.html +0 -44
  64. monitor_app-2.1.0/monitor_app/web/templates/index.html +0 -55
  65. monitor_app-2.1.0/monitor_app/web/templates/kiosk.html +0 -53
  66. {monitor_app-2.1.0 → monitor_app-2.3.1}/LICENSE +0 -0
  67. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/__init__.py +0 -0
  68. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/api/__init__.py +0 -0
  69. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/api/routers/__init__.py +0 -0
  70. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/api/routers/alerts.py +0 -0
  71. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/api/routers/audit.py +0 -0
  72. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/api/routers/ingest.py +1 -1
  73. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/api/routers/meta.py +1 -1
  74. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/db/__init__.py +0 -0
  75. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/db/engine.py +0 -0
  76. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/exceptions.py +0 -0
  77. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/logging_conf.py +0 -0
  78. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/scaffold/README.md +0 -0
  79. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/scaffold/csv/orders.csv +0 -0
  80. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/scaffold/csv/products.csv +0 -0
  81. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/scaffold/csv/users.csv +0 -0
  82. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/services/__init__.py +0 -0
  83. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/services/audit_service.py +0 -0
  84. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/services/coercion.py +0 -0
  85. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/services/notifiers/base.py +0 -0
  86. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/services/notifiers/email.py +0 -0
  87. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/services/notifiers/webhook.py +0 -0
  88. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/settings/__init__.py +0 -0
  89. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/settings/loader.py +0 -0
  90. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/web/static/css/bootstrap.min.css +0 -0
  91. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/web/static/img/background.webp +0 -0
  92. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/web/static/img/favicon.ico +0 -0
  93. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/web/static/img/logo.webp +0 -0
  94. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/web/static/js/chart.umd.min.js +0 -0
  95. {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/web/templates/form.html +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: monitor-app
3
- Version: 2.1.0
3
+ Version: 2.3.1
4
4
  Summary: Drop in a CSV, write one config file, and get a live-updating Web UI + REST API. FastAPI-based data monitoring for shop floors and small teams.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -25,16 +25,17 @@ Classifier: Programming Language :: Python :: 3.12
25
25
  Classifier: Programming Language :: Python :: 3.13
26
26
  Provides-Extra: xlsx
27
27
  Requires-Dist: click (>=8.1,<8.2)
28
- Requires-Dist: fastapi (>=0.115,<1.0)
28
+ Requires-Dist: fastapi (>=0.133,<1.0)
29
29
  Requires-Dist: jinja2 (>=3.1,<4.0)
30
30
  Requires-Dist: openpyxl (>=3.1,<4.0) ; extra == "xlsx"
31
- Requires-Dist: pg8000 (>=1.30,<2.0)
31
+ Requires-Dist: pg8000 (>=1.31.5,<2.0)
32
32
  Requires-Dist: pydantic (>=2.7,<3.0)
33
33
  Requires-Dist: pydantic-settings (>=2.3,<3.0)
34
34
  Requires-Dist: pymysql (>=1.1,<2.0)
35
- Requires-Dist: python-multipart (>=0.0.9)
35
+ Requires-Dist: python-multipart (>=0.0.31)
36
36
  Requires-Dist: sqlalchemy (>=2.0,<3.0)
37
37
  Requires-Dist: sse-starlette (>=2.1,<3.0)
38
+ Requires-Dist: starlette (>=1.3.1,<2.0)
38
39
  Requires-Dist: typer (>=0.12,<1.0)
39
40
  Requires-Dist: uvicorn[standard] (>=0.30,<1.0)
40
41
  Project-URL: Changelog, https://github.com/mikawa-bushi/monitor-app/blob/main/CHANGELOG.md
@@ -72,9 +73,12 @@ Monitor App is a FastAPI-based data monitoring tool designed for shop floors, la
72
73
  | Feature | What it does | How to enable |
73
74
  |---|---|---|
74
75
  | Auto data ingest | Watches `csv/` for changes; `POST /api/ingest/{table}` for sensors/PLC/MES | `MONITOR_INGEST_WATCH` / always on |
75
- | Threshold alerts | On-screen banner + beep, Webhook (Slack/Teams), LINE, e-mail; edge-triggered | `alerts=[AlertRule(...)]` |
76
+ | Threshold alerts | On-screen banner + browser notification, Webhook (Slack/Teams), e-mail; edge-triggered | `alerts=[AlertRule(...)]` |
76
77
  | Andon wallboard | Full-screen rotating views with a green/amber/red status light at `/kiosk` | `kiosk=KioskConfig(...)` |
77
78
  | Trend / SPC charts | Line & bar charts with UCL/LCL/target lines, out-of-control points highlighted | `ViewDef(chart=ChartDef(...))` |
79
+ | Dashboard | Home page shows mini-chart grid (columns=1–4) for chart-enabled views; non-chart views listed below | `dashboard=DashboardConfig(columns=2)` |
80
+ | Group navigation | Sidebar and view list support named groups; `ViewDef(group="...")` | `ViewDef(group=...)` |
81
+ | Dark theme | Toggle between light and dark; persisted in `localStorage["monitor-theme"]`; `<html data-theme="dark">` | theme-toggle button (☀️/🌙) |
78
82
  | KPI cards | Scalar SQL (counts, rates, OEE) as color-coded cards on the home page | `kpis={...}` |
79
83
  | Operator entry forms | Touch-friendly forms generated from your table schema at `/form/{table}` | `TableDef(form=FormDef(...))` |
80
84
  | Audit log | Who changed what and when, for every write | `MONITOR_AUDIT_ENABLED` |
@@ -172,7 +176,7 @@ MONITOR_WEBHOOK_URL=https://hooks.slack.com/services/...
172
176
 
173
177
  | Path | Description |
174
178
  |---|---|
175
- | `/` | Home: KPI cards + view list |
179
+ | `/` | Home: dashboard with mini-charts, KPI cards, and view list |
176
180
  | `/table/{view}` | Live table / chart page |
177
181
  | `/kiosk` | Andon wallboard (full screen, auto-rotating) |
178
182
  | `/form/{table}` | Operator entry form |
@@ -182,6 +186,24 @@ MONITOR_WEBHOOK_URL=https://hooks.slack.com/services/...
182
186
  | `/api/views/{view}` | View data (+ `/stream` for SSE, `/export` for CSV/Excel) |
183
187
  | `/api/kpis`, `/api/alerts`, `/api/audit`, `/api/schema`, `/api/health` | KPIs, active alerts, audit log, schema, health |
184
188
 
189
+ ## 🔌 External tool integrations (v2.2)
190
+
191
+ Connect procdiag, netdiag, and network-checker to Monitor App with a few lines — no changes to the external tools required (read-only, pull-based).
192
+
193
+ ```python
194
+ from monitor_app.integrations import procdiag, netdiag, network_checker
195
+
196
+ config = procdiag.attach(config, db_path="/opt/procdiag/data/procdiag.db")
197
+ config = netdiag.attach(config)
198
+ config = network_checker.attach(config, hosts=["plc-01", "gateway"])
199
+ ```
200
+
201
+ Each `attach()` call merges tables, views, KPIs, and alert rules into your existing config. Run `monitor-app sync-sources` to ingest on demand, or set `MONITOR_INGEST_WATCH=1` for continuous polling.
202
+
203
+ Full reference: [docs/integrations_v2_2.md](https://github.com/mikawa-bushi/monitor-app/blob/main/docs/integrations_v2_2.md)
204
+
205
+ ---
206
+
185
207
  ## 🔄 Migrating from v0.x
186
208
 
187
209
  v2 was rebuilt on FastAPI + Pydantic. The legacy dict-style `config.py`
@@ -193,7 +215,9 @@ compatibility shim, but new projects should use `MonitorConfig`. Key changes:
193
215
  - API paths: `/api/<table>` → `/api/tables/{table}`, `/api/view/<v>` → `/api/views/{view}`
194
216
 
195
217
  Details: [redesign notes](https://github.com/mikawa-bushi/monitor-app/blob/main/docs/redesign_v2.md) ·
196
- [v2.1 feature design](https://github.com/mikawa-bushi/monitor-app/blob/main/docs/feature_expansion_v2.1.md)
218
+ [v2.1 feature design](https://github.com/mikawa-bushi/monitor-app/blob/main/docs/feature_expansion_v2.1.md) ·
219
+ [v2.2 integrations](https://github.com/mikawa-bushi/monitor-app/blob/main/docs/integrations_v2_2.md) ·
220
+ [v2.3 UI redesign](https://github.com/mikawa-bushi/monitor-app/blob/main/docs/ui_redesign_v2_3.md)
197
221
 
198
222
  ## 🐳 Docker
199
223
 
@@ -211,6 +235,30 @@ poetry install
211
235
  poetry run pytest
212
236
  ```
213
237
 
238
+ ### Pre-push security & quality checks
239
+
240
+ A [pre-commit](https://pre-commit.com/) config scans for **secrets** and enforces
241
+ formatting locally, before code leaves your machine. Enable it once per clone:
242
+
243
+ ```sh
244
+ poetry run pre-commit install # installs the commit + pre-push hooks
245
+ poetry run pre-commit run --all-files # optional: run across the repo now
246
+ ```
247
+
248
+ - **gitleaks** — blocks hard-coded secrets (PyPI/AWS/GCP tokens, GitHub PATs, private keys)
249
+ - **detect-private-key / check-added-large-files / check-merge-conflict**, plus **black / ruff** (pinned to the CI versions)
250
+ - **check-commit-identity** (pre-push) — rejects commits whose author/committer e-mail is off the allowlist, keeping work/other identities out of history (`scripts/check_commit_identity.py`; extend via the `MONITOR_ALLOWED_EMAILS` env var)
251
+ - **check-ai-traces** (commit + pre-push) — flags traces of AI-agent tools (config dirs, file content, commit metadata) so they don't leak into the repo. Tool patterns are data-driven in `scripts/check_ai_traces.py` (extend the `TOOLS` list or `MONITOR_AI_TRACE_EXTRA`; ignore paths via `.ai-trace-ignore`)
252
+
253
+ Audit the **whole working tree and full git history** on demand:
254
+
255
+ ```sh
256
+ python3 scripts/check_ai_traces.py --all # exit 0 = no traces found
257
+ ```
258
+
259
+ > ⚠️ Local hooks can be skipped with `git commit/push --no-verify`. For an
260
+ > unbypassable gate, also make `gitleaks` a required GitHub Actions check.
261
+
214
262
  Contributions are welcome — see [CONTRIBUTING.md](https://github.com/mikawa-bushi/monitor-app/blob/main/CONTRIBUTING.md).
215
263
 
216
264
  ## 📜 License
@@ -27,9 +27,12 @@ Monitor App is a FastAPI-based data monitoring tool designed for shop floors, la
27
27
  | Feature | What it does | How to enable |
28
28
  |---|---|---|
29
29
  | Auto data ingest | Watches `csv/` for changes; `POST /api/ingest/{table}` for sensors/PLC/MES | `MONITOR_INGEST_WATCH` / always on |
30
- | Threshold alerts | On-screen banner + beep, Webhook (Slack/Teams), LINE, e-mail; edge-triggered | `alerts=[AlertRule(...)]` |
30
+ | Threshold alerts | On-screen banner + browser notification, Webhook (Slack/Teams), e-mail; edge-triggered | `alerts=[AlertRule(...)]` |
31
31
  | Andon wallboard | Full-screen rotating views with a green/amber/red status light at `/kiosk` | `kiosk=KioskConfig(...)` |
32
32
  | Trend / SPC charts | Line & bar charts with UCL/LCL/target lines, out-of-control points highlighted | `ViewDef(chart=ChartDef(...))` |
33
+ | Dashboard | Home page shows mini-chart grid (columns=1–4) for chart-enabled views; non-chart views listed below | `dashboard=DashboardConfig(columns=2)` |
34
+ | Group navigation | Sidebar and view list support named groups; `ViewDef(group="...")` | `ViewDef(group=...)` |
35
+ | Dark theme | Toggle between light and dark; persisted in `localStorage["monitor-theme"]`; `<html data-theme="dark">` | theme-toggle button (☀️/🌙) |
33
36
  | KPI cards | Scalar SQL (counts, rates, OEE) as color-coded cards on the home page | `kpis={...}` |
34
37
  | Operator entry forms | Touch-friendly forms generated from your table schema at `/form/{table}` | `TableDef(form=FormDef(...))` |
35
38
  | Audit log | Who changed what and when, for every write | `MONITOR_AUDIT_ENABLED` |
@@ -127,7 +130,7 @@ MONITOR_WEBHOOK_URL=https://hooks.slack.com/services/...
127
130
 
128
131
  | Path | Description |
129
132
  |---|---|
130
- | `/` | Home: KPI cards + view list |
133
+ | `/` | Home: dashboard with mini-charts, KPI cards, and view list |
131
134
  | `/table/{view}` | Live table / chart page |
132
135
  | `/kiosk` | Andon wallboard (full screen, auto-rotating) |
133
136
  | `/form/{table}` | Operator entry form |
@@ -137,6 +140,24 @@ MONITOR_WEBHOOK_URL=https://hooks.slack.com/services/...
137
140
  | `/api/views/{view}` | View data (+ `/stream` for SSE, `/export` for CSV/Excel) |
138
141
  | `/api/kpis`, `/api/alerts`, `/api/audit`, `/api/schema`, `/api/health` | KPIs, active alerts, audit log, schema, health |
139
142
 
143
+ ## 🔌 External tool integrations (v2.2)
144
+
145
+ Connect procdiag, netdiag, and network-checker to Monitor App with a few lines — no changes to the external tools required (read-only, pull-based).
146
+
147
+ ```python
148
+ from monitor_app.integrations import procdiag, netdiag, network_checker
149
+
150
+ config = procdiag.attach(config, db_path="/opt/procdiag/data/procdiag.db")
151
+ config = netdiag.attach(config)
152
+ config = network_checker.attach(config, hosts=["plc-01", "gateway"])
153
+ ```
154
+
155
+ Each `attach()` call merges tables, views, KPIs, and alert rules into your existing config. Run `monitor-app sync-sources` to ingest on demand, or set `MONITOR_INGEST_WATCH=1` for continuous polling.
156
+
157
+ Full reference: [docs/integrations_v2_2.md](https://github.com/mikawa-bushi/monitor-app/blob/main/docs/integrations_v2_2.md)
158
+
159
+ ---
160
+
140
161
  ## 🔄 Migrating from v0.x
141
162
 
142
163
  v2 was rebuilt on FastAPI + Pydantic. The legacy dict-style `config.py`
@@ -148,7 +169,9 @@ compatibility shim, but new projects should use `MonitorConfig`. Key changes:
148
169
  - API paths: `/api/<table>` → `/api/tables/{table}`, `/api/view/<v>` → `/api/views/{view}`
149
170
 
150
171
  Details: [redesign notes](https://github.com/mikawa-bushi/monitor-app/blob/main/docs/redesign_v2.md) ·
151
- [v2.1 feature design](https://github.com/mikawa-bushi/monitor-app/blob/main/docs/feature_expansion_v2.1.md)
172
+ [v2.1 feature design](https://github.com/mikawa-bushi/monitor-app/blob/main/docs/feature_expansion_v2.1.md) ·
173
+ [v2.2 integrations](https://github.com/mikawa-bushi/monitor-app/blob/main/docs/integrations_v2_2.md) ·
174
+ [v2.3 UI redesign](https://github.com/mikawa-bushi/monitor-app/blob/main/docs/ui_redesign_v2_3.md)
152
175
 
153
176
  ## 🐳 Docker
154
177
 
@@ -166,6 +189,30 @@ poetry install
166
189
  poetry run pytest
167
190
  ```
168
191
 
192
+ ### Pre-push security & quality checks
193
+
194
+ A [pre-commit](https://pre-commit.com/) config scans for **secrets** and enforces
195
+ formatting locally, before code leaves your machine. Enable it once per clone:
196
+
197
+ ```sh
198
+ poetry run pre-commit install # installs the commit + pre-push hooks
199
+ poetry run pre-commit run --all-files # optional: run across the repo now
200
+ ```
201
+
202
+ - **gitleaks** — blocks hard-coded secrets (PyPI/AWS/GCP tokens, GitHub PATs, private keys)
203
+ - **detect-private-key / check-added-large-files / check-merge-conflict**, plus **black / ruff** (pinned to the CI versions)
204
+ - **check-commit-identity** (pre-push) — rejects commits whose author/committer e-mail is off the allowlist, keeping work/other identities out of history (`scripts/check_commit_identity.py`; extend via the `MONITOR_ALLOWED_EMAILS` env var)
205
+ - **check-ai-traces** (commit + pre-push) — flags traces of AI-agent tools (config dirs, file content, commit metadata) so they don't leak into the repo. Tool patterns are data-driven in `scripts/check_ai_traces.py` (extend the `TOOLS` list or `MONITOR_AI_TRACE_EXTRA`; ignore paths via `.ai-trace-ignore`)
206
+
207
+ Audit the **whole working tree and full git history** on demand:
208
+
209
+ ```sh
210
+ python3 scripts/check_ai_traces.py --all # exit 0 = no traces found
211
+ ```
212
+
213
+ > ⚠️ Local hooks can be skipped with `git commit/push --no-verify`. For an
214
+ > unbypassable gate, also make `gitleaks` a required GitHub Actions check.
215
+
169
216
  Contributions are welcome — see [CONTRIBUTING.md](https://github.com/mikawa-bushi/monitor-app/blob/main/CONTRIBUTING.md).
170
217
 
171
218
  ## 📜 License
@@ -0,0 +1,140 @@
1
+ """FastAPI 依存。アプリ状態(app.state)へのアクセスと API キー / セッション検証。
2
+
3
+ 認証(#11): 書き込み系は ``api_key`` 設定時に必須、読み取り系は ``protect_reads``
4
+ 有効時に必須。クライアントは **X-API-Key ヘッダ** か **セッション Cookie** の
5
+ どちらかで認証できる。Cookie は同梱 Web UI がブラウザから認証するための経路で、
6
+ ``/api/login`` がキー検証後に発行する(値はキーの HMAC で、生キーは保存しない)。
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ import hmac
13
+ import secrets
14
+
15
+ from fastapi import Depends, HTTPException, Request, Security
16
+ from fastapi.security import APIKeyHeader
17
+
18
+ from ..services.crud_service import CrudService
19
+ from ..services.view_service import ViewService
20
+ from ..settings.declarative import MonitorConfig
21
+ from ..settings.runtime import AppSettings
22
+
23
+ _api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
24
+
25
+ #: セッション Cookie 名(#11)。
26
+ SESSION_COOKIE = "monitor_session"
27
+
28
+
29
+ def session_token(api_key: str) -> str:
30
+ """API キーから導出する署名付きセッショントークン。
31
+
32
+ 生キーを Cookie に置かないための HMAC。ステートレスなので再起動・多ワーカーでも
33
+ 一貫して検証できる。"""
34
+ return hmac.new(
35
+ api_key.encode("utf-8"), b"monitor-session", hashlib.sha256
36
+ ).hexdigest()
37
+
38
+
39
+ def get_settings(request: Request) -> AppSettings:
40
+ return request.app.state.settings
41
+
42
+
43
+ def get_config(request: Request) -> MonitorConfig:
44
+ return request.app.state.config
45
+
46
+
47
+ def get_crud_service(request: Request) -> CrudService:
48
+ return request.app.state.crud_service
49
+
50
+
51
+ def get_view_service(request: Request) -> ViewService:
52
+ return request.app.state.view_service
53
+
54
+
55
+ def get_alert_engine(request: Request):
56
+ """AlertEngine を返す(フェーズ1・A)。"""
57
+ return request.app.state.alert_engine
58
+
59
+
60
+ def get_kpi_service(request: Request):
61
+ """KpiService を返す(フェーズ2・E)。"""
62
+ return request.app.state.kpi_service
63
+
64
+
65
+ def get_audit_service(request: Request):
66
+ """AuditService を返す(フェーズ4・G)。"""
67
+ return request.app.state.audit_service
68
+
69
+
70
+ def _is_authed(settings: AppSettings, request: Request, provided: str | None) -> bool:
71
+ """ヘッダ or セッション Cookie で認証済みか。api_key 未設定なら常に True。"""
72
+ if settings.api_key is None:
73
+ return True
74
+ if provided and secrets.compare_digest(provided, settings.api_key):
75
+ return True
76
+ cookie = request.cookies.get(SESSION_COOKIE)
77
+ if cookie and secrets.compare_digest(cookie, session_token(settings.api_key)):
78
+ return True
79
+ return False
80
+
81
+
82
+ def get_actor(
83
+ request: Request,
84
+ provided: str | None = Security(_api_key_header),
85
+ ) -> str:
86
+ """監査用の実行主体を表す文字列(フェーズ4・G)。
87
+
88
+ ヘッダ認証なら "api"、Cookie セッションなら "ui"、いずれも無ければ "anon"。
89
+ クライアント IP を添える。
90
+ """
91
+ client = request.client.host if request.client else "unknown"
92
+ if provided:
93
+ who = "api"
94
+ elif request.cookies.get(SESSION_COOKIE):
95
+ who = "ui"
96
+ else:
97
+ who = "anon"
98
+ return f"{who}@{client}"
99
+
100
+
101
+ def _check_auth(settings: AppSettings, request: Request, provided: str | None) -> None:
102
+ if not _is_authed(settings, request, provided):
103
+ raise HTTPException(
104
+ status_code=401,
105
+ detail={"code": "UNAUTHORIZED", "message": "API キーが無効です"},
106
+ )
107
+
108
+
109
+ def require_write_auth(
110
+ request: Request,
111
+ settings: AppSettings = Depends(get_settings),
112
+ provided: str | None = Security(_api_key_header),
113
+ ) -> None:
114
+ """書き込み系(POST/PUT/DELETE)で必須。api_key 未設定なら素通り。"""
115
+ _check_auth(settings, request, provided)
116
+
117
+
118
+ def require_read_auth(
119
+ request: Request,
120
+ settings: AppSettings = Depends(get_settings),
121
+ provided: str | None = Security(_api_key_header),
122
+ ) -> None:
123
+ """読み取り系。protect_reads=True かつ api_key 設定時のみ検証。"""
124
+ if settings.protect_reads:
125
+ _check_auth(settings, request, provided)
126
+
127
+
128
+ class RedirectToLogin(Exception):
129
+ """HTML ページが未認証のとき送出。/login へのリダイレクトに変換される(#11)。"""
130
+
131
+
132
+ def page_auth_guard(
133
+ request: Request,
134
+ settings: AppSettings = Depends(get_settings),
135
+ provided: str | None = Security(_api_key_header),
136
+ ) -> None:
137
+ """HTML ページ用ガード。protect_reads+api_key 有効かつ未認証なら /login へ。"""
138
+ if settings.protect_reads and settings.api_key:
139
+ if not _is_authed(settings, request, provided):
140
+ raise RedirectToLogin()
@@ -6,9 +6,10 @@ import logging
6
6
  import uuid
7
7
 
8
8
  from fastapi import FastAPI, Request
9
- from fastapi.responses import JSONResponse
9
+ from fastapi.responses import JSONResponse, RedirectResponse
10
10
 
11
11
  from ..exceptions import MonitorAppError
12
+ from .deps import RedirectToLogin
12
13
 
13
14
  logger = logging.getLogger("monitor_app.api")
14
15
 
@@ -31,3 +32,8 @@ def register_exception_handlers(app: FastAPI) -> None:
31
32
  return JSONResponse(
32
33
  status_code=exc.status_code, content=_body(exc.code, exc.message)
33
34
  )
35
+
36
+ @app.exception_handler(RedirectToLogin)
37
+ async def _handle_login_redirect(_request: Request, _exc: RedirectToLogin):
38
+ # 未認証の HTML ページアクセスはログインページへ誘導する(#11)。
39
+ return RedirectResponse(url="/login", status_code=303)
@@ -0,0 +1,51 @@
1
+ """UI 向けセッション認証エンドポイント(#11)。
2
+
3
+ ``protect_reads``/``api_key`` 有効時に、ブラウザからキーを渡して同梱 UI を使える
4
+ ようにする。検証成功で HttpOnly な署名付き Cookie を発行し、以後の同一オリジン
5
+ fetch が自動的に認証される。
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import secrets
11
+
12
+ from fastapi import APIRouter, Depends, Form, Request
13
+ from fastapi.responses import RedirectResponse
14
+
15
+ from ...settings.runtime import AppSettings
16
+ from ..deps import SESSION_COOKIE, get_settings, session_token
17
+
18
+ router = APIRouter(prefix="/api", tags=["Auth"])
19
+
20
+
21
+ @router.post("/login")
22
+ def login(
23
+ request: Request,
24
+ key: str = Form(...),
25
+ settings: AppSettings = Depends(get_settings),
26
+ ):
27
+ """キーを検証し、成功なら HttpOnly セッション Cookie を発行して / へ。"""
28
+ if settings.api_key is not None and not secrets.compare_digest(
29
+ key, settings.api_key
30
+ ):
31
+ return RedirectResponse(url="/login?error=1", status_code=303)
32
+
33
+ resp = RedirectResponse(url="/", status_code=303)
34
+ if settings.api_key is not None:
35
+ resp.set_cookie(
36
+ SESSION_COOKIE,
37
+ session_token(settings.api_key),
38
+ httponly=True,
39
+ samesite="lax",
40
+ secure=request.url.scheme == "https",
41
+ max_age=60 * 60 * 12, # 12 時間
42
+ )
43
+ return resp
44
+
45
+
46
+ @router.post("/logout")
47
+ def logout():
48
+ """セッション Cookie を失効させ、ログインページへ。"""
49
+ resp = RedirectResponse(url="/login", status_code=303)
50
+ resp.delete_cookie(SESSION_COOKIE)
51
+ return resp
@@ -2,10 +2,11 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import Any, Dict
5
+ from typing import Any, Dict, Optional
6
6
 
7
- from fastapi import APIRouter, Body, Depends
7
+ from fastapi import APIRouter, Body, Depends, Query
8
8
 
9
+ from ...services.crud_service import CrudService
9
10
  from ..deps import (
10
11
  get_actor,
11
12
  get_crud_service,
@@ -13,7 +14,6 @@ from ..deps import (
13
14
  require_write_auth,
14
15
  )
15
16
  from ..schemas import MessageResponse, RecordResponse, TableDataResponse
16
- from ...services.crud_service import CrudService
17
17
 
18
18
  router = APIRouter(prefix="/api/tables", tags=["CRUD"])
19
19
 
@@ -21,11 +21,13 @@ router = APIRouter(prefix="/api/tables", tags=["CRUD"])
21
21
  @router.get("/{table_name}", response_model=TableDataResponse)
22
22
  def list_records(
23
23
  table_name: str,
24
+ limit: Optional[int] = Query(None, ge=1, description="返す最大行数(#17)"),
25
+ offset: int = Query(0, ge=0, description="先頭からのスキップ行数(#17)"),
24
26
  service: CrudService = Depends(get_crud_service),
25
27
  _: None = Depends(require_read_auth),
26
28
  ):
27
- """テーブルの全レコードを取得する。"""
28
- rows = service.list_records(table_name)
29
+ """テーブルのレコードを取得する。``limit``/``offset`` 省略時は全件。"""
30
+ rows = service.list_records(table_name, limit=limit, offset=offset)
29
31
  columns = service.config.tables[table_name].column_names
30
32
  return TableDataResponse(
31
33
  table_name=table_name, columns=columns, data=rows, count=len(rows)