cancan-microstack 0.0.1__py3-none-any.whl

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 (440) hide show
  1. cancan_microstack/__init__.py +14 -0
  2. cancan_microstack/__version__.py +10 -0
  3. cancan_microstack/assets/__init__.py +6 -0
  4. cancan_microstack/assets/builds/caddy/Caddyfile +187 -0
  5. cancan_microstack/assets/builds/caddy/DEPLOYMENT.md +303 -0
  6. cancan_microstack/assets/builds/caddy/Dockerfile +46 -0
  7. cancan_microstack/assets/builds/caddy/README.md +343 -0
  8. cancan_microstack/assets/builds/caddy/geoip/README.md +5 -0
  9. cancan_microstack/assets/builds/caddy/start.sh +78 -0
  10. cancan_microstack/assets/builds/caddy/waf/coraza.conf +179 -0
  11. cancan_microstack/assets/builds/service/Dockerfile +59 -0
  12. cancan_microstack/assets/builds/service/README.md +13 -0
  13. cancan_microstack/assets/ddl/create_db.sql +22 -0
  14. cancan_microstack/assets/ddl/infra/execution_log_tbl.sql +46 -0
  15. cancan_microstack/assets/ddl/infra/node_instance_tbl.sql +56 -0
  16. cancan_microstack/assets/ddl/infra/service_action_log_tbl.sql +36 -0
  17. cancan_microstack/assets/ddl/infra/service_config_tbl.sql +26 -0
  18. cancan_microstack/assets/ddl/infra/service_info_tbl.sql +45 -0
  19. cancan_microstack/assets/ddl/infra/service_instance_tbl.sql +54 -0
  20. cancan_microstack/assets/ddl/infra/service_operation_tbl.sql +47 -0
  21. cancan_microstack/assets/ddl/infra/workflow_definition_tbl.sql +60 -0
  22. cancan_microstack/assets/ddl/infra/workflow_definition_version_tbl.sql +35 -0
  23. cancan_microstack/assets/ddl/infra/workflow_engine_alert_tbl.sql +34 -0
  24. cancan_microstack/assets/ddl/infra/workflow_run_tbl.sql +52 -0
  25. cancan_microstack/assets/ddl/ops/admin_user_tbl.sql +34 -0
  26. cancan_microstack/assets/ddl/ops/caddy_access_log_tbl.sql +91 -0
  27. cancan_microstack/assets/ddl/ops/caddy_certificate_tbl.sql +59 -0
  28. cancan_microstack/assets/ddl/ops/caddy_rate_limit_tbl.sql +64 -0
  29. cancan_microstack/assets/ddl/ops/caddy_route_tbl.sql +63 -0
  30. cancan_microstack/assets/ddl/ops/caddy_stats_tbl.sql +77 -0
  31. cancan_microstack/assets/ddl/trigger.sql +21 -0
  32. cancan_microstack/assets/docker/docker-compose.infra.yml +401 -0
  33. cancan_microstack/assets/scripts/README.md +195 -0
  34. cancan_microstack/assets/scripts/docker/build_images.sh +44 -0
  35. cancan_microstack/assets/scripts/docker/force_rebuild_images.sh +38 -0
  36. cancan_microstack/assets/scripts/docker/rebuild_all.sh +34 -0
  37. cancan_microstack/assets/scripts/docker/rebuild_compose.sh +61 -0
  38. cancan_microstack/assets/scripts/docker/restart.sh +35 -0
  39. cancan_microstack/assets/scripts/docker/restart_compose.sh +35 -0
  40. cancan_microstack/assets/scripts/docker/start.sh +78 -0
  41. cancan_microstack/assets/scripts/docker/start_all.sh +46 -0
  42. cancan_microstack/assets/scripts/docker/start_compose.sh +66 -0
  43. cancan_microstack/assets/scripts/docker/stop.sh +67 -0
  44. cancan_microstack/assets/scripts/docker/stop_all.sh +38 -0
  45. cancan_microstack/assets/scripts/docker/stop_compose.sh +38 -0
  46. cancan_microstack/assets/scripts/podman/build_images_podman.sh +59 -0
  47. cancan_microstack/assets/scripts/podman/cleanup_podman.sh +25 -0
  48. cancan_microstack/assets/scripts/podman/force_rebuild_images_podman.sh +56 -0
  49. cancan_microstack/assets/scripts/podman/rebuild_all_podman.sh +37 -0
  50. cancan_microstack/assets/scripts/podman/rebuild_compose_podman.sh +60 -0
  51. cancan_microstack/assets/scripts/podman/restart_compose_podman.sh +73 -0
  52. cancan_microstack/assets/scripts/podman/start_all_podman.sh +66 -0
  53. cancan_microstack/assets/scripts/podman/start_compose_podman.sh +80 -0
  54. cancan_microstack/assets/scripts/podman/start_podman.sh +91 -0
  55. cancan_microstack/assets/scripts/podman/stop.sh +73 -0
  56. cancan_microstack/assets/scripts/podman/stop_all_podman.sh +34 -0
  57. cancan_microstack/assets/scripts/podman/stop_compose_podman.sh +58 -0
  58. cancan_microstack/assets/scripts/start_controllersrv.sh +9 -0
  59. cancan_microstack/assets/scripts/utils/check_all_db_tables.sh +104 -0
  60. cancan_microstack/assets/scripts/utils/check_env.sh +177 -0
  61. cancan_microstack/assets/scripts/utils/check_service_management_deployment.sh +225 -0
  62. cancan_microstack/assets/scripts/utils/deploy_service_management.sh +176 -0
  63. cancan_microstack/assets/scripts/utils/force_reload_infrasrv.sh +52 -0
  64. cancan_microstack/assets/scripts/utils/monitor_service_management.sh +187 -0
  65. cancan_microstack/assets/scripts/utils/reset_postgres_volume.sh +68 -0
  66. cancan_microstack/assets/scripts/utils/test_async_operations.sh +141 -0
  67. cancan_microstack/assets/scripts/utils/verify_real_operations.sh +76 -0
  68. cancan_microstack/assets/service/Dockerfile +65 -0
  69. cancan_microstack/assets/www/adminops/assets/AppEmpty.vue_vue_type_script_setup_true_lang-BOKUurnM.js +1 -0
  70. cancan_microstack/assets/www/adminops/assets/ConfigManage-DKV5YOUz.js +1 -0
  71. cancan_microstack/assets/www/adminops/assets/ConfigManage-Y5bhy7wG.css +1 -0
  72. cancan_microstack/assets/www/adminops/assets/ConsoleManage-8ljYvCW2.js +1 -0
  73. cancan_microstack/assets/www/adminops/assets/ConsoleManage-BWpyqbuQ.css +1 -0
  74. cancan_microstack/assets/www/adminops/assets/DashboardNew-B9Nf1OPl.js +1 -0
  75. cancan_microstack/assets/www/adminops/assets/DashboardNew-DYWZKQ1V.css +1 -0
  76. cancan_microstack/assets/www/adminops/assets/LogSearch-CA0Jhe78.js +1 -0
  77. cancan_microstack/assets/www/adminops/assets/LogSearch-CCZfTNPF.css +1 -0
  78. cancan_microstack/assets/www/adminops/assets/LoginView-BId3kP3M.css +1 -0
  79. cancan_microstack/assets/www/adminops/assets/LoginView-BQZTV_Qy.js +1 -0
  80. cancan_microstack/assets/www/adminops/assets/OperationProgressDialog-BdEYwqFq.js +1 -0
  81. cancan_microstack/assets/www/adminops/assets/OperationProgressDialog-D-pASR8G.css +1 -0
  82. cancan_microstack/assets/www/adminops/assets/PageContainer-Byss-yUC.js +1 -0
  83. cancan_microstack/assets/www/adminops/assets/PageContainer-C3nSZwM7.css +1 -0
  84. cancan_microstack/assets/www/adminops/assets/RateLimitManage-BDI8jLpC.css +1 -0
  85. cancan_microstack/assets/www/adminops/assets/RateLimitManage-DJY4NiF-.js +1 -0
  86. cancan_microstack/assets/www/adminops/assets/RouteManage-DaUQ4QLw.css +1 -0
  87. cancan_microstack/assets/www/adminops/assets/RouteManage-w9XCU0UA.js +1 -0
  88. cancan_microstack/assets/www/adminops/assets/ServiceCard-BFzHe6Tw.css +1 -0
  89. cancan_microstack/assets/www/adminops/assets/ServiceCard-BJUhWnA-.js +1 -0
  90. cancan_microstack/assets/www/adminops/assets/ServiceDetail-Cw24WuKp.js +1 -0
  91. cancan_microstack/assets/www/adminops/assets/ServiceDetail-Yum47zdB.css +1 -0
  92. cancan_microstack/assets/www/adminops/assets/ServiceList-C7ryvbhE.js +1 -0
  93. cancan_microstack/assets/www/adminops/assets/ServiceList-Cgd01fUx.css +1 -0
  94. cancan_microstack/assets/www/adminops/assets/ServiceLogs-COpG9H0h.js +1 -0
  95. cancan_microstack/assets/www/adminops/assets/ServiceLogs-H_Alq0cf.css +1 -0
  96. cancan_microstack/assets/www/adminops/assets/StatsOverview-D0TwMQkA.js +39 -0
  97. cancan_microstack/assets/www/adminops/assets/StatsOverview-lqAN6pqM.css +1 -0
  98. cancan_microstack/assets/www/adminops/assets/TotpBindView-CWlAmzFt.js +1 -0
  99. cancan_microstack/assets/www/adminops/assets/TotpBindView-HoQC1lhx.css +1 -0
  100. cancan_microstack/assets/www/adminops/assets/TotpVerifyView-BHN1VtX1.css +1 -0
  101. cancan_microstack/assets/www/adminops/assets/TotpVerifyView-D3w_lZk8.js +1 -0
  102. cancan_microstack/assets/www/adminops/assets/WorkflowCenter-DU_mpIA0.css +1 -0
  103. cancan_microstack/assets/www/adminops/assets/WorkflowCenter-i50rZyxN.js +1 -0
  104. cancan_microstack/assets/www/adminops/assets/WorkflowDesigner-CnHokPL9.js +1 -0
  105. cancan_microstack/assets/www/adminops/assets/WorkflowDesigner-DaZaZpLd.css +1 -0
  106. cancan_microstack/assets/www/adminops/assets/WorkflowRuns-B09hK48c.js +1 -0
  107. cancan_microstack/assets/www/adminops/assets/WorkflowRuns-wGutKIIU.css +1 -0
  108. cancan_microstack/assets/www/adminops/assets/caddy-nnCKf8fG.js +1 -0
  109. cancan_microstack/assets/www/adminops/assets/format-Cuzxgna9.js +1 -0
  110. cancan_microstack/assets/www/adminops/assets/index-CiFlm8oc.js +64 -0
  111. cancan_microstack/assets/www/adminops/assets/index-UW0T1Dkc.css +1 -0
  112. cancan_microstack/assets/www/adminops/assets/service-BYlgGPs_.js +1 -0
  113. cancan_microstack/assets/www/adminops/assets/service-operation-6GzLw2Z1.js +1 -0
  114. cancan_microstack/assets/www/adminops/assets/style-CcIXnQ5y.css +1 -0
  115. cancan_microstack/assets/www/adminops/assets/style-lRnStdGu.js +39 -0
  116. cancan_microstack/assets/www/adminops/assets/useDebounce-BRlqfXqf.js +1 -0
  117. cancan_microstack/assets/www/adminops/assets/workflow-CUXs39Ac.js +1 -0
  118. cancan_microstack/assets/www/adminops/index.html +16 -0
  119. cancan_microstack/assets/www/adminops/vite.svg +1 -0
  120. cancan_microstack/cli/__init__.py +14 -0
  121. cancan_microstack/cli/__main__.py +9 -0
  122. cancan_microstack/cli/main.py +552 -0
  123. cancan_microstack/cmd/__init__.py +54 -0
  124. cancan_microstack/cmd/cancan/__init__.py +12 -0
  125. cancan_microstack/cmd/cancan/run.py +395 -0
  126. cancan_microstack/cmd/controllersrv/__init__.py +0 -0
  127. cancan_microstack/cmd/controllersrv/run.py +131 -0
  128. cancan_microstack/cmd/infrasrv/__init__.py +5 -0
  129. cancan_microstack/cmd/infrasrv/run.py +100 -0
  130. cancan_microstack/cmd/opsbffsrv/__init__.py +5 -0
  131. cancan_microstack/cmd/opsbffsrv/run.py +96 -0
  132. cancan_microstack/core/__init__.py +5 -0
  133. cancan_microstack/core/assets.py +123 -0
  134. cancan_microstack/core/compose_builder.py +102 -0
  135. cancan_microstack/core/doctor.py +152 -0
  136. cancan_microstack/core/microstack.py +71 -0
  137. cancan_microstack/core/runner.py +56 -0
  138. cancan_microstack/core/stack_manager.py +186 -0
  139. cancan_microstack/public/__init__.py +7 -0
  140. cancan_microstack/public/api/__init__.py +1 -0
  141. cancan_microstack/public/api/controllersrv_client.py +277 -0
  142. cancan_microstack/public/api/infrasrv_client.py +404 -0
  143. cancan_microstack/public/const/__init__.py +1 -0
  144. cancan_microstack/public/const/action_consts.py +18 -0
  145. cancan_microstack/public/const/app_consts.py +42 -0
  146. cancan_microstack/public/const/caddy_consts.py +22 -0
  147. cancan_microstack/public/const/controllersrv_consts.py +163 -0
  148. cancan_microstack/public/const/docker_consts.py +15 -0
  149. cancan_microstack/public/const/error.py +56 -0
  150. cancan_microstack/public/const/health_consts.py +52 -0
  151. cancan_microstack/public/const/hook_enums.py +56 -0
  152. cancan_microstack/public/const/logging_enums.py +13 -0
  153. cancan_microstack/public/const/metrics_enums.py +36 -0
  154. cancan_microstack/public/const/monitor_enums.py +26 -0
  155. cancan_microstack/public/const/operation_consts.py +53 -0
  156. cancan_microstack/public/const/opsbffsrv_error.py +92 -0
  157. cancan_microstack/public/const/overrides_consts.py +13 -0
  158. cancan_microstack/public/const/redis.py +17 -0
  159. cancan_microstack/public/const/service_consts.py +15 -0
  160. cancan_microstack/public/const/workflow_consts.py +65 -0
  161. cancan_microstack/public/error.py +41 -0
  162. cancan_microstack/public/logging/__init__.py +0 -0
  163. cancan_microstack/public/logging/initializer.py +109 -0
  164. cancan_microstack/public/logging/mq_handler.py +279 -0
  165. cancan_microstack/public/schemas/__init__.py +1 -0
  166. cancan_microstack/public/schemas/caddy/__init__.py +381 -0
  167. cancan_microstack/public/schemas/caddy/analysis.py +90 -0
  168. cancan_microstack/public/schemas/caddy/route.py +18 -0
  169. cancan_microstack/public/schemas/common.py +79 -0
  170. cancan_microstack/public/schemas/controllersrv/__init__.py +3 -0
  171. cancan_microstack/public/schemas/controllersrv/async_requests.py +30 -0
  172. cancan_microstack/public/schemas/controllersrv/compose_models.py +47 -0
  173. cancan_microstack/public/schemas/controllersrv/const.py +24 -0
  174. cancan_microstack/public/schemas/controllersrv/docker_models.py +45 -0
  175. cancan_microstack/public/schemas/controllersrv/docker_responses.py +104 -0
  176. cancan_microstack/public/schemas/controllersrv/requests.py +54 -0
  177. cancan_microstack/public/schemas/controllersrv/responses.py +124 -0
  178. cancan_microstack/public/schemas/controllersrv/task_models.py +102 -0
  179. cancan_microstack/public/schemas/controllersrv/validation.py +23 -0
  180. cancan_microstack/public/schemas/hook_metrics.py +124 -0
  181. cancan_microstack/public/schemas/hooks.py +39 -0
  182. cancan_microstack/public/schemas/infra/__init__.py +0 -0
  183. cancan_microstack/public/schemas/infra/cleanup.py +25 -0
  184. cancan_microstack/public/schemas/infra/container.py +74 -0
  185. cancan_microstack/public/schemas/infra/enums.py +135 -0
  186. cancan_microstack/public/schemas/infra/health_check.py +42 -0
  187. cancan_microstack/public/schemas/infra/hook_log.py +42 -0
  188. cancan_microstack/public/schemas/infra/operation.py +90 -0
  189. cancan_microstack/public/schemas/infra/overview.py +25 -0
  190. cancan_microstack/public/schemas/infra/push.py +33 -0
  191. cancan_microstack/public/schemas/infra/service_action_log.py +47 -0
  192. cancan_microstack/public/schemas/infra/service_config.py +10 -0
  193. cancan_microstack/public/schemas/infra/service_info.py +69 -0
  194. cancan_microstack/public/schemas/infra/service_instance.py +93 -0
  195. cancan_microstack/public/schemas/infra/service_management.py +152 -0
  196. cancan_microstack/public/schemas/infra/service_operation.py +79 -0
  197. cancan_microstack/public/schemas/infra/service_registry.py +158 -0
  198. cancan_microstack/public/schemas/infra/status_types.py +19 -0
  199. cancan_microstack/public/schemas/infra/workflow.py +566 -0
  200. cancan_microstack/public/schemas/logging/__init__.py +1 -0
  201. cancan_microstack/public/schemas/logging/log_event.py +121 -0
  202. cancan_microstack/public/schemas/opsbffsrv/__init__.py +1 -0
  203. cancan_microstack/public/schemas/opsbffsrv/async_ops.py +17 -0
  204. cancan_microstack/public/schemas/opsbffsrv/db_admin.py +147 -0
  205. cancan_microstack/public/schemas/opsbffsrv/db_init.py +48 -0
  206. cancan_microstack/public/schemas/opsbffsrv/service_config.py +89 -0
  207. cancan_microstack/public/schemas/opsbffsrv/service_logs.py +54 -0
  208. cancan_microstack/public/schemas/service_operation.py +24 -0
  209. cancan_microstack/public/schemas/service_registry.py +40 -0
  210. cancan_microstack/public/types/__init__.py +7 -0
  211. cancan_microstack/public/web/__init__.py +0 -0
  212. cancan_microstack/public/web/config_value.py +105 -0
  213. cancan_microstack/public/web/server.py +385 -0
  214. cancan_microstack/py.typed +0 -0
  215. cancan_microstack/runtime/__init__.py +0 -0
  216. cancan_microstack/runtime/compose_cmd.py +228 -0
  217. cancan_microstack/runtime/host_daemon.py +318 -0
  218. cancan_microstack/runtime/overrides.py +103 -0
  219. cancan_microstack/runtime/resources.py +25 -0
  220. cancan_microstack/runtime/workspace.py +94 -0
  221. cancan_microstack/services/__init__.py +0 -0
  222. cancan_microstack/services/controllersrv/__init__.py +8 -0
  223. cancan_microstack/services/controllersrv/application/__init__.py +0 -0
  224. cancan_microstack/services/controllersrv/application/docker_compose_app.py +427 -0
  225. cancan_microstack/services/controllersrv/conf/__init__.py +0 -0
  226. cancan_microstack/services/controllersrv/conf/config.py +76 -0
  227. cancan_microstack/services/controllersrv/conf/settings.py +54 -0
  228. cancan_microstack/services/controllersrv/domain/__init__.py +0 -0
  229. cancan_microstack/services/controllersrv/domain/docker_compose/__init__.py +0 -0
  230. cancan_microstack/services/controllersrv/domain/docker_compose/docker_compose_domain.py +278 -0
  231. cancan_microstack/services/controllersrv/domain/service_validator.py +327 -0
  232. cancan_microstack/services/controllersrv/domain/task/__init__.py +17 -0
  233. cancan_microstack/services/controllersrv/domain/task/task_queue.py +286 -0
  234. cancan_microstack/services/controllersrv/domain/task/task_worker.py +495 -0
  235. cancan_microstack/services/controllersrv/infrastructure/__init__.py +0 -0
  236. cancan_microstack/services/controllersrv/interface/__init__.py +0 -0
  237. cancan_microstack/services/controllersrv/interface/api/__init__.py +0 -0
  238. cancan_microstack/services/controllersrv/interface/api/docker_control_api.py +470 -0
  239. cancan_microstack/services/controllersrv/router.py +132 -0
  240. cancan_microstack/services/infrasrv/__init__.py +4 -0
  241. cancan_microstack/services/infrasrv/application/__init__.py +0 -0
  242. cancan_microstack/services/infrasrv/application/health_check_app.py +24 -0
  243. cancan_microstack/services/infrasrv/application/logging/__init__.py +1 -0
  244. cancan_microstack/services/infrasrv/application/logging/log_ingestion_service.py +183 -0
  245. cancan_microstack/services/infrasrv/application/service_config.py +22 -0
  246. cancan_microstack/services/infrasrv/application/service_logs_app.py +53 -0
  247. cancan_microstack/services/infrasrv/application/service_management_app.py +689 -0
  248. cancan_microstack/services/infrasrv/application/service_operation_tracker.py +251 -0
  249. cancan_microstack/services/infrasrv/application/service_registry.py +53 -0
  250. cancan_microstack/services/infrasrv/application/workflow/__init__.py +0 -0
  251. cancan_microstack/services/infrasrv/application/workflow/workflow_app.py +991 -0
  252. cancan_microstack/services/infrasrv/application/workflow/workflow_queue.py +302 -0
  253. cancan_microstack/services/infrasrv/application/workflow/workflow_tasks.py +46 -0
  254. cancan_microstack/services/infrasrv/application/workflow/workflow_worker_runtime.py +122 -0
  255. cancan_microstack/services/infrasrv/conf/__init__.py +0 -0
  256. cancan_microstack/services/infrasrv/conf/config.py +98 -0
  257. cancan_microstack/services/infrasrv/domain/__init__.py +0 -0
  258. cancan_microstack/services/infrasrv/domain/health_check/__init__.py +3 -0
  259. cancan_microstack/services/infrasrv/domain/health_check/health_check_domain.py +576 -0
  260. cancan_microstack/services/infrasrv/domain/hooks/__init__.py +19 -0
  261. cancan_microstack/services/infrasrv/domain/hooks/builtin_hooks.py +308 -0
  262. cancan_microstack/services/infrasrv/domain/hooks/hook_registry.py +43 -0
  263. cancan_microstack/services/infrasrv/domain/hooks/hooks_log_utils.py +275 -0
  264. cancan_microstack/services/infrasrv/domain/hooks/init.py +17 -0
  265. cancan_microstack/services/infrasrv/domain/hooks/metrics.py +205 -0
  266. cancan_microstack/services/infrasrv/domain/hooks/pre_registration_hooks.py +490 -0
  267. cancan_microstack/services/infrasrv/domain/registry/__init__.py +0 -0
  268. cancan_microstack/services/infrasrv/domain/registry/service_registry.py +509 -0
  269. cancan_microstack/services/infrasrv/domain/service_config/__init__.py +0 -0
  270. cancan_microstack/services/infrasrv/domain/service_config/service_config.py +50 -0
  271. cancan_microstack/services/infrasrv/domain/service_logs/__init__.py +0 -0
  272. cancan_microstack/services/infrasrv/domain/service_logs/service_logs_domain.py +51 -0
  273. cancan_microstack/services/infrasrv/domain/workflow/__init__.py +4 -0
  274. cancan_microstack/services/infrasrv/domain/workflow/engine.py +159 -0
  275. cancan_microstack/services/infrasrv/domain/workflow/node_handlers.py +509 -0
  276. cancan_microstack/services/infrasrv/domain/workflow/workflow_domain.py +164 -0
  277. cancan_microstack/services/infrasrv/infrastructure/__init__.py +0 -0
  278. cancan_microstack/services/infrasrv/infrastructure/api/__init__.py +0 -0
  279. cancan_microstack/services/infrasrv/infrastructure/api/controllersrv_api.py +165 -0
  280. cancan_microstack/services/infrasrv/infrastructure/cache/__init__.py +0 -0
  281. cancan_microstack/services/infrasrv/infrastructure/cache/service_registry_cache.py +174 -0
  282. cancan_microstack/services/infrasrv/infrastructure/db/__init__.py +0 -0
  283. cancan_microstack/services/infrasrv/infrastructure/db/model/__init__.py +0 -0
  284. cancan_microstack/services/infrasrv/infrastructure/db/model/execution_log_tbl.py +53 -0
  285. cancan_microstack/services/infrasrv/infrastructure/db/model/node_instance_tbl.py +55 -0
  286. cancan_microstack/services/infrasrv/infrastructure/db/model/service_action_log_tbl.py +44 -0
  287. cancan_microstack/services/infrasrv/infrastructure/db/model/service_config_tbl.py +30 -0
  288. cancan_microstack/services/infrasrv/infrastructure/db/model/service_info_tbl.py +59 -0
  289. cancan_microstack/services/infrasrv/infrastructure/db/model/service_instance_tbl.py +88 -0
  290. cancan_microstack/services/infrasrv/infrastructure/db/model/service_operation_tbl.py +73 -0
  291. cancan_microstack/services/infrasrv/infrastructure/db/model/workflow_definition_tbl.py +55 -0
  292. cancan_microstack/services/infrasrv/infrastructure/db/model/workflow_definition_version_tbl.py +43 -0
  293. cancan_microstack/services/infrasrv/infrastructure/db/model/workflow_engine_alert_tbl.py +57 -0
  294. cancan_microstack/services/infrasrv/infrastructure/db/model/workflow_run_tbl.py +56 -0
  295. cancan_microstack/services/infrasrv/infrastructure/db/operate/__init__.py +0 -0
  296. cancan_microstack/services/infrasrv/infrastructure/db/operate/service_action_log_op.py +239 -0
  297. cancan_microstack/services/infrasrv/infrastructure/db/operate/service_config.py +80 -0
  298. cancan_microstack/services/infrasrv/infrastructure/db/operate/service_config_manager.py +198 -0
  299. cancan_microstack/services/infrasrv/infrastructure/db/operate/service_info_op.py +297 -0
  300. cancan_microstack/services/infrasrv/infrastructure/db/operate/service_instance_op.py +688 -0
  301. cancan_microstack/services/infrasrv/infrastructure/db/operate/service_operation_op.py +387 -0
  302. cancan_microstack/services/infrasrv/infrastructure/db/operate/service_registry.py +124 -0
  303. cancan_microstack/services/infrasrv/infrastructure/db/operate/workflow_op.py +804 -0
  304. cancan_microstack/services/infrasrv/infrastructure/ddl_manager.py +31 -0
  305. cancan_microstack/services/infrasrv/infrastructure/mongo/__init__.py +1 -0
  306. cancan_microstack/services/infrasrv/infrastructure/mongo/log_repository.py +129 -0
  307. cancan_microstack/services/infrasrv/interface/__init__.py +0 -0
  308. cancan_microstack/services/infrasrv/interface/api/__init__.py +0 -0
  309. cancan_microstack/services/infrasrv/interface/api/health_check_api.py +29 -0
  310. cancan_microstack/services/infrasrv/interface/api/hooks.py +284 -0
  311. cancan_microstack/services/infrasrv/interface/api/internal.py +49 -0
  312. cancan_microstack/services/infrasrv/interface/api/internal_instance_api.py +265 -0
  313. cancan_microstack/services/infrasrv/interface/api/internal_operation_api.py +206 -0
  314. cancan_microstack/services/infrasrv/interface/api/service_config.py +50 -0
  315. cancan_microstack/services/infrasrv/interface/api/service_logs_api.py +49 -0
  316. cancan_microstack/services/infrasrv/interface/api/service_management_api.py +113 -0
  317. cancan_microstack/services/infrasrv/interface/api/service_registry.py +117 -0
  318. cancan_microstack/services/infrasrv/interface/api/workflow_api.py +303 -0
  319. cancan_microstack/services/infrasrv/interface/schedule/__init__.py +0 -0
  320. cancan_microstack/services/infrasrv/interface/schedule/cleanup.py +13 -0
  321. cancan_microstack/services/infrasrv/interface/schedule/health_check.py +27 -0
  322. cancan_microstack/services/infrasrv/interface/schedule/log_cleanup.py +26 -0
  323. cancan_microstack/services/infrasrv/interface/schedule/operation_tracker.py +25 -0
  324. cancan_microstack/services/infrasrv/interface/schedule/scheduler.py +39 -0
  325. cancan_microstack/services/infrasrv/interface/schedule/workflow_scheduler.py +115 -0
  326. cancan_microstack/services/infrasrv/router.py +341 -0
  327. cancan_microstack/services/opsbffsrv/__init__.py +4 -0
  328. cancan_microstack/services/opsbffsrv/application/__init__.py +0 -0
  329. cancan_microstack/services/opsbffsrv/application/async_operation_app.py +150 -0
  330. cancan_microstack/services/opsbffsrv/application/auth_app.py +285 -0
  331. cancan_microstack/services/opsbffsrv/application/caddy/__init__.py +0 -0
  332. cancan_microstack/services/opsbffsrv/application/caddy/access_log_analysis_app.py +344 -0
  333. cancan_microstack/services/opsbffsrv/application/caddy/access_log_ingestion_service.py +169 -0
  334. cancan_microstack/services/opsbffsrv/application/caddy/certificate_management_app.py +355 -0
  335. cancan_microstack/services/opsbffsrv/application/caddy/rate_limit_management_app.py +496 -0
  336. cancan_microstack/services/opsbffsrv/application/caddy/route_management_app.py +401 -0
  337. cancan_microstack/services/opsbffsrv/application/caddy/stats_aggregation_app.py +364 -0
  338. cancan_microstack/services/opsbffsrv/application/db_admin_app.py +103 -0
  339. cancan_microstack/services/opsbffsrv/application/db_init_app.py +283 -0
  340. cancan_microstack/services/opsbffsrv/application/logging/__init__.py +1 -0
  341. cancan_microstack/services/opsbffsrv/application/logging/log_query_app.py +28 -0
  342. cancan_microstack/services/opsbffsrv/application/service_config.py +158 -0
  343. cancan_microstack/services/opsbffsrv/application/service_logs_app.py +74 -0
  344. cancan_microstack/services/opsbffsrv/application/service_registry.py +36 -0
  345. cancan_microstack/services/opsbffsrv/application/workflow_ops_app.py +730 -0
  346. cancan_microstack/services/opsbffsrv/conf/__init__.py +0 -0
  347. cancan_microstack/services/opsbffsrv/conf/config.py +224 -0
  348. cancan_microstack/services/opsbffsrv/domain/__init__.py +0 -0
  349. cancan_microstack/services/opsbffsrv/domain/auth/__init__.py +0 -0
  350. cancan_microstack/services/opsbffsrv/domain/auth/admin_init.py +38 -0
  351. cancan_microstack/services/opsbffsrv/domain/auth/auth_domain.py +108 -0
  352. cancan_microstack/services/opsbffsrv/domain/caddy/__init__.py +0 -0
  353. cancan_microstack/services/opsbffsrv/domain/caddy/access_log_analysis.py +358 -0
  354. cancan_microstack/services/opsbffsrv/domain/caddy/certificate_management.py +325 -0
  355. cancan_microstack/services/opsbffsrv/domain/caddy/default_routes.py +53 -0
  356. cancan_microstack/services/opsbffsrv/domain/caddy/rate_limit_management.py +308 -0
  357. cancan_microstack/services/opsbffsrv/domain/caddy/route_management.py +279 -0
  358. cancan_microstack/services/opsbffsrv/domain/caddy/stats_aggregation.py +654 -0
  359. cancan_microstack/services/opsbffsrv/domain/db_admin/__init__.py +0 -0
  360. cancan_microstack/services/opsbffsrv/domain/db_admin/db_admin_domain.py +118 -0
  361. cancan_microstack/services/opsbffsrv/domain/db_init/__init__.py +3 -0
  362. cancan_microstack/services/opsbffsrv/domain/db_init/db_init_domain.py +358 -0
  363. cancan_microstack/services/opsbffsrv/domain/logging/__init__.py +1 -0
  364. cancan_microstack/services/opsbffsrv/domain/logging/log_query_domain.py +99 -0
  365. cancan_microstack/services/opsbffsrv/domain/service_config/__init__.py +0 -0
  366. cancan_microstack/services/opsbffsrv/domain/service_config/service_config.py +81 -0
  367. cancan_microstack/services/opsbffsrv/domain/service_registry/__init__.py +0 -0
  368. cancan_microstack/services/opsbffsrv/domain/service_registry/service_registry.py +292 -0
  369. cancan_microstack/services/opsbffsrv/infrastructure/__init__.py +0 -0
  370. cancan_microstack/services/opsbffsrv/infrastructure/api/__init__.py +0 -0
  371. cancan_microstack/services/opsbffsrv/infrastructure/api/infrasrv_api.py +242 -0
  372. cancan_microstack/services/opsbffsrv/infrastructure/auth/__init__.py +0 -0
  373. cancan_microstack/services/opsbffsrv/infrastructure/auth/captcha_service.py +67 -0
  374. cancan_microstack/services/opsbffsrv/infrastructure/auth/password_service.py +12 -0
  375. cancan_microstack/services/opsbffsrv/infrastructure/auth/redis_store.py +131 -0
  376. cancan_microstack/services/opsbffsrv/infrastructure/auth/totp_service.py +59 -0
  377. cancan_microstack/services/opsbffsrv/infrastructure/caddy/__init__.py +0 -0
  378. cancan_microstack/services/opsbffsrv/infrastructure/caddy/access_log_parser.py +307 -0
  379. cancan_microstack/services/opsbffsrv/infrastructure/caddy/admin_api_client.py +678 -0
  380. cancan_microstack/services/opsbffsrv/infrastructure/caddy/ip_geo_locator.py +176 -0
  381. cancan_microstack/services/opsbffsrv/infrastructure/db/__init__.py +0 -0
  382. cancan_microstack/services/opsbffsrv/infrastructure/db/model/__init__.py +0 -0
  383. cancan_microstack/services/opsbffsrv/infrastructure/db/model/admin_user_tbl.py +33 -0
  384. cancan_microstack/services/opsbffsrv/infrastructure/db/model/caddy_access_log_tbl.py +90 -0
  385. cancan_microstack/services/opsbffsrv/infrastructure/db/model/caddy_certificate_tbl.py +65 -0
  386. cancan_microstack/services/opsbffsrv/infrastructure/db/model/caddy_rate_limit_tbl.py +69 -0
  387. cancan_microstack/services/opsbffsrv/infrastructure/db/model/caddy_route_tbl.py +66 -0
  388. cancan_microstack/services/opsbffsrv/infrastructure/db/model/caddy_stats_tbl.py +78 -0
  389. cancan_microstack/services/opsbffsrv/infrastructure/db/model/service_action_log_tbl.py +44 -0
  390. cancan_microstack/services/opsbffsrv/infrastructure/db/model/service_config_tbl.py +30 -0
  391. cancan_microstack/services/opsbffsrv/infrastructure/db/model/service_info_tbl.py +51 -0
  392. cancan_microstack/services/opsbffsrv/infrastructure/db/model/service_instance_tbl.py +68 -0
  393. cancan_microstack/services/opsbffsrv/infrastructure/db/operate/__init__.py +0 -0
  394. cancan_microstack/services/opsbffsrv/infrastructure/db/operate/admin_user_operate.py +59 -0
  395. cancan_microstack/services/opsbffsrv/infrastructure/db/operate/caddy_access_log.py +531 -0
  396. cancan_microstack/services/opsbffsrv/infrastructure/db/operate/caddy_certificate.py +451 -0
  397. cancan_microstack/services/opsbffsrv/infrastructure/db/operate/caddy_rate_limit.py +360 -0
  398. cancan_microstack/services/opsbffsrv/infrastructure/db/operate/caddy_route.py +271 -0
  399. cancan_microstack/services/opsbffsrv/infrastructure/db/operate/caddy_stats.py +343 -0
  400. cancan_microstack/services/opsbffsrv/infrastructure/db/operate/service_action_log_op.py +57 -0
  401. cancan_microstack/services/opsbffsrv/infrastructure/db/operate/service_config.py +86 -0
  402. cancan_microstack/services/opsbffsrv/infrastructure/db/operate/service_info_op.py +79 -0
  403. cancan_microstack/services/opsbffsrv/infrastructure/db/operate/service_instance.py +58 -0
  404. cancan_microstack/services/opsbffsrv/infrastructure/db/operate/service_registry.py +138 -0
  405. cancan_microstack/services/opsbffsrv/infrastructure/ddl_manager.py +31 -0
  406. cancan_microstack/services/opsbffsrv/infrastructure/mongo/__init__.py +1 -0
  407. cancan_microstack/services/opsbffsrv/infrastructure/mongo/log_query_repository.py +87 -0
  408. cancan_microstack/services/opsbffsrv/interface/__init__.py +0 -0
  409. cancan_microstack/services/opsbffsrv/interface/api/__init__.py +0 -0
  410. cancan_microstack/services/opsbffsrv/interface/api/async_operation_api.py +137 -0
  411. cancan_microstack/services/opsbffsrv/interface/api/auth_api.py +113 -0
  412. cancan_microstack/services/opsbffsrv/interface/api/caddy/__init__.py +3 -0
  413. cancan_microstack/services/opsbffsrv/interface/api/caddy/access_log_api.py +174 -0
  414. cancan_microstack/services/opsbffsrv/interface/api/caddy/certificate_api.py +235 -0
  415. cancan_microstack/services/opsbffsrv/interface/api/caddy/rate_limit_api.py +302 -0
  416. cancan_microstack/services/opsbffsrv/interface/api/caddy/route_api.py +250 -0
  417. cancan_microstack/services/opsbffsrv/interface/api/caddy/stats_api.py +243 -0
  418. cancan_microstack/services/opsbffsrv/interface/api/db_admin_api.py +62 -0
  419. cancan_microstack/services/opsbffsrv/interface/api/db_init_api.py +109 -0
  420. cancan_microstack/services/opsbffsrv/interface/api/instance_management_api.py +165 -0
  421. cancan_microstack/services/opsbffsrv/interface/api/log_query_api.py +41 -0
  422. cancan_microstack/services/opsbffsrv/interface/api/mongo_express_proxy_api.py +181 -0
  423. cancan_microstack/services/opsbffsrv/interface/api/pgweb_proxy_api.py +154 -0
  424. cancan_microstack/services/opsbffsrv/interface/api/rabbitmq_mgmt_proxy_api.py +518 -0
  425. cancan_microstack/services/opsbffsrv/interface/api/redis_commander_proxy_api.py +133 -0
  426. cancan_microstack/services/opsbffsrv/interface/api/service_config.py +146 -0
  427. cancan_microstack/services/opsbffsrv/interface/api/service_logs_api.py +81 -0
  428. cancan_microstack/services/opsbffsrv/interface/api/service_registry.py +66 -0
  429. cancan_microstack/services/opsbffsrv/interface/api/workflow_ops_api.py +413 -0
  430. cancan_microstack/services/opsbffsrv/interface/middleware/__init__.py +0 -0
  431. cancan_microstack/services/opsbffsrv/interface/middleware/auth_middleware.py +52 -0
  432. cancan_microstack/services/opsbffsrv/router.py +901 -0
  433. cancan_microstack/utils/__init__.py +1 -0
  434. cancan_microstack/utils/container_env.py +218 -0
  435. cancan_microstack-0.0.1.dist-info/METADATA +155 -0
  436. cancan_microstack-0.0.1.dist-info/RECORD +440 -0
  437. cancan_microstack-0.0.1.dist-info/WHEEL +5 -0
  438. cancan_microstack-0.0.1.dist-info/entry_points.txt +2 -0
  439. cancan_microstack-0.0.1.dist-info/licenses/LICENSE +21 -0
  440. cancan_microstack-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,804 @@
