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,558 @@
1
+ <q:component name="AdminAppDetail" require_auth="true" require_role="admin" login_url="/admin/login">
2
+ <!--
3
+ Uma aplicação: /admin/app/<nome>. Cada action acha o projeto pelo `name`
4
+ da URL (ROUTE-2) — a versão anterior confiava em project_id e app_name
5
+ vindos de campos escondidos do formulário. Os serviços estão em
6
+ quantum_admin/services/apps.py, projects.py e connectors.py.
7
+
8
+ Saíram desta tela, por nunca terem funcionado: gerar e rodar testes de
9
+ componente do projeto (o gerador importava `core.parser`, que não existe
10
+ desde a troca de src/ para quantum/) e o aviso de "mudanças externas" na
11
+ config. Os testes de componente ficam na tela Components.
12
+ -->
13
+ <q:import component="AdminShell" from="admin/_layout" />
14
+
15
+ <!-- ===================================================== projeto -->
16
+ <q:action name="updateProject" method="POST">
17
+ <q:param name="status" type="string" required="true" />
18
+ <q:invoke name="app" service="admin.projects.by_name"><q:param name="name" value="{name}" /></q:invoke>
19
+ <q:invoke name="salvo" service="admin.projects.update" onerror="continue">
20
+ <q:param name="project_id" value="{app.id}" type="integer" />
21
+ <q:param name="name" value="{form.new_name}" />
22
+ <q:param name="description" value="{form.description}" />
23
+ <q:param name="status" value="{status}" />
24
+ </q:invoke>
25
+ <q:if condition="salvo_result.success">
26
+ <q:redirect url="/admin/app/{salvo.name}" flash="Application updated" />
27
+ </q:if>
28
+ <q:flash type="error" message="{salvo_result.error.message}" />
29
+ <q:redirect url="/admin/app/{name}" />
30
+ </q:action>
31
+
32
+ <!-- ===================================================== connectors -->
33
+ <q:action name="createProjectConnector" method="POST">
34
+ <q:param name="conn_name" type="string" required="true" />
35
+ <q:param name="conn_type" type="string" required="true" />
36
+ <q:param name="provider" type="string" required="true" />
37
+ <q:param name="port" type="integer" default="0" />
38
+ <q:invoke name="app" service="admin.projects.by_name"><q:param name="name" value="{name}" /></q:invoke>
39
+ <q:invoke name="criado" service="admin.connectors.create" onerror="continue">
40
+ <q:param name="name" value="{conn_name}" />
41
+ <q:param name="type" value="{conn_type}" />
42
+ <q:param name="provider" value="{provider}" />
43
+ <q:param name="host" value="{form.host}" />
44
+ <q:param name="port" value="{port}" type="integer" />
45
+ <q:param name="database" value="{form.database}" />
46
+ <q:param name="username" value="{form.username}" />
47
+ <q:param name="password" value="{form.password}" />
48
+ <q:param name="application_id" value="{app.id}" type="integer" />
49
+ </q:invoke>
50
+ <q:if condition="criado_result.success">
51
+ <q:redirect url="/admin/app/{name}?tab=connectors" flash="Connector {criado.name} created" />
52
+ </q:if>
53
+ <q:flash type="error" message="{criado_result.error.message}" />
54
+ <q:redirect url="/admin/app/{name}?tab=connectors" />
55
+ </q:action>
56
+
57
+ <q:action name="testConnector" method="POST">
58
+ <q:param name="connector_id" type="string" required="true" />
59
+ <q:invoke name="teste" service="admin.connectors.test" onerror="continue">
60
+ <q:param name="connector_id" value="{connector_id}" />
61
+ </q:invoke>
62
+ <q:if condition="not teste_result.success">
63
+ <q:flash type="error" message="{teste_result.error.message}" />
64
+ <q:redirect url="/admin/app/{name}?tab=connectors" />
65
+ </q:if>
66
+ <q:if condition="teste.success">
67
+ <q:redirect url="/admin/app/{name}?tab=connectors" flash="Connection OK" />
68
+ </q:if>
69
+ <q:flash type="error" message="Connection failed: {teste.error}" />
70
+ <q:redirect url="/admin/app/{name}?tab=connectors" />
71
+ </q:action>
72
+
73
+ <q:action name="detachConnector" method="POST">
74
+ <q:param name="connector_id" type="string" required="true" />
75
+ <q:invoke name="solto" service="admin.connectors.detach" onerror="continue">
76
+ <q:param name="connector_id" value="{connector_id}" />
77
+ </q:invoke>
78
+ <q:if condition="solto_result.success">
79
+ <q:redirect url="/admin/app/{name}?tab=connectors" flash="Connector {solto.name} is now public" />
80
+ </q:if>
81
+ <q:flash type="error" message="{solto_result.error.message}" />
82
+ <q:redirect url="/admin/app/{name}?tab=connectors" />
83
+ </q:action>
84
+
85
+ <!-- ===================================================== config -->
86
+ <q:action name="saveProjectConfig" method="POST">
87
+ <q:param name="config_yaml" type="string" />
88
+ <q:invoke name="app" service="admin.projects.by_name"><q:param name="name" value="{name}" /></q:invoke>
89
+ <q:invoke name="gravado" service="admin.projects.save_config" onerror="continue">
90
+ <q:param name="project_id" value="{app.id}" type="integer" />
91
+ <q:param name="config_yaml" value="{form.config_yaml}" />
92
+ </q:invoke>
93
+ <q:if condition="gravado_result.success">
94
+ <q:redirect url="/admin/app/{name}?tab=config" flash="Configuration saved" />
95
+ </q:if>
96
+ <q:flash type="error" message="{gravado_result.error.message}" />
97
+ <q:redirect url="/admin/app/{name}?tab=config" />
98
+ </q:action>
99
+
100
+ <!-- ===================================================== ambientes -->
101
+ <q:action name="createEnvironment" method="POST">
102
+ <q:param name="env_name" type="string" required="true" />
103
+ <q:invoke name="app" service="admin.projects.by_name"><q:param name="name" value="{name}" /></q:invoke>
104
+ <q:invoke name="ambiente" service="admin.environments.create" onerror="continue">
105
+ <q:param name="project_id" value="{app.id}" type="integer" />
106
+ <q:param name="name" value="{env_name}" />
107
+ </q:invoke>
108
+ <q:if condition="ambiente_result.success">
109
+ <q:redirect url="/admin/app/{name}?tab=environments" flash="Environment {ambiente.name} created" />
110
+ </q:if>
111
+ <q:flash type="error" message="{ambiente_result.error.message}" />
112
+ <q:redirect url="/admin/app/{name}?tab=environments" />
113
+ </q:action>
114
+
115
+ <q:action name="createDefaultEnvironments" method="POST">
116
+ <q:invoke name="app" service="admin.projects.by_name"><q:param name="name" value="{name}" /></q:invoke>
117
+ <q:invoke name="padroes" service="admin.environments.create_defaults">
118
+ <q:param name="project_id" value="{app.id}" type="integer" />
119
+ </q:invoke>
120
+ <q:redirect url="/admin/app/{name}?tab=environments" flash="{len(padroes)} environment(s) created" />
121
+ </q:action>
122
+
123
+ <q:action name="updateEnvironment" method="POST">
124
+ <q:param name="environment_id" type="integer" required="true" />
125
+ <q:invoke name="ambiente" service="admin.environments.update" onerror="continue">
126
+ <q:param name="environment_id" value="{environment_id}" type="integer" />
127
+ <q:param name="port" value="{form.port}" />
128
+ <q:param name="branch" value="{form.branch}" />
129
+ <q:param name="variables" value="{form.variables}" />
130
+ </q:invoke>
131
+ <q:if condition="ambiente_result.success">
132
+ <q:redirect url="/admin/app/{name}?tab=environments" flash="Environment {ambiente.name} saved" />
133
+ </q:if>
134
+ <q:flash type="error" message="{ambiente_result.error.message}" />
135
+ <q:redirect url="/admin/app/{name}?tab=environments" />
136
+ </q:action>
137
+
138
+ <q:action name="deleteEnvironment" method="POST">
139
+ <q:param name="environment_id" type="integer" required="true" />
140
+ <q:invoke name="removido" service="admin.environments.delete" onerror="continue">
141
+ <q:param name="environment_id" value="{environment_id}" type="integer" />
142
+ </q:invoke>
143
+ <q:if condition="removido_result.success">
144
+ <q:redirect url="/admin/app/{name}?tab=environments" flash="Environment deleted" />
145
+ </q:if>
146
+ <q:flash type="error" message="{removido_result.error.message}" />
147
+ <q:redirect url="/admin/app/{name}?tab=environments" />
148
+ </q:action>
149
+
150
+ <!-- ===================================================== servidor -->
151
+ <q:action name="startServer" method="POST">
152
+ <q:invoke name="app" service="admin.projects.by_name"><q:param name="name" value="{name}" /></q:invoke>
153
+ <q:invoke name="subiu" service="admin.servers.start" onerror="continue">
154
+ <q:param name="project_id" value="{app.id}" type="integer" />
155
+ </q:invoke>
156
+ <q:if condition="subiu_result.success">
157
+ <q:redirect url="/admin/app/{name}?tab=runtime" flash="Server started" />
158
+ </q:if>
159
+ <q:flash type="error" message="{subiu_result.error.message}" />
160
+ <q:redirect url="/admin/app/{name}?tab=runtime" />
161
+ </q:action>
162
+
163
+ <q:action name="stopServer" method="POST">
164
+ <q:invoke name="app" service="admin.projects.by_name"><q:param name="name" value="{name}" /></q:invoke>
165
+ <q:invoke name="parou" service="admin.servers.stop" onerror="continue">
166
+ <q:param name="project_id" value="{app.id}" type="integer" />
167
+ </q:invoke>
168
+ <q:if condition="parou_result.success">
169
+ <q:redirect url="/admin/app/{name}?tab=runtime" flash="Server stopped" />
170
+ </q:if>
171
+ <q:flash type="error" message="{parou_result.error.message}" />
172
+ <q:redirect url="/admin/app/{name}?tab=runtime" />
173
+ </q:action>
174
+
175
+ <!-- ===================================================== dados da página -->
176
+ <q:set name="aba" value="{query.tab}" default="general" />
177
+ <q:set name="abas" type="array" value='[{"id": "general", "label": "General"}, {"id": "connectors", "label": "Connectors"}, {"id": "components", "label": "Components"}, {"id": "config", "label": "Config"}, {"id": "environments", "label": "Environments"}, {"id": "runtime", "label": "Runtime"}]' />
178
+ <q:invoke name="app" service="admin.projects.by_name" onerror="continue">
179
+ <q:param name="name" value="{name}" />
180
+ </q:invoke>
181
+ <q:if condition="app_result.success">
182
+ <q:invoke name="arquivos" service="admin.projects.files">
183
+ <q:param name="project_id" value="{app.id}" type="integer" />
184
+ </q:invoke>
185
+ <q:invoke name="conectores" service="admin.connectors.list">
186
+ <q:param name="application_id" value="{app.id}" type="integer" />
187
+ </q:invoke>
188
+ <q:invoke name="provedores" service="admin.connectors.providers" />
189
+ <q:invoke name="config" service="admin.projects.config">
190
+ <q:param name="project_id" value="{app.id}" type="integer" />
191
+ </q:invoke>
192
+ <q:invoke name="ambientes" service="admin.environments.list">
193
+ <q:param name="project_id" value="{app.id}" type="integer" />
194
+ </q:invoke>
195
+ <!-- sem quantum.config.yaml o status é erro: a aba Runtime diz o motivo -->
196
+ <q:invoke name="servidor" service="admin.servers.status" onerror="continue">
197
+ <q:param name="project_id" value="{app.id}" type="integer" />
198
+ </q:invoke>
199
+ <q:invoke name="log" service="admin.servers.log">
200
+ <q:param name="project_id" value="{app.id}" type="integer" />
201
+ </q:invoke>
202
+ </q:if>
203
+
204
+ <AdminShell title="{app.name if app_result.success else 'Application'}" active="applications" flash="{flash}" flashType="{flashType}">
205
+
206
+ <q:if condition="not app_result.success">
207
+ <div class="qa-card">
208
+ <div class="qa-empty">
209
+ <div class="qa-empty-title">Application Not Found</div>
210
+ <div class="qa-empty-text">{app_result.error.message}</div>
211
+ <a href="/admin/applications" class="qa-btn qa-btn-primary">Back to Applications</a>
212
+ </div>
213
+ </div>
214
+ </q:if>
215
+
216
+ <q:if condition="app_result.success">
217
+ <div class="qa-flex qa-items-center qa-mb-4" style="gap: 12px;">
218
+ <a href="/admin/applications" class="qa-text-sm">Applications</a>
219
+ <span class="{'qa-badge qa-badge-success' if app.status == 'active' else ('qa-badge qa-badge-danger' if app.status == 'error' else 'qa-badge qa-badge-gray')}">{app.status}</span>
220
+ <span class="{'qa-process-running' if servidor_result.success and servidor.running else 'qa-process-stopped'}">{'Running' if servidor_result.success and servidor.running else 'Stopped'}</span>
221
+ </div>
222
+
223
+ <q:loop type="array" var="t" items="{abas}">
224
+ <q:if condition="t.id == aba">
225
+ <input type="radio" name="tab" id="tab-{t.id}" class="qa-tab-radio" checked="checked" />
226
+ <q:else><input type="radio" name="tab" id="tab-{t.id}" class="qa-tab-radio" /></q:else>
227
+ </q:if>
228
+ </q:loop>
229
+ <div class="qa-tabs-bar">
230
+ <q:loop type="array" var="t" items="{abas}">
231
+ <label for="tab-{t.id}" class="qa-tab-label">{t.label}</label>
232
+ </q:loop>
233
+ </div>
234
+
235
+ <div class="qa-tabs-content">
236
+
237
+ <!-- ============================ General -->
238
+ <div class="qa-panel-general">
239
+ <div class="qa-grid qa-grid-4 qa-mb-6">
240
+ <div class="qa-stat-card"><div class="qa-stat-label">Components</div><div class="qa-stat-value">{len(arquivos.components)}</div></div>
241
+ <div class="qa-stat-card"><div class="qa-stat-label">Size on Disk</div><div class="qa-stat-value">{arquivos.disk_size}</div></div>
242
+ <div class="qa-stat-card"><div class="qa-stat-label">Environments</div><div class="qa-stat-value">{len(ambientes)}</div></div>
243
+ <div class="qa-stat-card"><div class="qa-stat-label">Static Files</div><div class="qa-stat-value">{arquivos.static_files}</div></div>
244
+ </div>
245
+
246
+ <div class="qa-card qa-mb-6">
247
+ <div class="qa-card-header"><div class="qa-card-title">Application</div></div>
248
+ <div class="qa-card-body">
249
+ <div class="qa-flex qa-items-center qa-justify-between"><span class="qa-text-sm qa-text-muted">Source path</span><span class="qa-font-mono qa-text-sm">{app.source_path}</span></div>
250
+ <div class="qa-flex qa-items-center qa-justify-between"><span class="qa-text-sm qa-text-muted">Port</span><span class="qa-font-mono qa-text-sm">{app.port or '-'}</span></div>
251
+ <div class="qa-flex qa-items-center qa-justify-between"><span class="qa-text-sm qa-text-muted">Created</span><span class="qa-text-sm">{app.created_at}</span></div>
252
+ <div class="qa-flex qa-items-center qa-justify-between"><span class="qa-text-sm qa-text-muted">Updated</span><span class="qa-text-sm">{app.updated_at}</span></div>
253
+ </div>
254
+ </div>
255
+
256
+ <div class="qa-card">
257
+ <div class="qa-card-header"><div class="qa-card-title">Edit Application</div></div>
258
+ <div class="qa-card-body">
259
+ <form method="POST" action="/admin/app/{app.name}">
260
+ <input type="hidden" name="action" value="updateProject" />
261
+ <div class="qa-form-row qa-mb-4">
262
+ <div class="qa-form-group">
263
+ <label class="qa-label" for="new_name">Name</label>
264
+ <input id="new_name" type="text" name="new_name" class="qa-input" value="{app.name}" minlength="2" required="" />
265
+ </div>
266
+ <div class="qa-form-group">
267
+ <label class="qa-label" for="status">Status</label>
268
+ <select id="status" name="status" class="qa-input qa-select">
269
+ <q:loop type="array" var="estado" items="{['active', 'archived', 'error']}">
270
+ <q:if condition="estado == app.status">
271
+ <option value="{estado}" selected="selected">{estado}</option>
272
+ <q:else><option value="{estado}">{estado}</option></q:else>
273
+ </q:if>
274
+ </q:loop>
275
+ </select>
276
+ </div>
277
+ </div>
278
+ <div class="qa-form-group qa-mb-4">
279
+ <label class="qa-label" for="description">Description</label>
280
+ <input id="description" type="text" name="description" class="qa-input" value="{app.description}" />
281
+ </div>
282
+ <button type="submit" class="qa-btn qa-btn-primary">Save Changes</button>
283
+ </form>
284
+ </div>
285
+ </div>
286
+ </div>
287
+
288
+ <!-- ============================ Connectors -->
289
+ <div class="qa-panel-connectors">
290
+ <div class="qa-card qa-mb-6">
291
+ <div class="qa-card-header">
292
+ <div>
293
+ <div class="qa-card-title">Connectors</div>
294
+ <div class="qa-card-subtitle">This application's own, and the public ones every application can use</div>
295
+ </div>
296
+ </div>
297
+ <div class="qa-card-body qa-p-0" style="overflow-x: auto;">
298
+ <q:if condition="len(conectores) &gt; 0">
299
+ <table class="qa-table">
300
+ <thead><tr><th>Name</th><th>Provider</th><th>Host:Port</th><th>Status</th><th>Scope</th><th></th></tr></thead>
301
+ <tbody>
302
+ <q:loop type="array" var="conn" items="{conectores}">
303
+ <tr>
304
+ <td><span class="qa-font-medium">{conn.name}</span> <span class="qa-badge qa-type-{conn.type}">{conn.type_label}</span></td>
305
+ <td><span class="qa-text-sm">{conn.provider_name}</span></td>
306
+ <td><span class="qa-font-mono qa-text-sm">{conn.host}:{conn.port}</span></td>
307
+ <td><div class="qa-status-badge"><span class="qa-status-dot {conn.status}"></span><span class="qa-status-text">{conn.status}</span></div></td>
308
+ <td><span class="qa-text-sm qa-text-muted">{'this application' if conn.application_id else 'public'}</span></td>
309
+ <td>
310
+ <div class="qa-flex" style="gap: 4px;">
311
+ <form method="POST" action="/admin/app/{app.name}" style="margin: 0;">
312
+ <input type="hidden" name="action" value="testConnector" />
313
+ <input type="hidden" name="connector_id" value="{conn.id}" />
314
+ <button type="submit" class="qa-btn qa-btn-ghost qa-btn-sm">Test</button>
315
+ </form>
316
+ <q:if condition="conn.application_id">
317
+ <form method="POST" action="/admin/app/{app.name}" style="margin: 0;" onsubmit="return confirm('Make this connector public? Every application will be able to use it.');">
318
+ <input type="hidden" name="action" value="detachConnector" />
319
+ <input type="hidden" name="connector_id" value="{conn.id}" />
320
+ <button type="submit" class="qa-btn qa-btn-ghost qa-btn-sm">Make Public</button>
321
+ </form>
322
+ </q:if>
323
+ </div>
324
+ </td>
325
+ </tr>
326
+ </q:loop>
327
+ </tbody>
328
+ </table>
329
+ </q:if>
330
+ <q:if condition="len(conectores) == 0">
331
+ <div class="qa-empty"><div class="qa-empty-title">No Connectors</div><div class="qa-empty-text">Create one below, or a public one on the Connectors page.</div></div>
332
+ </q:if>
333
+ </div>
334
+ </div>
335
+
336
+ <div class="qa-card">
337
+ <div class="qa-card-header"><div class="qa-card-title">New Connector for {app.name}</div></div>
338
+ <div class="qa-card-body">
339
+ <form method="POST" action="/admin/app/{app.name}">
340
+ <input type="hidden" name="action" value="createProjectConnector" />
341
+ <div class="qa-form-row qa-mb-4">
342
+ <div class="qa-form-group">
343
+ <label class="qa-label" for="conn_name">Name</label>
344
+ <input id="conn_name" type="text" name="conn_name" class="qa-input" placeholder="e.g. App Redis" required="" />
345
+ </div>
346
+ <div class="qa-form-group">
347
+ <label class="qa-label" for="conn_type">Type</label>
348
+ <select id="conn_type" name="conn_type" class="qa-input qa-select" required="">
349
+ <option value="">Select type...</option>
350
+ <option value="database">Database</option>
351
+ <option value="mq">Message Queue</option>
352
+ <option value="cache">Cache</option>
353
+ <option value="storage">Storage</option>
354
+ <option value="ai">AI</option>
355
+ </select>
356
+ </div>
357
+ </div>
358
+ <div class="qa-form-row qa-mb-4">
359
+ <div class="qa-form-group">
360
+ <label class="qa-label" for="provider">Provider</label>
361
+ <select id="provider" name="provider" class="qa-input qa-select" required="">
362
+ <option value="">Select provider...</option>
363
+ <q:loop type="array" var="p" items="{provedores}">
364
+ <option value="{p.provider}">{p.name}</option>
365
+ </q:loop>
366
+ </select>
367
+ </div>
368
+ <div class="qa-form-group">
369
+ <label class="qa-label" for="port">Port</label>
370
+ <input id="port" type="number" name="port" class="qa-input" min="0" max="65535" placeholder="provider default" />
371
+ </div>
372
+ </div>
373
+ <div class="qa-form-row qa-mb-4">
374
+ <div class="qa-form-group">
375
+ <label class="qa-label" for="host">Host</label>
376
+ <input id="host" type="text" name="host" class="qa-input" placeholder="localhost" />
377
+ </div>
378
+ <div class="qa-form-group">
379
+ <label class="qa-label" for="database">Database</label>
380
+ <input id="database" type="text" name="database" class="qa-input" />
381
+ </div>
382
+ </div>
383
+ <div class="qa-form-row qa-mb-4">
384
+ <div class="qa-form-group">
385
+ <label class="qa-label" for="username">Username</label>
386
+ <input id="username" type="text" name="username" class="qa-input" autocomplete="off" />
387
+ </div>
388
+ <div class="qa-form-group">
389
+ <label class="qa-label" for="password">Password or API key</label>
390
+ <input id="password" type="password" name="password" class="qa-input" autocomplete="new-password" />
391
+ </div>
392
+ </div>
393
+ <button type="submit" class="qa-btn qa-btn-primary">Create Connector</button>
394
+ </form>
395
+ </div>
396
+ </div>
397
+ </div>
398
+
399
+ <!-- ============================ Components -->
400
+ <div class="qa-panel-components">
401
+ <div class="qa-card">
402
+ <div class="qa-card-header">
403
+ <div>
404
+ <div class="qa-card-title">Components and Routes</div>
405
+ <div class="qa-card-subtitle">{len(arquivos.components)} .q files in {app.source_path}/components</div>
406
+ </div>
407
+ </div>
408
+ <div class="qa-card-body qa-p-0" style="overflow-x: auto;">
409
+ <q:if condition="len(arquivos.components) &gt; 0">
410
+ <table class="qa-table">
411
+ <thead><tr><th>Route</th><th>Component</th><th>File</th><th>Lines</th><th>Tags</th></tr></thead>
412
+ <tbody>
413
+ <q:loop type="array" var="comp" items="{arquivos.components}">
414
+ <tr>
415
+ <td><span class="qa-font-mono qa-font-medium">{comp.route}</span> <q:if condition="comp.dynamic"><span class="qa-badge qa-badge-info">dynamic</span></q:if></td>
416
+ <td>{comp.name}</td>
417
+ <td><a href="/admin/source?file={comp.source}" class="qa-font-mono qa-text-sm">{comp.path}</a></td>
418
+ <td style="font-variant-numeric: tabular-nums;">{comp.lines}</td>
419
+ <td><span class="qa-text-sm qa-text-muted">{join(comp.tags, ', ') if comp.tags else '-'}</span></td>
420
+ </tr>
421
+ </q:loop>
422
+ </tbody>
423
+ </table>
424
+ </q:if>
425
+ <q:if condition="len(arquivos.components) == 0">
426
+ <div class="qa-empty"><div class="qa-empty-title">No Components</div><div class="qa-empty-text">Add .q files to {app.source_path}/components; each one is served at the route shown here.</div></div>
427
+ </q:if>
428
+ </div>
429
+ </div>
430
+ </div>
431
+
432
+ <!-- ============================ Config -->
433
+ <div class="qa-panel-config">
434
+ <q:if condition="config.error">
435
+ <div class="qa-flash qa-flash-error" role="alert">The file on disk is not valid YAML: {config.error}</div>
436
+ </q:if>
437
+ <div class="qa-card">
438
+ <div class="qa-card-header">
439
+ <div>
440
+ <div class="qa-card-title">quantum.config.yaml</div>
441
+ <div class="qa-card-subtitle qa-font-mono">{config.path}</div>
442
+ </div>
443
+ </div>
444
+ <div class="qa-card-body">
445
+ <form method="POST" action="/admin/app/{app.name}">
446
+ <input type="hidden" name="action" value="saveProjectConfig" />
447
+ <label class="qa-label" for="config_yaml">YAML</label>
448
+ <textarea id="config_yaml" name="config_yaml" class="qa-config-editor" rows="20" spellcheck="false">{config.text}</textarea>
449
+ <div class="qa-input-hint qa-mb-4">Saved exactly as written, comments included. Invalid YAML, or YAML that is not a mapping, is refused and the file is left as it was.</div>
450
+ <button type="submit" class="qa-btn qa-btn-primary">Save Configuration</button>
451
+ </form>
452
+ </div>
453
+ </div>
454
+ </div>
455
+
456
+ <!-- ============================ Environments -->
457
+ <div class="qa-panel-environments">
458
+ <div class="qa-card qa-mb-6">
459
+ <div class="qa-card-header"><div class="qa-card-title">New Environment</div></div>
460
+ <div class="qa-card-body">
461
+ <form method="POST" action="/admin/app/{app.name}" class="qa-flex qa-items-center" style="gap: 8px; flex-wrap: wrap;">
462
+ <input type="hidden" name="action" value="createEnvironment" />
463
+ <label class="qa-label" for="env_name">Name</label>
464
+ <input id="env_name" type="text" name="env_name" class="qa-input" placeholder="staging" required="" style="max-width: 240px;" />
465
+ <button type="submit" class="qa-btn qa-btn-primary">Create</button>
466
+ </form>
467
+ <q:if condition="len(ambientes) == 0">
468
+ <form method="POST" action="/admin/app/{app.name}" style="margin-top: 12px;">
469
+ <input type="hidden" name="action" value="createDefaultEnvironments" />
470
+ <button type="submit" class="qa-btn qa-btn-secondary qa-btn-sm">Create development, staging and production</button>
471
+ </form>
472
+ </q:if>
473
+ </div>
474
+ </div>
475
+
476
+ <q:loop type="array" var="env" items="{ambientes}">
477
+ <div class="qa-env-card qa-mb-4">
478
+ <div class="qa-flex qa-items-center qa-justify-between qa-mb-4">
479
+ <div>
480
+ <span class="qa-font-semibold qa-text-lg">{env.display_name or env.name}</span>
481
+ <span class="qa-text-xs qa-text-muted">{len(env.variables)} variables</span>
482
+ </div>
483
+ <form method="POST" action="/admin/app/{app.name}" style="margin: 0;" onsubmit="return confirm('Delete this environment?');">
484
+ <input type="hidden" name="action" value="deleteEnvironment" />
485
+ <input type="hidden" name="environment_id" value="{env.id}" />
486
+ <button type="submit" class="qa-btn qa-btn-danger qa-btn-sm" aria-label="Delete {env.name}">Delete</button>
487
+ </form>
488
+ </div>
489
+ <form method="POST" action="/admin/app/{app.name}">
490
+ <input type="hidden" name="action" value="updateEnvironment" />
491
+ <input type="hidden" name="environment_id" value="{env.id}" />
492
+ <div class="qa-form-group qa-mb-4">
493
+ <label class="qa-label" for="variables_{env.id}">Variables (NAME=value, one per line)</label>
494
+ <textarea id="variables_{env.id}" name="variables" class="qa-input qa-textarea" rows="4">{env.variables_text}</textarea>
495
+ </div>
496
+ <div class="qa-form-row qa-mb-4">
497
+ <div class="qa-form-group">
498
+ <label class="qa-label" for="port_{env.id}">Port</label>
499
+ <input id="port_{env.id}" type="number" name="port" class="qa-input" value="{env.port or ''}" />
500
+ </div>
501
+ <div class="qa-form-group">
502
+ <label class="qa-label" for="branch_{env.id}">Branch</label>
503
+ <input id="branch_{env.id}" type="text" name="branch" class="qa-input" value="{env.branch or ''}" />
504
+ </div>
505
+ </div>
506
+ <button type="submit" class="qa-btn qa-btn-secondary qa-btn-sm">Save Environment</button>
507
+ </form>
508
+ </div>
509
+ </q:loop>
510
+ </div>
511
+
512
+ <!-- ============================ Runtime -->
513
+ <div class="qa-panel-runtime">
514
+ <div class="qa-card qa-mb-6">
515
+ <div class="qa-card-header">
516
+ <div class="qa-card-title">Server</div>
517
+ </div>
518
+ <div class="qa-card-body">
519
+ <q:if condition="not servidor_result.success">
520
+ <div class="qa-text-sm qa-text-muted">{servidor_result.error.message}</div>
521
+ </q:if>
522
+ <q:if condition="servidor_result.success">
523
+ <div class="qa-flex qa-items-center qa-justify-between qa-mb-4"><span class="qa-text-sm qa-text-muted">Port</span><span class="qa-font-mono qa-text-sm">{app.port or 8080}</span></div>
524
+ <q:if condition="servidor.running">
525
+ <div class="qa-flex qa-items-center qa-justify-between qa-mb-4"><span class="qa-text-sm qa-text-muted">PID</span><span class="qa-font-mono qa-text-sm">{join(servidor.pids, ', ')}</span></div>
526
+ <form method="POST" action="/admin/app/{app.name}" onsubmit="return confirm('Stop the running server?');">
527
+ <input type="hidden" name="action" value="stopServer" />
528
+ <button type="submit" class="qa-btn qa-btn-danger">Stop Server</button>
529
+ </form>
530
+ <q:else>
531
+ <form method="POST" action="/admin/app/{app.name}">
532
+ <input type="hidden" name="action" value="startServer" />
533
+ <button type="submit" class="qa-btn qa-btn-success">Start Server</button>
534
+ </form>
535
+ </q:else>
536
+ </q:if>
537
+ </q:if>
538
+ </div>
539
+ </div>
540
+ <div class="qa-card">
541
+ <div class="qa-card-header"><div class="qa-card-title">Log</div><div class="qa-card-subtitle">last 50 lines</div></div>
542
+ <div class="qa-card-body">
543
+ <q:if condition="log.text">
544
+ <pre class="qa-log-viewer">{log.text}</pre>
545
+ <q:else>
546
+ <div class="qa-text-sm qa-text-muted">Nothing logged yet. Start the server to see its output here.</div>
547
+ </q:else>
548
+ </q:if>
549
+ </div>
550
+ </div>
551
+ </div>
552
+
553
+ </div>
554
+ </q:if>
555
+
556
+ <script src="/static/quantum-admin-connectors.js"></script>
557
+ </AdminShell>
558
+ </q:component>