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.
Files changed (392) hide show
  1. aegis/__init__.py +5 -0
  2. aegis/__main__.py +51 -0
  3. aegis/cli/__init__.py +6 -0
  4. aegis/cli/callbacks.py +114 -0
  5. aegis/cli/interactive.py +611 -0
  6. aegis/cli/utils.py +70 -0
  7. aegis/cli/validators.py +34 -0
  8. aegis/commands/__init__.py +6 -0
  9. aegis/commands/add.py +353 -0
  10. aegis/commands/add_service.py +332 -0
  11. aegis/commands/components.py +35 -0
  12. aegis/commands/init.py +370 -0
  13. aegis/commands/remove.py +227 -0
  14. aegis/commands/services.py +52 -0
  15. aegis/commands/update.py +252 -0
  16. aegis/commands/version.py +12 -0
  17. aegis/config/__init__.py +1 -0
  18. aegis/config/shared_files.py +136 -0
  19. aegis/core/CLAUDE.md +377 -0
  20. aegis/core/__init__.py +6 -0
  21. aegis/core/component_files.py +228 -0
  22. aegis/core/component_utils.py +220 -0
  23. aegis/core/components.py +127 -0
  24. aegis/core/copier_manager.py +315 -0
  25. aegis/core/copier_updater.py +475 -0
  26. aegis/core/dependency_resolver.py +119 -0
  27. aegis/core/manual_updater.py +554 -0
  28. aegis/core/post_gen_tasks.py +547 -0
  29. aegis/core/service_resolver.py +261 -0
  30. aegis/core/services.py +157 -0
  31. aegis/core/template_generator.py +266 -0
  32. aegis/core/version_compatibility.py +259 -0
  33. aegis/templates/CLAUDE.md +591 -0
  34. aegis/templates/cookiecutter-aegis-project/cookiecutter.json +39 -0
  35. aegis/templates/cookiecutter-aegis-project/hooks/post_gen_project.py +214 -0
  36. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.dockerignore +71 -0
  37. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.env.example.j2 +130 -0
  38. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.gitignore +131 -0
  39. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Dockerfile +53 -0
  40. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Makefile +236 -0
  41. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/README.md.j2 +196 -0
  42. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/alembic.ini.j2 +111 -0
  43. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/env.py.j2 +91 -0
  44. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/script.py.mako +25 -0
  45. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/versions/001_initial_auth.py.j2 +51 -0
  46. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/__init__.py +5 -0
  47. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/__init__.py +6 -0
  48. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/ai.py.j2 +700 -0
  49. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/ai_rendering.py +361 -0
  50. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/auth.py.j2 +253 -0
  51. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/health.py.j2 +419 -0
  52. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/load_test.py.j2 +656 -0
  53. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/main.py.j2 +65 -0
  54. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/marko_terminal_renderer.py +489 -0
  55. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/tasks.py.j2 +328 -0
  56. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/{% if cookiecutter.include_scheduler == /"yes/" %}tasks.py{% endif %}" +340 -0
  57. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/__init__.py +0 -0
  58. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/__init__.py +0 -0
  59. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/ai/__init__.py +8 -0
  60. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/ai/router.py +329 -0
  61. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/auth/__init__.py +1 -0
  62. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/auth/router.py +64 -0
  63. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/deps.py +58 -0
  64. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/health.py +163 -0
  65. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/models.py.j2 +280 -0
  66. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/routing.py.j2 +32 -0
  67. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/scheduler.py.j2 +121 -0
  68. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/worker.py.j2 +478 -0
  69. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/hooks.py +144 -0
  70. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/main.py +31 -0
  71. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/__init__.py +1 -0
  72. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/cors.py +20 -0
  73. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/__init__.py +1 -0
  74. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/cleanup.py +14 -0
  75. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/__init__.py +1 -0
  76. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/component_health.py.j2 +418 -0
  77. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/database_init.py.j2 +83 -0
  78. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/__init__.py +5 -0
  79. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/__init__.py +27 -0
  80. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/table.py +78 -0
  81. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/text.py +142 -0
  82. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/__init__.py.j2 +47 -0
  83. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/ai_card.py +287 -0
  84. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/auth_card.py +198 -0
  85. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/base_card.py +256 -0
  86. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/card_factory.py +227 -0
  87. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/card_utils.py +333 -0
  88. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/database_card.py +420 -0
  89. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/fastapi_card.py +328 -0
  90. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/flet_card.py +267 -0
  91. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/redis_card.py +322 -0
  92. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/scheduler_card.py +352 -0
  93. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/services_card.py +233 -0
  94. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/worker_card.py +684 -0
  95. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/main.py.j2 +653 -0
  96. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/theme.py +48 -0
  97. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/__init__.py +1 -0
  98. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/main.py.j2 +156 -0
  99. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/CLAUDE.md.j2 +213 -0
  100. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/__init__.py +6 -0
  101. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/constants.py.j2 +30 -0
  102. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/pools.py +97 -0
  103. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/__init__.py +1 -0
  104. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/load_test.py +55 -0
  105. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/media.py +49 -0
  106. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/system.py +44 -0
  107. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/registry.py +139 -0
  108. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/__init__.py +120 -0
  109. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/load_tasks.py +507 -0
  110. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/simple_system_tasks.py +33 -0
  111. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/system_tasks.py +281 -0
  112. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/config.py.j2 +178 -0
  113. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/constants.py +58 -0
  114. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/db.py.j2 +176 -0
  115. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/log.py +92 -0
  116. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/security.py +62 -0
  117. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/__init__.py +1 -0
  118. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/webserver.py +40 -0
  119. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/{% if cookiecutter.include_scheduler == /"yes/" %}scheduler.py{% endif %}" +21 -0
  120. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/__init__.py +0 -0
  121. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/main.py +62 -0
  122. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/models/__init__.py +1 -0
  123. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/models/user.py +44 -0
  124. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/py.typed +0 -0
  125. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/__init__.py +1 -0
  126. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/__init__.py +8 -0
  127. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/config.py +130 -0
  128. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/conversation.py +213 -0
  129. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/health.py +96 -0
  130. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/models.py +229 -0
  131. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/providers.py +370 -0
  132. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/service.py +388 -0
  133. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/__init__.py +1 -0
  134. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/auth_service.py +41 -0
  135. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/health.py +164 -0
  136. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/user_service.py +83 -0
  137. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/middleware_inspector.py.j2 +223 -0
  138. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/models.py.j2 +70 -0
  139. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/route_inspector.py.j2 +155 -0
  140. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test.py +679 -0
  141. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test_models.py +266 -0
  142. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/__init__.py.j2 +21 -0
  143. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/models.py.j2 +119 -0
  144. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/scheduled_task_manager.py.j2 +273 -0
  145. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/task_monitor.py.j2 +189 -0
  146. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/__init__.py +15 -0
  147. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/models.py +26 -0
  148. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/__init__.py +52 -0
  149. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/alerts.py +94 -0
  150. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/backup.py.j2 +119 -0
  151. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/health.py.j2 +1333 -0
  152. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/models.py +243 -0
  153. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/ui.py +52 -0
  154. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto-dark.png +0 -0
  155. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto-square-backup.png +0 -0
  156. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto.png +0 -0
  157. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.dockerignore +71 -0
  158. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.env.example.j2 +64 -0
  159. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.gitignore +131 -0
  160. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/Dockerfile +53 -0
  161. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/Makefile +211 -0
  162. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/README.md.j2 +172 -0
  163. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/docker-compose.yml.j2 +78 -0
  164. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/mkdocs.yml.j2 +62 -0
  165. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/pyproject.toml.j2 +120 -0
  166. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/uv.lock +1673 -0
  167. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docker-compose.yml.j2 +200 -0
  168. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/api.md +191 -0
  169. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/components/scheduler.md +0 -0
  170. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/components/scheduler.md.j2 +621 -0
  171. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/development.md +215 -0
  172. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/health.md +240 -0
  173. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/javascripts/mermaid-config.js +62 -0
  174. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/stylesheets/mermaid.css +95 -0
  175. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/mkdocs.yml.j2 +62 -0
  176. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/pyproject.toml.j2 +131 -0
  177. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh +87 -0
  178. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh.j2 +93 -0
  179. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/gen_docs.py +16 -0
  180. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/__init__.py +1 -0
  181. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_auth_endpoints.py.j2 +307 -0
  182. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_health_endpoints.py.j2 +262 -0
  183. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_scheduler_endpoints.py.j2 +214 -0
  184. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_worker_endpoints.py.j2 +165 -0
  185. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/cli/test_ai_rendering.py +427 -0
  186. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/cli/test_conversation_memory.py +465 -0
  187. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/components/test_scheduler.py +43 -0
  188. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/conftest.py.j2 +195 -0
  189. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/__init__.py +1 -0
  190. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/__init__.py +1 -0
  191. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/conftest.py +78 -0
  192. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_health.py +157 -0
  193. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_models.py +164 -0
  194. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_service.py +198 -0
  195. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_auth_integration.py.j2 +528 -0
  196. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_component_integration.py.j2 +387 -0
  197. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_conversation_persistence.py +342 -0
  198. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_health_logic.py.j2 +663 -0
  199. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_models.py +619 -0
  200. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_service.py +603 -0
  201. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_middleware_inspector.py.j2 +248 -0
  202. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_scheduled_task_manager.py.j2 +292 -0
  203. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_system_service.py +98 -0
  204. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_worker_health_registration.py.j2 +257 -0
  205. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/test_core.py +49 -0
  206. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/uv.lock +1673 -0
  207. aegis/templates/copier-aegis-project/{{ project_slug }}/.copier-answers.yml.jinja +21 -0
  208. aegis/templates/copier-aegis-project/{{ project_slug }}/.dockerignore +71 -0
  209. aegis/templates/copier-aegis-project/{{ project_slug }}/.env.example.jinja +130 -0
  210. aegis/templates/copier-aegis-project/{{ project_slug }}/.gitignore +131 -0
  211. aegis/templates/copier-aegis-project/{{ project_slug }}/Dockerfile +53 -0
  212. aegis/templates/copier-aegis-project/{{ project_slug }}/Makefile.jinja +236 -0
  213. aegis/templates/copier-aegis-project/{{ project_slug }}/README.md.jinja +196 -0
  214. aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/alembic.ini.jinja +111 -0
  215. aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/env.py.jinja +91 -0
  216. aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/script.py.mako +25 -0
  217. aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/versions/001_initial_auth.py.jinja +51 -0
  218. aegis/templates/copier-aegis-project/{{ project_slug }}/app/__init__.py.jinja +5 -0
  219. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/__init__.py.jinja +6 -0
  220. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/ai.py.jinja +700 -0
  221. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/ai_rendering.py +360 -0
  222. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/auth.py.jinja +253 -0
  223. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/health.py.jinja +419 -0
  224. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/load_test.py.jinja +656 -0
  225. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/main.py.jinja +65 -0
  226. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/marko_terminal_renderer.py +489 -0
  227. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/tasks.py.jinja +328 -0
  228. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/__init__.py +0 -0
  229. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/__init__.py +0 -0
  230. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/ai/__init__.py +8 -0
  231. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/ai/router.py +329 -0
  232. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/auth/__init__.py +1 -0
  233. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/auth/router.py +64 -0
  234. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/deps.py +58 -0
  235. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/health.py.jinja +163 -0
  236. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/models.py.jinja +280 -0
  237. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/routing.py.jinja +32 -0
  238. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/scheduler.py.jinja +121 -0
  239. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/worker.py.jinja +478 -0
  240. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/hooks.py +144 -0
  241. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/main.py +31 -0
  242. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/middleware/__init__.py +1 -0
  243. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/middleware/cors.py +20 -0
  244. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/shutdown/__init__.py +1 -0
  245. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/shutdown/cleanup.py +14 -0
  246. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/__init__.py +1 -0
  247. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/component_health.py.jinja +418 -0
  248. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/database_init.py.jinja +83 -0
  249. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/__init__.py +5 -0
  250. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/__init__.py +27 -0
  251. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/table.py +78 -0
  252. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/text.py +142 -0
  253. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/__init__.py.jinja +47 -0
  254. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/ai_card.py +287 -0
  255. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/auth_card.py +198 -0
  256. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/base_card.py +256 -0
  257. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/card_factory.py +227 -0
  258. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/card_utils.py +333 -0
  259. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/database_card.py +420 -0
  260. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/fastapi_card.py +328 -0
  261. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/flet_card.py +267 -0
  262. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/redis_card.py +322 -0
  263. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/scheduler_card.py +352 -0
  264. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/services_card.py +233 -0
  265. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/worker_card.py +684 -0
  266. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/main.py.jinja +653 -0
  267. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/theme.py +48 -0
  268. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/scheduler/__init__.py +1 -0
  269. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/scheduler/main.py.jinja +156 -0
  270. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/CLAUDE.md.jinja +213 -0
  271. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/__init__.py +6 -0
  272. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/constants.py.jinja +30 -0
  273. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/pools.py +97 -0
  274. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/__init__.py +1 -0
  275. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/load_test.py +55 -0
  276. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/media.py +49 -0
  277. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/system.py +44 -0
  278. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/registry.py +139 -0
  279. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/__init__.py +120 -0
  280. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/load_tasks.py +507 -0
  281. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/simple_system_tasks.py +33 -0
  282. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/system_tasks.py +281 -0
  283. aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/config.py.jinja +178 -0
  284. aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/constants.py +58 -0
  285. aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/db.py.jinja +176 -0
  286. aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/log.py +92 -0
  287. aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/security.py +62 -0
  288. aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/__init__.py +1 -0
  289. aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/scheduler.py.jinja +21 -0
  290. aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/webserver.py +39 -0
  291. aegis/templates/copier-aegis-project/{{ project_slug }}/app/integrations/__init__.py +0 -0
  292. aegis/templates/copier-aegis-project/{{ project_slug }}/app/integrations/main.py +61 -0
  293. aegis/templates/copier-aegis-project/{{ project_slug }}/app/models/__init__.py +1 -0
  294. aegis/templates/copier-aegis-project/{{ project_slug }}/app/models/user.py +44 -0
  295. aegis/templates/copier-aegis-project/{{ project_slug }}/app/py.typed +0 -0
  296. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/__init__.py +1 -0
  297. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/__init__.py +8 -0
  298. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/config.py +130 -0
  299. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/conversation.py +213 -0
  300. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/health.py +96 -0
  301. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/models.py +229 -0
  302. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/providers.py.jinja +370 -0
  303. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/service.py +387 -0
  304. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/__init__.py +1 -0
  305. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/auth_service.py +40 -0
  306. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/health.py +162 -0
  307. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/user_service.py +82 -0
  308. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/middleware_inspector.py.jinja +223 -0
  309. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/models.py.jinja +70 -0
  310. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/route_inspector.py.jinja +155 -0
  311. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/load_test.py +678 -0
  312. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/load_test_models.py +265 -0
  313. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/__init__.py.jinja +21 -0
  314. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/models.py.jinja +119 -0
  315. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/scheduled_task_manager.py.jinja +273 -0
  316. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/task_monitor.py.jinja +189 -0
  317. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/shared/__init__.py +15 -0
  318. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/shared/models.py +26 -0
  319. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/__init__.py +52 -0
  320. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/alerts.py +94 -0
  321. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/backup.py.jinja +119 -0
  322. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/health.py.jinja +1333 -0
  323. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/models.py +243 -0
  324. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/ui.py +52 -0
  325. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57223!aegis-manifesto.png +0 -0
  326. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57224!aegis-manifesto-dark.png +0 -0
  327. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57225!aegis-manifesto-square-backup.png +0 -0
  328. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57533!aegis-manifesto.png +0 -0
  329. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57534!aegis-manifesto-dark.png +0 -0
  330. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57538!aegis-manifesto-square-backup.png +0 -0
  331. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57897!aegis-manifesto.png +0 -0
  332. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57898!aegis-manifesto-dark.png +0 -0
  333. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57904!aegis-manifesto-square-backup.png +0 -0
  334. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58315!aegis-manifesto.png +0 -0
  335. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58316!aegis-manifesto-dark.png +0 -0
  336. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58324!aegis-manifesto-square-backup.png +0 -0
  337. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58837!aegis-manifesto.png +0 -0
  338. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58838!aegis-manifesto-dark.png +0 -0
  339. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58849!aegis-manifesto-square-backup.png +0 -0
  340. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto-dark.png +0 -0
  341. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto-square-backup.png +0 -0
  342. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto.png +0 -0
  343. aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/.env.example.jinja +64 -0
  344. aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/README.md.jinja +172 -0
  345. aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/docker-compose.yml.jinja +78 -0
  346. aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/mkdocs.yml.jinja +62 -0
  347. aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/pyproject.toml.jinja +120 -0
  348. aegis/templates/copier-aegis-project/{{ project_slug }}/docker-compose.yml.jinja +200 -0
  349. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/api.md.jinja +191 -0
  350. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/components/scheduler.md +0 -0
  351. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/components/scheduler.md.jinja +621 -0
  352. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/development.md.jinja +215 -0
  353. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/health.md.jinja +240 -0
  354. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/javascripts/mermaid-config.js +62 -0
  355. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/stylesheets/mermaid.css +95 -0
  356. aegis/templates/copier-aegis-project/{{ project_slug }}/mkdocs.yml.jinja +62 -0
  357. aegis/templates/copier-aegis-project/{{ project_slug }}/pyproject.toml.jinja +131 -0
  358. aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/entrypoint.sh +87 -0
  359. aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/entrypoint.sh.jinja +93 -0
  360. aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/gen_docs.py +16 -0
  361. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/__init__.py +1 -0
  362. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_auth_endpoints.py.jinja +307 -0
  363. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_health_endpoints.py.jinja +262 -0
  364. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_scheduler_endpoints.py.jinja +214 -0
  365. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_worker_endpoints.py.jinja +165 -0
  366. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/cli/test_ai_rendering.py +427 -0
  367. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/cli/test_conversation_memory.py +465 -0
  368. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/components/test_scheduler.py +43 -0
  369. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/conftest.py.jinja +195 -0
  370. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/__init__.py +1 -0
  371. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/__init__.py +1 -0
  372. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/conftest.py +78 -0
  373. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_health.py +157 -0
  374. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_models.py +164 -0
  375. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_service.py +198 -0
  376. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_auth_integration.py.jinja +528 -0
  377. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_component_integration.py.jinja +387 -0
  378. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_conversation_persistence.py +342 -0
  379. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_health_logic.py.jinja +663 -0
  380. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_load_test_models.py +619 -0
  381. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_load_test_service.py +603 -0
  382. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_middleware_inspector.py.jinja +248 -0
  383. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_scheduled_task_manager.py.jinja +292 -0
  384. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_system_service.py +98 -0
  385. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_worker_health_registration.py.jinja +257 -0
  386. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/test_core.py +49 -0
  387. aegis/templates/copier-aegis-project/{{ project_slug }}/uv.lock +1673 -0
  388. aegis_stack-0.2.0rc2.dist-info/METADATA +165 -0
  389. aegis_stack-0.2.0rc2.dist-info/RECORD +392 -0
  390. aegis_stack-0.2.0rc2.dist-info/WHEEL +4 -0
  391. aegis_stack-0.2.0rc2.dist-info/entry_points.txt +3 -0
  392. 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 cookiecutter.include_ai == "yes" %}
