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.
- quantum/__init__.py +1 -0
- quantum/cli/__init__.py +2 -0
- quantum/cli/admin.py +131 -0
- quantum/cli/check.py +223 -0
- quantum/cli/commands/__init__.py +15 -0
- quantum/cli/commands/build.py +414 -0
- quantum/cli/commands/dev.py +185 -0
- quantum/cli/commands/docs.py +329 -0
- quantum/cli/commands/lint.py +523 -0
- quantum/cli/commands/migrate.py +548 -0
- quantum/cli/commands/new.py +622 -0
- quantum/cli/commands/serve.py +190 -0
- quantum/cli/commands/test.py +193 -0
- quantum/cli/deploy.py +810 -0
- quantum/cli/hot_reload.py +951 -0
- quantum/cli/jobs.py +356 -0
- quantum/cli/mq.py +582 -0
- quantum/cli/pkg.py +390 -0
- quantum/cli/runner.py +565 -0
- quantum/cli/server_process.py +159 -0
- quantum/cli/utils.py +334 -0
- quantum/compiler/__init__.py +30 -0
- quantum/compiler/base_generator.py +367 -0
- quantum/compiler/cli.py +295 -0
- quantum/compiler/expression_transformer.py +444 -0
- quantum/compiler/javascript/__init__.py +10 -0
- quantum/compiler/javascript/generator.py +659 -0
- quantum/compiler/optimizer.py +270 -0
- quantum/compiler/python/__init__.py +10 -0
- quantum/compiler/python/generator.py +883 -0
- quantum/compiler/python/runtime.py +863 -0
- quantum/compiler/transpiler.py +330 -0
- quantum/core/__init__.py +3 -0
- quantum/core/ast_nodes.py +2619 -0
- quantum/core/config_env.py +54 -0
- quantum/core/docs_links.py +8 -0
- quantum/core/expression_diagnostics.py +87 -0
- quantum/core/expression_stdlib.py +208 -0
- quantum/core/expressions.py +677 -0
- quantum/core/features/agents/src/__init__.py +30 -0
- quantum/core/features/agents/src/ast_node.py +540 -0
- quantum/core/features/conditionals/src/__init__.py +8 -0
- quantum/core/features/conditionals/src/ast_node.py +68 -0
- quantum/core/features/data_import/src/__init__.py +0 -0
- quantum/core/features/data_import/src/ast_node.py +292 -0
- quantum/core/features/data_import/src/runtime.py +533 -0
- quantum/core/features/dump/src/__init__.py +12 -0
- quantum/core/features/dump/src/ast_node.py +106 -0
- quantum/core/features/dump/src/parser.py +61 -0
- quantum/core/features/dump/src/runtime.py +246 -0
- quantum/core/features/functions/src/__init__.py +8 -0
- quantum/core/features/functions/src/ast_node.py +149 -0
- quantum/core/features/game_engine_2d/src/__init__.py +19 -0
- quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
- quantum/core/features/game_engine_2d/src/parser.py +983 -0
- quantum/core/features/invocation/src/__init__.py +0 -0
- quantum/core/features/invocation/src/ast_node.py +147 -0
- quantum/core/features/invocation/src/runtime.py +332 -0
- quantum/core/features/knowledge_base/src/__init__.py +6 -0
- quantum/core/features/knowledge_base/src/ast_node.py +113 -0
- quantum/core/features/knowledge_base/src/parser.py +82 -0
- quantum/core/features/logging/src/__init__.py +12 -0
- quantum/core/features/logging/src/ast_node.py +111 -0
- quantum/core/features/logging/src/parser.py +50 -0
- quantum/core/features/logging/src/runtime.py +190 -0
- quantum/core/features/loops/src/__init__.py +8 -0
- quantum/core/features/loops/src/ast_node.py +60 -0
- quantum/core/features/query/src/__init__.py +0 -0
- quantum/core/features/query/src/query_validators.py +20 -0
- quantum/core/features/state_management/src/__init__.py +11 -0
- quantum/core/features/state_management/src/ast_node.py +121 -0
- quantum/core/features/terminal_engine/src/__init__.py +21 -0
- quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
- quantum/core/features/terminal_engine/src/parser.py +361 -0
- quantum/core/features/testing_engine/src/__init__.py +41 -0
- quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
- quantum/core/features/testing_engine/src/parser.py +604 -0
- quantum/core/features/theming/src/__init__.py +48 -0
- quantum/core/features/theming/src/ast_node.py +137 -0
- quantum/core/features/theming/src/presets.py +405 -0
- quantum/core/features/ui_engine/src/__init__.py +20 -0
- quantum/core/features/ui_engine/src/ast_nodes.py +1913 -0
- quantum/core/features/ui_engine/src/parser.py +1192 -0
- quantum/core/features/websocket/src/__init__.py +24 -0
- quantum/core/features/websocket/src/ast_node.py +247 -0
- quantum/core/guards.py +53 -0
- quantum/core/html_compat.py +299 -0
- quantum/core/parser.py +1551 -0
- quantum/core/parser_registry.py +213 -0
- quantum/core/parsers/__init__.py +76 -0
- quantum/core/parsers/ai/__init__.py +12 -0
- quantum/core/parsers/ai/agent_parser.py +106 -0
- quantum/core/parsers/ai/knowledge_parser.py +86 -0
- quantum/core/parsers/ai/llm_parser.py +78 -0
- quantum/core/parsers/ai/team_parser.py +88 -0
- quantum/core/parsers/base.py +322 -0
- quantum/core/parsers/composition/__init__.py +10 -0
- quantum/core/parsers/composition/import_parser.py +50 -0
- quantum/core/parsers/composition/slot_parser.py +48 -0
- quantum/core/parsers/control_flow/__init__.py +11 -0
- quantum/core/parsers/control_flow/if_parser.py +71 -0
- quantum/core/parsers/control_flow/loop_parser.py +105 -0
- quantum/core/parsers/control_flow/set_parser.py +93 -0
- quantum/core/parsers/data/__init__.py +12 -0
- quantum/core/parsers/data/data_parser.py +229 -0
- quantum/core/parsers/data/invoke_parser.py +133 -0
- quantum/core/parsers/data/query_parser.py +213 -0
- quantum/core/parsers/data/transaction_parser.py +86 -0
- quantum/core/parsers/events/__init__.py +9 -0
- quantum/core/parsers/events/dispatch_event_parser.py +44 -0
- quantum/core/parsers/forms/__init__.py +11 -0
- quantum/core/parsers/forms/action_parser.py +79 -0
- quantum/core/parsers/forms/flash_parser.py +34 -0
- quantum/core/parsers/forms/redirect_parser.py +33 -0
- quantum/core/parsers/functions/__init__.py +11 -0
- quantum/core/parsers/functions/function_parser.py +98 -0
- quantum/core/parsers/functions/param_parser.py +73 -0
- quantum/core/parsers/functions/return_parser.py +31 -0
- quantum/core/parsers/html/__init__.py +10 -0
- quantum/core/parsers/html/component_call_parser.py +130 -0
- quantum/core/parsers/html/html_parser.py +115 -0
- quantum/core/parsers/jobs/__init__.py +11 -0
- quantum/core/parsers/jobs/job_parser.py +71 -0
- quantum/core/parsers/jobs/schedule_parser.py +62 -0
- quantum/core/parsers/jobs/thread_parser.py +57 -0
- quantum/core/parsers/messaging/__init__.py +17 -0
- quantum/core/parsers/messaging/message_ack_parser.py +30 -0
- quantum/core/parsers/messaging/message_nack_parser.py +30 -0
- quantum/core/parsers/messaging/message_parser.py +114 -0
- quantum/core/parsers/messaging/queue_parser.py +61 -0
- quantum/core/parsers/messaging/websocket_parser.py +121 -0
- quantum/core/parsers/routing/__init__.py +9 -0
- quantum/core/parsers/routing/route_parser.py +41 -0
- quantum/core/parsers/scripting/__init__.py +12 -0
- quantum/core/parsers/scripting/pyclass_parser.py +62 -0
- quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
- quantum/core/parsers/scripting/pyimport_parser.py +52 -0
- quantum/core/parsers/scripting/python_parser.py +49 -0
- quantum/core/parsers/services/__init__.py +12 -0
- quantum/core/parsers/services/dump_parser.py +52 -0
- quantum/core/parsers/services/file_parser.py +48 -0
- quantum/core/parsers/services/log_parser.py +46 -0
- quantum/core/parsers/services/mail_parser.py +65 -0
- quantum/core/tiers.py +164 -0
- quantum/core/xml_lines.py +81 -0
- quantum/packages/__init__.py +28 -0
- quantum/packages/manager.py +413 -0
- quantum/packages/manifest.py +351 -0
- quantum/packages/registry.py +399 -0
- quantum/packages/resolver.py +336 -0
- quantum/plugins/__init__.py +33 -0
- quantum/plugins/hooks.py +329 -0
- quantum/plugins/loader.py +479 -0
- quantum/plugins/manifest.py +336 -0
- quantum/plugins/registry.py +371 -0
- quantum/runtime/__init__.py +25 -0
- quantum/runtime/action_handler.py +532 -0
- quantum/runtime/adapters/__init__.py +88 -0
- quantum/runtime/adapters/memory_adapter.py +690 -0
- quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
- quantum/runtime/adapters/redis_adapter.py +582 -0
- quantum/runtime/adapters/sqlite_adapter.py +414 -0
- quantum/runtime/agent_service.py +1133 -0
- quantum/runtime/ast_cache.py +506 -0
- quantum/runtime/auth_service.py +267 -0
- quantum/runtime/component.py +1048 -0
- quantum/runtime/component_composer.py +133 -0
- quantum/runtime/component_resolver.py +174 -0
- quantum/runtime/conditions.py +54 -0
- quantum/runtime/database_service.py +595 -0
- quantum/runtime/db_schema.py +100 -0
- quantum/runtime/dev_panel.py +178 -0
- quantum/runtime/email_service.py +162 -0
- quantum/runtime/error_handler.py +295 -0
- quantum/runtime/execution_context.py +283 -0
- quantum/runtime/executor_registry.py +178 -0
- quantum/runtime/executors/__init__.py +71 -0
- quantum/runtime/executors/ai/__init__.py +12 -0
- quantum/runtime/executors/ai/agent_executor.py +217 -0
- quantum/runtime/executors/ai/knowledge_executor.py +114 -0
- quantum/runtime/executors/ai/llm_executor.py +153 -0
- quantum/runtime/executors/ai/team_executor.py +171 -0
- quantum/runtime/executors/base.py +264 -0
- quantum/runtime/executors/control_flow/__init__.py +12 -0
- quantum/runtime/executors/control_flow/if_executor.py +98 -0
- quantum/runtime/executors/control_flow/loop_executor.py +308 -0
- quantum/runtime/executors/control_flow/redirect_executor.py +43 -0
- quantum/runtime/executors/control_flow/set_executor.py +464 -0
- quantum/runtime/executors/data/__init__.py +12 -0
- quantum/runtime/executors/data/data_executor.py +156 -0
- quantum/runtime/executors/data/invoke_executor.py +228 -0
- quantum/runtime/executors/data/query_executor.py +290 -0
- quantum/runtime/executors/data/transaction_executor.py +91 -0
- quantum/runtime/executors/jobs/__init__.py +11 -0
- quantum/runtime/executors/jobs/job_executor.py +190 -0
- quantum/runtime/executors/jobs/schedule_executor.py +132 -0
- quantum/runtime/executors/jobs/thread_executor.py +127 -0
- quantum/runtime/executors/messaging/__init__.py +17 -0
- quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
- quantum/runtime/executors/messaging/message_executor.py +174 -0
- quantum/runtime/executors/messaging/queue_executor.py +103 -0
- quantum/runtime/executors/messaging/websocket_executor.py +197 -0
- quantum/runtime/executors/scripting/__init__.py +11 -0
- quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
- quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
- quantum/runtime/executors/scripting/python_executor.py +249 -0
- quantum/runtime/executors/services/__init__.py +12 -0
- quantum/runtime/executors/services/dump_executor.py +72 -0
- quantum/runtime/executors/services/file_executor.py +89 -0
- quantum/runtime/executors/services/log_executor.py +77 -0
- quantum/runtime/executors/services/mail_executor.py +81 -0
- quantum/runtime/expression_cache.py +498 -0
- quantum/runtime/file_upload_service.py +326 -0
- quantum/runtime/function_registry.py +118 -0
- quantum/runtime/game_builder.py +166 -0
- quantum/runtime/game_code_generator.py +2371 -0
- quantum/runtime/game_templates.py +2006 -0
- quantum/runtime/godot_code_generator.py +4681 -0
- quantum/runtime/godot_templates.py +1449 -0
- quantum/runtime/job_executor.py +1599 -0
- quantum/runtime/knowledge_service.py +546 -0
- quantum/runtime/llm_cache.py +100 -0
- quantum/runtime/llm_providers.py +704 -0
- quantum/runtime/llm_service.py +287 -0
- quantum/runtime/local_server.py +28 -0
- quantum/runtime/logging_setup.py +141 -0
- quantum/runtime/message_broker.py +364 -0
- quantum/runtime/message_queue_service.py +571 -0
- quantum/runtime/param_validation.py +218 -0
- quantum/runtime/pypy_compat.py +317 -0
- quantum/runtime/python_bridge.py +698 -0
- quantum/runtime/query_validators.py +304 -0
- quantum/runtime/renderer.py +734 -0
- quantum/runtime/service_container.py +470 -0
- quantum/runtime/table_params.py +163 -0
- quantum/runtime/terminal_builder.py +76 -0
- quantum/runtime/terminal_code_generator.py +607 -0
- quantum/runtime/terminal_templates.py +243 -0
- quantum/runtime/testing_builder.py +77 -0
- quantum/runtime/testing_code_generator.py +833 -0
- quantum/runtime/testing_templates.py +85 -0
- quantum/runtime/ui_builder.py +222 -0
- quantum/runtime/ui_console.py +405 -0
- quantum/runtime/ui_desktop.py +56 -0
- quantum/runtime/ui_form_rules.py +193 -0
- quantum/runtime/ui_html_adapter.py +2567 -0
- quantum/runtime/ui_html_templates.py +2106 -0
- quantum/runtime/ui_mobile_adapter.py +1832 -0
- quantum/runtime/ui_mobile_templates.py +1003 -0
- quantum/runtime/ui_page_renderer.py +392 -0
- quantum/runtime/ui_pager.py +73 -0
- quantum/runtime/ui_table_edit.py +139 -0
- quantum/runtime/ui_textual_adapter.py +1866 -0
- quantum/runtime/ui_textual_templates.py +45 -0
- quantum/runtime/ui_tokens.py +465 -0
- quantum/runtime/ui_validator.py +365 -0
- quantum/runtime/ui_view.py +221 -0
- quantum/runtime/validators.py +256 -0
- quantum/runtime/web_server.py +2131 -0
- quantum/runtime/websocket_adapter.py +501 -0
- quantum/runtime/websocket_service.py +585 -0
- quantum/runtime/websocket_transport.py +289 -0
- quantum/runtime/wsgi.py +101 -0
- quantum/services.py +109 -0
- quantum/utils/__init__.py +1 -0
- quantum_admin/__init__.py +4 -0
- quantum_admin/assets/quantum-admin-connectors.js +123 -0
- quantum_admin/assets/quantum-admin.css +2058 -0
- quantum_admin/assets/quantum-source-highlight.js +106 -0
- quantum_admin/backend/__init__.py +4 -0
- quantum_admin/backend/audit_service.py +346 -0
- quantum_admin/backend/auth_service.py +394 -0
- quantum_admin/backend/component_discovery.py +766 -0
- quantum_admin/backend/config_generator.py +586 -0
- quantum_admin/backend/connector_service.py +1016 -0
- quantum_admin/backend/crud.py +557 -0
- quantum_admin/backend/database.py +226 -0
- quantum_admin/backend/db_setup_service.py +193 -0
- quantum_admin/backend/deploy_service.py +698 -0
- quantum_admin/backend/docker_service.py +422 -0
- quantum_admin/backend/environment_service.py +418 -0
- quantum_admin/backend/git_service.py +370 -0
- quantum_admin/backend/health_service.py +390 -0
- quantum_admin/backend/job_service.py +347 -0
- quantum_admin/backend/log_service.py +331 -0
- quantum_admin/backend/main.py +14426 -0
- quantum_admin/backend/models.py +986 -0
- quantum_admin/backend/pipeline_service.py +812 -0
- quantum_admin/backend/resource_manager.py +1118 -0
- quantum_admin/backend/schemas.py +419 -0
- quantum_admin/backend/secret_manager.py +277 -0
- quantum_admin/backend/settings_service.py +488 -0
- quantum_admin/backend/template_service.py +710 -0
- quantum_admin/backend/test_execution_service.py +364 -0
- quantum_admin/backend/test_generator.py +782 -0
- quantum_admin/backend/webhook_service.py +454 -0
- quantum_admin/backend/websocket_manager.py +360 -0
- quantum_admin/components/admin/_layout/AdminShell.q +121 -0
- quantum_admin/components/admin/agents.q +107 -0
- quantum_admin/components/admin/app/[name].q +558 -0
- quantum_admin/components/admin/applications.q +160 -0
- quantum_admin/components/admin/component/[...path].q +205 -0
- quantum_admin/components/admin/components.q +59 -0
- quantum_admin/components/admin/connectors.q +283 -0
- quantum_admin/components/admin/dashboard.q +86 -0
- quantum_admin/components/admin/database.q +95 -0
- quantum_admin/components/admin/features.q +69 -0
- quantum_admin/components/admin/index.q +12 -0
- quantum_admin/components/admin/jobs.q +75 -0
- quantum_admin/components/admin/login.q +65 -0
- quantum_admin/components/admin/logout.q +11 -0
- quantum_admin/components/admin/settings.q +174 -0
- quantum_admin/components/admin/source.q +45 -0
- quantum_admin/components/admin/tests.q +73 -0
- quantum_admin/run.py +13 -0
- quantum_admin/services/__init__.py +16 -0
- quantum_admin/services/_base.py +84 -0
- quantum_admin/services/_component_test_generator.py +205 -0
- quantum_admin/services/_yaml_editor.py +82 -0
- quantum_admin/services/apps.py +330 -0
- quantum_admin/services/auth.py +61 -0
- quantum_admin/services/components.py +210 -0
- quantum_admin/services/connectors.py +164 -0
- quantum_admin/services/projects.py +189 -0
- quantum_admin/services/repository.py +223 -0
- quantum_admin/services/settings.py +150 -0
- quantum_admin/services/yaml_import.py +118 -0
- quantum_framework-0.18.0.dist-info/METADATA +281 -0
- quantum_framework-0.18.0.dist-info/RECORD +333 -0
- quantum_framework-0.18.0.dist-info/WHEEL +5 -0
- quantum_framework-0.18.0.dist-info/entry_points.txt +2 -0
- quantum_framework-0.18.0.dist-info/licenses/LICENSE +21 -0
- quantum_framework-0.18.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Config Generator - Generate configuration files for projects
|
|
3
|
+
|
|
4
|
+
Generates:
|
|
5
|
+
- .env files
|
|
6
|
+
- docker-compose.yml
|
|
7
|
+
- nginx.conf
|
|
8
|
+
- systemd service files
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import json
|
|
13
|
+
import logging
|
|
14
|
+
from typing import Dict, List, Optional, Any
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from sqlalchemy.orm import Session
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
from .models import Project, Environment, Datasource, PortAllocation, Secret, ServiceRegistry
|
|
20
|
+
from .resource_manager import get_resource_manager
|
|
21
|
+
except ImportError:
|
|
22
|
+
from models import Project, Environment, Datasource, PortAllocation, Secret, ServiceRegistry
|
|
23
|
+
from resource_manager import get_resource_manager
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ConfigGenerator:
|
|
29
|
+
"""Generate configuration files for projects"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, db: Session):
|
|
32
|
+
self.db = db
|
|
33
|
+
self.resource_manager = get_resource_manager(db)
|
|
34
|
+
|
|
35
|
+
def generate_env_file(
|
|
36
|
+
self,
|
|
37
|
+
project_id: int,
|
|
38
|
+
environment_id: int = None,
|
|
39
|
+
include_secrets: bool = True
|
|
40
|
+
) -> str:
|
|
41
|
+
"""
|
|
42
|
+
Generate a .env file content for a project
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
project_id: Project ID
|
|
46
|
+
environment_id: Optional environment ID for environment-specific vars
|
|
47
|
+
include_secrets: Include decrypted secret values
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
String content of .env file
|
|
51
|
+
"""
|
|
52
|
+
project = self.db.query(Project).filter(Project.id == project_id).first()
|
|
53
|
+
if not project:
|
|
54
|
+
raise ValueError(f"Project {project_id} not found")
|
|
55
|
+
|
|
56
|
+
lines = [
|
|
57
|
+
f"# Generated by Quantum Admin - {datetime.now().isoformat()}",
|
|
58
|
+
f"# Project: {project.name}",
|
|
59
|
+
""
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
# Get environment if specified
|
|
63
|
+
environment = None
|
|
64
|
+
if environment_id:
|
|
65
|
+
environment = self.db.query(Environment).filter(
|
|
66
|
+
Environment.id == environment_id
|
|
67
|
+
).first()
|
|
68
|
+
if environment:
|
|
69
|
+
lines.append(f"# Environment: {environment.name}")
|
|
70
|
+
lines.append("")
|
|
71
|
+
|
|
72
|
+
# Add project-level config
|
|
73
|
+
lines.append("# Application")
|
|
74
|
+
lines.append(f"APP_NAME={project.name}")
|
|
75
|
+
lines.append(f"APP_ENV={'production' if environment and environment.name == 'production' else 'development'}")
|
|
76
|
+
lines.append("")
|
|
77
|
+
|
|
78
|
+
# Get port allocations
|
|
79
|
+
ports = self.db.query(PortAllocation).filter(
|
|
80
|
+
PortAllocation.project_id == project_id,
|
|
81
|
+
PortAllocation.status == 'allocated'
|
|
82
|
+
)
|
|
83
|
+
if environment_id:
|
|
84
|
+
ports = ports.filter(PortAllocation.environment_id == environment_id)
|
|
85
|
+
ports = ports.all()
|
|
86
|
+
|
|
87
|
+
if ports:
|
|
88
|
+
lines.append("# Ports")
|
|
89
|
+
for port in ports:
|
|
90
|
+
var_name = f"{port.service_name.upper().replace('-', '_')}_PORT"
|
|
91
|
+
lines.append(f"{var_name}={port.port}")
|
|
92
|
+
lines.append("")
|
|
93
|
+
|
|
94
|
+
# Get secrets
|
|
95
|
+
if include_secrets:
|
|
96
|
+
secrets = self.resource_manager.secrets.list_secrets(
|
|
97
|
+
project_id=project_id,
|
|
98
|
+
environment_id=environment_id
|
|
99
|
+
)
|
|
100
|
+
if secrets:
|
|
101
|
+
lines.append("# Secrets")
|
|
102
|
+
for secret in secrets:
|
|
103
|
+
value = self.resource_manager.secrets.get(
|
|
104
|
+
secret.key,
|
|
105
|
+
project_id=project_id,
|
|
106
|
+
environment_id=environment_id
|
|
107
|
+
)
|
|
108
|
+
if value:
|
|
109
|
+
lines.append(f"{secret.key}={value}")
|
|
110
|
+
lines.append("")
|
|
111
|
+
|
|
112
|
+
# Get datasources
|
|
113
|
+
datasources = self.db.query(Datasource).filter(
|
|
114
|
+
Datasource.project_id == project_id
|
|
115
|
+
).all()
|
|
116
|
+
|
|
117
|
+
if datasources:
|
|
118
|
+
lines.append("# Databases")
|
|
119
|
+
for ds in datasources:
|
|
120
|
+
prefix = ds.name.upper().replace('-', '_')
|
|
121
|
+
lines.append(f"{prefix}_HOST={ds.host or 'localhost'}")
|
|
122
|
+
lines.append(f"{prefix}_PORT={ds.port or 5432}")
|
|
123
|
+
lines.append(f"{prefix}_NAME={ds.database_name or ds.name}")
|
|
124
|
+
if ds.username:
|
|
125
|
+
lines.append(f"{prefix}_USER={ds.username}")
|
|
126
|
+
# Don't include encrypted password - use secrets instead
|
|
127
|
+
lines.append("")
|
|
128
|
+
|
|
129
|
+
# Get services
|
|
130
|
+
services = self.resource_manager.services.list_services(
|
|
131
|
+
project_id=project_id,
|
|
132
|
+
environment_id=environment_id
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if services:
|
|
136
|
+
lines.append("# Services")
|
|
137
|
+
for svc in services:
|
|
138
|
+
prefix = svc.name.upper().replace('-', '_')
|
|
139
|
+
lines.append(f"{prefix}_URL={svc.protocol}://{svc.host}:{svc.port}")
|
|
140
|
+
lines.append("")
|
|
141
|
+
|
|
142
|
+
return "\n".join(lines)
|
|
143
|
+
|
|
144
|
+
def generate_docker_compose(
|
|
145
|
+
self,
|
|
146
|
+
project_id: int,
|
|
147
|
+
environment_id: int = None,
|
|
148
|
+
include_databases: bool = True,
|
|
149
|
+
include_cache: bool = True
|
|
150
|
+
) -> str:
|
|
151
|
+
"""
|
|
152
|
+
Generate a docker-compose.yml content for a project
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
project_id: Project ID
|
|
156
|
+
environment_id: Optional environment ID
|
|
157
|
+
include_databases: Include database containers
|
|
158
|
+
include_cache: Include Redis cache
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
String content of docker-compose.yml
|
|
162
|
+
"""
|
|
163
|
+
project = self.db.query(Project).filter(Project.id == project_id).first()
|
|
164
|
+
if not project:
|
|
165
|
+
raise ValueError(f"Project {project_id} not found")
|
|
166
|
+
|
|
167
|
+
# Get environment for naming
|
|
168
|
+
env_name = "dev"
|
|
169
|
+
environment = None
|
|
170
|
+
if environment_id:
|
|
171
|
+
environment = self.db.query(Environment).filter(
|
|
172
|
+
Environment.id == environment_id
|
|
173
|
+
).first()
|
|
174
|
+
if environment:
|
|
175
|
+
env_name = environment.name
|
|
176
|
+
|
|
177
|
+
# Get port allocations
|
|
178
|
+
ports = self.db.query(PortAllocation).filter(
|
|
179
|
+
PortAllocation.project_id == project_id,
|
|
180
|
+
PortAllocation.status == 'allocated'
|
|
181
|
+
)
|
|
182
|
+
if environment_id:
|
|
183
|
+
ports = ports.filter(PortAllocation.environment_id == environment_id)
|
|
184
|
+
ports_list = ports.all()
|
|
185
|
+
|
|
186
|
+
# Find app port
|
|
187
|
+
app_port = 8000
|
|
188
|
+
for p in ports_list:
|
|
189
|
+
if p.port_type == 'app':
|
|
190
|
+
app_port = p.port
|
|
191
|
+
break
|
|
192
|
+
|
|
193
|
+
# Build compose content
|
|
194
|
+
compose = {
|
|
195
|
+
"version": "3.8",
|
|
196
|
+
"services": {},
|
|
197
|
+
"networks": {
|
|
198
|
+
"quantum-network": {
|
|
199
|
+
"driver": "bridge"
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
"volumes": {}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
# Main app service
|
|
206
|
+
service_name = f"{project.name.lower().replace(' ', '-')}-app"
|
|
207
|
+
compose["services"][service_name] = {
|
|
208
|
+
"build": ".",
|
|
209
|
+
"container_name": f"{project.name.lower()}-{env_name}",
|
|
210
|
+
"ports": [f"{app_port}:8000"],
|
|
211
|
+
"environment": [
|
|
212
|
+
f"APP_NAME={project.name}",
|
|
213
|
+
f"APP_ENV={env_name}",
|
|
214
|
+
],
|
|
215
|
+
"networks": ["quantum-network"],
|
|
216
|
+
"restart": "unless-stopped"
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
# Get datasources
|
|
220
|
+
if include_databases:
|
|
221
|
+
datasources = self.db.query(Datasource).filter(
|
|
222
|
+
Datasource.project_id == project_id
|
|
223
|
+
).all()
|
|
224
|
+
|
|
225
|
+
for ds in datasources:
|
|
226
|
+
ds_name = ds.name.lower().replace(' ', '-')
|
|
227
|
+
ds_port = ds.port or self._get_default_db_port(ds.type)
|
|
228
|
+
|
|
229
|
+
if ds.type == 'postgres':
|
|
230
|
+
compose["services"][f"{ds_name}-db"] = {
|
|
231
|
+
"image": "postgres:15-alpine",
|
|
232
|
+
"container_name": f"{ds_name}-{env_name}",
|
|
233
|
+
"environment": [
|
|
234
|
+
f"POSTGRES_DB={ds.database_name or ds.name}",
|
|
235
|
+
f"POSTGRES_USER={ds.username or 'quantum'}",
|
|
236
|
+
"POSTGRES_PASSWORD=${DB_PASSWORD:-quantum123}"
|
|
237
|
+
],
|
|
238
|
+
"ports": [f"{ds_port}:5432"],
|
|
239
|
+
"volumes": [f"{ds_name}-data:/var/lib/postgresql/data"],
|
|
240
|
+
"networks": ["quantum-network"],
|
|
241
|
+
"restart": "unless-stopped"
|
|
242
|
+
}
|
|
243
|
+
compose["volumes"][f"{ds_name}-data"] = None
|
|
244
|
+
|
|
245
|
+
elif ds.type == 'mysql':
|
|
246
|
+
compose["services"][f"{ds_name}-db"] = {
|
|
247
|
+
"image": "mysql:8",
|
|
248
|
+
"container_name": f"{ds_name}-{env_name}",
|
|
249
|
+
"environment": [
|
|
250
|
+
f"MYSQL_DATABASE={ds.database_name or ds.name}",
|
|
251
|
+
f"MYSQL_USER={ds.username or 'quantum'}",
|
|
252
|
+
"MYSQL_PASSWORD=${DB_PASSWORD:-quantum123}",
|
|
253
|
+
"MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD:-root123}"
|
|
254
|
+
],
|
|
255
|
+
"ports": [f"{ds_port}:3306"],
|
|
256
|
+
"volumes": [f"{ds_name}-data:/var/lib/mysql"],
|
|
257
|
+
"networks": ["quantum-network"],
|
|
258
|
+
"restart": "unless-stopped"
|
|
259
|
+
}
|
|
260
|
+
compose["volumes"][f"{ds_name}-data"] = None
|
|
261
|
+
|
|
262
|
+
elif ds.type == 'mongodb':
|
|
263
|
+
compose["services"][f"{ds_name}-db"] = {
|
|
264
|
+
"image": "mongo:6",
|
|
265
|
+
"container_name": f"{ds_name}-{env_name}",
|
|
266
|
+
"environment": [
|
|
267
|
+
f"MONGO_INITDB_DATABASE={ds.database_name or ds.name}",
|
|
268
|
+
],
|
|
269
|
+
"ports": [f"{ds_port}:27017"],
|
|
270
|
+
"volumes": [f"{ds_name}-data:/data/db"],
|
|
271
|
+
"networks": ["quantum-network"],
|
|
272
|
+
"restart": "unless-stopped"
|
|
273
|
+
}
|
|
274
|
+
compose["volumes"][f"{ds_name}-data"] = None
|
|
275
|
+
|
|
276
|
+
# Add Redis cache
|
|
277
|
+
if include_cache:
|
|
278
|
+
cache_port = 6379
|
|
279
|
+
for p in ports_list:
|
|
280
|
+
if p.port_type == 'cache':
|
|
281
|
+
cache_port = p.port
|
|
282
|
+
break
|
|
283
|
+
|
|
284
|
+
compose["services"]["redis"] = {
|
|
285
|
+
"image": "redis:7-alpine",
|
|
286
|
+
"container_name": f"{project.name.lower()}-redis-{env_name}",
|
|
287
|
+
"ports": [f"{cache_port}:6379"],
|
|
288
|
+
"networks": ["quantum-network"],
|
|
289
|
+
"restart": "unless-stopped"
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
# Convert to YAML format
|
|
293
|
+
return self._dict_to_yaml(compose)
|
|
294
|
+
|
|
295
|
+
def generate_nginx_conf(
|
|
296
|
+
self,
|
|
297
|
+
project_id: int,
|
|
298
|
+
environment_id: int = None,
|
|
299
|
+
domain: str = None,
|
|
300
|
+
ssl: bool = True
|
|
301
|
+
) -> str:
|
|
302
|
+
"""
|
|
303
|
+
Generate nginx configuration for a project
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
project_id: Project ID
|
|
307
|
+
environment_id: Optional environment ID
|
|
308
|
+
domain: Domain name (defaults to localhost)
|
|
309
|
+
ssl: Enable SSL configuration
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
String content of nginx.conf
|
|
313
|
+
"""
|
|
314
|
+
project = self.db.query(Project).filter(Project.id == project_id).first()
|
|
315
|
+
if not project:
|
|
316
|
+
raise ValueError(f"Project {project_id} not found")
|
|
317
|
+
|
|
318
|
+
# Get environment
|
|
319
|
+
env_name = "dev"
|
|
320
|
+
environment = None
|
|
321
|
+
if environment_id:
|
|
322
|
+
environment = self.db.query(Environment).filter(
|
|
323
|
+
Environment.id == environment_id
|
|
324
|
+
).first()
|
|
325
|
+
if environment:
|
|
326
|
+
env_name = environment.name
|
|
327
|
+
|
|
328
|
+
# Get app port
|
|
329
|
+
ports = self.db.query(PortAllocation).filter(
|
|
330
|
+
PortAllocation.project_id == project_id,
|
|
331
|
+
PortAllocation.port_type == 'app',
|
|
332
|
+
PortAllocation.status == 'allocated'
|
|
333
|
+
)
|
|
334
|
+
if environment_id:
|
|
335
|
+
ports = ports.filter(PortAllocation.environment_id == environment_id)
|
|
336
|
+
port_alloc = ports.first()
|
|
337
|
+
app_port = port_alloc.port if port_alloc else 8000
|
|
338
|
+
|
|
339
|
+
# Determine domain
|
|
340
|
+
if not domain:
|
|
341
|
+
if environment and hasattr(environment, 'health_url') and environment.health_url:
|
|
342
|
+
# Extract domain from health URL
|
|
343
|
+
from urllib.parse import urlparse
|
|
344
|
+
parsed = urlparse(environment.health_url)
|
|
345
|
+
domain = parsed.netloc.split(':')[0]
|
|
346
|
+
else:
|
|
347
|
+
domain = "localhost"
|
|
348
|
+
|
|
349
|
+
upstream_name = f"{project.name.lower().replace(' ', '_')}_upstream"
|
|
350
|
+
|
|
351
|
+
conf_lines = [
|
|
352
|
+
f"# Nginx configuration for {project.name} ({env_name})",
|
|
353
|
+
f"# Generated by Quantum Admin - {datetime.now().isoformat()}",
|
|
354
|
+
"",
|
|
355
|
+
f"upstream {upstream_name} {{",
|
|
356
|
+
f" server 127.0.0.1:{app_port};",
|
|
357
|
+
" keepalive 32;",
|
|
358
|
+
"}",
|
|
359
|
+
"",
|
|
360
|
+
]
|
|
361
|
+
|
|
362
|
+
# HTTP to HTTPS redirect if SSL
|
|
363
|
+
if ssl:
|
|
364
|
+
conf_lines.extend([
|
|
365
|
+
"server {",
|
|
366
|
+
" listen 80;",
|
|
367
|
+
f" server_name {domain};",
|
|
368
|
+
"",
|
|
369
|
+
" location / {",
|
|
370
|
+
" return 301 https://$host$request_uri;",
|
|
371
|
+
" }",
|
|
372
|
+
"}",
|
|
373
|
+
"",
|
|
374
|
+
])
|
|
375
|
+
|
|
376
|
+
# Main server block
|
|
377
|
+
if ssl:
|
|
378
|
+
conf_lines.extend([
|
|
379
|
+
"server {",
|
|
380
|
+
" listen 443 ssl http2;",
|
|
381
|
+
f" server_name {domain};",
|
|
382
|
+
"",
|
|
383
|
+
f" ssl_certificate /etc/letsencrypt/live/{domain}/fullchain.pem;",
|
|
384
|
+
f" ssl_certificate_key /etc/letsencrypt/live/{domain}/privkey.pem;",
|
|
385
|
+
" ssl_protocols TLSv1.2 TLSv1.3;",
|
|
386
|
+
" ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;",
|
|
387
|
+
" ssl_prefer_server_ciphers off;",
|
|
388
|
+
"",
|
|
389
|
+
])
|
|
390
|
+
else:
|
|
391
|
+
conf_lines.extend([
|
|
392
|
+
"server {",
|
|
393
|
+
" listen 80;",
|
|
394
|
+
f" server_name {domain};",
|
|
395
|
+
"",
|
|
396
|
+
])
|
|
397
|
+
|
|
398
|
+
conf_lines.extend([
|
|
399
|
+
" # Logging",
|
|
400
|
+
f" access_log /var/log/nginx/{project.name.lower()}-access.log;",
|
|
401
|
+
f" error_log /var/log/nginx/{project.name.lower()}-error.log;",
|
|
402
|
+
"",
|
|
403
|
+
" # Security headers",
|
|
404
|
+
" add_header X-Frame-Options \"SAMEORIGIN\" always;",
|
|
405
|
+
" add_header X-Content-Type-Options \"nosniff\" always;",
|
|
406
|
+
" add_header X-XSS-Protection \"1; mode=block\" always;",
|
|
407
|
+
"",
|
|
408
|
+
" # Gzip compression",
|
|
409
|
+
" gzip on;",
|
|
410
|
+
" gzip_types text/plain text/css application/json application/javascript text/xml application/xml;",
|
|
411
|
+
"",
|
|
412
|
+
" # Static files",
|
|
413
|
+
" location /static/ {",
|
|
414
|
+
" alias /var/www/{project.name.lower()}/static/;",
|
|
415
|
+
" expires 30d;",
|
|
416
|
+
" add_header Cache-Control \"public, immutable\";",
|
|
417
|
+
" }",
|
|
418
|
+
"",
|
|
419
|
+
" # Proxy to application",
|
|
420
|
+
" location / {",
|
|
421
|
+
f" proxy_pass http://{upstream_name};",
|
|
422
|
+
" proxy_http_version 1.1;",
|
|
423
|
+
" proxy_set_header Host $host;",
|
|
424
|
+
" proxy_set_header X-Real-IP $remote_addr;",
|
|
425
|
+
" proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;",
|
|
426
|
+
" proxy_set_header X-Forwarded-Proto $scheme;",
|
|
427
|
+
" proxy_set_header Connection \"\";",
|
|
428
|
+
"",
|
|
429
|
+
" # Timeouts",
|
|
430
|
+
" proxy_connect_timeout 60s;",
|
|
431
|
+
" proxy_send_timeout 60s;",
|
|
432
|
+
" proxy_read_timeout 60s;",
|
|
433
|
+
" }",
|
|
434
|
+
"",
|
|
435
|
+
" # WebSocket support",
|
|
436
|
+
" location /ws/ {",
|
|
437
|
+
f" proxy_pass http://{upstream_name};",
|
|
438
|
+
" proxy_http_version 1.1;",
|
|
439
|
+
" proxy_set_header Upgrade $http_upgrade;",
|
|
440
|
+
" proxy_set_header Connection \"upgrade\";",
|
|
441
|
+
" proxy_set_header Host $host;",
|
|
442
|
+
" proxy_read_timeout 86400;",
|
|
443
|
+
" }",
|
|
444
|
+
"",
|
|
445
|
+
" # Health check endpoint",
|
|
446
|
+
" location /health {",
|
|
447
|
+
f" proxy_pass http://{upstream_name}/health;",
|
|
448
|
+
" access_log off;",
|
|
449
|
+
" }",
|
|
450
|
+
"}",
|
|
451
|
+
])
|
|
452
|
+
|
|
453
|
+
return "\n".join(conf_lines)
|
|
454
|
+
|
|
455
|
+
def generate_systemd_service(
|
|
456
|
+
self,
|
|
457
|
+
project_id: int,
|
|
458
|
+
environment_id: int = None,
|
|
459
|
+
working_dir: str = None,
|
|
460
|
+
python_path: str = None
|
|
461
|
+
) -> str:
|
|
462
|
+
"""
|
|
463
|
+
Generate systemd service file for a project
|
|
464
|
+
|
|
465
|
+
Args:
|
|
466
|
+
project_id: Project ID
|
|
467
|
+
environment_id: Optional environment ID
|
|
468
|
+
working_dir: Working directory (defaults to /var/www/{project})
|
|
469
|
+
python_path: Python executable path
|
|
470
|
+
|
|
471
|
+
Returns:
|
|
472
|
+
String content of systemd service file
|
|
473
|
+
"""
|
|
474
|
+
project = self.db.query(Project).filter(Project.id == project_id).first()
|
|
475
|
+
if not project:
|
|
476
|
+
raise ValueError(f"Project {project_id} not found")
|
|
477
|
+
|
|
478
|
+
env_name = "dev"
|
|
479
|
+
if environment_id:
|
|
480
|
+
environment = self.db.query(Environment).filter(
|
|
481
|
+
Environment.id == environment_id
|
|
482
|
+
).first()
|
|
483
|
+
if environment:
|
|
484
|
+
env_name = environment.name
|
|
485
|
+
|
|
486
|
+
project_slug = project.name.lower().replace(' ', '-')
|
|
487
|
+
if not working_dir:
|
|
488
|
+
working_dir = f"/var/www/{project_slug}"
|
|
489
|
+
if not python_path:
|
|
490
|
+
python_path = f"{working_dir}/venv/bin/python"
|
|
491
|
+
|
|
492
|
+
# Get app port
|
|
493
|
+
ports = self.db.query(PortAllocation).filter(
|
|
494
|
+
PortAllocation.project_id == project_id,
|
|
495
|
+
PortAllocation.port_type == 'app',
|
|
496
|
+
PortAllocation.status == 'allocated'
|
|
497
|
+
)
|
|
498
|
+
if environment_id:
|
|
499
|
+
ports = ports.filter(PortAllocation.environment_id == environment_id)
|
|
500
|
+
port_alloc = ports.first()
|
|
501
|
+
app_port = port_alloc.port if port_alloc else 8000
|
|
502
|
+
|
|
503
|
+
service_lines = [
|
|
504
|
+
f"# Systemd service for {project.name} ({env_name})",
|
|
505
|
+
f"# Generated by Quantum Admin - {datetime.now().isoformat()}",
|
|
506
|
+
"",
|
|
507
|
+
"[Unit]",
|
|
508
|
+
f"Description={project.name} Quantum Application ({env_name})",
|
|
509
|
+
"After=network.target postgresql.service redis.service",
|
|
510
|
+
"Wants=postgresql.service redis.service",
|
|
511
|
+
"",
|
|
512
|
+
"[Service]",
|
|
513
|
+
"Type=simple",
|
|
514
|
+
f"User=www-data",
|
|
515
|
+
f"Group=www-data",
|
|
516
|
+
f"WorkingDirectory={working_dir}",
|
|
517
|
+
f"Environment=APP_ENV={env_name}",
|
|
518
|
+
f"EnvironmentFile={working_dir}/.env",
|
|
519
|
+
f"ExecStart={python_path} -m uvicorn main:app --host 0.0.0.0 --port {app_port}",
|
|
520
|
+
"Restart=always",
|
|
521
|
+
"RestartSec=5",
|
|
522
|
+
"StandardOutput=journal",
|
|
523
|
+
"StandardError=journal",
|
|
524
|
+
"",
|
|
525
|
+
"# Security",
|
|
526
|
+
"NoNewPrivileges=true",
|
|
527
|
+
"ProtectSystem=strict",
|
|
528
|
+
f"ReadWritePaths={working_dir}",
|
|
529
|
+
"PrivateTmp=true",
|
|
530
|
+
"",
|
|
531
|
+
"[Install]",
|
|
532
|
+
"WantedBy=multi-user.target",
|
|
533
|
+
]
|
|
534
|
+
|
|
535
|
+
return "\n".join(service_lines)
|
|
536
|
+
|
|
537
|
+
def _get_default_db_port(self, db_type: str) -> int:
|
|
538
|
+
"""Get default port for database type"""
|
|
539
|
+
defaults = {
|
|
540
|
+
'postgres': 5432,
|
|
541
|
+
'mysql': 3306,
|
|
542
|
+
'mongodb': 27017,
|
|
543
|
+
'redis': 6379,
|
|
544
|
+
}
|
|
545
|
+
return defaults.get(db_type, 5432)
|
|
546
|
+
|
|
547
|
+
def _dict_to_yaml(self, data: Dict, indent: int = 0) -> str:
|
|
548
|
+
"""Simple dict to YAML converter"""
|
|
549
|
+
lines = []
|
|
550
|
+
prefix = " " * indent
|
|
551
|
+
|
|
552
|
+
for key, value in data.items():
|
|
553
|
+
if value is None:
|
|
554
|
+
lines.append(f"{prefix}{key}:")
|
|
555
|
+
elif isinstance(value, dict):
|
|
556
|
+
lines.append(f"{prefix}{key}:")
|
|
557
|
+
lines.append(self._dict_to_yaml(value, indent + 1))
|
|
558
|
+
elif isinstance(value, list):
|
|
559
|
+
lines.append(f"{prefix}{key}:")
|
|
560
|
+
for item in value:
|
|
561
|
+
if isinstance(item, dict):
|
|
562
|
+
lines.append(f"{prefix} -")
|
|
563
|
+
for k, v in item.items():
|
|
564
|
+
lines.append(f"{prefix} {k}: {v}")
|
|
565
|
+
else:
|
|
566
|
+
lines.append(f"{prefix} - {item}")
|
|
567
|
+
else:
|
|
568
|
+
lines.append(f"{prefix}{key}: {value}")
|
|
569
|
+
|
|
570
|
+
return "\n".join(lines)
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
# Singleton instance
|
|
574
|
+
_config_generator = None
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
def get_config_generator(db: Session = None) -> ConfigGenerator:
|
|
578
|
+
"""Get config generator instance"""
|
|
579
|
+
if db:
|
|
580
|
+
return ConfigGenerator(db)
|
|
581
|
+
|
|
582
|
+
global _config_generator
|
|
583
|
+
if _config_generator is None:
|
|
584
|
+
from .database import SessionLocal
|
|
585
|
+
_config_generator = ConfigGenerator(SessionLocal())
|
|
586
|
+
return _config_generator
|