1
+ """
2
+ 工作流引擎数据库操作
3
+ Workflow engine database operations
4
+ """
5
+ import uuid
6
+ from datetime import (
7
+ datetime,
8
+ timezone,
9
+ timedelta,
10
+ )
11
+ from typing import (
12
+ List,
13
+ Optional,
14
+ Dict,
15
+ Any,
16
+ Tuple,
17
+ )
18
+
19
+ from sqlalchemy import (
20
+ select,
21
+ desc,
22
+ and_,
23
+ or_,
24
+ update,
25
+ func,
26
+ )
27
+ from sqlalchemy.dialects.postgresql import insert
28
+
29
+ from linglong_web import Rmanager
30
+ from cancan_microstack.public.schemas.infra import workflow as workflow_types
31
+ from cancan_microstack.public.schemas.infra.enums import (
32
+ WorkflowStatus,
33
+ NodeStatus,
34
+ ExecutionLogStatus,
35
+ WorkflowEngineAlertStatus,
36
+ )
37
+ from cancan_microstack.services.infrasrv.infrastructure.db.model.workflow_run_tbl import WorkflowRunTbl
38
+ from cancan_microstack.services.infrasrv.infrastructure.db.model.workflow_definition_tbl import WorkflowDefinitionTbl
39
+ from cancan_microstack.services.infrasrv.infrastructure.db.model.workflow_definition_version_tbl import \
40
+ WorkflowDefinitionVersionTbl
41
+ from cancan_microstack.services.infrasrv.infrastructure.db.model.node_instance_tbl import NodeInstanceTbl
42
+ from cancan_microstack.services.infrasrv.infrastructure.db.model.execution_log_tbl import ExecutionLogTbl
43
+ from cancan_microstack.services.infrasrv.infrastructure.db.model.workflow_engine_alert_tbl import WorkflowEngineAlertTbl
44
+
45
+
46
+ async def _insert_version_snapshot(session, workflow_row, change_summary: Optional[str]) -> None:
47
+ """写入工作流版本快照
48
+ Persist a workflow definition snapshot for version tracking"""
49
+
50
+ snapshot_values = {
51
+ "workflow_id": workflow_row.id,
52
+ "version": getattr(workflow_row, "version", 1),
53
+ "name": workflow_row.name,
54
+ "description": workflow_row.description,
55
+ "schedule": workflow_row.schedule,
56
+ "graph_data": workflow_row.graph_data,
57
+ "nodes_config": workflow_row.nodes_config,
58
+ "global_context": workflow_row.global_context,
59
+ "is_active": workflow_row.is_active,
60
+ "change_summary": change_summary,
61
+ }
62
+ await session.execute(
63
+ insert(WorkflowDefinitionVersionTbl).values(**snapshot_values)
64
+ )
65
+
66
+
67
+ # 工作流运行终态集合,用于计算耗时并停止派发
68
+ # Set of terminal workflow statuses used to finalize runtime bookkeeping
69
+ TERMINAL_WORKFLOW_STATUSES = {
70
+ WorkflowStatus.SUCCESS,
71
+ WorkflowStatus.FAILURE,
72
+ WorkflowStatus.CANCELLED,
73
+ }
74
+
75
+
76
+ async def create_workflow_definition(
77
+ data: workflow_types.WorkflowDefinitionCreate) -> workflow_types.WorkflowDefinition:
78
+ """创建工作流定义记录 / Insert a workflow definition row."""
79
+ async with Rmanager.pg_session() as session:
80
+ async with session.begin():
81
+ payload = data.model_dump()
82
+ payload.setdefault("version", 1)
83
+ stmt = insert(WorkflowDefinitionTbl).values(**payload).returning(WorkflowDefinitionTbl)
84
+ row = (await session.execute(stmt)).scalar_one()
85
+ await _insert_version_snapshot(session, row, change_summary="Initial version")
86
+ return workflow_types.WorkflowDefinition.model_validate(row, from_attributes=True)
87
+
88
+
89
+ async def list_workflow_definitions(
90
+ limit: int = 100,
91
+ offset: int = 0,
92
+ ) -> Tuple[List[workflow_types.WorkflowDefinition], int]:
93
+ """分页查询工作流定义并返回总数 / List workflow definitions with pagination and total."""
94
+
95
+ async with Rmanager.pg_session() as session:
96
+ async with session.begin():
97
+ base_stmt = select(WorkflowDefinitionTbl).where(WorkflowDefinitionTbl.flag == 0)
98
+ total_stmt = select(func.count()).select_from(base_stmt.subquery())
99
+ total = (await session.execute(total_stmt)).scalar_one()
100
+
101
+ stmt = (
102
+ base_stmt.order_by(desc(WorkflowDefinitionTbl.update_time))
103
+ .limit(limit)
104
+ .offset(offset)
105
+ )
106
+ rows = list((await session.execute(stmt)).scalars().all())
107
+ definitions = [workflow_types.WorkflowDefinition.model_validate(r, from_attributes=True) for r in rows]
108
+ return definitions, total
109
+
110
+
111
+ async def get_workflow_definition_by_id(workflow_id: uuid.UUID) -> Optional[workflow_types.WorkflowDefinition]:
112
+ """按ID获取工作流定义 / Fetch workflow definition by primary key."""
113
+ async with Rmanager.pg_session() as session:
114
+ async with session.begin():
115
+ stmt = select(WorkflowDefinitionTbl).where(WorkflowDefinitionTbl.id == workflow_id)
116
+ row = (await session.execute(stmt)).scalar_one_or_none()
117
+ return workflow_types.WorkflowDefinition.model_validate(row, from_attributes=True) if row else None
118
+
119
+
120
+ async def update_workflow_definition(
121
+ workflow_id: uuid.UUID,
122
+ data: workflow_types.WorkflowDefinitionUpdate,
123
+ ) -> Optional[workflow_types.WorkflowDefinition]:
124
+ """更新工作流定义并写入版本快照
125
+ Update workflow definition fields and record a version snapshot."""
126
+
127
+ async with Rmanager.pg_session() as session:
128
+ async with session.begin():
129
+ current_stmt = select(WorkflowDefinitionTbl).where(WorkflowDefinitionTbl.id == workflow_id)
130
+ current_row = (await session.execute(current_stmt)).scalar_one_or_none()
131
+ if not current_row:
132
+ return None
133
+
134
+ update_dict = data.model_dump(exclude_unset=True)
135
+ change_summary = update_dict.pop("change_summary", data.change_summary)
136
+ new_version = (current_row.version or 1) + 1
137
+ update_dict["version"] = new_version
138
+ if change_summary:
139
+ update_dict["change_summary"] = change_summary
140
+
141
+ stmt = (
142
+ update(WorkflowDefinitionTbl)
143
+ .where(WorkflowDefinitionTbl.id == workflow_id)
144
+ .values(**update_dict)
145
+ .returning(WorkflowDefinitionTbl)
146
+ )
147
+ row = (await session.execute(stmt)).scalar_one_or_none()
148
+ if row:
149
+ await _insert_version_snapshot(session, row, change_summary or "Definition updated")
150
+ return workflow_types.WorkflowDefinition.model_validate(row, from_attributes=True)
151
+ return None
152
+
153
+
154
+ async def get_scheduled_workflows() -> List[workflow_types.WorkflowDefinition]:
155
+ """获取启用的定时工作流 / Fetch active workflows that own schedules."""
156
+ async with Rmanager.pg_session() as session:
157
+ async with session.begin():
158
+ stmt = select(WorkflowDefinitionTbl).where(
159
+ and_(
160
+ WorkflowDefinitionTbl.flag == 0,
161
+ WorkflowDefinitionTbl.is_active == True,
162
+ WorkflowDefinitionTbl.schedule != None,
163
+ WorkflowDefinitionTbl.schedule != ''
164
+ )
165
+ )
166
+ rows = list((await session.execute(stmt)).scalars().all())
167
+ return [workflow_types.WorkflowDefinition.model_validate(r, from_attributes=True) for r in rows]
168
+
169
+
170
+ async def list_workflow_versions(
171
+ workflow_id: uuid.UUID,
172
+ limit: int = 50,
173
+ ) -> List[workflow_types.WorkflowDefinitionVersion]:
174
+ """列出工作流版本历史
175
+ List workflow definition versions (most recent first)."""
176
+
177
+ async with Rmanager.pg_session() as session:
178
+ async with session.begin():
179
+ stmt = (
180
+ select(WorkflowDefinitionVersionTbl)
181
+ .where(WorkflowDefinitionVersionTbl.workflow_id == workflow_id)
182
+ .order_by(desc(WorkflowDefinitionVersionTbl.version))
183
+ .limit(limit)
184
+ )
185
+ rows = list((await session.execute(stmt)).scalars().all())
186
+ return [workflow_types.WorkflowDefinitionVersion.model_validate(r, from_attributes=True) for r in rows]
187
+
188
+
189
+ async def get_workflow_version(
190
+ workflow_id: uuid.UUID,
191
+ version: int,
192
+ ) -> Optional[workflow_types.WorkflowDefinitionVersion]:
193
+ """获取指定版本快照 / Fetch an individual workflow version snapshot."""
194
+
195
+ async with Rmanager.pg_session() as session:
196
+ async with session.begin():
197
+ stmt = select(WorkflowDefinitionVersionTbl).where(
198
+ and_(
199
+ WorkflowDefinitionVersionTbl.workflow_id == workflow_id,
200
+ WorkflowDefinitionVersionTbl.version == version,
201
+ )
202
+ )
203
+ row = (await session.execute(stmt)).scalar_one_or_none()
204
+ return workflow_types.WorkflowDefinitionVersion.model_validate(row, from_attributes=True) if row else None
205
+
206
+
207
+ async def rollback_workflow_definition(
208
+ workflow_id: uuid.UUID,
209
+ target_version: int,
210
+ reason: Optional[str] = None,
211
+ ) -> Optional[workflow_types.WorkflowDefinition]:
212
+ """回滚到指定版本并生成新版本
213
+ Restore workflow definition to a historical version and emit a new version snapshot."""
214
+
215
+ async with Rmanager.pg_session() as session:
216
+ async with session.begin():
217
+ snapshot_stmt = select(WorkflowDefinitionVersionTbl).where(
218
+ and_(
219
+ WorkflowDefinitionVersionTbl.workflow_id == workflow_id,
220
+ WorkflowDefinitionVersionTbl.version == target_version,
221
+ )
222
+ )
223
+ snapshot = (await session.execute(snapshot_stmt)).scalar_one_or_none()
224
+ if not snapshot:
225
+ return None
226
+
227
+ current_stmt = select(WorkflowDefinitionTbl).where(WorkflowDefinitionTbl.id == workflow_id)
228
+ current_row = (await session.execute(current_stmt)).scalar_one_or_none()
229
+ if not current_row:
230
+ return None
231
+
232
+ new_version = (current_row.version or 1) + 1
233
+ summary = reason or f"Rollback to v{target_version}"
234
+ update_values = {
235
+ "name": snapshot.name,
236
+ "description": snapshot.description,
237
+ "schedule": snapshot.schedule,
238
+ "graph_data": snapshot.graph_data,
239
+ "nodes_config": snapshot.nodes_config,
240
+ "global_context": snapshot.global_context,
241
+ "is_active": snapshot.is_active,
242
+ "version": new_version,
243
+ "change_summary": summary,
244
+ }
245
+ stmt = (
246
+ update(WorkflowDefinitionTbl)
247
+ .where(WorkflowDefinitionTbl.id == workflow_id)
248
+ .values(**update_values)
249
+ .returning(WorkflowDefinitionTbl)
250
+ )
251
+ row = (await session.execute(stmt)).scalar_one_or_none()
252
+ if row:
253
+ await _insert_version_snapshot(session, row, summary)
254
+ return workflow_types.WorkflowDefinition.model_validate(row, from_attributes=True)
255
+ return None
256
+
257
+
258
+ # --- 工作流运行操作 / WorkflowRun Operations ---
259
+
260
+ async def create_workflow_run(data: workflow_types.WorkflowRunCreate) -> workflow_types.WorkflowRun:
261
+ """创建工作流运行实例 / Insert workflow run row when triggering workflows."""
262
+ async with Rmanager.pg_session() as session:
263
+ async with session.begin():
264
+ stmt = insert(WorkflowRunTbl).values(**data.model_dump()).returning(WorkflowRunTbl)
265
+ row = (await session.execute(stmt)).scalar_one()
266
+ return workflow_types.WorkflowRun.model_validate(row, from_attributes=True)
267
+
268
+
269
+ async def list_workflow_runs(
270
+ workflow_id: Optional[uuid.UUID] = None,
271
+ reqid: Optional[str] = None,
272
+ limit: int = 20,
273
+ offset: int = 0,
274
+ status: Optional[WorkflowStatus] = None,
275
+ date_from: Optional[datetime] = None,
276
+ date_to: Optional[datetime] = None,
277
+ ) -> Tuple[List[workflow_types.WorkflowRun], int]:
278
+ """查询运行实例列表 / List workflow runs optionally filtered by workflow ID, status, or time window."""
279
+
280
+ async with Rmanager.pg_session() as session:
281
+ async with session.begin():
282
+ filters = [WorkflowRunTbl.flag == 0]
283
+ if workflow_id:
284
+ filters.append(WorkflowRunTbl.workflow_id == workflow_id)
285
+ if reqid:
286
+ normalized_reqid = reqid.strip()
287
+ if normalized_reqid:
288
+ filters.append(
289
+ or_(
290
+ WorkflowRunTbl.trigger_context["reqid"].astext == normalized_reqid,
291
+ WorkflowRunTbl.global_context["reqid"].astext == normalized_reqid,
292
+ )
293
+ )
294
+ if status:
295
+ filters.append(WorkflowRunTbl.status == status.value)
296
+ if date_from:
297
+ filters.append(WorkflowRunTbl.started_at >= date_from)
298
+ if date_to:
299
+ filters.append(WorkflowRunTbl.started_at <= date_to)
300
+
301
+ base_stmt = select(WorkflowRunTbl)
302
+ if filters:
303
+ base_stmt = base_stmt.where(and_(*filters))
304
+
305
+ total_stmt = select(func.count()).select_from(base_stmt.subquery())
306
+ total = (await session.execute(total_stmt)).scalar_one()
307
+
308
+ stmt = (
309
+ base_stmt.order_by(desc(WorkflowRunTbl.started_at))
310
+ .limit(limit)
311
+ .offset(offset)
312
+ )
313
+ rows = list((await session.execute(stmt)).scalars().all())
314
+ runs = [workflow_types.WorkflowRun.model_validate(r, from_attributes=True) for r in rows]
315
+ return runs, total
316
+
317
+
318
+ async def get_workflow_run_by_id(run_id: uuid.UUID) -> Optional[workflow_types.WorkflowRun]:
319
+ """按ID查询运行实例 / Fetch workflow run by primary key."""
320
+ async with Rmanager.pg_session() as session:
321
+ async with session.begin():
322
+ stmt = select(WorkflowRunTbl).where(WorkflowRunTbl.id == run_id)
323
+ row = (await session.execute(stmt)).scalar_one_or_none()
324
+ return workflow_types.WorkflowRun.model_validate(row, from_attributes=True) if row else None
325
+
326
+
327
+ async def update_workflow_run_status(run_id: uuid.UUID, status: WorkflowStatus) -> Optional[workflow_types.WorkflowRun]:
328
+ """更新工作流运行状态并计算耗时 / Update workflow run status and compute duration."""
329
+
330
+ async with Rmanager.pg_session() as session:
331
+ async with session.begin():
332
+ current_stmt = select(WorkflowRunTbl).where(WorkflowRunTbl.id == run_id)
333
+ current = (await session.execute(current_stmt)).scalar_one_or_none()
334
+ if not current:
335
+ return None
336
+
337
+ now = datetime.now(timezone.utc)
338
+ update_values = {
339
+ "status": status.value,
340
+ }
341
+ if status in TERMINAL_WORKFLOW_STATUSES:
342
+ update_values["finished_at"] = now
343
+ if current.started_at:
344
+ duration_ms = int((now - current.started_at).total_seconds() * 1000)
345
+ update_values["duration_ms"] = duration_ms
346
+
347
+ stmt = (
348
+ update(WorkflowRunTbl)
349
+ .where(WorkflowRunTbl.id == run_id)
350
+ .values(**update_values)
351
+ .returning(WorkflowRunTbl)
352
+ )
353
+ row = (await session.execute(stmt)).scalar_one_or_none()
354
+ return workflow_types.WorkflowRun.model_validate(row, from_attributes=True) if row else None
355
+
356
+
357
+ async def update_workflow_run_global_context(
358
+ run_id: uuid.UUID,
359
+ global_context: Dict[str, Any],
360
+ ) -> Optional[workflow_types.WorkflowRun]:
361
+ """更新工作流运行的全局上下文 / Update workflow run global context."""
362
+
363
+ async with Rmanager.pg_session() as session:
364
+ async with session.begin():
365
+ stmt = (
366
+ update(WorkflowRunTbl)
367
+ .where(WorkflowRunTbl.id == run_id)
368
+ .values(global_context=global_context)
369
+ .returning(WorkflowRunTbl)
370
+ )
371
+ row = (await session.execute(stmt)).scalar_one_or_none()
372
+ return workflow_types.WorkflowRun.model_validate(row, from_attributes=True) if row else None
373
+
374
+
375
+ # --- 节点实例操作 / NodeInstance Operations ---
376
+
377
+ async def get_node_instances_by_run_id(run_id: uuid.UUID) -> List[workflow_types.NodeInstance]:
378
+ """查询指定运行的全部节点实例 / List all node instances for a run."""
379
+ async with Rmanager.pg_session() as session:
380
+ async with session.begin():
381
+ stmt = select(NodeInstanceTbl).where(NodeInstanceTbl.run_id == run_id).order_by(NodeInstanceTbl.loop_index)
382
+ rows = list((await session.execute(stmt)).scalars().all())
383
+ return [workflow_types.NodeInstance.model_validate(r, from_attributes=True) for r in rows]
384
+
385
+
386
+ async def get_node_instances_by_run_and_node_id(run_id: uuid.UUID, node_id: str) -> List[workflow_types.NodeInstance]:
387
+ """查询某运行中某节点的全部实例 / Fetch node instances filtered by run and node ID."""
388
+ async with Rmanager.pg_session() as session:
389
+ async with session.begin():
390
+ stmt = select(NodeInstanceTbl).where(
391
+ and_(
392
+ NodeInstanceTbl.run_id == run_id,
393
+ NodeInstanceTbl.node_id == node_id
394
+ )
395
+ ).order_by(NodeInstanceTbl.loop_index)
396
+ rows = list((await session.execute(stmt)).scalars().all())
397
+ return [workflow_types.NodeInstance.model_validate(r, from_attributes=True) for r in rows]
398
+
399
+
400
+ async def get_node_instance_by_id(instance_id: uuid.UUID) -> Optional[workflow_types.NodeInstance]:
401
+ """按节点实例 ID 查询记录 / Fetch node instance by primary key."""
402
+
403
+ async with Rmanager.pg_session() as session:
404
+ async with session.begin():
405
+ stmt = select(NodeInstanceTbl).where(NodeInstanceTbl.id == instance_id)
406
+ row = (await session.execute(stmt)).scalar_one_or_none()
407
+ return workflow_types.NodeInstance.model_validate(row, from_attributes=True) if row else None
408
+
409
+
410
+ async def upsert_node_instance(run_id: uuid.UUID, node_id: str, loop_index: int) -> workflow_types.NodeInstance:
411
+ """插入或更新节点实例(用于记录重试次数)/ Upsert a node instance to track attempts."""
412
+
413
+ async with Rmanager.pg_session() as session:
414
+ async with session.begin():
415
+ stmt = (
416
+ insert(NodeInstanceTbl)
417
+ .values(
418
+ run_id=run_id,
419
+ node_id=node_id,
420
+ loop_index=loop_index,
421
+ status=NodeStatus.RUNNING.value,
422
+ attempt_count=1,
423
+ )
424
+ .on_conflict_do_update(
425
+ index_elements=["run_id", "node_id", "loop_index"],
426
+ set_={
427
+ "status": NodeStatus.RUNNING.value,
428
+ "attempt_count": NodeInstanceTbl.attempt_count + 1,
429
+ "update_time": func.current_timestamp(),
430
+ },
431
+ )
432
+ .returning(NodeInstanceTbl)
433
+ )
434
+ row = (await session.execute(stmt)).scalar_one()
435
+ return workflow_types.NodeInstance.model_validate(row, from_attributes=True)
436
+
437
+
438
+ async def update_node_instance_result(
439
+ instance_id: uuid.UUID,
440
+ status: NodeStatus,
441
+ final_output: Optional[Dict[str, Any]] = None,
442
+ error_msg: Optional[str] = None,
443
+ ) -> Optional[workflow_types.NodeInstance]:
444
+ """更新节点执行结果 / Persist node execution result payload."""
445
+
446
+ async with Rmanager.pg_session() as session:
447
+ async with session.begin():
448
+ update_values: Dict[str, Any] = {
449
+ "status": status.value,
450
+ "final_output": final_output,
451
+ "error_msg": error_msg,
452
+ "update_time": func.current_timestamp(),
453
+ }
454
+ stmt = (
455
+ update(NodeInstanceTbl)
456
+ .where(NodeInstanceTbl.id == instance_id)
457
+ .values(**update_values)
458
+ .returning(NodeInstanceTbl)
459
+ )
460
+ row = (await session.execute(stmt)).scalar_one_or_none()
461
+ return workflow_types.NodeInstance.model_validate(row, from_attributes=True) if row else None
462
+
463
+
464
+ # --- 执行日志操作 / ExecutionLog Operations ---
465
+
466
+ async def create_execution_log(
467
+ node_instance_id: uuid.UUID,
468
+ attempt_no: int,
469
+ request_snapshot: Optional[Dict[str, Any]] = None,
470
+ ) -> workflow_types.ExecutionLog:
471
+ """创建执行日志记录 / Create execution log entry."""
472
+
473
+ async with Rmanager.pg_session() as session:
474
+ async with session.begin():
475
+ stmt = (
476
+ insert(ExecutionLogTbl)
477
+ .values(
478
+ node_instance_id=node_instance_id,
479
+ attempt_no=attempt_no,
480
+ request_snapshot=request_snapshot,
481
+ status=ExecutionLogStatus.PENDING.value,
482
+ )
483
+ .returning(ExecutionLogTbl)
484
+ )
485
+ row = (await session.execute(stmt)).scalar_one()
486
+ return workflow_types.ExecutionLog.model_validate(row, from_attributes=True)
487
+
488
+
489
+ async def update_execution_log_result(
490
+ log_id: uuid.UUID,
491
+ status: ExecutionLogStatus,
492
+ response_snapshot: Optional[Dict[str, Any]] = None,
493
+ error_detail: Optional[str] = None,
494
+ ) -> Optional[workflow_types.ExecutionLog]:
495
+ """更新执行日志的结果 / Update execution log outcome."""
496
+
497
+ async with Rmanager.pg_session() as session:
498
+ async with session.begin():
499
+ current_stmt = select(ExecutionLogTbl).where(ExecutionLogTbl.id == log_id)
500
+ current = (await session.execute(current_stmt)).scalar_one_or_none()
501
+ if not current:
502
+ return None
503
+
504
+ now = datetime.now(timezone.utc)
505
+ duration_ms = None
506
+ if current.start_time:
507
+ duration_ms = int((now - current.start_time).total_seconds() * 1000)
508
+
509
+ update_values: Dict[str, Any] = {
510
+ "status": status.value,
511
+ "response_snapshot": response_snapshot,
512
+ "error_detail": error_detail,
513
+ "end_time": now,
514
+ }
515
+ if duration_ms is not None:
516
+ update_values["duration_ms"] = duration_ms
517
+
518
+ stmt = (
519
+ update(ExecutionLogTbl)
520
+ .where(ExecutionLogTbl.id == log_id)
521
+ .values(**update_values)
522
+ .returning(ExecutionLogTbl)
523
+ )
524
+ row = (await session.execute(stmt)).scalar_one_or_none()
525
+ return workflow_types.ExecutionLog.model_validate(row, from_attributes=True) if row else None
526
+
527
+
528
+ async def get_execution_logs_by_node_instance_id(node_instance_id: uuid.UUID) -> List[workflow_types.ExecutionLog]:
529
+ """查询节点实例的执行日志 / Fetch execution logs for a node instance."""
530
+ async with Rmanager.pg_session() as session:
531
+ async with session.begin():
532
+ stmt = select(ExecutionLogTbl).where(ExecutionLogTbl.node_instance_id == node_instance_id).order_by(
533
+ ExecutionLogTbl.attempt_no)
534
+ rows = list((await session.execute(stmt)).scalars().all())
535
+ return [workflow_types.ExecutionLog.model_validate(r, from_attributes=True) for r in rows]
536
+
537
+
538
+ async def get_workflow_stats() -> workflow_types.WorkflowStats:
539
+ """获取工作流统计信息 / Fetch workflow statistics with recent run metrics."""
540
+
541
+ async with Rmanager.pg_session() as session:
542
+ async with session.begin():
543
+ active_filter = WorkflowDefinitionTbl.flag == 0
544
+ total_defs = await session.scalar(
545
+ select(func.count()).select_from(WorkflowDefinitionTbl).where(active_filter))
546
+ active_defs = await session.scalar(
547
+ select(func.count())
548
+ .select_from(WorkflowDefinitionTbl)
549
+ .where(and_(active_filter, WorkflowDefinitionTbl.is_active == True))
550
+ )
551
+ scheduled_defs = await session.scalar(
552
+ select(func.count())
553
+ .select_from(WorkflowDefinitionTbl)
554
+ .where(
555
+ and_(
556
+ active_filter,
557
+ WorkflowDefinitionTbl.is_active == True,
558
+ WorkflowDefinitionTbl.schedule != None,
559
+ WorkflowDefinitionTbl.schedule != ''
560
+ )
561
+ )
562
+ )
563
+
564
+ run_active_filter = WorkflowRunTbl.flag == 0
565
+ total_runs = await session.scalar(
566
+ select(func.count()).select_from(WorkflowRunTbl).where(run_active_filter)
567
+ )
568
+ running_runs = await session.scalar(
569
+ select(func.count()).select_from(WorkflowRunTbl).where(
570
+ and_(run_active_filter, WorkflowRunTbl.status == WorkflowStatus.RUNNING.value)
571
+ )
572
+ ) or 0
573
+ pending_runs = await session.scalar(
574
+ select(func.count()).select_from(WorkflowRunTbl).where(
575
+ and_(run_active_filter, WorkflowRunTbl.status == WorkflowStatus.PENDING.value)
576
+ )
577
+ ) or 0
578
+
579
+ runs_by_status_stmt = (
580
+ select(WorkflowRunTbl.status, func.count(WorkflowRunTbl.id))
581
+ .where(run_active_filter)
582
+ .group_by(WorkflowRunTbl.status)
583
+ )
584
+ runs_by_status_res = (await session.execute(runs_by_status_stmt)).all()
585
+ runs_by_status = {str(status): count for status, count in runs_by_status_res}
586
+
587
+ now = datetime.now(timezone.utc)
588
+ since_24h = now - timedelta(hours=24)
589
+ last_24h_filter = and_(run_active_filter, WorkflowRunTbl.started_at >= since_24h)
590
+
591
+ runs_today = await session.scalar(
592
+ select(func.count()).select_from(WorkflowRunTbl).where(last_24h_filter)
593
+ ) or 0
594
+ failed_runs_today = await session.scalar(
595
+ select(func.count())
596
+ .select_from(WorkflowRunTbl)
597
+ .where(and_(last_24h_filter, WorkflowRunTbl.status == WorkflowStatus.FAILURE.value))
598
+ ) or 0
599
+ success_24h = await session.scalar(
600
+ select(func.count())
601
+ .select_from(WorkflowRunTbl)
602
+ .where(and_(last_24h_filter, WorkflowRunTbl.status == WorkflowStatus.SUCCESS.value))
603
+ ) or 0
604
+ success_rate_24h = float(success_24h) / runs_today * 100 if runs_today else 0.0
605
+
606
+ node_active_filter = NodeInstanceTbl.flag == 0
607
+ active_node_statuses = [
608
+ NodeStatus.RUNNING.value,
609
+ NodeStatus.SUSPENDED.value,
610
+ NodeStatus.RETRYING.value,
611
+ ]
612
+ active_node_instances = await session.scalar(
613
+ select(func.count()).select_from(NodeInstanceTbl).where(
614
+ and_(node_active_filter, NodeInstanceTbl.status.in_(active_node_statuses))
615
+ )
616
+ ) or 0
617
+ waiting_node_instances = await session.scalar(
618
+ select(func.count()).select_from(NodeInstanceTbl).where(
619
+ and_(node_active_filter, NodeInstanceTbl.status == NodeStatus.PENDING.value)
620
+ )
621
+ ) or 0
622
+
623
+ inflight_attempts = await session.scalar(
624
+ select(func.count()).select_from(ExecutionLogTbl).where(
625
+ and_(
626
+ ExecutionLogTbl.flag == 0,
627
+ or_(
628
+ ExecutionLogTbl.status == None,
629
+ ExecutionLogTbl.status == ExecutionLogStatus.PENDING.value,
630
+ ),
631
+ ExecutionLogTbl.end_time == None,
632
+ )
633
+ )
634
+ ) or 0
635
+
636
+ return workflow_types.WorkflowStats(
637
+ total_definitions=total_defs or 0,
638
+ active_definitions=active_defs or 0,
639
+ scheduled_definitions=scheduled_defs or 0,
640
+ total_runs=total_runs or 0,
641
+ runs_by_status=runs_by_status,
642
+ runs_today=runs_today,
643
+ failed_runs_today=failed_runs_today,
644
+ success_rate_24h=round(success_rate_24h, 2),
645
+ running_runs=running_runs,
646
+ pending_runs=pending_runs,
647
+ active_node_instances=active_node_instances,
648
+ waiting_node_instances=waiting_node_instances,
649
+ inflight_attempts=inflight_attempts,
650
+ )
651
+
652
+
653
+ # --- Engine Alert Operations ---
654
+
655
+ async def create_engine_alert(
656
+ data: workflow_types.WorkflowEngineAlertCreate,
657
+ ) -> workflow_types.WorkflowEngineAlert:
658
+ """创建引擎告警记录 / Persist a workflow engine alert entry."""
659
+
660
+ payload = data.model_dump(mode="json", exclude_none=True)
661
+ async with Rmanager.pg_session() as session:
662
+ async with session.begin():
663
+ stmt = (
664
+ insert(WorkflowEngineAlertTbl)
665
+ .values(**payload)
666
+ .returning(WorkflowEngineAlertTbl)
667
+ )
668
+ row = (await session.execute(stmt)).scalar_one()
669
+ return workflow_types.WorkflowEngineAlert.model_validate(row, from_attributes=True)
670
+
671
+
672
+ async def list_engine_alerts(
673
+ query: workflow_types.WorkflowEngineAlertQuery,
674
+ ) -> workflow_types.WorkflowEngineAlertListResponse:
675
+ """查询引擎告警列表 / List workflow engine alerts with pagination."""
676
+
677
+ async with Rmanager.pg_session() as session:
678
+ async with session.begin():
679
+ filters = [WorkflowEngineAlertTbl.flag == 0]
680
+ if query.status:
681
+ filters.append(WorkflowEngineAlertTbl.status == query.status.value)
682
+ if query.severity:
683
+ filters.append(WorkflowEngineAlertTbl.severity == query.severity.value)
684
+ if query.run_id:
685
+ try:
686
+ run_uuid = uuid.UUID(query.run_id)
687
+ filters.append(WorkflowEngineAlertTbl.run_id == run_uuid)
688
+ except ValueError:
689
+ filters.append(WorkflowEngineAlertTbl.run_id == uuid.UUID(int=0))
690
+
691
+ base_stmt = select(WorkflowEngineAlertTbl)
692
+ if filters:
693
+ base_stmt = base_stmt.where(and_(*filters))
694
+
695
+ total_stmt = select(func.count()).select_from(base_stmt.subquery())
696
+ total = (await session.execute(total_stmt)).scalar_one() or 0
697
+
698
+ stmt = (
699
+ base_stmt
700
+ .order_by(desc(WorkflowEngineAlertTbl.created_time))
701
+ .limit(query.page_size)
702
+ .offset((query.page - 1) * query.page_size)
703
+ )
704
+ rows = list((await session.execute(stmt)).scalars().all())
705
+ alerts = [workflow_types.WorkflowEngineAlert.model_validate(r, from_attributes=True) for r in rows]
706
+ return workflow_types.WorkflowEngineAlertListResponse(
707
+ list=alerts,
708
+ total=total,
709
+ page=query.page,
710
+ page_size=query.page_size,
711
+ )
712
+
713
+
714
+ async def acknowledge_engine_alert(
715
+ alert_id: uuid.UUID,
716
+ operator: Optional[str],
717
+ note: Optional[str] = None,
718
+ ) -> Optional[workflow_types.WorkflowEngineAlert]:
719
+ """标记引擎告警为已知晓 / Mark an engine alert as acknowledged."""
720
+
721
+ return await _update_engine_alert(
722
+ alert_id,
723
+ WorkflowEngineAlertStatus.ACKED,
724
+ operator,
725
+ note,
726
+ mutate_ack=True,
727
+ )
728
+
729
+
730
+ async def resolve_engine_alert(
731
+ alert_id: uuid.UUID,
732
+ operator: Optional[str],
733
+ note: Optional[str] = None,
734
+ ) -> Optional[workflow_types.WorkflowEngineAlert]:
735
+ """标记引擎告警为已解决 / Mark an engine alert as resolved."""
736
+
737
+ return await _update_engine_alert(
738
+ alert_id,
739
+ WorkflowEngineAlertStatus.RESOLVED,
740
+ operator,
741
+ note,
742
+ mutate_resolved=True,
743
+ )
744
+
745
+
746
+ async def _update_engine_alert(
747
+ alert_id: uuid.UUID,
748
+ status: WorkflowEngineAlertStatus,
749
+ operator: Optional[str],
750
+ note: Optional[str],
751
+ *,
752
+ mutate_ack: bool = False,
753
+ mutate_resolved: bool = False,
754
+ ) -> Optional[workflow_types.WorkflowEngineAlert]:
755
+ """内部通用方法:更新告警状态 / Internal helper to update alert status fields."""
756
+
757
+ async with Rmanager.pg_session() as session:
758
+ async with session.begin():
759
+ now = datetime.now(timezone.utc)
760
+ update_values: Dict[str, Any] = {
761
+ "status": status.value,
762
+ "update_time": func.current_timestamp(),
763
+ }
764
+ if note is not None:
765
+ update_values["note"] = note
766
+
767
+ if mutate_ack:
768
+ update_values.update(
769
+ {
770
+ "acknowledged_by": operator,
771
+ "acknowledged_at": now,
772
+ }
773
+ )
774
+ if mutate_resolved:
775
+ update_values.update(
776
+ {
777
+ "resolved_by": operator,
778
+ "resolved_at": now,
779
+ }
780
+ )
781
+ # 如果之前未标记 ack,则同步填充
782
+ update_values.setdefault("acknowledged_by", operator)
783
+ update_values.setdefault("acknowledged_at", now)
784
+
785
+ status_filter = []
786
+ if mutate_ack:
787
+ status_filter.append(WorkflowEngineAlertTbl.status == WorkflowEngineAlertStatus.OPEN.value)
788
+ elif mutate_resolved:
789
+ status_filter.append(WorkflowEngineAlertTbl.status != WorkflowEngineAlertStatus.RESOLVED.value)
790
+
791
+ stmt = (
792
+ update(WorkflowEngineAlertTbl)
793
+ .where(
794
+ and_(
795
+ WorkflowEngineAlertTbl.id == alert_id,
796
+ WorkflowEngineAlertTbl.flag == 0,
797
+ *status_filter,
798
+ )
799
+ )
800
+ .values(**update_values)
801
+ .returning(WorkflowEngineAlertTbl)
802
+ )
803
+ row = (await session.execute(stmt)).scalar_one_or_none()
804
+ return workflow_types.WorkflowEngineAlert.model_validate(row, from_attributes=True) if row else None