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,710 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Template Service for Quantum Admin
|
|
3
|
+
Provides project scaffolding from templates
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import shutil
|
|
8
|
+
import logging
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Dict, Any, List, Optional
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
import yaml
|
|
14
|
+
import json
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class TemplateInfo:
|
|
21
|
+
"""Information about a project template"""
|
|
22
|
+
id: str
|
|
23
|
+
name: str
|
|
24
|
+
description: str
|
|
25
|
+
icon: str = "📁" # folder icon
|
|
26
|
+
category: str = "general"
|
|
27
|
+
features: List[str] = field(default_factory=list)
|
|
28
|
+
options: List[Dict[str, Any]] = field(default_factory=list)
|
|
29
|
+
files: List[str] = field(default_factory=list)
|
|
30
|
+
|
|
31
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
32
|
+
return {
|
|
33
|
+
"id": self.id,
|
|
34
|
+
"name": self.name,
|
|
35
|
+
"description": self.description,
|
|
36
|
+
"icon": self.icon,
|
|
37
|
+
"category": self.category,
|
|
38
|
+
"features": self.features,
|
|
39
|
+
"options": self.options,
|
|
40
|
+
"files": self.files
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Built-in templates
|
|
45
|
+
TEMPLATES = {
|
|
46
|
+
"blank": TemplateInfo(
|
|
47
|
+
id="blank",
|
|
48
|
+
name="Blank Project",
|
|
49
|
+
description="Empty project with minimal configuration",
|
|
50
|
+
icon="📄",
|
|
51
|
+
category="starter",
|
|
52
|
+
features=["Minimal setup", "Ready for customization"],
|
|
53
|
+
options=[]
|
|
54
|
+
),
|
|
55
|
+
"blog": TemplateInfo(
|
|
56
|
+
id="blog",
|
|
57
|
+
name="Blog",
|
|
58
|
+
description="Full-featured blog with posts, comments, and admin",
|
|
59
|
+
icon="📝",
|
|
60
|
+
category="content",
|
|
61
|
+
features=["Posts management", "Comments", "Categories/Tags", "User authentication", "Admin panel"],
|
|
62
|
+
options=[
|
|
63
|
+
{"name": "include_auth", "label": "Include Authentication", "type": "boolean", "default": True},
|
|
64
|
+
{"name": "include_comments", "label": "Enable Comments", "type": "boolean", "default": True},
|
|
65
|
+
{"name": "sample_data", "label": "Include Sample Posts", "type": "boolean", "default": True}
|
|
66
|
+
]
|
|
67
|
+
),
|
|
68
|
+
"api": TemplateInfo(
|
|
69
|
+
id="api",
|
|
70
|
+
name="REST API",
|
|
71
|
+
description="RESTful API with CRUD operations and authentication",
|
|
72
|
+
icon="🔌",
|
|
73
|
+
category="backend",
|
|
74
|
+
features=["RESTful endpoints", "JWT authentication", "Database integration", "Swagger docs"],
|
|
75
|
+
options=[
|
|
76
|
+
{"name": "include_auth", "label": "Include Authentication", "type": "boolean", "default": True},
|
|
77
|
+
{"name": "include_swagger", "label": "Generate Swagger Docs", "type": "boolean", "default": True},
|
|
78
|
+
{"name": "database", "label": "Database Type", "type": "select", "default": "postgres",
|
|
79
|
+
"choices": ["postgres", "mysql", "sqlite"]}
|
|
80
|
+
]
|
|
81
|
+
),
|
|
82
|
+
"dashboard": TemplateInfo(
|
|
83
|
+
id="dashboard",
|
|
84
|
+
name="Dashboard",
|
|
85
|
+
description="Admin dashboard with charts, tables, and data visualization",
|
|
86
|
+
icon="📊",
|
|
87
|
+
category="admin",
|
|
88
|
+
features=["Charts & Graphs", "Data tables", "Real-time updates", "User management"],
|
|
89
|
+
options=[
|
|
90
|
+
{"name": "include_auth", "label": "Include Authentication", "type": "boolean", "default": True},
|
|
91
|
+
{"name": "include_charts", "label": "Include Chart Components", "type": "boolean", "default": True},
|
|
92
|
+
{"name": "sample_data", "label": "Include Sample Data", "type": "boolean", "default": True}
|
|
93
|
+
]
|
|
94
|
+
),
|
|
95
|
+
"landing": TemplateInfo(
|
|
96
|
+
id="landing",
|
|
97
|
+
name="Landing Page",
|
|
98
|
+
description="Marketing landing page with sections and contact form",
|
|
99
|
+
icon="🏠",
|
|
100
|
+
category="marketing",
|
|
101
|
+
features=["Hero section", "Features grid", "Testimonials", "Contact form", "Responsive design"],
|
|
102
|
+
options=[
|
|
103
|
+
{"name": "include_contact", "label": "Include Contact Form", "type": "boolean", "default": True},
|
|
104
|
+
{"name": "include_newsletter", "label": "Include Newsletter Signup", "type": "boolean", "default": False}
|
|
105
|
+
]
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class TemplateService:
|
|
111
|
+
"""Service for managing and scaffolding project templates"""
|
|
112
|
+
|
|
113
|
+
def __init__(self, templates_dir: Path = None):
|
|
114
|
+
if templates_dir:
|
|
115
|
+
self.templates_dir = templates_dir
|
|
116
|
+
else:
|
|
117
|
+
# Default to quantum_admin/templates
|
|
118
|
+
self.templates_dir = Path(__file__).parent.parent / "templates"
|
|
119
|
+
|
|
120
|
+
def list_templates(self) -> List[TemplateInfo]:
|
|
121
|
+
"""List all available templates"""
|
|
122
|
+
templates = list(TEMPLATES.values())
|
|
123
|
+
|
|
124
|
+
# Also check for custom templates in templates directory
|
|
125
|
+
if self.templates_dir.exists():
|
|
126
|
+
for template_dir in self.templates_dir.iterdir():
|
|
127
|
+
if template_dir.is_dir() and template_dir.name not in TEMPLATES:
|
|
128
|
+
manifest_path = template_dir / "template.yaml"
|
|
129
|
+
if manifest_path.exists():
|
|
130
|
+
try:
|
|
131
|
+
with open(manifest_path) as f:
|
|
132
|
+
manifest = yaml.safe_load(f)
|
|
133
|
+
templates.append(TemplateInfo(
|
|
134
|
+
id=template_dir.name,
|
|
135
|
+
name=manifest.get("name", template_dir.name),
|
|
136
|
+
description=manifest.get("description", ""),
|
|
137
|
+
icon=manifest.get("icon", "📁"),
|
|
138
|
+
category=manifest.get("category", "custom"),
|
|
139
|
+
features=manifest.get("features", []),
|
|
140
|
+
options=manifest.get("options", [])
|
|
141
|
+
))
|
|
142
|
+
except Exception as e:
|
|
143
|
+
logger.warning(f"Failed to load template manifest {manifest_path}: {e}")
|
|
144
|
+
|
|
145
|
+
return templates
|
|
146
|
+
|
|
147
|
+
def get_template(self, template_id: str) -> Optional[TemplateInfo]:
|
|
148
|
+
"""Get a specific template by ID"""
|
|
149
|
+
if template_id in TEMPLATES:
|
|
150
|
+
return TEMPLATES[template_id]
|
|
151
|
+
|
|
152
|
+
# Check custom templates
|
|
153
|
+
template_dir = self.templates_dir / template_id
|
|
154
|
+
if template_dir.exists():
|
|
155
|
+
manifest_path = template_dir / "template.yaml"
|
|
156
|
+
if manifest_path.exists():
|
|
157
|
+
with open(manifest_path) as f:
|
|
158
|
+
manifest = yaml.safe_load(f)
|
|
159
|
+
return TemplateInfo(
|
|
160
|
+
id=template_id,
|
|
161
|
+
name=manifest.get("name", template_id),
|
|
162
|
+
description=manifest.get("description", ""),
|
|
163
|
+
icon=manifest.get("icon", "📁"),
|
|
164
|
+
category=manifest.get("category", "custom"),
|
|
165
|
+
features=manifest.get("features", []),
|
|
166
|
+
options=manifest.get("options", [])
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
return None
|
|
170
|
+
|
|
171
|
+
def scaffold_project(
|
|
172
|
+
self,
|
|
173
|
+
template_id: str,
|
|
174
|
+
project_name: str,
|
|
175
|
+
target_dir: Path,
|
|
176
|
+
options: Dict[str, Any] = None
|
|
177
|
+
) -> Dict[str, Any]:
|
|
178
|
+
"""
|
|
179
|
+
Create a new project from a template.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
template_id: ID of the template to use
|
|
183
|
+
project_name: Name of the new project
|
|
184
|
+
target_dir: Directory where the project will be created
|
|
185
|
+
options: Template-specific options
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Dict with created files and status
|
|
189
|
+
"""
|
|
190
|
+
options = options or {}
|
|
191
|
+
template = self.get_template(template_id)
|
|
192
|
+
|
|
193
|
+
if not template:
|
|
194
|
+
raise ValueError(f"Template not found: {template_id}")
|
|
195
|
+
|
|
196
|
+
project_dir = target_dir / project_name
|
|
197
|
+
if project_dir.exists():
|
|
198
|
+
raise ValueError(f"Project directory already exists: {project_dir}")
|
|
199
|
+
|
|
200
|
+
project_dir.mkdir(parents=True)
|
|
201
|
+
created_files = []
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
# Create base structure
|
|
205
|
+
(project_dir / "components").mkdir()
|
|
206
|
+
(project_dir / "migrations").mkdir(exist_ok=True)
|
|
207
|
+
|
|
208
|
+
# Create quantum.config.yaml
|
|
209
|
+
config = self._generate_config(template_id, project_name, options)
|
|
210
|
+
config_path = project_dir / "quantum.config.yaml"
|
|
211
|
+
with open(config_path, 'w') as f:
|
|
212
|
+
yaml.dump(config, f, default_flow_style=False)
|
|
213
|
+
created_files.append("quantum.config.yaml")
|
|
214
|
+
|
|
215
|
+
# Generate template-specific files
|
|
216
|
+
if template_id == "blank":
|
|
217
|
+
created_files.extend(self._scaffold_blank(project_dir, project_name, options))
|
|
218
|
+
elif template_id == "blog":
|
|
219
|
+
created_files.extend(self._scaffold_blog(project_dir, project_name, options))
|
|
220
|
+
elif template_id == "api":
|
|
221
|
+
created_files.extend(self._scaffold_api(project_dir, project_name, options))
|
|
222
|
+
elif template_id == "dashboard":
|
|
223
|
+
created_files.extend(self._scaffold_dashboard(project_dir, project_name, options))
|
|
224
|
+
elif template_id == "landing":
|
|
225
|
+
created_files.extend(self._scaffold_landing(project_dir, project_name, options))
|
|
226
|
+
else:
|
|
227
|
+
# Copy from custom template directory
|
|
228
|
+
template_src = self.templates_dir / template_id
|
|
229
|
+
if template_src.exists():
|
|
230
|
+
created_files.extend(self._copy_template_files(template_src, project_dir, project_name, options))
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
"success": True,
|
|
234
|
+
"project_dir": str(project_dir),
|
|
235
|
+
"files_created": created_files,
|
|
236
|
+
"message": f"Project '{project_name}' created successfully"
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
except Exception as e:
|
|
240
|
+
# Cleanup on failure
|
|
241
|
+
if project_dir.exists():
|
|
242
|
+
shutil.rmtree(project_dir)
|
|
243
|
+
raise
|
|
244
|
+
|
|
245
|
+
def _generate_config(self, template_id: str, project_name: str, options: Dict[str, Any]) -> Dict:
|
|
246
|
+
"""Generate quantum.config.yaml content"""
|
|
247
|
+
config = {
|
|
248
|
+
"name": project_name,
|
|
249
|
+
"version": "1.0.0",
|
|
250
|
+
"template": template_id,
|
|
251
|
+
"created_at": datetime.utcnow().isoformat(),
|
|
252
|
+
"server": {
|
|
253
|
+
"port": 8080,
|
|
254
|
+
"host": "0.0.0.0"
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
# Add database config if needed
|
|
259
|
+
if options.get("database") or template_id in ("blog", "api", "dashboard"):
|
|
260
|
+
db_type = options.get("database", "sqlite")
|
|
261
|
+
config["database"] = {
|
|
262
|
+
"type": db_type,
|
|
263
|
+
"name": project_name if db_type == "sqlite" else f"{project_name}_db"
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
# Add auth config if needed
|
|
267
|
+
if options.get("include_auth", True) and template_id != "landing":
|
|
268
|
+
config["auth"] = {
|
|
269
|
+
"enabled": True,
|
|
270
|
+
"type": "session",
|
|
271
|
+
"session_timeout": 3600
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return config
|
|
275
|
+
|
|
276
|
+
def _scaffold_blank(self, project_dir: Path, name: str, options: Dict) -> List[str]:
|
|
277
|
+
"""Scaffold a blank project"""
|
|
278
|
+
files = []
|
|
279
|
+
|
|
280
|
+
# Create index.q
|
|
281
|
+
index_content = f'''<q:component name="index">
|
|
282
|
+
<q:head>
|
|
283
|
+
<title>{name}</title>
|
|
284
|
+
</q:head>
|
|
285
|
+
|
|
286
|
+
<q:body>
|
|
287
|
+
<div class="container">
|
|
288
|
+
<h1>Welcome to {name}</h1>
|
|
289
|
+
<p>Your Quantum project is ready!</p>
|
|
290
|
+
</div>
|
|
291
|
+
</q:body>
|
|
292
|
+
</q:component>
|
|
293
|
+
'''
|
|
294
|
+
with open(project_dir / "components" / "index.q", 'w') as f:
|
|
295
|
+
f.write(index_content)
|
|
296
|
+
files.append("components/index.q")
|
|
297
|
+
|
|
298
|
+
return files
|
|
299
|
+
|
|
300
|
+
def _scaffold_blog(self, project_dir: Path, name: str, options: Dict) -> List[str]:
|
|
301
|
+
"""Scaffold a blog project"""
|
|
302
|
+
files = []
|
|
303
|
+
include_auth = options.get("include_auth", True)
|
|
304
|
+
include_comments = options.get("include_comments", True)
|
|
305
|
+
|
|
306
|
+
# Index page
|
|
307
|
+
index_content = f'''<q:component name="index">
|
|
308
|
+
<q:head>
|
|
309
|
+
<title>{name} - Blog</title>
|
|
310
|
+
<link rel="stylesheet" href="/static/blog.css" />
|
|
311
|
+
</q:head>
|
|
312
|
+
|
|
313
|
+
<q:query name="posts" datasource="default">
|
|
314
|
+
SELECT id, title, slug, excerpt, created_at, author
|
|
315
|
+
FROM posts
|
|
316
|
+
WHERE published = true
|
|
317
|
+
ORDER BY created_at DESC
|
|
318
|
+
LIMIT 10
|
|
319
|
+
</q:query>
|
|
320
|
+
|
|
321
|
+
<q:body>
|
|
322
|
+
<header class="blog-header">
|
|
323
|
+
<h1>{name}</h1>
|
|
324
|
+
<nav>
|
|
325
|
+
<a href="/">Home</a>
|
|
326
|
+
<a href="/about">About</a>
|
|
327
|
+
{"<a href='/admin'>Admin</a>" if include_auth else ""}
|
|
328
|
+
</nav>
|
|
329
|
+
</header>
|
|
330
|
+
|
|
331
|
+
<main class="blog-posts">
|
|
332
|
+
<q:loop items="{{posts}}" item="post">
|
|
333
|
+
<article class="post-card">
|
|
334
|
+
<h2><a href="/post/{{post.slug}}">{{post.title}}</a></h2>
|
|
335
|
+
<p class="post-meta">By {{post.author}} on {{post.created_at}}</p>
|
|
336
|
+
<p>{{post.excerpt}}</p>
|
|
337
|
+
<a href="/post/{{post.slug}}" class="read-more">Read more</a>
|
|
338
|
+
</article>
|
|
339
|
+
</q:loop>
|
|
340
|
+
</main>
|
|
341
|
+
</q:body>
|
|
342
|
+
</q:component>
|
|
343
|
+
'''
|
|
344
|
+
with open(project_dir / "components" / "index.q", 'w') as f:
|
|
345
|
+
f.write(index_content)
|
|
346
|
+
files.append("components/index.q")
|
|
347
|
+
|
|
348
|
+
# Post detail page
|
|
349
|
+
post_content = f'''<q:component name="post">
|
|
350
|
+
<q:set name="slug" value="{{url.params.slug}}" />
|
|
351
|
+
|
|
352
|
+
<q:query name="post" datasource="default">
|
|
353
|
+
SELECT id, title, content, created_at, author
|
|
354
|
+
FROM posts
|
|
355
|
+
WHERE slug = :slug AND published = true
|
|
356
|
+
</q:query>
|
|
357
|
+
|
|
358
|
+
{"<q:query name='comments' datasource='default'>SELECT * FROM comments WHERE post_id = :post.id ORDER BY created_at</q:query>" if include_comments else ""}
|
|
359
|
+
|
|
360
|
+
<q:head>
|
|
361
|
+
<title>{{post.title}} - {name}</title>
|
|
362
|
+
</q:head>
|
|
363
|
+
|
|
364
|
+
<q:body>
|
|
365
|
+
<article class="post-full">
|
|
366
|
+
<h1>{{post.title}}</h1>
|
|
367
|
+
<p class="post-meta">By {{post.author}} on {{post.created_at}}</p>
|
|
368
|
+
<div class="post-content">
|
|
369
|
+
{{post.content}}
|
|
370
|
+
</div>
|
|
371
|
+
</article>
|
|
372
|
+
|
|
373
|
+
{"<section class='comments'><h3>Comments</h3><q:loop items='{comments}' item='comment'><div class='comment'><p>{comment.content}</p><small>By {comment.author}</small></div></q:loop></section>" if include_comments else ""}
|
|
374
|
+
|
|
375
|
+
<a href="/" class="back-link">Back to posts</a>
|
|
376
|
+
</q:body>
|
|
377
|
+
</q:component>
|
|
378
|
+
'''
|
|
379
|
+
with open(project_dir / "components" / "post.q", 'w') as f:
|
|
380
|
+
f.write(post_content)
|
|
381
|
+
files.append("components/post.q")
|
|
382
|
+
|
|
383
|
+
# Migration for posts table
|
|
384
|
+
migration_content = '''-- Migration: Create posts table
|
|
385
|
+
-- Version: V001
|
|
386
|
+
|
|
387
|
+
CREATE TABLE IF NOT EXISTS posts (
|
|
388
|
+
id SERIAL PRIMARY KEY,
|
|
389
|
+
title VARCHAR(255) NOT NULL,
|
|
390
|
+
slug VARCHAR(255) NOT NULL UNIQUE,
|
|
391
|
+
excerpt TEXT,
|
|
392
|
+
content TEXT,
|
|
393
|
+
author VARCHAR(100),
|
|
394
|
+
published BOOLEAN DEFAULT false,
|
|
395
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
396
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
CREATE INDEX idx_posts_slug ON posts(slug);
|
|
400
|
+
CREATE INDEX idx_posts_published ON posts(published);
|
|
401
|
+
'''
|
|
402
|
+
with open(project_dir / "migrations" / "V001_create_posts.sql", 'w') as f:
|
|
403
|
+
f.write(migration_content)
|
|
404
|
+
files.append("migrations/V001_create_posts.sql")
|
|
405
|
+
|
|
406
|
+
if include_comments:
|
|
407
|
+
comments_migration = '''-- Migration: Create comments table
|
|
408
|
+
-- Version: V002
|
|
409
|
+
|
|
410
|
+
CREATE TABLE IF NOT EXISTS comments (
|
|
411
|
+
id SERIAL PRIMARY KEY,
|
|
412
|
+
post_id INTEGER REFERENCES posts(id) ON DELETE CASCADE,
|
|
413
|
+
author VARCHAR(100),
|
|
414
|
+
email VARCHAR(255),
|
|
415
|
+
content TEXT NOT NULL,
|
|
416
|
+
approved BOOLEAN DEFAULT false,
|
|
417
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
CREATE INDEX idx_comments_post ON comments(post_id);
|
|
421
|
+
'''
|
|
422
|
+
with open(project_dir / "migrations" / "V002_create_comments.sql", 'w') as f:
|
|
423
|
+
f.write(comments_migration)
|
|
424
|
+
files.append("migrations/V002_create_comments.sql")
|
|
425
|
+
|
|
426
|
+
return files
|
|
427
|
+
|
|
428
|
+
def _scaffold_api(self, project_dir: Path, name: str, options: Dict) -> List[str]:
|
|
429
|
+
"""Scaffold a REST API project"""
|
|
430
|
+
files = []
|
|
431
|
+
include_auth = options.get("include_auth", True)
|
|
432
|
+
|
|
433
|
+
# API index with endpoints listing
|
|
434
|
+
index_content = f'''<q:component name="api-index">
|
|
435
|
+
<q:head>
|
|
436
|
+
<title>{name} API</title>
|
|
437
|
+
</q:head>
|
|
438
|
+
|
|
439
|
+
<q:body>
|
|
440
|
+
<h1>{name} API</h1>
|
|
441
|
+
<p>Welcome to the {name} REST API</p>
|
|
442
|
+
|
|
443
|
+
<h2>Available Endpoints</h2>
|
|
444
|
+
<ul>
|
|
445
|
+
<li>GET /api/items - List all items</li>
|
|
446
|
+
<li>GET /api/items/:id - Get item by ID</li>
|
|
447
|
+
<li>POST /api/items - Create new item</li>
|
|
448
|
+
<li>PUT /api/items/:id - Update item</li>
|
|
449
|
+
<li>DELETE /api/items/:id - Delete item</li>
|
|
450
|
+
{"<li>POST /api/auth/login - Authenticate user</li>" if include_auth else ""}
|
|
451
|
+
</ul>
|
|
452
|
+
</q:body>
|
|
453
|
+
</q:component>
|
|
454
|
+
'''
|
|
455
|
+
with open(project_dir / "components" / "index.q", 'w') as f:
|
|
456
|
+
f.write(index_content)
|
|
457
|
+
files.append("components/index.q")
|
|
458
|
+
|
|
459
|
+
# Items API component
|
|
460
|
+
items_content = '''<q:component name="items-api">
|
|
461
|
+
<!-- List items -->
|
|
462
|
+
<q:function name="list" method="GET" path="/api/items">
|
|
463
|
+
<q:query name="items" datasource="default">
|
|
464
|
+
SELECT * FROM items ORDER BY created_at DESC
|
|
465
|
+
</q:query>
|
|
466
|
+
<q:return format="json">{{items}}</q:return>
|
|
467
|
+
</q:function>
|
|
468
|
+
|
|
469
|
+
<!-- Get single item -->
|
|
470
|
+
<q:function name="get" method="GET" path="/api/items/:id">
|
|
471
|
+
<q:query name="item" datasource="default">
|
|
472
|
+
SELECT * FROM items WHERE id = :id
|
|
473
|
+
</q:query>
|
|
474
|
+
<q:if condition="{{!item}}">
|
|
475
|
+
<q:return format="json" status="404">{"error": "Item not found"}</q:return>
|
|
476
|
+
</q:if>
|
|
477
|
+
<q:return format="json">{{item}}</q:return>
|
|
478
|
+
</q:function>
|
|
479
|
+
|
|
480
|
+
<!-- Create item -->
|
|
481
|
+
<q:function name="create" method="POST" path="/api/items">
|
|
482
|
+
<q:query name="result" datasource="default">
|
|
483
|
+
INSERT INTO items (name, description)
|
|
484
|
+
VALUES (:body.name, :body.description)
|
|
485
|
+
RETURNING *
|
|
486
|
+
</q:query>
|
|
487
|
+
<q:return format="json" status="201">{{result}}</q:return>
|
|
488
|
+
</q:function>
|
|
489
|
+
|
|
490
|
+
<!-- Update item -->
|
|
491
|
+
<q:function name="update" method="PUT" path="/api/items/:id">
|
|
492
|
+
<q:query name="result" datasource="default">
|
|
493
|
+
UPDATE items
|
|
494
|
+
SET name = :body.name, description = :body.description, updated_at = NOW()
|
|
495
|
+
WHERE id = :id
|
|
496
|
+
RETURNING *
|
|
497
|
+
</q:query>
|
|
498
|
+
<q:return format="json">{{result}}</q:return>
|
|
499
|
+
</q:function>
|
|
500
|
+
|
|
501
|
+
<!-- Delete item -->
|
|
502
|
+
<q:function name="delete" method="DELETE" path="/api/items/:id">
|
|
503
|
+
<q:query datasource="default">
|
|
504
|
+
DELETE FROM items WHERE id = :id
|
|
505
|
+
</q:query>
|
|
506
|
+
<q:return format="json">{"message": "Item deleted"}</q:return>
|
|
507
|
+
</q:function>
|
|
508
|
+
</q:component>
|
|
509
|
+
'''
|
|
510
|
+
with open(project_dir / "components" / "items.q", 'w') as f:
|
|
511
|
+
f.write(items_content)
|
|
512
|
+
files.append("components/items.q")
|
|
513
|
+
|
|
514
|
+
# Items migration
|
|
515
|
+
migration_content = '''-- Migration: Create items table
|
|
516
|
+
-- Version: V001
|
|
517
|
+
|
|
518
|
+
CREATE TABLE IF NOT EXISTS items (
|
|
519
|
+
id SERIAL PRIMARY KEY,
|
|
520
|
+
name VARCHAR(255) NOT NULL,
|
|
521
|
+
description TEXT,
|
|
522
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
523
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
524
|
+
);
|
|
525
|
+
'''
|
|
526
|
+
with open(project_dir / "migrations" / "V001_create_items.sql", 'w') as f:
|
|
527
|
+
f.write(migration_content)
|
|
528
|
+
files.append("migrations/V001_create_items.sql")
|
|
529
|
+
|
|
530
|
+
return files
|
|
531
|
+
|
|
532
|
+
def _scaffold_dashboard(self, project_dir: Path, name: str, options: Dict) -> List[str]:
|
|
533
|
+
"""Scaffold a dashboard project"""
|
|
534
|
+
files = []
|
|
535
|
+
|
|
536
|
+
# Dashboard index
|
|
537
|
+
index_content = f'''<q:component name="dashboard">
|
|
538
|
+
<q:head>
|
|
539
|
+
<title>{name} - Dashboard</title>
|
|
540
|
+
<link rel="stylesheet" href="/static/dashboard.css" />
|
|
541
|
+
</q:head>
|
|
542
|
+
|
|
543
|
+
<q:query name="stats" datasource="default">
|
|
544
|
+
SELECT
|
|
545
|
+
(SELECT COUNT(*) FROM users) as total_users,
|
|
546
|
+
(SELECT COUNT(*) FROM orders) as total_orders,
|
|
547
|
+
(SELECT SUM(amount) FROM orders) as total_revenue
|
|
548
|
+
</q:query>
|
|
549
|
+
|
|
550
|
+
<q:body>
|
|
551
|
+
<div class="dashboard-container">
|
|
552
|
+
<header class="dashboard-header">
|
|
553
|
+
<h1>{name}</h1>
|
|
554
|
+
<nav class="dashboard-nav">
|
|
555
|
+
<a href="/dashboard">Overview</a>
|
|
556
|
+
<a href="/dashboard/users">Users</a>
|
|
557
|
+
<a href="/dashboard/orders">Orders</a>
|
|
558
|
+
<a href="/dashboard/settings">Settings</a>
|
|
559
|
+
</nav>
|
|
560
|
+
</header>
|
|
561
|
+
|
|
562
|
+
<main class="dashboard-main">
|
|
563
|
+
<div class="stats-grid">
|
|
564
|
+
<div class="stat-card">
|
|
565
|
+
<h3>Total Users</h3>
|
|
566
|
+
<p class="stat-value">{{stats.total_users}}</p>
|
|
567
|
+
</div>
|
|
568
|
+
<div class="stat-card">
|
|
569
|
+
<h3>Total Orders</h3>
|
|
570
|
+
<p class="stat-value">{{stats.total_orders}}</p>
|
|
571
|
+
</div>
|
|
572
|
+
<div class="stat-card">
|
|
573
|
+
<h3>Revenue</h3>
|
|
574
|
+
<p class="stat-value">${{stats.total_revenue}}</p>
|
|
575
|
+
</div>
|
|
576
|
+
</div>
|
|
577
|
+
|
|
578
|
+
<section class="recent-activity">
|
|
579
|
+
<h2>Recent Activity</h2>
|
|
580
|
+
<q:query name="recent" datasource="default">
|
|
581
|
+
SELECT * FROM activity_log ORDER BY created_at DESC LIMIT 10
|
|
582
|
+
</q:query>
|
|
583
|
+
<table class="data-table">
|
|
584
|
+
<thead>
|
|
585
|
+
<tr><th>Action</th><th>User</th><th>Time</th></tr>
|
|
586
|
+
</thead>
|
|
587
|
+
<tbody>
|
|
588
|
+
<q:loop items="{{recent}}" item="log">
|
|
589
|
+
<tr>
|
|
590
|
+
<td>{{log.action}}</td>
|
|
591
|
+
<td>{{log.user_name}}</td>
|
|
592
|
+
<td>{{log.created_at}}</td>
|
|
593
|
+
</tr>
|
|
594
|
+
</q:loop>
|
|
595
|
+
</tbody>
|
|
596
|
+
</table>
|
|
597
|
+
</section>
|
|
598
|
+
</main>
|
|
599
|
+
</div>
|
|
600
|
+
</q:body>
|
|
601
|
+
</q:component>
|
|
602
|
+
'''
|
|
603
|
+
with open(project_dir / "components" / "index.q", 'w') as f:
|
|
604
|
+
f.write(index_content)
|
|
605
|
+
files.append("components/index.q")
|
|
606
|
+
|
|
607
|
+
return files
|
|
608
|
+
|
|
609
|
+
def _scaffold_landing(self, project_dir: Path, name: str, options: Dict) -> List[str]:
|
|
610
|
+
"""Scaffold a landing page project"""
|
|
611
|
+
files = []
|
|
612
|
+
include_contact = options.get("include_contact", True)
|
|
613
|
+
|
|
614
|
+
# Landing page
|
|
615
|
+
landing_content = f'''<q:component name="landing">
|
|
616
|
+
<q:head>
|
|
617
|
+
<title>{name}</title>
|
|
618
|
+
<link rel="stylesheet" href="/static/landing.css" />
|
|
619
|
+
</q:head>
|
|
620
|
+
|
|
621
|
+
<q:body>
|
|
622
|
+
<!-- Hero Section -->
|
|
623
|
+
<section class="hero">
|
|
624
|
+
<div class="hero-content">
|
|
625
|
+
<h1>{name}</h1>
|
|
626
|
+
<p class="hero-subtitle">Your amazing product description goes here</p>
|
|
627
|
+
<a href="#features" class="cta-button">Get Started</a>
|
|
628
|
+
</div>
|
|
629
|
+
</section>
|
|
630
|
+
|
|
631
|
+
<!-- Features Section -->
|
|
632
|
+
<section id="features" class="features">
|
|
633
|
+
<h2>Features</h2>
|
|
634
|
+
<div class="features-grid">
|
|
635
|
+
<div class="feature-card">
|
|
636
|
+
<h3>Feature One</h3>
|
|
637
|
+
<p>Description of your first amazing feature</p>
|
|
638
|
+
</div>
|
|
639
|
+
<div class="feature-card">
|
|
640
|
+
<h3>Feature Two</h3>
|
|
641
|
+
<p>Description of your second amazing feature</p>
|
|
642
|
+
</div>
|
|
643
|
+
<div class="feature-card">
|
|
644
|
+
<h3>Feature Three</h3>
|
|
645
|
+
<p>Description of your third amazing feature</p>
|
|
646
|
+
</div>
|
|
647
|
+
</div>
|
|
648
|
+
</section>
|
|
649
|
+
|
|
650
|
+
<!-- Testimonials -->
|
|
651
|
+
<section class="testimonials">
|
|
652
|
+
<h2>What People Say</h2>
|
|
653
|
+
<div class="testimonials-grid">
|
|
654
|
+
<blockquote>
|
|
655
|
+
<p>"This product changed my life!"</p>
|
|
656
|
+
<cite>- Happy Customer</cite>
|
|
657
|
+
</blockquote>
|
|
658
|
+
</div>
|
|
659
|
+
</section>
|
|
660
|
+
|
|
661
|
+
{"<!-- Contact Form --><section id='contact' class='contact'><h2>Contact Us</h2><form class='contact-form' action='/api/contact' method='POST'><input type='text' name='name' placeholder='Your Name' required /><input type='email' name='email' placeholder='Your Email' required /><textarea name='message' placeholder='Your Message' required></textarea><button type='submit'>Send Message</button></form></section>" if include_contact else ""}
|
|
662
|
+
|
|
663
|
+
<!-- Footer -->
|
|
664
|
+
<footer class="footer">
|
|
665
|
+
<p>© 2024 {name}. All rights reserved.</p>
|
|
666
|
+
</footer>
|
|
667
|
+
</q:body>
|
|
668
|
+
</q:component>
|
|
669
|
+
'''
|
|
670
|
+
with open(project_dir / "components" / "index.q", 'w') as f:
|
|
671
|
+
f.write(landing_content)
|
|
672
|
+
files.append("components/index.q")
|
|
673
|
+
|
|
674
|
+
return files
|
|
675
|
+
|
|
676
|
+
def _copy_template_files(
|
|
677
|
+
self,
|
|
678
|
+
template_src: Path,
|
|
679
|
+
project_dir: Path,
|
|
680
|
+
name: str,
|
|
681
|
+
options: Dict
|
|
682
|
+
) -> List[str]:
|
|
683
|
+
"""Copy files from a custom template directory"""
|
|
684
|
+
files = []
|
|
685
|
+
|
|
686
|
+
for src_file in template_src.rglob("*"):
|
|
687
|
+
if src_file.is_file() and src_file.name != "template.yaml":
|
|
688
|
+
rel_path = src_file.relative_to(template_src)
|
|
689
|
+
dest_file = project_dir / rel_path
|
|
690
|
+
dest_file.parent.mkdir(parents=True, exist_ok=True)
|
|
691
|
+
|
|
692
|
+
# Read and process content (replace placeholders)
|
|
693
|
+
try:
|
|
694
|
+
content = src_file.read_text()
|
|
695
|
+
content = content.replace("{{PROJECT_NAME}}", name)
|
|
696
|
+
for key, value in options.items():
|
|
697
|
+
content = content.replace(f"{{{{OPTIONS.{key}}}}}", str(value))
|
|
698
|
+
dest_file.write_text(content)
|
|
699
|
+
except Exception:
|
|
700
|
+
# Binary file, just copy
|
|
701
|
+
shutil.copy2(src_file, dest_file)
|
|
702
|
+
|
|
703
|
+
files.append(str(rel_path))
|
|
704
|
+
|
|
705
|
+
return files
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
def get_template_service() -> TemplateService:
|
|
709
|
+
"""Get template service instance"""
|
|
710
|
+
return TemplateService()
|