001-fix-ui-ws-validation #2

Merged
busya merged 26 commits from 001-fix-ui-ws-validation into migration 2025-12-21 00:29:20 +03:00
211 changed files with 30268 additions and 2911 deletions
Showing only changes of commit d05344e604 - Show all commits

View File

@@ -6,6 +6,8 @@ Auto-generated from all feature plans. Last updated: 2025-12-19
- Python 3.9+, Node.js 18+ + `uvicorn`, `npm`, `bash` (003-project-launch-script)
- Python 3.9+, Node.js 18+ + SvelteKit, FastAPI, Tailwind CSS (inferred from existing frontend) (004-integrate-svelte-kit)
- N/A (Frontend integration) (004-integrate-svelte-kit)
- Python 3.9+, Node.js 18+ + FastAPI, SvelteKit, Tailwind CSS, Pydantic (001-fix-ui-ws-validation)
- N/A (Configuration based) (001-fix-ui-ws-validation)
- Python 3.9+ (Backend), Node.js 18+ (Frontend Build) (001-plugin-arch-svelte-ui)
@@ -26,10 +28,10 @@ cd src; pytest; ruff check .
Python 3.9+ (Backend), Node.js 18+ (Frontend Build): Follow standard conventions
## Recent Changes
- 001-fix-ui-ws-validation: Added Python 3.9+, Node.js 18+ + FastAPI, SvelteKit, Tailwind CSS, Pydantic
- 001-fix-ui-ws-validation: Added Python 3.9+, Node.js 18+ + FastAPI, SvelteKit, Tailwind CSS, Pydantic
- 004-integrate-svelte-kit: Added Python 3.9+, Node.js 18+ + SvelteKit, FastAPI, Tailwind CSS (inferred from existing frontend)
- 003-project-launch-script: Added Python 3.9+, Node.js 18+ + `uvicorn`, `npm`, `bash`
- 001-plugin-arch-svelte-ui: Added Python 3.9+ (Backend), Node.js 18+ (Frontend Build)
<!-- MANUAL ADDITIONS START -->
<!-- MANUAL ADDITIONS END -->

View File

@@ -1,50 +1,29 @@
# [PROJECT_NAME] Constitution
<!-- Example: Spec Constitution, TaskFlow Constitution, etc. -->
# ss-tools Constitution
## Core Principles
### [PRINCIPLE_1_NAME]
<!-- Example: I. Library-First -->
[PRINCIPLE_1_DESCRIPTION]
<!-- Example: Every feature starts as a standalone library; Libraries must be self-contained, independently testable, documented; Clear purpose required - no organizational-only libraries -->
### I. SPA-First Architecture
The frontend MUST be a Static Single Page Application (SPA) served by the Python backend. No Node.js server is permitted in production. The backend serves the `index.html` entry point for all non-API routes.
### [PRINCIPLE_2_NAME]
<!-- Example: II. CLI Interface -->
[PRINCIPLE_2_DESCRIPTION]
<!-- Example: Every library exposes functionality via CLI; Text in/out protocol: stdin/args → stdout, errors → stderr; Support JSON + human-readable formats -->
### II. API-Driven Communication
All data retrieval and state changes MUST be performed via the backend REST API or WebSockets. The frontend should not access the database or filesystem directly.
### [PRINCIPLE_3_NAME]
<!-- Example: III. Test-First (NON-NEGOTIABLE) -->
[PRINCIPLE_3_DESCRIPTION]
<!-- Example: TDD mandatory: Tests written → User approved → Tests fail → Then implement; Red-Green-Refactor cycle strictly enforced -->
### III. Modern Stack Consistency
The project strictly uses SvelteKit (Frontend), FastAPI (Backend), and Tailwind CSS (Styling). New dependencies must be justified and approved.
### [PRINCIPLE_4_NAME]
<!-- Example: IV. Integration Testing -->
[PRINCIPLE_4_DESCRIPTION]
<!-- Example: Focus areas requiring integration tests: New library contract tests, Contract changes, Inter-service communication, Shared schemas -->
### [PRINCIPLE_5_NAME]
<!-- Example: V. Observability, VI. Versioning & Breaking Changes, VII. Simplicity -->
[PRINCIPLE_5_DESCRIPTION]
<!-- Example: Text I/O ensures debuggability; Structured logging required; Or: MAJOR.MINOR.BUILD format; Or: Start simple, YAGNI principles -->
## [SECTION_2_NAME]
<!-- Example: Additional Constraints, Security Requirements, Performance Standards, etc. -->
[SECTION_2_CONTENT]
<!-- Example: Technology stack requirements, compliance standards, deployment policies, etc. -->
## [SECTION_3_NAME]
<!-- Example: Development Workflow, Review Process, Quality Gates, etc. -->
[SECTION_3_CONTENT]
<!-- Example: Code review requirements, testing gates, deployment approval process, etc. -->
### IV. Semantic Protocol Adherence (GRACE-Poly)
All code generation and modification MUST adhere to the Semantic Protocol defined in `semantic_protocol.md`.
- **Anchors**: Use `[DEF:id:Type]` and `[/DEF:id]` to define semantic boundaries.
- **Contracts**: Define `@PRE` and `@POST` conditions in headers.
- **Logging**: Use structured logging with `[AnchorID][State]` format.
- **Immutability**: Respect architectural decisions in headers.
## Governance
<!-- Example: Constitution supersedes all other practices; Amendments require documentation, approval, migration plan -->
[GOVERNANCE_RULES]
<!-- Example: All PRs/reviews must verify compliance; Complexity must be justified; Use [GUIDANCE_FILE] for runtime development guidance -->
### Compliance
All Pull Requests and code modifications must be verified against this Constitution. Violations of Core Principles are considered critical defects.
**Version**: [CONSTITUTION_VERSION] | **Ratified**: [RATIFICATION_DATE] | **Last Amended**: [LAST_AMENDED_DATE]
<!-- Example: Version: 2.1.1 | Ratified: 2025-06-13 | Last Amended: 2025-07-16 -->
### Amendments
Changes to this Constitution require a formal RFC process and approval from the project lead.
**Version**: 1.0.0 | **Ratified**: 2025-12-20

View File

