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,488 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Settings Service for Quantum Admin
|
|
3
|
+
Manages global and project-specific settings with YAML persistence
|
|
4
|
+
"""
|
|
5
|
+
import os
|
|
6
|
+
import yaml
|
|
7
|
+
import logging
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Dict, Optional
|
|
10
|
+
from dataclasses import dataclass, field, asdict
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
# Base paths
|
|
16
|
+
# QUANTUM_ADMIN_SETTINGS_DIR moves these files, as QUANTUM_ADMIN_DATABASE_URL
|
|
17
|
+
# moves the database: the test suite points both at a temporary directory
|
|
18
|
+
# before importing the backend, so no test run writes the owner's settings.
|
|
19
|
+
SETTINGS_DIR = Path(os.environ.get("QUANTUM_ADMIN_SETTINGS_DIR") or Path(__file__).parent.parent / "settings")
|
|
20
|
+
GLOBAL_SETTINGS_FILE = SETTINGS_DIR / "global.yaml"
|
|
21
|
+
PROJECTS_SETTINGS_DIR = SETTINGS_DIR / "projects"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class ServerSettings:
|
|
26
|
+
port: int = 8001
|
|
27
|
+
host: str = "0.0.0.0"
|
|
28
|
+
debug: bool = False
|
|
29
|
+
reload: bool = True
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class DockerSettings:
|
|
34
|
+
host: str = "" # Empty = local, or ssh://user@host
|
|
35
|
+
registry: str = ""
|
|
36
|
+
timeout: int = 30
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class EmailSettings:
|
|
41
|
+
enabled: bool = False
|
|
42
|
+
provider: str = "smtp"
|
|
43
|
+
host: str = ""
|
|
44
|
+
port: int = 587
|
|
45
|
+
use_tls: bool = True
|
|
46
|
+
username: str = ""
|
|
47
|
+
password: str = ""
|
|
48
|
+
from_address: str = "noreply@quantum.local"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class SecuritySettings:
|
|
53
|
+
# There was a `secret_key: str = "change-me-in-production"` here. Nothing
|
|
54
|
+
# in the admin ever read it — the JWT signing key comes from
|
|
55
|
+
# JWT_SECRET_KEY in auth_service — but _create_default_global_settings
|
|
56
|
+
# wrote it into the settings file on disk, where it looked like the place
|
|
57
|
+
# to set a secret. A decoy credential is worse than none: someone sets it,
|
|
58
|
+
# believes the admin is configured, and it signs nothing. Removed; set
|
|
59
|
+
# JWT_SECRET_KEY instead.
|
|
60
|
+
jwt_algorithm: str = "HS256"
|
|
61
|
+
jwt_expiry_hours: int = 24
|
|
62
|
+
# Loopback by default. "*" here plus allow_credentials let any website
|
|
63
|
+
# call the admin API.
|
|
64
|
+
allowed_origins: list = field(default_factory=lambda: [
|
|
65
|
+
"http://localhost:8000", "http://127.0.0.1:8000",
|
|
66
|
+
])
|
|
67
|
+
rate_limit_enabled: bool = False
|
|
68
|
+
rate_limit_requests: int = 100
|
|
69
|
+
rate_limit_window: int = 60
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class DeployEnvironment:
|
|
74
|
+
name: str = ""
|
|
75
|
+
host: str = ""
|
|
76
|
+
user: str = ""
|
|
77
|
+
ssh_key: str = "~/.ssh/id_ed25519"
|
|
78
|
+
deploy_path: str = ""
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class DeploymentSettings:
|
|
83
|
+
default_dockerfile: str = "Dockerfile"
|
|
84
|
+
default_port: int = 8080
|
|
85
|
+
health_check_path: str = "/health"
|
|
86
|
+
health_check_timeout: int = 30
|
|
87
|
+
environments: list = field(default_factory=list)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass
|
|
91
|
+
class DatabaseSettings:
|
|
92
|
+
url: str = "sqlite:///quantum_admin.db"
|
|
93
|
+
echo: bool = False
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class LoggingSettings:
|
|
98
|
+
level: str = "INFO"
|
|
99
|
+
format: str = "%(asctime)s [%(levelname)s] %(message)s"
|
|
100
|
+
file: str = "logs/quantum_admin.log"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass
|
|
104
|
+
class GlobalSettings:
|
|
105
|
+
server: ServerSettings = field(default_factory=ServerSettings)
|
|
106
|
+
docker: DockerSettings = field(default_factory=DockerSettings)
|
|
107
|
+
email: EmailSettings = field(default_factory=EmailSettings)
|
|
108
|
+
security: SecuritySettings = field(default_factory=SecuritySettings)
|
|
109
|
+
deployment: DeploymentSettings = field(default_factory=DeploymentSettings)
|
|
110
|
+
database: DatabaseSettings = field(default_factory=DatabaseSettings)
|
|
111
|
+
logging: LoggingSettings = field(default_factory=LoggingSettings)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@dataclass
|
|
115
|
+
class ProjectSettings:
|
|
116
|
+
name: str = ""
|
|
117
|
+
description: str = ""
|
|
118
|
+
default_datasource: str = ""
|
|
119
|
+
dockerfile: str = "Dockerfile"
|
|
120
|
+
port: int = 8080
|
|
121
|
+
env_vars: Dict[str, str] = field(default_factory=dict)
|
|
122
|
+
features: Dict[str, bool] = field(default_factory=lambda: {
|
|
123
|
+
"htmx": True,
|
|
124
|
+
"auth": False,
|
|
125
|
+
"email": False
|
|
126
|
+
})
|
|
127
|
+
deployment: Dict[str, str] = field(default_factory=dict)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class SettingsService:
|
|
131
|
+
"""Service for managing application settings"""
|
|
132
|
+
|
|
133
|
+
def __init__(self):
|
|
134
|
+
self._ensure_directories()
|
|
135
|
+
self._settings_cache: Optional[Dict] = None
|
|
136
|
+
self._cache_time: Optional[datetime] = None
|
|
137
|
+
self._cache_ttl = 5 # seconds
|
|
138
|
+
|
|
139
|
+
def _ensure_directories(self):
|
|
140
|
+
"""Ensure settings directories exist"""
|
|
141
|
+
SETTINGS_DIR.mkdir(parents=True, exist_ok=True)
|
|
142
|
+
PROJECTS_SETTINGS_DIR.mkdir(parents=True, exist_ok=True)
|
|
143
|
+
|
|
144
|
+
def _resolve_env_vars(self, value: Any) -> Any:
|
|
145
|
+
"""Resolve ${ENV_VAR} placeholders in values"""
|
|
146
|
+
if isinstance(value, str) and value.startswith("${") and value.endswith("}"):
|
|
147
|
+
env_var = value[2:-1]
|
|
148
|
+
return os.environ.get(env_var, value)
|
|
149
|
+
elif isinstance(value, dict):
|
|
150
|
+
return {k: self._resolve_env_vars(v) for k, v in value.items()}
|
|
151
|
+
elif isinstance(value, list):
|
|
152
|
+
return [self._resolve_env_vars(v) for v in value]
|
|
153
|
+
return value
|
|
154
|
+
|
|
155
|
+
# =========================================================================
|
|
156
|
+
# Global Settings
|
|
157
|
+
# =========================================================================
|
|
158
|
+
|
|
159
|
+
def get_global_settings(self, resolve_env: bool = True) -> Dict:
|
|
160
|
+
"""
|
|
161
|
+
Load global settings from YAML file
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
resolve_env: Whether to resolve ${ENV_VAR} placeholders
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Dictionary of settings
|
|
168
|
+
"""
|
|
169
|
+
# Check cache
|
|
170
|
+
if self._settings_cache and self._cache_time:
|
|
171
|
+
age = (datetime.now() - self._cache_time).total_seconds()
|
|
172
|
+
if age < self._cache_ttl:
|
|
173
|
+
return self._settings_cache
|
|
174
|
+
|
|
175
|
+
if not GLOBAL_SETTINGS_FILE.exists():
|
|
176
|
+
logger.warning("Global settings file not found, creating default")
|
|
177
|
+
self._create_default_global_settings()
|
|
178
|
+
|
|
179
|
+
try:
|
|
180
|
+
with open(GLOBAL_SETTINGS_FILE, 'r', encoding='utf-8') as f:
|
|
181
|
+
settings = yaml.safe_load(f) or {}
|
|
182
|
+
|
|
183
|
+
if resolve_env:
|
|
184
|
+
settings = self._resolve_env_vars(settings)
|
|
185
|
+
|
|
186
|
+
# Update cache
|
|
187
|
+
self._settings_cache = settings
|
|
188
|
+
self._cache_time = datetime.now()
|
|
189
|
+
|
|
190
|
+
return settings
|
|
191
|
+
except Exception as e:
|
|
192
|
+
logger.error(f"Error loading global settings: {e}")
|
|
193
|
+
return {}
|
|
194
|
+
|
|
195
|
+
def update_global_settings(self, updates: Dict) -> Dict:
|
|
196
|
+
"""
|
|
197
|
+
Update global settings (partial update)
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
updates: Dictionary of settings to update (can be nested)
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
Updated settings dictionary
|
|
204
|
+
"""
|
|
205
|
+
current = self.get_global_settings(resolve_env=False)
|
|
206
|
+
merged = self._deep_merge(current, updates)
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
with open(GLOBAL_SETTINGS_FILE, 'w', encoding='utf-8') as f:
|
|
210
|
+
yaml.dump(merged, f, default_flow_style=False, allow_unicode=True)
|
|
211
|
+
|
|
212
|
+
# Invalidate cache
|
|
213
|
+
self._settings_cache = None
|
|
214
|
+
self._cache_time = None
|
|
215
|
+
|
|
216
|
+
logger.info("Global settings updated successfully")
|
|
217
|
+
return self.get_global_settings()
|
|
218
|
+
except Exception as e:
|
|
219
|
+
logger.error(f"Error saving global settings: {e}")
|
|
220
|
+
raise
|
|
221
|
+
|
|
222
|
+
def get_setting(self, path: str, default: Any = None) -> Any:
|
|
223
|
+
"""
|
|
224
|
+
Get a specific setting by dot-notation path
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
path: Setting path (e.g., "server.port" or "docker.host")
|
|
228
|
+
default: Default value if not found
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
Setting value or default
|
|
232
|
+
"""
|
|
233
|
+
settings = self.get_global_settings()
|
|
234
|
+
keys = path.split(".")
|
|
235
|
+
value = settings
|
|
236
|
+
|
|
237
|
+
try:
|
|
238
|
+
for key in keys:
|
|
239
|
+
value = value[key]
|
|
240
|
+
return value
|
|
241
|
+
except (KeyError, TypeError):
|
|
242
|
+
return default
|
|
243
|
+
|
|
244
|
+
def set_setting(self, path: str, value: Any) -> Dict:
|
|
245
|
+
"""
|
|
246
|
+
Set a specific setting by dot-notation path
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
path: Setting path (e.g., "server.port")
|
|
250
|
+
value: Value to set
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
Updated settings
|
|
254
|
+
"""
|
|
255
|
+
keys = path.split(".")
|
|
256
|
+
updates = {}
|
|
257
|
+
current = updates
|
|
258
|
+
|
|
259
|
+
for i, key in enumerate(keys[:-1]):
|
|
260
|
+
current[key] = {}
|
|
261
|
+
current = current[key]
|
|
262
|
+
|
|
263
|
+
current[keys[-1]] = value
|
|
264
|
+
return self.update_global_settings(updates)
|
|
265
|
+
|
|
266
|
+
def _create_default_global_settings(self):
|
|
267
|
+
"""Create default global settings file"""
|
|
268
|
+
defaults = asdict(GlobalSettings())
|
|
269
|
+
with open(GLOBAL_SETTINGS_FILE, 'w', encoding='utf-8') as f:
|
|
270
|
+
yaml.dump(defaults, f, default_flow_style=False, allow_unicode=True)
|
|
271
|
+
|
|
272
|
+
# =========================================================================
|
|
273
|
+
# Project Settings
|
|
274
|
+
# =========================================================================
|
|
275
|
+
|
|
276
|
+
def get_project_settings(self, project_id: int) -> Dict:
|
|
277
|
+
"""Load settings for a specific project"""
|
|
278
|
+
project_file = PROJECTS_SETTINGS_DIR / f"{project_id}.yaml"
|
|
279
|
+
|
|
280
|
+
if not project_file.exists():
|
|
281
|
+
return asdict(ProjectSettings())
|
|
282
|
+
|
|
283
|
+
try:
|
|
284
|
+
with open(project_file, 'r', encoding='utf-8') as f:
|
|
285
|
+
settings = yaml.safe_load(f) or {}
|
|
286
|
+
return self._resolve_env_vars(settings)
|
|
287
|
+
except Exception as e:
|
|
288
|
+
logger.error(f"Error loading project {project_id} settings: {e}")
|
|
289
|
+
return asdict(ProjectSettings())
|
|
290
|
+
|
|
291
|
+
def update_project_settings(self, project_id: int, updates: Dict) -> Dict:
|
|
292
|
+
"""Update settings for a specific project"""
|
|
293
|
+
project_file = PROJECTS_SETTINGS_DIR / f"{project_id}.yaml"
|
|
294
|
+
current = self.get_project_settings(project_id)
|
|
295
|
+
merged = self._deep_merge(current, updates)
|
|
296
|
+
|
|
297
|
+
try:
|
|
298
|
+
with open(project_file, 'w', encoding='utf-8') as f:
|
|
299
|
+
yaml.dump(merged, f, default_flow_style=False, allow_unicode=True)
|
|
300
|
+
|
|
301
|
+
logger.info(f"Project {project_id} settings updated")
|
|
302
|
+
return self.get_project_settings(project_id)
|
|
303
|
+
except Exception as e:
|
|
304
|
+
logger.error(f"Error saving project {project_id} settings: {e}")
|
|
305
|
+
raise
|
|
306
|
+
|
|
307
|
+
def delete_project_settings(self, project_id: int) -> bool:
|
|
308
|
+
"""Delete settings for a project"""
|
|
309
|
+
project_file = PROJECTS_SETTINGS_DIR / f"{project_id}.yaml"
|
|
310
|
+
|
|
311
|
+
if project_file.exists():
|
|
312
|
+
project_file.unlink()
|
|
313
|
+
logger.info(f"Project {project_id} settings deleted")
|
|
314
|
+
return True
|
|
315
|
+
return False
|
|
316
|
+
|
|
317
|
+
def list_project_settings(self) -> Dict[int, Dict]:
|
|
318
|
+
"""List all project settings"""
|
|
319
|
+
result = {}
|
|
320
|
+
for file in PROJECTS_SETTINGS_DIR.glob("*.yaml"):
|
|
321
|
+
try:
|
|
322
|
+
project_id = int(file.stem)
|
|
323
|
+
result[project_id] = self.get_project_settings(project_id)
|
|
324
|
+
except ValueError:
|
|
325
|
+
continue
|
|
326
|
+
return result
|
|
327
|
+
|
|
328
|
+
# =========================================================================
|
|
329
|
+
# Utility Methods
|
|
330
|
+
# =========================================================================
|
|
331
|
+
|
|
332
|
+
def _deep_merge(self, base: Dict, updates: Dict) -> Dict:
|
|
333
|
+
"""Deep merge two dictionaries"""
|
|
334
|
+
result = base.copy()
|
|
335
|
+
|
|
336
|
+
for key, value in updates.items():
|
|
337
|
+
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
|
|
338
|
+
result[key] = self._deep_merge(result[key], value)
|
|
339
|
+
else:
|
|
340
|
+
result[key] = value
|
|
341
|
+
|
|
342
|
+
return result
|
|
343
|
+
|
|
344
|
+
# =========================================================================
|
|
345
|
+
# Connectors
|
|
346
|
+
# =========================================================================
|
|
347
|
+
|
|
348
|
+
def get_connectors(self) -> list:
|
|
349
|
+
"""
|
|
350
|
+
Get list of available connectors (email, LLM, storage, etc.)
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
List of connector dictionaries with name, type, provider, scope
|
|
354
|
+
"""
|
|
355
|
+
# Define available connector types
|
|
356
|
+
connectors = [
|
|
357
|
+
{
|
|
358
|
+
"id": "email",
|
|
359
|
+
"name": "Email Service",
|
|
360
|
+
"type": "email",
|
|
361
|
+
"provider": self.get_setting("email.provider", "SMTP"),
|
|
362
|
+
"scope": "private", # Requires explicit access
|
|
363
|
+
"icon": "envelope"
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
"id": "llm_openai",
|
|
367
|
+
"name": "OpenAI API",
|
|
368
|
+
"type": "llm",
|
|
369
|
+
"provider": "OpenAI",
|
|
370
|
+
"scope": "private",
|
|
371
|
+
"icon": "robot"
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
"id": "llm_anthropic",
|
|
375
|
+
"name": "Anthropic API",
|
|
376
|
+
"type": "llm",
|
|
377
|
+
"provider": "Anthropic",
|
|
378
|
+
"scope": "private",
|
|
379
|
+
"icon": "brain"
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
"id": "storage_s3",
|
|
383
|
+
"name": "AWS S3",
|
|
384
|
+
"type": "storage",
|
|
385
|
+
"provider": "AWS",
|
|
386
|
+
"scope": "private",
|
|
387
|
+
"icon": "cloud"
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
"id": "storage_local",
|
|
391
|
+
"name": "Local Storage",
|
|
392
|
+
"type": "storage",
|
|
393
|
+
"provider": "Filesystem",
|
|
394
|
+
"scope": "public", # Available to all projects
|
|
395
|
+
"icon": "folder"
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
"id": "cache_redis",
|
|
399
|
+
"name": "Redis Cache",
|
|
400
|
+
"type": "cache",
|
|
401
|
+
"provider": "Redis",
|
|
402
|
+
"scope": "private",
|
|
403
|
+
"icon": "bolt"
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
"id": "queue_rabbitmq",
|
|
407
|
+
"name": "RabbitMQ",
|
|
408
|
+
"type": "queue",
|
|
409
|
+
"provider": "RabbitMQ",
|
|
410
|
+
"scope": "private",
|
|
411
|
+
"icon": "share"
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
"id": "webhook",
|
|
415
|
+
"name": "Webhooks",
|
|
416
|
+
"type": "integration",
|
|
417
|
+
"provider": "HTTP",
|
|
418
|
+
"scope": "public", # Available to all projects
|
|
419
|
+
"icon": "link"
|
|
420
|
+
}
|
|
421
|
+
]
|
|
422
|
+
|
|
423
|
+
return connectors
|
|
424
|
+
|
|
425
|
+
def get_project_connectors(self, project_id: int) -> Dict[str, bool]:
|
|
426
|
+
"""
|
|
427
|
+
Get which connectors are enabled for a specific project
|
|
428
|
+
|
|
429
|
+
Args:
|
|
430
|
+
project_id: Project ID
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
Dictionary of connector_id -> enabled status
|
|
434
|
+
"""
|
|
435
|
+
settings = self.get_project_settings(project_id)
|
|
436
|
+
return settings.get("connectors", {})
|
|
437
|
+
|
|
438
|
+
def update_project_connector(self, project_id: int, connector_id: str, enabled: bool) -> Dict:
|
|
439
|
+
"""
|
|
440
|
+
Enable/disable a connector for a project
|
|
441
|
+
|
|
442
|
+
Args:
|
|
443
|
+
project_id: Project ID
|
|
444
|
+
connector_id: Connector ID (e.g., "email", "llm_openai")
|
|
445
|
+
enabled: Whether to enable the connector
|
|
446
|
+
|
|
447
|
+
Returns:
|
|
448
|
+
Updated project settings
|
|
449
|
+
"""
|
|
450
|
+
settings = self.get_project_settings(project_id)
|
|
451
|
+
if "connectors" not in settings:
|
|
452
|
+
settings["connectors"] = {}
|
|
453
|
+
settings["connectors"][connector_id] = enabled
|
|
454
|
+
return self.update_project_settings(project_id, {"connectors": settings["connectors"]})
|
|
455
|
+
|
|
456
|
+
def export_settings(self) -> Dict:
|
|
457
|
+
"""Export all settings (global + all projects)"""
|
|
458
|
+
return {
|
|
459
|
+
"global": self.get_global_settings(resolve_env=False),
|
|
460
|
+
"projects": self.list_project_settings()
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
def import_settings(self, data: Dict) -> bool:
|
|
464
|
+
"""Import settings from export data"""
|
|
465
|
+
try:
|
|
466
|
+
if "global" in data:
|
|
467
|
+
self.update_global_settings(data["global"])
|
|
468
|
+
|
|
469
|
+
if "projects" in data:
|
|
470
|
+
for project_id, settings in data["projects"].items():
|
|
471
|
+
self.update_project_settings(int(project_id), settings)
|
|
472
|
+
|
|
473
|
+
return True
|
|
474
|
+
except Exception as e:
|
|
475
|
+
logger.error(f"Error importing settings: {e}")
|
|
476
|
+
return False
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
# Singleton instance
|
|
480
|
+
_settings_service = None
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def get_settings_service() -> SettingsService:
|
|
484
|
+
"""Get singleton instance of SettingsService"""
|
|
485
|
+
global _settings_service
|
|
486
|
+
if _settings_service is None:
|
|
487
|
+
_settings_service = SettingsService()
|
|
488
|
+
return _settings_service
|