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.
- cancan_microstack/__init__.py +14 -0
- cancan_microstack/__version__.py +10 -0
- cancan_microstack/assets/__init__.py +6 -0
- cancan_microstack/assets/builds/caddy/Caddyfile +187 -0
- cancan_microstack/assets/builds/caddy/DEPLOYMENT.md +303 -0
- cancan_microstack/assets/builds/caddy/Dockerfile +46 -0
- cancan_microstack/assets/builds/caddy/README.md +343 -0
- cancan_microstack/assets/builds/caddy/geoip/README.md +5 -0
- cancan_microstack/assets/builds/caddy/start.sh +78 -0
- cancan_microstack/assets/builds/caddy/waf/coraza.conf +179 -0
- cancan_microstack/assets/builds/service/Dockerfile +59 -0
- cancan_microstack/assets/builds/service/README.md +13 -0
- cancan_microstack/assets/ddl/create_db.sql +22 -0
- cancan_microstack/assets/ddl/infra/execution_log_tbl.sql +46 -0
- cancan_microstack/assets/ddl/infra/node_instance_tbl.sql +56 -0
- cancan_microstack/assets/ddl/infra/service_action_log_tbl.sql +36 -0
- cancan_microstack/assets/ddl/infra/service_config_tbl.sql +26 -0
- cancan_microstack/assets/ddl/infra/service_info_tbl.sql +45 -0
- cancan_microstack/assets/ddl/infra/service_instance_tbl.sql +54 -0
- cancan_microstack/assets/ddl/infra/service_operation_tbl.sql +47 -0
- cancan_microstack/assets/ddl/infra/workflow_definition_tbl.sql +60 -0
- cancan_microstack/assets/ddl/infra/workflow_definition_version_tbl.sql +35 -0
- cancan_microstack/assets/ddl/infra/workflow_engine_alert_tbl.sql +34 -0
- cancan_microstack/assets/ddl/infra/workflow_run_tbl.sql +52 -0
- cancan_microstack/assets/ddl/ops/admin_user_tbl.sql +34 -0
- cancan_microstack/assets/ddl/ops/caddy_access_log_tbl.sql +91 -0
- cancan_microstack/assets/ddl/ops/caddy_certificate_tbl.sql +59 -0
- cancan_microstack/assets/ddl/ops/caddy_rate_limit_tbl.sql +64 -0
- cancan_microstack/assets/ddl/ops/caddy_route_tbl.sql +63 -0
- cancan_microstack/assets/ddl/ops/caddy_stats_tbl.sql +77 -0
- cancan_microstack/assets/ddl/trigger.sql +21 -0
- cancan_microstack/assets/docker/docker-compose.infra.yml +401 -0
- cancan_microstack/assets/scripts/README.md +195 -0
- cancan_microstack/assets/scripts/docker/build_images.sh +44 -0
- cancan_microstack/assets/scripts/docker/force_rebuild_images.sh +38 -0
- cancan_microstack/assets/scripts/docker/rebuild_all.sh +34 -0
- cancan_microstack/assets/scripts/docker/rebuild_compose.sh +61 -0
- cancan_microstack/assets/scripts/docker/restart.sh +35 -0
- cancan_microstack/assets/scripts/docker/restart_compose.sh +35 -0
- cancan_microstack/assets/scripts/docker/start.sh +78 -0
- cancan_microstack/assets/scripts/docker/start_all.sh +46 -0
- cancan_microstack/assets/scripts/docker/start_compose.sh +66 -0
- cancan_microstack/assets/scripts/docker/stop.sh +67 -0
- cancan_microstack/assets/scripts/docker/stop_all.sh +38 -0
- cancan_microstack/assets/scripts/docker/stop_compose.sh +38 -0
- cancan_microstack/assets/scripts/podman/build_images_podman.sh +59 -0
- cancan_microstack/assets/scripts/podman/cleanup_podman.sh +25 -0
- cancan_microstack/assets/scripts/podman/force_rebuild_images_podman.sh +56 -0
- cancan_microstack/assets/scripts/podman/rebuild_all_podman.sh +37 -0
- cancan_microstack/assets/scripts/podman/rebuild_compose_podman.sh +60 -0
- cancan_microstack/assets/scripts/podman/restart_compose_podman.sh +73 -0
- cancan_microstack/assets/scripts/podman/start_all_podman.sh +66 -0
- cancan_microstack/assets/scripts/podman/start_compose_podman.sh +80 -0
- cancan_microstack/assets/scripts/podman/start_podman.sh +91 -0
- cancan_microstack/assets/scripts/podman/stop.sh +73 -0
- cancan_microstack/assets/scripts/podman/stop_all_podman.sh +34 -0
- cancan_microstack/assets/scripts/podman/stop_compose_podman.sh +58 -0
- cancan_microstack/assets/scripts/start_controllersrv.sh +9 -0
- cancan_microstack/assets/scripts/utils/check_all_db_tables.sh +104 -0
- cancan_microstack/assets/scripts/utils/check_env.sh +177 -0
- cancan_microstack/assets/scripts/utils/check_service_management_deployment.sh +225 -0
- cancan_microstack/assets/scripts/utils/deploy_service_management.sh +176 -0
- cancan_microstack/assets/scripts/utils/force_reload_infrasrv.sh +52 -0
- cancan_microstack/assets/scripts/utils/monitor_service_management.sh +187 -0
- cancan_microstack/assets/scripts/utils/reset_postgres_volume.sh +68 -0
- cancan_microstack/assets/scripts/utils/test_async_operations.sh +141 -0
- cancan_microstack/assets/scripts/utils/verify_real_operations.sh +76 -0
- cancan_microstack/assets/service/Dockerfile +65 -0
- cancan_microstack/assets/www/adminops/assets/AppEmpty.vue_vue_type_script_setup_true_lang-BOKUurnM.js +1 -0
- cancan_microstack/assets/www/adminops/assets/ConfigManage-DKV5YOUz.js +1 -0
- cancan_microstack/assets/www/adminops/assets/ConfigManage-Y5bhy7wG.css +1 -0
- cancan_microstack/assets/www/adminops/assets/ConsoleManage-8ljYvCW2.js +1 -0
- cancan_microstack/assets/www/adminops/assets/ConsoleManage-BWpyqbuQ.css +1 -0
- cancan_microstack/assets/www/adminops/assets/DashboardNew-B9Nf1OPl.js +1 -0
- cancan_microstack/assets/www/adminops/assets/DashboardNew-DYWZKQ1V.css +1 -0
- cancan_microstack/assets/www/adminops/assets/LogSearch-CA0Jhe78.js +1 -0
- cancan_microstack/assets/www/adminops/assets/LogSearch-CCZfTNPF.css +1 -0
- cancan_microstack/assets/www/adminops/assets/LoginView-BId3kP3M.css +1 -0
- cancan_microstack/assets/www/adminops/assets/LoginView-BQZTV_Qy.js +1 -0
- cancan_microstack/assets/www/adminops/assets/OperationProgressDialog-BdEYwqFq.js +1 -0
- cancan_microstack/assets/www/adminops/assets/OperationProgressDialog-D-pASR8G.css +1 -0
- cancan_microstack/assets/www/adminops/assets/PageContainer-Byss-yUC.js +1 -0
- cancan_microstack/assets/www/adminops/assets/PageContainer-C3nSZwM7.css +1 -0
- cancan_microstack/assets/www/adminops/assets/RateLimitManage-BDI8jLpC.css +1 -0
- cancan_microstack/assets/www/adminops/assets/RateLimitManage-DJY4NiF-.js +1 -0
- cancan_microstack/assets/www/adminops/assets/RouteManage-DaUQ4QLw.css +1 -0
- cancan_microstack/assets/www/adminops/assets/RouteManage-w9XCU0UA.js +1 -0
- cancan_microstack/assets/www/adminops/assets/ServiceCard-BFzHe6Tw.css +1 -0
- cancan_microstack/assets/www/adminops/assets/ServiceCard-BJUhWnA-.js +1 -0
- cancan_microstack/assets/www/adminops/assets/ServiceDetail-Cw24WuKp.js +1 -0
- cancan_microstack/assets/www/adminops/assets/ServiceDetail-Yum47zdB.css +1 -0
- cancan_microstack/assets/www/adminops/assets/ServiceList-C7ryvbhE.js +1 -0
- cancan_microstack/assets/www/adminops/assets/ServiceList-Cgd01fUx.css +1 -0
- cancan_microstack/assets/www/adminops/assets/ServiceLogs-COpG9H0h.js +1 -0
- cancan_microstack/assets/www/adminops/assets/ServiceLogs-H_Alq0cf.css +1 -0
- cancan_microstack/assets/www/adminops/assets/StatsOverview-D0TwMQkA.js +39 -0
- cancan_microstack/assets/www/adminops/assets/StatsOverview-lqAN6pqM.css +1 -0
- cancan_microstack/assets/www/adminops/assets/TotpBindView-CWlAmzFt.js +1 -0
- cancan_microstack/assets/www/adminops/assets/TotpBindView-HoQC1lhx.css +1 -0
- cancan_microstack/assets/www/adminops/assets/TotpVerifyView-BHN1VtX1.css +1 -0
- cancan_microstack/assets/www/adminops/assets/TotpVerifyView-D3w_lZk8.js +1 -0
- cancan_microstack/assets/www/adminops/assets/WorkflowCenter-DU_mpIA0.css +1 -0
- cancan_microstack/assets/www/adminops/assets/WorkflowCenter-i50rZyxN.js +1 -0
- cancan_microstack/assets/www/adminops/assets/WorkflowDesigner-CnHokPL9.js +1 -0
- cancan_microstack/assets/www/adminops/assets/WorkflowDesigner-DaZaZpLd.css +1 -0
- cancan_microstack/assets/www/adminops/assets/WorkflowRuns-B09hK48c.js +1 -0
- cancan_microstack/assets/www/adminops/assets/WorkflowRuns-wGutKIIU.css +1 -0
- cancan_microstack/assets/www/adminops/assets/caddy-nnCKf8fG.js +1 -0
- cancan_microstack/assets/www/adminops/assets/format-Cuzxgna9.js +1 -0
- cancan_microstack/assets/www/adminops/assets/index-CiFlm8oc.js +64 -0
- cancan_microstack/assets/www/adminops/assets/index-UW0T1Dkc.css +1 -0
- cancan_microstack/assets/www/adminops/assets/service-BYlgGPs_.js +1 -0
- cancan_microstack/assets/www/adminops/assets/service-operation-6GzLw2Z1.js +1 -0
- cancan_microstack/assets/www/adminops/assets/style-CcIXnQ5y.css +1 -0
- cancan_microstack/assets/www/adminops/assets/style-lRnStdGu.js +39 -0
- cancan_microstack/assets/www/adminops/assets/useDebounce-BRlqfXqf.js +1 -0
- cancan_microstack/assets/www/adminops/assets/workflow-CUXs39Ac.js +1 -0
- cancan_microstack/assets/www/adminops/index.html +16 -0
- cancan_microstack/assets/www/adminops/vite.svg +1 -0
- cancan_microstack/cli/__init__.py +14 -0
- cancan_microstack/cli/__main__.py +9 -0
- cancan_microstack/cli/main.py +552 -0
- cancan_microstack/cmd/__init__.py +54 -0
- cancan_microstack/cmd/cancan/__init__.py +12 -0
- cancan_microstack/cmd/cancan/run.py +395 -0
- cancan_microstack/cmd/controllersrv/__init__.py +0 -0
- cancan_microstack/cmd/controllersrv/run.py +131 -0
- cancan_microstack/cmd/infrasrv/__init__.py +5 -0
- cancan_microstack/cmd/infrasrv/run.py +100 -0
- cancan_microstack/cmd/opsbffsrv/__init__.py +5 -0
- cancan_microstack/cmd/opsbffsrv/run.py +96 -0
- cancan_microstack/core/__init__.py +5 -0
- cancan_microstack/core/assets.py +123 -0
- cancan_microstack/core/compose_builder.py +102 -0
- cancan_microstack/core/doctor.py +152 -0
- cancan_microstack/core/microstack.py +71 -0
- cancan_microstack/core/runner.py +56 -0
- cancan_microstack/core/stack_manager.py +186 -0
- cancan_microstack/public/__init__.py +7 -0
- cancan_microstack/public/api/__init__.py +1 -0
- cancan_microstack/public/api/controllersrv_client.py +277 -0
- cancan_microstack/public/api/infrasrv_client.py +404 -0
- cancan_microstack/public/const/__init__.py +1 -0
- cancan_microstack/public/const/action_consts.py +18 -0
- cancan_microstack/public/const/app_consts.py +42 -0
- cancan_microstack/public/const/caddy_consts.py +22 -0
- cancan_microstack/public/const/controllersrv_consts.py +163 -0
- cancan_microstack/public/const/docker_consts.py +15 -0
- cancan_microstack/public/const/error.py +56 -0
- cancan_microstack/public/const/health_consts.py +52 -0
- cancan_microstack/public/const/hook_enums.py +56 -0
- cancan_microstack/public/const/logging_enums.py +13 -0
- cancan_microstack/public/const/metrics_enums.py +36 -0
- cancan_microstack/public/const/monitor_enums.py +26 -0
- cancan_microstack/public/const/operation_consts.py +53 -0
- cancan_microstack/public/const/opsbffsrv_error.py +92 -0
- cancan_microstack/public/const/overrides_consts.py +13 -0
- cancan_microstack/public/const/redis.py +17 -0
- cancan_microstack/public/const/service_consts.py +15 -0
- cancan_microstack/public/const/workflow_consts.py +65 -0
- cancan_microstack/public/error.py +41 -0
- cancan_microstack/public/logging/__init__.py +0 -0
- cancan_microstack/public/logging/initializer.py +109 -0
- cancan_microstack/public/logging/mq_handler.py +279 -0
- cancan_microstack/public/schemas/__init__.py +1 -0
- cancan_microstack/public/schemas/caddy/__init__.py +381 -0
- cancan_microstack/public/schemas/caddy/analysis.py +90 -0
- cancan_microstack/public/schemas/caddy/route.py +18 -0
- cancan_microstack/public/schemas/common.py +79 -0
- cancan_microstack/public/schemas/controllersrv/__init__.py +3 -0
- cancan_microstack/public/schemas/controllersrv/async_requests.py +30 -0
- cancan_microstack/public/schemas/controllersrv/compose_models.py +47 -0
- cancan_microstack/public/schemas/controllersrv/const.py +24 -0
- cancan_microstack/public/schemas/controllersrv/docker_models.py +45 -0
- cancan_microstack/public/schemas/controllersrv/docker_responses.py +104 -0
- cancan_microstack/public/schemas/controllersrv/requests.py +54 -0
- cancan_microstack/public/schemas/controllersrv/responses.py +124 -0
- cancan_microstack/public/schemas/controllersrv/task_models.py +102 -0
- cancan_microstack/public/schemas/controllersrv/validation.py +23 -0
- cancan_microstack/public/schemas/hook_metrics.py +124 -0
- cancan_microstack/public/schemas/hooks.py +39 -0
- cancan_microstack/public/schemas/infra/__init__.py +0 -0
- cancan_microstack/public/schemas/infra/cleanup.py +25 -0
- cancan_microstack/public/schemas/infra/container.py +74 -0
- cancan_microstack/public/schemas/infra/enums.py +135 -0
- cancan_microstack/public/schemas/infra/health_check.py +42 -0
- cancan_microstack/public/schemas/infra/hook_log.py +42 -0
- cancan_microstack/public/schemas/infra/operation.py +90 -0
- cancan_microstack/public/schemas/infra/overview.py +25 -0
- cancan_microstack/public/schemas/infra/push.py +33 -0
- cancan_microstack/public/schemas/infra/service_action_log.py +47 -0
- cancan_microstack/public/schemas/infra/service_config.py +10 -0
- cancan_microstack/public/schemas/infra/service_info.py +69 -0
- cancan_microstack/public/schemas/infra/service_instance.py +93 -0
- cancan_microstack/public/schemas/infra/service_management.py +152 -0
- cancan_microstack/public/schemas/infra/service_operation.py +79 -0
- cancan_microstack/public/schemas/infra/service_registry.py +158 -0
- cancan_microstack/public/schemas/infra/status_types.py +19 -0
- cancan_microstack/public/schemas/infra/workflow.py +566 -0
- cancan_microstack/public/schemas/logging/__init__.py +1 -0
- cancan_microstack/public/schemas/logging/log_event.py +121 -0
- cancan_microstack/public/schemas/opsbffsrv/__init__.py +1 -0
- cancan_microstack/public/schemas/opsbffsrv/async_ops.py +17 -0
- cancan_microstack/public/schemas/opsbffsrv/db_admin.py +147 -0
- cancan_microstack/public/schemas/opsbffsrv/db_init.py +48 -0
- cancan_microstack/public/schemas/opsbffsrv/service_config.py +89 -0
- cancan_microstack/public/schemas/opsbffsrv/service_logs.py +54 -0
- cancan_microstack/public/schemas/service_operation.py +24 -0
- cancan_microstack/public/schemas/service_registry.py +40 -0
- cancan_microstack/public/types/__init__.py +7 -0
- cancan_microstack/public/web/__init__.py +0 -0
- cancan_microstack/public/web/config_value.py +105 -0
- cancan_microstack/public/web/server.py +385 -0
- cancan_microstack/py.typed +0 -0
- cancan_microstack/runtime/__init__.py +0 -0
- cancan_microstack/runtime/compose_cmd.py +228 -0
- cancan_microstack/runtime/host_daemon.py +318 -0
- cancan_microstack/runtime/overrides.py +103 -0
- cancan_microstack/runtime/resources.py +25 -0
- cancan_microstack/runtime/workspace.py +94 -0
- cancan_microstack/services/__init__.py +0 -0
- cancan_microstack/services/controllersrv/__init__.py +8 -0
- cancan_microstack/services/controllersrv/application/__init__.py +0 -0
- cancan_microstack/services/controllersrv/application/docker_compose_app.py +427 -0
- cancan_microstack/services/controllersrv/conf/__init__.py +0 -0
- cancan_microstack/services/controllersrv/conf/config.py +76 -0
- cancan_microstack/services/controllersrv/conf/settings.py +54 -0
- cancan_microstack/services/controllersrv/domain/__init__.py +0 -0
- cancan_microstack/services/controllersrv/domain/docker_compose/__init__.py +0 -0
- cancan_microstack/services/controllersrv/domain/docker_compose/docker_compose_domain.py +278 -0
- cancan_microstack/services/controllersrv/domain/service_validator.py +327 -0
- cancan_microstack/services/controllersrv/domain/task/__init__.py +17 -0
- cancan_microstack/services/controllersrv/domain/task/task_queue.py +286 -0
- cancan_microstack/services/controllersrv/domain/task/task_worker.py +495 -0
- cancan_microstack/services/controllersrv/infrastructure/__init__.py +0 -0
- cancan_microstack/services/controllersrv/interface/__init__.py +0 -0
- cancan_microstack/services/controllersrv/interface/api/__init__.py +0 -0
- cancan_microstack/services/controllersrv/interface/api/docker_control_api.py +470 -0
- cancan_microstack/services/controllersrv/router.py +132 -0
- cancan_microstack/services/infrasrv/__init__.py +4 -0
- cancan_microstack/services/infrasrv/application/__init__.py +0 -0
- cancan_microstack/services/infrasrv/application/health_check_app.py +24 -0
- cancan_microstack/services/infrasrv/application/logging/__init__.py +1 -0
- cancan_microstack/services/infrasrv/application/logging/log_ingestion_service.py +183 -0
- cancan_microstack/services/infrasrv/application/service_config.py +22 -0
- cancan_microstack/services/infrasrv/application/service_logs_app.py +53 -0
- cancan_microstack/services/infrasrv/application/service_management_app.py +689 -0
- cancan_microstack/services/infrasrv/application/service_operation_tracker.py +251 -0
- cancan_microstack/services/infrasrv/application/service_registry.py +53 -0
- cancan_microstack/services/infrasrv/application/workflow/__init__.py +0 -0
- cancan_microstack/services/infrasrv/application/workflow/workflow_app.py +991 -0
- cancan_microstack/services/infrasrv/application/workflow/workflow_queue.py +302 -0
- cancan_microstack/services/infrasrv/application/workflow/workflow_tasks.py +46 -0
- cancan_microstack/services/infrasrv/application/workflow/workflow_worker_runtime.py +122 -0
- cancan_microstack/services/infrasrv/conf/__init__.py +0 -0
- cancan_microstack/services/infrasrv/conf/config.py +98 -0
- cancan_microstack/services/infrasrv/domain/__init__.py +0 -0
- cancan_microstack/services/infrasrv/domain/health_check/__init__.py +3 -0
- cancan_microstack/services/infrasrv/domain/health_check/health_check_domain.py +576 -0
- cancan_microstack/services/infrasrv/domain/hooks/__init__.py +19 -0
- cancan_microstack/services/infrasrv/domain/hooks/builtin_hooks.py +308 -0
- cancan_microstack/services/infrasrv/domain/hooks/hook_registry.py +43 -0
- cancan_microstack/services/infrasrv/domain/hooks/hooks_log_utils.py +275 -0
- cancan_microstack/services/infrasrv/domain/hooks/init.py +17 -0
- cancan_microstack/services/infrasrv/domain/hooks/metrics.py +205 -0
- cancan_microstack/services/infrasrv/domain/hooks/pre_registration_hooks.py +490 -0
- cancan_microstack/services/infrasrv/domain/registry/__init__.py +0 -0
- cancan_microstack/services/infrasrv/domain/registry/service_registry.py +509 -0
- cancan_microstack/services/infrasrv/domain/service_config/__init__.py +0 -0
- cancan_microstack/services/infrasrv/domain/service_config/service_config.py +50 -0
- cancan_microstack/services/infrasrv/domain/service_logs/__init__.py +0 -0
- cancan_microstack/services/infrasrv/domain/service_logs/service_logs_domain.py +51 -0
- cancan_microstack/services/infrasrv/domain/workflow/__init__.py +4 -0
- cancan_microstack/services/infrasrv/domain/workflow/engine.py +159 -0
- cancan_microstack/services/infrasrv/domain/workflow/node_handlers.py +509 -0
- cancan_microstack/services/infrasrv/domain/workflow/workflow_domain.py +164 -0
- cancan_microstack/services/infrasrv/infrastructure/__init__.py +0 -0
- cancan_microstack/services/infrasrv/infrastructure/api/__init__.py +0 -0
- cancan_microstack/services/infrasrv/infrastructure/api/controllersrv_api.py +165 -0
- cancan_microstack/services/infrasrv/infrastructure/cache/__init__.py +0 -0
- cancan_microstack/services/infrasrv/infrastructure/cache/service_registry_cache.py +174 -0
- cancan_microstack/services/infrasrv/infrastructure/db/__init__.py +0 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/__init__.py +0 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/execution_log_tbl.py +53 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/node_instance_tbl.py +55 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/service_action_log_tbl.py +44 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/service_config_tbl.py +30 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/service_info_tbl.py +59 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/service_instance_tbl.py +88 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/service_operation_tbl.py +73 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/workflow_definition_tbl.py +55 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/workflow_definition_version_tbl.py +43 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/workflow_engine_alert_tbl.py +57 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/workflow_run_tbl.py +56 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/__init__.py +0 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/service_action_log_op.py +239 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/service_config.py +80 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/service_config_manager.py +198 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/service_info_op.py +297 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/service_instance_op.py +688 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/service_operation_op.py +387 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/service_registry.py +124 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/workflow_op.py +804 -0
- cancan_microstack/services/infrasrv/infrastructure/ddl_manager.py +31 -0
- cancan_microstack/services/infrasrv/infrastructure/mongo/__init__.py +1 -0
- cancan_microstack/services/infrasrv/infrastructure/mongo/log_repository.py +129 -0
- cancan_microstack/services/infrasrv/interface/__init__.py +0 -0
- cancan_microstack/services/infrasrv/interface/api/__init__.py +0 -0
- cancan_microstack/services/infrasrv/interface/api/health_check_api.py +29 -0
- cancan_microstack/services/infrasrv/interface/api/hooks.py +284 -0
- cancan_microstack/services/infrasrv/interface/api/internal.py +49 -0
- cancan_microstack/services/infrasrv/interface/api/internal_instance_api.py +265 -0
- cancan_microstack/services/infrasrv/interface/api/internal_operation_api.py +206 -0
- cancan_microstack/services/infrasrv/interface/api/service_config.py +50 -0
- cancan_microstack/services/infrasrv/interface/api/service_logs_api.py +49 -0
- cancan_microstack/services/infrasrv/interface/api/service_management_api.py +113 -0
- cancan_microstack/services/infrasrv/interface/api/service_registry.py +117 -0
- cancan_microstack/services/infrasrv/interface/api/workflow_api.py +303 -0
- cancan_microstack/services/infrasrv/interface/schedule/__init__.py +0 -0
- cancan_microstack/services/infrasrv/interface/schedule/cleanup.py +13 -0
- cancan_microstack/services/infrasrv/interface/schedule/health_check.py +27 -0
- cancan_microstack/services/infrasrv/interface/schedule/log_cleanup.py +26 -0
- cancan_microstack/services/infrasrv/interface/schedule/operation_tracker.py +25 -0
- cancan_microstack/services/infrasrv/interface/schedule/scheduler.py +39 -0
- cancan_microstack/services/infrasrv/interface/schedule/workflow_scheduler.py +115 -0
- cancan_microstack/services/infrasrv/router.py +341 -0
- cancan_microstack/services/opsbffsrv/__init__.py +4 -0
- cancan_microstack/services/opsbffsrv/application/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/application/async_operation_app.py +150 -0
- cancan_microstack/services/opsbffsrv/application/auth_app.py +285 -0
- cancan_microstack/services/opsbffsrv/application/caddy/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/application/caddy/access_log_analysis_app.py +344 -0
- cancan_microstack/services/opsbffsrv/application/caddy/access_log_ingestion_service.py +169 -0
- cancan_microstack/services/opsbffsrv/application/caddy/certificate_management_app.py +355 -0
- cancan_microstack/services/opsbffsrv/application/caddy/rate_limit_management_app.py +496 -0
- cancan_microstack/services/opsbffsrv/application/caddy/route_management_app.py +401 -0
- cancan_microstack/services/opsbffsrv/application/caddy/stats_aggregation_app.py +364 -0
- cancan_microstack/services/opsbffsrv/application/db_admin_app.py +103 -0
- cancan_microstack/services/opsbffsrv/application/db_init_app.py +283 -0
- cancan_microstack/services/opsbffsrv/application/logging/__init__.py +1 -0
- cancan_microstack/services/opsbffsrv/application/logging/log_query_app.py +28 -0
- cancan_microstack/services/opsbffsrv/application/service_config.py +158 -0
- cancan_microstack/services/opsbffsrv/application/service_logs_app.py +74 -0
- cancan_microstack/services/opsbffsrv/application/service_registry.py +36 -0
- cancan_microstack/services/opsbffsrv/application/workflow_ops_app.py +730 -0
- cancan_microstack/services/opsbffsrv/conf/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/conf/config.py +224 -0
- cancan_microstack/services/opsbffsrv/domain/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/domain/auth/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/domain/auth/admin_init.py +38 -0
- cancan_microstack/services/opsbffsrv/domain/auth/auth_domain.py +108 -0
- cancan_microstack/services/opsbffsrv/domain/caddy/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/domain/caddy/access_log_analysis.py +358 -0
- cancan_microstack/services/opsbffsrv/domain/caddy/certificate_management.py +325 -0
- cancan_microstack/services/opsbffsrv/domain/caddy/default_routes.py +53 -0
- cancan_microstack/services/opsbffsrv/domain/caddy/rate_limit_management.py +308 -0
- cancan_microstack/services/opsbffsrv/domain/caddy/route_management.py +279 -0
- cancan_microstack/services/opsbffsrv/domain/caddy/stats_aggregation.py +654 -0
- cancan_microstack/services/opsbffsrv/domain/db_admin/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/domain/db_admin/db_admin_domain.py +118 -0
- cancan_microstack/services/opsbffsrv/domain/db_init/__init__.py +3 -0
- cancan_microstack/services/opsbffsrv/domain/db_init/db_init_domain.py +358 -0
- cancan_microstack/services/opsbffsrv/domain/logging/__init__.py +1 -0
- cancan_microstack/services/opsbffsrv/domain/logging/log_query_domain.py +99 -0
- cancan_microstack/services/opsbffsrv/domain/service_config/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/domain/service_config/service_config.py +81 -0
- cancan_microstack/services/opsbffsrv/domain/service_registry/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/domain/service_registry/service_registry.py +292 -0
- cancan_microstack/services/opsbffsrv/infrastructure/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/infrastructure/api/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/infrastructure/api/infrasrv_api.py +242 -0
- cancan_microstack/services/opsbffsrv/infrastructure/auth/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/infrastructure/auth/captcha_service.py +67 -0
- cancan_microstack/services/opsbffsrv/infrastructure/auth/password_service.py +12 -0
- cancan_microstack/services/opsbffsrv/infrastructure/auth/redis_store.py +131 -0
- cancan_microstack/services/opsbffsrv/infrastructure/auth/totp_service.py +59 -0
- cancan_microstack/services/opsbffsrv/infrastructure/caddy/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/infrastructure/caddy/access_log_parser.py +307 -0
- cancan_microstack/services/opsbffsrv/infrastructure/caddy/admin_api_client.py +678 -0
- cancan_microstack/services/opsbffsrv/infrastructure/caddy/ip_geo_locator.py +176 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/admin_user_tbl.py +33 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/caddy_access_log_tbl.py +90 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/caddy_certificate_tbl.py +65 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/caddy_rate_limit_tbl.py +69 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/caddy_route_tbl.py +66 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/caddy_stats_tbl.py +78 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/service_action_log_tbl.py +44 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/service_config_tbl.py +30 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/service_info_tbl.py +51 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/service_instance_tbl.py +68 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/admin_user_operate.py +59 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/caddy_access_log.py +531 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/caddy_certificate.py +451 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/caddy_rate_limit.py +360 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/caddy_route.py +271 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/caddy_stats.py +343 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/service_action_log_op.py +57 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/service_config.py +86 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/service_info_op.py +79 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/service_instance.py +58 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/service_registry.py +138 -0
- cancan_microstack/services/opsbffsrv/infrastructure/ddl_manager.py +31 -0
- cancan_microstack/services/opsbffsrv/infrastructure/mongo/__init__.py +1 -0
- cancan_microstack/services/opsbffsrv/infrastructure/mongo/log_query_repository.py +87 -0
- cancan_microstack/services/opsbffsrv/interface/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/interface/api/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/interface/api/async_operation_api.py +137 -0
- cancan_microstack/services/opsbffsrv/interface/api/auth_api.py +113 -0
- cancan_microstack/services/opsbffsrv/interface/api/caddy/__init__.py +3 -0
- cancan_microstack/services/opsbffsrv/interface/api/caddy/access_log_api.py +174 -0
- cancan_microstack/services/opsbffsrv/interface/api/caddy/certificate_api.py +235 -0
- cancan_microstack/services/opsbffsrv/interface/api/caddy/rate_limit_api.py +302 -0
- cancan_microstack/services/opsbffsrv/interface/api/caddy/route_api.py +250 -0
- cancan_microstack/services/opsbffsrv/interface/api/caddy/stats_api.py +243 -0
- cancan_microstack/services/opsbffsrv/interface/api/db_admin_api.py +62 -0
- cancan_microstack/services/opsbffsrv/interface/api/db_init_api.py +109 -0
- cancan_microstack/services/opsbffsrv/interface/api/instance_management_api.py +165 -0
- cancan_microstack/services/opsbffsrv/interface/api/log_query_api.py +41 -0
- cancan_microstack/services/opsbffsrv/interface/api/mongo_express_proxy_api.py +181 -0
- cancan_microstack/services/opsbffsrv/interface/api/pgweb_proxy_api.py +154 -0
- cancan_microstack/services/opsbffsrv/interface/api/rabbitmq_mgmt_proxy_api.py +518 -0
- cancan_microstack/services/opsbffsrv/interface/api/redis_commander_proxy_api.py +133 -0
- cancan_microstack/services/opsbffsrv/interface/api/service_config.py +146 -0
- cancan_microstack/services/opsbffsrv/interface/api/service_logs_api.py +81 -0
- cancan_microstack/services/opsbffsrv/interface/api/service_registry.py +66 -0
- cancan_microstack/services/opsbffsrv/interface/api/workflow_ops_api.py +413 -0
- cancan_microstack/services/opsbffsrv/interface/middleware/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/interface/middleware/auth_middleware.py +52 -0
- cancan_microstack/services/opsbffsrv/router.py +901 -0
- cancan_microstack/utils/__init__.py +1 -0
- cancan_microstack/utils/container_env.py +218 -0
- cancan_microstack-0.0.1.dist-info/METADATA +155 -0
- cancan_microstack-0.0.1.dist-info/RECORD +440 -0
- cancan_microstack-0.0.1.dist-info/WHEEL +5 -0
- cancan_microstack-0.0.1.dist-info/entry_points.txt +2 -0
- cancan_microstack-0.0.1.dist-info/licenses/LICENSE +21 -0
- cancan_microstack-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""
|
|
2
|
+
工作流执行引擎领域服务
|
|
3
|
+
Workflow Execution Engine Domain Service
|
|
4
|
+
"""
|
|
5
|
+
from typing import (
|
|
6
|
+
Any,
|
|
7
|
+
List,
|
|
8
|
+
Tuple,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from cancan_microstack.public.schemas.infra.enums import (
|
|
12
|
+
NodeStatus,
|
|
13
|
+
NodeType,
|
|
14
|
+
)
|
|
15
|
+
from cancan_microstack.public.schemas.infra.workflow import (
|
|
16
|
+
NodeConfig,
|
|
17
|
+
WorkflowExecutionContext,
|
|
18
|
+
WorkflowDefinition,
|
|
19
|
+
LogicNodeConfig,
|
|
20
|
+
ForkNodeConfig,
|
|
21
|
+
LoopNodeConfig,
|
|
22
|
+
)
|
|
23
|
+
from .node_handlers import (
|
|
24
|
+
StartNodeHandler,
|
|
25
|
+
ActionNodeHandler,
|
|
26
|
+
TransformNodeHandler,
|
|
27
|
+
LogicNodeHandler,
|
|
28
|
+
ForkNodeHandler,
|
|
29
|
+
JoinNodeHandler,
|
|
30
|
+
LoopNodeHandler,
|
|
31
|
+
EndNodeHandler,
|
|
32
|
+
)
|
|
33
|
+
from linglong_web.utils import logger
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class WorkflowEngine:
|
|
37
|
+
"""
|
|
38
|
+
工作流引擎,负责编排和执行节点逻辑。
|
|
39
|
+
The workflow engine, responsible for orchestrating and executing node logic.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self):
|
|
43
|
+
# 注册可用的节点处理器,便于根据类型动态分发
|
|
44
|
+
# Register available node handlers so we can dispatch by type at runtime
|
|
45
|
+
self._handlers = {
|
|
46
|
+
NodeType.START: StartNodeHandler,
|
|
47
|
+
NodeType.ACTION: ActionNodeHandler,
|
|
48
|
+
NodeType.TRANSFORM: TransformNodeHandler,
|
|
49
|
+
NodeType.LOGIC: LogicNodeHandler,
|
|
50
|
+
NodeType.FORK: ForkNodeHandler,
|
|
51
|
+
NodeType.JOIN: JoinNodeHandler,
|
|
52
|
+
NodeType.LOOP: LoopNodeHandler,
|
|
53
|
+
NodeType.END: EndNodeHandler,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async def process_node(
|
|
57
|
+
self,
|
|
58
|
+
workflow_def: WorkflowDefinition,
|
|
59
|
+
node_config: NodeConfig,
|
|
60
|
+
context: WorkflowExecutionContext,
|
|
61
|
+
) -> Tuple[Any, NodeStatus, List[str], int]:
|
|
62
|
+
"""
|
|
63
|
+
处理单个节点并返回结果、状态、下一个节点ID和新的循环索引。
|
|
64
|
+
Process a single node and return the result, status, next node IDs, and new loop index.
|
|
65
|
+
"""
|
|
66
|
+
# 1. 通过节点类型选择处理器 / Lookup handler class by node type
|
|
67
|
+
handler_class = self._handlers.get(node_config.type)
|
|
68
|
+
if not handler_class:
|
|
69
|
+
logger.warning(f"No handler found for node type: {node_config.type}")
|
|
70
|
+
return None, NodeStatus.FAILURE, [], context.loop_index
|
|
71
|
+
|
|
72
|
+
handler = handler_class(node_config)
|
|
73
|
+
|
|
74
|
+
# 2. 特定处理器需要额外依赖 / Some handlers (e.g., JOIN) require extra dependencies
|
|
75
|
+
if node_config.type == NodeType.JOIN:
|
|
76
|
+
output, status = await handler.process(context, workflow_def=workflow_def)
|
|
77
|
+
else:
|
|
78
|
+
output, status = await handler.process(context)
|
|
79
|
+
|
|
80
|
+
# 3. 根据节点输出决定后续节点与循环索引 / Compute downstream topology and loop index
|
|
81
|
+
next_node_ids, new_loop_index = self._determine_next_nodes(
|
|
82
|
+
node_config, output, context.loop_index
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return output, status, next_node_ids, new_loop_index
|
|
86
|
+
|
|
87
|
+
def _determine_next_nodes(
|
|
88
|
+
self, node_config: NodeConfig, output: Any, current_loop_index: int
|
|
89
|
+
) -> Tuple[List[str], int]:
|
|
90
|
+
"""
|
|
91
|
+
根据节点结果确定下一个节点和循环索引。
|
|
92
|
+
Determine next nodes and loop index based on node result.
|
|
93
|
+
"""
|
|
94
|
+
next_ids = []
|
|
95
|
+
new_loop_index = current_loop_index
|
|
96
|
+
|
|
97
|
+
if node_config.type == NodeType.LOGIC:
|
|
98
|
+
config: LogicNodeConfig = node_config.config
|
|
99
|
+
condition_result = False
|
|
100
|
+
if isinstance(output, dict):
|
|
101
|
+
condition_result = bool(output.get("result"))
|
|
102
|
+
else:
|
|
103
|
+
condition_result = bool(output)
|
|
104
|
+
|
|
105
|
+
target = config.true_next_node_id if condition_result else config.false_next_node_id
|
|
106
|
+
if target:
|
|
107
|
+
next_ids.append(target)
|
|
108
|
+
elif node_config.type == NodeType.FORK:
|
|
109
|
+
# 并行分支节点:返回所有分支节点 ID
|
|
110
|
+
# Fork node: return all branch node IDs for parallel execution
|
|
111
|
+
config: ForkNodeConfig = node_config.config
|
|
112
|
+
if config.branch_node_ids:
|
|
113
|
+
next_ids.extend(config.branch_node_ids)
|
|
114
|
+
elif node_config.type == NodeType.LOOP:
|
|
115
|
+
config: LoopNodeConfig = node_config.config
|
|
116
|
+
body_entry_id = config.body_entry_id or config.jump_target_id
|
|
117
|
+
exit_candidates = []
|
|
118
|
+
if config.exit_node_id:
|
|
119
|
+
exit_candidates.append(config.exit_node_id)
|
|
120
|
+
elif node_config.next_node_ids:
|
|
121
|
+
exit_candidates.extend(node_config.next_node_ids)
|
|
122
|
+
|
|
123
|
+
should_exit = False
|
|
124
|
+
if isinstance(output, dict):
|
|
125
|
+
should_exit = bool(output.get("exit_condition_met"))
|
|
126
|
+
else:
|
|
127
|
+
should_exit = bool(output)
|
|
128
|
+
|
|
129
|
+
if not should_exit:
|
|
130
|
+
if body_entry_id:
|
|
131
|
+
next_ids.append(body_entry_id)
|
|
132
|
+
else:
|
|
133
|
+
# 缺少循环体配置时记录警告并走退出路径
|
|
134
|
+
# Warn and fallback to exit path when loop body is missing
|
|
135
|
+
logger.warning(
|
|
136
|
+
"Loop node %s lacks body_entry_id, falling back to exit path",
|
|
137
|
+
node_config.id,
|
|
138
|
+
)
|
|
139
|
+
next_ids.extend(exit_candidates)
|
|
140
|
+
else:
|
|
141
|
+
next_ids.extend(exit_candidates)
|
|
142
|
+
else:
|
|
143
|
+
if node_config.next_node_ids:
|
|
144
|
+
next_ids.extend(node_config.next_node_ids)
|
|
145
|
+
|
|
146
|
+
# 去重以避免重复派发,但保持结果顺序稳定
|
|
147
|
+
# Deduplicate next nodes to avoid double dispatch while keeping ordering stable
|
|
148
|
+
seen = set()
|
|
149
|
+
ordered_next_ids = []
|
|
150
|
+
for nid in next_ids:
|
|
151
|
+
if nid and nid not in seen:
|
|
152
|
+
seen.add(nid)
|
|
153
|
+
ordered_next_ids.append(nid)
|
|
154
|
+
|
|
155
|
+
return ordered_next_ids, new_loop_index
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# Singleton instance
|
|
159
|
+
workflow_engine = WorkflowEngine()
|
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
"""
|
|
2
|
+
工作流节点处理器
|
|
3
|
+
Workflow Node Handlers
|
|
4
|
+
|
|
5
|
+
此文件包含处理不同类型工作流节点的领域逻辑。
|
|
6
|
+
This file contains the domain logic for processing different types of workflow nodes.
|
|
7
|
+
"""
|
|
8
|
+
from typing import (
|
|
9
|
+
Dict,
|
|
10
|
+
Any,
|
|
11
|
+
Optional,
|
|
12
|
+
Tuple,
|
|
13
|
+
Type,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
import aiohttp
|
|
17
|
+
import jinja2
|
|
18
|
+
import jmespath
|
|
19
|
+
from pydantic import BaseModel
|
|
20
|
+
|
|
21
|
+
from cancan_microstack.public.schemas.infra.workflow import (
|
|
22
|
+
NodeConfig,
|
|
23
|
+
ActionNodeConfig,
|
|
24
|
+
RequestConfig,
|
|
25
|
+
LogicNodeConfig,
|
|
26
|
+
TransformNodeConfig,
|
|
27
|
+
ForkNodeConfig,
|
|
28
|
+
JoinNodeConfig,
|
|
29
|
+
LoopNodeConfig,
|
|
30
|
+
EndNodeConfig,
|
|
31
|
+
WorkflowExecutionContext,
|
|
32
|
+
WorkflowDefinition,
|
|
33
|
+
HttpResponseOutput,
|
|
34
|
+
)
|
|
35
|
+
from cancan_microstack.public.schemas.infra.enums import (
|
|
36
|
+
NodeStatus,
|
|
37
|
+
JoinMode,
|
|
38
|
+
EndStatus,
|
|
39
|
+
TransformEngine,
|
|
40
|
+
ConditionTruth,
|
|
41
|
+
)
|
|
42
|
+
from linglong_web import http_client
|
|
43
|
+
from linglong_web.utils import logger
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _evaluate_truthy_flag(raw_value: str) -> bool:
|
|
47
|
+
"""将条件表达式渲染后的字符串转换为布尔值 / Convert rendered condition string into boolean."""
|
|
48
|
+
|
|
49
|
+
normalized = (raw_value or "").strip().lower()
|
|
50
|
+
try:
|
|
51
|
+
truth = ConditionTruth(normalized)
|
|
52
|
+
except ValueError:
|
|
53
|
+
truth = ConditionTruth.FALSE if normalized in {"", "0"} else ConditionTruth.TRUE
|
|
54
|
+
return truth is ConditionTruth.TRUE
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class NodeHandler:
|
|
58
|
+
"""所有节点的基类处理器 / TableBase handler for all nodes."""
|
|
59
|
+
|
|
60
|
+
def __init__(self, node_config: NodeConfig):
|
|
61
|
+
self.node_config = node_config
|
|
62
|
+
|
|
63
|
+
def _ensure_config_model(self, model_cls: Type[BaseModel]) -> Any:
|
|
64
|
+
"""
|
|
65
|
+
确保节点配置被解析为期望的 Pydantic 模型
|
|
66
|
+
Ensure the node config is parsed into the expected Pydantic model
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
config = self.node_config.config
|
|
70
|
+
if config is None or isinstance(config, model_cls):
|
|
71
|
+
return config
|
|
72
|
+
if isinstance(config, dict):
|
|
73
|
+
try:
|
|
74
|
+
typed_config = model_cls.model_validate(config)
|
|
75
|
+
self.node_config.config = typed_config
|
|
76
|
+
return typed_config
|
|
77
|
+
except Exception as exc: # noqa: BLE001
|
|
78
|
+
logger.warning(
|
|
79
|
+
"Failed to coerce config for node %s into %s: %s",
|
|
80
|
+
self.node_config.id,
|
|
81
|
+
model_cls.__name__,
|
|
82
|
+
exc,
|
|
83
|
+
)
|
|
84
|
+
return config
|
|
85
|
+
|
|
86
|
+
async def process(self, context: WorkflowExecutionContext, **kwargs) -> Tuple[Any, NodeStatus]:
|
|
87
|
+
"""
|
|
88
|
+
处理节点的核心方法。
|
|
89
|
+
Core method for processing the node.
|
|
90
|
+
"""
|
|
91
|
+
raise NotImplementedError
|
|
92
|
+
|
|
93
|
+
@staticmethod
|
|
94
|
+
def _build_template_context(context: WorkflowExecutionContext) -> Dict[str, Any]:
|
|
95
|
+
"""构建模板上下文,支持工作流官方模板写法。
|
|
96
|
+
Build template context for official workflow template syntax.
|
|
97
|
+
|
|
98
|
+
约定(官方)/ Official conventions:
|
|
99
|
+
- `context.xxx` 访问执行上下文顶层字段
|
|
100
|
+
- `context.global_context.xxx` 访问业务上下文
|
|
101
|
+
- `context.__runtime__.xxx` 访问运行时命名空间
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
payload = context.model_dump()
|
|
105
|
+
global_context = payload.get("global_context")
|
|
106
|
+
runtime_context: Dict[str, Any] = {}
|
|
107
|
+
if isinstance(global_context, dict):
|
|
108
|
+
runtime_context = dict(global_context.get("__runtime__") or {})
|
|
109
|
+
global_context.setdefault("__runtime__", runtime_context)
|
|
110
|
+
for key, value in global_context.items():
|
|
111
|
+
if key not in payload:
|
|
112
|
+
payload[key] = value
|
|
113
|
+
payload["runtime"] = runtime_context
|
|
114
|
+
payload["context"] = payload
|
|
115
|
+
return payload
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def _prepare_template_expression(template_expr: str) -> str:
|
|
119
|
+
"""将官方 runtime 点语法转换为 Jinja 可解析形式。
|
|
120
|
+
Convert official runtime dot syntax into Jinja-parseable form.
|
|
121
|
+
|
|
122
|
+
官方写法 / Official syntax:
|
|
123
|
+
- `context.__runtime__.last_status`
|
|
124
|
+
|
|
125
|
+
Jinja 对双下划线键的点访问会触发属性解析限制,
|
|
126
|
+
因此仅对 `context.__runtime__` 做确定性转换。
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
normalized = template_expr
|
|
130
|
+
normalized = normalized.replace("context.__runtime__", "context['__runtime__']")
|
|
131
|
+
return normalized
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class StartNodeHandler(NodeHandler):
|
|
135
|
+
"""处理开始节点 / Handles Start Nodes."""
|
|
136
|
+
|
|
137
|
+
async def process(self, context: WorkflowExecutionContext, **kwargs) -> Tuple[Dict[str, Any], NodeStatus]:
|
|
138
|
+
return {
|
|
139
|
+
"run_id": str(context.run_id),
|
|
140
|
+
"loop_index": context.loop_index,
|
|
141
|
+
"message": "START node accepted trigger context",
|
|
142
|
+
}, NodeStatus.SUCCESS
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class ActionNodeHandler(NodeHandler):
|
|
146
|
+
"""处理动作节点,负责执行 HTTP 请求 / Handles Action Nodes for HTTP requests."""
|
|
147
|
+
|
|
148
|
+
async def process(self, context: WorkflowExecutionContext, **kwargs) -> Tuple[HttpResponseOutput, NodeStatus]:
|
|
149
|
+
config = self._ensure_config_model(ActionNodeConfig)
|
|
150
|
+
if not isinstance(config, ActionNodeConfig):
|
|
151
|
+
raise ValueError("Invalid action node config")
|
|
152
|
+
req_conf = config.request
|
|
153
|
+
env = jinja2.Environment()
|
|
154
|
+
|
|
155
|
+
input_dict = self._build_template_context(context)
|
|
156
|
+
rendered_url = env.from_string(req_conf.url).render(**input_dict)
|
|
157
|
+
rendered_headers = {k: env.from_string(v).render(**input_dict) for k, v in (req_conf.headers or {}).items()}
|
|
158
|
+
rendered_params = {k: env.from_string(v).render(**input_dict) for k, v in (req_conf.params or {}).items()}
|
|
159
|
+
|
|
160
|
+
json_payload, data_payload = self._resolve_request_body(req_conf, rendered_headers, env, input_dict)
|
|
161
|
+
|
|
162
|
+
if config.async_mode and context.callback_url:
|
|
163
|
+
rendered_headers["X-Callback-Url"] = context.callback_url
|
|
164
|
+
|
|
165
|
+
resp = await http_client.fetch(
|
|
166
|
+
method=req_conf.method,
|
|
167
|
+
url=rendered_url,
|
|
168
|
+
params=rendered_params,
|
|
169
|
+
headers=rendered_headers,
|
|
170
|
+
json=json_payload,
|
|
171
|
+
data=data_payload,
|
|
172
|
+
timeout=req_conf.timeout_seconds,
|
|
173
|
+
passthrough_errors=True
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
text_content = await resp.text()
|
|
177
|
+
json_content = None
|
|
178
|
+
if "application/json" in resp.headers.get("Content-Type", ""):
|
|
179
|
+
try:
|
|
180
|
+
json_content = await resp.json(content_type=None)
|
|
181
|
+
except Exception:
|
|
182
|
+
logger.warning(f"Failed to decode JSON from response: {rendered_url}")
|
|
183
|
+
|
|
184
|
+
output = HttpResponseOutput(
|
|
185
|
+
status_code=resp.status,
|
|
186
|
+
headers=dict(resp.headers),
|
|
187
|
+
text=text_content,
|
|
188
|
+
json_body=json_content,
|
|
189
|
+
ok=resp.ok,
|
|
190
|
+
)
|
|
191
|
+
status = NodeStatus.SUSPENDED if config.async_mode else NodeStatus.SUCCESS
|
|
192
|
+
return output, status
|
|
193
|
+
|
|
194
|
+
def _resolve_request_body(
|
|
195
|
+
self,
|
|
196
|
+
req_conf: RequestConfig,
|
|
197
|
+
headers: Dict[str, str],
|
|
198
|
+
env: jinja2.Environment,
|
|
199
|
+
context: Dict[str, Any],
|
|
200
|
+
) -> Tuple[Optional[Any], Optional[Any]]:
|
|
201
|
+
"""解析请求体并自动适配 json/data 及 Content-Type。"""
|
|
202
|
+
|
|
203
|
+
rendered_legacy_body = self._render_recursive(req_conf.body, env, context)
|
|
204
|
+
rendered_json_body = self._render_recursive(req_conf.json_body, env, context)
|
|
205
|
+
rendered_form_body = self._render_recursive(req_conf.form_body, env, context)
|
|
206
|
+
rendered_raw_body = self._render_recursive(req_conf.raw_body, env, context)
|
|
207
|
+
|
|
208
|
+
content_type = ""
|
|
209
|
+
for key, value in headers.items():
|
|
210
|
+
if key.lower() == "content-type":
|
|
211
|
+
content_type = str(value).lower()
|
|
212
|
+
break
|
|
213
|
+
|
|
214
|
+
body_type = req_conf.body_type
|
|
215
|
+
if body_type == "auto":
|
|
216
|
+
if rendered_json_body is not None:
|
|
217
|
+
body_type = "json"
|
|
218
|
+
elif rendered_form_body is not None:
|
|
219
|
+
if "multipart/form-data" in content_type:
|
|
220
|
+
body_type = "multipart"
|
|
221
|
+
else:
|
|
222
|
+
body_type = "form-urlencoded"
|
|
223
|
+
elif rendered_raw_body is not None:
|
|
224
|
+
body_type = "raw"
|
|
225
|
+
else:
|
|
226
|
+
if "multipart/form-data" in content_type:
|
|
227
|
+
body_type = "multipart"
|
|
228
|
+
elif "application/x-www-form-urlencoded" in content_type:
|
|
229
|
+
body_type = "form-urlencoded"
|
|
230
|
+
elif isinstance(rendered_legacy_body, (dict, list)):
|
|
231
|
+
body_type = "json"
|
|
232
|
+
else:
|
|
233
|
+
body_type = "raw"
|
|
234
|
+
|
|
235
|
+
if body_type == "json":
|
|
236
|
+
json_payload = rendered_json_body if rendered_json_body is not None else rendered_legacy_body
|
|
237
|
+
if json_payload is not None and not content_type:
|
|
238
|
+
headers["Content-Type"] = "application/json"
|
|
239
|
+
return json_payload, None
|
|
240
|
+
|
|
241
|
+
if body_type == "form-urlencoded":
|
|
242
|
+
form_payload = rendered_form_body if rendered_form_body is not None else rendered_legacy_body
|
|
243
|
+
if form_payload is not None and not content_type:
|
|
244
|
+
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
|
245
|
+
return None, form_payload
|
|
246
|
+
|
|
247
|
+
if body_type == "multipart":
|
|
248
|
+
multipart_payload = rendered_form_body if rendered_form_body is not None else rendered_legacy_body
|
|
249
|
+
normalized = self._to_form_data(multipart_payload)
|
|
250
|
+
|
|
251
|
+
content_type_keys = [key for key in headers if key.lower() == "content-type"]
|
|
252
|
+
for key in content_type_keys:
|
|
253
|
+
headers.pop(key, None)
|
|
254
|
+
return None, normalized
|
|
255
|
+
|
|
256
|
+
raw_payload = rendered_raw_body if rendered_raw_body is not None else rendered_legacy_body
|
|
257
|
+
return None, raw_payload
|
|
258
|
+
|
|
259
|
+
@staticmethod
|
|
260
|
+
def _to_form_data(payload: Any) -> aiohttp.FormData:
|
|
261
|
+
"""将 payload 规范化为 multipart FormData。"""
|
|
262
|
+
|
|
263
|
+
form = aiohttp.FormData()
|
|
264
|
+
if isinstance(payload, dict):
|
|
265
|
+
for key, value in payload.items():
|
|
266
|
+
form.add_field(str(key), "" if value is None else str(value))
|
|
267
|
+
return form
|
|
268
|
+
|
|
269
|
+
if isinstance(payload, list):
|
|
270
|
+
for item in payload:
|
|
271
|
+
if isinstance(item, (tuple, list)) and len(item) == 2:
|
|
272
|
+
key, value = item
|
|
273
|
+
form.add_field(str(key), "" if value is None else str(value))
|
|
274
|
+
return form
|
|
275
|
+
|
|
276
|
+
if payload is not None:
|
|
277
|
+
form.add_field("payload", str(payload))
|
|
278
|
+
return form
|
|
279
|
+
|
|
280
|
+
def _render_recursive(self, data: Any, env: jinja2.Environment, context: Dict[str, Any]) -> Any:
|
|
281
|
+
if isinstance(data, str):
|
|
282
|
+
normalized = self._prepare_template_expression(data)
|
|
283
|
+
return env.from_string(normalized).render(**context)
|
|
284
|
+
elif isinstance(data, dict):
|
|
285
|
+
return {k: self._render_recursive(v, env, context) for k, v in data.items()}
|
|
286
|
+
elif isinstance(data, list):
|
|
287
|
+
return [self._render_recursive(v, env, context) for v in data]
|
|
288
|
+
return data
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class LogicNodeHandler(NodeHandler):
|
|
292
|
+
"""处理逻辑节点,用于评估条件表达式 / Handles Logic Nodes for conditions."""
|
|
293
|
+
|
|
294
|
+
async def process(self, context: WorkflowExecutionContext, **kwargs) -> Tuple[Dict[str, Any], NodeStatus]:
|
|
295
|
+
config = self._ensure_config_model(LogicNodeConfig)
|
|
296
|
+
if not isinstance(config, LogicNodeConfig):
|
|
297
|
+
raise ValueError("Invalid logic node config")
|
|
298
|
+
env = jinja2.Environment()
|
|
299
|
+
expr = config.condition
|
|
300
|
+
if not expr.startswith("{{"):
|
|
301
|
+
expr = f"{{{{ {expr} }}}}"
|
|
302
|
+
expr = self._prepare_template_expression(expr)
|
|
303
|
+
|
|
304
|
+
rendered = env.from_string(expr).render(**self._build_template_context(context))
|
|
305
|
+
result = _evaluate_truthy_flag(rendered)
|
|
306
|
+
return {
|
|
307
|
+
"condition": config.condition,
|
|
308
|
+
"rendered": rendered,
|
|
309
|
+
"result": result,
|
|
310
|
+
"selected_branch": "TRUE" if result else "FALSE",
|
|
311
|
+
"true_next_node_id": config.true_next_node_id,
|
|
312
|
+
"false_next_node_id": config.false_next_node_id,
|
|
313
|
+
}, NodeStatus.SUCCESS
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class TransformNodeHandler(NodeHandler):
|
|
317
|
+
"""处理转换节点,用于重塑上下文数据 / Handles Transform Nodes for reshaping data."""
|
|
318
|
+
|
|
319
|
+
async def process(self, context: WorkflowExecutionContext, **kwargs) -> Tuple[Dict[str, Any], NodeStatus]:
|
|
320
|
+
config = self._ensure_config_model(TransformNodeConfig)
|
|
321
|
+
if not isinstance(config, TransformNodeConfig):
|
|
322
|
+
raise ValueError("Invalid transform node config")
|
|
323
|
+
result = {}
|
|
324
|
+
input_dict = self._build_template_context(context)
|
|
325
|
+
|
|
326
|
+
for tgt_key, expr in config.output_schema.items():
|
|
327
|
+
try:
|
|
328
|
+
if config.engine == TransformEngine.JMESPATH:
|
|
329
|
+
val = jmespath.search(expr, input_dict)
|
|
330
|
+
result[tgt_key] = val
|
|
331
|
+
else:
|
|
332
|
+
template = jinja2.Template(self._prepare_template_expression(expr))
|
|
333
|
+
val = template.render(**input_dict)
|
|
334
|
+
result[tgt_key] = val
|
|
335
|
+
except Exception as e:
|
|
336
|
+
logger.error(f"Failed to transform key '{tgt_key}' with expression '{expr}': {e}")
|
|
337
|
+
result[tgt_key] = None
|
|
338
|
+
return result, NodeStatus.SUCCESS
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
class ForkNodeHandler(NodeHandler):
|
|
342
|
+
"""处理并行分支节点 / Handles Fork Nodes for parallel execution."""
|
|
343
|
+
|
|
344
|
+
async def process(self, context: WorkflowExecutionContext, **kwargs) -> Tuple[Dict[str, Any], NodeStatus]:
|
|
345
|
+
config = self._ensure_config_model(ForkNodeConfig)
|
|
346
|
+
if not isinstance(config, ForkNodeConfig):
|
|
347
|
+
raise ValueError("Invalid fork node config")
|
|
348
|
+
"""
|
|
349
|
+
处理并行分支节点,直接返回成功状态。
|
|
350
|
+
Process fork node and return success status directly.
|
|
351
|
+
|
|
352
|
+
并行逻辑由 WorkflowEngine._determine_next_nodes 实现,
|
|
353
|
+
该方法会将 config.branch_node_ids 返回为 next_node_ids,
|
|
354
|
+
Celery 任务编排层会为每个分支创建独立的执行任务。
|
|
355
|
+
|
|
356
|
+
The parallel logic is implemented by WorkflowEngine._determine_next_nodes,
|
|
357
|
+
which returns config.branch_node_ids as next_node_ids.
|
|
358
|
+
The Celery orchestration layer will create independent execution tasks for each branch.
|
|
359
|
+
"""
|
|
360
|
+
# Fork 节点本身不执行任何操作,只是语义标记
|
|
361
|
+
# Fork node itself performs no operation, it's just a semantic marker
|
|
362
|
+
return {
|
|
363
|
+
"branch_node_ids": config.branch_node_ids,
|
|
364
|
+
"branch_count": len(config.branch_node_ids or []),
|
|
365
|
+
}, NodeStatus.SUCCESS
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
class JoinNodeHandler(NodeHandler):
|
|
369
|
+
"""处理汇合节点 / Handles Join Nodes."""
|
|
370
|
+
|
|
371
|
+
async def process(self, context: WorkflowExecutionContext, **kwargs) -> Tuple[Dict[str, Any], NodeStatus]:
|
|
372
|
+
workflow_def: WorkflowDefinition = kwargs.get("workflow_def")
|
|
373
|
+
if not workflow_def:
|
|
374
|
+
raise ValueError("workflow_def is required for JoinNodeHandler")
|
|
375
|
+
|
|
376
|
+
join_config = self._ensure_config_model(JoinNodeConfig)
|
|
377
|
+
if not isinstance(join_config, JoinNodeConfig):
|
|
378
|
+
raise ValueError("Invalid join node config")
|
|
379
|
+
|
|
380
|
+
upstream_node_ids = [
|
|
381
|
+
nid for nid, n_cfg_dict in workflow_def.nodes_config.items()
|
|
382
|
+
if self.node_config.id in (n_cfg_dict.get('next_node_ids') or [])
|
|
383
|
+
]
|
|
384
|
+
|
|
385
|
+
if not upstream_node_ids:
|
|
386
|
+
return {
|
|
387
|
+
"mode": join_config.mode.value,
|
|
388
|
+
"upstream_node_ids": [],
|
|
389
|
+
"upstream_statuses": {},
|
|
390
|
+
"is_ready": True,
|
|
391
|
+
"reason": "No upstream nodes found",
|
|
392
|
+
}, NodeStatus.SUCCESS
|
|
393
|
+
|
|
394
|
+
upstream_status_map = {
|
|
395
|
+
uid: (context.nodes.get(uid).status.value if context.nodes.get(uid) else "MISSING")
|
|
396
|
+
for uid in upstream_node_ids
|
|
397
|
+
}
|
|
398
|
+
upstream_statuses = [context.nodes.get(uid).status for uid in upstream_node_ids if context.nodes.get(uid)]
|
|
399
|
+
|
|
400
|
+
if join_config.mode == JoinMode.ALL:
|
|
401
|
+
is_ready = all(s in [NodeStatus.SUCCESS, NodeStatus.SKIPPED] for s in upstream_statuses)
|
|
402
|
+
else:
|
|
403
|
+
is_ready = any(s == NodeStatus.SUCCESS for s in upstream_statuses)
|
|
404
|
+
|
|
405
|
+
return {
|
|
406
|
+
"mode": join_config.mode.value,
|
|
407
|
+
"upstream_node_ids": upstream_node_ids,
|
|
408
|
+
"upstream_statuses": upstream_status_map,
|
|
409
|
+
"is_ready": is_ready,
|
|
410
|
+
}, NodeStatus.SUCCESS if is_ready else NodeStatus.PENDING
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
class LoopNodeHandler(NodeHandler):
|
|
414
|
+
"""处理循环节点 / Handles Loop Nodes."""
|
|
415
|
+
|
|
416
|
+
def _evaluate_exit_condition(
|
|
417
|
+
self,
|
|
418
|
+
config: LoopNodeConfig,
|
|
419
|
+
context: WorkflowExecutionContext,
|
|
420
|
+
loop_index_override: Optional[int] = None,
|
|
421
|
+
) -> Tuple[str, bool]:
|
|
422
|
+
"""渲染并评估循环退出条件。
|
|
423
|
+
Render and evaluate loop exit condition.
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
config: 循环配置 / Loop config.
|
|
427
|
+
context: 当前执行上下文 / Current execution context.
|
|
428
|
+
loop_index_override: 可选覆盖 loop_index(用于边界轮次对比)/
|
|
429
|
+
Optional override of loop_index (used for boundary comparison).
|
|
430
|
+
"""
|
|
431
|
+
|
|
432
|
+
env = jinja2.Environment()
|
|
433
|
+
expr = config.condition
|
|
434
|
+
if not expr.startswith("{{"):
|
|
435
|
+
expr = f"{{{{ {expr} }}}}"
|
|
436
|
+
expr = self._prepare_template_expression(expr)
|
|
437
|
+
|
|
438
|
+
template_context = self._build_template_context(context)
|
|
439
|
+
if loop_index_override is not None:
|
|
440
|
+
template_context["loop_index"] = loop_index_override
|
|
441
|
+
nested_context = template_context.get("context")
|
|
442
|
+
if isinstance(nested_context, dict):
|
|
443
|
+
nested_context["loop_index"] = loop_index_override
|
|
444
|
+
|
|
445
|
+
rendered = env.from_string(expr).render(**template_context)
|
|
446
|
+
return rendered, _evaluate_truthy_flag(rendered)
|
|
447
|
+
|
|
448
|
+
async def process(self, context: WorkflowExecutionContext, **kwargs) -> Tuple[Dict[str, Any], NodeStatus]:
|
|
449
|
+
config = self._ensure_config_model(LoopNodeConfig)
|
|
450
|
+
if not isinstance(config, LoopNodeConfig):
|
|
451
|
+
raise ValueError("Invalid loop node config")
|
|
452
|
+
|
|
453
|
+
if context.loop_index > config.max_iterations:
|
|
454
|
+
return {
|
|
455
|
+
"condition": config.condition,
|
|
456
|
+
"rendered": None,
|
|
457
|
+
"exit_condition_met": True,
|
|
458
|
+
"decision": "EXIT",
|
|
459
|
+
"forced_exit_by_max_iterations": True,
|
|
460
|
+
"max_iterations": config.max_iterations,
|
|
461
|
+
"current_loop_index": context.loop_index,
|
|
462
|
+
}, NodeStatus.SUCCESS
|
|
463
|
+
|
|
464
|
+
rendered, exit_condition_met = self._evaluate_exit_condition(config, context)
|
|
465
|
+
|
|
466
|
+
# 边界轮次防误判:当条件仅在 loop_index == max_iterations 才变为 true 时,
|
|
467
|
+
# 应允许本轮循环体执行,避免出现“max=3 但 body 仅 2 次”的 off-by-one。
|
|
468
|
+
# Boundary guard: when condition flips true only at loop_index == max_iterations,
|
|
469
|
+
# allow current body execution to avoid off-by-one (max=3 but body runs only twice).
|
|
470
|
+
deferred_by_boundary = False
|
|
471
|
+
boundary_probe_rendered = None
|
|
472
|
+
if exit_condition_met and context.loop_index == config.max_iterations:
|
|
473
|
+
boundary_probe_rendered, previous_iteration_exit = self._evaluate_exit_condition(
|
|
474
|
+
config,
|
|
475
|
+
context,
|
|
476
|
+
loop_index_override=max(context.loop_index - 1, 0),
|
|
477
|
+
)
|
|
478
|
+
if not previous_iteration_exit:
|
|
479
|
+
deferred_by_boundary = True
|
|
480
|
+
exit_condition_met = False
|
|
481
|
+
|
|
482
|
+
return {
|
|
483
|
+
"condition": config.condition,
|
|
484
|
+
"rendered": rendered,
|
|
485
|
+
"boundary_probe_rendered": boundary_probe_rendered,
|
|
486
|
+
"exit_condition_met": exit_condition_met,
|
|
487
|
+
"decision": "EXIT" if exit_condition_met else "CONTINUE",
|
|
488
|
+
"deferred_exit_by_max_boundary": deferred_by_boundary,
|
|
489
|
+
"forced_exit_by_max_iterations": False,
|
|
490
|
+
"max_iterations": config.max_iterations,
|
|
491
|
+
"current_loop_index": context.loop_index,
|
|
492
|
+
}, NodeStatus.SUCCESS
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
class EndNodeHandler(NodeHandler):
|
|
496
|
+
"""处理结束节点 / Handles End Nodes."""
|
|
497
|
+
|
|
498
|
+
async def process(self, context: WorkflowExecutionContext, **kwargs) -> Tuple[Dict[str, Any], NodeStatus]:
|
|
499
|
+
config = self._ensure_config_model(EndNodeConfig)
|
|
500
|
+
if config is None:
|
|
501
|
+
config = EndNodeConfig()
|
|
502
|
+
self.node_config.config = config
|
|
503
|
+
if not isinstance(config, EndNodeConfig):
|
|
504
|
+
raise ValueError("Invalid end node config")
|
|
505
|
+
status = NodeStatus.SUCCESS if config.status == EndStatus.SUCCESS else NodeStatus.FAILURE
|
|
506
|
+
return {
|
|
507
|
+
"end_status": config.status.value,
|
|
508
|
+
"loop_index": context.loop_index,
|
|
509
|
+
}, status
|