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.
- {monitor_app-2.1.0 → monitor_app-2.3.1}/PKG-INFO +55 -7
- {monitor_app-2.1.0 → monitor_app-2.3.1}/README.md +50 -3
- monitor_app-2.3.1/monitor_app/api/deps.py +140 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/api/errors.py +7 -1
- monitor_app-2.3.1/monitor_app/api/routers/auth.py +51 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/api/routers/crud.py +7 -5
- monitor_app-2.3.1/monitor_app/api/routers/pages.py +282 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/api/routers/views.py +77 -24
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/api/schemas.py +1 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/cli.py +52 -2
- monitor_app-2.3.1/monitor_app/db/ingest_state.py +49 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/db/registry.py +13 -1
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/db/repository.py +10 -2
- monitor_app-2.3.1/monitor_app/integrations/__init__.py +53 -0
- monitor_app-2.3.1/monitor_app/integrations/netdiag.py +166 -0
- monitor_app-2.3.1/monitor_app/integrations/network_checker.py +192 -0
- monitor_app-2.3.1/monitor_app/integrations/procdiag.py +281 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/main.py +58 -6
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/scaffold/config.py +40 -3
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/scaffold/env.example +0 -1
- monitor_app-2.3.1/monitor_app/services/alert_scheduler.py +73 -0
- monitor_app-2.3.1/monitor_app/services/alert_service.py +203 -0
- monitor_app-2.3.1/monitor_app/services/connectors.py +439 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/services/crud_service.py +38 -8
- monitor_app-2.3.1/monitor_app/services/importer.py +182 -0
- monitor_app-2.3.1/monitor_app/services/ingest_watcher.py +138 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/services/kpi_service.py +22 -7
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/services/notifiers/__init__.py +0 -3
- monitor_app-2.3.1/monitor_app/services/view_cache.py +39 -0
- monitor_app-2.3.1/monitor_app/services/view_service.py +107 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/settings/declarative.py +105 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/settings/runtime.py +8 -1
- monitor_app-2.3.1/monitor_app/web/static/css/app.css +571 -0
- monitor_app-2.3.1/monitor_app/web/static/css/dashboard.css +147 -0
- monitor_app-2.3.1/monitor_app/web/static/css/kiosk.css +189 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/web/static/js/alerts-ui.js +23 -30
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/web/static/js/app.js +50 -63
- monitor_app-2.3.1/monitor_app/web/static/js/chart-view.js +195 -0
- monitor_app-2.3.1/monitor_app/web/static/js/dashboard.js +193 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/web/static/js/form.js +7 -0
- monitor_app-2.3.1/monitor_app/web/static/js/kiosk.js +220 -0
- monitor_app-2.3.1/monitor_app/web/static/js/kpis.js +104 -0
- monitor_app-2.3.1/monitor_app/web/static/js/table-render.js +87 -0
- monitor_app-2.3.1/monitor_app/web/static/js/theme.js +73 -0
- monitor_app-2.3.1/monitor_app/web/templates/base.html +110 -0
- monitor_app-2.3.1/monitor_app/web/templates/index.html +116 -0
- monitor_app-2.3.1/monitor_app/web/templates/kiosk.html +75 -0
- monitor_app-2.3.1/monitor_app/web/templates/login.html +49 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/web/templates/table.html +26 -6
- {monitor_app-2.1.0 → monitor_app-2.3.1}/pyproject.toml +35 -6
- monitor_app-2.1.0/monitor_app/api/deps.py +0 -88
- monitor_app-2.1.0/monitor_app/api/routers/pages.py +0 -111
- monitor_app-2.1.0/monitor_app/services/alert_service.py +0 -130
- monitor_app-2.1.0/monitor_app/services/importer.py +0 -114
- monitor_app-2.1.0/monitor_app/services/ingest_watcher.py +0 -74
- monitor_app-2.1.0/monitor_app/services/notifiers/line.py +0 -22
- monitor_app-2.1.0/monitor_app/services/view_service.py +0 -54
- monitor_app-2.1.0/monitor_app/web/static/css/app.css +0 -278
- monitor_app-2.1.0/monitor_app/web/static/css/kiosk.css +0 -100
- monitor_app-2.1.0/monitor_app/web/static/js/chart-view.js +0 -95
- monitor_app-2.1.0/monitor_app/web/static/js/kiosk.js +0 -131
- monitor_app-2.1.0/monitor_app/web/static/js/kpis.js +0 -57
- monitor_app-2.1.0/monitor_app/web/templates/base.html +0 -44
- monitor_app-2.1.0/monitor_app/web/templates/index.html +0 -55
- monitor_app-2.1.0/monitor_app/web/templates/kiosk.html +0 -53
- {monitor_app-2.1.0 → monitor_app-2.3.1}/LICENSE +0 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/__init__.py +0 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/api/__init__.py +0 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/api/routers/__init__.py +0 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/api/routers/alerts.py +0 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/api/routers/audit.py +0 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/api/routers/ingest.py +1 -1
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/api/routers/meta.py +1 -1
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/db/__init__.py +0 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/db/engine.py +0 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/exceptions.py +0 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/logging_conf.py +0 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/scaffold/README.md +0 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/scaffold/csv/orders.csv +0 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/scaffold/csv/products.csv +0 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/scaffold/csv/users.csv +0 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/services/__init__.py +0 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/services/audit_service.py +0 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/services/coercion.py +0 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/services/notifiers/base.py +0 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/services/notifiers/email.py +0 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/services/notifiers/webhook.py +0 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/settings/__init__.py +0 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/settings/loader.py +0 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/web/static/css/bootstrap.min.css +0 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/web/static/img/background.webp +0 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/web/static/img/favicon.ico +0 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/web/static/img/logo.webp +0 -0
- {monitor_app-2.1.0 → monitor_app-2.3.1}/monitor_app/web/static/js/chart.umd.min.js +0 -0
- {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
|
|
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.
|
|
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.
|
|
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.
|
|
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 +
|
|
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
|
|
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 +
|
|
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
|
|
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)
|