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,518 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import functools
|
|
3
|
+
import http
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
from typing import Awaitable, Callable, Optional
|
|
7
|
+
|
|
8
|
+
from starlette.requests import Request
|
|
9
|
+
from starlette.responses import RedirectResponse
|
|
10
|
+
from starlette.responses import Response
|
|
11
|
+
|
|
12
|
+
from linglong_web.utils import logger
|
|
13
|
+
from linglong_web import build_success_response
|
|
14
|
+
from cancan_microstack.public.error import HTTPException
|
|
15
|
+
from cancan_microstack.public.schemas.common import APIResponse
|
|
16
|
+
from linglong_web import http_client
|
|
17
|
+
from linglong_web import LinglongConfig
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _allowed_cors_origins() -> set[str]:
|
|
21
|
+
"""读取配置的跨域白名单 / Read configured cross-origin allowlist."""
|
|
22
|
+
raw = getattr(LinglongConfig, "PROXY_CORS_ALLOWED_ORIGINS", "") or ""
|
|
23
|
+
return {o.strip() for o in raw.split(",") if o.strip()}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _hardened_proxy_cors(func: Callable[..., Awaitable[Response]]):
|
|
27
|
+
"""安全的代理 CORS 装饰器(替代 linglong_web.allow_cors_specific)。
|
|
28
|
+
|
|
29
|
+
Hardened CORS for proxy handlers, replacing the reflective
|
|
30
|
+
``allow_cors_specific`` (which echoed any Origin AND set
|
|
31
|
+
Allow-Credentials:true AND stripped CSP/X-Frame-Options).
|
|
32
|
+
|
|
33
|
+
安全权衡 / Security tradeoffs:
|
|
34
|
+
- 同源访问(无 Origin 头)不下发任何 CORS 头,正常工作。
|
|
35
|
+
- 仅当请求 Origin 命中显式白名单时,才回显该 Origin 并允许带凭证;
|
|
36
|
+
绝不出现 "反射任意 Origin + Allow-Credentials:true" 组合(CSRF/凭证泄露风险)。
|
|
37
|
+
- 不在白名单内的跨域请求不下发 CORS 头(浏览器自行拦截)。
|
|
38
|
+
- 不再无差别删除 CSP / X-Frame-Options(由下游 handler 自己按需最小调整)。
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
@functools.wraps(func)
|
|
42
|
+
async def wrapper(request: Request, *args, **kwargs):
|
|
43
|
+
request_origin = request.headers.get("Origin") or request.headers.get("origin")
|
|
44
|
+
allowlist = _allowed_cors_origins()
|
|
45
|
+
origin_allowed = bool(request_origin) and request_origin in allowlist
|
|
46
|
+
|
|
47
|
+
cors_headers: dict[str, str] = {}
|
|
48
|
+
if origin_allowed:
|
|
49
|
+
# 白名单命中:回显具体 Origin(非 "*"),可安全带凭证。
|
|
50
|
+
cors_headers = {
|
|
51
|
+
"Access-Control-Allow-Origin": request_origin,
|
|
52
|
+
"Vary": "Origin",
|
|
53
|
+
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS, PATCH",
|
|
54
|
+
"Access-Control-Allow-Headers": (
|
|
55
|
+
"Authorization, Content-Type, X-Requested-With, x-forwarded-uri, "
|
|
56
|
+
"x-forwarded-proto, x-forwarded-host, x-forwarded-port, x-original-uri"
|
|
57
|
+
),
|
|
58
|
+
"Access-Control-Allow-Credentials": "true",
|
|
59
|
+
"Access-Control-Max-Age": "86400",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if request.method == "OPTIONS":
|
|
63
|
+
# 同源 / 非白名单跨域:返回 204 但不下发放行头,浏览器自行处理。
|
|
64
|
+
return Response(status_code=204, headers=cors_headers)
|
|
65
|
+
|
|
66
|
+
response = await func(request, *args, **kwargs)
|
|
67
|
+
|
|
68
|
+
if isinstance(response, Response):
|
|
69
|
+
for key, value in cors_headers.items():
|
|
70
|
+
response.headers[key] = value
|
|
71
|
+
|
|
72
|
+
return response
|
|
73
|
+
|
|
74
|
+
return wrapper
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@_hardened_proxy_cors
|
|
78
|
+
async def rabbitmq_mgmt_proxy_handler(request: Request) -> Response:
|
|
79
|
+
"""RabbitMQ Management 代理处理器"""
|
|
80
|
+
original_path = request.url.path
|
|
81
|
+
|
|
82
|
+
# 1. 根路径强制加斜杠,防止静态资源路径错误
|
|
83
|
+
if original_path.endswith("/rabbitmq_mgmt") and not original_path.endswith("/rabbitmq_mgmt/"):
|
|
84
|
+
return RedirectResponse(url=f"{request.url}/", status_code=301)
|
|
85
|
+
|
|
86
|
+
proxy_path = original_path.replace("/v1/opsbffsrv/rabbitmq_mgmt", "", 1) or "/"
|
|
87
|
+
base_url = (LinglongConfig.RABBITMQ_MGMT_BASE_URL or "").rstrip("/")
|
|
88
|
+
target_url = f"{base_url}{proxy_path}"
|
|
89
|
+
if request.url.query:
|
|
90
|
+
target_url = f"{target_url}?{request.url.query}"
|
|
91
|
+
|
|
92
|
+
logger.debug(f"[RabbitMQ Proxy] {request.method} {proxy_path} -> {target_url}")
|
|
93
|
+
|
|
94
|
+
# 2. 准备请求头
|
|
95
|
+
headers = dict(request.headers)
|
|
96
|
+
headers.pop('host', None)
|
|
97
|
+
headers.pop('content-length', None) # 让 httpx 重新计算
|
|
98
|
+
|
|
99
|
+
# 【核心逻辑】:后端真替换
|
|
100
|
+
# 无论前端传什么 Authorization (包括我们注入的假 Token),全部清洗掉
|
|
101
|
+
# 强制换成 RabbitMQ 真正的 Admin 账号
|
|
102
|
+
headers.pop('authorization', None)
|
|
103
|
+
headers.pop('Authorization', None)
|
|
104
|
+
_inject_real_basic_auth(headers)
|
|
105
|
+
|
|
106
|
+
body = await request.body() if request.method in {"POST", "PUT", "PATCH"} else None
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
resp = await http_client.fetch(
|
|
110
|
+
method=request.method,
|
|
111
|
+
url=target_url,
|
|
112
|
+
format_type="raw", # 透传二进制资源,避免对图片/字体等做 text decode / passthrough binary safely
|
|
113
|
+
headers=headers,
|
|
114
|
+
data=body,
|
|
115
|
+
timeout=30.0,
|
|
116
|
+
passthrough_errors=True,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
content = await resp.read()
|
|
120
|
+
|
|
121
|
+
# 3. 处理响应头
|
|
122
|
+
# 仅剥离破坏透传的 hop-by-hop / 长度 / 编码相关头。
|
|
123
|
+
# 保留 X-Content-Type-Options / X-Frame-Options 等安全头(不再无差别删除)。
|
|
124
|
+
# Only strip hop-by-hop / length / encoding headers that break passthrough.
|
|
125
|
+
# Keep security headers like X-Content-Type-Options / X-Frame-Options intact.
|
|
126
|
+
hop_by_hop_headers = [
|
|
127
|
+
'transfer-encoding', 'content-encoding', 'connection', 'content-length',
|
|
128
|
+
]
|
|
129
|
+
response_headers = {
|
|
130
|
+
k: v for k, v in resp.headers.items()
|
|
131
|
+
if k.lower() not in hop_by_hop_headers
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
# 4. 【核心逻辑】:对所有 HTML 页面注入脚本(RabbitMQ 4 可能有多个入口页面)
|
|
135
|
+
# Inject into ALL HTML pages to ensure login bypass works regardless of entry point
|
|
136
|
+
content_type = _get_header_case_insensitive(response_headers, "content-type")
|
|
137
|
+
is_html_response = "text/html" in content_type.lower()
|
|
138
|
+
if is_html_response:
|
|
139
|
+
logger.debug(f"[RabbitMQ Proxy] Injecting auto-login script into HTML: {proxy_path}")
|
|
140
|
+
content = _inject_auto_login_script(content)
|
|
141
|
+
# 注入后内容长度变了,必须删除 Content-Length 让服务器自动处理
|
|
142
|
+
response_headers.pop("Content-Length", None)
|
|
143
|
+
# 安全权衡 / Security tradeoff:
|
|
144
|
+
# 我们向 HTML 注入了内联 login-bypass 脚本。若上游 RabbitMQ 下发了
|
|
145
|
+
# 限制性 CSP(默认通常没有),它会阻断该内联脚本,导致免登录失效。
|
|
146
|
+
# 因此仅在「确实注入」的 HTML 响应上移除上游 CSP;其余响应(API/JSON/
|
|
147
|
+
# 静态资源)保留 CSP。X-Frame-Options 始终保留。
|
|
148
|
+
# We inject an inline login-bypass script into HTML. A restrictive upstream
|
|
149
|
+
# CSP (rare for RabbitMQ mgmt UI) would block it, breaking the bypass.
|
|
150
|
+
# So we drop the upstream CSP ONLY on HTML responses we actually inject into;
|
|
151
|
+
# all other responses keep their CSP. X-Frame-Options is always preserved.
|
|
152
|
+
for csp_key in ("content-security-policy", "Content-Security-Policy"):
|
|
153
|
+
response_headers.pop(csp_key, None)
|
|
154
|
+
|
|
155
|
+
# 注入判定诊断头,便于线上快速确认是否命中注入分支
|
|
156
|
+
# Diagnostic headers for quick runtime verification of injection decision
|
|
157
|
+
response_headers["X-OpsBFF-RMQ-Injected"] = "1" if is_html_response else "0"
|
|
158
|
+
response_headers["X-OpsBFF-RMQ-ContentType"] = content_type or "-"
|
|
159
|
+
|
|
160
|
+
return Response(content=content, status_code=resp.status, headers=response_headers)
|
|
161
|
+
|
|
162
|
+
except Exception as e:
|
|
163
|
+
logger.error(f"Proxy Error: {e}")
|
|
164
|
+
raise HTTPException(status_code=502, msg="RabbitMQ Gateway Error")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _inject_real_basic_auth(headers: dict):
|
|
168
|
+
"""注入真正的 RabbitMQ 账号密码"""
|
|
169
|
+
username = LinglongConfig.RABBITMQ_MGMT_USERNAME
|
|
170
|
+
password = LinglongConfig.RABBITMQ_MGMT_PASSWORD
|
|
171
|
+
if username and password:
|
|
172
|
+
token = base64.b64encode(f"{username}:{password}".encode("latin1")).decode("ascii")
|
|
173
|
+
headers["Authorization"] = f"Basic {token}"
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _get_header_case_insensitive(headers: dict, header_name: str) -> str:
|
|
177
|
+
"""按大小写不敏感方式读取响应头。
|
|
178
|
+
Read response header value in case-insensitive way.
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
target = header_name.lower()
|
|
182
|
+
for key, value in headers.items():
|
|
183
|
+
if key.lower() == target:
|
|
184
|
+
return str(value)
|
|
185
|
+
return ""
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _inject_auto_login_script(html_content: bytes) -> bytes:
|
|
189
|
+
html_str = html_content.decode("utf-8", errors="replace")
|
|
190
|
+
|
|
191
|
+
# RabbitMQ UI 使用 localStorage 的 auth,并通过 XHR 发送 Authorization。
|
|
192
|
+
# RabbitMQ UI stores auth in localStorage and sends it via XHR Authorization header.
|
|
193
|
+
#
|
|
194
|
+
# 安全说明 / Security note:
|
|
195
|
+
# - 这里不向浏览器暴露真实账号密码。
|
|
196
|
+
# - 代理层会强制清洗 Authorization 并注入真实 BasicAuth。
|
|
197
|
+
# - 只要 UI 认为"有 auth",就不会跳登录页。
|
|
198
|
+
try:
|
|
199
|
+
dummy_user = getattr(LinglongConfig, "DUMMY_USER", None) or "ops_user"
|
|
200
|
+
except Exception:
|
|
201
|
+
dummy_user = "ops_user"
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
dummy_auth_token = getattr(LinglongConfig, "DUMMY_AUTH_TOKEN", None) or ""
|
|
205
|
+
except Exception:
|
|
206
|
+
dummy_auth_token = ""
|
|
207
|
+
|
|
208
|
+
if not dummy_auth_token:
|
|
209
|
+
try:
|
|
210
|
+
dummy_pass = getattr(LinglongConfig, "DUMMY_PASS", None) or "dummy_pass"
|
|
211
|
+
except Exception:
|
|
212
|
+
dummy_pass = "dummy_pass"
|
|
213
|
+
token = base64.b64encode(f"{dummy_user}:{dummy_pass}".encode("latin1")).decode("ascii")
|
|
214
|
+
dummy_auth_token = f"Basic {token}"
|
|
215
|
+
|
|
216
|
+
credentials = dummy_auth_token.replace("Basic ", "", 1).strip()
|
|
217
|
+
|
|
218
|
+
# 构造注入脚本 - 增强版,支持 RabbitMQ 3.x 和 4.x
|
|
219
|
+
# Enhanced script supporting both RabbitMQ 3.x and 4.x
|
|
220
|
+
injection = f"""
|
|
221
|
+
<script>
|
|
222
|
+
(function() {{
|
|
223
|
+
console.log('[OpsBFF] 🚀 Injecting Credentials & Bypassing Login (Enhanced for RMQ 3.x/4.x)...');
|
|
224
|
+
|
|
225
|
+
var FAKE_AUTH = '{dummy_auth_token}';
|
|
226
|
+
var FAKE_CREDENTIALS = '{credentials}';
|
|
227
|
+
var FAKE_SCHEME = 'Basic';
|
|
228
|
+
var FAKE_USER = '{dummy_user}';
|
|
229
|
+
|
|
230
|
+
function shortKey(str) {{
|
|
231
|
+
var hash = 0;
|
|
232
|
+
if (!str || str.length === 0) return '0';
|
|
233
|
+
for (var i = 0; i < str.length; i++) {{
|
|
234
|
+
hash = (31 * hash + str.charCodeAt(i)) | 0;
|
|
235
|
+
}}
|
|
236
|
+
var shortHash = Math.abs((hash << 16) >> 16);
|
|
237
|
+
return shortHash.toString(16);
|
|
238
|
+
}}
|
|
239
|
+
|
|
240
|
+
function parseCookieM() {{
|
|
241
|
+
var cookies = document.cookie ? document.cookie.split(';') : [];
|
|
242
|
+
var m = '';
|
|
243
|
+
for (var i = 0; i < cookies.length; i++) {{
|
|
244
|
+
var parts = cookies[i].trim().split('=');
|
|
245
|
+
if (parts[0] === 'm') {{
|
|
246
|
+
m = parts.slice(1).join('=');
|
|
247
|
+
break;
|
|
248
|
+
}}
|
|
249
|
+
}}
|
|
250
|
+
var dict = {{}};
|
|
251
|
+
if (!m) return dict;
|
|
252
|
+
var items = m.split('|');
|
|
253
|
+
for (var j = 0; j < items.length; j++) {{
|
|
254
|
+
if (!items[j]) continue;
|
|
255
|
+
var kv = items[j].split(':');
|
|
256
|
+
if (kv.length >= 2) {{
|
|
257
|
+
var key = kv[0];
|
|
258
|
+
var value = decodeURIComponent(kv.slice(1).join(':'));
|
|
259
|
+
dict[key] = value;
|
|
260
|
+
}}
|
|
261
|
+
}}
|
|
262
|
+
return dict;
|
|
263
|
+
}}
|
|
264
|
+
|
|
265
|
+
function writeCookieM(dict, expiresAt) {{
|
|
266
|
+
var enc = [];
|
|
267
|
+
for (var k in dict) {{
|
|
268
|
+
if (!Object.prototype.hasOwnProperty.call(dict, k)) continue;
|
|
269
|
+
enc.push(k + ':' + encodeURIComponent(String(dict[k])));
|
|
270
|
+
}}
|
|
271
|
+
document.cookie = 'm=' + enc.join('|') + '; expires=' + expiresAt.toUTCString() + '; path=/';
|
|
272
|
+
}}
|
|
273
|
+
|
|
274
|
+
function seedLoggedInCookie() {{
|
|
275
|
+
try {{
|
|
276
|
+
var dict = parseCookieM();
|
|
277
|
+
dict[shortKey('loggedIn')] = 'true';
|
|
278
|
+
dict[shortKey('login_session_timeout')] = '480';
|
|
279
|
+
var expiry = new Date();
|
|
280
|
+
expiry.setHours(expiry.getHours() + 8);
|
|
281
|
+
writeCookieM(dict, expiry);
|
|
282
|
+
}} catch (e) {{
|
|
283
|
+
console.error('[OpsBFF] Cookie seed error:', e);
|
|
284
|
+
}}
|
|
285
|
+
}}
|
|
286
|
+
|
|
287
|
+
// 1. 强制写入 LocalStorage 和 SessionStorage (覆盖 RabbitMQ 3.x 和 4.x 的 keys)
|
|
288
|
+
// Force write to both LocalStorage and SessionStorage (covers RMQ 3.x and 4.x keys)
|
|
289
|
+
function credentialsSeeded() {{
|
|
290
|
+
try {{
|
|
291
|
+
// Minimal readiness check: once these are present, RMQ UI can proceed.
|
|
292
|
+
// 最小就绪判断:这些 key 存在后,UI 的 prefs/auth 读取即可正常继续。
|
|
293
|
+
return (
|
|
294
|
+
localStorage.getItem('rabbitmq.credentials') === FAKE_CREDENTIALS &&
|
|
295
|
+
localStorage.getItem('rabbitmq.auth-scheme') === FAKE_SCHEME &&
|
|
296
|
+
(localStorage.getItem('auth') === FAKE_AUTH || sessionStorage.getItem('auth') === FAKE_AUTH)
|
|
297
|
+
);
|
|
298
|
+
}} catch (e) {{
|
|
299
|
+
return false;
|
|
300
|
+
}}
|
|
301
|
+
}}
|
|
302
|
+
|
|
303
|
+
function seedCredentials() {{
|
|
304
|
+
try {{
|
|
305
|
+
// RabbitMQ 4.x required keys (prefs.js contract)
|
|
306
|
+
localStorage.setItem('rabbitmq.credentials', FAKE_CREDENTIALS);
|
|
307
|
+
localStorage.setItem('rabbitmq.auth-scheme', FAKE_SCHEME);
|
|
308
|
+
localStorage.setItem('rabbitmq.auth_resource', '/');
|
|
309
|
+
|
|
310
|
+
// RabbitMQ 3.x keys
|
|
311
|
+
localStorage.setItem('auth', FAKE_AUTH);
|
|
312
|
+
localStorage.setItem('rabbitmq_current_user', FAKE_USER);
|
|
313
|
+
localStorage.setItem('last_login', new Date().getTime());
|
|
314
|
+
|
|
315
|
+
// RabbitMQ 4.x possible keys (auth may be stored differently)
|
|
316
|
+
localStorage.setItem('rabbitmq.auth', FAKE_AUTH);
|
|
317
|
+
localStorage.setItem('rabbitmq.user', FAKE_USER);
|
|
318
|
+
localStorage.setItem('rabbitmq-auth', FAKE_AUTH);
|
|
319
|
+
localStorage.setItem('user', FAKE_USER);
|
|
320
|
+
|
|
321
|
+
// Also try sessionStorage for some RabbitMQ versions
|
|
322
|
+
sessionStorage.setItem('auth', FAKE_AUTH);
|
|
323
|
+
sessionStorage.setItem('rabbitmq_current_user', FAKE_USER);
|
|
324
|
+
sessionStorage.setItem('rabbitmq.credentials', FAKE_CREDENTIALS);
|
|
325
|
+
sessionStorage.setItem('rabbitmq.auth-scheme', FAKE_SCHEME);
|
|
326
|
+
seedLoggedInCookie();
|
|
327
|
+
|
|
328
|
+
// Avoid log spam: seed happens frequently during bootstrap.
|
|
329
|
+
// 避免刷屏:初始化阶段会频繁 seed,这里只打印一次。
|
|
330
|
+
try {{
|
|
331
|
+
if (!sessionStorage.getItem('opsbff-rmq-seed-log-once')) {{
|
|
332
|
+
sessionStorage.setItem('opsbff-rmq-seed-log-once', '1');
|
|
333
|
+
console.log('[OpsBFF] Credentials seeded to localStorage and sessionStorage.');
|
|
334
|
+
}}
|
|
335
|
+
}} catch (e) {{}}
|
|
336
|
+
}} catch(e) {{ console.error('[OpsBFF] Seed error:', e); }}
|
|
337
|
+
}}
|
|
338
|
+
seedCredentials();
|
|
339
|
+
|
|
340
|
+
// 2) Patch XHR to always carry Authorization header.
|
|
341
|
+
// RabbitMQ UI mainly uses jQuery/XHR for API calls.
|
|
342
|
+
try {{
|
|
343
|
+
var originalOpen = XMLHttpRequest.prototype.open;
|
|
344
|
+
var originalSend = XMLHttpRequest.prototype.send;
|
|
345
|
+
XMLHttpRequest.prototype.open = function(method, url) {{
|
|
346
|
+
this.__opsbff_url = url;
|
|
347
|
+
return originalOpen.apply(this, arguments);
|
|
348
|
+
}};
|
|
349
|
+
XMLHttpRequest.prototype.send = function(body) {{
|
|
350
|
+
try {{
|
|
351
|
+
// Add a fake auth header; proxy will replace it with real BasicAuth.
|
|
352
|
+
this.setRequestHeader('Authorization', FAKE_AUTH);
|
|
353
|
+
}} catch(e) {{}}
|
|
354
|
+
return originalSend.apply(this, arguments);
|
|
355
|
+
}};
|
|
356
|
+
console.log('[OpsBFF] XHR patched.');
|
|
357
|
+
}} catch(e) {{ console.error('[OpsBFF] XHR patch error:', e); }}
|
|
358
|
+
|
|
359
|
+
// 3) Patch fetch for modern JS code
|
|
360
|
+
try {{
|
|
361
|
+
var originalFetch = window.fetch;
|
|
362
|
+
if (originalFetch) {{
|
|
363
|
+
window.fetch = function(url, options) {{
|
|
364
|
+
if (!options) options = {{}};
|
|
365
|
+
if (!options.headers) options.headers = {{}};
|
|
366
|
+
if (options.headers instanceof Headers) {{
|
|
367
|
+
options.headers.set('Authorization', FAKE_AUTH);
|
|
368
|
+
}} else {{
|
|
369
|
+
options.headers['Authorization'] = FAKE_AUTH;
|
|
370
|
+
}}
|
|
371
|
+
return originalFetch(url, options);
|
|
372
|
+
}};
|
|
373
|
+
console.log('[OpsBFF] Fetch patched.');
|
|
374
|
+
}}
|
|
375
|
+
}} catch(e) {{ console.error('[OpsBFF] Fetch patch error:', e); }}
|
|
376
|
+
|
|
377
|
+
// 4) Route guard: if redirected to login page, force back to dashboard
|
|
378
|
+
function enforceDashboard() {{
|
|
379
|
+
var hash = window.location.hash.toLowerCase();
|
|
380
|
+
var path = window.location.pathname.toLowerCase();
|
|
381
|
+
var hasLoginForm = !!document.querySelector("input[name='username'], #login input, form[action*='login']");
|
|
382
|
+
if (hash.includes('login') || path.includes('login')) {{
|
|
383
|
+
console.log('[OpsBFF] Login redirect detected, forcing Dashboard...');
|
|
384
|
+
seedCredentials(); // 再次写入,防止被清除
|
|
385
|
+
if (hash.includes('login')) {{
|
|
386
|
+
window.location.hash = '#/';
|
|
387
|
+
}} else {{
|
|
388
|
+
// For path-based login (RabbitMQ 4 might use this)
|
|
389
|
+
window.history.replaceState(null, '', window.location.origin + window.location.pathname.replace(/login.*/i, ''));
|
|
390
|
+
}}
|
|
391
|
+
}}
|
|
392
|
+
|
|
393
|
+
if (hasLoginForm && !sessionStorage.getItem('opsbff-rmq-reload-once')) {{
|
|
394
|
+
sessionStorage.setItem('opsbff-rmq-reload-once', '1');
|
|
395
|
+
window.location.hash = '#/';
|
|
396
|
+
window.location.reload();
|
|
397
|
+
}}
|
|
398
|
+
}}
|
|
399
|
+
|
|
400
|
+
window.addEventListener('hashchange', enforceDashboard);
|
|
401
|
+
window.addEventListener('popstate', enforceDashboard);
|
|
402
|
+
|
|
403
|
+
// 定时轮询,防止初始化时的跳转
|
|
404
|
+
var checkTimer = setInterval(function() {{
|
|
405
|
+
enforceDashboard();
|
|
406
|
+
// 持续写入防止被清除,但避免无意义的重复写入
|
|
407
|
+
// Keep seeding during bootstrap, but skip when already present
|
|
408
|
+
if (!credentialsSeeded()) {{
|
|
409
|
+
seedCredentials();
|
|
410
|
+
}}
|
|
411
|
+
}}, 200);
|
|
412
|
+
// 10秒后停止轮询,节省性能
|
|
413
|
+
setTimeout(function() {{ clearInterval(checkTimer); }}, 10000);
|
|
414
|
+
|
|
415
|
+
// 立即检查一次
|
|
416
|
+
enforceDashboard();
|
|
417
|
+
|
|
418
|
+
}})();
|
|
419
|
+
</script>
|
|
420
|
+
"""
|
|
421
|
+
|
|
422
|
+
head_match = re.search(r"<head[^>]*>", html_str, flags=re.IGNORECASE)
|
|
423
|
+
if head_match:
|
|
424
|
+
insert_at = head_match.end()
|
|
425
|
+
return (html_str[:insert_at] + injection + html_str[insert_at:]).encode("utf-8")
|
|
426
|
+
|
|
427
|
+
title_match = re.search(r"<title[^>]*>", html_str, flags=re.IGNORECASE)
|
|
428
|
+
if title_match:
|
|
429
|
+
insert_at = title_match.start()
|
|
430
|
+
return (html_str[:insert_at] + injection + html_str[insert_at:]).encode("utf-8")
|
|
431
|
+
|
|
432
|
+
return (injection + html_str).encode("utf-8")
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
async def rabbitmq_mgmt_login_bypass_probe_handler() -> APIResponse[dict]:
|
|
436
|
+
"""RabbitMQ Management 免登录状态探针。
|
|
437
|
+
RabbitMQ Management login-bypass readiness probe.
|
|
438
|
+
"""
|
|
439
|
+
|
|
440
|
+
base_url = (LinglongConfig.RABBITMQ_MGMT_BASE_URL or "").rstrip("/")
|
|
441
|
+
upstream_whoami_url = f"{base_url}/api/whoami"
|
|
442
|
+
upstream_root_url = f"{base_url}/"
|
|
443
|
+
|
|
444
|
+
probe_result = {
|
|
445
|
+
"base_url": base_url,
|
|
446
|
+
"upstream_whoami_ok": False,
|
|
447
|
+
"upstream_root_html_ok": False,
|
|
448
|
+
"injector_contract_ok": False,
|
|
449
|
+
"login_bypass_ready": False,
|
|
450
|
+
"diagnostics": {
|
|
451
|
+
"whoami_status": None,
|
|
452
|
+
"whoami_body": None,
|
|
453
|
+
"root_status": None,
|
|
454
|
+
"root_content_type": None,
|
|
455
|
+
"injector_markers": {},
|
|
456
|
+
},
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
headers = {}
|
|
460
|
+
_inject_real_basic_auth(headers)
|
|
461
|
+
|
|
462
|
+
try:
|
|
463
|
+
whoami_resp = await http_client.fetch(
|
|
464
|
+
method=http.HTTPMethod.GET,
|
|
465
|
+
url=upstream_whoami_url,
|
|
466
|
+
headers=headers,
|
|
467
|
+
timeout=15.0,
|
|
468
|
+
passthrough_errors=True,
|
|
469
|
+
)
|
|
470
|
+
probe_result["diagnostics"]["whoami_status"] = whoami_resp.status
|
|
471
|
+
|
|
472
|
+
whoami_text = await whoami_resp.text()
|
|
473
|
+
probe_result["diagnostics"]["whoami_body"] = whoami_text[:256]
|
|
474
|
+
if whoami_resp.status == http.HTTPStatus.OK:
|
|
475
|
+
try:
|
|
476
|
+
whoami_json = json.loads(whoami_text)
|
|
477
|
+
probe_result["upstream_whoami_ok"] = bool(whoami_json.get("name"))
|
|
478
|
+
except Exception:
|
|
479
|
+
probe_result["upstream_whoami_ok"] = False
|
|
480
|
+
except Exception as exc: # noqa: BLE001
|
|
481
|
+
probe_result["diagnostics"]["whoami_body"] = f"whoami probe failed: {exc}"
|
|
482
|
+
|
|
483
|
+
try:
|
|
484
|
+
root_resp = await http_client.fetch(
|
|
485
|
+
method=http.HTTPMethod.GET,
|
|
486
|
+
url=upstream_root_url,
|
|
487
|
+
headers=headers,
|
|
488
|
+
timeout=15.0,
|
|
489
|
+
passthrough_errors=True,
|
|
490
|
+
)
|
|
491
|
+
probe_result["diagnostics"]["root_status"] = root_resp.status
|
|
492
|
+
root_content_type = _get_header_case_insensitive(dict(root_resp.headers), "content-type")
|
|
493
|
+
probe_result["diagnostics"]["root_content_type"] = root_content_type
|
|
494
|
+
|
|
495
|
+
root_bytes = await root_resp.read()
|
|
496
|
+
is_html = "text/html" in (root_content_type or "").lower()
|
|
497
|
+
probe_result["upstream_root_html_ok"] = root_resp.status == http.HTTPStatus.OK and is_html
|
|
498
|
+
|
|
499
|
+
injected = _inject_auto_login_script(root_bytes).decode("utf-8", errors="replace")
|
|
500
|
+
markers = {
|
|
501
|
+
"has_opsbff_marker": "[OpsBFF]" in injected,
|
|
502
|
+
"has_rabbitmq_credentials_key": "rabbitmq.credentials" in injected,
|
|
503
|
+
"has_rabbitmq_auth_scheme_key": "rabbitmq.auth-scheme" in injected,
|
|
504
|
+
"has_logged_in_cookie_seed": "loggedIn" in injected and "document.cookie = 'm='" in injected,
|
|
505
|
+
}
|
|
506
|
+
probe_result["diagnostics"]["injector_markers"] = markers
|
|
507
|
+
probe_result["injector_contract_ok"] = all(markers.values())
|
|
508
|
+
except Exception as exc: # noqa: BLE001
|
|
509
|
+
probe_result["diagnostics"]["root_content_type"] = f"root probe failed: {exc}"
|
|
510
|
+
|
|
511
|
+
probe_result["login_bypass_ready"] = bool(
|
|
512
|
+
probe_result["upstream_whoami_ok"]
|
|
513
|
+
and probe_result["upstream_root_html_ok"]
|
|
514
|
+
and probe_result["injector_contract_ok"]
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
return build_success_response(data=probe_result)
|
|
518
|
+
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""
|
|
2
|
+
redis-commander 代理 API
|
|
3
|
+
将前端对 /v1/opsbffsrv/redis_commander/* 的请求转发到 redis-commander.internal:8081
|
|
4
|
+
"""
|
|
5
|
+
import http
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from starlette.requests import Request
|
|
9
|
+
from starlette.responses import (
|
|
10
|
+
RedirectResponse,
|
|
11
|
+
Response,
|
|
12
|
+
)
|
|
13
|
+
from linglong_web.utils import logger
|
|
14
|
+
from linglong_web import http_client
|
|
15
|
+
from linglong_web import LinglongConfig
|
|
16
|
+
|
|
17
|
+
from cancan_microstack.public.error import HTTPException
|
|
18
|
+
from cancan_microstack.public.const.error import ErrorCode
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
async def redis_commander_proxy_handler(request: Request) -> Response:
|
|
22
|
+
"""
|
|
23
|
+
redis-commander 代理处理器 / Proxy handler for redis-commander
|
|
24
|
+
|
|
25
|
+
将所有 /v1/opsbffsrv/redis_commander/* 请求转发到 redis-commander.internal:8081/*
|
|
26
|
+
Forward every /v1/opsbffsrv/redis_commander/* request to redis-commander.internal:8081/*
|
|
27
|
+
"""
|
|
28
|
+
original_path = request.url.path
|
|
29
|
+
if original_path.endswith("/redis_commander") and not original_path.endswith("/redis_commander/"):
|
|
30
|
+
redirect_response = _build_proxy_aware_redirect(request)
|
|
31
|
+
if redirect_response:
|
|
32
|
+
return redirect_response
|
|
33
|
+
|
|
34
|
+
html_content = (
|
|
35
|
+
"<!DOCTYPE html><html lang=\"zh-CN\"><head>"
|
|
36
|
+
"<meta charset=\"utf-8\" />"
|
|
37
|
+
"<title>Redirecting</title>"
|
|
38
|
+
"<script>(function(){var url=new URL(window.location.href);"
|
|
39
|
+
"if(!url.pathname.endsWith('/')){url.pathname=url.pathname+'/';}"
|
|
40
|
+
"window.location.replace(url.toString());})();</script>"
|
|
41
|
+
"</head><body></body></html>"
|
|
42
|
+
)
|
|
43
|
+
return Response(
|
|
44
|
+
content=html_content,
|
|
45
|
+
status_code=http.HTTPStatus.OK.value,
|
|
46
|
+
media_type="text/html",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
redis_path = original_path.replace("/v1/opsbffsrv/redis_commander", "", 1)
|
|
50
|
+
if not redis_path:
|
|
51
|
+
redis_path = "/"
|
|
52
|
+
|
|
53
|
+
base_url = (LinglongConfig.REDIS_COMMANDER_BASE_URL or "").rstrip("/")
|
|
54
|
+
target_url = f"{base_url}{redis_path}"
|
|
55
|
+
if request.url.query:
|
|
56
|
+
target_url = f"{target_url}?{request.url.query}"
|
|
57
|
+
|
|
58
|
+
logger.debug(f"Proxying {request.method} {original_path} -> {target_url}")
|
|
59
|
+
|
|
60
|
+
headers = dict(request.headers)
|
|
61
|
+
headers.pop('host', None)
|
|
62
|
+
|
|
63
|
+
body = None
|
|
64
|
+
if request.method in ("POST", "PUT", "PATCH"):
|
|
65
|
+
body = await request.body()
|
|
66
|
+
|
|
67
|
+
resp = await http_client.fetch(
|
|
68
|
+
method=request.method,
|
|
69
|
+
url=target_url,
|
|
70
|
+
format_type='raw', # 禁止解析二进制响应 / keep binary payload intact
|
|
71
|
+
headers=headers,
|
|
72
|
+
data=body,
|
|
73
|
+
timeout=30.0,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if not resp:
|
|
77
|
+
logger.error(f"Failed to proxy request to redis-commander: {target_url}")
|
|
78
|
+
raise HTTPException(
|
|
79
|
+
status_code=http.HTTPStatus.BAD_GATEWAY.value,
|
|
80
|
+
error_code=ErrorCode.NETWORK_ERROR,
|
|
81
|
+
msg="Failed to connect to redis-commander",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
content = await resp.read()
|
|
85
|
+
response_headers = dict(resp.headers)
|
|
86
|
+
response_headers.pop('transfer-encoding', None)
|
|
87
|
+
response_headers.pop('content-encoding', None)
|
|
88
|
+
|
|
89
|
+
logger.debug(f"Proxied redis-commander response: {resp.status} {len(content)} bytes")
|
|
90
|
+
|
|
91
|
+
return Response(
|
|
92
|
+
content=content,
|
|
93
|
+
status_code=resp.status,
|
|
94
|
+
headers=response_headers,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _build_proxy_aware_redirect(request: Request) -> Optional[RedirectResponse]:
|
|
99
|
+
"""构造考虑代理头的 308 重定向 / Build a proxy-aware 308 redirect"""
|
|
100
|
+
forwarded_uri = request.headers.get("x-original-uri") or request.headers.get("x-forwarded-uri")
|
|
101
|
+
corrected_path: Optional[str] = None
|
|
102
|
+
|
|
103
|
+
if forwarded_uri:
|
|
104
|
+
corrected_path = forwarded_uri if forwarded_uri.endswith("/") else f"{forwarded_uri}/"
|
|
105
|
+
else:
|
|
106
|
+
forwarded_prefix = request.headers.get("x-forwarded-prefix")
|
|
107
|
+
if forwarded_prefix:
|
|
108
|
+
trimmed = forwarded_prefix.rstrip("/")
|
|
109
|
+
corrected_path = f"{trimmed}/"
|
|
110
|
+
|
|
111
|
+
if not corrected_path:
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
scheme = request.headers.get("x-forwarded-proto") or request.url.scheme
|
|
115
|
+
host = request.headers.get("x-forwarded-host") or request.headers.get("host") or request.url.hostname
|
|
116
|
+
port = request.headers.get("x-forwarded-port")
|
|
117
|
+
|
|
118
|
+
if not host:
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
netloc = host
|
|
122
|
+
if port and ":" not in host:
|
|
123
|
+
netloc = f"{host}:{port}"
|
|
124
|
+
|
|
125
|
+
redirect_target = f"{scheme}://{netloc}{corrected_path}"
|
|
126
|
+
if request.url.query:
|
|
127
|
+
redirect_target = f"{redirect_target}?{request.url.query}"
|
|
128
|
+
|
|
129
|
+
logger.debug(f"Redirecting redis-commander request to {redirect_target}")
|
|
130
|
+
return RedirectResponse(
|
|
131
|
+
url=redirect_target,
|
|
132
|
+
status_code=http.HTTPStatus.PERMANENT_REDIRECT.value,
|
|
133
|
+
)
|