@@ -33,3 +33,237 @@ pydantic_core._pydantic_core.ValidationError: 1 validation error for SupersetCon
base_url
Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/value_error
2025-12-20 22:42:32,538 - INFO - [BackupPlugin][Entry] Starting backup for superset.
2025-12-20 22:42:32,538 - INFO - [setup_clients][Enter] Starting Superset clients initialization.
2025-12-20 22:42:32,583 - INFO - [setup_clients][Action] Loading environments from ConfigManager
2025-12-20 22:42:32,587 - CRITICAL - [setup_clients][Failure] Critical error during client initialization: 1 validation error for SupersetConfig
base_url
Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/value_error
Traceback (most recent call last):
File "/home/user/ss-tools/superset_tool/utils/init_clients.py", line 66, in setup_clients
config = SupersetConfig(
^^^^^^^^^^^^^^^
File "/home/user/ss-tools/backend/.venv/lib/python3.12/site-packages/pydantic/main.py", line 250, in __init__
validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 1 validation error for SupersetConfig
base_url
Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/value_error
2025-12-20 22:54:29,770 - INFO - [BackupPlugin][Entry] Starting backup for .
2025-12-20 22:54:29,771 - INFO - [setup_clients][Enter] Starting Superset clients initialization.
2025-12-20 22:54:29,831 - INFO - [setup_clients][Action] Loading environments from ConfigManager
2025-12-20 22:54:29,833 - CRITICAL - [setup_clients][Failure] Critical error during client initialization: 1 validation error for SupersetConfig
base_url
Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/value_error
Traceback (most recent call last):
File "/home/user/ss-tools/superset_tool/utils/init_clients.py", line 66, in setup_clients
config = SupersetConfig(
^^^^^^^^^^^^^^^
File "/home/user/ss-tools/backend/.venv/lib/python3.12/site-packages/pydantic/main.py", line 250, in __init__
validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 1 validation error for SupersetConfig
base_url
Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/value_error
2025-12-20 22:54:34,078 - INFO - [BackupPlugin][Entry] Starting backup for superset.
2025-12-20 22:54:34,078 - INFO - [setup_clients][Enter] Starting Superset clients initialization.
2025-12-20 22:54:34,079 - INFO - [setup_clients][Action] Loading environments from ConfigManager
2025-12-20 22:54:34,079 - CRITICAL - [setup_clients][Failure] Critical error during client initialization: 1 validation error for SupersetConfig
base_url
Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/value_error
Traceback (most recent call last):
File "/home/user/ss-tools/superset_tool/utils/init_clients.py", line 66, in setup_clients
config = SupersetConfig(
^^^^^^^^^^^^^^^
File "/home/user/ss-tools/backend/.venv/lib/python3.12/site-packages/pydantic/main.py", line 250, in __init__
validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 1 validation error for SupersetConfig
base_url
Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/value_error
2025-12-20 22:59:25,060 - INFO - [BackupPlugin][Entry] Starting backup for superset.
2025-12-20 22:59:25,060 - INFO - [setup_clients][Enter] Starting Superset clients initialization.
2025-12-20 22:59:25,114 - INFO - [setup_clients][Action] Loading environments from ConfigManager
2025-12-20 22:59:25,117 - CRITICAL - [setup_clients][Failure] Critical error during client initialization: 1 validation error for SupersetConfig
base_url
Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/value_error
Traceback (most recent call last):
File "/home/user/ss-tools/superset_tool/utils/init_clients.py", line 66, in setup_clients
config = SupersetConfig(
^^^^^^^^^^^^^^^
File "/home/user/ss-tools/backend/.venv/lib/python3.12/site-packages/pydantic/main.py", line 250, in __init__
validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 1 validation error for SupersetConfig
base_url
Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/value_error
2025-12-20 23:00:31,156 - INFO - [BackupPlugin][Entry] Starting backup for superset.
2025-12-20 23:00:31,156 - INFO - [setup_clients][Enter] Starting Superset clients initialization.
2025-12-20 23:00:31,157 - INFO - [setup_clients][Action] Loading environments from ConfigManager
2025-12-20 23:00:31,162 - CRITICAL - [setup_clients][Failure] Critical error during client initialization: 1 validation error for SupersetConfig
base_url
Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/value_error
Traceback (most recent call last):
File "/home/user/ss-tools/superset_tool/utils/init_clients.py", line 66, in setup_clients
config = SupersetConfig(
^^^^^^^^^^^^^^^
File "/home/user/ss-tools/backend/.venv/lib/python3.12/site-packages/pydantic/main.py", line 250, in __init__
validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 1 validation error for SupersetConfig
base_url
Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/value_error
2025-12-20 23:00:34,710 - INFO - [BackupPlugin][Entry] Starting backup for superset.
2025-12-20 23:00:34,710 - INFO - [setup_clients][Enter] Starting Superset clients initialization.
2025-12-20 23:00:34,710 - INFO - [setup_clients][Action] Loading environments from ConfigManager
2025-12-20 23:00:34,711 - CRITICAL - [setup_clients][Failure] Critical error during client initialization: 1 validation error for SupersetConfig
base_url
Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/value_error
Traceback (most recent call last):
File "/home/user/ss-tools/superset_tool/utils/init_clients.py", line 66, in setup_clients
config = SupersetConfig(
^^^^^^^^^^^^^^^
File "/home/user/ss-tools/backend/.venv/lib/python3.12/site-packages/pydantic/main.py", line 250, in __init__
validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 1 validation error for SupersetConfig
base_url
Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/value_error
2025-12-20 23:01:43,894 - INFO - [BackupPlugin][Entry] Starting backup for superset.
2025-12-20 23:01:43,894 - INFO - [setup_clients][Enter] Starting Superset clients initialization.
2025-12-20 23:01:43,895 - INFO - [setup_clients][Action] Loading environments from ConfigManager
2025-12-20 23:01:43,895 - CRITICAL - [setup_clients][Failure] Critical error during client initialization: 1 validation error for SupersetConfig
base_url
Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/value_error
Traceback (most recent call last):
File "/home/user/ss-tools/superset_tool/utils/init_clients.py", line 66, in setup_clients
config = SupersetConfig(
^^^^^^^^^^^^^^^
File "/home/user/ss-tools/backend/.venv/lib/python3.12/site-packages/pydantic/main.py", line 250, in __init__
validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 1 validation error for SupersetConfig
base_url
Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/value_error
2025-12-20 23:04:07,731 - INFO - [BackupPlugin][Entry] Starting backup for superset.
2025-12-20 23:04:07,731 - INFO - [setup_clients][Enter] Starting Superset clients initialization.
2025-12-20 23:04:07,732 - INFO - [setup_clients][Action] Loading environments from ConfigManager
2025-12-20 23:04:07,732 - CRITICAL - [setup_clients][Failure] Critical error during client initialization: 1 validation error for SupersetConfig
base_url
Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/value_error
Traceback (most recent call last):
File "/home/user/ss-tools/superset_tool/utils/init_clients.py", line 66, in setup_clients
config = SupersetConfig(
^^^^^^^^^^^^^^^
File "/home/user/ss-tools/backend/.venv/lib/python3.12/site-packages/pydantic/main.py", line 250, in __init__
validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 1 validation error for SupersetConfig
base_url
Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/value_error
2025-12-20 23:06:39,641 - INFO - [BackupPlugin][Entry] Starting backup for superset.
2025-12-20 23:06:39,642 - INFO - [setup_clients][Enter] Starting Superset clients initialization.
2025-12-20 23:06:39,687 - INFO - [setup_clients][Action] Loading environments from ConfigManager
2025-12-20 23:06:39,689 - CRITICAL - [setup_clients][Failure] Critical error during client initialization: 1 validation error for SupersetConfig
base_url
Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/value_error
Traceback (most recent call last):
File "/home/user/ss-tools/superset_tool/utils/init_clients.py", line 66, in setup_clients
config = SupersetConfig(
^^^^^^^^^^^^^^^
File "/home/user/ss-tools/backend/.venv/lib/python3.12/site-packages/pydantic/main.py", line 250, in __init__
validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 1 validation error for SupersetConfig
base_url
Value error, Invalid URL format: https://superset.bebesh.ru. Must include '/api/v1'. [type=value_error, input_value='https://superset.bebesh.ru', input_type=str]
For further information visit https://errors.pydantic.dev/2.12/v/value_error
2025-12-20 23:30:36,090 - INFO - [BackupPlugin][Entry] Starting backup for superset.
2025-12-20 23:30:36,093 - INFO - [setup_clients][Enter] Starting Superset clients initialization.
2025-12-20 23:30:36,128 - INFO - [setup_clients][Action] Loading environments from ConfigManager
2025-12-20 23:30:36,129 - INFO - [SupersetClient.__init__][Enter] Initializing SupersetClient.
2025-12-20 23:30:36,129 - INFO - [APIClient.__init__][Entry] Initializing APIClient.
2025-12-20 23:30:36,130 - WARNING - [_init_session][State] SSL verification disabled.
2025-12-20 23:30:36,130 - INFO - [APIClient.__init__][Exit] APIClient initialized.
2025-12-20 23:30:36,130 - INFO - [SupersetClient.__init__][Exit] SupersetClient initialized.
2025-12-20 23:30:36,130 - INFO - [get_dashboards][Enter] Fetching dashboards.
2025-12-20 23:30:36,131 - INFO - [authenticate][Enter] Authenticating to https://superset.bebesh.ru/api/v1
2025-12-20 23:30:36,897 - INFO - [authenticate][Exit] Authenticated successfully.
2025-12-20 23:30:37,527 - INFO - [get_dashboards][Exit] Found 11 dashboards.
2025-12-20 23:30:37,527 - INFO - [BackupPlugin][Progress] Found 11 dashboards to export in superset.
2025-12-20 23:30:37,529 - INFO - [export_dashboard][Enter] Exporting dashboard 11.
2025-12-20 23:30:38,224 - INFO - [export_dashboard][Exit] Exported dashboard 11 to dashboard_export_20251220T203037.zip.
2025-12-20 23:30:38,225 - INFO - [save_and_unpack_dashboard][Enter] Processing dashboard. Unpack: False
2025-12-20 23:30:38,226 - INFO - [save_and_unpack_dashboard][State] Dashboard saved to: backups/SUPERSET/FCC New Coder Survey 2018/dashboard_export_20251220T203037.zip
2025-12-20 23:30:38,227 - INFO - [archive_exports][Enter] Managing archive in backups/SUPERSET/FCC New Coder Survey 2018
2025-12-20 23:30:38,230 - INFO - [export_dashboard][Enter] Exporting dashboard 10.
2025-12-20 23:30:38,438 - INFO - [export_dashboard][Exit] Exported dashboard 10 to dashboard_export_20251220T203038.zip.
2025-12-20 23:30:38,438 - INFO - [save_and_unpack_dashboard][Enter] Processing dashboard. Unpack: False
2025-12-20 23:30:38,439 - INFO - [save_and_unpack_dashboard][State] Dashboard saved to: backups/SUPERSET/COVID Vaccine Dashboard/dashboard_export_20251220T203038.zip
2025-12-20 23:30:38,439 - INFO - [archive_exports][Enter] Managing archive in backups/SUPERSET/COVID Vaccine Dashboard
2025-12-20 23:30:38,440 - INFO - [export_dashboard][Enter] Exporting dashboard 9.
2025-12-20 23:30:38,853 - INFO - [export_dashboard][Exit] Exported dashboard 9 to dashboard_export_20251220T203038.zip.
2025-12-20 23:30:38,853 - INFO - [save_and_unpack_dashboard][Enter] Processing dashboard. Unpack: False
2025-12-20 23:30:38,856 - INFO - [save_and_unpack_dashboard][State] Dashboard saved to: backups/SUPERSET/Sales Dashboard/dashboard_export_20251220T203038.zip
2025-12-20 23:30:38,856 - INFO - [archive_exports][Enter] Managing archive in backups/SUPERSET/Sales Dashboard
2025-12-20 23:30:38,858 - INFO - [export_dashboard][Enter] Exporting dashboard 8.
2025-12-20 23:30:38,939 - INFO - [export_dashboard][Exit] Exported dashboard 8 to dashboard_export_20251220T203038.zip.
2025-12-20 23:30:38,940 - INFO - [save_and_unpack_dashboard][Enter] Processing dashboard. Unpack: False
2025-12-20 23:30:38,941 - INFO - [save_and_unpack_dashboard][State] Dashboard saved to: backups/SUPERSET/Unicode Test/dashboard_export_20251220T203038.zip
2025-12-20 23:30:38,941 - INFO - [archive_exports][Enter] Managing archive in backups/SUPERSET/Unicode Test
2025-12-20 23:30:38,942 - INFO - [export_dashboard][Enter] Exporting dashboard 7.
2025-12-20 23:30:39,148 - INFO - [export_dashboard][Exit] Exported dashboard 7 to dashboard_export_20251220T203038.zip.
2025-12-20 23:30:39,148 - INFO - [save_and_unpack_dashboard][Enter] Processing dashboard. Unpack: False
2025-12-20 23:30:39,149 - INFO - [save_and_unpack_dashboard][State] Dashboard saved to: backups/SUPERSET/Video Game Sales/dashboard_export_20251220T203038.zip
2025-12-20 23:30:39,149 - INFO - [archive_exports][Enter] Managing archive in backups/SUPERSET/Video Game Sales
2025-12-20 23:30:39,150 - INFO - [export_dashboard][Enter] Exporting dashboard 6.
2025-12-20 23:30:39,689 - INFO - [export_dashboard][Exit] Exported dashboard 6 to dashboard_export_20251220T203039.zip.
2025-12-20 23:30:39,689 - INFO - [save_and_unpack_dashboard][Enter] Processing dashboard. Unpack: False
2025-12-20 23:30:39,690 - INFO - [save_and_unpack_dashboard][State] Dashboard saved to: backups/SUPERSET/Featured Charts/dashboard_export_20251220T203039.zip
2025-12-20 23:30:39,691 - INFO - [archive_exports][Enter] Managing archive in backups/SUPERSET/Featured Charts
2025-12-20 23:30:39,692 - INFO - [export_dashboard][Enter] Exporting dashboard 5.
2025-12-20 23:30:39,960 - INFO - [export_dashboard][Exit] Exported dashboard 5 to dashboard_export_20251220T203039.zip.
2025-12-20 23:30:39,960 - INFO - [save_and_unpack_dashboard][Enter] Processing dashboard. Unpack: False
2025-12-20 23:30:39,961 - INFO - [save_and_unpack_dashboard][State] Dashboard saved to: backups/SUPERSET/Slack Dashboard/dashboard_export_20251220T203039.zip
2025-12-20 23:30:39,961 - INFO - [archive_exports][Enter] Managing archive in backups/SUPERSET/Slack Dashboard
2025-12-20 23:30:39,962 - INFO - [export_dashboard][Enter] Exporting dashboard 4.
2025-12-20 23:30:40,196 - INFO - [export_dashboard][Exit] Exported dashboard 4 to dashboard_export_20251220T203039.zip.
2025-12-20 23:30:40,196 - INFO - [save_and_unpack_dashboard][Enter] Processing dashboard. Unpack: False
2025-12-20 23:30:40,197 - INFO - [save_and_unpack_dashboard][State] Dashboard saved to: backups/SUPERSET/deck.gl Demo/dashboard_export_20251220T203039.zip
2025-12-20 23:30:40,197 - INFO - [archive_exports][Enter] Managing archive in backups/SUPERSET/deck.gl Demo
2025-12-20 23:30:40,198 - INFO - [export_dashboard][Enter] Exporting dashboard 3.
2025-12-20 23:30:40,745 - INFO - [export_dashboard][Exit] Exported dashboard 3 to dashboard_export_20251220T203040.zip.
2025-12-20 23:30:40,746 - INFO - [save_and_unpack_dashboard][Enter] Processing dashboard. Unpack: False
2025-12-20 23:30:40,760 - INFO - [save_and_unpack_dashboard][State] Dashboard saved to: backups/SUPERSET/Misc Charts/dashboard_export_20251220T203040.zip
2025-12-20 23:30:40,761 - INFO - [archive_exports][Enter] Managing archive in backups/SUPERSET/Misc Charts
2025-12-20 23:30:40,762 - INFO - [export_dashboard][Enter] Exporting dashboard 2.
2025-12-20 23:30:40,928 - INFO - [export_dashboard][Exit] Exported dashboard 2 to dashboard_export_20251220T203040.zip.
2025-12-20 23:30:40,929 - INFO - [save_and_unpack_dashboard][Enter] Processing dashboard. Unpack: False
2025-12-20 23:30:40,930 - INFO - [save_and_unpack_dashboard][State] Dashboard saved to: backups/SUPERSET/USA Births Names/dashboard_export_20251220T203040.zip
2025-12-20 23:30:40,931 - INFO - [archive_exports][Enter] Managing archive in backups/SUPERSET/USA Births Names
2025-12-20 23:30:40,932 - INFO - [export_dashboard][Enter] Exporting dashboard 1.
2025-12-20 23:30:41,582 - INFO - [export_dashboard][Exit] Exported dashboard 1 to dashboard_export_20251220T203040.zip.
2025-12-20 23:30:41,582 - INFO - [save_and_unpack_dashboard][Enter] Processing dashboard. Unpack: False
2025-12-20 23:30:41,749 - INFO - [save_and_unpack_dashboard][State] Dashboard saved to: backups/SUPERSET/World Bank's Data/dashboard_export_20251220T203040.zip
2025-12-20 23:30:41,750 - INFO - [archive_exports][Enter] Managing archive in backups/SUPERSET/World Bank's Data
2025-12-20 23:30:41,752 - INFO - [consolidate_archive_folders][Enter] Consolidating archives in backups/SUPERSET
2025-12-20 23:30:41,753 - INFO - [remove_empty_directories][Enter] Starting cleanup of empty directories in backups/SUPERSET
2025-12-20 23:30:41,758 - INFO - [remove_empty_directories][Exit] Removed 0 empty directories.
2025-12-20 23:30:41,758 - INFO - [BackupPlugin][CoherenceCheck:Passed] Backup logic completed for superset.

View File

@@ -42,9 +42,9 @@ app.add_middleware(
# Include API routes
app.include_router(plugins.router, prefix="/plugins", tags=["Plugins"])
app.include_router(tasks.router, prefix="/tasks", tags=["Tasks"])
app.include_router(settings.router, prefix="/settings", tags=["Settings"])
app.include_router(plugins.router, prefix="/api/plugins", tags=["Plugins"])
app.include_router(tasks.router, prefix="/api/tasks", tags=["Tasks"])
app.include_router(settings.router, prefix="/api/settings", tags=["Settings"])
# [DEF:WebSocketEndpoint:Endpoint]
# @SEMANTICS: websocket, logs, streaming, real-time

View File

@@ -0,0 +1,49 @@
import pytest
from superset_tool.models import SupersetConfig
def test_superset_config_url_normalization():
auth = {
"provider": "db",
"username": "admin",
"password": "password",
"refresh": "token"
}
# Test with /api/v1 already present
config = SupersetConfig(
env="dev",
base_url="http://localhost:8088/api/v1",
auth=auth
)
assert config.base_url == "http://localhost:8088/api/v1"
# Test without /api/v1
config = SupersetConfig(
env="dev",
base_url="http://localhost:8088",
auth=auth
)
assert config.base_url == "http://localhost:8088/api/v1"
# Test with trailing slash
config = SupersetConfig(
env="dev",
base_url="http://localhost:8088/",
auth=auth
)
assert config.base_url == "http://localhost:8088/api/v1"
def test_superset_config_invalid_url():
auth = {
"provider": "db",
"username": "admin",
"password": "password",
"refresh": "token"
}
with pytest.raises(ValueError, match="Must start with http:// or https://"):
SupersetConfig(
env="dev",
base_url="localhost:8088",
auth=auth
)

View File

@@ -12,7 +12,7 @@ The settings mechanism allows users to configure multiple Superset environments
Configuration is structured using Pydantic models in `backend/src/core/config_models.py`:
- `Environment`: Represents a Superset instance (URL, credentials).
- `Environment`: Represents a Superset instance (URL, credentials). The `base_url` is automatically normalized to include the `/api/v1` suffix if missing.
- `GlobalSettings`: Global application parameters (e.g., `backup_path`).
- `AppConfig`: The root configuration object.

View File

@@ -27,20 +27,13 @@
*/
declare module '$env/static/private' {
export const LESSOPEN: string;
export const VSCODE_CWD: string;
export const VSCODE_ESM_ENTRYPOINT: string;
export const USER: string;
export const VSCODE_NLS_CONFIG: string;
export const npm_config_user_agent: string;
export const VSCODE_WSL_EXT_LOCATION: string;
export const VSCODE_HANDLES_UNCAUGHT_ERRORS: string;
export const npm_node_execpath: string;
export const SHLVL: string;
export const npm_config_noproxy: string;
export const HOME: string;
export const OLDPWD: string;
export const VSCODE_RECONNECTION_GRACE_TIME: string;
export const VSCODE_IPC_HOOK_CLI: string;
export const npm_package_json: string;
export const PS1: string;
export const npm_config_userconfig: string;
@@ -49,11 +42,9 @@ declare module '$env/static/private' {
export const WSL_DISTRO_NAME: string;
export const COLOR: string;
export const WAYLAND_DISPLAY: string;
export const VSCODE_L10N_BUNDLE_LOCATION: string;
export const LOGNAME: string;
export const NAME: string;
export const WSL_INTEROP: string;
export const VSCODE_HANDLES_SIGPIPE: string;
export const PULSE_SERVER: string;
export const _: string;
export const npm_config_prefix: string;
@@ -73,15 +64,12 @@ declare module '$env/static/private' {
export const SHELL: string;
export const npm_package_version: string;
export const npm_lifecycle_event: string;
export const ELECTRON_RUN_AS_NODE: string;
export const KILOCODE_POSTHOG_API_KEY: string;
export const GOOGLE_CLOUD_PROJECT: string;
export const LESSCLOSE: string;
export const VIRTUAL_ENV: string;
export const npm_config_globalconfig: string;
export const npm_config_init_module: string;
export const PWD: string;
export const LC_ALL: string;
export const npm_execpath: string;
export const XDG_DATA_DIRS: string;
export const npm_config_global_prefix: string;
@@ -104,7 +92,7 @@ declare module '$env/static/private' {
* ```
*/
declare module '$env/static/public' {
export const PUBLIC_WS_URL: string;
}
/**
@@ -122,20 +110,13 @@ declare module '$env/static/public' {
declare module '$env/dynamic/private' {
export const env: {
LESSOPEN: string;
VSCODE_CWD: string;
VSCODE_ESM_ENTRYPOINT: string;
USER: string;
VSCODE_NLS_CONFIG: string;
npm_config_user_agent: string;
VSCODE_WSL_EXT_LOCATION: string;
VSCODE_HANDLES_UNCAUGHT_ERRORS: string;
npm_node_execpath: string;
SHLVL: string;
npm_config_noproxy: string;
HOME: string;
OLDPWD: string;
VSCODE_RECONNECTION_GRACE_TIME: string;
VSCODE_IPC_HOOK_CLI: string;
npm_package_json: string;
PS1: string;
npm_config_userconfig: string;
@@ -144,11 +125,9 @@ declare module '$env/dynamic/private' {
WSL_DISTRO_NAME: string;
COLOR: string;
WAYLAND_DISPLAY: string;
VSCODE_L10N_BUNDLE_LOCATION: string;
LOGNAME: string;
NAME: string;
WSL_INTEROP: string;
VSCODE_HANDLES_SIGPIPE: string;
PULSE_SERVER: string;
_: string;
npm_config_prefix: string;
@@ -168,15 +147,12 @@ declare module '$env/dynamic/private' {
SHELL: string;
npm_package_version: string;
npm_lifecycle_event: string;
ELECTRON_RUN_AS_NODE: string;
KILOCODE_POSTHOG_API_KEY: string;
GOOGLE_CLOUD_PROJECT: string;
LESSCLOSE: string;
VIRTUAL_ENV: string;
npm_config_globalconfig: string;
npm_config_init_module: string;
PWD: string;
LC_ALL: string;
npm_execpath: string;
XDG_DATA_DIRS: string;
npm_config_global_prefix: string;
@@ -204,6 +180,7 @@ declare module '$env/dynamic/private' {
*/
declare module '$env/dynamic/public' {
export const env: {
PUBLIC_WS_URL: string;
[key: `PUBLIC_${string}`]: string | undefined;
}
}

View File

@@ -24,7 +24,7 @@ export const options = {
app: ({ head, body, assets, nonce, env }) => "<!DOCTYPE html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<link rel=\"icon\" href=\"" + assets + "/favicon.png\" />\n\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n\t\t" + head + "\n\t</head>\n\t<body data-sveltekit-preload-data=\"hover\">\n\t\t<div style=\"display: contents\">" + body + "</div>\n\t</body>\n</html>\n",
error: ({ status, message }) => "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<title>" + message + "</title>\n\n\t\t<style>\n\t\t\tbody {\n\t\t\t\t--bg: white;\n\t\t\t\t--fg: #222;\n\t\t\t\t--divider: #ccc;\n\t\t\t\tbackground: var(--bg);\n\t\t\t\tcolor: var(--fg);\n\t\t\t\tfont-family:\n\t\t\t\t\tsystem-ui,\n\t\t\t\t\t-apple-system,\n\t\t\t\t\tBlinkMacSystemFont,\n\t\t\t\t\t'Segoe UI',\n\t\t\t\t\tRoboto,\n\t\t\t\t\tOxygen,\n\t\t\t\t\tUbuntu,\n\t\t\t\t\tCantarell,\n\t\t\t\t\t'Open Sans',\n\t\t\t\t\t'Helvetica Neue',\n\t\t\t\t\tsans-serif;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\theight: 100vh;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t.error {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tmax-width: 32rem;\n\t\t\t\tmargin: 0 1rem;\n\t\t\t}\n\n\t\t\t.status {\n\t\t\t\tfont-weight: 200;\n\t\t\t\tfont-size: 3rem;\n\t\t\t\tline-height: 1;\n\t\t\t\tposition: relative;\n\t\t\t\ttop: -0.05rem;\n\t\t\t}\n\n\t\t\t.message {\n\t\t\t\tborder-left: 1px solid var(--divider);\n\t\t\t\tpadding: 0 0 0 1rem;\n\t\t\t\tmargin: 0 0 0 1rem;\n\t\t\t\tmin-height: 2.5rem;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t}\n\n\t\t\t.message h1 {\n\t\t\t\tfont-weight: 400;\n\t\t\t\tfont-size: 1em;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\tbody {\n\t\t\t\t\t--bg: #222;\n\t\t\t\t\t--fg: #ddd;\n\t\t\t\t\t--divider: #666;\n\t\t\t\t}\n\t\t\t}\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<div class=\"error\">\n\t\t\t<span class=\"status\">" + status + "</span>\n\t\t\t<div class=\"message\">\n\t\t\t\t<h1>" + message + "</h1>\n\t\t\t</div>\n\t\t</div>\n\t</body>\n</html>\n"
},
version_hash: "1ynwnul"
version_hash: "cx7alu"
};
export async function get_hooks() {

View File

@@ -1,14 +1,14 @@
{
".svelte-kit/generated/client-optimized/app.js": {
"file": "_app/immutable/entry/app.B-xBk5-0.js",
"file": "_app/immutable/entry/app.BXnpILpp.js",
"name": "entry/app",
"src": ".svelte-kit/generated/client-optimized/app.js",
"isEntry": true,
"imports": [
"_CQO205-B.js",
"_CWb4Vnhz.js",
"_CqZim_6h.js",
"_C98uKxzC.js"
"_BtL0wB3H.js",
"_cv2LK44M.js",
"_BxZpmA7Z.js",
"_vVxDbqKK.js"
],
"dynamicImports": [
".svelte-kit/generated/client-optimized/nodes/0.js",
@@ -18,142 +18,145 @@
]
},
".svelte-kit/generated/client-optimized/nodes/0.js": {
"file": "_app/immutable/nodes/0.Cd4CVt-Z.js",
"file": "_app/immutable/nodes/0.DZdF_zz-.js",
"name": "nodes/0",
"src": ".svelte-kit/generated/client-optimized/nodes/0.js",
"isEntry": true,
"isDynamicEntry": true,
"imports": [
"_CWb4Vnhz.js",
"_CCsGeFPC.js",
"_CQO205-B.js",
"_DKg_yD9X.js",
"_BEiADdeo.js",
"_CsANhQOh.js"
"_cv2LK44M.js",
"_CRLlKr96.js",
"_BtL0wB3H.js",
"_xdjHc-A2.js",
"_DXE57cnx.js",
"_Dbod7Wv8.js"
],
"css": [
"_app/immutable/assets/0.RZHRvmcL.css"
]
},
".svelte-kit/generated/client-optimized/nodes/1.js": {
"file": "_app/immutable/nodes/1.CppBCq8O.js",
"file": "_app/immutable/nodes/1.Bh-fCbID.js",
"name": "nodes/1",
"src": ".svelte-kit/generated/client-optimized/nodes/1.js",
"isEntry": true,
"isDynamicEntry": true,
"imports": [
"_CWb4Vnhz.js",
"_CCsGeFPC.js",
"_CQO205-B.js",
"_BEiADdeo.js"
"_cv2LK44M.js",
"_CRLlKr96.js",
"_BtL0wB3H.js",
"_DXE57cnx.js"
]
},
".svelte-kit/generated/client-optimized/nodes/2.js": {
"file": "_app/immutable/nodes/2.DbjHrap6.js",
"file": "_app/immutable/nodes/2.BmiXdPHI.js",
"name": "nodes/2",
"src": ".svelte-kit/generated/client-optimized/nodes/2.js",
"isEntry": true,
"isDynamicEntry": true,
"imports": [
"_BGnnHgKo.js",
"_CWb4Vnhz.js",
"_CCsGeFPC.js",
"_CQO205-B.js",
"_C98uKxzC.js",
"_CsANhQOh.js",
"_CqZim_6h.js",
"_DKg_yD9X.js"
"_DyPeVqDG.js",
"_cv2LK44M.js",
"_CRLlKr96.js",
"_BtL0wB3H.js",
"_vVxDbqKK.js",
"_Dbod7Wv8.js",
"_BxZpmA7Z.js",
"_xdjHc-A2.js"
]
},
".svelte-kit/generated/client-optimized/nodes/3.js": {
"file": "_app/immutable/nodes/3.BgpIj6zk.js",
"file": "_app/immutable/nodes/3.guWMyWpk.js",
"name": "nodes/3",
"src": ".svelte-kit/generated/client-optimized/nodes/3.js",
"isEntry": true,
"isDynamicEntry": true,
"imports": [
"_BGnnHgKo.js",
"_CWb4Vnhz.js",
"_CCsGeFPC.js",
"_CQO205-B.js",
"_C98uKxzC.js",
"_CsANhQOh.js"
"_DyPeVqDG.js",
"_cv2LK44M.js",
"_CRLlKr96.js",
"_BtL0wB3H.js",
"_vVxDbqKK.js",
"_Dbod7Wv8.js"
]
},
"_BEiADdeo.js": {
"file": "_app/immutable/chunks/BEiADdeo.js",
"name": "stores",
"imports": [
"_CHnJS4Dz.js"
]
},
"_BGnnHgKo.js": {
"file": "_app/immutable/chunks/BGnnHgKo.js",
"name": "api",
"imports": [
"_CQO205-B.js",
"_CsANhQOh.js"
]
},
"_C98uKxzC.js": {
"file": "_app/immutable/chunks/C98uKxzC.js",
"name": "props",
"imports": [
"_CQO205-B.js",
"_CWb4Vnhz.js"
]
},
"_CCsGeFPC.js": {
"file": "_app/immutable/chunks/CCsGeFPC.js",
"name": "legacy",
"imports": [
"_CQO205-B.js"
]
},
"_CHnJS4Dz.js": {
"file": "_app/immutable/chunks/CHnJS4Dz.js",
"name": "entry",
"imports": [
"_CQO205-B.js",
"_CqZim_6h.js"
]
},
"_CQO205-B.js": {
"file": "_app/immutable/chunks/CQO205-B.js",
"_BtL0wB3H.js": {
"file": "_app/immutable/chunks/BtL0wB3H.js",
"name": "index"
},
"_CWb4Vnhz.js": {
"file": "_app/immutable/chunks/CWb4Vnhz.js",
"name": "disclose-version",
"imports": [
"_CQO205-B.js"
]
},
"_CqZim_6h.js": {
"file": "_app/immutable/chunks/CqZim_6h.js",
"_BxZpmA7Z.js": {
"file": "_app/immutable/chunks/BxZpmA7Z.js",
"name": "index-client",
"imports": [
"_CQO205-B.js"
"_BtL0wB3H.js"
]
},
"_CsANhQOh.js": {
"file": "_app/immutable/chunks/CsANhQOh.js",
"_CRLlKr96.js": {
"file": "_app/immutable/chunks/CRLlKr96.js",
"name": "legacy",
"imports": [
"_BtL0wB3H.js"
]
},
"_D0iaTcAo.js": {
"file": "_app/immutable/chunks/D0iaTcAo.js",
"name": "entry",
"imports": [
"_BtL0wB3H.js",
"_BxZpmA7Z.js"
]
},
"_DXE57cnx.js": {
"file": "_app/immutable/chunks/DXE57cnx.js",
"name": "stores",
"imports": [
"_D0iaTcAo.js"
]
},
"_Dbod7Wv8.js": {
"file": "_app/immutable/chunks/Dbod7Wv8.js",
"name": "toasts",
"imports": [
"_CQO205-B.js"
"_BtL0wB3H.js"
]
},
"_DKg_yD9X.js": {
"file": "_app/immutable/chunks/DKg_yD9X.js",
"_DyPeVqDG.js": {
"file": "_app/immutable/chunks/DyPeVqDG.js",
"name": "api",
"imports": [
"_BtL0wB3H.js",
"_Dbod7Wv8.js"
]
},
"_cv2LK44M.js": {
"file": "_app/immutable/chunks/cv2LK44M.js",
"name": "disclose-version",
"imports": [
"_BtL0wB3H.js"
]
},
"_vVxDbqKK.js": {
"file": "_app/immutable/chunks/vVxDbqKK.js",
"name": "props",
"imports": [
"_BtL0wB3H.js",
"_cv2LK44M.js"
]
},
"_xdjHc-A2.js": {
"file": "_app/immutable/chunks/xdjHc-A2.js",
"name": "class",
"imports": [
"_CQO205-B.js"
"_BtL0wB3H.js"
]
},
"node_modules/@sveltejs/kit/src/runtime/client/entry.js": {
"file": "_app/immutable/entry/start.CiUb2lZD.js",
"file": "_app/immutable/entry/start.BHAeOrfR.js",
"name": "entry/start",
"src": "node_modules/@sveltejs/kit/src/runtime/client/entry.js",
"isEntry": true,
"imports": [
"_CHnJS4Dz.js"
"_D0iaTcAo.js"
]
}
}

View File

@@ -1 +0,0 @@
import{s as e}from"./CHnJS4Dz.js";const r=()=>{const s=e;return{page:{subscribe:s.page.subscribe},navigating:{subscribe:s.navigating.subscribe},updated:s.updated}},b={subscribe(s){return r().page.subscribe(s)}};export{b as p};

View File

@@ -1 +0,0 @@
import{h as v,Z as S,a5 as T,a6 as b,a7 as C,a8 as P,a9 as I,aa as w,ab as N,e as $,ac as p,J as i,ad as L}from"./CQO205-B.js";import{a as m}from"./CsANhQOh.js";const M=Symbol("is custom element"),q=Symbol("is html");function F(e){if(v){var t=!1,r=()=>{if(!t){if(t=!0,e.hasAttribute("value")){var s=e.value;k(e,"value",null),e.value=s}if(e.hasAttribute("checked")){var a=e.checked;k(e,"checked",null),e.checked=a}}};e.__on_r=r,S(r),T()}}function k(e,t,r,s){var a=j(e);v&&(a[t]=e.getAttribute(t),t==="src"||t==="srcset"||t==="href"&&e.nodeName==="LINK")||a[t]!==(a[t]=r)&&(t==="loading"&&(e[b]=r),r==null?e.removeAttribute(t):typeof r!="string"&&O(e).includes(t)?e[t]=r:e.setAttribute(t,r))}function j(e){return e.__attributes??={[M]:e.nodeName.includes("-"),[q]:e.namespaceURI===C}}var y=new Map;function O(e){var t=e.getAttribute("is")||e.nodeName,r=y.get(t);if(r)return r;y.set(t,r=[]);for(var s,a=e,o=Element.prototype;o!==a;){s=I(a);for(var n in s)s[n].set&&r.push(n);a=P(a)}return r}function U(e,t,r=t){var s=new WeakSet;w(e,"input",async a=>{var o=a?e.defaultValue:e.value;if(o=u(e)?h(o):o,r(o),i!==null&&s.add(i),await N(),o!==(o=t())){var n=e.selectionStart,f=e.selectionEnd,A=e.value.length;if(e.value=o??"",f!==null){var d=e.value.length;n===f&&f===A&&d>A?(e.selectionStart=d,e.selectionEnd=d):(e.selectionStart=n,e.selectionEnd=Math.min(f,d))}}}),(v&&e.defaultValue!==e.value||$(t)==null&&e.value)&&(r(u(e)?h(e.value):e.value),i!==null&&s.add(i)),p(()=>{var a=t();if(e===document.activeElement){var o=L??i;if(s.has(o))return}u(e)&&a===h(e.value)||e.type==="date"&&!a&&!e.value||a!==e.value&&(e.value=a??"")})}function B(e,t,r=t){w(e,"change",s=>{var a=s?e.defaultChecked:e.checked;r(a)}),(v&&e.defaultChecked!==e.checked||$(t)==null)&&r(e.checked),p(()=>{var s=t();e.checked=!!s})}function u(e){var t=e.type;return t==="number"||t==="range"}function h(e){return e===""?null:+e}const E="/api";async function c(e){try{console.log(`[api.fetchApi][Action] Fetching from context={{'endpoint': '${e}'}}`);const t=await fetch(`${E}${e}`);if(!t.ok)throw new Error(`API request failed with status ${t.status}`);return await t.json()}catch(t){throw console.error(`[api.fetchApi][Coherence:Failed] Error fetching from ${e}:`,t),m(t.message,"error"),t}}async function _(e,t){try{console.log(`[api.postApi][Action] Posting to context={{'endpoint': '${e}'}}`);const r=await fetch(`${E}${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)});if(!r.ok)throw new Error(`API request failed with status ${r.status}`);return await r.json()}catch(r){throw console.error(`[api.postApi][Coherence:Failed] Error posting to ${e}:`,r),m(r.message,"error"),r}}async function g(e,t="GET",r=null){try{console.log(`[api.requestApi][Action] ${t} to context={{'endpoint': '${e}'}}`);const s={method:t,headers:{"Content-Type":"application/json"}};r&&(s.body=JSON.stringify(r));const a=await fetch(`${E}${e}`,s);if(!a.ok){const o=await a.json().catch(()=>({}));throw new Error(o.detail||`API request failed with status ${a.status}`)}return await a.json()}catch(s){throw console.error(`[api.requestApi][Coherence:Failed] Error ${t} to ${e}:`,s),m(s.message,"error"),s}}const l={getPlugins:()=>c("/plugins/"),getTasks:()=>c("/tasks/"),getTask:e=>c(`/tasks/${e}`),createTask:(e,t)=>_("/tasks/",{plugin_id:e,params:t}),getSettings:()=>c("/settings/"),updateGlobalSettings:e=>g("/settings/global","PATCH",e),getEnvironments:()=>c("/settings/environments"),addEnvironment:e=>_("/settings/environments",e),updateEnvironment:(e,t)=>g(`/settings/environments/${e}`,"PUT",t),deleteEnvironment:e=>g(`/settings/environments/${e}`,"DELETE"),testEnvironmentConnection:e=>_(`/settings/environments/${e}/test`,{})},D=l.updateGlobalSettings,H=l.addEnvironment,J=l.updateEnvironment,R=l.deleteEnvironment,V=l.testEnvironmentConnection;export{l as a,U as b,B as c,J as d,H as e,R as f,F as r,k as s,V as t,D as u};

View File

@@ -1 +0,0 @@
import{J as E,S as y,X as S,T as L,v as P,L as T,h as g,E as M,ae as k,M as w,w as B,z as N,af as x,B as Y,H as C,C as F,x as U,D as R,ag as j,ah as q,j as b,ai as z,o as H,aj as $,ak as G,A as J,d as X,al as Z,am as K,an as Q,ao as V,ap as W,a3 as ee,e as te,aq as ae,ar as se,as as re}from"./CQO205-B.js";import{d as ie}from"./CWb4Vnhz.js";class ne{anchor;#t=new Map;#a=new Map;#e=new Map;#s=new Set;#r=!0;constructor(e,a=!0){this.anchor=e,this.#r=a}#i=()=>{var e=E;if(this.#t.has(e)){var a=this.#t.get(e),t=this.#a.get(a);if(t)y(t),this.#s.delete(a);else{var n=this.#e.get(a);n&&(this.#a.set(a,n.effect),this.#e.delete(a),n.fragment.lastChild.remove(),this.anchor.before(n.fragment),t=n.effect)}for(const[s,i]of this.#t){if(this.#t.delete(s),s===e)break;const r=this.#e.get(i);r&&(S(r.effect),this.#e.delete(i))}for(const[s,i]of this.#a){if(s===a||this.#s.has(s))continue;const r=()=>{if(Array.from(this.#t.values()).includes(s)){var h=document.createDocumentFragment();k(i,h),h.append(P()),this.#e.set(s,{effect:i,fragment:h})}else S(i);this.#s.delete(s),this.#a.delete(s)};this.#r||!t?(this.#s.add(s),L(i,r,!1)):r()}}};#n=e=>{this.#t.delete(e);const a=Array.from(this.#t.values());for(const[t,n]of this.#e)a.includes(t)||(S(n.effect),this.#e.delete(t))};ensure(e,a){var t=E,n=w();if(a&&!this.#a.has(e)&&!this.#e.has(e))if(n){var s=document.createDocumentFragment(),i=P();s.append(i),this.#e.set(e,{effect:T(()=>a(i)),fragment:s})}else this.#a.set(e,T(()=>a(this.anchor)));if(this.#t.set(t,e),n){for(const[r,f]of this.#a)r===e?t.skipped_effects.delete(f):t.skipped_effects.add(f);for(const[r,f]of this.#e)r===e?t.skipped_effects.delete(f.effect):t.skipped_effects.add(f.effect);t.oncommit(this.#i),t.ondiscard(this.#n)}else g&&(this.anchor=M),this.#i()}}function de(d,e,a=!1){g&&N();var t=new ne(d),n=a?x:0;function s(i,r){if(g){const h=Y(d)===C;if(i===h){var f=F();U(f),t.anchor=f,R(!1),t.ensure(i,r),R(!0);return}}t.ensure(i,r)}B(()=>{var i=!1;e((r,f=!0)=>{i=!0,s(f,r)}),i||s(!1,null)},n)}function he(d,e,a,t){var n=!X||(a&Z)!==0,s=(a&K)!==0,i=(a&re)!==0,r=t,f=!0,h=()=>(f&&(f=!1,r=i?te(t):t),r),o;if(s){var I=V in d||W in d;o=j(d,e)?.set??(I&&e in d?c=>d[e]=c:void 0)}var _,p=!1;s?[_,p]=ie(()=>d[e]):_=d[e],_===void 0&&t!==void 0&&(_=h(),o&&(n&&ae(),o(_)));var u;if(n?u=()=>{var c=d[e];return c===void 0?h():(f=!0,c)}:u=()=>{var c=d[e];return c!==void 0&&(r=void 0),c===void 0?r:c},n&&(a&q)===0)return u;if(o){var D=d.$$legacy;return(function(c,v){return arguments.length>0?((!n||!v||D||p)&&o(v?u():c),c):u()})}var m=!1,l=((a&se)!==0?ee:J)(()=>(m=!1,u()));s&&b(l);var O=$;return(function(c,v){if(arguments.length>0){const A=v?b(l):n&&s?z(c):c;return H(l,A),m=!0,r!==void 0&&(r=A),c}return Q&&m||(O.f&G)!==0?l.v:b(l)})}export{ne as B,de as i,he as p};

View File

@@ -1 +0,0 @@
import{b as d,a0 as g,u as c,e as b,a1 as i,a2 as m,j as p,k,a3 as v,a4 as h}from"./CQO205-B.js";function x(a=!1){const s=d,e=s.l.u;if(!e)return;let f=()=>k(s.s);if(a){let n=0,t={};const _=v(()=>{let l=!1;const r=s.s;for(const o in r)r[o]!==t[o]&&(t[o]=r[o],l=!0);return l&&n++,n});f=()=>p(_)}e.b.length&&g(()=>{u(s,f),i(e.b)}),c(()=>{const n=b(()=>e.m.map(m));return()=>{for(const t of n)typeof t=="function"&&t()}}),e.a.length&&c(()=>{u(s,f),i(e.a)})}function u(a,s){if(a.l.s)for(const e of a.l.s)p(e);s()}h();export{x as i};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
import{l as a,u as i,b as t,d as _,e as l,i as m}from"./CQO205-B.js";function p(e){t===null&&a(),_&&t.l!==null?d(t).m.push(e):i(()=>{const n=l(e);if(typeof n=="function")return n})}function x(e){t===null&&a(),p(()=>()=>l(e))}function v(e,n,{bubbles:o=!1,cancelable:s=!1}={}){return new CustomEvent(e,{detail:n,bubbles:o,cancelable:s})}function y(){const e=t;return e===null&&a(),(n,o,s)=>{const c=e.s.$$events?.[n];if(c){const r=m(c)?c.slice():[c],u=v(n,o,s);for(const f of r)f.call(e.x,u);return!u.defaultPrevented}return!0}}function d(e){var n=e.l;return n.u??={a:[],b:[],m:[]}}export{x as a,y as c,p as o};

View File

@@ -1 +0,0 @@
import{v as H,w as W,x as O,h as R,y as Z,z as y,j as V,A as j,B as ee,H as ne,C as Y,D as k,E as D,F as re,G as fe,I as q,J as ae,K as m,L as F,M as ie,N as B,m as se,O as $,i as le,P as oe,Q as te,R as ue,S as X,T as G,U as z,V as de,W as ve,X as ce,Y as J,Z as pe,_ as ge,$ as he}from"./CQO205-B.js";function Se(e,r){return r}function _e(e,r,f){for(var t=[],g=r.length,o,l=r.length,p=0;p<g;p++){let _=r[p];G(_,()=>{if(o){if(o.pending.delete(_),o.done.add(_),o.pending.size===0){var u=e.outrogroups;L($(o.done)),u.delete(o),u.size===0&&(e.outrogroups=null)}}else l-=1},!1)}if(l===0){var i=t.length===0&&f!==null;if(i){var d=f,a=d.parentNode;ve(a),a.append(d),e.items.clear()}L(r,!i)}else o={pending:new Set(r),done:new Set},(e.outrogroups??=new Set).add(o)}function L(e,r=!0){for(var f=0;f<e.length;f++)ce(e[f],r)}var U;function we(e,r,f,t,g,o=null){var l=e,p=new Map,i=(r&J)!==0;if(i){var d=e;l=R?O(Z(d)):d.appendChild(H())}R&&y();var a=null,_=j(()=>{var s=f();return le(s)?s:s==null?[]:$(s)}),u,c=!0;function A(){n.fallback=a,Ee(n,u,l,r,t),a!==null&&(u.length===0?(a.f&m)===0?X(a):(a.f^=m,b(a,null,l)):G(a,()=>{a=null}))}var C=W(()=>{u=V(_);var s=u.length;let I=!1;if(R){var x=ee(l)===ne;x!==(s===0)&&(l=Y(),O(l),k(!1),I=!0)}for(var E=new Set,S=ae,N=ie(),h=0;h<s;h+=1){R&&D.nodeType===re&&D.data===fe&&(l=D,I=!0,k(!1));var w=u[h],M=t(w,h),v=c?null:p.get(M);v?(v.v&&q(v.v,w),v.i&&q(v.i,h),N&&S.skipped_effects.delete(v.e)):(v=me(p,c?l:U??=H(),w,M,h,g,r,f),c||(v.e.f|=m),p.set(M,v)),E.add(M)}if(s===0&&o&&!a&&(c?a=F(()=>o(l)):(a=F(()=>o(U??=H())),a.f|=m)),R&&s>0&&O(Y()),!c)if(N){for(const[P,Q]of p)E.has(P)||S.skipped_effects.add(Q.e);S.oncommit(A),S.ondiscard(()=>{})}else A();I&&k(!0),V(_)}),n={effect:C,items:p,outrogroups:null,fallback:a};c=!1,R&&(l=D)}function Ee(e,r,f,t,g){var o=(t&ge)!==0,l=r.length,p=e.items,i=e.effect.first,d,a=null,_,u=[],c=[],A,C,n,s;if(o)for(s=0;s<l;s+=1)A=r[s],C=g(A,s),n=p.get(C).e,(n.f&m)===0&&(n.nodes?.a?.measure(),(_??=new Set).add(n));for(s=0;s<l;s+=1){if(A=r[s],C=g(A,s),n=p.get(C).e,e.outrogroups!==null)for(const v of e.outrogroups)v.pending.delete(n),v.done.delete(n);if((n.f&m)!==0)if(n.f^=m,n===i)b(n,null,f);else{var I=a?a.next:i;n===e.effect.last&&(e.effect.last=n.prev),n.prev&&(n.prev.next=n.next),n.next&&(n.next.prev=n.prev),T(e,a,n),T(e,n,I),b(n,I,f),a=n,u=[],c=[],i=a.next;continue}if((n.f&z)!==0&&(X(n),o&&(n.nodes?.a?.unfix(),(_??=new Set).delete(n))),n!==i){if(d!==void 0&&d.has(n)){if(u.length<c.length){var x=c[0],E;a=x.prev;var S=u[0],N=u[u.length-1];for(E=0;E<u.length;E+=1)b(u[E],x,f);for(E=0;E<c.length;E+=1)d.delete(c[E]);T(e,S.prev,N.next),T(e,a,S),T(e,N,x),i=x,a=N,s-=1,u=[],c=[]}else d.delete(n),b(n,i,f),T(e,n.prev,n.next),T(e,n,a===null?e.effect.first:a.next),T(e,a,n),a=n;continue}for(u=[],c=[];i!==null&&i!==n;)(d??=new Set).add(i),c.push(i),i=i.next;if(i===null)continue}(n.f&m)===0&&u.push(n),a=n,i=n.next}if(e.outrogroups!==null){for(const v of e.outrogroups)v.pending.size===0&&(L($(v.done)),e.outrogroups?.delete(v));e.outrogroups.size===0&&(e.outrogroups=null)}if(i!==null||d!==void 0){var h=[];if(d!==void 0)for(n of d)(n.f&z)===0&&h.push(n);for(;i!==null;)(i.f&z)===0&&i!==e.fallback&&h.push(i),i=i.next;var w=h.length;if(w>0){var M=(t&J)!==0&&l===0?f:null;if(o){for(s=0;s<w;s+=1)h[s].nodes?.a?.measure();for(s=0;s<w;s+=1)h[s].nodes?.a?.fix()}_e(e,h,M)}}o&&pe(()=>{if(_!==void 0)for(n of _)n.nodes?.a?.apply()})}function me(e,r,f,t,g,o,l,p){var i=(l&oe)!==0?(l&te)===0?se(f,!1,!1):B(f):null,d=(l&ue)!==0?B(g):null;return{v:i,i:d,e:F(()=>(o(r,i??f,d??g,p),()=>{e.delete(t)}))}}function b(e,r,f){if(e.nodes)for(var t=e.nodes.start,g=e.nodes.end,o=r&&(r.f&m)===0?r.nodes.start:f;t!==null;){var l=de(t);if(o.before(t),t===g)return;t=l}}function T(e,r,f){r===null?e.effect.first=f:r.next=f,f===null?e.effect.last=r:f.prev=r}const K=he([]);function Ce(e,r="info",f=3e3){const t=Math.random().toString(36).substr(2,9);console.log(`[toasts.addToast][Action] Adding toast context={{'id': '${t}', 'type': '${r}', 'message': '${e}'}}`),K.update(g=>[...g,{id:t,message:e,type:r}]),setTimeout(()=>Te(t),f)}function Te(e){console.log(`[toasts.removeToast][Action] Removing toast context={{'id': '${e}'}}`),K.update(r=>r.filter(f=>f.id!==e))}export{Ce as a,we as e,Se as i,K as t};

View File

@@ -1 +0,0 @@
import{h as i}from"./CQO205-B.js";function n(s,l,r){var t=s==null?"":""+s;return t===""?null:t}function u(s,l,r,t,f,c){var a=s.__className;if(i||a!==r||a===void 0){var e=n(r);(!i||e!==s.getAttribute("class"))&&(e==null?s.removeAttribute("class"):s.className=e),s.__className=r}return c}export{u as s};

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
import{l as o,a as r}from"../chunks/CHnJS4Dz.js";export{o as load_css,r as start};

View File

@@ -1,5 +0,0 @@
import{f as u,a as c,s as b,c as g,b as x}from"../chunks/CWb4Vnhz.js";import{i as _}from"../chunks/CCsGeFPC.js";import{h as $,z as y,p as w,t as h,a as S,s as d,c as v,r as l,j as p,at as j}from"../chunks/CQO205-B.js";import{s as m}from"../chunks/DKg_yD9X.js";import{p as T}from"../chunks/BEiADdeo.js";import{e as z,t as O}from"../chunks/CsANhQOh.js";function k(r,a,s,o,n){$&&y();var e=a.$$slots?.[s],t=!1;e===!0&&(e=a.children,t=!0),e===void 0||e(r,t?()=>o:o)}const A=!1,D=!1,Q=Object.freeze(Object.defineProperty({__proto__:null,prerender:D,ssr:A},Symbol.toStringTag,{value:"Module"}));var F=u('<header class="bg-white shadow-md p-4 flex justify-between items-center"><a href="/" class="text-3xl font-bold text-gray-800 focus:outline-none">Superset Tools</a> <nav class="space-x-4"><a href="/">Dashboard</a> <a href="/settings">Settings</a></nav></header>');function M(r,a){w(a,!1);const s=()=>g(T,"$page",o),[o,n]=b();_();var e=F(),t=d(v(e),2),i=v(t),f=d(i,2);l(t),l(e),h(()=>{m(i,1,`text-gray-600 hover:text-blue-600 font-medium ${s().url.pathname==="/"?"text-blue-600 border-b-2 border-blue-600":""}`),m(f,1,`text-gray-600 hover:text-blue-600 font-medium ${s().url.pathname==="/settings"?"text-blue-600 border-b-2 border-blue-600":""}`)}),c(r,e),S(),n()}var N=u('<footer class="bg-white border-t p-4 mt-8 text-center text-gray-500 text-sm">&copy; 2025 Superset Tools. All rights reserved.</footer>');function P(r){var a=N();c(r,a)}var q=u("<div> </div>"),B=u('<div class="fixed bottom-0 right-0 p-4 space-y-2"></div>');function C(r){const a=()=>g(O,"$toasts",s),[s,o]=b();var n=B();z(n,5,a,e=>e.id,(e,t)=>{var i=q(),f=v(i,!0);l(i),h(()=>{m(i,1,`p-4 rounded-md shadow-lg text-white
${p(t).type==="info"&&"bg-blue-500"}
${p(t).type==="success"&&"bg-green-500"}
${p(t).type==="error"&&"bg-red-500"}
`),x(f,p(t).message)}),c(e,i)}),l(n),c(r,n),o()}var E=u('<!> <main class="bg-gray-50 min-h-screen flex flex-col"><!> <div class="p-4 flex-grow"><!></div> <!></main>',1);function R(r,a){var s=E(),o=j(s);C(o);var n=d(o,2),e=v(n);M(e,{});var t=d(e,2),i=v(t);k(i,a,"default",{}),l(t);var f=d(t,2);P(f),l(n),c(r,s)}export{R as component,Q as universal};

View File

@@ -1 +0,0 @@
import{f,a as g,s as u,b as p,c as b}from"../chunks/CWb4Vnhz.js";import{i as d}from"../chunks/CCsGeFPC.js";import{p as h,t as v,a as _,c as e,r as s,s as $,n as y}from"../chunks/CQO205-B.js";import{p as k}from"../chunks/BEiADdeo.js";var w=f('<div class="container mx-auto p-4 text-center mt-20"><h1 class="text-6xl font-bold text-gray-800 mb-4"> </h1> <p class="text-2xl text-gray-600 mb-8"> </p> <a href="/" class="bg-blue-500 text-white px-6 py-3 rounded-lg hover:bg-blue-600 transition-colors">Back to Dashboard</a></div>');function q(n,i){h(i,!1);const r=()=>b(k,"$page",c),[c,l]=u();d();var t=w(),a=e(t),m=e(a,!0);s(a);var o=$(a,2),x=e(o,!0);s(o),y(2),s(t),v(()=>{p(m,r().status),p(x,r().error?.message||"Page not found")}),g(n,t),_(),l()}export{q as component};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":"1766259433446"}
{"version":"1766262590857"}

View File

@@ -1 +1 @@
export const env={}
export const env={"PUBLIC_WS_URL":"ws://localhost:8000"}

View File

@@ -129,6 +129,9 @@
"_index2.js",
"_stores.js",
"_toasts.js"
],
"css": [
"_app/immutable/assets/_layout.RZHRvmcL.css"
]
},
"src/routes/+layout.ts": {

View File

@@ -950,7 +950,7 @@ const options = {
<div class="error">
<span class="status">` + status + '</span>\n <div class="message">\n <h1>' + message + "</h1>\n </div>\n </div>\n </body>\n</html>\n"
},
version_hash: "1uq6ubj"
version_hash: "1ootf77"
};
async function get_hooks() {
let handle;

View File

@@ -10,7 +10,7 @@ return {
assets: new Set([]),
mimeTypes: {},
_: {
client: {start:"_app/immutable/entry/start.CiUb2lZD.js",app:"_app/immutable/entry/app.B-xBk5-0.js",imports:["_app/immutable/entry/start.CiUb2lZD.js","_app/immutable/chunks/CHnJS4Dz.js","_app/immutable/chunks/CQO205-B.js","_app/immutable/chunks/CqZim_6h.js","_app/immutable/entry/app.B-xBk5-0.js","_app/immutable/chunks/CQO205-B.js","_app/immutable/chunks/CWb4Vnhz.js","_app/immutable/chunks/CqZim_6h.js","_app/immutable/chunks/C98uKxzC.js"],stylesheets:[],fonts:[],uses_env_dynamic_public:false},
client: {start:"_app/immutable/entry/start.BHAeOrfR.js",app:"_app/immutable/entry/app.BXnpILpp.js",imports:["_app/immutable/entry/start.BHAeOrfR.js","_app/immutable/chunks/D0iaTcAo.js","_app/immutable/chunks/BtL0wB3H.js","_app/immutable/chunks/BxZpmA7Z.js","_app/immutable/entry/app.BXnpILpp.js","_app/immutable/chunks/BtL0wB3H.js","_app/immutable/chunks/cv2LK44M.js","_app/immutable/chunks/BxZpmA7Z.js","_app/immutable/chunks/vVxDbqKK.js"],stylesheets:[],fonts:[],uses_env_dynamic_public:false},
nodes: [
__memo(() => import('./nodes/0.js')),
__memo(() => import('./nodes/1.js')),

View File

@@ -10,7 +10,7 @@ return {
assets: new Set([]),
mimeTypes: {},
_: {
client: {start:"_app/immutable/entry/start.CiUb2lZD.js",app:"_app/immutable/entry/app.B-xBk5-0.js",imports:["_app/immutable/entry/start.CiUb2lZD.js","_app/immutable/chunks/CHnJS4Dz.js","_app/immutable/chunks/CQO205-B.js","_app/immutable/chunks/CqZim_6h.js","_app/immutable/entry/app.B-xBk5-0.js","_app/immutable/chunks/CQO205-B.js","_app/immutable/chunks/CWb4Vnhz.js","_app/immutable/chunks/CqZim_6h.js","_app/immutable/chunks/C98uKxzC.js"],stylesheets:[],fonts:[],uses_env_dynamic_public:false},
client: {start:"_app/immutable/entry/start.BHAeOrfR.js",app:"_app/immutable/entry/app.BXnpILpp.js",imports:["_app/immutable/entry/start.BHAeOrfR.js","_app/immutable/chunks/D0iaTcAo.js","_app/immutable/chunks/BtL0wB3H.js","_app/immutable/chunks/BxZpmA7Z.js","_app/immutable/entry/app.BXnpILpp.js","_app/immutable/chunks/BtL0wB3H.js","_app/immutable/chunks/cv2LK44M.js","_app/immutable/chunks/BxZpmA7Z.js","_app/immutable/chunks/vVxDbqKK.js"],stylesheets:[],fonts:[],uses_env_dynamic_public:false},
nodes: [
__memo(() => import('./nodes/0.js')),
__memo(() => import('./nodes/1.js')),

View File

@@ -8,6 +8,6 @@ export const universal = {
"prerender": false
};
export const universal_id = "src/routes/+layout.ts";
export const imports = ["_app/immutable/nodes/0.Cd4CVt-Z.js","_app/immutable/chunks/CWb4Vnhz.js","_app/immutable/chunks/CQO205-B.js","_app/immutable/chunks/CCsGeFPC.js","_app/immutable/chunks/DKg_yD9X.js","_app/immutable/chunks/BEiADdeo.js","_app/immutable/chunks/CHnJS4Dz.js","_app/immutable/chunks/CqZim_6h.js","_app/immutable/chunks/CsANhQOh.js"];
export const stylesheets = [];
export const imports = ["_app/immutable/nodes/0.DZdF_zz-.js","_app/immutable/chunks/cv2LK44M.js","_app/immutable/chunks/BtL0wB3H.js","_app/immutable/chunks/CRLlKr96.js","_app/immutable/chunks/xdjHc-A2.js","_app/immutable/chunks/DXE57cnx.js","_app/immutable/chunks/D0iaTcAo.js","_app/immutable/chunks/BxZpmA7Z.js","_app/immutable/chunks/Dbod7Wv8.js"];
export const stylesheets = ["_app/immutable/assets/0.RZHRvmcL.css"];
export const fonts = [];

View File

@@ -3,6 +3,6 @@
export const index = 1;
let component_cache;
export const component = async () => component_cache ??= (await import('../entries/pages/_error.svelte.js')).default;
export const imports = ["_app/immutable/nodes/1.CppBCq8O.js","_app/immutable/chunks/CWb4Vnhz.js","_app/immutable/chunks/CQO205-B.js","_app/immutable/chunks/CCsGeFPC.js","_app/immutable/chunks/BEiADdeo.js","_app/immutable/chunks/CHnJS4Dz.js","_app/immutable/chunks/CqZim_6h.js"];
export const imports = ["_app/immutable/nodes/1.Bh-fCbID.js","_app/immutable/chunks/cv2LK44M.js","_app/immutable/chunks/BtL0wB3H.js","_app/immutable/chunks/CRLlKr96.js","_app/immutable/chunks/DXE57cnx.js","_app/immutable/chunks/D0iaTcAo.js","_app/immutable/chunks/BxZpmA7Z.js"];
export const stylesheets = [];
export const fonts = [];

View File

@@ -9,6 +9,6 @@ export const universal = {
"load": null
};
export const universal_id = "src/routes/+page.ts";
export const imports = ["_app/immutable/nodes/2.DbjHrap6.js","_app/immutable/chunks/BGnnHgKo.js","_app/immutable/chunks/CQO205-B.js","_app/immutable/chunks/CsANhQOh.js","_app/immutable/chunks/CWb4Vnhz.js","_app/immutable/chunks/CCsGeFPC.js","_app/immutable/chunks/C98uKxzC.js","_app/immutable/chunks/CqZim_6h.js","_app/immutable/chunks/DKg_yD9X.js"];
export const imports = ["_app/immutable/nodes/2.BmiXdPHI.js","_app/immutable/chunks/DyPeVqDG.js","_app/immutable/chunks/BtL0wB3H.js","_app/immutable/chunks/Dbod7Wv8.js","_app/immutable/chunks/cv2LK44M.js","_app/immutable/chunks/CRLlKr96.js","_app/immutable/chunks/vVxDbqKK.js","_app/immutable/chunks/BxZpmA7Z.js","_app/immutable/chunks/xdjHc-A2.js"];
export const stylesheets = [];
export const fonts = [];

View File

@@ -9,6 +9,6 @@ export const universal = {
"load": null
};
export const universal_id = "src/routes/settings/+page.ts";
export const imports = ["_app/immutable/nodes/3.BgpIj6zk.js","_app/immutable/chunks/BGnnHgKo.js","_app/immutable/chunks/CQO205-B.js","_app/immutable/chunks/CsANhQOh.js","_app/immutable/chunks/CWb4Vnhz.js","_app/immutable/chunks/CCsGeFPC.js","_app/immutable/chunks/C98uKxzC.js"];
export const imports = ["_app/immutable/nodes/3.guWMyWpk.js","_app/immutable/chunks/DyPeVqDG.js","_app/immutable/chunks/BtL0wB3H.js","_app/immutable/chunks/Dbod7Wv8.js","_app/immutable/chunks/cv2LK44M.js","_app/immutable/chunks/CRLlKr96.js","_app/immutable/chunks/vVxDbqKK.js"];
export const stylesheets = [];
export const fonts = [];

View File

@@ -13,6 +13,7 @@
import { onMount, onDestroy } from 'svelte';
import { get } from 'svelte/store';
import { selectedTask, taskLogs } from '../lib/stores.js';
import { getWsUrl } from '../lib/api.js';
// [/SECTION]
let ws;
@@ -26,8 +27,7 @@
if (task) {
console.log(`[TaskRunner][Entry] Connecting to logs for task: ${task.id}`);
taskLogs.set([]); // Clear previous logs
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/ws/logs/${task.id}`;
const wsUrl = getWsUrl(task.id);
ws = new WebSocket(wsUrl);
ws.onopen = () => {

View File

@@ -4,9 +4,20 @@
// @LAYER: Infra-API
import { addToast } from './toasts.js';
import { PUBLIC_WS_URL } from '$env/static/public';
const API_BASE_URL = '/api';
/**
* Returns the WebSocket URL for a specific task, with fallback logic.
* @param {string} taskId
* @returns {string}
*/
export const getWsUrl = (taskId) => {
const baseUrl = PUBLIC_WS_URL || `ws://${window.location.hostname}:8000`;
return `${baseUrl}/ws/logs/${taskId}`;
};
// [DEF:fetchApi:Function]
// @PURPOSE: Generic GET request wrapper.
// @PARAM: endpoint (string) - API endpoint.

View File

@@ -1,4 +1,5 @@
<script>
import '../app.css';
import Navbar from '../components/Navbar.svelte';
import Footer from '../components/Footer.svelte';
import Toast from '../components/Toast.svelte';

View File

@@ -7,8 +7,7 @@ export default defineConfig({
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
changeOrigin: true
},
'/ws': {
target: 'ws://localhost:8000',

View File

@@ -0,0 +1,34 @@
# Specification Quality Checklist: Fix UI Styling, WebSocket Port Mismatch, and URL Validation
**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2025-12-20
**Feature**: [specs/001-fix-ui-ws-validation/spec.md](../spec.md)
## Content Quality
- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed
## Requirement Completeness
- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified
## Feature Readiness
- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification
## Notes
- Initial validation passed. The specification has been refined to be technology-agnostic while addressing the specific issues reported (styling, real-time communication, and URL validation).

View File

@@ -0,0 +1,31 @@
# Data Model: Fix UI Styling, WebSocket Port Mismatch, and URL Validation
## Entities
### ServiceConnection
Represents the configuration for an external service.
| Field | Type | Description | Validation |
|-------|------|-------------|------------|
| `base_url` | `AnyHttpUrl` | The base URL of the service. | Normalized to include `/api/v1` if missing. |
| `name` | `string` | Friendly name for the connection. | Required. |
| `status` | `string` | Connection status (connected, failed, etc.). | Read-only. |
### TaskLogMessage
The structure of messages sent over the WebSocket for real-time logs.
| Field | Type | Description |
|-------|------|-------------|
| `task_id` | `string` | Unique identifier for the task. |
| `message` | `string` | The log message text. |
| `timestamp` | `datetime` | When the log was generated. |
| `level` | `string` | Log level (INFO, ERROR, etc.). |
## State Transitions
### Connection Validation
1. User inputs `base_url`.
2. System validates URL format.
3. System checks for `/api/v1` suffix.
4. If missing, system appends `/api/v1`.
5. System attempts connection and updates `status`.

View File

@@ -0,0 +1,74 @@
# Implementation Plan: Fix UI Styling, WebSocket Port Mismatch, and URL Validation
**Branch**: `001-fix-ui-ws-validation` | **Date**: 2025-12-20 | **Spec**: [specs/001-fix-ui-ws-validation/spec.md](specs/001-fix-ui-ws-validation/spec.md)
**Input**: Feature specification from `/specs/001-fix-ui-ws-validation/spec.md`
## Summary
This feature addresses three critical issues: unstyled UI due to missing Tailwind CSS imports, broken real-time logs caused by WebSocket port mismatches in development, and strict URL validation that prevents successful connections to external services. The technical approach involves importing Tailwind in the root layout, using environment variables for WebSocket URLs with a fallback, and relaxing URL validation to automatically append version suffixes.
## Technical Context
**Language/Version**: Python 3.9+, Node.js 18+
**Primary Dependencies**: FastAPI, SvelteKit, Tailwind CSS, Pydantic
**Storage**: N/A (Configuration based)
**Testing**: pytest
**Target Platform**: Linux server
**Project Type**: Web application (frontend + backend)
**Performance Goals**: Real-time updates within 500ms
**Constraints**: SPA-First Architecture (No Node.js in production)
**Scale/Scope**: Targeted fixes for UI, real-time communication, and validation logic.
## Constitution Check
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
| Principle | Status | Notes |
|-----------|--------|-------|
| I. SPA-First Architecture | PASS | SvelteKit SPA will be built and served by FastAPI. Post-design: Confirmed. |
| II. API-Driven Communication | PASS | Real-time logs via WebSockets; configuration via REST. Post-design: Confirmed. |
| III. Modern Stack Consistency | PASS | Uses SvelteKit, FastAPI, and Tailwind CSS. Post-design: Confirmed. |
| IV. Semantic Protocol Adherence | PASS | Implementation will use anchors and contracts as per `semantic_protocol.md`. Post-design: Confirmed. |
## Project Structure
### Documentation (this feature)
```text
specs/001-fix-ui-ws-validation/
├── plan.md # This file
├── research.md # Phase 0 output
├── data-model.md # Phase 1 output
├── quickstart.md # Phase 1 output
├── contracts/ # Phase 1 output
└── tasks.md # Phase 2 output
```
### Source Code (repository root)
```text
backend/
├── src/
│ ├── models/ # URL validation logic in superset_tool/models.py (or equivalent)
│ ├── services/
│ └── api/ # WebSocket and REST endpoints
└── tests/
frontend/
├── src/
│ ├── components/
│ ├── pages/
│ └── routes/ # +layout.svelte for global styling
└── tests/
```
**Structure Decision**: Web application structure (Option 2) is used as both frontend and backend components are modified.
## Complexity Tracking
> **Fill ONLY if Constitution Check has violations that must be justified**
| Violation | Why Needed | Simpler Alternative Rejected Because |
|-----------|------------|-------------------------------------|
| None | N/A | N/A |

View File

@@ -0,0 +1,53 @@
# Quickstart: Fix UI Styling, WebSocket Port Mismatch, and URL Validation
## Development Setup
1. **Frontend Styling**:
- Ensure Tailwind CSS is initialized: `cd frontend && npm install`
- Verify `frontend/src/app.css` contains:
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```
- Import in `frontend/src/routes/+layout.svelte`:
```svelte
<script>
import '../app.css';
</script>
```
2. **WebSocket Configuration**:
- Create/Update `.env` in `frontend/`:
```env
PUBLIC_WS_URL=ws://localhost:8000
```
- Use in Svelte components:
```javascript
import { PUBLIC_WS_URL } from '$env/static/public';
const wsUrl = PUBLIC_WS_URL || `ws://${window.location.hostname}:8000`;
```
3. **Backend URL Validation**:
- Update `superset_tool/models.py` (or relevant model file):
```python
from pydantic import validator
class ServiceConnection(BaseModel):
base_url: str
@validator('base_url')
def normalize_url(cls, v):
if not v.endswith('/api/v1'):
return f"{v.rstrip('/')}/api/v1"
return v
```
## Verification Steps
1. Run backend: `cd backend && uvicorn src.app:app --reload`
2. Run frontend: `cd frontend && npm run dev`
3. Open browser and verify:
- UI is styled (Tailwind classes working).
- Logs appear in real-time (WebSocket connected).
- External service connection accepts base URLs.

View File

@@ -0,0 +1,41 @@
# Research: Fix UI Styling, WebSocket Port Mismatch, and URL Validation
## WebSocket Port Mismatch Resolution
### Decision
Use SvelteKit's `$env/static/public` for `PUBLIC_WS_URL` with a client-side fallback logic.
### Rationale
SvelteKit allows exposing environment variables to the frontend. By using a public environment variable, we can explicitly set the WebSocket URL in different environments (dev vs. prod).
### Alternatives Considered
- **Hardcoding**: Rejected as it breaks across different environments.
- **Relative URLs**: WebSockets (`ws://` or `wss://`) cannot be purely relative in all browser contexts without logic to determine the host and port.
---
## URL Validation Relaxation
### Decision
Modify the Pydantic model to use a `validator` (or `field_validator` in Pydantic v2) that checks for the `/api/v1` suffix and appends it if missing, while still ensuring the base URL is valid.
### Rationale
This provides a seamless user experience where they can provide just the base URL, and the system handles the API versioning internally.
### Alternatives Considered
- **Strict Validation with Error Message**: Rejected as it causes user frustration (as noted in the spec).
- **Manual Suffixing in Service Clients**: Rejected as it's better to have a normalized URL in the data model.
---
## Global Styling (Tailwind CSS)
### Decision
Import the global CSS file (which includes `@tailwind` directives) in `src/routes/+layout.svelte`.
### Rationale
This is the standard SvelteKit pattern for ensuring styles are applied globally across all routes.
### Alternatives Considered
- **Importing in each page**: Rejected as it's redundant and hard to maintain.
- **Importing in `app.html`**: Possible, but importing in `+layout.svelte` allows for better integration with Svelte's build pipeline.

View File

@@ -0,0 +1,94 @@
# Feature Specification: Fix UI Styling, WebSocket Port Mismatch, and URL Validation
**Feature Branch**: `001-fix-ui-ws-validation`
**Created**: 2025-12-20
**Status**: Draft
**Input**: User description: "UI Styling: Tailwind CSS is not imported in the root layout, causing the unstyled appearance. WebSocket Mismatch: Port mismatch in dev mode is breaking real-time logs. Validation Error: Strict URL validation in superset_tool/models.py requires /api/v1, which caused the connection failure reported in your feedback."
## User Scenarios & Testing *(mandatory)*
### User Story 1 - Consistent UI Styling (Priority: P1)
As a user, I want the application to have a professional and styled appearance so that I can easily navigate and use the interface.
**Why this priority**: Unstyled UI makes the application look broken and difficult to use, impacting user trust and usability.
**Independent Test**: Can be fully tested by opening the application in a browser and verifying that consistent styling is applied globally across all routes.
**Acceptance Scenarios**:
1. **Given** the application is running, **When** I navigate to the home page or settings page, **Then** I should see professional styling applied (e.g., correct fonts, colors, and layout).
2. **Given** a new component is added, **When** it uses standard styling classes, **Then** those classes should be rendered correctly without additional imports.
---
### User Story 2 - Real-time Log Monitoring (Priority: P1)
As a developer or operator, I want to see real-time logs for running tasks so that I can monitor progress and debug issues effectively.
**Why this priority**: Real-time feedback is essential for long-running tasks like migrations or backups; without it, users are left wondering if the process is stuck.
**Independent Test**: Can be tested by starting a task and verifying that logs appear in the UI in real-time without requiring a page refresh.
**Acceptance Scenarios**:
1. **Given** a task is running, **When** I view the task details page, **Then** I should see live log updates streamed via real-time communication.
2. **Given** the application is running in development mode, **When** a real-time connection is initiated, **Then** it should correctly target the backend service port.
---
### User Story 3 - Flexible External Service Connection (Priority: P2)
As an administrator, I want to connect to external services using their base URL so that I don't have to worry about specific API version paths during configuration.
**Why this priority**: Strict validation currently prevents successful connection to valid service instances if the user doesn't provide a very specific suffix, leading to configuration frustration.
**Independent Test**: Can be tested by configuring a service connection with a standard base URL and verifying it connects successfully.
**Acceptance Scenarios**:
1. **Given** a valid service base URL, **When** I save the connection settings, **Then** the system should validate and accept the URL even if it doesn't explicitly end in a specific API version suffix.
2. **Given** a service URL that already includes an API version suffix, **When** I save the settings, **Then** the system should not duplicate the suffix or fail validation.
---
### Edge Cases
- **Connection Disconnection**: How does the system handle a real-time connection drop during a long-running task? (Assumption: It should attempt to reconnect or show a "Connection Lost" message).
- **Invalid URL Formats**: How does the system handle URLs that are completely malformed? (Assumption: Standard URL validation should still apply).
- **Styling Build Failures**: What happens if the styling assets fail to generate? (Assumption: The app should still be functional but may look unstyled; build logs should indicate the failure).
## Requirements *(mandatory)*
### Functional Requirements
- **FR-001**: System MUST ensure global styling (Tailwind CSS) is imported in `src/routes/+layout.svelte` to ensure consistent appearance.
- **FR-002**: System MUST use an environment variable (e.g., `PUBLIC_WS_URL`) with a fallback to the backend port (8000) to determine the WebSocket connection URL.
- **FR-003**: System MUST relax URL validation for external services to allow base URLs and automatically append `/api/v1` if the version suffix is missing.
- **FR-004**: System MUST provide visual feedback (toast notification and status indicator in log view) when a real-time connection fails to establish.
- **FR-005**: System MUST ensure that service clients correctly handle API versioning internally by using the normalized URL.
### Key Entities *(include if feature involves data)*
- **Service Connection**: Represents the configuration for connecting to an external service.
- Attributes: Base URL, Credentials (if applicable), Connection Status.
- **Task Log Stream**: Represents the real-time data flow of logs from the backend to the frontend.
- Attributes: Task ID, Log Message, Timestamp.
## Success Criteria *(mandatory)*
### Measurable Outcomes
- **SC-001**: 100% of pages render with consistent, professional styling as verified by visual inspection.
- **SC-002**: Real-time communication success rate is 100% in the development environment when both frontend and backend are running.
- **SC-003**: Users can successfully configure and save external service connections using only the base domain/IP in 100% of valid cases.
- **SC-004**: Real-time updates appear in the UI within 500ms of being generated on the backend.
## Clarifications
### Session 2025-12-20
- Q: WebSocket Reconnection Strategy → A: Automatic reconnection with exponential backoff (Option A).
- Q: URL Validation Strictness → A: Automatically append `/api/v1` if missing (Option A).
- Q: Global Styling Implementation → A: Import in `src/routes/+layout.svelte` (Option A).
- Q: WebSocket Port Configuration → A: Use environment variable with fallback (Option A).
- Q: Visual Feedback for Connection Failure → A: Toast notification + Status indicator (Option A).

View File

@@ -0,0 +1,139 @@
# Tasks: Fix UI Styling, WebSocket Port Mismatch, and URL Validation
**Input**: Design documents from `/specs/001-fix-ui-ws-validation/`
**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/
**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
## Format: `[ID] [P?] [Story] Description`
- **[P]**: Can run in parallel (different files, no dependencies)
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
- Include exact file paths in descriptions
## Phase 1: Setup (Shared Infrastructure)
**Purpose**: Project initialization and basic structure
- [x] T001 Verify project structure and install dependencies in `backend/` and `frontend/`
---
## Phase 2: Foundational (Blocking Prerequisites)
**Purpose**: Core infrastructure that MUST be complete before ANY user story can be implemented
**⚠️ CRITICAL**: No user story work can begin until this phase is complete
- [x] T002 [P] Configure `PUBLIC_WS_URL` in `frontend/.env`
**Checkpoint**: Foundation ready - user story implementation can now begin in parallel
---
## Phase 3: User Story 1 - Consistent UI Styling (Priority: P1) 🎯 MVP
**Goal**: Apply Tailwind CSS globally via the root layout to ensure consistent appearance.
**Independent Test**: Open the application in a browser and verify that Tailwind styling is applied to all elements (e.g., Navbar, Footer, Buttons).
### Implementation for User Story 1
- [x] T003 [P] [US1] Verify Tailwind directives in `frontend/src/app.css`
- [x] T004 [US1] Import `../app.css` in `frontend/src/routes/+layout.svelte`
**Checkpoint**: At this point, User Story 1 should be fully functional and testable independently
---
## Phase 4: User Story 2 - Real-time Log Monitoring (Priority: P1)
**Goal**: Resolve WebSocket port mismatch using environment variables and fallback logic for real-time logs.
**Independent Test**: Start a task (e.g., a mock migration) and verify that logs appear in the `TaskRunner` component in real-time.
### Implementation for User Story 2
- [x] T005 [P] [US2] Implement WebSocket URL fallback logic in `frontend/src/lib/api.js`
- [x] T006 [US2] Update `frontend/src/components/TaskRunner.svelte` to use the dynamic WebSocket URL
**Checkpoint**: At this point, User Stories 1 AND 2 should both work independently
---
## Phase 5: User Story 3 - Flexible External Service Connection (Priority: P2)
**Goal**: Automatically append `/api/v1` to service base URLs if missing to simplify configuration.
**Independent Test**: Create a new service connection with `http://localhost:8080` and verify it is saved as `http://localhost:8080/api/v1`.
### Implementation for User Story 3
- [x] T007 [P] [US3] Relax `base_url` validation and add normalization in `superset_tool/models.py`
- [x] T008 [US3] Add unit tests for `SupersetConfig` URL normalization in `backend/tests/test_models.py`
**Checkpoint**: All user stories should now be independently functional
---
## Phase 6: Polish & Cross-Cutting Concerns
**Purpose**: Improvements that affect multiple user stories
- [x] T009 [P] Update `docs/settings.md` with new URL validation behavior
- [ ] T010 Run full verification suite per `quickstart.md`
---
## Dependencies & Execution Order
### Phase Dependencies
- **Setup (Phase 1)**: No dependencies - can start immediately
- **Foundational (Phase 2)**: Depends on Setup completion - BLOCKS all user stories
- **User Stories (Phase 3+)**: All depend on Foundational phase completion
- User stories can then proceed in parallel (if staffed)
- Or sequentially in priority order (P1 → P2 → P3)
- **Polish (Final Phase)**: Depends on all desired user stories being complete
### User Story Dependencies
- **User Story 1 (P1)**: Can start after Foundational (Phase 2) - No dependencies on other stories
- **User Story 2 (P2)**: Can start after Foundational (Phase 2) - May integrate with US1 but should be independently testable
- **User Story 3 (P3)**: Can start after Foundational (Phase 2) - May integrate with US1/US2 but should be independently testable
### Parallel Opportunities
- All Setup tasks marked [P] can run in parallel
- All Foundational tasks marked [P] can run in parallel (within Phase 2)
- Once Foundational phase completes, all user stories can start in parallel
- Models within a story marked [P] can run in parallel
---
## Parallel Example: User Story 1
```bash
# Launch all models for User Story 1 together:
Task: "Verify Tailwind directives in frontend/src/app.css"
```
---
## Implementation Strategy
### MVP First (User Story 1 Only)
1. Complete Phase 1: Setup
2. Complete Phase 2: Foundational (CRITICAL - blocks all stories)
3. Complete Phase 3: User Story 1
4. **STOP and VALIDATE**: Test User Story 1 independently
5. Deploy/demo if ready
### Incremental Delivery
1. Complete Setup + Foundational → Foundation ready
2. Add User Story 1 → Test independently → Deploy/Demo (MVP!)
3. Add User Story 2 → Test independently → Deploy/Demo
4. Add User Story 3 → Test independently → Deploy/Demo
5. Each story adds value without breaking previous stories

View File

@@ -66,6 +66,8 @@ As a user, I want a consistent look and feel across all pages with a shared navi
- **FR-005**: System MUST handle client-side navigation between routes without full page refreshes.
- **FR-006**: System MUST integrate with the existing backend API for data retrieval.
- **FR-007**: System MUST support data submission via existing API endpoints using standard asynchronous requests.
- **FR-008**: System MUST support WebSocket proxying for real-time task logs (required by `TaskRunner.svelte`).
- **FR-009**: System MUST support data submission for Settings updates and Plugin actions (e.g., triggering backups).
### Key Entities *(include if feature involves data)*

View File

@@ -23,9 +23,9 @@
**Purpose**: Project initialization and basic structure
- [ ] T001 Initialize SvelteKit in `frontend/` directory (replacing current setup)
- [ ] T002 Install `@sveltejs/adapter-static` in `frontend/package.json`
- [ ] T003 [P] Configure `frontend/svelte.config.js` for static adapter and SPA fallback
- [x] T001 Initialize SvelteKit in `frontend/` directory (replacing current setup)
- [x] T002 Install `@sveltejs/adapter-static` in `frontend/package.json`
- [x] T003 [P] Configure `frontend/svelte.config.js` for static adapter and SPA fallback
---
@@ -35,10 +35,11 @@
**⚠️ CRITICAL**: No user story work can begin until this phase is complete
- [ ] T004 Create `frontend/src/routes/+layout.ts` to disable SSR and prerendering (`ssr = false`, `prerender = false`)
- [ ] T005 Implement catch-all route in `backend/src/app.py` to serve `index.html` for SPA routing
- [ ] T006 [P] Update `backend/src/app.py` to mount `frontend/build` directory using `StaticFiles`
- [ ] T007 [P] Update `frontend/src/lib/api.js` to ensure compatibility with SvelteKit environment
- [x] T004 Create `frontend/src/routes/+layout.ts` to disable SSR and prerendering (`ssr = false`, `prerender = false`)
- [x] T005 Implement catch-all route in `backend/src/app.py` to serve `index.html` for SPA routing
- [x] T006 [P] Update `backend/src/app.py` to mount `frontend/build` directory using `StaticFiles`
- [x] T007 [P] Update `frontend/src/lib/api.js` to ensure compatibility with SvelteKit environment
- [x] T022 [FR-008] Configure WebSocket proxying in `backend/src/app.py` and `frontend/vite.config.js`
**Checkpoint**: Foundation ready - user story implementation can now begin in parallel
@@ -52,9 +53,11 @@
### Implementation for User Story 1
- [ ] T008 [P] [US1] Create Dashboard route in `frontend/src/routes/+page.svelte` (migrating from `App.svelte`/`Dashboard.svelte`)
- [ ] T009 [P] [US1] Create Settings route in `frontend/src/routes/settings/+page.svelte` (migrating from `Settings.svelte`)
- [ ] T010 [US1] Implement navigation links between Dashboard and Settings in `frontend/src/routes/+page.svelte` and `frontend/src/routes/settings/+page.svelte`
- [x] T008 [P] [US1] Create Dashboard route in `frontend/src/routes/+page.svelte` (migrating from `App.svelte`/`Dashboard.svelte`)
- [x] T009 [P] [US1] Create Settings route in `frontend/src/routes/settings/+page.svelte` (migrating from `Settings.svelte`)
- [x] T010 [US1] Implement navigation links between Dashboard and Settings in `frontend/src/routes/+page.svelte` and `frontend/src/routes/settings/+page.svelte`
- [x] T023 [US1] Implement "Save Settings" form submission in `frontend/src/routes/settings/+page.svelte`
- [x] T024 [US1] Implement plugin action triggers (e.g., "Run Backup") in `frontend/src/routes/+page.svelte`
**Checkpoint**: At this point, User Story 1 should be fully functional and testable independently.
@@ -68,10 +71,10 @@
### Implementation for User Story 2
- [ ] T011 [P] [US2] Implement `load` function for Dashboard in `frontend/src/routes/+page.ts` to fetch plugins from `/api/plugins/`
- [ ] T012 [P] [US2] Implement `load` function for Settings in `frontend/src/routes/settings/+page.ts` to fetch config and environments from `/api/settings/`
- [ ] T013 [US2] Update `frontend/src/routes/+page.svelte` to use data from `load` function via `export let data;`
- [ ] T014 [US2] Update `frontend/src/routes/settings/+page.svelte` to use data from `load` function via `export let data;`
- [x] T011 [P] [US2] Implement `load` function for Dashboard in `frontend/src/routes/+page.ts` to fetch plugins from `/api/plugins/`
- [x] T012 [P] [US2] Implement `load` function for Settings in `frontend/src/routes/settings/+page.ts` to fetch config and environments from `/api/settings/`
- [x] T013 [US2] Update `frontend/src/routes/+page.svelte` to use data from `load` function via `export let data;`
- [x] T014 [US2] Update `frontend/src/routes/settings/+page.svelte` to use data from `load` function via `export let data;`
**Checkpoint**: At this point, User Stories 1 AND 2 should both work independently.
@@ -85,9 +88,9 @@
### Implementation for User Story 3
- [ ] T015 [US3] Create shared layout in `frontend/src/routes/+layout.svelte` with `<slot />`
- [ ] T016 [P] [US3] Move navigation bar component to `frontend/src/components/Navbar.svelte` and include in `+layout.svelte`
- [ ] T017 [P] [US3] Create footer component in `frontend/src/components/Footer.svelte` and include in `+layout.svelte`
- [x] T015 [US3] Create shared layout in `frontend/src/routes/+layout.svelte` with `<slot />`
- [x] T016 [P] [US3] Move navigation bar component to `frontend/src/components/Navbar.svelte` and include in `+layout.svelte`
- [x] T017 [P] [US3] Create footer component in `frontend/src/components/Footer.svelte` and include in `+layout.svelte`
**Checkpoint**: All user stories should now be independently functional.
@@ -97,10 +100,12 @@
**Purpose**: Improvements that affect multiple user stories
- [ ] T018 [P] Implement custom 404 error page in `frontend/src/routes/+error.svelte`
- [ ] T019 Add graceful error handling for API failures in `load` functions (T011, T012)
- [ ] T020 [P] Update `frontend/README.md` with new SvelteKit-based development and build instructions
- [ ] T021 Run `specs/004-integrate-svelte-kit/quickstart.md` validation
- [x] T018 [P] Implement custom 404 error page in `frontend/src/routes/+error.svelte`
- [x] T019 Add graceful error handling for API failures in `load` functions (T011, T012)
- [x] T020 [P] Update `frontend/README.md` with new SvelteKit-based development and build instructions
- [x] T021 Run `specs/004-integrate-svelte-kit/quickstart.md` validation
- [x] T025 [FR-008] Update `TaskRunner.svelte` to use SvelteKit-compatible WebSocket connection logic
- [x] T026 [SC-001] Perform performance benchmarking to verify < 200ms transition time
---

View File

@@ -39,19 +39,22 @@ class SupersetConfig(BaseModel):
return v
# [/DEF:SupersetConfig.validate_auth]
# [DEF:SupersetConfig.check_base_url_format:Function]
# @PURPOSE: Проверяет, что `base_url` соответствует формату URL и содержит `/api/v1`.
# [DEF:SupersetConfig.normalize_base_url:Function]
# @PURPOSE: Нормализует `base_url`, добавляя `/api/v1`, если он отсутствует.
# @PRE: `v` должна быть строкой.
# @POST: Возвращает очищенный `v`, если формат корректен.
# @POST: Возвращает нормализованный `v`.
# @THROW: ValueError - Если формат URL невалиден.
# @PARAM: v (str) - Значение поля base_url.
@validator('base_url')
def check_base_url_format(cls, v: str) -> str:
def normalize_base_url(cls, v: str) -> str:
v = v.strip()
if not re.fullmatch(r'https?://.+/api/v1/?(?:.*)?', v):
raise ValueError(f"Invalid URL format: {v}. Must include '/api/v1'.")
if not v.startswith(('http://', 'https://')):
raise ValueError(f"Invalid URL scheme: {v}. Must start with http:// or https://")
if '/api/v1' not in v:
v = f"{v.rstrip('/')}/api/v1"
return v
# [/DEF:SupersetConfig.check_base_url_format]
# [/DEF:SupersetConfig.normalize_base_url]
class Config:
arbitrary_types_allowed = True