monitor-app 2.4.0__tar.gz → 2.5.0__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 (338) hide show
  1. {monitor_app-2.4.0 → monitor_app-2.5.0}/CHANGELOG.md +36 -0
  2. {monitor_app-2.4.0 → monitor_app-2.5.0}/LICENSE +1 -1
  3. {monitor_app-2.4.0 → monitor_app-2.5.0}/PKG-INFO +135 -39
  4. {monitor_app-2.4.0 → monitor_app-2.5.0}/README.md +134 -38
  5. {monitor_app-2.4.0 → monitor_app-2.5.0}/README_ja.md +84 -18
  6. {monitor_app-2.4.0 → monitor_app-2.5.0}/docker/entrypoint.sh +49 -3
  7. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/api/deps.py +133 -17
  8. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/api/errors.py +6 -2
  9. monitor_app-2.5.0/monitor_app/api/routers/auth.py +95 -0
  10. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/api/routers/pages.py +82 -3
  11. monitor_app-2.5.0/monitor_app/api/routers/settings.py +125 -0
  12. monitor_app-2.5.0/monitor_app/api/routers/uploads.py +145 -0
  13. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/api/routers/views.py +22 -12
  14. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/checks.py +38 -6
  15. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/cli/__init__.py +6 -2
  16. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/cli/_common.py +18 -0
  17. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/cli/_errors.py +1 -1
  18. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/cli/_signals.py +1 -2
  19. monitor_app-2.5.0/monitor_app/cli/demo.py +117 -0
  20. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/cli/project.py +44 -15
  21. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/cli/server.py +73 -12
  22. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/cli/zero.py +68 -153
  23. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/container.py +112 -11
  24. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/db/engine.py +22 -2
  25. monitor_app-2.5.0/monitor_app/db/settings_store.py +90 -0
  26. monitor_app-2.5.0/monitor_app/demo/__init__.py +36 -0
  27. monitor_app-2.5.0/monitor_app/demo/simulation.py +722 -0
  28. monitor_app-2.5.0/monitor_app/demo/warehouse/README.md +31 -0
  29. monitor_app-2.5.0/monitor_app/demo/warehouse/config.py +444 -0
  30. monitor_app-2.5.0/monitor_app/demo/warehouse/csv/items.csv +25 -0
  31. monitor_app-2.5.0/monitor_app/demo/warehouse/csv/locations.csv +73 -0
  32. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/main.py +57 -4
  33. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/scaffold/config.py +1 -0
  34. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/scaffold/env.example +4 -0
  35. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/security.py +55 -6
  36. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/services/alert_scheduler.py +10 -4
  37. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/services/alert_service.py +27 -3
  38. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/services/crud_service.py +7 -8
  39. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/services/kpi_service.py +5 -6
  40. monitor_app-2.5.0/monitor_app/services/settings_service.py +310 -0
  41. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/services/view_service.py +5 -5
  42. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/settings/declarative.py +4 -0
  43. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/settings/infer.py +11 -10
  44. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/settings/loader.py +5 -0
  45. monitor_app-2.5.0/monitor_app/settings/overrides.py +110 -0
  46. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/settings/runtime.py +4 -0
  47. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/web/serving.py +8 -0
  48. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/web/static/css/app.css +2 -1
  49. monitor_app-2.5.0/monitor_app/web/static/css/settings.css +245 -0
  50. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/web/static/js/app.js +5 -0
  51. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/web/static/js/chart-view.js +2 -1
  52. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/web/static/js/core.js +78 -18
  53. monitor_app-2.5.0/monitor_app/web/static/js/settings.js +1193 -0
  54. monitor_app-2.5.0/monitor_app/web/static/js/upload.js +158 -0
  55. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/web/templates/base.html +11 -0
  56. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/web/templates/form.html +3 -3
  57. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/web/templates/index.html +3 -3
  58. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/web/templates/kiosk.html +1 -0
  59. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/web/templates/login.html +10 -2
  60. monitor_app-2.5.0/monitor_app/web/templates/settings.html +73 -0
  61. monitor_app-2.5.0/monitor_app/web/templates/upload.html +73 -0
  62. {monitor_app-2.4.0 → monitor_app-2.5.0}/pyproject.toml +2 -1
  63. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/e2e/test_browser_ui.py +204 -2
  64. monitor_app-2.5.0/tests/e2e/test_e2e_demo.py +123 -0
  65. monitor_app-2.5.0/tests/js/chart-view.test.mjs +68 -0
  66. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/js/core.test.mjs +69 -9
  67. monitor_app-2.5.0/tests/test_cli_demo.py +319 -0
  68. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_cli_help.py +5 -2
  69. monitor_app-2.5.0/tests/test_csv_upload.py +215 -0
  70. monitor_app-2.5.0/tests/test_demo_config.py +32 -0
  71. monitor_app-2.5.0/tests/test_demo_simulation.py +280 -0
  72. monitor_app-2.5.0/tests/test_demo_simulator.py +234 -0
  73. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_docker_entrypoint.py +101 -0
  74. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_docker_hardening.py +7 -0
  75. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_features.py +23 -0
  76. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_oneoff_commands_sigterm.py +3 -3
  77. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_security.py +56 -0
  78. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_serve.py +28 -8
  79. monitor_app-2.5.0/tests/test_ui_settings.py +652 -0
  80. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_view_concurrency.py +11 -0
  81. monitor_app-2.4.0/monitor_app/api/routers/auth.py +0 -65
  82. {monitor_app-2.4.0 → monitor_app-2.5.0}/.gitignore +0 -0
  83. {monitor_app-2.4.0 → monitor_app-2.5.0}/docker/healthcheck.py +0 -0
  84. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/__init__.py +0 -0
  85. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/__main__.py +0 -0
  86. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/api/__init__.py +0 -0
  87. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/api/routers/__init__.py +0 -0
  88. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/api/routers/alerts.py +0 -0
  89. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/api/routers/audit.py +0 -0
  90. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/api/routers/crud.py +0 -0
  91. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/api/routers/ingest.py +0 -0
  92. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/api/routers/meta.py +0 -0
  93. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/api/schemas.py +0 -0
  94. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/cancel.py +0 -0
  95. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/cli/__main__.py +0 -0
  96. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/cli/data.py +0 -0
  97. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/cli/deploy.py +0 -0
  98. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/cli/maintenance.py +0 -0
  99. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/db/__init__.py +0 -0
  100. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/db/backup.py +0 -0
  101. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/db/bulk.py +0 -0
  102. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/db/change_token.py +0 -0
  103. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/db/errors.py +0 -0
  104. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/db/gate.py +0 -0
  105. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/db/ingest_state.py +0 -0
  106. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/db/registry.py +0 -0
  107. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/db/repository.py +0 -0
  108. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/db/schema.py +0 -0
  109. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/db/types.py +0 -0
  110. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/deploy.py +0 -0
  111. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/exceptions.py +0 -0
  112. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/integrations/__init__.py +0 -0
  113. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/integrations/netdiag.py +0 -0
  114. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/integrations/network_checker.py +0 -0
  115. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/integrations/procdiag.py +0 -0
  116. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/logging_conf.py +0 -0
  117. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/py.typed +0 -0
  118. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/scaffold/README.md +0 -0
  119. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/scaffold/csv/orders.csv +0 -0
  120. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/scaffold/csv/products.csv +0 -0
  121. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/scaffold/csv/users.csv +0 -0
  122. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/scaffold/deploy/compose.yaml +0 -0
  123. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/scaffold/deploy/dockerignore +0 -0
  124. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/scaffold/deploy/install-task.ps1 +0 -0
  125. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/scaffold/deploy/monitor-app.service +0 -0
  126. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/scaffold/deploy/start-monitor.bat +0 -0
  127. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/services/__init__.py +0 -0
  128. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/services/audit_service.py +0 -0
  129. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/services/backup_task.py +0 -0
  130. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/services/coercion.py +0 -0
  131. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/services/connectors.py +0 -0
  132. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/services/csv_encoding.py +0 -0
  133. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/services/csv_tail.py +0 -0
  134. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/services/importer.py +0 -0
  135. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/services/ingest_watcher.py +0 -0
  136. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/services/notifiers/__init__.py +0 -0
  137. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/services/notifiers/base.py +0 -0
  138. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/services/notifiers/email.py +0 -0
  139. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/services/notifiers/webhook.py +0 -0
  140. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/services/periodic.py +0 -0
  141. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/services/retention.py +0 -0
  142. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/services/rows.py +0 -0
  143. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/services/value_limits.py +0 -0
  144. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/services/view_cache.py +0 -0
  145. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/settings/__init__.py +0 -0
  146. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/web/__init__.py +0 -0
  147. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/web/api_docs.py +0 -0
  148. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/web/static/css/bootstrap.min.css +0 -0
  149. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/web/static/css/dashboard.css +0 -0
  150. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/web/static/css/kiosk.css +0 -0
  151. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/web/static/img/favicon.ico +0 -0
  152. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/web/static/js/alerts-ui.js +0 -0
  153. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/web/static/js/chart.umd.min.js +0 -0
  154. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/web/static/js/dashboard.js +0 -0
  155. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/web/static/js/form.js +0 -0
  156. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/web/static/js/kiosk.js +0 -0
  157. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/web/static/js/kpis.js +0 -0
  158. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/web/static/js/table-render.js +0 -0
  159. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/web/static/js/theme.js +0 -0
  160. {monitor_app-2.4.0 → monitor_app-2.5.0}/monitor_app/web/templates/table.html +0 -0
  161. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/conftest.py +0 -0
  162. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/contract/__init__.py +0 -0
  163. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/contract/_make_baseline.py +0 -0
  164. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/contract/_make_upgrade_fixture.py +0 -0
  165. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/contract/baseline/cli.json +0 -0
  166. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/contract/baseline/models.json +0 -0
  167. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/contract/baseline/openapi.json +0 -0
  168. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/contract/baseline/public_api.json +0 -0
  169. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/contract/baseline/responses.json +0 -0
  170. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/contract/baseline/settings.json +0 -0
  171. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/contract/compat.py +0 -0
  172. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/contract/fixtures/.gitattributes +0 -0
  173. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/contract/fixtures/legacy_v1_config.py +0 -0
  174. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/contract/fixtures/v2_3_1_project/.env +0 -0
  175. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/contract/fixtures/v2_3_1_project/config.py +0 -0
  176. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/contract/fixtures/v2_3_1_project/csv/orders.csv +0 -0
  177. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/contract/fixtures/v2_3_1_project/csv/products.csv +0 -0
  178. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/contract/fixtures/v2_3_1_project/csv/users.csv +0 -0
  179. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/contract/fixtures/v2_3_1_project/database.sql +0 -0
  180. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/contract/fixtures/v2_3_1_project/sources/line_events.jsonl +0 -0
  181. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/contract/helpers.py +0 -0
  182. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/contract/snapshot.py +0 -0
  183. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/e2e/__init__.py +0 -0
  184. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/e2e/browser_contrast.js +0 -0
  185. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/e2e/conftest.py +0 -0
  186. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/e2e/test_e2e_db_wait_stop.py +0 -0
  187. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/e2e/test_e2e_logging.py +0 -0
  188. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/e2e/test_e2e_serve.py +0 -0
  189. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/e2e/test_e2e_server.py +0 -0
  190. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/e2e/test_e2e_sse_shutdown.py +0 -0
  191. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/e2e/test_e2e_windows_bat.py +0 -0
  192. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/server_db/__init__.py +0 -0
  193. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/server_db/conftest.py +0 -0
  194. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/server_db/test_bulk_server.py +0 -0
  195. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/server_db/test_datetime_server.py +0 -0
  196. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/server_db/test_downgrade_cursors_server.py +0 -0
  197. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/server_db/test_import_fk_server.py +0 -0
  198. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/server_db/test_import_server_limits.py +0 -0
  199. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/server_db/test_ingest_pipeline_server.py +0 -0
  200. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/server_db/test_migrate_sequence_repair_server.py +0 -0
  201. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/server_db/test_nonfinite_server.py +0 -0
  202. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/server_db/test_pg_sequence_repair.py +0 -0
  203. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/server_db/test_retention_text_server.py +0 -0
  204. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/server_db/test_server_databases.py +0 -0
  205. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_alert_evaluation.py +0 -0
  206. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_alert_notification_order.py +0 -0
  207. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_api.py +0 -0
  208. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_async_background.py +0 -0
  209. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_auth_hardening.py +0 -0
  210. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_auth_session.py +0 -0
  211. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_backend_hardening.py +0 -0
  212. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_backup_recovery.py +0 -0
  213. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_backup_robustness.py +0 -0
  214. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_change_token.py +0 -0
  215. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_check_import_parity.py +0 -0
  216. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_checks.py +0 -0
  217. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_cli.py +0 -0
  218. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_cli_backup_restore.py +0 -0
  219. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_cli_check_migrate.py +0 -0
  220. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_cli_errors.py +0 -0
  221. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_cli_from_csv.py +0 -0
  222. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_cli_import_options.py +0 -0
  223. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_cli_launcher.py +0 -0
  224. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_cli_module_split.py +0 -0
  225. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_cli_paths.py +0 -0
  226. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_cli_smoke.py +0 -0
  227. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_cli_value_errors.py +0 -0
  228. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_coercion.py +0 -0
  229. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_config_folder_paths.py +0 -0
  230. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_config_ux.py +0 -0
  231. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_connectors.py +0 -0
  232. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_connectors_cursor.py +0 -0
  233. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_container.py +0 -0
  234. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_container_db.py +0 -0
  235. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_contract.py +0 -0
  236. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_csv_bad_bytes.py +0 -0
  237. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_csv_encoding_auto.py +0 -0
  238. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_csv_encoding_policy.py +0 -0
  239. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_csv_import_streaming.py +0 -0
  240. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_csv_nul_and_unparsable.py +0 -0
  241. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_csv_tailer.py +0 -0
  242. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_datetime_coercion.py +0 -0
  243. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_datetime_storage.py +0 -0
  244. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_db_backup.py +0 -0
  245. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_db_bulk.py +0 -0
  246. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_db_busy.py +0 -0
  247. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_db_connect_timeout.py +0 -0
  248. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_db_engine.py +0 -0
  249. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_db_errors.py +0 -0
  250. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_db_readonly.py +0 -0
  251. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_db_wait_abort.py +0 -0
  252. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_deploy_files.py +0 -0
  253. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_deploy_hardening.py +0 -0
  254. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_docker_healthcheck.py +0 -0
  255. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_downgrade_cursors.py +0 -0
  256. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_env_unknown_key_message.py +0 -0
  257. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_error_and_docs_hardening.py +0 -0
  258. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_export_formula_injection.py +0 -0
  259. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_favicon_legacy.py +0 -0
  260. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_fetch_gate_aging.py +0 -0
  261. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_fetch_gate_fairness.py +0 -0
  262. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_form_replace_warning.py +0 -0
  263. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_frontend_js.py +0 -0
  264. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_health.py +0 -0
  265. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_health_tasks.py +0 -0
  266. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_host_env_isolation.py +0 -0
  267. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_host_guard.py +0 -0
  268. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_http_integration.py +0 -0
  269. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_import_csv_summary.py +0 -0
  270. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_import_fk_order.py +0 -0
  271. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_import_modes.py +0 -0
  272. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_import_type_hints.py +0 -0
  273. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_infer.py +0 -0
  274. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_ingest_on_change.py +0 -0
  275. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_ingest_on_conflict.py +0 -0
  276. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_ingest_robustness.py +0 -0
  277. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_ingest_sources.py +0 -0
  278. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_integration_netcheck.py +0 -0
  279. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_integration_netdiag.py +0 -0
  280. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_integration_procdiag.py +0 -0
  281. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_internal_ids_documented.py +0 -0
  282. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_known_issues.py +0 -0
  283. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_kpi_cache.py +0 -0
  284. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_legacy_table_rebuild.py +0 -0
  285. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_logging_conf.py +0 -0
  286. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_migrate_sequence_repair.py +0 -0
  287. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_nav_groups.py +0 -0
  288. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_nonfinite_floats.py +0 -0
  289. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_notifier_delivery.py +0 -0
  290. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_notifiers.py +0 -0
  291. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_pages.py +0 -0
  292. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_periodic.py +0 -0
  293. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_periodic_status.py +0 -0
  294. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_postgres_url_hint.py +0 -0
  295. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_retention.py +0 -0
  296. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_retention_text_formats.py +0 -0
  297. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_rows_converter.py +0 -0
  298. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_runserver_bind_errors.py +0 -0
  299. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_runserver_passes_host.py +0 -0
  300. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_runserver_wiring.py +0 -0
  301. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_scaffold_config.py +0 -0
  302. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_scaffold_env.py +0 -0
  303. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_schema_drift.py +0 -0
  304. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_sequence_repair.py +0 -0
  305. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_serve_append_handover.py +0 -0
  306. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_serve_import_notices.py +0 -0
  307. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_server_int_columns.py +0 -0
  308. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_settings.py +0 -0
  309. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_settings_additions.py +0 -0
  310. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_settings_errors.py +0 -0
  311. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_shared_helpers.py +0 -0
  312. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_shutdown_inflight_tick.py +0 -0
  313. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_smtp_tls_verify.py +0 -0
  314. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_source_snapshot_append.py +0 -0
  315. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_sources_config.py +0 -0
  316. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_sqlite_error_hints.py +0 -0
  317. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_sse_thread_budget.py +0 -0
  318. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_staged_import_cleanup.py +0 -0
  319. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_table_paging_order.py +0 -0
  320. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_tail_resume.py +0 -0
  321. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_ui_base.py +0 -0
  322. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_ui_config.py +0 -0
  323. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_ui_dashboard.py +0 -0
  324. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_ui_kiosk.py +0 -0
  325. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_ui_viewpage.py +0 -0
  326. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_unknown_env_suggestions.py +0 -0
  327. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_upgrade_v2_3_1.py +0 -0
  328. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_value_limits.py +0 -0
  329. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_view_cache.py +0 -0
  330. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_view_cache_memory_budget.py +0 -0
  331. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_view_cache_shared_error.py +0 -0
  332. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_view_export.py +0 -0
  333. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_view_gzip_cache.py +0 -0
  334. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_view_payload.py +0 -0
  335. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_view_perf.py +0 -0
  336. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_watcher_retry.py +0 -0
  337. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_watcher_retry_message.py +0 -0
  338. {monitor_app-2.4.0 → monitor_app-2.5.0}/tests/test_web_serving.py +0 -0