16
+ AICard,
17
+ {%- endif %}
18
+ {%- if cookiecutter.include_auth == "yes" %}
19
+ AuthCard,
20
+ ServicesCard,
21
+ {%- endif %}
22
+ {%- if cookiecutter.include_database == "yes" %}
23
+ DatabaseCard,
24
+ {%- endif %}
25
+ {%- if cookiecutter.include_redis == "yes" %}
26
+ RedisCard,
27
+ {%- endif %}
28
+ {%- if cookiecutter.include_scheduler == "yes" %}
29
+ SchedulerCard,
30
+ {%- endif %}
31
+ {%- if cookiecutter.include_worker == "yes" %}
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 cookiecutter.include_worker == "yes" %}
449
+ elif component_name == "worker":
450
+ return WorkerCard(component_data).build()
451
+ {%- endif %}
452
+ {%- if cookiecutter.include_redis == "yes" %}
453
+ elif component_name == "cache":
454
+ return RedisCard(component_data).build()
455
+ {%- endif %}
456
+ {%- if cookiecutter.include_database == "yes" %}
457
+ elif component_name == "database":
458
+ return DatabaseCard(component_data).build()
459
+ {%- endif %}
460
+ {%- if cookiecutter.include_scheduler == "yes" %}
461
+ elif component_name == "scheduler":
462
+ return SchedulerCard(component_data).build()
463
+ {%- endif %}
464
+ {%- if cookiecutter.include_ai == "yes" or cookiecutter.include_auth == "yes" %}
465
+ {%- if cookiecutter.include_auth == "yes" %}
466
+ elif component_name == "services":
467
+ return ServicesCard(component_data).build()
468
+ {%- endif %}
469
+ # Individual service cards
470
+ {%- if cookiecutter.include_ai == "yes" %}
471
+ elif component_name == "service_ai":
472
+ return AICard(component_data).build()
473
+ {%- endif %}
474
+ {%- if cookiecutter.include_auth == "yes" %}
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