quantum-framework 0.18.0__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 (333) hide show
  1. quantum/__init__.py +1 -0
  2. quantum/cli/__init__.py +2 -0
  3. quantum/cli/admin.py +131 -0
  4. quantum/cli/check.py +223 -0
  5. quantum/cli/commands/__init__.py +15 -0
  6. quantum/cli/commands/build.py +414 -0
  7. quantum/cli/commands/dev.py +185 -0
  8. quantum/cli/commands/docs.py +329 -0
  9. quantum/cli/commands/lint.py +523 -0
  10. quantum/cli/commands/migrate.py +548 -0
  11. quantum/cli/commands/new.py +622 -0
  12. quantum/cli/commands/serve.py +190 -0
  13. quantum/cli/commands/test.py +193 -0
  14. quantum/cli/deploy.py +810 -0
  15. quantum/cli/hot_reload.py +951 -0
  16. quantum/cli/jobs.py +356 -0
  17. quantum/cli/mq.py +582 -0
  18. quantum/cli/pkg.py +390 -0
  19. quantum/cli/runner.py +565 -0
  20. quantum/cli/server_process.py +159 -0
  21. quantum/cli/utils.py +334 -0
  22. quantum/compiler/__init__.py +30 -0
  23. quantum/compiler/base_generator.py +367 -0
  24. quantum/compiler/cli.py +295 -0
  25. quantum/compiler/expression_transformer.py +444 -0
  26. quantum/compiler/javascript/__init__.py +10 -0
  27. quantum/compiler/javascript/generator.py +659 -0
  28. quantum/compiler/optimizer.py +270 -0
  29. quantum/compiler/python/__init__.py +10 -0
  30. quantum/compiler/python/generator.py +883 -0
  31. quantum/compiler/python/runtime.py +863 -0
  32. quantum/compiler/transpiler.py +330 -0
  33. quantum/core/__init__.py +3 -0
  34. quantum/core/ast_nodes.py +2619 -0
  35. quantum/core/config_env.py +54 -0
  36. quantum/core/docs_links.py +8 -0
  37. quantum/core/expression_diagnostics.py +87 -0
  38. quantum/core/expression_stdlib.py +208 -0
  39. quantum/core/expressions.py +677 -0
  40. quantum/core/features/agents/src/__init__.py +30 -0
  41. quantum/core/features/agents/src/ast_node.py +540 -0
  42. quantum/core/features/conditionals/src/__init__.py +8 -0
  43. quantum/core/features/conditionals/src/ast_node.py +68 -0
  44. quantum/core/features/data_import/src/__init__.py +0 -0
  45. quantum/core/features/data_import/src/ast_node.py +292 -0
  46. quantum/core/features/data_import/src/runtime.py +533 -0
  47. quantum/core/features/dump/src/__init__.py +12 -0
  48. quantum/core/features/dump/src/ast_node.py +106 -0
  49. quantum/core/features/dump/src/parser.py +61 -0
  50. quantum/core/features/dump/src/runtime.py +246 -0
  51. quantum/core/features/functions/src/__init__.py +8 -0
  52. quantum/core/features/functions/src/ast_node.py +149 -0
  53. quantum/core/features/game_engine_2d/src/__init__.py +19 -0
  54. quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
  55. quantum/core/features/game_engine_2d/src/parser.py +983 -0
  56. quantum/core/features/invocation/src/__init__.py +0 -0
  57. quantum/core/features/invocation/src/ast_node.py +147 -0
  58. quantum/core/features/invocation/src/runtime.py +332 -0
  59. quantum/core/features/knowledge_base/src/__init__.py +6 -0
  60. quantum/core/features/knowledge_base/src/ast_node.py +113 -0
  61. quantum/core/features/knowledge_base/src/parser.py +82 -0
  62. quantum/core/features/logging/src/__init__.py +12 -0
  63. quantum/core/features/logging/src/ast_node.py +111 -0
  64. quantum/core/features/logging/src/parser.py +50 -0
  65. quantum/core/features/logging/src/runtime.py +190 -0
  66. quantum/core/features/loops/src/__init__.py +8 -0
  67. quantum/core/features/loops/src/ast_node.py +60 -0
  68. quantum/core/features/query/src/__init__.py +0 -0
  69. quantum/core/features/query/src/query_validators.py +20 -0
  70. quantum/core/features/state_management/src/__init__.py +11 -0
  71. quantum/core/features/state_management/src/ast_node.py +121 -0
  72. quantum/core/features/terminal_engine/src/__init__.py +21 -0
  73. quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
  74. quantum/core/features/terminal_engine/src/parser.py +361 -0
  75. quantum/core/features/testing_engine/src/__init__.py +41 -0
  76. quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
  77. quantum/core/features/testing_engine/src/parser.py +604 -0
  78. quantum/core/features/theming/src/__init__.py +48 -0
  79. quantum/core/features/theming/src/ast_node.py +137 -0
  80. quantum/core/features/theming/src/presets.py +405 -0
  81. quantum/core/features/ui_engine/src/__init__.py +20 -0
  82. quantum/core/features/ui_engine/src/ast_nodes.py +1913 -0
  83. quantum/core/features/ui_engine/src/parser.py +1192 -0
  84. quantum/core/features/websocket/src/__init__.py +24 -0
  85. quantum/core/features/websocket/src/ast_node.py +247 -0
  86. quantum/core/guards.py +53 -0
  87. quantum/core/html_compat.py +299 -0
  88. quantum/core/parser.py +1551 -0
  89. quantum/core/parser_registry.py +213 -0
  90. quantum/core/parsers/__init__.py +76 -0
  91. quantum/core/parsers/ai/__init__.py +12 -0
  92. quantum/core/parsers/ai/agent_parser.py +106 -0
  93. quantum/core/parsers/ai/knowledge_parser.py +86 -0
  94. quantum/core/parsers/ai/llm_parser.py +78 -0
  95. quantum/core/parsers/ai/team_parser.py +88 -0
  96. quantum/core/parsers/base.py +322 -0
  97. quantum/core/parsers/composition/__init__.py +10 -0
  98. quantum/core/parsers/composition/import_parser.py +50 -0
  99. quantum/core/parsers/composition/slot_parser.py +48 -0
  100. quantum/core/parsers/control_flow/__init__.py +11 -0
  101. quantum/core/parsers/control_flow/if_parser.py +71 -0
  102. quantum/core/parsers/control_flow/loop_parser.py +105 -0
  103. quantum/core/parsers/control_flow/set_parser.py +93 -0
  104. quantum/core/parsers/data/__init__.py +12 -0
  105. quantum/core/parsers/data/data_parser.py +229 -0
  106. quantum/core/parsers/data/invoke_parser.py +133 -0
  107. quantum/core/parsers/data/query_parser.py +213 -0
  108. quantum/core/parsers/data/transaction_parser.py +86 -0
  109. quantum/core/parsers/events/__init__.py +9 -0
  110. quantum/core/parsers/events/dispatch_event_parser.py +44 -0
  111. quantum/core/parsers/forms/__init__.py +11 -0
  112. quantum/core/parsers/forms/action_parser.py +79 -0
  113. quantum/core/parsers/forms/flash_parser.py +34 -0
  114. quantum/core/parsers/forms/redirect_parser.py +33 -0
  115. quantum/core/parsers/functions/__init__.py +11 -0
  116. quantum/core/parsers/functions/function_parser.py +98 -0
  117. quantum/core/parsers/functions/param_parser.py +73 -0
  118. quantum/core/parsers/functions/return_parser.py +31 -0
  119. quantum/core/parsers/html/__init__.py +10 -0
  120. quantum/core/parsers/html/component_call_parser.py +130 -0
  121. quantum/core/parsers/html/html_parser.py +115 -0
  122. quantum/core/parsers/jobs/__init__.py +11 -0
  123. quantum/core/parsers/jobs/job_parser.py +71 -0
  124. quantum/core/parsers/jobs/schedule_parser.py +62 -0
  125. quantum/core/parsers/jobs/thread_parser.py +57 -0
  126. quantum/core/parsers/messaging/__init__.py +17 -0
  127. quantum/core/parsers/messaging/message_ack_parser.py +30 -0
  128. quantum/core/parsers/messaging/message_nack_parser.py +30 -0
  129. quantum/core/parsers/messaging/message_parser.py +114 -0
  130. quantum/core/parsers/messaging/queue_parser.py +61 -0
  131. quantum/core/parsers/messaging/websocket_parser.py +121 -0
  132. quantum/core/parsers/routing/__init__.py +9 -0
  133. quantum/core/parsers/routing/route_parser.py +41 -0
  134. quantum/core/parsers/scripting/__init__.py +12 -0
  135. quantum/core/parsers/scripting/pyclass_parser.py +62 -0
  136. quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
  137. quantum/core/parsers/scripting/pyimport_parser.py +52 -0
  138. quantum/core/parsers/scripting/python_parser.py +49 -0
  139. quantum/core/parsers/services/__init__.py +12 -0
  140. quantum/core/parsers/services/dump_parser.py +52 -0
  141. quantum/core/parsers/services/file_parser.py +48 -0
  142. quantum/core/parsers/services/log_parser.py +46 -0
  143. quantum/core/parsers/services/mail_parser.py +65 -0
  144. quantum/core/tiers.py +164 -0
  145. quantum/core/xml_lines.py +81 -0
  146. quantum/packages/__init__.py +28 -0
  147. quantum/packages/manager.py +413 -0
  148. quantum/packages/manifest.py +351 -0
  149. quantum/packages/registry.py +399 -0
  150. quantum/packages/resolver.py +336 -0
  151. quantum/plugins/__init__.py +33 -0
  152. quantum/plugins/hooks.py +329 -0
  153. quantum/plugins/loader.py +479 -0
  154. quantum/plugins/manifest.py +336 -0
  155. quantum/plugins/registry.py +371 -0
  156. quantum/runtime/__init__.py +25 -0
  157. quantum/runtime/action_handler.py +532 -0
  158. quantum/runtime/adapters/__init__.py +88 -0
  159. quantum/runtime/adapters/memory_adapter.py +690 -0
  160. quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
  161. quantum/runtime/adapters/redis_adapter.py +582 -0
  162. quantum/runtime/adapters/sqlite_adapter.py +414 -0
  163. quantum/runtime/agent_service.py +1133 -0
  164. quantum/runtime/ast_cache.py +506 -0
  165. quantum/runtime/auth_service.py +267 -0
  166. quantum/runtime/component.py +1048 -0
  167. quantum/runtime/component_composer.py +133 -0
  168. quantum/runtime/component_resolver.py +174 -0
  169. quantum/runtime/conditions.py +54 -0
  170. quantum/runtime/database_service.py +595 -0
  171. quantum/runtime/db_schema.py +100 -0
  172. quantum/runtime/dev_panel.py +178 -0
  173. quantum/runtime/email_service.py +162 -0
  174. quantum/runtime/error_handler.py +295 -0
  175. quantum/runtime/execution_context.py +283 -0
  176. quantum/runtime/executor_registry.py +178 -0
  177. quantum/runtime/executors/__init__.py +71 -0
  178. quantum/runtime/executors/ai/__init__.py +12 -0
  179. quantum/runtime/executors/ai/agent_executor.py +217 -0
  180. quantum/runtime/executors/ai/knowledge_executor.py +114 -0
  181. quantum/runtime/executors/ai/llm_executor.py +153 -0
  182. quantum/runtime/executors/ai/team_executor.py +171 -0
  183. quantum/runtime/executors/base.py +264 -0
  184. quantum/runtime/executors/control_flow/__init__.py +12 -0
  185. quantum/runtime/executors/control_flow/if_executor.py +98 -0
  186. quantum/runtime/executors/control_flow/loop_executor.py +308 -0
  187. quantum/runtime/executors/control_flow/redirect_executor.py +43 -0
  188. quantum/runtime/executors/control_flow/set_executor.py +464 -0
  189. quantum/runtime/executors/data/__init__.py +12 -0
  190. quantum/runtime/executors/data/data_executor.py +156 -0
  191. quantum/runtime/executors/data/invoke_executor.py +228 -0
  192. quantum/runtime/executors/data/query_executor.py +290 -0
  193. quantum/runtime/executors/data/transaction_executor.py +91 -0
  194. quantum/runtime/executors/jobs/__init__.py +11 -0
  195. quantum/runtime/executors/jobs/job_executor.py +190 -0
  196. quantum/runtime/executors/jobs/schedule_executor.py +132 -0
  197. quantum/runtime/executors/jobs/thread_executor.py +127 -0
  198. quantum/runtime/executors/messaging/__init__.py +17 -0
  199. quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
  200. quantum/runtime/executors/messaging/message_executor.py +174 -0
  201. quantum/runtime/executors/messaging/queue_executor.py +103 -0
  202. quantum/runtime/executors/messaging/websocket_executor.py +197 -0
  203. quantum/runtime/executors/scripting/__init__.py +11 -0
  204. quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
  205. quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
  206. quantum/runtime/executors/scripting/python_executor.py +249 -0
  207. quantum/runtime/executors/services/__init__.py +12 -0
  208. quantum/runtime/executors/services/dump_executor.py +72 -0
  209. quantum/runtime/executors/services/file_executor.py +89 -0
  210. quantum/runtime/executors/services/log_executor.py +77 -0
  211. quantum/runtime/executors/services/mail_executor.py +81 -0
  212. quantum/runtime/expression_cache.py +498 -0
  213. quantum/runtime/file_upload_service.py +326 -0
  214. quantum/runtime/function_registry.py +118 -0
  215. quantum/runtime/game_builder.py +166 -0
  216. quantum/runtime/game_code_generator.py +2371 -0
  217. quantum/runtime/game_templates.py +2006 -0
  218. quantum/runtime/godot_code_generator.py +4681 -0
  219. quantum/runtime/godot_templates.py +1449 -0
  220. quantum/runtime/job_executor.py +1599 -0
  221. quantum/runtime/knowledge_service.py +546 -0
  222. quantum/runtime/llm_cache.py +100 -0
  223. quantum/runtime/llm_providers.py +704 -0
  224. quantum/runtime/llm_service.py +287 -0
  225. quantum/runtime/local_server.py +28 -0
  226. quantum/runtime/logging_setup.py +141 -0
  227. quantum/runtime/message_broker.py +364 -0
  228. quantum/runtime/message_queue_service.py +571 -0
  229. quantum/runtime/param_validation.py +218 -0
  230. quantum/runtime/pypy_compat.py +317 -0
  231. quantum/runtime/python_bridge.py +698 -0
  232. quantum/runtime/query_validators.py +304 -0
  233. quantum/runtime/renderer.py +734 -0
  234. quantum/runtime/service_container.py +470 -0
  235. quantum/runtime/table_params.py +163 -0
  236. quantum/runtime/terminal_builder.py +76 -0
  237. quantum/runtime/terminal_code_generator.py +607 -0
  238. quantum/runtime/terminal_templates.py +243 -0
  239. quantum/runtime/testing_builder.py +77 -0
  240. quantum/runtime/testing_code_generator.py +833 -0
  241. quantum/runtime/testing_templates.py +85 -0
  242. quantum/runtime/ui_builder.py +222 -0
  243. quantum/runtime/ui_console.py +405 -0
  244. quantum/runtime/ui_desktop.py +56 -0
  245. quantum/runtime/ui_form_rules.py +193 -0
  246. quantum/runtime/ui_html_adapter.py +2567 -0
  247. quantum/runtime/ui_html_templates.py +2106 -0
  248. quantum/runtime/ui_mobile_adapter.py +1832 -0
  249. quantum/runtime/ui_mobile_templates.py +1003 -0
  250. quantum/runtime/ui_page_renderer.py +392 -0
  251. quantum/runtime/ui_pager.py +73 -0
  252. quantum/runtime/ui_table_edit.py +139 -0
  253. quantum/runtime/ui_textual_adapter.py +1866 -0
  254. quantum/runtime/ui_textual_templates.py +45 -0
  255. quantum/runtime/ui_tokens.py +465 -0
  256. quantum/runtime/ui_validator.py +365 -0
  257. quantum/runtime/ui_view.py +221 -0
  258. quantum/runtime/validators.py +256 -0
  259. quantum/runtime/web_server.py +2131 -0
  260. quantum/runtime/websocket_adapter.py +501 -0
  261. quantum/runtime/websocket_service.py +585 -0
  262. quantum/runtime/websocket_transport.py +289 -0
  263. quantum/runtime/wsgi.py +101 -0
  264. quantum/services.py +109 -0
  265. quantum/utils/__init__.py +1 -0
  266. quantum_admin/__init__.py +4 -0
  267. quantum_admin/assets/quantum-admin-connectors.js +123 -0
  268. quantum_admin/assets/quantum-admin.css +2058 -0
  269. quantum_admin/assets/quantum-source-highlight.js +106 -0
  270. quantum_admin/backend/__init__.py +4 -0
  271. quantum_admin/backend/audit_service.py +346 -0
  272. quantum_admin/backend/auth_service.py +394 -0
  273. quantum_admin/backend/component_discovery.py +766 -0
  274. quantum_admin/backend/config_generator.py +586 -0
  275. quantum_admin/backend/connector_service.py +1016 -0
  276. quantum_admin/backend/crud.py +557 -0
  277. quantum_admin/backend/database.py +226 -0
  278. quantum_admin/backend/db_setup_service.py +193 -0
  279. quantum_admin/backend/deploy_service.py +698 -0
  280. quantum_admin/backend/docker_service.py +422 -0
  281. quantum_admin/backend/environment_service.py +418 -0
  282. quantum_admin/backend/git_service.py +370 -0
  283. quantum_admin/backend/health_service.py +390 -0
  284. quantum_admin/backend/job_service.py +347 -0
  285. quantum_admin/backend/log_service.py +331 -0
  286. quantum_admin/backend/main.py +14426 -0
  287. quantum_admin/backend/models.py +986 -0
  288. quantum_admin/backend/pipeline_service.py +812 -0
  289. quantum_admin/backend/resource_manager.py +1118 -0
  290. quantum_admin/backend/schemas.py +419 -0
  291. quantum_admin/backend/secret_manager.py +277 -0
  292. quantum_admin/backend/settings_service.py +488 -0
  293. quantum_admin/backend/template_service.py +710 -0
  294. quantum_admin/backend/test_execution_service.py +364 -0
  295. quantum_admin/backend/test_generator.py +782 -0
  296. quantum_admin/backend/webhook_service.py +454 -0
  297. quantum_admin/backend/websocket_manager.py +360 -0
  298. quantum_admin/components/admin/_layout/AdminShell.q +121 -0
  299. quantum_admin/components/admin/agents.q +107 -0
  300. quantum_admin/components/admin/app/[name].q +558 -0
  301. quantum_admin/components/admin/applications.q +160 -0
  302. quantum_admin/components/admin/component/[...path].q +205 -0
  303. quantum_admin/components/admin/components.q +59 -0
  304. quantum_admin/components/admin/connectors.q +283 -0
  305. quantum_admin/components/admin/dashboard.q +86 -0
  306. quantum_admin/components/admin/database.q +95 -0
  307. quantum_admin/components/admin/features.q +69 -0
  308. quantum_admin/components/admin/index.q +12 -0
  309. quantum_admin/components/admin/jobs.q +75 -0
  310. quantum_admin/components/admin/login.q +65 -0
  311. quantum_admin/components/admin/logout.q +11 -0
  312. quantum_admin/components/admin/settings.q +174 -0
  313. quantum_admin/components/admin/source.q +45 -0
  314. quantum_admin/components/admin/tests.q +73 -0
  315. quantum_admin/run.py +13 -0
  316. quantum_admin/services/__init__.py +16 -0
  317. quantum_admin/services/_base.py +84 -0
  318. quantum_admin/services/_component_test_generator.py +205 -0
  319. quantum_admin/services/_yaml_editor.py +82 -0
  320. quantum_admin/services/apps.py +330 -0
  321. quantum_admin/services/auth.py +61 -0
  322. quantum_admin/services/components.py +210 -0
  323. quantum_admin/services/connectors.py +164 -0
  324. quantum_admin/services/projects.py +189 -0
  325. quantum_admin/services/repository.py +223 -0
  326. quantum_admin/services/settings.py +150 -0
  327. quantum_admin/services/yaml_import.py +118 -0
  328. quantum_framework-0.18.0.dist-info/METADATA +281 -0
  329. quantum_framework-0.18.0.dist-info/RECORD +333 -0
  330. quantum_framework-0.18.0.dist-info/WHEEL +5 -0
  331. quantum_framework-0.18.0.dist-info/entry_points.txt +2 -0
  332. quantum_framework-0.18.0.dist-info/licenses/LICENSE +21 -0
  333. quantum_framework-0.18.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,347 @@