@@ -1,5 +1,41 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.5.0] - 2026-11-08
4
+
5
+ > 「準備なしで試せて、ブラウザから設定の編集と CSV の取り込みができる」ことを目標にした機能リリース。架空の物流センターの在庫画面を開く `monitor-app demo`、同じ内容でプロジェクトを作る `startproject --template warehouse`、`config.py` を書き換えずにビュー・KPI・アラートなどを編集する設定ページ(`/settings`)、ブラウザからの CSV のアップロード(`/upload`)、管理の機能だけに使う鍵 `MONITOR_ADMIN_KEY` を含む。既存のプロジェクト・`config.py`・DB・REST API・CLI はそのまま動く。挙動が変わる点は「Changed」、更新するときの注意は「Upgrade notes」を参照。
6
+
7
+ ### Added
8
+ - **設定ページ(`/settings`)** — 表示(アプリの名前・見出し・更新の間隔と方式)・ビュー(タイトル・SQL・列の見出し・セルの色分け・グラフと管理限界線)・KPI・アラート・大型表示・ダッシュボードを、`config.py` を書き換えずに画面で編集する。ビュー・KPI・アラートの追加と削除もできる。テーブルの定義・`sources`・favicon は対象外(`config.py` で変える)
9
+ - 保存するとサーバーを止めずに反映する(ビューのキャッシュを捨て、アラートのルールを差し替え、初めてアラートを定義したら評価を始める)。開いている画面は、応答の `X-Monitor-Config` ヘッダ(SSE では `reload` イベント)で設定が変わったことを知って読み直す(大型表示も追従する。作業者の入力フォームは読み直さない)
10
+ - 保存するのは `config.py` との差分だけ(JSON Merge Patch)で、DB の内部テーブル `_settings` に置く(再起動しても残り、SQLite のバックアップにも入る)。画面で変えていない項目は `config.py` の値を使う。「config.py の設定に戻す」で差分を消す。`config.py` を変えて差分を重ねられなくなったときは `config.py` の設定だけで起動し、設定ページで知らせる
11
+ - 保存の前に `monitor-app check` と同じ点検(SQL の空実行・アラートとグラフの列・通知先)を行い、エラーがあれば保存しない(`422`)。画面が読み込んだ後にほかの人が保存していたら上書きしない(`409`)。`MONITOR_AUDIT_ENABLED=true` なら変更を監査ログに残す
12
+ - API: `GET/PUT/DELETE /api/settings`・`POST /api/settings/check`
13
+ - **CSV のアップロード(`/upload`・`POST /api/upload/{table}`)** — ブラウザから CSV を選んでテーブルに取り込む。`monitor-app import-csv` と同じ処理(文字コードの自動判別・見出しの表記ゆれ・1 トランザクション・列が丸ごと無い CSV での置き換えの拒否と `force`)。追記と置き換えを選べる。送ったファイルはサーバーに残さず、監査ログ(`action=import`)に残す
14
+ - **`MONITOR_ADMIN_KEY`** — 管理の機能(設定の編集・CSV のアップロード)だけに使う鍵。設定すると管理の機能はこの鍵でだけ使え、`MONITOR_API_KEY` を渡した作業者・閲覧者は設定を変えられない。この鍵でログインすると閲覧・書き込みもできる。未設定なら管理の機能は `MONITOR_API_KEY` で使え、どちらの鍵も無ければこの PC からだけ使える(LAN やリバースプロキシの先に公開しているときは使えない)。`monitor-app check` と起動時のログは、説明用の値・短すぎる管理者の鍵と、`.env` の綴りの誤り(エラー)も知らせる
15
+ - ログインの画面は、ログインの後に元の画面へ戻る(`/login?next=/settings`。アプリ内のパスだけ)
16
+ - **`monitor-app demo`** — 準備なしで、架空の物流センターの在庫画面を開く(`--host`・`--port`・`--open`)。在庫は入出庫の台帳を SQL で合計して求め、4 秒ごとに入荷・出荷・移動を足すので、KPI・グラフ・アラートがひとりでに動く。データは起動した日時を基準に作り(7 日分)、一時的な SQLite に入れて終了時に消す。作業フォルダの `.env` は読まず、メール・Webhook・バックアップ・フォルダ監視は使わない
17
+ - **`startproject --template warehouse`** — デモと同じ `config.py`・マスタ・7 日分の架空のデータでプロジェクトを作る(入出庫の自動発生はなし)
18
+ - **`FormDef(title=...)`** — 作業者の入力フォームの名前。フォームの見出し・ブラウザのタブの題名・トップページのリンクに出る。未指定なら今までどおり「<テーブル名> の入力」(タブの題名だけは「<テーブル名> 入力」から見出しにそろえた)。デモと雛形のフォームにも名前を付けた(`入出庫の入力`・`注文の入力`)
19
+
20
+ ### Changed
21
+ - Docker イメージを何もマウントせずに起動すると、サンプルのプロジェクトの代わりに倉庫在庫のデモ(`monitor-app demo`)を起動する。`--host`・`--port` はデモに渡す。空のフォルダをマウントしたときはこれまでどおりサンプルのプロジェクトを書き込む
22
+
23
+ ### Security
24
+ - 管理の機能は、Cookie で認証した要求と鍵なしの要求について、ほかのサイトのページから送られたもの(`Sec-Fetch-Site: cross-site`・`Origin` の不一致)を断る(CSRF)
25
+ - 画面から保存するビュー・KPI の SQL は、`;` を含むものを断る(文字列・コメントの中も。末尾の `;` は除く)。PostgreSQL / MySQL の読み取り専用の制限は 1 つのトランザクションにしか効かず、psycopg・mysqlclient では 1 回で複数の文を送れるため。引用・コメントの読み方は DB ごとに違うので、引用の外かどうかでは判定しない。KPI の表示形式は値の書式だけを許す(`{0.__class__}` のような属性・添字の読み取りを断る)。新しいビュー・KPI の名前に URL や SQL を壊す文字を使えない。`config.py` に書いたものは対象外
26
+ - CSV のアップロードで送られたファイル名は、どの OS でも Windows の規則でフォルダとドライブを取り除いて一時フォルダに置く(Windows で `D:config.py` のような名前が一時フォルダの外を指し、プロジェクトのファイルを書き換えないように)。`:` を含む名前・`CON` などの装置の名前はテーブル名の `.csv` にする
27
+ - KPI の評価で 1 つが失敗して取り消した後、PostgreSQL / MySQL では読み取り専用の制限が外れたまま残りの KPI を実行していたのを、かけ直すようにした
28
+
29
+ ### Fixed
30
+ - KPI の表示形式が値を整形できない形(`{}{}` など)のとき、`/api/kpis` が 500 になっていたのを、値をそのまま表示するようにした
31
+ - グラフの凡例に列名(`shipped` など)が出ていたのを、ビューの列の見出し(`labels`)を使うようにした(見出しの無い列は列名のまま)
32
+ - 一時フォルダのパス(`TMPDIR` など)に `?` が含まれるとき、`monitor-app demo`・`serve` が DB を一時フォルダの外に作って終了後も残していたのを、案内を表示して止めるようにした
33
+
34
+ ### Upgrade notes
35
+ - **何もマウントしない Docker**: サンプルのプロジェクトの代わりに倉庫在庫のデモが開く(データは一時的な DB に入り、終了すると消える)。サンプルのプロジェクトを使うには、空のフォルダを `/project` にマウントする。空のフォルダには、これまでどおりサンプルのプロジェクトを書き込む(例: 空のフォルダで `docker run -p 9990:9990 -v "$PWD":/project ghcr.io/mikawa-bushi/monitor-app`)
36
+ - **設定ページと CSV のアップロード**: `MONITOR_ADMIN_KEY` も `MONITOR_API_KEY` も無ければ、この PC からだけ使える。LAN・Docker・リバースプロキシの先に公開しているサーバーで使うには、`.env` にどちらかの鍵を書く。`MONITOR_API_KEY` だけを設定していると、その鍵を渡した作業者も設定を変え、CSV で表を置き換えられる。作業者に変えさせないなら、別の `MONITOR_ADMIN_KEY` を設定する
37
+ - **`title` の無い `FormDef`**: ブラウザのタブの題名が「<テーブル名> 入力」から「<テーブル名> の入力」に変わった(見出しとトップページのリンクは変わらない)。タブの題名で画面を見分けている場合は、新しい題名に合わせる
38
+
3
39
  ## [2.4.0] - 2026-10-27
