aegis-stack 0.2.0rc2__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.
- aegis/__init__.py +5 -0
- aegis/__main__.py +51 -0
- aegis/cli/__init__.py +6 -0
- aegis/cli/callbacks.py +114 -0
- aegis/cli/interactive.py +611 -0
- aegis/cli/utils.py +70 -0
- aegis/cli/validators.py +34 -0
- aegis/commands/__init__.py +6 -0
- aegis/commands/add.py +353 -0
- aegis/commands/add_service.py +332 -0
- aegis/commands/components.py +35 -0
- aegis/commands/init.py +370 -0
- aegis/commands/remove.py +227 -0
- aegis/commands/services.py +52 -0
- aegis/commands/update.py +252 -0
- aegis/commands/version.py +12 -0
- aegis/config/__init__.py +1 -0
- aegis/config/shared_files.py +136 -0
- aegis/core/CLAUDE.md +377 -0
- aegis/core/__init__.py +6 -0
- aegis/core/component_files.py +228 -0
- aegis/core/component_utils.py +220 -0
- aegis/core/components.py +127 -0
- aegis/core/copier_manager.py +315 -0
- aegis/core/copier_updater.py +475 -0
- aegis/core/dependency_resolver.py +119 -0
- aegis/core/manual_updater.py +554 -0
- aegis/core/post_gen_tasks.py +547 -0
- aegis/core/service_resolver.py +261 -0
- aegis/core/services.py +157 -0
- aegis/core/template_generator.py +266 -0
- aegis/core/version_compatibility.py +259 -0
- aegis/templates/CLAUDE.md +591 -0
- aegis/templates/cookiecutter-aegis-project/cookiecutter.json +39 -0
- aegis/templates/cookiecutter-aegis-project/hooks/post_gen_project.py +214 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.dockerignore +71 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.env.example.j2 +130 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.gitignore +131 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Dockerfile +53 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Makefile +236 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/README.md.j2 +196 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/alembic.ini.j2 +111 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/env.py.j2 +91 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/script.py.mako +25 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/versions/001_initial_auth.py.j2 +51 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/__init__.py +5 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/__init__.py +6 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/ai.py.j2 +700 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/ai_rendering.py +361 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/auth.py.j2 +253 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/health.py.j2 +419 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/load_test.py.j2 +656 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/main.py.j2 +65 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/marko_terminal_renderer.py +489 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/tasks.py.j2 +328 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/{% if cookiecutter.include_scheduler == /"yes/" %}tasks.py{% endif %}" +340 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/ai/__init__.py +8 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/ai/router.py +329 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/auth/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/auth/router.py +64 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/deps.py +58 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/health.py +163 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/models.py.j2 +280 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/routing.py.j2 +32 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/scheduler.py.j2 +121 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/worker.py.j2 +478 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/hooks.py +144 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/main.py +31 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/cors.py +20 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/cleanup.py +14 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/component_health.py.j2 +418 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/database_init.py.j2 +83 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/__init__.py +5 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/__init__.py +27 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/table.py +78 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/text.py +142 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/__init__.py.j2 +47 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/ai_card.py +287 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/auth_card.py +198 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/base_card.py +256 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/card_factory.py +227 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/card_utils.py +333 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/database_card.py +420 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/fastapi_card.py +328 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/flet_card.py +267 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/redis_card.py +322 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/scheduler_card.py +352 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/services_card.py +233 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/worker_card.py +684 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/main.py.j2 +653 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/theme.py +48 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/main.py.j2 +156 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/CLAUDE.md.j2 +213 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/__init__.py +6 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/constants.py.j2 +30 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/pools.py +97 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/load_test.py +55 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/media.py +49 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/system.py +44 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/registry.py +139 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/__init__.py +120 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/load_tasks.py +507 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/simple_system_tasks.py +33 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/system_tasks.py +281 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/config.py.j2 +178 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/constants.py +58 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/db.py.j2 +176 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/log.py +92 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/security.py +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/webserver.py +40 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/{% if cookiecutter.include_scheduler == /"yes/" %}scheduler.py{% endif %}" +21 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/main.py +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/models/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/models/user.py +44 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/py.typed +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/__init__.py +8 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/config.py +130 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/conversation.py +213 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/health.py +96 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/models.py +229 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/providers.py +370 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/service.py +388 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/auth_service.py +41 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/health.py +164 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/user_service.py +83 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/middleware_inspector.py.j2 +223 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/models.py.j2 +70 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/route_inspector.py.j2 +155 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test.py +679 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test_models.py +266 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/__init__.py.j2 +21 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/models.py.j2 +119 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/scheduled_task_manager.py.j2 +273 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/task_monitor.py.j2 +189 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/__init__.py +15 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/models.py +26 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/__init__.py +52 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/alerts.py +94 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/backup.py.j2 +119 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/health.py.j2 +1333 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/models.py +243 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/ui.py +52 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto-dark.png +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto-square-backup.png +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto.png +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.dockerignore +71 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.env.example.j2 +64 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.gitignore +131 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/Dockerfile +53 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/Makefile +211 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/README.md.j2 +172 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/docker-compose.yml.j2 +78 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/mkdocs.yml.j2 +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/pyproject.toml.j2 +120 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/uv.lock +1673 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docker-compose.yml.j2 +200 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/api.md +191 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/components/scheduler.md +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/components/scheduler.md.j2 +621 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/development.md +215 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/health.md +240 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/javascripts/mermaid-config.js +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/stylesheets/mermaid.css +95 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/mkdocs.yml.j2 +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/pyproject.toml.j2 +131 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh +87 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh.j2 +93 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/gen_docs.py +16 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_auth_endpoints.py.j2 +307 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_health_endpoints.py.j2 +262 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_scheduler_endpoints.py.j2 +214 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_worker_endpoints.py.j2 +165 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/cli/test_ai_rendering.py +427 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/cli/test_conversation_memory.py +465 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/components/test_scheduler.py +43 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/conftest.py.j2 +195 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/conftest.py +78 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_health.py +157 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_models.py +164 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_service.py +198 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_auth_integration.py.j2 +528 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_component_integration.py.j2 +387 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_conversation_persistence.py +342 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_health_logic.py.j2 +663 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_models.py +619 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_service.py +603 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_middleware_inspector.py.j2 +248 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_scheduled_task_manager.py.j2 +292 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_system_service.py +98 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_worker_health_registration.py.j2 +257 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/test_core.py +49 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/uv.lock +1673 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/.copier-answers.yml.jinja +21 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/.dockerignore +71 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/.env.example.jinja +130 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/.gitignore +131 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/Dockerfile +53 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/Makefile.jinja +236 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/README.md.jinja +196 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/alembic.ini.jinja +111 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/env.py.jinja +91 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/script.py.mako +25 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/versions/001_initial_auth.py.jinja +51 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/__init__.py.jinja +5 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/__init__.py.jinja +6 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/ai.py.jinja +700 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/ai_rendering.py +360 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/auth.py.jinja +253 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/health.py.jinja +419 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/load_test.py.jinja +656 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/main.py.jinja +65 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/marko_terminal_renderer.py +489 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/tasks.py.jinja +328 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/__init__.py +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/__init__.py +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/ai/__init__.py +8 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/ai/router.py +329 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/auth/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/auth/router.py +64 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/deps.py +58 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/health.py.jinja +163 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/models.py.jinja +280 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/routing.py.jinja +32 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/scheduler.py.jinja +121 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/worker.py.jinja +478 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/hooks.py +144 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/main.py +31 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/middleware/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/middleware/cors.py +20 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/shutdown/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/shutdown/cleanup.py +14 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/component_health.py.jinja +418 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/database_init.py.jinja +83 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/__init__.py +5 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/__init__.py +27 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/table.py +78 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/text.py +142 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/__init__.py.jinja +47 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/ai_card.py +287 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/auth_card.py +198 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/base_card.py +256 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/card_factory.py +227 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/card_utils.py +333 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/database_card.py +420 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/fastapi_card.py +328 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/flet_card.py +267 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/redis_card.py +322 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/scheduler_card.py +352 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/services_card.py +233 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/worker_card.py +684 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/main.py.jinja +653 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/theme.py +48 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/scheduler/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/scheduler/main.py.jinja +156 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/CLAUDE.md.jinja +213 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/__init__.py +6 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/constants.py.jinja +30 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/pools.py +97 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/load_test.py +55 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/media.py +49 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/system.py +44 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/registry.py +139 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/__init__.py +120 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/load_tasks.py +507 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/simple_system_tasks.py +33 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/system_tasks.py +281 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/config.py.jinja +178 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/constants.py +58 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/db.py.jinja +176 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/log.py +92 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/security.py +62 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/scheduler.py.jinja +21 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/webserver.py +39 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/integrations/__init__.py +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/integrations/main.py +61 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/models/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/models/user.py +44 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/py.typed +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/__init__.py +8 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/config.py +130 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/conversation.py +213 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/health.py +96 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/models.py +229 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/providers.py.jinja +370 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/service.py +387 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/auth_service.py +40 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/health.py +162 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/user_service.py +82 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/middleware_inspector.py.jinja +223 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/models.py.jinja +70 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/route_inspector.py.jinja +155 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/load_test.py +678 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/load_test_models.py +265 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/__init__.py.jinja +21 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/models.py.jinja +119 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/scheduled_task_manager.py.jinja +273 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/task_monitor.py.jinja +189 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/shared/__init__.py +15 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/shared/models.py +26 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/__init__.py +52 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/alerts.py +94 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/backup.py.jinja +119 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/health.py.jinja +1333 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/models.py +243 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/ui.py +52 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57223!aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57224!aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57225!aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57533!aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57534!aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57538!aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57897!aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57898!aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57904!aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58315!aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58316!aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58324!aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58837!aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58838!aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58849!aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto-dark.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto-square-backup.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto.png +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/.env.example.jinja +64 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/README.md.jinja +172 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/docker-compose.yml.jinja +78 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/mkdocs.yml.jinja +62 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/pyproject.toml.jinja +120 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docker-compose.yml.jinja +200 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/api.md.jinja +191 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/components/scheduler.md +0 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/components/scheduler.md.jinja +621 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/development.md.jinja +215 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/health.md.jinja +240 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/javascripts/mermaid-config.js +62 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/docs/stylesheets/mermaid.css +95 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/mkdocs.yml.jinja +62 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/pyproject.toml.jinja +131 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/entrypoint.sh +87 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/entrypoint.sh.jinja +93 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/gen_docs.py +16 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_auth_endpoints.py.jinja +307 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_health_endpoints.py.jinja +262 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_scheduler_endpoints.py.jinja +214 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_worker_endpoints.py.jinja +165 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/cli/test_ai_rendering.py +427 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/cli/test_conversation_memory.py +465 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/components/test_scheduler.py +43 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/conftest.py.jinja +195 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/__init__.py +1 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/conftest.py +78 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_health.py +157 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_models.py +164 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_service.py +198 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_auth_integration.py.jinja +528 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_component_integration.py.jinja +387 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_conversation_persistence.py +342 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_health_logic.py.jinja +663 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_load_test_models.py +619 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_load_test_service.py +603 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_middleware_inspector.py.jinja +248 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_scheduled_task_manager.py.jinja +292 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_system_service.py +98 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_worker_health_registration.py.jinja +257 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/tests/test_core.py +49 -0
- aegis/templates/copier-aegis-project/{{ project_slug }}/uv.lock +1673 -0
- aegis_stack-0.2.0rc2.dist-info/METADATA +165 -0
- aegis_stack-0.2.0rc2.dist-info/RECORD +392 -0
- aegis_stack-0.2.0rc2.dist-info/WHEEL +4 -0
- aegis_stack-0.2.0rc2.dist-info/entry_points.txt +3 -0
- aegis_stack-0.2.0rc2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
"""Stunning marketing-grade dashboard with professional component cards."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from collections.abc import Awaitable, Callable
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import flet as ft
|
|
8
|
+
from flet import PageDisconnectedException
|
|
9
|
+
|
|
10
|
+
from app.core.log import logger
|
|
11
|
+
from app.services.system.models import ComponentStatus, ComponentStatusType
|
|
12
|
+
from .dashboard.cards import (
|
|
13
|
+
FastAPICard,
|
|
14
|
+
FletCard,
|
|
15
|
+
{%- if include_ai %}
|
|
16
|
+
AICard,
|
|
17
|
+
{%- endif %}
|
|
18
|
+
{%- if include_auth %}
|
|
19
|
+
AuthCard,
|
|
20
|
+
ServicesCard,
|
|
21
|
+
{%- endif %}
|
|
22
|
+
{%- if include_database %}
|
|
23
|
+
DatabaseCard,
|
|
24
|
+
{%- endif %}
|
|
25
|
+
{%- if include_redis %}
|
|
26
|
+
RedisCard,
|
|
27
|
+
{%- endif %}
|
|
28
|
+
{%- if include_scheduler %}
|
|
29
|
+
SchedulerCard,
|
|
30
|
+
{%- endif %}
|
|
31
|
+
{%- if include_worker %}
|
|
32
|
+
WorkerCard,
|
|
33
|
+
{%- endif %}
|
|
34
|
+
)
|
|
35
|
+
from .dashboard.cards.card_utils import create_health_status_indicator
|
|
36
|
+
from .theme import ThemeManager
|
|
37
|
+
|
|
38
|
+
# Constants for health system grouping
|
|
39
|
+
COMPONENTS_GROUP_KEY = "components"
|
|
40
|
+
SERVICES_GROUP_KEY = "services"
|
|
41
|
+
SERVICE_PREFIX = "service_"
|
|
42
|
+
|
|
43
|
+
# Use simple filenames - Flet should auto-resolve from assets_dir
|
|
44
|
+
DEFAULT_LOGO_PATH = "aegis-manifesto.png"
|
|
45
|
+
DEFAULT_DARK_LOGO_PATH = "aegis-manifesto-dark.png"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class SystemDashboard:
|
|
49
|
+
"""
|
|
50
|
+
Professional system dashboard with safe component references.
|
|
51
|
+
|
|
52
|
+
Eliminates IndexError crashes by storing direct references to dashboard
|
|
53
|
+
components instead of using brittle index-based access patterns.
|
|
54
|
+
|
|
55
|
+
Includes robust page disconnection handling to prevent crashes when
|
|
56
|
+
users navigate away from the dashboard during auto-refresh cycles.
|
|
57
|
+
|
|
58
|
+
Note: Uses defensive programming around Flet's private APIs for
|
|
59
|
+
connection checking. This may need updates with future Flet versions.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self):
|
|
63
|
+
# Direct component references - no more brittle indexing!
|
|
64
|
+
self._health_indicator_container: ft.Container | None = None
|
|
65
|
+
self._cards_container: ft.Container | None = None
|
|
66
|
+
self._theme_manager: ThemeManager | None = None
|
|
67
|
+
self._logo_image: ft.Image | None = None
|
|
68
|
+
self._page: ft.Page | None = None
|
|
69
|
+
|
|
70
|
+
def initialize_components(
|
|
71
|
+
self,
|
|
72
|
+
health_indicator_container: ft.Container,
|
|
73
|
+
cards_container: ft.Container,
|
|
74
|
+
theme_manager: ThemeManager,
|
|
75
|
+
logo_image: ft.Image,
|
|
76
|
+
page: ft.Page,
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Initialize dashboard with component references."""
|
|
79
|
+
self._health_indicator_container = health_indicator_container
|
|
80
|
+
self._cards_container = cards_container
|
|
81
|
+
self._theme_manager = theme_manager
|
|
82
|
+
self._logo_image = logo_image
|
|
83
|
+
self._page = page
|
|
84
|
+
|
|
85
|
+
# Log Flet version for debugging connection check compatibility
|
|
86
|
+
try:
|
|
87
|
+
logger.debug(f"Initializing dashboard with Flet version: {ft.__version__}")
|
|
88
|
+
except AttributeError:
|
|
89
|
+
logger.debug("Flet version not available for compatibility logging")
|
|
90
|
+
|
|
91
|
+
def _is_page_connected(self) -> bool:
|
|
92
|
+
"""
|
|
93
|
+
Check if the page is still connected.
|
|
94
|
+
|
|
95
|
+
Note: This uses Flet's private attribute access as a last resort.
|
|
96
|
+
This is necessary because Flet doesn't provide a public API for
|
|
97
|
+
connection status checking. While brittle, this prevents crashes
|
|
98
|
+
when users navigate away from the dashboard.
|
|
99
|
+
|
|
100
|
+
Returns False on any error to fail safely.
|
|
101
|
+
"""
|
|
102
|
+
try:
|
|
103
|
+
if self._page is None:
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
# Attempt to access Flet's private connection attribute
|
|
107
|
+
# This may break with future Flet versions, but will fail safely
|
|
108
|
+
if not hasattr(self._page, '_Page__conn'):
|
|
109
|
+
logger.debug(
|
|
110
|
+
"Flet page connection attribute not found - assuming disconnected"
|
|
111
|
+
)
|
|
112
|
+
return False
|
|
113
|
+
|
|
114
|
+
return self._page._Page__conn is not None
|
|
115
|
+
|
|
116
|
+
except (AttributeError, Exception) as e:
|
|
117
|
+
# If anything goes wrong with connection checking, assume disconnected
|
|
118
|
+
# This provides defensive behavior against Flet internal changes
|
|
119
|
+
logger.debug(f"Page connection check failed, assuming disconnected: {e}")
|
|
120
|
+
return False
|
|
121
|
+
|
|
122
|
+
async def update_health_status(self, healthy_count: int, total_count: int) -> None:
|
|
123
|
+
"""Safely update health status indicator."""
|
|
124
|
+
if not self._is_page_connected():
|
|
125
|
+
logger.debug("Page disconnected, skipping health status update")
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
if not self._health_indicator_container:
|
|
129
|
+
logger.warning("Health indicator container not initialized")
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
new_health_indicator = create_health_status_indicator(
|
|
134
|
+
healthy_count, total_count
|
|
135
|
+
)
|
|
136
|
+
self._health_indicator_container.content = new_health_indicator
|
|
137
|
+
# Check connection again before updating container
|
|
138
|
+
if self._is_page_connected():
|
|
139
|
+
self._health_indicator_container.update()
|
|
140
|
+
except PageDisconnectedException:
|
|
141
|
+
logger.debug("Page disconnected during health status update")
|
|
142
|
+
return
|
|
143
|
+
except Exception as e:
|
|
144
|
+
logger.error(
|
|
145
|
+
f"Failed to update health status: {e}",
|
|
146
|
+
exc_info=True,
|
|
147
|
+
extra={
|
|
148
|
+
"error_type": type(e).__name__,
|
|
149
|
+
"function": "update_health_status",
|
|
150
|
+
"healthy_count": healthy_count,
|
|
151
|
+
"total_count": total_count,
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
async def update_component_cards(
|
|
156
|
+
self, components: dict[str, ComponentStatus], card_creator_fn: Callable
|
|
157
|
+
) -> None:
|
|
158
|
+
"""Safely update component cards."""
|
|
159
|
+
if not self._is_page_connected():
|
|
160
|
+
logger.debug("Page disconnected, skipping component cards update")
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
if not self._cards_container or not self._cards_container.content:
|
|
164
|
+
logger.warning("Cards container not initialized")
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
# Clear existing cards
|
|
169
|
+
self._cards_container.content.controls.clear()
|
|
170
|
+
|
|
171
|
+
# Create cards for all available components with responsive sizing
|
|
172
|
+
for component_name, component_data in components.items():
|
|
173
|
+
card = card_creator_fn(component_name, component_data)
|
|
174
|
+
if (
|
|
175
|
+
isinstance(card.content, ft.Text)
|
|
176
|
+
and "Unknown component" in card.content.value
|
|
177
|
+
):
|
|
178
|
+
continue # Skip unknown components
|
|
179
|
+
|
|
180
|
+
# Set responsive column sizing based on card type
|
|
181
|
+
if component_name.startswith("service_"):
|
|
182
|
+
# Service cards: 1/3 width (4 = 33% = 3 columns)
|
|
183
|
+
card.col = {"xs": 12, "sm": 6, "md": 4, "lg": 4, "xl": 4}
|
|
184
|
+
else:
|
|
185
|
+
# Component cards: 1/2 width (6 = 50% = 2 columns)
|
|
186
|
+
card.col = {"xs": 12, "sm": 12, "md": 6, "lg": 6, "xl": 6}
|
|
187
|
+
self._cards_container.content.controls.append(card)
|
|
188
|
+
|
|
189
|
+
# Check connection again before updating container
|
|
190
|
+
if self._is_page_connected():
|
|
191
|
+
self._cards_container.update()
|
|
192
|
+
except PageDisconnectedException:
|
|
193
|
+
logger.debug("Page disconnected during component cards update")
|
|
194
|
+
return
|
|
195
|
+
except Exception as e:
|
|
196
|
+
logger.error(
|
|
197
|
+
f"Failed to update component cards: {e}",
|
|
198
|
+
exc_info=True,
|
|
199
|
+
extra={
|
|
200
|
+
"error_type": type(e).__name__,
|
|
201
|
+
"function": "update_component_cards",
|
|
202
|
+
"component_count": len(components),
|
|
203
|
+
}
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
async def show_error_status(self) -> None:
|
|
207
|
+
"""Safely show error status in health indicator."""
|
|
208
|
+
if not self._is_page_connected():
|
|
209
|
+
logger.debug("Page disconnected, skipping error status display")
|
|
210
|
+
return
|
|
211
|
+
|
|
212
|
+
if not self._health_indicator_container:
|
|
213
|
+
logger.warning(
|
|
214
|
+
"Health indicator container not initialized for error display"
|
|
215
|
+
)
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
try:
|
|
219
|
+
error_indicator = create_health_status_indicator(0, 1)
|
|
220
|
+
self._health_indicator_container.content = error_indicator
|
|
221
|
+
# Check connection again before updating container
|
|
222
|
+
if self._is_page_connected():
|
|
223
|
+
self._health_indicator_container.update()
|
|
224
|
+
except PageDisconnectedException:
|
|
225
|
+
logger.debug("Page disconnected during error status display")
|
|
226
|
+
return
|
|
227
|
+
except Exception as e:
|
|
228
|
+
logger.error(
|
|
229
|
+
f"Failed to show error status: {e}",
|
|
230
|
+
exc_info=True,
|
|
231
|
+
extra={
|
|
232
|
+
"error_type": type(e).__name__,
|
|
233
|
+
"function": "show_error_status",
|
|
234
|
+
}
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# Load both light and dark logos as base64
|
|
239
|
+
def get_logo_base64(dark_mode: bool = False) -> str:
|
|
240
|
+
"""Get the logo as base64 for light or dark mode."""
|
|
241
|
+
try:
|
|
242
|
+
import base64
|
|
243
|
+
from pathlib import Path
|
|
244
|
+
|
|
245
|
+
filename = "aegis-manifesto-dark.png" if dark_mode else "aegis-manifesto.png"
|
|
246
|
+
logo_path = Path(__file__).parent.parent.parent.parent / "assets" / filename
|
|
247
|
+
with open(logo_path, "rb") as f:
|
|
248
|
+
return base64.b64encode(f.read()).decode()
|
|
249
|
+
except Exception:
|
|
250
|
+
# Fallback to tiny red pixel if file read fails
|
|
251
|
+
return (
|
|
252
|
+
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHg"
|
|
253
|
+
"gJ/PchI7wAAAABJRU5ErkJggg=="
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def create_frontend_app() -> Callable[[ft.Page], Awaitable[None]]:
|
|
258
|
+
"""Returns the Flet target function - simple system health dashboard."""
|
|
259
|
+
|
|
260
|
+
async def flet_main(page: ft.Page) -> None:
|
|
261
|
+
page.title = "Aegis Stack - System Dashboard"
|
|
262
|
+
page.padding = ft.padding.only(
|
|
263
|
+
left=20, right=20, top=20, bottom=20
|
|
264
|
+
) # Proper left padding
|
|
265
|
+
page.scroll = ft.ScrollMode.AUTO
|
|
266
|
+
|
|
267
|
+
# Simple theme setup
|
|
268
|
+
theme_manager = ThemeManager(page)
|
|
269
|
+
await theme_manager.initialize_themes()
|
|
270
|
+
|
|
271
|
+
# Aegis Stack logo - bigger size and theme-aware loading
|
|
272
|
+
logo_image = ft.Image(
|
|
273
|
+
src_base64=get_logo_base64(theme_manager.is_dark_mode),
|
|
274
|
+
width=300, # Bigger logo
|
|
275
|
+
height=90, # Bigger logo
|
|
276
|
+
fit=ft.ImageFit.CONTAIN,
|
|
277
|
+
error_content=ft.Text(
|
|
278
|
+
"AEGIS STACK",
|
|
279
|
+
size=20,
|
|
280
|
+
weight=ft.FontWeight.BOLD,
|
|
281
|
+
color=ft.Colors.ON_SURFACE,
|
|
282
|
+
),
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
# Theme toggle button
|
|
286
|
+
theme_button = ft.IconButton(
|
|
287
|
+
icon=ft.Icons.DARK_MODE,
|
|
288
|
+
tooltip="Switch to Dark Mode",
|
|
289
|
+
icon_size=24,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
async def update_logo() -> None:
|
|
293
|
+
"""Update logo based on current theme."""
|
|
294
|
+
try:
|
|
295
|
+
# Update the base64 source for the current theme
|
|
296
|
+
logo_image.src_base64 = get_logo_base64(theme_manager.is_dark_mode)
|
|
297
|
+
logo_image.src = None # Clear the src to use src_base64
|
|
298
|
+
except Exception as e:
|
|
299
|
+
logger.error(
|
|
300
|
+
f"Logo update failed: {e}",
|
|
301
|
+
exc_info=True,
|
|
302
|
+
extra={
|
|
303
|
+
"error_type": type(e).__name__,
|
|
304
|
+
"function": "update_logo",
|
|
305
|
+
"is_dark_mode": getattr(
|
|
306
|
+
theme_manager, 'is_dark_mode', 'unknown'
|
|
307
|
+
)
|
|
308
|
+
}
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
async def toggle_theme(_: Any) -> None:
|
|
312
|
+
"""Toggle theme and update button icon and logo."""
|
|
313
|
+
try:
|
|
314
|
+
await theme_manager.toggle_theme()
|
|
315
|
+
if theme_manager.is_dark_mode:
|
|
316
|
+
theme_button.icon = ft.Icons.LIGHT_MODE
|
|
317
|
+
theme_button.tooltip = "Switch to Light Mode"
|
|
318
|
+
else:
|
|
319
|
+
theme_button.icon = ft.Icons.DARK_MODE
|
|
320
|
+
theme_button.tooltip = "Switch to Dark Mode"
|
|
321
|
+
|
|
322
|
+
# Update logo immediately after theme change
|
|
323
|
+
await update_logo()
|
|
324
|
+
# Safe updates - check connection first
|
|
325
|
+
if dashboard._is_page_connected():
|
|
326
|
+
try:
|
|
327
|
+
logo_image.update()
|
|
328
|
+
page.update()
|
|
329
|
+
except PageDisconnectedException:
|
|
330
|
+
logger.debug("Page disconnected during theme toggle")
|
|
331
|
+
except PageDisconnectedException:
|
|
332
|
+
logger.debug("Page disconnected during theme toggle")
|
|
333
|
+
except Exception as e:
|
|
334
|
+
logger.error(
|
|
335
|
+
f"Theme toggle failed: {e}",
|
|
336
|
+
exc_info=True,
|
|
337
|
+
extra={
|
|
338
|
+
"error_type": type(e).__name__,
|
|
339
|
+
"function": "toggle_theme"
|
|
340
|
+
}
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
theme_button.on_click = toggle_theme
|
|
344
|
+
|
|
345
|
+
# Set initial logo based on current theme after theme manager is ready
|
|
346
|
+
await update_logo()
|
|
347
|
+
|
|
348
|
+
# Health status indicator with circular progress - create before header
|
|
349
|
+
health_status_indicator = create_health_status_indicator(
|
|
350
|
+
0, 0
|
|
351
|
+
) # Start with loading state
|
|
352
|
+
|
|
353
|
+
# Create health indicator container with direct reference
|
|
354
|
+
# (no more brittle indexing)
|
|
355
|
+
health_indicator_container = ft.Container(
|
|
356
|
+
content=health_status_indicator,
|
|
357
|
+
margin=ft.margin.only(right=20), # Space before theme button
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
# Professional header with Aegis Stack logo positioned further left
|
|
361
|
+
header = ft.Container(
|
|
362
|
+
content=ft.Row(
|
|
363
|
+
[
|
|
364
|
+
ft.Row(
|
|
365
|
+
[
|
|
366
|
+
ft.Container(
|
|
367
|
+
content=logo_image,
|
|
368
|
+
margin=ft.margin.only(left=-20), # Adjust for padding
|
|
369
|
+
),
|
|
370
|
+
ft.Container(
|
|
371
|
+
content=ft.Text(
|
|
372
|
+
"System Health Dashboard",
|
|
373
|
+
size=24,
|
|
374
|
+
weight=ft.FontWeight.W_400,
|
|
375
|
+
color=ft.Colors.ON_SURFACE,
|
|
376
|
+
),
|
|
377
|
+
margin=ft.margin.only(left=10), # Logo spacing
|
|
378
|
+
),
|
|
379
|
+
],
|
|
380
|
+
alignment=ft.MainAxisAlignment.START,
|
|
381
|
+
),
|
|
382
|
+
health_indicator_container, # Use the direct reference
|
|
383
|
+
ft.Container(content=theme_button, padding=10),
|
|
384
|
+
],
|
|
385
|
+
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
|
|
386
|
+
),
|
|
387
|
+
margin=ft.margin.only(bottom=20),
|
|
388
|
+
padding=ft.padding.only(left=0, right=0), # Remove any default padding
|
|
389
|
+
)
|
|
390
|
+
# Responsive grid container - force 2 columns always
|
|
391
|
+
component_cards_container = ft.Container(
|
|
392
|
+
content=ft.ResponsiveRow(
|
|
393
|
+
controls=[], # Will be populated with cards
|
|
394
|
+
spacing=20, # Space between cards
|
|
395
|
+
run_spacing=20, # Space between rows
|
|
396
|
+
),
|
|
397
|
+
alignment=ft.alignment.center,
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
# Create SystemDashboard with safe component references
|
|
401
|
+
dashboard = SystemDashboard()
|
|
402
|
+
|
|
403
|
+
# Initialize dashboard with component references (using direct references)
|
|
404
|
+
dashboard.initialize_components(
|
|
405
|
+
health_indicator_container=health_indicator_container,
|
|
406
|
+
cards_container=component_cards_container,
|
|
407
|
+
theme_manager=theme_manager,
|
|
408
|
+
logo_image=logo_image,
|
|
409
|
+
page=page,
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
# Add everything to page with modern layout
|
|
413
|
+
page.add(
|
|
414
|
+
header,
|
|
415
|
+
ft.Container(
|
|
416
|
+
content=ft.Column(
|
|
417
|
+
[
|
|
418
|
+
ft.Divider(color=ft.Colors.OUTLINE_VARIANT),
|
|
419
|
+
ft.Text(
|
|
420
|
+
"System Components",
|
|
421
|
+
size=24,
|
|
422
|
+
weight=ft.FontWeight.W_600,
|
|
423
|
+
color=ft.Colors.ON_SURFACE,
|
|
424
|
+
),
|
|
425
|
+
component_cards_container,
|
|
426
|
+
],
|
|
427
|
+
spacing=20,
|
|
428
|
+
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
|
429
|
+
),
|
|
430
|
+
alignment=ft.alignment.top_center,
|
|
431
|
+
),
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
def create_component_card(
|
|
435
|
+
component_name: str, component_data: Any
|
|
436
|
+
) -> ft.Container:
|
|
437
|
+
"""Create stunning marketing-grade component cards."""
|
|
438
|
+
try:
|
|
439
|
+
if not component_data:
|
|
440
|
+
logger.warning(f"No component data provided for {component_name}")
|
|
441
|
+
return ft.Container()
|
|
442
|
+
|
|
443
|
+
# Map component names to their stunning card classes
|
|
444
|
+
if component_name == "backend":
|
|
445
|
+
return FastAPICard(component_data).build()
|
|
446
|
+
elif component_name == "frontend":
|
|
447
|
+
return FletCard(component_data).build()
|
|
448
|
+
{%- if include_worker %}
|
|
449
|
+
elif component_name == "worker":
|
|
450
|
+
return WorkerCard(component_data).build()
|
|
451
|
+
{%- endif %}
|
|
452
|
+
{%- if include_redis %}
|
|
453
|
+
elif component_name == "cache":
|
|
454
|
+
return RedisCard(component_data).build()
|
|
455
|
+
{%- endif %}
|
|
456
|
+
{%- if include_database %}
|
|
457
|
+
elif component_name == "database":
|
|
458
|
+
return DatabaseCard(component_data).build()
|
|
459
|
+
{%- endif %}
|
|
460
|
+
{%- if include_scheduler %}
|
|
461
|
+
elif component_name == "scheduler":
|
|
462
|
+
return SchedulerCard(component_data).build()
|
|
463
|
+
{%- endif %}
|
|
464
|
+
{%- if include_ai or include_auth %}
|
|
465
|
+
{%- if include_auth %}
|
|
466
|
+
elif component_name == "services":
|
|
467
|
+
return ServicesCard(component_data).build()
|
|
468
|
+
{%- endif %}
|
|
469
|
+
# Individual service cards
|
|
470
|
+
{%- if include_ai %}
|
|
471
|
+
elif component_name == "service_ai":
|
|
472
|
+
return AICard(component_data).build()
|
|
473
|
+
{%- endif %}
|
|
474
|
+
{%- if include_auth %}
|
|
475
|
+
elif component_name == "service_auth":
|
|
476
|
+
return AuthCard(component_data).build()
|
|
477
|
+
elif component_name.startswith("service_"):
|
|
478
|
+
# For other services, use generic ServicesCard for now
|
|
479
|
+
return ServicesCard(component_data).build()
|
|
480
|
+
{%- endif %}
|
|
481
|
+
{%- endif %}
|
|
482
|
+
else:
|
|
483
|
+
# Fallback for unknown components - should not happen in practice
|
|
484
|
+
logger.warning(f"Unknown component type: {component_name}")
|
|
485
|
+
return ft.Container(
|
|
486
|
+
content=ft.Text(f"Unknown component: {component_name}"),
|
|
487
|
+
padding=20,
|
|
488
|
+
bgcolor=ft.Colors.SURFACE,
|
|
489
|
+
border=ft.border.all(1, ft.Colors.OUTLINE_VARIANT),
|
|
490
|
+
border_radius=16,
|
|
491
|
+
width=800,
|
|
492
|
+
height=240,
|
|
493
|
+
)
|
|
494
|
+
except Exception as e:
|
|
495
|
+
logger.error(
|
|
496
|
+
f"Failed to create component card for {component_name}: {e}",
|
|
497
|
+
exc_info=True,
|
|
498
|
+
extra={
|
|
499
|
+
"error_type": type(e).__name__,
|
|
500
|
+
"function": "create_component_card",
|
|
501
|
+
"component_name": component_name,
|
|
502
|
+
"component_data": component_data
|
|
503
|
+
}
|
|
504
|
+
)
|
|
505
|
+
# Return fallback card on error
|
|
506
|
+
return ft.Container(
|
|
507
|
+
content=ft.Text(f"Error loading {component_name}"),
|
|
508
|
+
padding=20,
|
|
509
|
+
bgcolor=ft.Colors.ERROR_CONTAINER,
|
|
510
|
+
border=ft.border.all(1, ft.Colors.ERROR),
|
|
511
|
+
border_radius=16,
|
|
512
|
+
width=800,
|
|
513
|
+
height=240,
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
async def refresh_dashboard() -> None:
|
|
517
|
+
"""Refresh the stunning marketing-grade dashboard."""
|
|
518
|
+
try:
|
|
519
|
+
# Use the single /health/ endpoint for all health data
|
|
520
|
+
import httpx
|
|
521
|
+
|
|
522
|
+
async with httpx.AsyncClient() as client:
|
|
523
|
+
response = await client.get("http://localhost:8000/health/")
|
|
524
|
+
data = response.json()
|
|
525
|
+
|
|
526
|
+
# Extract components from health API response (navigate structure)
|
|
527
|
+
if "components" in data and "aegis" in data["components"]:
|
|
528
|
+
aegis_component = data["components"]["aegis"]
|
|
529
|
+
if "sub_components" in aegis_component:
|
|
530
|
+
api_components = aegis_component["sub_components"]
|
|
531
|
+
else:
|
|
532
|
+
api_components = {}
|
|
533
|
+
else:
|
|
534
|
+
api_components = {}
|
|
535
|
+
|
|
536
|
+
# Convert API data back to ComponentStatus objects for the cards
|
|
537
|
+
def convert_component(comp_data: dict[str, Any]) -> ComponentStatus:
|
|
538
|
+
"""Recursively convert API component data to ComponentStatus."""
|
|
539
|
+
try:
|
|
540
|
+
sub_components = {}
|
|
541
|
+
if "sub_components" in comp_data:
|
|
542
|
+
for sub_name, sub_data in comp_data[
|
|
543
|
+
"sub_components"
|
|
544
|
+
].items():
|
|
545
|
+
sub_components[sub_name] = convert_component(sub_data)
|
|
546
|
+
|
|
547
|
+
return ComponentStatus(
|
|
548
|
+
name=comp_data.get("name", "Unknown"),
|
|
549
|
+
status=ComponentStatusType.HEALTHY
|
|
550
|
+
if comp_data.get("healthy", False)
|
|
551
|
+
else ComponentStatusType.UNHEALTHY,
|
|
552
|
+
message=comp_data.get("message", "No message"),
|
|
553
|
+
response_time_ms=comp_data.get("response_time_ms"),
|
|
554
|
+
metadata=comp_data.get("metadata", {}),
|
|
555
|
+
sub_components=sub_components,
|
|
556
|
+
)
|
|
557
|
+
except Exception as e:
|
|
558
|
+
logger.error(
|
|
559
|
+
f"Failed to convert component data: {e}",
|
|
560
|
+
exc_info=True,
|
|
561
|
+
extra={
|
|
562
|
+
"error_type": type(e).__name__,
|
|
563
|
+
"function": "convert_component",
|
|
564
|
+
"comp_data": comp_data
|
|
565
|
+
}
|
|
566
|
+
)
|
|
567
|
+
# Return a fallback ComponentStatus
|
|
568
|
+
return ComponentStatus(
|
|
569
|
+
name=comp_data.get("name", "Unknown"),
|
|
570
|
+
status=ComponentStatusType.UNHEALTHY,
|
|
571
|
+
message=f"Error converting component: {e}",
|
|
572
|
+
response_time_ms=None,
|
|
573
|
+
metadata={},
|
|
574
|
+
sub_components={},
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
components = {}
|
|
578
|
+
for name, comp_data in api_components.items():
|
|
579
|
+
# Special handling for "components" grouping - expand it
|
|
580
|
+
if name == COMPONENTS_GROUP_KEY and "sub_components" in comp_data:
|
|
581
|
+
# Add all individual components from the grouping
|
|
582
|
+
for sub_name, sub_data in comp_data["sub_components"].items():
|
|
583
|
+
components[sub_name] = convert_component(sub_data)
|
|
584
|
+
# Special handling for "services" grouping - expand services
|
|
585
|
+
elif name == SERVICES_GROUP_KEY and "sub_components" in comp_data:
|
|
586
|
+
# Add all individual services from the grouping
|
|
587
|
+
for service_name, service_data in comp_data[
|
|
588
|
+
"sub_components"
|
|
589
|
+
].items():
|
|
590
|
+
components[
|
|
591
|
+
f"{SERVICE_PREFIX}{service_name}"
|
|
592
|
+
] = convert_component(service_data)
|
|
593
|
+
else:
|
|
594
|
+
# For other groupings, add as-is
|
|
595
|
+
components[name] = convert_component(comp_data)
|
|
596
|
+
|
|
597
|
+
total_components = len(components)
|
|
598
|
+
healthy_components = len([c for c in components.values() if c.healthy])
|
|
599
|
+
|
|
600
|
+
# Update health status and component cards using safe dashboard methods
|
|
601
|
+
await dashboard.update_health_status(
|
|
602
|
+
healthy_components, total_components
|
|
603
|
+
)
|
|
604
|
+
await dashboard.update_component_cards(
|
|
605
|
+
components, create_component_card
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
# Safe page update - check connection first
|
|
609
|
+
if dashboard._is_page_connected():
|
|
610
|
+
try:
|
|
611
|
+
page.update()
|
|
612
|
+
except PageDisconnectedException:
|
|
613
|
+
logger.debug("Page disconnected during page.update()")
|
|
614
|
+
raise
|
|
615
|
+
|
|
616
|
+
except PageDisconnectedException:
|
|
617
|
+
logger.debug("Page disconnected during dashboard refresh")
|
|
618
|
+
# Do NOT call show_error_status here: the page is already disconnected,
|
|
619
|
+
# so any attempt to update the UI (including showing an error) fails.
|
|
620
|
+
# Instead, propagate this exception so the auto_refresh loop can handle
|
|
621
|
+
# the disconnection gracefully and stop further updates.
|
|
622
|
+
raise
|
|
623
|
+
except Exception as e:
|
|
624
|
+
logger.error(
|
|
625
|
+
f"Dashboard refresh failed: {e}",
|
|
626
|
+
exc_info=True,
|
|
627
|
+
extra={
|
|
628
|
+
"error_type": type(e).__name__,
|
|
629
|
+
"function": "refresh_dashboard"
|
|
630
|
+
}
|
|
631
|
+
)
|
|
632
|
+
# Show error indicator using safe dashboard method
|
|
633
|
+
await dashboard.show_error_status()
|
|
634
|
+
|
|
635
|
+
async def auto_refresh() -> None:
|
|
636
|
+
"""Simple auto-refresh loop that stops when page disconnects."""
|
|
637
|
+
while dashboard._is_page_connected():
|
|
638
|
+
try:
|
|
639
|
+
await refresh_dashboard()
|
|
640
|
+
await asyncio.sleep(30)
|
|
641
|
+
except PageDisconnectedException:
|
|
642
|
+
logger.debug("Page disconnected, stopping auto-refresh loop")
|
|
643
|
+
break
|
|
644
|
+
except Exception as e:
|
|
645
|
+
logger.error(f"Error in auto-refresh loop: {e}", exc_info=True)
|
|
646
|
+
# Continue the loop even if refresh fails
|
|
647
|
+
await asyncio.sleep(30)
|
|
648
|
+
|
|
649
|
+
# Initial load and start refresh
|
|
650
|
+
await refresh_dashboard()
|
|
651
|
+
asyncio.create_task(auto_refresh())
|
|
652
|
+
|
|
653
|
+
return flet_main
|