1
+ """
2
+ Job Management Service for Quantum Admin
3
+ Provides admin interface for managing Quantum jobs, schedules, and queues
4
+ """
5
+ import os
6
+ import sys
7
+ import logging
8
+ from typing import Dict, List, Optional, Any
9
+ from datetime import datetime
10
+ from dataclasses import asdict
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ # quantum is an installed package now (FRAMEWORK_PLAN Fase 1.1). This used to
15
+ # push <repo>/src onto sys.path and import `runtime.job_executor` — the
16
+ # pre-packaging layout. src/ became quantum/, so the import failed, the
17
+ # except swallowed it into a warning, HAS_JOB_EXECUTOR went False, and the
18
+ # admin's whole Jobs subsystem quietly reported zero jobs forever. The repo
19
+ # root is still added so the admin works from a checkout without installing.
20
+ QUANTUM_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
21
+ if QUANTUM_ROOT not in sys.path:
22
+ sys.path.insert(0, QUANTUM_ROOT)
23
+
24
+ try:
25
+ from quantum.runtime.job_executor import (
26
+ JobExecutor, JobQueueService, ScheduleService, ThreadService
27
+ )
28
+ HAS_JOB_EXECUTOR = True
29
+ except ImportError as e:
30
+ logger.warning(f"JobExecutor not available: {e}")
31
+ HAS_JOB_EXECUTOR = False
32
+ JobExecutor = None
33
+
34
+
35
+ class JobAdminService:
36
+ """
37
+ Admin service for managing Quantum jobs.
38
+
39
+ Provides:
40
+ - Job queue management
41
+ - Schedule management
42
+ - Thread monitoring
43
+ - Queue statistics
44
+ """
45
+
46
+ def __init__(self, job_db_path: str = "quantum_jobs.db"):
47
+ """
48
+ Initialize job admin service.
49
+
50
+ Args:
51
+ job_db_path: Path to job queue database
52
+ """
53
+ self._executor: Optional[JobExecutor] = None
54
+ self._job_db_path = job_db_path
55
+
56
+ @property
57
+ def executor(self) -> Optional[JobExecutor]:
58
+ """Get or create job executor instance."""
59
+ if not HAS_JOB_EXECUTOR:
60
+ return None
61
+
62
+ if self._executor is None:
63
+ self._executor = JobExecutor(job_db_path=self._job_db_path)
64
+ return self._executor
65
+
66
+ def is_available(self) -> bool:
67
+ """Check if job system is available."""
68
+ return HAS_JOB_EXECUTOR
69
+
70
+ # =========================================================================
71
+ # Jobs (Queued)
72
+ # =========================================================================
73
+
74
+ def list_jobs(
75
+ self,
76
+ queue: Optional[str] = None,
77
+ status: Optional[str] = None,
78
+ limit: int = 100
79
+ ) -> List[Dict]:
80
+ """
81
+ List all queued jobs.
82
+
83
+ Args:
84
+ queue: Filter by queue name
85
+ status: Filter by status (pending, running, completed, failed)
86
+ limit: Maximum jobs to return
87
+
88
+ Returns:
89
+ List of job dicts
90
+ """
91
+ if not self.executor:
92
+ return []
93
+
94
+ jobs = self.executor.job_queue.list_jobs(queue=queue, status=status, limit=limit)
95
+ return [self._job_to_dict(job) for job in jobs]
96
+
97
+ def get_job(self, job_id: int) -> Optional[Dict]:
98
+ """Get job details by ID."""
99
+ if not self.executor:
100
+ return None
101
+
102
+ job = self.executor.job_queue.get_job(job_id)
103
+ return self._job_to_dict(job) if job else None
104
+
105
+ def cancel_job(self, job_id: int) -> bool:
106
+ """Cancel a pending job."""
107
+ if not self.executor:
108
+ return False
109
+ return self.executor.job_queue.cancel_job(job_id)
110
+
111
+ def retry_job(self, job_id: int) -> bool:
112
+ """Retry a failed job."""
113
+ if not self.executor:
114
+ return False
115
+ return self.executor.job_queue.retry_job(job_id)
116
+
117
+ def dispatch_job(
118
+ self,
119
+ name: str,
120
+ queue: str = 'default',
121
+ params: Optional[Dict] = None,
122
+ delay: Optional[str] = None,
123
+ priority: int = 0
124
+ ) -> Optional[int]:
125
+ """
126
+ Dispatch a new job.
127
+
128
+ Args:
129
+ name: Job name
130
+ queue: Queue name
131
+ params: Job parameters
132
+ delay: Delay before execution
133
+ priority: Job priority (0-10)
134
+
135
+ Returns:
136
+ Job ID or None
137
+ """
138
+ if not self.executor:
139
+ return None
140
+
141
+ return self.executor.job_queue.dispatch(
142
+ name=name,
143
+ queue=queue,
144
+ params=params,
145
+ delay=delay,
146
+ priority=priority
147
+ )
148
+
149
+ def _job_to_dict(self, job) -> Dict:
150
+ """Convert JobInfo to dict."""
151
+ if not job:
152
+ return {}
153
+ return {
154
+ 'id': job.id,
155
+ 'name': job.name,
156
+ 'queue': job.queue,
157
+ 'params': job.params,
158
+ 'status': job.status,
159
+ 'attempts': job.attempts,
160
+ 'max_attempts': job.max_attempts,
161
+ 'scheduled_at': job.scheduled_at.isoformat() if job.scheduled_at else None,
162
+ 'started_at': job.started_at.isoformat() if job.started_at else None,
163
+ 'completed_at': job.completed_at.isoformat() if job.completed_at else None,
164
+ 'error': job.error,
165
+ 'created_at': job.created_at.isoformat() if job.created_at else None
166
+ }
167
+
168
+ # =========================================================================
169
+ # Schedules
170
+ # =========================================================================
171
+
172
+ def list_schedules(self) -> List[Dict]:
173
+ """List all scheduled tasks."""
174
+ if not self.executor:
175
+ return []
176
+
177
+ schedules = self.executor.schedule.list_schedules()
178
+ return [self._schedule_to_dict(s) for s in schedules]
179
+
180
+ def get_schedule(self, name: str) -> Optional[Dict]:
181
+ """Get schedule details by name."""
182
+ if not self.executor:
183
+ return None
184
+
185
+ schedule = self.executor.schedule.get_schedule(name)
186
+ return self._schedule_to_dict(schedule) if schedule else None
187
+
188
+ def pause_schedule(self, name: str) -> bool:
189
+ """Pause a scheduled task."""
190
+ if not self.executor:
191
+ return False
192
+ return self.executor.schedule.pause_schedule(name)
193
+
194
+ def resume_schedule(self, name: str) -> bool:
195
+ """Resume a paused schedule."""
196
+ if not self.executor:
197
+ return False
198
+ return self.executor.schedule.resume_schedule(name)
199
+
200
+ def run_schedule_now(self, name: str) -> bool:
201
+ """Trigger immediate execution of a schedule."""
202
+ if not self.executor:
203
+ return False
204
+ return self.executor.schedule.run_now(name)
205
+
206
+ def remove_schedule(self, name: str) -> bool:
207
+ """Remove a scheduled task."""
208
+ if not self.executor:
209
+ return False
210
+ return self.executor.schedule.remove_schedule(name)
211
+
212
+ def _schedule_to_dict(self, schedule) -> Dict:
213
+ """Convert ScheduleInfo to dict."""
214
+ if not schedule:
215
+ return {}
216
+ return {
217
+ 'name': schedule.name,
218
+ 'trigger_type': schedule.trigger_type,
219
+ 'trigger_info': schedule.trigger_info,
220
+ 'next_run': schedule.next_run.isoformat() if schedule.next_run else None,
221
+ 'enabled': schedule.enabled,
222
+ 'run_count': schedule.run_count,
223
+ 'last_run': schedule.last_run.isoformat() if schedule.last_run else None,
224
+ 'last_error': schedule.last_error
225
+ }
226
+
227
+ # =========================================================================
228
+ # Threads
229
+ # =========================================================================
230
+
231
+ def list_threads(self, status: Optional[str] = None) -> List[Dict]:
232
+ """List all threads."""
233
+ if not self.executor:
234
+ return []
235
+
236
+ threads = self.executor.thread.list_threads(status=status)
237
+ return [self._thread_to_dict(t) for t in threads]
238
+
239
+ def get_thread(self, name: str) -> Optional[Dict]:
240
+ """Get thread details by name."""
241
+ if not self.executor:
242
+ return None
243
+
244
+ thread = self.executor.thread.get_thread(name)
245
+ return self._thread_to_dict(thread) if thread else None
246
+
247
+ def terminate_thread(self, name: str) -> bool:
248
+ """Request thread termination."""
249
+ if not self.executor:
250
+ return False
251
+ return self.executor.thread.terminate_thread(name)
252
+
253
+ def _thread_to_dict(self, thread) -> Dict:
254
+ """Convert ThreadInfo to dict."""
255
+ if not thread:
256
+ return {}
257
+ return {
258
+ 'name': thread.name,
259
+ 'priority': thread.priority,
260
+ 'started_at': thread.started_at.isoformat() if thread.started_at else None,
261
+ 'status': thread.status,
262
+ 'timeout': thread.timeout,
263
+ 'error': thread.error
264
+ }
265
+
266
+ # =========================================================================
267
+ # Queues
268
+ # =========================================================================
269
+
270
+ def list_queues(self) -> List[Dict]:
271
+ """List all queues with stats."""
272
+ if not self.executor:
273
+ return [{'name': 'default', 'pending': 0, 'running': 0, 'completed': 0, 'failed': 0}]
274
+ return self.executor.list_queues()
275
+
276
+ def get_queue_stats(self, queue: str = 'default') -> Dict:
277
+ """Get queue statistics."""
278
+ if not self.executor:
279
+ return {'pending': 0, 'running': 0, 'completed': 0, 'failed': 0}
280
+ return self.executor.job_queue.get_queue_stats(queue)
281
+
282
+ def purge_queue(self, queue: str, status: Optional[str] = None) -> int:
283
+ """Purge jobs from a queue."""
284
+ if not self.executor:
285
+ return 0
286
+ return self.executor.job_queue.purge_queue(queue, status=status)
287
+
288
+ # =========================================================================
289
+ # Overview
290
+ # =========================================================================
291
+
292
+ def get_overview(self) -> Dict:
293
+ """
294
+ Get job system overview.
295
+
296
+ Returns:
297
+ Dict with counts and status
298
+ """
299
+ if not self.executor:
300
+ return {
301
+ 'available': False,
302
+ 'message': 'Job executor not available'
303
+ }
304
+
305
+ # Get queue stats
306
+ queue_stats = self.get_queue_stats()
307
+
308
+ # Get schedule count
309
+ schedules = self.list_schedules()
310
+ active_schedules = len([s for s in schedules if s.get('enabled')])
311
+
312
+ # Get thread count
313
+ threads = self.list_threads()
314
+ running_threads = len([t for t in threads if t.get('status') == 'running'])
315
+
316
+ return {
317
+ 'available': True,
318
+ 'jobs': {
319
+ 'pending': queue_stats.get('pending', 0),
320
+ 'running': queue_stats.get('running', 0),
321
+ 'completed': queue_stats.get('completed', 0),
322
+ 'failed': queue_stats.get('failed', 0),
323
+ 'total': sum(queue_stats.values())
324
+ },
325
+ 'schedules': {
326
+ 'total': len(schedules),
327
+ 'active': active_schedules,
328
+ 'paused': len(schedules) - active_schedules
329
+ },
330
+ 'threads': {
331
+ 'total': len(threads),
332
+ 'running': running_threads
333
+ },
334
+ 'queues': self.list_queues()
335
+ }
336
+
337
+
338
+ # Singleton instance
339
+ _job_service: Optional[JobAdminService] = None
340
+
341
+
342
+ def get_job_service() -> JobAdminService:
343
+ """Get job service singleton."""
344
+ global _job_service
345
+ if _job_service is None:
346
+ _job_service = JobAdminService()
347
+ return _job_service
@@ -0,0 +1,331 @@
1
+ """
2
+ Log Service for Quantum Admin
3
+ Unified logging for application, database, and access logs
4
+ """
5
+ import json
6
+ import logging
7
+ import asyncio
8
+ from typing import Optional, List, Dict, Any
9
+ from datetime import datetime, timedelta
10
+ from sqlalchemy.orm import Session
11
+ from sqlalchemy import desc, or_
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ try:
16
+ from .models import ApplicationLog, Project, Environment
17
+ except ImportError:
18
+ from models import ApplicationLog, Project, Environment
19
+
20
+
21
+ class LogLevel:
22
+ DEBUG = "DEBUG"
23
+ INFO = "INFO"
24
+ WARNING = "WARNING"
25
+ ERROR = "ERROR"
26
+ CRITICAL = "CRITICAL"
27
+
28
+
29
+ class LogSource:
30
+ APP = "app" # Application logs (stdout/stderr)
31
+ DB = "database" # Database query logs
32
+ ACCESS = "access" # HTTP access logs
33
+ API = "api" # External API calls
34
+ SECURITY = "security" # Security events (auth, permissions)
35
+ DEPLOY = "deploy" # Deployment logs
36
+ SYSTEM = "system" # System/infrastructure logs
37
+
38
+ @classmethod
39
+ def all_sources(cls):
40
+ return [cls.APP, cls.DB, cls.ACCESS, cls.API, cls.SECURITY, cls.DEPLOY, cls.SYSTEM]
41
+
42
+
43
+ def create_log(
44
+ db: Session,
45
+ project_id: int,
46
+ level: str,
47
+ source: str,
48
+ message: str,
49
+ environment_id: int = None,
50
+ component_name: str = None,
51
+ function_name: str = None,
52
+ line_number: int = None,
53
+ request_id: str = None,
54
+ request_method: str = None,
55
+ request_path: str = None,
56
+ response_status: int = None,
57
+ response_time_ms: int = None,
58
+ extra_data: Dict = None
59
+ ) -> ApplicationLog:
60
+ """
61
+ Create a log entry.
62
+
63
+ Args:
64
+ db: Database session
65
+ project_id: Project this log belongs to
66
+ level: Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
67
+ source: Log source (app, db, access, deploy, system)
68
+ message: Log message
69
+ environment_id: Optional environment ID
70
+ component_name: Optional component name
71
+ function_name: Optional function name
72
+ line_number: Optional line number
73
+ request_id: Optional request ID for tracing
74
+ request_method: Optional HTTP method
75
+ request_path: Optional request path
76
+ response_status: Optional HTTP status code
77
+ response_time_ms: Optional response time in milliseconds
78
+ extra_data: Optional additional data as dictionary
79
+
80
+ Returns:
81
+ The created ApplicationLog object
82
+ """
83
+ log_entry = ApplicationLog(
84
+ project_id=project_id,
85
+ environment_id=environment_id,
86
+ timestamp=datetime.utcnow(),
87
+ level=level,
88
+ source=source,
89
+ message=message,
90
+ component_name=component_name,
91
+ function_name=function_name,
92
+ line_number=line_number,
93
+ request_id=request_id,
94
+ request_method=request_method,
95
+ request_path=request_path,
96
+ response_status=response_status,
97
+ response_time_ms=response_time_ms,
98
+ extra_data=json.dumps(extra_data) if extra_data else None
99
+ )
100
+
101
+ db.add(log_entry)
102
+ db.commit()
103
+ db.refresh(log_entry)
104
+
105
+ return log_entry
106
+
107
+
108
+ def get_logs(
109
+ db: Session,
110
+ project_id: int,
111
+ source: str = None,
112
+ level: str = None,
113
+ environment_id: int = None,
114
+ search: str = None,
115
+ since: datetime = None,
116
+ until: datetime = None,
117
+ limit: int = 100,
118
+ offset: int = 0
119
+ ) -> List[ApplicationLog]:
120
+ """
121
+ Query logs with optional filters.
122
+
123
+ Args:
124
+ db: Database session
125
+ project_id: Filter by project
126
+ source: Filter by source (app, db, access, deploy, system)
127
+ level: Filter by log level
128
+ environment_id: Filter by environment
129
+ search: Search in message
130
+ since: Filter entries after this time
131
+ until: Filter entries before this time
132
+ limit: Maximum number of entries to return
133
+ offset: Number of entries to skip
134
+
135
+ Returns:
136
+ List of ApplicationLog objects
137
+ """
138
+ query = db.query(ApplicationLog).filter(ApplicationLog.project_id == project_id)
139
+
140
+ if source and source != 'all':
141
+ query = query.filter(ApplicationLog.source == source)
142
+ if level:
143
+ query = query.filter(ApplicationLog.level == level)
144
+ if environment_id:
145
+ query = query.filter(ApplicationLog.environment_id == environment_id)
146
+ if search:
147
+ query = query.filter(ApplicationLog.message.ilike(f'%{search}%'))
148
+ if since:
149
+ query = query.filter(ApplicationLog.timestamp >= since)
150
+ if until:
151
+ query = query.filter(ApplicationLog.timestamp <= until)
152
+
153
+ return query.order_by(desc(ApplicationLog.timestamp)).offset(offset).limit(limit).all()
154
+
155
+
156
+ def get_log_stats(
157
+ db: Session,
158
+ project_id: int,
159
+ hours: int = 24
160
+ ) -> Dict:
161
+ """
162
+ Get log statistics for a project.
163
+ """
164
+ from sqlalchemy import func
165
+
166
+ since = datetime.utcnow() - timedelta(hours=hours)
167
+ query = db.query(ApplicationLog).filter(
168
+ ApplicationLog.project_id == project_id,
169
+ ApplicationLog.timestamp >= since
170
+ )
171
+
172
+ total = query.count()
173
+
174
+ # Count by level
175
+ level_counts = {}
176
+ for level in [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARNING, LogLevel.ERROR, LogLevel.CRITICAL]:
177
+ count = query.filter(ApplicationLog.level == level).count()
178
+ level_counts[level.lower()] = count
179
+
180
+ # Count by source
181
+ source_counts = {}
182
+ for source in LogSource.all_sources():
183
+ count = query.filter(ApplicationLog.source == source).count()
184
+ source_counts[source] = count
185
+
186
+ # Error rate
187
+ error_count = level_counts.get('error', 0) + level_counts.get('critical', 0)
188
+ error_rate = (error_count / total * 100) if total > 0 else 0
189
+
190
+ return {
191
+ "total": total,
192
+ "period_hours": hours,
193
+ "by_level": level_counts,
194
+ "by_source": source_counts,
195
+ "error_rate": round(error_rate, 2)
196
+ }
197
+
198
+
199
+ def format_log_for_display(log: ApplicationLog) -> Dict:
200
+ """Format a log entry for display."""
201
+ return {
202
+ "id": log.id,
203
+ "timestamp": log.timestamp.strftime('%H:%M:%S') if log.timestamp else '',
204
+ "timestamp_full": log.timestamp.isoformat() if log.timestamp else '',
205
+ "level": log.level,
206
+ "source": log.source,
207
+ "message": log.message[:200] if log.message else '',
208
+ "full_message": log.message,
209
+ "component": log.component_name,
210
+ "function": log.function_name,
211
+ "request": f"{log.request_method} {log.request_path}" if log.request_method else None,
212
+ "status": log.response_status,
213
+ "duration": f"{log.response_time_ms}ms" if log.response_time_ms else None
214
+ }
215
+
216
+
217
+ class LogService:
218
+ """Service class for log operations"""
219
+
220
+ def __init__(self, db: Session):
221
+ self.db = db
222
+
223
+ def log(
224
+ self,
225
+ project_id: int,
226
+ level: str,
227
+ source: str,
228
+ message: str,
229
+ **kwargs
230
+ ) -> ApplicationLog:
231
+ """Create a log entry"""
232
+ return create_log(
233
+ self.db,
234
+ project_id=project_id,
235
+ level=level,
236
+ source=source,
237
+ message=message,
238
+ **kwargs
239
+ )
240
+
241
+ def info(self, project_id: int, source: str, message: str, **kwargs) -> ApplicationLog:
242
+ return self.log(project_id, LogLevel.INFO, source, message, **kwargs)
243
+
244
+ def warning(self, project_id: int, source: str, message: str, **kwargs) -> ApplicationLog:
245
+ return self.log(project_id, LogLevel.WARNING, source, message, **kwargs)
246
+
247
+ def error(self, project_id: int, source: str, message: str, **kwargs) -> ApplicationLog:
248
+ return self.log(project_id, LogLevel.ERROR, source, message, **kwargs)
249
+
250
+ def debug(self, project_id: int, source: str, message: str, **kwargs) -> ApplicationLog:
251
+ return self.log(project_id, LogLevel.DEBUG, source, message, **kwargs)
252
+
253
+ def access_log(
254
+ self,
255
+ project_id: int,
256
+ method: str,
257
+ path: str,
258
+ status: int,
259
+ duration_ms: int,
260
+ **kwargs
261
+ ) -> ApplicationLog:
262
+ """Create an access log entry"""
263
+ message = f"{method} {path} {status} {duration_ms}ms"
264
+ return self.log(
265
+ project_id,
266
+ LogLevel.INFO if status < 400 else LogLevel.WARNING if status < 500 else LogLevel.ERROR,
267
+ LogSource.ACCESS,
268
+ message,
269
+ request_method=method,
270
+ request_path=path,
271
+ response_status=status,
272
+ response_time_ms=duration_ms,
273
+ **kwargs
274
+ )
275
+
276
+ def query_log(
277
+ self,
278
+ project_id: int,
279
+ query: str,
280
+ duration_ms: int,
281
+ **kwargs
282
+ ) -> ApplicationLog:
283
+ """Create a database query log entry"""
284
+ message = f"Query executed in {duration_ms}ms: {query[:100]}..."
285
+ return self.log(
286
+ project_id,
287
+ LogLevel.DEBUG,
288
+ LogSource.DB,
289
+ message,
290
+ response_time_ms=duration_ms,
291
+ **kwargs
292
+ )
293
+
294
+ def get_logs(
295
+ self,
296
+ project_id: int,
297
+ source: str = None,
298
+ level: str = None,
299
+ environment_id: int = None,
300
+ search: str = None,
301
+ since: datetime = None,
302
+ until: datetime = None,
303
+ limit: int = 100,
304
+ offset: int = 0
305
+ ) -> List[ApplicationLog]:
306
+ """Query logs"""
307
+ return get_logs(
308
+ self.db,
309
+ project_id=project_id,
310
+ source=source,
311
+ level=level,
312
+ environment_id=environment_id,
313
+ search=search,
314
+ since=since,
315
+ until=until,
316
+ limit=limit,
317
+ offset=offset
318
+ )
319
+
320
+ def get_stats(self, project_id: int, hours: int = 24) -> Dict:
321
+ """Get log statistics"""
322
+ return get_log_stats(self.db, project_id=project_id, hours=hours)
323
+
324
+ def format_logs(self, logs: List[ApplicationLog]) -> List[Dict]:
325
+ """Format logs for display"""
326
+ return [format_log_for_display(log) for log in logs]
327
+
328
+
329
+ def get_log_service(db: Session) -> LogService:
330
+ """Get log service instance"""
331
+ return LogService(db)