4
40
 
5
41
  > 「誰でも手元ですぐ動かせて、速く、簡単に公開できる」ことを目標にした機能リリース。設定ファイルなしでダッシュボードを開く `monitor-app serve`、SQLite の WAL 化とストリーミング取り込み・共有キャッシュによる高速化、Docker イメージと `monitor-app deploy`(API キーの生成つき)、バックアップ・保持期間・`check` の強化、公開時のセキュリティの強化を含む。既存のプロジェクト・`config.py`(v1 の dict 形式も)・DB・REST API・CLI はそのまま動く。挙動が変わる点は「Changed」、更新の手順は「Upgrade notes」を参照。
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Your Name
3
+ Copyright (c) 2025 hardwork9047
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: monitor-app
3
- Version: 2.4.0
3
+ Version: 2.5.0
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
  Project-URL: Homepage, https://github.com/mikawa-bushi/monitor-app
6
6
  Project-URL: Repository, https://github.com/mikawa-bushi/monitor-app
@@ -68,38 +68,72 @@ works on offline factory networks.
68
68
 
69
69
  ---
70
70
 
71
- ## ✨ What's new in 2.4
72
-
73
- - **No configuration needed to start.** `monitor-app serve ./my-csv-folder` (or
74
- `uvx monitor-app serve ...` without installing) infers column types, keys, charts and
75
- KPI cards from your CSV files. Keep the result with `startproject --from-csv`.
76
- - **Much faster.** SQLite runs in WAL mode, CSV files are imported as a stream (1 million
77
- rows: about 1.3× faster, 4.7 s instead of 6.0 s on the test PC, with a constant
78
- memory use of about 60 MB instead of 580 MB), folder watching in append mode reads
79
- only the new rows (10 rows appended to a 1-million-row file: about 1 ms, was 3 s), and
80
- 30 simultaneous requests for the same 10,000-row view take about 40 ms instead of
81
- about 40 s because the query runs once and is shared. Unchanged data is answered with
82
- `304`, and responses are gzip-compressed.
83
- - **Excel-friendly import.** Shift_JIS/cp932 CSV files from Excel are detected
84
- automatically, Excel dates such as `2026/10/21 8:00` are understood, header spelling
85
- variants are matched, and exports can carry a BOM for Excel (`?bom=1`).
86
- - **Safe by default.** `runserver --import-csv-if-empty` keeps rows entered in the UI
87
- across restarts, an import that would wipe a column is refused, and `monitor-app check`
88
- finds typos in `config.py` and `.env`, broken SQL and alert columns that never match.
89
- `monitor-app deploy` writes a random API key into `.env`, and a server that listens on
90
- this PC only refuses requests for foreign host names (DNS rebinding).
91
- - **Docker in one line.** `docker run -p 9990:9990 ghcr.io/mikawa-bushi/monitor-app`
92
- (linux/amd64 and linux/arm64). Mount your project folder as is: the container runs as
93
- the folder's owner.
94
- - **`monitor-app deploy`** writes the start-up files for Docker Compose, Linux/Raspberry
95
- Pi (systemd) and Windows (Task Scheduler), plus an API key.
96
- - **Backups and retention.** `monitor-app backup` / `restore` (also for a damaged
97
- database), scheduled backups, `Retention(keep_days=...)` and `monitor-app migrate`
98
- for columns you add later.
99
- - **Reverse proxies.** Serve under a sub-path such as `/monitor/` (`MONITOR_ROOT_PATH`),
100
- monitor `/api/health` (checks the database), JSON logs.
71
+ ## 👀 See it first: a warehouse inventory demo
72
+
73
+ Open the inventory screen of a fictional distribution center, with nothing to prepare:
74
+
75
+ ```sh
76
+ uvx monitor-app demo --open # if installed: monitor-app demo --open
77
+ docker run --rm -p 9990:9990 ghcr.io/mikawa-bushi/monitor-app # with Docker (mount nothing)
78
+ ```
79
+
80
+ Open http://127.0.0.1:9990 (with Docker, http://localhost:9990). What to look at:
81
+
82
+ - **Stock list**: location, lot, best-before date, days left, cases and pieces. Stock is
83
+ the SQL sum of a ledger of stock movements.
84
+ - **It moves on its own**: every 4 seconds a receipt, shipment or move happens, and the
85
+ movement history, KPI cards and charts update by themselves.
86
+ - **Alerts**: right after start, banners show an item below safety stock and lots within
87
+ 2 days of their best-before date. About 90 seconds later a delivery arrives and the
88
+ safety-stock banner clears.
89
+ - **Other screens**: `/kiosk` (wallboard) and `/form/movements` (entry form). When the
90
+ demo runs on your own PC, `/settings` (edit settings in the browser) works too.
91
+
92
+ The data lives in a temporary database that is deleted on exit. At start the demo
93
+ prints where its `config.py` is. Docker shows times in UTC; add `-e TZ=Asia/Tokyo` (or
94
+ your zone) for local time.
95
+
96
+ To start your own project with the same configuration and fictional data:
97
+
98
+ ```sh
99
+ monitor-app startproject my-warehouse --template warehouse
100
+ cd my-warehouse
101
+ monitor-app runserver --import-csv-if-empty
102
+ ```
103
+
104
+ In a project, movements do not happen on their own. The project's `README.md` explains
105
+ how to replace the data with your own.
106
+
107
+ ## ✨ What's new in 2.5
108
+
109
+ - **A demo with nothing to prepare.** `monitor-app demo` opens the inventory screen of a
110
+ fictional distribution center. Every 4 seconds a receipt, shipment or move happens, so
111
+ the KPI cards, charts and alerts change on their own. The data lives in a temporary
112
+ SQLite database that is deleted on exit. Docker started with nothing mounted now runs
113
+ this demo instead of the sample project. Mount an empty folder to get the sample
114
+ project as before.
115
+ - **Start a project from the demo.** `monitor-app startproject NAME --template warehouse`
116
+ writes the demo's `config.py`, master data and 7 days of fictional data.
117
+ - **Settings page.** `/settings` edits the display options, views (SQL, column labels,
118
+ cell styles, charts), KPI cards, alerts, the wallboard and the dashboard without
119
+ editing `config.py`. A save runs the same checks as `monitor-app check`, then applies
120
+ at once without a restart. Only the difference from `config.py` is stored, in the
121
+ database.
122
+ - **CSV upload.** `/upload` imports a CSV file into a table from the browser, with the
123
+ same code as `monitor-app import-csv`. Choose append or replace.
124
+ - **`MONITOR_ADMIN_KEY`.** A key for the settings page and CSV upload only. When it is
125
+ set, operators who log in with `MONITOR_API_KEY` cannot change the settings. Without
126
+ it, `MONITOR_API_KEY` works, and with neither key the admin features work only from
127
+ this PC.
128
+ - **Named entry forms.** `FormDef(title="...")` sets the form's heading, its browser tab
129
+ title and its link on the home page.
130
+ - **Security.** The admin features refuse requests sent from pages on other sites
131
+ (CSRF). SQL saved from the browser must be one statement with no `;`, and a KPI number
132
+ format can only format the value. The name of an uploaded file loses its folder and
133
+ drive parts on every OS, so it cannot point outside the temporary folder.
101
134
 
102
135
  Full list: [CHANGELOG.md](https://github.com/mikawa-bushi/monitor-app/blob/main/CHANGELOG.md).
136
+ Coming from 2.4? Nothing needs changing beyond the **Upgrade notes** in the CHANGELOG.
103
137
  Coming from 2.3? Read **Upgrading from 2.3.x** below.
104
138
 
105
139
  ## Features
@@ -114,6 +148,7 @@ Coming from 2.3? Read **Upgrading from 2.3.x** below.
114
148
  | Trend / SPC charts | Line and bar charts with UCL / LCL / target lines | `ViewDef(chart=ChartDef(...))` |
115
149
  | Threshold alerts | Banner, browser notification, webhook (Slack, Teams), e-mail; notified once per change, retried on failure | `alerts=[AlertRule(...)]` |
116
150
  | Operator entry forms | Touch-friendly forms generated from the table definition | `TableDef(form=FormDef(...))`, `/form/{table}` |
151
+ | Settings and CSV upload in the browser | Edit display options, views (SQL), cell styles, charts, KPIs, alerts and the wallboard in the browser; applied without a restart, and open pages reload themselves. Upload CSV files into tables | `/settings`, `/upload` |
117
152
  | Data input | CSV import (UTF-8 or Excel cp932), folder watching (replace or append), bulk `POST` for PLC/MES/sensors, read-only `sources` (SQLite/JSONL/JSON) | `import-csv`, `MONITOR_INGEST_WATCH`, `/api/ingest/{table}` |
118
153
  | REST API | CRUD per table, view data, CSV/Excel export, OpenAPI docs | `/api/...`, `/docs`, `/redoc` |
119
154
  | Python API | Build the app from your own code, load and validate `config.py` | `create_app`, `load_config` (see **Python API**) |
@@ -311,8 +346,9 @@ Note that `--keep` adds back rows you deleted in the UI if the CSV file still co
311
346
 
312
347
  | Command | What it does | Main options |
313
348
  |---|---|---|
349
+ | `monitor-app demo` | Warehouse inventory demo, nothing to prepare (fictional data, deleted on exit) | `--host`, `--port`, `--open` |
314
350
  | `monitor-app serve [PATH...]` | Dashboard straight from CSV files, no `config.py` | `--host`, `--port`, `--db FILE`, `--append`, `--no-watch`, `--write-config FILE`, `--open` |
315
- | `monitor-app startproject NAME` | Create a project folder | `--from-csv DIR`, `--deploy docker,systemd,windows` (or `all`) |
351
+ | `monitor-app startproject NAME` | Create a project folder | `--template warehouse` (the demo's configuration and data), `--from-csv DIR`, `--deploy docker,systemd,windows` (or `all`) |
316
352
  | `monitor-app runserver` | Start the web server | `--import-csv-if-empty`, `--import-csv`, `--force`, `--host`, `--port`, `--reload`, `--config` |
317
353
  | `monitor-app check` | Check `config.py`, SQL, settings and the database tables without starting | `--data` (also the CSV files), `--strict` (warnings fail, for CI), `--fix-schema`, `--config` |
318
354
  | `monitor-app import-csv` | Import `csv/` into the database (replace) | `--keep`, `--if-empty`, `--force`, `--config` |
@@ -355,7 +391,8 @@ config = MonitorConfig(
355
391
  primary_key="id",
356
392
  indexes=[["ts"]], # speeds up ORDER BY / WHERE ts
357
393
  retention=Retention(keep_days=90, column="ts"), # delete rows older than 90 days
358
- form=FormDef(labels={"temp": "Temperature (°C)"},
394
+ form=FormDef(title="Enter readings",
395
+ labels={"temp": "Temperature (°C)"},
359
396
  choices={"line": ["L1", "L2"]}), # entry form at /form/readings
360
397
  ),
361
398
  },
@@ -441,6 +478,58 @@ MySQL `MONITOR_DATABASE_URL`, values those databases would reject.
441
478
  The legacy v1 dict-style `config.py` (`ALLOWED_TABLES` ...) still loads; see
442
479
  **Migrating from v0.x** below.
443
480
 
481
+ ## 🖱 Change settings and upload CSV files in the browser
482
+
483
+ The **⚙️ Settings** page (`/settings`) changes the following without editing
484
+ `config.py`. Saving applies the change at once, without restarting the server,
485
+ and open pages (the wallboard too) reload themselves.
486
+
487
+ | Tab | What you can change |
488
+ |---|---|
489
+ | General | App name, header and footer text, refresh interval and mode |
490
+ | Views | Title, description, group, row limit, SQL, column labels, cell styles, chart (control and target lines); add and delete views |
491
+ | KPIs | Title, SQL, unit, target, number format, linked view; add and delete KPIs |
492
+ | Alerts | View, column, condition, value, level, message, channels; add and delete alerts |
493
+ | Wallboard and dashboard | Wallboard rotation, theme and views; how many charts per row on the home page and which ones |
494
+
495
+ - Tables, `sources` and the favicon stay in `config.py` (changing them would
496
+ rebuild tables or read files on the server).
497
+ - Only the difference from `config.py` is stored, in the database (internal
498
+ table `_settings`), so it survives restarts and is part of SQLite backups.
499
+ Fields changed in the browser win over the same fields in `config.py`; the
500
+ rest still come from `config.py` (a list such as the alerts is replaced as a
501
+ whole). **Return to config.py** deletes the stored difference.
502
+ - Before saving, the same checks as `monitor-app check` run (dry-run of the
503
+ SQL, alert and chart columns, channels); errors block the save. **Check**
504
+ runs them without saving. SQL entered in the browser must be a single
505
+ `SELECT` with no `;` (not even inside quotes or comments; a trailing one is
506
+ fine) and runs read-only. Put SQL that needs `;` in `config.py`.
507
+ - If someone else saved in the meantime, the save is refused and the page asks
508
+ you to reload. With `MONITOR_AUDIT_ENABLED=true` changes go to the audit log
509
+ (`/api/audit?table=_settings`).
510
+ - If the stored difference no longer fits `config.py` (for example a view was
511
+ removed there), the server starts with `config.py` alone and the settings page
512
+ says so.
513
+
514
+ **📤 Upload CSV** (`/upload`) imports a CSV file into a table with the same code
515
+ as `monitor-app import-csv`: encoding detection (UTF-8, Excel's Shift_JIS),
516
+ header variants, one transaction (nothing changes on failure) and refusing to
517
+ replace a table from a CSV that lacks its columns. Choose **append** (rows whose
518
+ key exists are skipped) or **replace** (all rows become the CSV's rows). The
519
+ uploaded file is not kept on the server.
520
+
521
+ **Who can use them.** Settings and CSV upload are *admin features*:
522
+
523
+ | Configuration | Who can use the admin features |
524
+ |---|---|
525
+ | `MONITOR_ADMIN_KEY` set | Whoever logs in with the admin key (or sends it as `X-API-Key`). Holders of `MONITOR_API_KEY` cannot |
526
+ | Only `MONITOR_API_KEY` | Whoever logs in with the API key (same as writes) |
527
+ | Neither | Only this PC (`MONITOR_HOST=127.0.0.1` and no reverse-proxy settings). Disabled when the server is exposed on the LAN or behind a proxy |
528
+
529
+ Requests sent from pages on other sites (CSRF) are refused. If operators log in
530
+ with the API key to use the entry forms, set a separate `MONITOR_ADMIN_KEY` so
531
+ they cannot change the settings.
532
+
444
533
  ## ⚙️ Settings (`MONITOR_*` / `.env`)
445
534
 
446
535
  Settings that differ per PC (database URL, port, passwords) do not belong in `config.py`.
@@ -466,6 +555,7 @@ setting) or set them as environment variables, which win over `.env`.
466
555
  | **Security** | | |
467
556
  | `MONITOR_API_KEY` | (unset) | Turns on authentication: writes (API, forms, ingest) need the key — `X-API-Key` header, or a login in the browser (the session lasts 12 hours). Use 16 or more random characters; `monitor-app deploy` writes one into `.env`. An empty value counts as unset. |
468
557
  | `MONITOR_PROTECT_READS` | `false` | Require the key for reading too (pages, read APIs and the API documentation). |
558
+ | `MONITOR_ADMIN_KEY` | (unset) | Key for the admin features only (settings page, CSV upload). When set, they accept this key and nothing else; logging in with it also allows reading and writing. Unset: `MONITOR_API_KEY` works. See **Change settings and upload CSV files in the browser** above. |
469
559
  | `MONITOR_ALLOWED_HOSTS` | (unset) | Host names the server answers to (comma list; `*.example.com` matches subdomains, `*` turns the check off). Unset: a server that listens on `127.0.0.1` refuses host names other than `localhost`, its own names and IP addresses (DNS-rebinding protection); LAN, Docker and the reverse-proxy setups below are not checked. A list is enforced on every interface. |
470
560
  | `MONITOR_CORS_ORIGINS` | `[]` | Other web origins allowed to call the API, as JSON: `["http://localhost:3000"]`. Empty = same origin only. |
471
561
  | **CSV import** | | |
@@ -607,7 +697,7 @@ delays the shutdown until the container is killed.
607
697
  ### Docker
608
698
 
609
699
  ```sh
610
- docker run --rm -p 9990:9990 ghcr.io/mikawa-bushi/monitor-app # demo, no files needed
700
+ docker run --rm -p 9990:9990 ghcr.io/mikawa-bushi/monitor-app # warehouse demo, no files needed
611
701
  ```
612
702
 
613
703
  Run your own project (the folder with `config.py`):
@@ -621,7 +711,8 @@ docker run -d --name monitor-app -p 9990:9990 -v "$PWD":/project \
621
711
  (Windows PowerShell: `-v "${PWD}:/project"`.) The container switches to the owner of the
622
712
  mounted folder and drops all Linux capabilities, so the database and backups belong to
623
713
  you on the host whatever your user id is. It starts `runserver --import-csv-if-empty`, so
624
- rows entered in the UI survive updates. An empty folder gets the sample project.
714
+ rows entered in the UI survive updates. An empty folder gets the sample project. With
715
+ nothing mounted, the warehouse demo above runs on a temporary database.
625
716
  For a hardened container add `--cap-drop ALL --cap-add SETUID --cap-add SETGID
626
717
  --cap-add CHOWN --security-opt no-new-privileges` (the generated `compose.yaml` sets
627
718
  these). `docker run --rm ghcr.io/mikawa-bushi/monitor-app --version` prints the version.
@@ -641,13 +732,13 @@ docker compose pull && docker compose up -d # update to the newest image
641
732
  deleting rows needs the key (log in on the page, or send `X-API-Key`).
642
733
  - `.env` is read by the app inside the container as usual. The generated `compose.yaml`
643
734
  also takes `MONITOR_PORT` (port on the host), `TZ` (for example `TZ=Asia/Tokyo`),
644
- `MONITOR_IMAGE` (image name) and `MONITOR_IMAGE_TAG` (for example `2.4.0` to stay on a
735
+ `MONITOR_IMAGE` (image name) and `MONITOR_IMAGE_TAG` (for example `2.5.0` to stay on a
645
736
  version) from `.env`.
646
737
  - Other commands run the same way as the folder's owner:
647
738
  `docker compose run --rm monitor-app check`, `... backup`, `... import-csv`. Do not use
648
739
  `docker compose exec monitor-app monitor-app backup`: that runs as root and the backup
649
740
  file can then only be read by root.
650
- - Image tags: `latest`, `2`, `2.4`, `2.4.0`; platforms `linux/amd64` and `linux/arm64`
741
+ - Image tags: `latest`, `2`, `2.5`, `2.5.0`; platforms `linux/amd64` and `linux/arm64`
651
742
  (64-bit Raspberry Pi OS). The image has a health check (`/api/health`) and stops within
652
743
  about a second.
653
744
  - On Docker Desktop (Windows, macOS) keep `instances/` in a named volume, for speed and
@@ -788,7 +879,7 @@ curl http://127.0.0.1:9990/api/health
788
879
  ```
789
880
 
790
881
  ```json
791
- {"status":"ok","version":"2.4.0","database":"ok","tasks":{"alert scheduler":{"running":true,"last_tick":"2026-10-21T17:30:44+09:00","last_error":null}}}
882
+ {"status":"ok","version":"2.5.0","database":"ok","tasks":{"alert scheduler":{"running":true,"last_tick":"2026-10-21T17:30:44+09:00","last_error":null}}}
792
883
  ```
793
884
 
794
885
  It answers `200` when the database responds to `SELECT 1` and `503` (with
@@ -804,7 +895,9 @@ It answers `200` when the database responds to `SELECT 1` and `503` (with
804
895
  | `/table/{view}` | Live table / chart page |
805
896
  | `/kiosk` | Andon wallboard (full screen, rotating) |
806
897
  | `/form/{table}` | Operator entry form |
807
- | `/login` | Login page (used when `MONITOR_API_KEY` is set) |
898
+ | `/settings` | Settings page (admin feature) |
899
+ | `/upload` | CSV upload (admin feature) |
900
+ | `/login` | Login page (used when `MONITOR_API_KEY` or `MONITOR_ADMIN_KEY` is set) |
808
901
  | `/docs`, `/redoc` | OpenAPI documentation (these two pages load pinned Swagger UI / ReDoc versions from a CDN; login needed with `MONITOR_PROTECT_READS`; off with `MONITOR_API_DOCS=false`) |
809
902
  | `GET/POST /api/tables/{table}` | List rows (`?limit=`, `?offset=`; pages are ordered by primary key) / add a row |
810
903
  | `GET/PUT/DELETE /api/tables/{table}/{id}` | One row |
@@ -815,6 +908,9 @@ It answers `200` when the database responds to `SELECT 1` and `503` (with
815
908
  | `GET /api/views/{view}/export` | `?format=csv` or `xlsx`; `?bom=1` adds a UTF-8 BOM so Excel shows Japanese text correctly. Text that starts with `=` `+` `-` `@` is exported so that spreadsheets do not run it as a formula; `?raw=1` exports the values exactly as stored |
816
909
  | `GET /api/kpis`, `/api/alerts`, `/api/schema` | KPI values, active alerts, table definitions |
817
910
  | `GET /api/audit` | Change history (`?table=`, `?limit=`) when `MONITOR_AUDIT_ENABLED=true` |
911
+ | `GET/PUT/DELETE /api/settings` | Settings editable in the browser / check and save (`{"settings": {...}, "revision": "..."}`; errors → `422`, saved by someone else first → `409`) / return to `config.py`. Admin feature |
912
+ | `POST /api/settings/check` | Check without saving (same rules as `monitor-app check`). Admin feature |
913
+ | `POST /api/upload/{table}` | CSV upload (multipart `file`, `mode=append` or `replace`, `force`). Admin feature |
818
914
  | `GET/HEAD /api/health` | Health check (see above) |
819
915
 
820
916
  With `MONITOR_API_KEY` set, send the key in the `X-API-Key` header:
@@ -21,38 +21,72 @@ works on offline factory networks.
21
21
 
22
22
  ---
23
23
 
24
- ## ✨ What's new in 2.4
25
-
26
- - **No configuration needed to start.** `monitor-app serve ./my-csv-folder` (or
27
- `uvx monitor-app serve ...` without installing) infers column types, keys, charts and
28
- KPI cards from your CSV files. Keep the result with `startproject --from-csv`.
29
- - **Much faster.** SQLite runs in WAL mode, CSV files are imported as a stream (1 million
30
- rows: about 1.3× faster, 4.7 s instead of 6.0 s on the test PC, with a constant
31
- memory use of about 60 MB instead of 580 MB), folder watching in append mode reads
32
- only the new rows (10 rows appended to a 1-million-row file: about 1 ms, was 3 s), and
33
- 30 simultaneous requests for the same 10,000-row view take about 40 ms instead of
34
- about 40 s because the query runs once and is shared. Unchanged data is answered with
35
- `304`, and responses are gzip-compressed.
36
- - **Excel-friendly import.** Shift_JIS/cp932 CSV files from Excel are detected
37
- automatically, Excel dates such as `2026/10/21 8:00` are understood, header spelling
38
- variants are matched, and exports can carry a BOM for Excel (`?bom=1`).
39
- - **Safe by default.** `runserver --import-csv-if-empty` keeps rows entered in the UI
40
- across restarts, an import that would wipe a column is refused, and `monitor-app check`
41
- finds typos in `config.py` and `.env`, broken SQL and alert columns that never match.
42
- `monitor-app deploy` writes a random API key into `.env`, and a server that listens on
43
- this PC only refuses requests for foreign host names (DNS rebinding).
44
- - **Docker in one line.** `docker run -p 9990:9990 ghcr.io/mikawa-bushi/monitor-app`
45
- (linux/amd64 and linux/arm64). Mount your project folder as is: the container runs as
46
- the folder's owner.
47
- - **`monitor-app deploy`** writes the start-up files for Docker Compose, Linux/Raspberry
48
- Pi (systemd) and Windows (Task Scheduler), plus an API key.
49
- - **Backups and retention.** `monitor-app backup` / `restore` (also for a damaged
50
- database), scheduled backups, `Retention(keep_days=...)` and `monitor-app migrate`
51
- for columns you add later.
52
- - **Reverse proxies.** Serve under a sub-path such as `/monitor/` (`MONITOR_ROOT_PATH`),
53
- monitor `/api/health` (checks the database), JSON logs.
24
+ ## 👀 See it first: a warehouse inventory demo
25
+
26
+ Open the inventory screen of a fictional distribution center, with nothing to prepare:
27
+
28
+ ```sh
29
+ uvx monitor-app demo --open # if installed: monitor-app demo --open
30
+ docker run --rm -p 9990:9990 ghcr.io/mikawa-bushi/monitor-app # with Docker (mount nothing)
31
+ ```
32
+
33
+ Open http://127.0.0.1:9990 (with Docker, http://localhost:9990). What to look at:
34
+
35
+ - **Stock list**: location, lot, best-before date, days left, cases and pieces. Stock is
36
+ the SQL sum of a ledger of stock movements.
37
+ - **It moves on its own**: every 4 seconds a receipt, shipment or move happens, and the
38
+ movement history, KPI cards and charts update by themselves.
39
+ - **Alerts**: right after start, banners show an item below safety stock and lots within
40
+ 2 days of their best-before date. About 90 seconds later a delivery arrives and the
41
+ safety-stock banner clears.
42
+ - **Other screens**: `/kiosk` (wallboard) and `/form/movements` (entry form). When the
43
+ demo runs on your own PC, `/settings` (edit settings in the browser) works too.
44
+
45
+ The data lives in a temporary database that is deleted on exit. At start the demo
46
+ prints where its `config.py` is. Docker shows times in UTC; add `-e TZ=Asia/Tokyo` (or
47
+ your zone) for local time.
48
+
49
+ To start your own project with the same configuration and fictional data:
50
+
51
+ ```sh
52
+ monitor-app startproject my-warehouse --template warehouse
53
+ cd my-warehouse
54
+ monitor-app runserver --import-csv-if-empty
55
+ ```
56
+
57
+ In a project, movements do not happen on their own. The project's `README.md` explains
58
+ how to replace the data with your own.
59
+
60
+ ## ✨ What's new in 2.5
61
+
62
+ - **A demo with nothing to prepare.** `monitor-app demo` opens the inventory screen of a
63
+ fictional distribution center. Every 4 seconds a receipt, shipment or move happens, so
64
+ the KPI cards, charts and alerts change on their own. The data lives in a temporary
65
+ SQLite database that is deleted on exit. Docker started with nothing mounted now runs
66
+ this demo instead of the sample project. Mount an empty folder to get the sample
67
+ project as before.
68
+ - **Start a project from the demo.** `monitor-app startproject NAME --template warehouse`
69
+ writes the demo's `config.py`, master data and 7 days of fictional data.
70
+ - **Settings page.** `/settings` edits the display options, views (SQL, column labels,
71
+ cell styles, charts), KPI cards, alerts, the wallboard and the dashboard without
72
+ editing `config.py`. A save runs the same checks as `monitor-app check`, then applies
73
+ at once without a restart. Only the difference from `config.py` is stored, in the
74
+ database.
75
+ - **CSV upload.** `/upload` imports a CSV file into a table from the browser, with the
76
+ same code as `monitor-app import-csv`. Choose append or replace.
77
+ - **`MONITOR_ADMIN_KEY`.** A key for the settings page and CSV upload only. When it is
78
+ set, operators who log in with `MONITOR_API_KEY` cannot change the settings. Without
79
+ it, `MONITOR_API_KEY` works, and with neither key the admin features work only from
80
+ this PC.
81
+ - **Named entry forms.** `FormDef(title="...")` sets the form's heading, its browser tab
82
+ title and its link on the home page.
83
+ - **Security.** The admin features refuse requests sent from pages on other sites
84
+ (CSRF). SQL saved from the browser must be one statement with no `;`, and a KPI number
85
+ format can only format the value. The name of an uploaded file loses its folder and
86
+ drive parts on every OS, so it cannot point outside the temporary folder.
54
87
 
55
88
  Full list: [CHANGELOG.md](https://github.com/mikawa-bushi/monitor-app/blob/main/CHANGELOG.md).
89
+ Coming from 2.4? Nothing needs changing beyond the **Upgrade notes** in the CHANGELOG.
56
90
  Coming from 2.3? Read **Upgrading from 2.3.x** below.
57
91
 
58
92
  ## Features
@@ -67,6 +101,7 @@ Coming from 2.3? Read **Upgrading from 2.3.x** below.
67
101
  | Trend / SPC charts | Line and bar charts with UCL / LCL / target lines | `ViewDef(chart=ChartDef(...))` |
68
102
  | Threshold alerts | Banner, browser notification, webhook (Slack, Teams), e-mail; notified once per change, retried on failure | `alerts=[AlertRule(...)]` |
69
103
  | Operator entry forms | Touch-friendly forms generated from the table definition | `TableDef(form=FormDef(...))`, `/form/{table}` |
104
+ | Settings and CSV upload in the browser | Edit display options, views (SQL), cell styles, charts, KPIs, alerts and the wallboard in the browser; applied without a restart, and open pages reload themselves. Upload CSV files into tables | `/settings`, `/upload` |
70
105
  | Data input | CSV import (UTF-8 or Excel cp932), folder watching (replace or append), bulk `POST` for PLC/MES/sensors, read-only `sources` (SQLite/JSONL/JSON) | `import-csv`, `MONITOR_INGEST_WATCH`, `/api/ingest/{table}` |
71
106
  | REST API | CRUD per table, view data, CSV/Excel export, OpenAPI docs | `/api/...`, `/docs`, `/redoc` |
72
107
  | Python API | Build the app from your own code, load and validate `config.py` | `create_app`, `load_config` (see **Python API**) |
@@ -264,8 +299,9 @@ Note that `--keep` adds back rows you deleted in the UI if the CSV file still co
264
299
 
265
300
  | Command | What it does | Main options |
266
301
  |---|---|---|
302
+ | `monitor-app demo` | Warehouse inventory demo, nothing to prepare (fictional data, deleted on exit) | `--host`, `--port`, `--open` |
267
303
  | `monitor-app serve [PATH...]` | Dashboard straight from CSV files, no `config.py` | `--host`, `--port`, `--db FILE`, `--append`, `--no-watch`, `--write-config FILE`, `--open` |
268
- | `monitor-app startproject NAME` | Create a project folder | `--from-csv DIR`, `--deploy docker,systemd,windows` (or `all`) |
304
+ | `monitor-app startproject NAME` | Create a project folder | `--template warehouse` (the demo's configuration and data), `--from-csv DIR`, `--deploy docker,systemd,windows` (or `all`) |
269
305
  | `monitor-app runserver` | Start the web server | `--import-csv-if-empty`, `--import-csv`, `--force`, `--host`, `--port`, `--reload`, `--config` |
270
306
  | `monitor-app check` | Check `config.py`, SQL, settings and the database tables without starting | `--data` (also the CSV files), `--strict` (warnings fail, for CI), `--fix-schema`, `--config` |
271
307
  | `monitor-app import-csv` | Import `csv/` into the database (replace) | `--keep`, `--if-empty`, `--force`, `--config` |
@@ -308,7 +344,8 @@ config = MonitorConfig(
308
344
  primary_key="id",
309
345
  indexes=[["ts"]], # speeds up ORDER BY / WHERE ts
310
346
  retention=Retention(keep_days=90, column="ts"), # delete rows older than 90 days
311
- form=FormDef(labels={"temp": "Temperature (°C)"},
347
+ form=FormDef(title="Enter readings",
348
+ labels={"temp": "Temperature (°C)"},
312
349
  choices={"line": ["L1", "L2"]}), # entry form at /form/readings
313
350
  ),
314
351
  },
@@ -394,6 +431,58 @@ MySQL `MONITOR_DATABASE_URL`, values those databases would reject.
394
431
  The legacy v1 dict-style `config.py` (`ALLOWED_TABLES` ...) still loads; see
395
432
  **Migrating from v0.x** below.
396
433
 
434
+ ## 🖱 Change settings and upload CSV files in the browser
435
+
436
+ The **⚙️ Settings** page (`/settings`) changes the following without editing
437
+ `config.py`. Saving applies the change at once, without restarting the server,
438
+ and open pages (the wallboard too) reload themselves.
439
+
440
+ | Tab | What you can change |
441
+ |---|---|
442
+ | General | App name, header and footer text, refresh interval and mode |
443
+ | Views | Title, description, group, row limit, SQL, column labels, cell styles, chart (control and target lines); add and delete views |
444
+ | KPIs | Title, SQL, unit, target, number format, linked view; add and delete KPIs |
445
+ | Alerts | View, column, condition, value, level, message, channels; add and delete alerts |
446
+ | Wallboard and dashboard | Wallboard rotation, theme and views; how many charts per row on the home page and which ones |
447
+
448
+ - Tables, `sources` and the favicon stay in `config.py` (changing them would
449
+ rebuild tables or read files on the server).
450
+ - Only the difference from `config.py` is stored, in the database (internal
451
+ table `_settings`), so it survives restarts and is part of SQLite backups.
452
+ Fields changed in the browser win over the same fields in `config.py`; the
453
+ rest still come from `config.py` (a list such as the alerts is replaced as a
454
+ whole). **Return to config.py** deletes the stored difference.
455
+ - Before saving, the same checks as `monitor-app check` run (dry-run of the
456
+ SQL, alert and chart columns, channels); errors block the save. **Check**
457
+ runs them without saving. SQL entered in the browser must be a single
458
+ `SELECT` with no `;` (not even inside quotes or comments; a trailing one is
459
+ fine) and runs read-only. Put SQL that needs `;` in `config.py`.
460
+ - If someone else saved in the meantime, the save is refused and the page asks
461
+ you to reload. With `MONITOR_AUDIT_ENABLED=true` changes go to the audit log
462
+ (`/api/audit?table=_settings`).
463
+ - If the stored difference no longer fits `config.py` (for example a view was
464
+ removed there), the server starts with `config.py` alone and the settings page
465
+ says so.
466
+
467
+ **📤 Upload CSV** (`/upload`) imports a CSV file into a table with the same code
468
+ as `monitor-app import-csv`: encoding detection (UTF-8, Excel's Shift_JIS),
469
+ header variants, one transaction (nothing changes on failure) and refusing to
470
+ replace a table from a CSV that lacks its columns. Choose **append** (rows whose
471
+ key exists are skipped) or **replace** (all rows become the CSV's rows). The
472
+ uploaded file is not kept on the server.
473
+
474
+ **Who can use them.** Settings and CSV upload are *admin features*:
475
+
476
+ | Configuration | Who can use the admin features |
477
+ |---|---|
478
+ | `MONITOR_ADMIN_KEY` set | Whoever logs in with the admin key (or sends it as `X-API-Key`). Holders of `MONITOR_API_KEY` cannot |
479
+ | Only `MONITOR_API_KEY` | Whoever logs in with the API key (same as writes) |
480
+ | Neither | Only this PC (`MONITOR_HOST=127.0.0.1` and no reverse-proxy settings). Disabled when the server is exposed on the LAN or behind a proxy |
481
+
482
+ Requests sent from pages on other sites (CSRF) are refused. If operators log in
483
+ with the API key to use the entry forms, set a separate `MONITOR_ADMIN_KEY` so
484
+ they cannot change the settings.
485
+
397
486
  ## ⚙️ Settings (`MONITOR_*` / `.env`)
398
487
 
399
488
  Settings that differ per PC (database URL, port, passwords) do not belong in `config.py`.
@@ -419,6 +508,7 @@ setting) or set them as environment variables, which win over `.env`.
419
508
  | **Security** | | |
420
509
  | `MONITOR_API_KEY` | (unset) | Turns on authentication: writes (API, forms, ingest) need the key — `X-API-Key` header, or a login in the browser (the session lasts 12 hours). Use 16 or more random characters; `monitor-app deploy` writes one into `.env`. An empty value counts as unset. |
421
510
  | `MONITOR_PROTECT_READS` | `false` | Require the key for reading too (pages, read APIs and the API documentation). |
511
+ | `MONITOR_ADMIN_KEY` | (unset) | Key for the admin features only (settings page, CSV upload). When set, they accept this key and nothing else; logging in with it also allows reading and writing. Unset: `MONITOR_API_KEY` works. See **Change settings and upload CSV files in the browser** above. |
422
512
  | `MONITOR_ALLOWED_HOSTS` | (unset) | Host names the server answers to (comma list; `*.example.com` matches subdomains, `*` turns the check off). Unset: a server that listens on `127.0.0.1` refuses host names other than `localhost`, its own names and IP addresses (DNS-rebinding protection); LAN, Docker and the reverse-proxy setups below are not checked. A list is enforced on every interface. |
423
513
  | `MONITOR_CORS_ORIGINS` | `[]` | Other web origins allowed to call the API, as JSON: `["http://localhost:3000"]`. Empty = same origin only. |
424
514
  | **CSV import** | | |
@@ -560,7 +650,7 @@ delays the shutdown until the container is killed.
560
650
  ### Docker
561
651
 
562
652
  ```sh
563
- docker run --rm -p 9990:9990 ghcr.io/mikawa-bushi/monitor-app # demo, no files needed
653
+ docker run --rm -p 9990:9990 ghcr.io/mikawa-bushi/monitor-app # warehouse demo, no files needed
564
654
  ```
565
655
 
566
656
  Run your own project (the folder with `config.py`):
@@ -574,7 +664,8 @@ docker run -d --name monitor-app -p 9990:9990 -v "$PWD":/project \
574
664
  (Windows PowerShell: `-v "${PWD}:/project"`.) The container switches to the owner of the
575
665
  mounted folder and drops all Linux capabilities, so the database and backups belong to
576
666
  you on the host whatever your user id is. It starts `runserver --import-csv-if-empty`, so
577
- rows entered in the UI survive updates. An empty folder gets the sample project.
667
+ rows entered in the UI survive updates. An empty folder gets the sample project. With
668
+ nothing mounted, the warehouse demo above runs on a temporary database.
578
669
  For a hardened container add `--cap-drop ALL --cap-add SETUID --cap-add SETGID
579
670
  --cap-add CHOWN --security-opt no-new-privileges` (the generated `compose.yaml` sets
580
671
  these). `docker run --rm ghcr.io/mikawa-bushi/monitor-app --version` prints the version.
@@ -594,13 +685,13 @@ docker compose pull && docker compose up -d # update to the newest image
594
685
  deleting rows needs the key (log in on the page, or send `X-API-Key`).
595
686
  - `.env` is read by the app inside the container as usual. The generated `compose.yaml`
596
687
  also takes `MONITOR_PORT` (port on the host), `TZ` (for example `TZ=Asia/Tokyo`),
597
- `MONITOR_IMAGE` (image name) and `MONITOR_IMAGE_TAG` (for example `2.4.0` to stay on a
688
+ `MONITOR_IMAGE` (image name) and `MONITOR_IMAGE_TAG` (for example `2.5.0` to stay on a
598
689
  version) from `.env`.
599
690
  - Other commands run the same way as the folder's owner:
600
691
  `docker compose run --rm monitor-app check`, `... backup`, `... import-csv`. Do not use
601
692
  `docker compose exec monitor-app monitor-app backup`: that runs as root and the backup
602
693
  file can then only be read by root.
603
- - Image tags: `latest`, `2`, `2.4`, `2.4.0`; platforms `linux/amd64` and `linux/arm64`
694
+ - Image tags: `latest`, `2`, `2.5`, `2.5.0`; platforms `linux/amd64` and `linux/arm64`
604
695
  (64-bit Raspberry Pi OS). The image has a health check (`/api/health`) and stops within
605
696
  about a second.
606
697
  - On Docker Desktop (Windows, macOS) keep `instances/` in a named volume, for speed and
@@ -741,7 +832,7 @@ curl http://127.0.0.1:9990/api/health
741
832
  ```
742
833
 
743
834
  ```json
744
- {"status":"ok","version":"2.4.0","database":"ok","tasks":{"alert scheduler":{"running":true,"last_tick":"2026-10-21T17:30:44+09:00","last_error":null}}}
835
+ {"status":"ok","version":"2.5.0","database":"ok","tasks":{"alert scheduler":{"running":true,"last_tick":"2026-10-21T17:30:44+09:00","last_error":null}}}
745
836
  ```
746
837
 
747
838
  It answers `200` when the database responds to `SELECT 1` and `503` (with
@@ -757,7 +848,9 @@ It answers `200` when the database responds to `SELECT 1` and `503` (with
757
848
  | `/table/{view}` | Live table / chart page |
758
849
  | `/kiosk` | Andon wallboard (full screen, rotating) |
759
850
  | `/form/{table}` | Operator entry form |
760
- | `/login` | Login page (used when `MONITOR_API_KEY` is set) |
851
+ | `/settings` | Settings page (admin feature) |
852
+ | `/upload` | CSV upload (admin feature) |
853
+ | `/login` | Login page (used when `MONITOR_API_KEY` or `MONITOR_ADMIN_KEY` is set) |
761
854
  | `/docs`, `/redoc` | OpenAPI documentation (these two pages load pinned Swagger UI / ReDoc versions from a CDN; login needed with `MONITOR_PROTECT_READS`; off with `MONITOR_API_DOCS=false`) |
762
855
  | `GET/POST /api/tables/{table}` | List rows (`?limit=`, `?offset=`; pages are ordered by primary key) / add a row |
763
856
  | `GET/PUT/DELETE /api/tables/{table}/{id}` | One row |
@@ -768,6 +861,9 @@ It answers `200` when the database responds to `SELECT 1` and `503` (with
768
861
  | `GET /api/views/{view}/export` | `?format=csv` or `xlsx`; `?bom=1` adds a UTF-8 BOM so Excel shows Japanese text correctly. Text that starts with `=` `+` `-` `@` is exported so that spreadsheets do not run it as a formula; `?raw=1` exports the values exactly as stored |
769
862
  | `GET /api/kpis`, `/api/alerts`, `/api/schema` | KPI values, active alerts, table definitions |
770
863
  | `GET /api/audit` | Change history (`?table=`, `?limit=`) when `MONITOR_AUDIT_ENABLED=true` |
864
+ | `GET/PUT/DELETE /api/settings` | Settings editable in the browser / check and save (`{"settings": {...}, "revision": "..."}`; errors → `422`, saved by someone else first → `409`) / return to `config.py`. Admin feature |
865
+ | `POST /api/settings/check` | Check without saving (same rules as `monitor-app check`). Admin feature |
866
+ | `POST /api/upload/{table}` | CSV upload (multipart `file`, `mode=append` or `replace`, `force`). Admin feature |
771
867
  | `GET/HEAD /api/health` | Health check (see above) |
772
868
 
773
869
  With `MONITOR_API_KEY` set, send the key in the `X-API-Key` header: