Compare commits
9 Commits
07ec2d9797
...
014-file-s
| Author | SHA1 | Date | |
|---|---|---|---|
| 51e9ee3fcc | |||
| edf9286071 | |||
| a542e7d2df | |||
| a863807cf2 | |||
| e2bc68683f | |||
| 43cb82697b | |||
| 4ba28cf93e | |||
| 343f2e29f5 | |||
| c9a53578fd |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -69,3 +69,4 @@ backend/tasks.db
|
||||
|
||||
# Git Integration repositories
|
||||
backend/git_repos/
|
||||
backend/backend/git_repos
|
||||
|
||||
@@ -25,6 +25,10 @@ Auto-generated from all feature plans. Last updated: 2025-12-19
|
||||
- Filesystem (local git repo), SQLite (for GitServerConfig, Environment) (011-git-integration-dashboard)
|
||||
- Python 3.9+ (Backend), Node.js 18+ (Frontend) + FastAPI, SvelteKit, GitPython (or CLI git), Pydantic, SQLAlchemy, Superset API (011-git-integration-dashboard)
|
||||
- SQLite (for config/history), Filesystem (local Git repositories) (011-git-integration-dashboard)
|
||||
- Node.js 18+ (Frontend Build), Svelte 5.x + SvelteKit, Tailwind CSS, `date-fns` (existing) (013-unify-frontend-css)
|
||||
- LocalStorage (for language preference) (013-unify-frontend-css)
|
||||
- Python 3.9+ (Backend), Node.js 18+ (Frontend) + FastAPI (Backend), SvelteKit (Frontend) (014-file-storage-ui)
|
||||
- Local Filesystem (for artifacts), Config (for storage path) (014-file-storage-ui)
|
||||
|
||||
- Python 3.9+ (Backend), Node.js 18+ (Frontend Build) (001-plugin-arch-svelte-ui)
|
||||
|
||||
@@ -45,9 +49,9 @@ cd src; pytest; ruff check .
|
||||
Python 3.9+ (Backend), Node.js 18+ (Frontend Build): Follow standard conventions
|
||||
|
||||
## Recent Changes
|
||||
- 014-file-storage-ui: Added Python 3.9+ (Backend), Node.js 18+ (Frontend) + FastAPI (Backend), SvelteKit (Frontend)
|
||||
- 013-unify-frontend-css: Added Node.js 18+ (Frontend Build), Svelte 5.x + SvelteKit, Tailwind CSS, `date-fns` (existing)
|
||||
- 011-git-integration-dashboard: Added Python 3.9+ (Backend), Node.js 18+ (Frontend) + FastAPI, SvelteKit, GitPython (or CLI git), Pydantic, SQLAlchemy, Superset API
|
||||
- 011-git-integration-dashboard: Added Python 3.9+ (Backend), Node.js 18+ (Frontend) + FastAPI, SvelteKit, GitPython (or CLI git), Pydantic, SQLAlchemy, Superset API
|
||||
- 011-git-integration-dashboard: Added Python 3.9+, Node.js 18+
|
||||
|
||||
|
||||
<!-- MANUAL ADDITIONS START -->
|
||||
|
||||
Submodule backend/backend/git_repos/12 updated: d592fa7ed5...f46772443a
@@ -1,269 +0,0 @@
|
||||
2025-12-20 19:55:11,325 - INFO - [BackupPlugin][Entry] Starting backup for superset.
|
||||
2025-12-20 19:55:11,325 - INFO - [setup_clients][Enter] Starting Superset clients initialization.
|
||||
2025-12-20 19:55:11,327 - 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 43, 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 21:01:49,905 - INFO - [BackupPlugin][Entry] Starting backup for superset.
|
||||
2025-12-20 21:01:49,906 - INFO - [setup_clients][Enter] Starting Superset clients initialization.
|
||||
2025-12-20 21:01:49,988 - INFO - [setup_clients][Action] Loading environments from ConfigManager
|
||||
2025-12-20 21:01:49,990 - 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/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: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.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,10 +1,17 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Script to delete tasks with RUNNING status from the database."""
|
||||
# [DEF:backend.delete_running_tasks:Module]
|
||||
# @PURPOSE: Script to delete tasks with RUNNING status from the database.
|
||||
# @LAYER: Utility
|
||||
# @SEMANTICS: maintenance, database, cleanup
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from src.core.database import TasksSessionLocal
|
||||
from src.models.task import TaskRecord
|
||||
|
||||
# [DEF:delete_running_tasks:Function]
|
||||
# @PURPOSE: Delete all tasks with RUNNING status from the database.
|
||||
# @PRE: Database is accessible and TaskRecord model is defined.
|
||||
# @POST: All tasks with status 'RUNNING' are removed from the database.
|
||||
def delete_running_tasks():
|
||||
"""Delete all tasks with RUNNING status from the database."""
|
||||
session: Session = TasksSessionLocal()
|
||||
@@ -30,6 +37,8 @@ def delete_running_tasks():
|
||||
print(f"Error deleting tasks: {e}")
|
||||
finally:
|
||||
session.close()
|
||||
# [/DEF:delete_running_tasks:Function]
|
||||
|
||||
if __name__ == "__main__":
|
||||
delete_running_tasks()
|
||||
# [/DEF:backend.delete_running_tasks:Module]
|
||||
|
||||
79101
backend/logs/app.log.1
Normal file
79101
backend/logs/app.log.1
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -1 +1 @@
|
||||
from . import plugins, tasks, settings, connections, environments, mappings, migration, git
|
||||
from . import plugins, tasks, settings, connections, environments, mappings, migration, git, storage
|
||||
|
||||
@@ -30,6 +30,8 @@ git_service = GitService()
|
||||
|
||||
# [DEF:get_git_configs:Function]
|
||||
# @PURPOSE: List all configured Git servers.
|
||||
# @PRE: Database session `db` is available.
|
||||
# @POST: Returns a list of all GitServerConfig objects from the database.
|
||||
# @RETURN: List[GitServerConfigSchema]
|
||||
@router.get("/config", response_model=List[GitServerConfigSchema])
|
||||
async def get_git_configs(db: Session = Depends(get_db)):
|
||||
@@ -39,6 +41,8 @@ async def get_git_configs(db: Session = Depends(get_db)):
|
||||
|
||||
# [DEF:create_git_config:Function]
|
||||
# @PURPOSE: Register a new Git server configuration.
|
||||
# @PRE: `config` contains valid GitServerConfigCreate data.
|
||||
# @POST: A new GitServerConfig record is created in the database.
|
||||
# @PARAM: config (GitServerConfigCreate)
|
||||
# @RETURN: GitServerConfigSchema
|
||||
@router.post("/config", response_model=GitServerConfigSchema)
|
||||
@@ -53,6 +57,8 @@ async def create_git_config(config: GitServerConfigCreate, db: Session = Depends
|
||||
|
||||
# [DEF:delete_git_config:Function]
|
||||
# @PURPOSE: Remove a Git server configuration.
|
||||
# @PRE: `config_id` corresponds to an existing configuration.
|
||||
# @POST: The configuration record is removed from the database.
|
||||
# @PARAM: config_id (str)
|
||||
@router.delete("/config/{config_id}")
|
||||
async def delete_git_config(config_id: str, db: Session = Depends(get_db)):
|
||||
@@ -68,6 +74,8 @@ async def delete_git_config(config_id: str, db: Session = Depends(get_db)):
|
||||
|
||||
# [DEF:test_git_config:Function]
|
||||
# @PURPOSE: Validate connection to a Git server using provided credentials.
|
||||
# @PRE: `config` contains provider, url, and pat.
|
||||
# @POST: Returns success if the connection is validated via GitService.
|
||||
# @PARAM: config (GitServerConfigCreate)
|
||||
@router.post("/config/test")
|
||||
async def test_git_config(config: GitServerConfigCreate):
|
||||
@@ -81,6 +89,8 @@ async def test_git_config(config: GitServerConfigCreate):
|
||||
|
||||
# [DEF:init_repository:Function]
|
||||
# @PURPOSE: Link a dashboard to a Git repository and perform initial clone/init.
|
||||
# @PRE: `dashboard_id` exists and `init_data` contains valid config_id and remote_url.
|
||||
# @POST: Repository is initialized on disk and a GitRepository record is saved in DB.
|
||||
# @PARAM: dashboard_id (int)
|
||||
# @PARAM: init_data (RepoInitRequest)
|
||||
@router.post("/repositories/{dashboard_id}/init")
|
||||
@@ -123,6 +133,8 @@ async def init_repository(dashboard_id: int, init_data: RepoInitRequest, db: Ses
|
||||
|
||||
# [DEF:get_branches:Function]
|
||||
# @PURPOSE: List all branches for a dashboard's repository.
|
||||
# @PRE: Repository for `dashboard_id` is initialized.
|
||||
# @POST: Returns a list of branches from the local repository.
|
||||
# @PARAM: dashboard_id (int)
|
||||
# @RETURN: List[BranchSchema]
|
||||
@router.get("/repositories/{dashboard_id}/branches", response_model=List[BranchSchema])
|
||||
@@ -136,6 +148,8 @@ async def get_branches(dashboard_id: int):
|
||||
|
||||
# [DEF:create_branch:Function]
|
||||
# @PURPOSE: Create a new branch in the dashboard's repository.
|
||||
# @PRE: `dashboard_id` repository exists and `branch_data` has name and from_branch.
|
||||
# @POST: A new branch is created in the local repository.
|
||||
# @PARAM: dashboard_id (int)
|
||||
# @PARAM: branch_data (BranchCreate)
|
||||
@router.post("/repositories/{dashboard_id}/branches")
|
||||
@@ -150,6 +164,8 @@ async def create_branch(dashboard_id: int, branch_data: BranchCreate):
|
||||
|
||||
# [DEF:checkout_branch:Function]
|
||||
# @PURPOSE: Switch the dashboard's repository to a specific branch.
|
||||
# @PRE: `dashboard_id` repository exists and branch `checkout_data.name` exists.
|
||||
# @POST: The local repository HEAD is moved to the specified branch.
|
||||
# @PARAM: dashboard_id (int)
|
||||
# @PARAM: checkout_data (BranchCheckout)
|
||||
@router.post("/repositories/{dashboard_id}/checkout")
|
||||
@@ -164,6 +180,8 @@ async def checkout_branch(dashboard_id: int, checkout_data: BranchCheckout):
|
||||
|
||||
# [DEF:commit_changes:Function]
|
||||
# @PURPOSE: Stage and commit changes in the dashboard's repository.
|
||||
# @PRE: `dashboard_id` repository exists and `commit_data` has message and files.
|
||||
# @POST: Specified files are staged and a new commit is created.
|
||||
# @PARAM: dashboard_id (int)
|
||||
# @PARAM: commit_data (CommitCreate)
|
||||
@router.post("/repositories/{dashboard_id}/commit")
|
||||
@@ -178,6 +196,8 @@ async def commit_changes(dashboard_id: int, commit_data: CommitCreate):
|
||||
|
||||
# [DEF:push_changes:Function]
|
||||
# @PURPOSE: Push local commits to the remote repository.
|
||||
# @PRE: `dashboard_id` repository exists and has a remote configured.
|
||||
# @POST: Local commits are pushed to the remote repository.
|
||||
# @PARAM: dashboard_id (int)
|
||||
@router.post("/repositories/{dashboard_id}/push")
|
||||
async def push_changes(dashboard_id: int):
|
||||
@@ -191,6 +211,8 @@ async def push_changes(dashboard_id: int):
|
||||
|
||||
# [DEF:pull_changes:Function]
|
||||
# @PURPOSE: Pull changes from the remote repository.
|
||||
# @PRE: `dashboard_id` repository exists and has a remote configured.
|
||||
# @POST: Remote changes are fetched and merged into the local branch.
|
||||
# @PARAM: dashboard_id (int)
|
||||
@router.post("/repositories/{dashboard_id}/pull")
|
||||
async def pull_changes(dashboard_id: int):
|
||||
@@ -204,6 +226,8 @@ async def pull_changes(dashboard_id: int):
|
||||
|
||||
# [DEF:sync_dashboard:Function]
|
||||
# @PURPOSE: Sync dashboard state from Superset to Git using the GitPlugin.
|
||||
# @PRE: `dashboard_id` is valid; GitPlugin is available.
|
||||
# @POST: Dashboard YAMLs are exported from Superset and committed to Git.
|
||||
# @PARAM: dashboard_id (int)
|
||||
# @PARAM: source_env_id (Optional[str])
|
||||
@router.post("/repositories/{dashboard_id}/sync")
|
||||
@@ -223,6 +247,8 @@ async def sync_dashboard(dashboard_id: int, source_env_id: typing.Optional[str]
|
||||
|
||||
# [DEF:get_environments:Function]
|
||||
# @PURPOSE: List all deployment environments.
|
||||
# @PRE: Config manager is accessible.
|
||||
# @POST: Returns a list of DeploymentEnvironmentSchema objects.
|
||||
# @RETURN: List[DeploymentEnvironmentSchema]
|
||||
@router.get("/environments", response_model=List[DeploymentEnvironmentSchema])
|
||||
async def get_environments(config_manager=Depends(get_config_manager)):
|
||||
@@ -240,6 +266,8 @@ async def get_environments(config_manager=Depends(get_config_manager)):
|
||||
|
||||
# [DEF:deploy_dashboard:Function]
|
||||
# @PURPOSE: Deploy dashboard from Git to a target environment.
|
||||
# @PRE: `dashboard_id` and `deploy_data.environment_id` are valid.
|
||||
# @POST: Dashboard YAMLs are read from Git and imported into the target Superset.
|
||||
# @PARAM: dashboard_id (int)
|
||||
# @PARAM: deploy_data (DeployRequest)
|
||||
@router.post("/repositories/{dashboard_id}/deploy")
|
||||
@@ -259,6 +287,8 @@ async def deploy_dashboard(dashboard_id: int, deploy_data: DeployRequest):
|
||||
|
||||
# [DEF:get_history:Function]
|
||||
# @PURPOSE: View commit history for a dashboard's repository.
|
||||
# @PRE: `dashboard_id` repository exists.
|
||||
# @POST: Returns a list of recent commits from the repository.
|
||||
# @PARAM: dashboard_id (int)
|
||||
# @PARAM: limit (int)
|
||||
# @RETURN: List[CommitSchema]
|
||||
@@ -273,6 +303,8 @@ async def get_history(dashboard_id: int, limit: int = 50):
|
||||
|
||||
# [DEF:get_repository_status:Function]
|
||||
# @PURPOSE: Get current Git status for a dashboard repository.
|
||||
# @PRE: `dashboard_id` repository exists.
|
||||
# @POST: Returns the status of the working directory (staged, unstaged, untracked).
|
||||
# @PARAM: dashboard_id (int)
|
||||
# @RETURN: dict
|
||||
@router.get("/repositories/{dashboard_id}/status")
|
||||
@@ -286,6 +318,8 @@ async def get_repository_status(dashboard_id: int):
|
||||
|
||||
# [DEF:get_repository_diff:Function]
|
||||
# @PURPOSE: Get Git diff for a dashboard repository.
|
||||
# @PRE: `dashboard_id` repository exists.
|
||||
# @POST: Returns the diff text for the specified file or all changes.
|
||||
# @PARAM: dashboard_id (int)
|
||||
# @PARAM: file_path (Optional[str])
|
||||
# @PARAM: staged (bool)
|
||||
|
||||
@@ -14,6 +14,7 @@ from uuid import UUID
|
||||
from src.models.git import GitProvider, GitStatus, SyncStatus
|
||||
|
||||
# [DEF:GitServerConfigBase:Class]
|
||||
# @PURPOSE: Base schema for Git server configuration attributes.
|
||||
class GitServerConfigBase(BaseModel):
|
||||
name: str = Field(..., description="Display name for the Git server")
|
||||
provider: GitProvider = Field(..., description="Git provider (GITHUB, GITLAB, GITEA)")
|
||||
@@ -23,12 +24,14 @@ class GitServerConfigBase(BaseModel):
|
||||
# [/DEF:GitServerConfigBase:Class]
|
||||
|
||||
# [DEF:GitServerConfigCreate:Class]
|
||||
# @PURPOSE: Schema for creating a new Git server configuration.
|
||||
class GitServerConfigCreate(GitServerConfigBase):
|
||||
"""Schema for creating a new Git server configuration."""
|
||||
pass
|
||||
# [/DEF:GitServerConfigCreate:Class]
|
||||
|
||||
# [DEF:GitServerConfigSchema:Class]
|
||||
# @PURPOSE: Schema for representing a Git server configuration with metadata.
|
||||
class GitServerConfigSchema(GitServerConfigBase):
|
||||
"""Schema for representing a Git server configuration with metadata."""
|
||||
id: str
|
||||
@@ -40,6 +43,7 @@ class GitServerConfigSchema(GitServerConfigBase):
|
||||
# [/DEF:GitServerConfigSchema:Class]
|
||||
|
||||
# [DEF:GitRepositorySchema:Class]
|
||||
# @PURPOSE: Schema for tracking a local Git repository linked to a dashboard.
|
||||
class GitRepositorySchema(BaseModel):
|
||||
"""Schema for tracking a local Git repository linked to a dashboard."""
|
||||
id: str
|
||||
@@ -55,6 +59,7 @@ class GitRepositorySchema(BaseModel):
|
||||
# [/DEF:GitRepositorySchema:Class]
|
||||
|
||||
# [DEF:BranchSchema:Class]
|
||||
# @PURPOSE: Schema for representing a Git branch metadata.
|
||||
class BranchSchema(BaseModel):
|
||||
"""Schema for representing a Git branch."""
|
||||
name: str
|
||||
@@ -64,6 +69,7 @@ class BranchSchema(BaseModel):
|
||||
# [/DEF:BranchSchema:Class]
|
||||
|
||||
# [DEF:CommitSchema:Class]
|
||||
# @PURPOSE: Schema for representing Git commit details.
|
||||
class CommitSchema(BaseModel):
|
||||
"""Schema for representing a Git commit."""
|
||||
hash: str
|
||||
@@ -75,6 +81,7 @@ class CommitSchema(BaseModel):
|
||||
# [/DEF:CommitSchema:Class]
|
||||
|
||||
# [DEF:BranchCreate:Class]
|
||||
# @PURPOSE: Schema for branch creation requests.
|
||||
class BranchCreate(BaseModel):
|
||||
"""Schema for branch creation requests."""
|
||||
name: str
|
||||
@@ -82,12 +89,14 @@ class BranchCreate(BaseModel):
|
||||
# [/DEF:BranchCreate:Class]
|
||||
|
||||
# [DEF:BranchCheckout:Class]
|
||||
# @PURPOSE: Schema for branch checkout requests.
|
||||
class BranchCheckout(BaseModel):
|
||||
"""Schema for branch checkout requests."""
|
||||
name: str
|
||||
# [/DEF:BranchCheckout:Class]
|
||||
|
||||
# [DEF:CommitCreate:Class]
|
||||
# @PURPOSE: Schema for staging and committing changes.
|
||||
class CommitCreate(BaseModel):
|
||||
"""Schema for staging and committing changes."""
|
||||
message: str
|
||||
@@ -95,6 +104,7 @@ class CommitCreate(BaseModel):
|
||||
# [/DEF:CommitCreate:Class]
|
||||
|
||||
# [DEF:ConflictResolution:Class]
|
||||
# @PURPOSE: Schema for resolving merge conflicts.
|
||||
class ConflictResolution(BaseModel):
|
||||
"""Schema for resolving merge conflicts."""
|
||||
file_path: str
|
||||
@@ -103,6 +113,7 @@ class ConflictResolution(BaseModel):
|
||||
# [/DEF:ConflictResolution:Class]
|
||||
|
||||
# [DEF:DeploymentEnvironmentSchema:Class]
|
||||
# @PURPOSE: Schema for representing a target deployment environment.
|
||||
class DeploymentEnvironmentSchema(BaseModel):
|
||||
"""Schema for representing a target deployment environment."""
|
||||
id: str
|
||||
@@ -115,12 +126,14 @@ class DeploymentEnvironmentSchema(BaseModel):
|
||||
# [/DEF:DeploymentEnvironmentSchema:Class]
|
||||
|
||||
# [DEF:DeployRequest:Class]
|
||||
# @PURPOSE: Schema for dashboard deployment requests.
|
||||
class DeployRequest(BaseModel):
|
||||
"""Schema for deployment requests."""
|
||||
environment_id: str
|
||||
# [/DEF:DeployRequest:Class]
|
||||
|
||||
# [DEF:RepoInitRequest:Class]
|
||||
# @PURPOSE: Schema for repository initialization requests.
|
||||
class RepoInitRequest(BaseModel):
|
||||
"""Schema for repository initialization requests."""
|
||||
config_id: str
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from typing import List
|
||||
from ...core.config_models import AppConfig, Environment, GlobalSettings
|
||||
from ...models.storage import StorageConfig
|
||||
from ...dependencies import get_config_manager
|
||||
from ...core.config_manager import ConfigManager
|
||||
from ...core.logger import logger, belief_scope
|
||||
@@ -52,10 +53,38 @@ async def update_global_settings(
|
||||
):
|
||||
with belief_scope("update_global_settings"):
|
||||
logger.info("[update_global_settings][Entry] Updating global settings")
|
||||
|
||||
config_manager.update_global_settings(settings)
|
||||
return settings
|
||||
# [/DEF:update_global_settings:Function]
|
||||
|
||||
# [DEF:get_storage_settings:Function]
|
||||
# @PURPOSE: Retrieves storage-specific settings.
|
||||
# @RETURN: StorageConfig - The storage configuration.
|
||||
@router.get("/storage", response_model=StorageConfig)
|
||||
async def get_storage_settings(config_manager: ConfigManager = Depends(get_config_manager)):
|
||||
with belief_scope("get_storage_settings"):
|
||||
return config_manager.get_config().settings.storage
|
||||
# [/DEF:get_storage_settings:Function]
|
||||
|
||||
# [DEF:update_storage_settings:Function]
|
||||
# @PURPOSE: Updates storage-specific settings.
|
||||
# @PARAM: storage (StorageConfig) - The new storage settings.
|
||||
# @POST: Storage settings are updated and saved.
|
||||
# @RETURN: StorageConfig - The updated storage settings.
|
||||
@router.put("/storage", response_model=StorageConfig)
|
||||
async def update_storage_settings(storage: StorageConfig, config_manager: ConfigManager = Depends(get_config_manager)):
|
||||
with belief_scope("update_storage_settings"):
|
||||
is_valid, message = config_manager.validate_path(storage.root_path)
|
||||
if not is_valid:
|
||||
raise HTTPException(status_code=400, detail=message)
|
||||
|
||||
settings = config_manager.get_config().settings
|
||||
settings.storage = storage
|
||||
config_manager.update_global_settings(settings)
|
||||
return config_manager.get_config().settings.storage
|
||||
# [/DEF:update_storage_settings:Function]
|
||||
|
||||
# [DEF:get_environments:Function]
|
||||
# @PURPOSE: Lists all configured Superset environments.
|
||||
# @PRE: Config manager is available.
|
||||
@@ -179,30 +208,5 @@ async def test_environment_connection(
|
||||
return {"status": "error", "message": str(e)}
|
||||
# [/DEF:test_environment_connection:Function]
|
||||
|
||||
# [DEF:validate_backup_path:Function]
|
||||
# @PURPOSE: Validates if a backup path exists and is writable.
|
||||
# @PRE: Path is provided in path_data.
|
||||
# @POST: Returns success or error status.
|
||||
# @PARAM: path (str) - The path to validate.
|
||||
# @RETURN: dict - Validation result.
|
||||
@router.post("/validate-path")
|
||||
async def validate_backup_path(
|
||||
path_data: dict,
|
||||
config_manager: ConfigManager = Depends(get_config_manager)
|
||||
):
|
||||
with belief_scope("validate_backup_path"):
|
||||
path = path_data.get("path")
|
||||
if not path:
|
||||
raise HTTPException(status_code=400, detail="Path is required")
|
||||
|
||||
logger.info(f"[validate_backup_path][Entry] Validating path: {path}")
|
||||
|
||||
valid, message = config_manager.validate_path(path)
|
||||
|
||||
if not valid:
|
||||
return {"status": "error", "message": message}
|
||||
|
||||
return {"status": "success", "message": message}
|
||||
# [/DEF:validate_backup_path:Function]
|
||||
|
||||
# [/DEF:SettingsRouter:Module]
|
||||
|
||||
132
backend/src/api/routes/storage.py
Normal file
132
backend/src/api/routes/storage.py
Normal file
@@ -0,0 +1,132 @@
|
||||
# [DEF:storage_routes:Module]
|
||||
#
|
||||
# @SEMANTICS: storage, files, upload, download, backup, repository
|
||||
# @PURPOSE: API endpoints for file storage management (backups and repositories).
|
||||
# @LAYER: API
|
||||
# @RELATION: DEPENDS_ON -> backend.src.models.storage
|
||||
#
|
||||
# @INVARIANT: All paths must be validated against path traversal.
|
||||
|
||||
# [SECTION: IMPORTS]
|
||||
from fastapi import APIRouter, Depends, UploadFile, File, Form, HTTPException
|
||||
from fastapi.responses import FileResponse
|
||||
from typing import List, Optional
|
||||
from ...models.storage import StoredFile, FileCategory
|
||||
from ...dependencies import get_plugin_loader
|
||||
from ...plugins.storage.plugin import StoragePlugin
|
||||
from ...core.logger import belief_scope
|
||||
# [/SECTION]
|
||||
|
||||
router = APIRouter(tags=["storage"])
|
||||
|
||||
# [DEF:list_files:Function]
|
||||
# @PURPOSE: List all files and directories in the storage system.
|
||||
#
|
||||
# @PRE: None.
|
||||
# @POST: Returns a list of StoredFile objects.
|
||||
#
|
||||
# @PARAM: category (Optional[FileCategory]) - Filter by category.
|
||||
# @PARAM: path (Optional[str]) - Subpath within the category.
|
||||
# @RETURN: List[StoredFile] - List of files/directories.
|
||||
#
|
||||
# @RELATION: CALLS -> StoragePlugin.list_files
|
||||
@router.get("/files", response_model=List[StoredFile])
|
||||
async def list_files(
|
||||
category: Optional[FileCategory] = None,
|
||||
path: Optional[str] = None,
|
||||
plugin_loader=Depends(get_plugin_loader)
|
||||
):
|
||||
with belief_scope("list_files"):
|
||||
storage_plugin: StoragePlugin = plugin_loader.get_plugin("storage-manager")
|
||||
if not storage_plugin:
|
||||
raise HTTPException(status_code=500, detail="Storage plugin not loaded")
|
||||
return storage_plugin.list_files(category, path)
|
||||
# [/DEF:list_files:Function]
|
||||
|
||||
# [DEF:upload_file:Function]
|
||||
# @PURPOSE: Upload a file to the storage system.
|
||||
#
|
||||
# @PRE: category must be a valid FileCategory.
|
||||
# @PRE: file must be a valid UploadFile.
|
||||
# @POST: Returns the StoredFile object of the uploaded file.
|
||||
#
|
||||
# @PARAM: category (FileCategory) - Target category.
|
||||
# @PARAM: path (Optional[str]) - Target subpath.
|
||||
# @PARAM: file (UploadFile) - The file content.
|
||||
# @RETURN: StoredFile - Metadata of the uploaded file.
|
||||
#
|
||||
# @SIDE_EFFECT: Writes file to the filesystem.
|
||||
#
|
||||
# @RELATION: CALLS -> StoragePlugin.save_file
|
||||
@router.post("/upload", response_model=StoredFile, status_code=201)
|
||||
async def upload_file(
|
||||
category: FileCategory = Form(...),
|
||||
path: Optional[str] = Form(None),
|
||||
file: UploadFile = File(...),
|
||||
plugin_loader=Depends(get_plugin_loader)
|
||||
):
|
||||
with belief_scope("upload_file"):
|
||||
storage_plugin: StoragePlugin = plugin_loader.get_plugin("storage-manager")
|
||||
if not storage_plugin:
|
||||
raise HTTPException(status_code=500, detail="Storage plugin not loaded")
|
||||
try:
|
||||
return await storage_plugin.save_file(file, category, path)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
# [/DEF:upload_file:Function]
|
||||
|
||||
# [DEF:delete_file:Function]
|
||||
# @PURPOSE: Delete a specific file or directory.
|
||||
#
|
||||
# @PRE: category must be a valid FileCategory.
|
||||
# @POST: Item is removed from storage.
|
||||
#
|
||||
# @PARAM: category (FileCategory) - File category.
|
||||
# @PARAM: path (str) - Relative path of the item.
|
||||
# @RETURN: None
|
||||
#
|
||||
# @SIDE_EFFECT: Deletes item from the filesystem.
|
||||
#
|
||||
# @RELATION: CALLS -> StoragePlugin.delete_file
|
||||
@router.delete("/files/{category}/{path:path}", status_code=204)
|
||||
async def delete_file(category: FileCategory, path: str, plugin_loader=Depends(get_plugin_loader)):
|
||||
with belief_scope("delete_file"):
|
||||
storage_plugin: StoragePlugin = plugin_loader.get_plugin("storage-manager")
|
||||
if not storage_plugin:
|
||||
raise HTTPException(status_code=500, detail="Storage plugin not loaded")
|
||||
try:
|
||||
storage_plugin.delete_file(category, path)
|
||||
except FileNotFoundError:
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
# [/DEF:delete_file:Function]
|
||||
|
||||
# [DEF:download_file:Function]
|
||||
# @PURPOSE: Retrieve a file for download.
|
||||
#
|
||||
# @PRE: category must be a valid FileCategory.
|
||||
# @POST: Returns a FileResponse.
|
||||
#
|
||||
# @PARAM: category (FileCategory) - File category.
|
||||
# @PARAM: path (str) - Relative path of the file.
|
||||
# @RETURN: FileResponse - The file content.
|
||||
#
|
||||
# @RELATION: CALLS -> StoragePlugin.get_file_path
|
||||
@router.get("/download/{category}/{path:path}")
|
||||
async def download_file(category: FileCategory, path: str, plugin_loader=Depends(get_plugin_loader)):
|
||||
with belief_scope("download_file"):
|
||||
storage_plugin: StoragePlugin = plugin_loader.get_plugin("storage-manager")
|
||||
if not storage_plugin:
|
||||
raise HTTPException(status_code=500, detail="Storage plugin not loaded")
|
||||
try:
|
||||
abs_path = storage_plugin.get_file_path(category, path)
|
||||
filename = Path(path).name
|
||||
return FileResponse(path=abs_path, filename=filename)
|
||||
except FileNotFoundError:
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
# [/DEF:download_file:Function]
|
||||
|
||||
# [/DEF:storage_routes:Module]
|
||||
@@ -18,7 +18,7 @@ import os
|
||||
|
||||
from .dependencies import get_task_manager, get_scheduler_service
|
||||
from .core.logger import logger, belief_scope
|
||||
from .api.routes import plugins, tasks, settings, environments, mappings, migration, connections, git
|
||||
from .api.routes import plugins, tasks, settings, environments, mappings, migration, connections, git, storage
|
||||
from .core.database import init_db
|
||||
|
||||
# [DEF:App:Global]
|
||||
@@ -89,6 +89,7 @@ app.include_router(environments.router, prefix="/api/environments", tags=["Envir
|
||||
app.include_router(mappings.router)
|
||||
app.include_router(migration.router)
|
||||
app.include_router(git.router)
|
||||
app.include_router(storage.router, prefix="/api/storage", tags=["Storage"])
|
||||
|
||||
# [DEF:websocket_endpoint:Function]
|
||||
# @PURPOSE: Provides a WebSocket endpoint for real-time log streaming of a task.
|
||||
|
||||
@@ -62,14 +62,18 @@ class ConfigManager:
|
||||
logger.info(f"[_load_config][Action] Config file not found. Creating default.")
|
||||
default_config = AppConfig(
|
||||
environments=[],
|
||||
settings=GlobalSettings(backup_path="backups")
|
||||
settings=GlobalSettings()
|
||||
)
|
||||
self._save_config_to_disk(default_config)
|
||||
return default_config
|
||||
|
||||
try:
|
||||
with open(self.config_path, "r") as f:
|
||||
data = json.load(f)
|
||||
|
||||
# Check for deprecated field
|
||||
if "settings" in data and "backup_path" in data["settings"]:
|
||||
del data["settings"]["backup_path"]
|
||||
|
||||
config = AppConfig(**data)
|
||||
logger.info(f"[_load_config][Coherence:OK] Configuration loaded")
|
||||
return config
|
||||
@@ -79,7 +83,7 @@ class ConfigManager:
|
||||
# For now, return default to be safe, but log the error prominently.
|
||||
return AppConfig(
|
||||
environments=[],
|
||||
settings=GlobalSettings(backup_path="backups")
|
||||
settings=GlobalSettings(storage=StorageConfig())
|
||||
)
|
||||
# [/DEF:_load_config:Function]
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List, Optional
|
||||
from ..models.storage import StorageConfig
|
||||
|
||||
# [DEF:Schedule:DataClass]
|
||||
# @PURPOSE: Represents a backup schedule configuration.
|
||||
@@ -42,7 +43,7 @@ class LoggingConfig(BaseModel):
|
||||
# [DEF:GlobalSettings:DataClass]
|
||||
# @PURPOSE: Represents global application settings.
|
||||
class GlobalSettings(BaseModel):
|
||||
backup_path: str
|
||||
storage: StorageConfig = Field(default_factory=StorageConfig)
|
||||
default_environment_id: Optional[str] = None
|
||||
logging: LoggingConfig = Field(default_factory=LoggingConfig)
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ class BeliefFormatter(logging.Formatter):
|
||||
# @POST: Returns formatted string.
|
||||
# @PARAM: record (logging.LogRecord) - The log record to format.
|
||||
# @RETURN: str - The formatted log message.
|
||||
# @SEMANTICS: logging, formatter, context
|
||||
def format(self, record):
|
||||
anchor_id = getattr(_belief_state, 'anchor_id', None)
|
||||
if anchor_id:
|
||||
@@ -54,6 +55,7 @@ class LogEntry(BaseModel):
|
||||
# @PARAM: message (str) - Optional entry message.
|
||||
# @PRE: anchor_id must be provided.
|
||||
# @POST: Thread-local belief state is updated and entry/exit logs are generated.
|
||||
# @SEMANTICS: logging, context, belief_state
|
||||
@contextmanager
|
||||
def belief_scope(anchor_id: str, message: str = ""):
|
||||
# Log Entry if enabled
|
||||
@@ -88,6 +90,7 @@ def belief_scope(anchor_id: str, message: str = ""):
|
||||
# @PRE: config is a valid LoggingConfig instance.
|
||||
# @POST: Logger level, handlers, and belief state flag are updated.
|
||||
# @PARAM: config (LoggingConfig) - The logging configuration.
|
||||
# @SEMANTICS: logging, configuration, initialization
|
||||
def configure_logger(config):
|
||||
global _enable_belief_state
|
||||
_enable_belief_state = config.enable_belief_state
|
||||
@@ -140,6 +143,7 @@ class WebSocketLogHandler(logging.Handler):
|
||||
# @PRE: capacity is an integer.
|
||||
# @POST: Instance initialized with empty deque.
|
||||
# @PARAM: capacity (int) - Maximum number of logs to keep in memory.
|
||||
# @SEMANTICS: logging, initialization, buffer
|
||||
def __init__(self, capacity: int = 1000):
|
||||
super().__init__()
|
||||
self.log_buffer: deque[LogEntry] = deque(maxlen=capacity)
|
||||
@@ -152,6 +156,7 @@ class WebSocketLogHandler(logging.Handler):
|
||||
# @PRE: record is a logging.LogRecord.
|
||||
# @POST: Log is added to the log_buffer.
|
||||
# @PARAM: record (logging.LogRecord) - The log record to emit.
|
||||
# @SEMANTICS: logging, handler, buffer
|
||||
def emit(self, record: logging.LogRecord):
|
||||
try:
|
||||
log_entry = LogEntry(
|
||||
@@ -179,6 +184,7 @@ class WebSocketLogHandler(logging.Handler):
|
||||
# @PRE: None.
|
||||
# @POST: Returns list of LogEntry objects.
|
||||
# @RETURN: List[LogEntry] - List of buffered log entries.
|
||||
# @SEMANTICS: logging, buffer, retrieval
|
||||
def get_recent_logs(self) -> List[LogEntry]:
|
||||
"""
|
||||
Returns a list of recent log entries from the buffer.
|
||||
@@ -196,12 +202,24 @@ logger = logging.getLogger("superset_tools_app")
|
||||
# [DEF:believed:Function]
|
||||
# @PURPOSE: A decorator that wraps a function in a belief scope.
|
||||
# @PARAM: anchor_id (str) - The identifier for the semantic block.
|
||||
# @PRE: anchor_id must be a string.
|
||||
# @POST: Returns a decorator function.
|
||||
def believed(anchor_id: str):
|
||||
# [DEF:decorator:Function]
|
||||
# @PURPOSE: Internal decorator for belief scope.
|
||||
# @PRE: func must be a callable.
|
||||
# @POST: Returns the wrapped function.
|
||||
def decorator(func):
|
||||
# [DEF:wrapper:Function]
|
||||
# @PURPOSE: Internal wrapper that enters belief scope.
|
||||
# @PRE: None.
|
||||
# @POST: Executes the function within a belief scope.
|
||||
def wrapper(*args, **kwargs):
|
||||
with belief_scope(anchor_id):
|
||||
return func(*args, **kwargs)
|
||||
# [/DEF:wrapper:Function]
|
||||
return wrapper
|
||||
# [/DEF:decorator:Function]
|
||||
return decorator
|
||||
# [/DEF:believed:Function]
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
@@ -50,9 +50,18 @@ class PluginLoader:
|
||||
sys.path.insert(0, plugin_parent_dir)
|
||||
|
||||
for filename in os.listdir(self.plugin_dir):
|
||||
file_path = os.path.join(self.plugin_dir, filename)
|
||||
|
||||
# Handle directory-based plugins (packages)
|
||||
if os.path.isdir(file_path):
|
||||
init_file = os.path.join(file_path, "__init__.py")
|
||||
if os.path.exists(init_file):
|
||||
self._load_module(filename, init_file)
|
||||
continue
|
||||
|
||||
# Handle single-file plugins
|
||||
if filename.endswith(".py") and filename != "__init__.py":
|
||||
module_name = filename[:-3]
|
||||
file_path = os.path.join(self.plugin_dir, filename)
|
||||
self._load_module(module_name, file_path)
|
||||
# [/DEF:_load_plugins:Function]
|
||||
|
||||
|
||||
@@ -65,6 +65,8 @@ class SupersetClient:
|
||||
@property
|
||||
# [DEF:headers:Function]
|
||||
# @PURPOSE: Возвращает базовые HTTP-заголовки, используемые сетевым клиентом.
|
||||
# @PRE: APIClient is initialized and authenticated.
|
||||
# @POST: Returns a dictionary of HTTP headers.
|
||||
def headers(self) -> dict:
|
||||
with belief_scope("headers"):
|
||||
return self.network.headers
|
||||
@@ -75,6 +77,8 @@ class SupersetClient:
|
||||
# [DEF:get_dashboards:Function]
|
||||
# @PURPOSE: Получает полный список дашбордов, автоматически обрабатывая пагинацию.
|
||||
# @PARAM: query (Optional[Dict]) - Дополнительные параметры запроса для API.
|
||||
# @PRE: Client is authenticated.
|
||||
# @POST: Returns a tuple with total count and list of dashboards.
|
||||
# @RETURN: Tuple[int, List[Dict]] - Кортеж (общее количество, список дашбордов).
|
||||
def get_dashboards(self, query: Optional[Dict] = None) -> Tuple[int, List[Dict]]:
|
||||
with belief_scope("get_dashboards"):
|
||||
@@ -94,6 +98,8 @@ class SupersetClient:
|
||||
|
||||
# [DEF:get_dashboards_summary:Function]
|
||||
# @PURPOSE: Fetches dashboard metadata optimized for the grid.
|
||||
# @PRE: Client is authenticated.
|
||||
# @POST: Returns a list of dashboard metadata summaries.
|
||||
# @RETURN: List[Dict]
|
||||
def get_dashboards_summary(self) -> List[Dict]:
|
||||
with belief_scope("SupersetClient.get_dashboards_summary"):
|
||||
@@ -117,6 +123,8 @@ class SupersetClient:
|
||||
# [DEF:export_dashboard:Function]
|
||||
# @PURPOSE: Экспортирует дашборд в виде ZIP-архива.
|
||||
# @PARAM: dashboard_id (int) - ID дашборда для экспорта.
|
||||
# @PRE: dashboard_id must exist in Superset.
|
||||
# @POST: Returns ZIP content and filename.
|
||||
# @RETURN: Tuple[bytes, str] - Бинарное содержимое ZIP-архива и имя файла.
|
||||
def export_dashboard(self, dashboard_id: int) -> Tuple[bytes, str]:
|
||||
with belief_scope("export_dashboard"):
|
||||
@@ -140,6 +148,8 @@ class SupersetClient:
|
||||
# @PARAM: file_name (Union[str, Path]) - Путь к ZIP-архиву.
|
||||
# @PARAM: dash_id (Optional[int]) - ID дашборда для удаления при сбое.
|
||||
# @PARAM: dash_slug (Optional[str]) - Slug дашборда для поиска ID.
|
||||
# @PRE: file_name must be a valid ZIP dashboard export.
|
||||
# @POST: Dashboard is imported or re-imported after deletion.
|
||||
# @RETURN: Dict - Ответ API в случае успеха.
|
||||
def import_dashboard(self, file_name: Union[str, Path], dash_id: Optional[int] = None, dash_slug: Optional[str] = None) -> Dict:
|
||||
with belief_scope("import_dashboard"):
|
||||
@@ -165,6 +175,8 @@ class SupersetClient:
|
||||
# [DEF:delete_dashboard:Function]
|
||||
# @PURPOSE: Удаляет дашборд по его ID или slug.
|
||||
# @PARAM: dashboard_id (Union[int, str]) - ID или slug дашборда.
|
||||
# @PRE: dashboard_id must exist.
|
||||
# @POST: Dashboard is removed from Superset.
|
||||
def delete_dashboard(self, dashboard_id: Union[int, str]) -> None:
|
||||
with belief_scope("delete_dashboard"):
|
||||
app_logger.info("[delete_dashboard][Enter] Deleting dashboard %s.", dashboard_id)
|
||||
@@ -183,6 +195,8 @@ class SupersetClient:
|
||||
# [DEF:get_datasets:Function]
|
||||
# @PURPOSE: Получает полный список датасетов, автоматически обрабатывая пагинацию.
|
||||
# @PARAM: query (Optional[Dict]) - Дополнительные параметры запроса.
|
||||
# @PRE: Client is authenticated.
|
||||
# @POST: Returns total count and list of datasets.
|
||||
# @RETURN: Tuple[int, List[Dict]] - Кортеж (общее количество, список датасетов).
|
||||
def get_datasets(self, query: Optional[Dict] = None) -> Tuple[int, List[Dict]]:
|
||||
with belief_scope("get_datasets"):
|
||||
@@ -201,6 +215,8 @@ class SupersetClient:
|
||||
# [DEF:get_dataset:Function]
|
||||
# @PURPOSE: Получает информацию о конкретном датасете по его ID.
|
||||
# @PARAM: dataset_id (int) - ID датасета.
|
||||
# @PRE: dataset_id must exist.
|
||||
# @POST: Returns dataset details.
|
||||
# @RETURN: Dict - Информация о датасете.
|
||||
def get_dataset(self, dataset_id: int) -> Dict:
|
||||
with belief_scope("SupersetClient.get_dataset", f"id={dataset_id}"):
|
||||
@@ -215,6 +231,8 @@ class SupersetClient:
|
||||
# @PURPOSE: Обновляет данные датасета по его ID.
|
||||
# @PARAM: dataset_id (int) - ID датасета.
|
||||
# @PARAM: data (Dict) - Данные для обновления.
|
||||
# @PRE: dataset_id must exist.
|
||||
# @POST: Dataset is updated in Superset.
|
||||
# @RETURN: Dict - Ответ API.
|
||||
def update_dataset(self, dataset_id: int, data: Dict) -> Dict:
|
||||
with belief_scope("SupersetClient.update_dataset", f"id={dataset_id}"):
|
||||
@@ -237,6 +255,8 @@ class SupersetClient:
|
||||
# [DEF:get_databases:Function]
|
||||
# @PURPOSE: Получает полный список баз данных.
|
||||
# @PARAM: query (Optional[Dict]) - Дополнительные параметры запроса.
|
||||
# @PRE: Client is authenticated.
|
||||
# @POST: Returns total count and list of databases.
|
||||
# @RETURN: Tuple[int, List[Dict]] - Кортеж (общее количество, список баз данных).
|
||||
def get_databases(self, query: Optional[Dict] = None) -> Tuple[int, List[Dict]]:
|
||||
with belief_scope("get_databases"):
|
||||
@@ -256,6 +276,8 @@ class SupersetClient:
|
||||
# [DEF:get_database:Function]
|
||||
# @PURPOSE: Получает информацию о конкретной базе данных по её ID.
|
||||
# @PARAM: database_id (int) - ID базы данных.
|
||||
# @PRE: database_id must exist.
|
||||
# @POST: Returns database details.
|
||||
# @RETURN: Dict - Информация о базе данных.
|
||||
def get_database(self, database_id: int) -> Dict:
|
||||
with belief_scope("get_database"):
|
||||
@@ -268,6 +290,8 @@ class SupersetClient:
|
||||
|
||||
# [DEF:get_databases_summary:Function]
|
||||
# @PURPOSE: Fetch a summary of databases including uuid, name, and engine.
|
||||
# @PRE: Client is authenticated.
|
||||
# @POST: Returns list of database summaries.
|
||||
# @RETURN: List[Dict] - Summary of databases.
|
||||
def get_databases_summary(self) -> List[Dict]:
|
||||
with belief_scope("SupersetClient.get_databases_summary"):
|
||||
@@ -286,6 +310,8 @@ class SupersetClient:
|
||||
# [DEF:get_database_by_uuid:Function]
|
||||
# @PURPOSE: Find a database by its UUID.
|
||||
# @PARAM: db_uuid (str) - The UUID of the database.
|
||||
# @PRE: db_uuid must be a valid UUID string.
|
||||
# @POST: Returns database info or None.
|
||||
# @RETURN: Optional[Dict] - Database info if found, else None.
|
||||
def get_database_by_uuid(self, db_uuid: str) -> Optional[Dict]:
|
||||
with belief_scope("SupersetClient.get_database_by_uuid", f"uuid={db_uuid}"):
|
||||
@@ -301,6 +327,9 @@ class SupersetClient:
|
||||
# [SECTION: HELPERS]
|
||||
|
||||
# [DEF:_resolve_target_id_for_delete:Function]
|
||||
# @PURPOSE: Resolves a dashboard ID from either an ID or a slug.
|
||||
# @PRE: Either dash_id or dash_slug should be provided.
|
||||
# @POST: Returns the resolved ID or None.
|
||||
def _resolve_target_id_for_delete(self, dash_id: Optional[int], dash_slug: Optional[str]) -> Optional[int]:
|
||||
with belief_scope("_resolve_target_id_for_delete"):
|
||||
if dash_id is not None:
|
||||
@@ -319,6 +348,9 @@ class SupersetClient:
|
||||
# [/DEF:_resolve_target_id_for_delete:Function]
|
||||
|
||||
# [DEF:_do_import:Function]
|
||||
# @PURPOSE: Performs the actual multipart upload for import.
|
||||
# @PRE: file_name must be a path to an existing ZIP file.
|
||||
# @POST: Returns the API response from the upload.
|
||||
def _do_import(self, file_name: Union[str, Path]) -> Dict:
|
||||
with belief_scope("_do_import"):
|
||||
app_logger.debug(f"[_do_import][State] Uploading file: {file_name}")
|
||||
@@ -336,6 +368,9 @@ class SupersetClient:
|
||||
# [/DEF:_do_import:Function]
|
||||
|
||||
# [DEF:_validate_export_response:Function]
|
||||
# @PURPOSE: Validates that the export response is a non-empty ZIP archive.
|
||||
# @PRE: response must be a valid requests.Response object.
|
||||
# @POST: Raises SupersetAPIError if validation fails.
|
||||
def _validate_export_response(self, response: Response, dashboard_id: int) -> None:
|
||||
with belief_scope("_validate_export_response"):
|
||||
content_type = response.headers.get("Content-Type", "")
|
||||
@@ -346,6 +381,9 @@ class SupersetClient:
|
||||
# [/DEF:_validate_export_response:Function]
|
||||
|
||||
# [DEF:_resolve_export_filename:Function]
|
||||
# @PURPOSE: Determines the filename for an exported dashboard.
|
||||
# @PRE: response must contain Content-Disposition header or dashboard_id must be provided.
|
||||
# @POST: Returns a sanitized filename string.
|
||||
def _resolve_export_filename(self, response: Response, dashboard_id: int) -> str:
|
||||
with belief_scope("_resolve_export_filename"):
|
||||
filename = get_filename_from_headers(dict(response.headers))
|
||||
@@ -358,6 +396,9 @@ class SupersetClient:
|
||||
# [/DEF:_resolve_export_filename:Function]
|
||||
|
||||
# [DEF:_validate_query_params:Function]
|
||||
# @PURPOSE: Ensures query parameters have default page and page_size.
|
||||
# @PRE: query can be None or a dictionary.
|
||||
# @POST: Returns a dictionary with at least page and page_size.
|
||||
def _validate_query_params(self, query: Optional[Dict]) -> Dict:
|
||||
with belief_scope("_validate_query_params"):
|
||||
base_query = {"page": 0, "page_size": 1000}
|
||||
@@ -365,6 +406,9 @@ class SupersetClient:
|
||||
# [/DEF:_validate_query_params:Function]
|
||||
|
||||
# [DEF:_fetch_total_object_count:Function]
|
||||
# @PURPOSE: Fetches the total number of items for a given endpoint.
|
||||
# @PRE: endpoint must be a valid Superset API path.
|
||||
# @POST: Returns the total count as an integer.
|
||||
def _fetch_total_object_count(self, endpoint: str) -> int:
|
||||
with belief_scope("_fetch_total_object_count"):
|
||||
return self.network.fetch_paginated_count(
|
||||
@@ -375,12 +419,18 @@ class SupersetClient:
|
||||
# [/DEF:_fetch_total_object_count:Function]
|
||||
|
||||
# [DEF:_fetch_all_pages:Function]
|
||||
# @PURPOSE: Iterates through all pages to collect all data items.
|
||||
# @PRE: pagination_options must contain base_query, total_count, and results_field.
|
||||
# @POST: Returns a combined list of all items.
|
||||
def _fetch_all_pages(self, endpoint: str, pagination_options: Dict) -> List[Dict]:
|
||||
with belief_scope("_fetch_all_pages"):
|
||||
return self.network.fetch_paginated_data(endpoint=endpoint, pagination_options=pagination_options)
|
||||
# [/DEF:_fetch_all_pages:Function]
|
||||
|
||||
# [DEF:_validate_import_file:Function]
|
||||
# @PURPOSE: Validates that the file to be imported is a valid ZIP with metadata.yaml.
|
||||
# @PRE: zip_path must be a path to a file.
|
||||
# @POST: Raises error if file is missing, not a ZIP, or missing metadata.
|
||||
def _validate_import_file(self, zip_path: Union[str, Path]) -> None:
|
||||
with belief_scope("_validate_import_file"):
|
||||
path = Path(zip_path)
|
||||
|
||||
@@ -24,8 +24,10 @@ from ..logger import logger as app_logger, belief_scope
|
||||
# [/SECTION]
|
||||
|
||||
# [DEF:InvalidZipFormatError:Class]
|
||||
# @PURPOSE: Exception raised when a file is not a valid ZIP archive.
|
||||
class InvalidZipFormatError(Exception):
|
||||
pass
|
||||
# [/DEF:InvalidZipFormatError:Class]
|
||||
|
||||
# [DEF:create_temp_file:Function]
|
||||
# @PURPOSE: Контекстный менеджер для создания временного файла или директории с гарантированным удалением.
|
||||
|
||||
@@ -20,31 +20,71 @@ from ..logger import logger as app_logger, belief_scope
|
||||
# [/SECTION]
|
||||
|
||||
# [DEF:SupersetAPIError:Class]
|
||||
# @PURPOSE: Base exception for all Superset API related errors.
|
||||
class SupersetAPIError(Exception):
|
||||
# [DEF:__init__:Function]
|
||||
# @PURPOSE: Initializes the exception with a message and context.
|
||||
# @PRE: message is a string, context is a dict.
|
||||
# @POST: Exception is initialized with context.
|
||||
def __init__(self, message: str = "Superset API error", **context: Any):
|
||||
self.context = context
|
||||
super().__init__(f"[API_FAILURE] {message} | Context: {self.context}")
|
||||
with belief_scope("SupersetAPIError.__init__"):
|
||||
self.context = context
|
||||
super().__init__(f"[API_FAILURE] {message} | Context: {self.context}")
|
||||
# [/DEF:__init__:Function]
|
||||
# [/DEF:SupersetAPIError:Class]
|
||||
|
||||
# [DEF:AuthenticationError:Class]
|
||||
# @PURPOSE: Exception raised when authentication fails.
|
||||
class AuthenticationError(SupersetAPIError):
|
||||
# [DEF:__init__:Function]
|
||||
# @PURPOSE: Initializes the authentication error.
|
||||
# @PRE: message is a string, context is a dict.
|
||||
# @POST: AuthenticationError is initialized.
|
||||
def __init__(self, message: str = "Authentication failed", **context: Any):
|
||||
super().__init__(message, type="authentication", **context)
|
||||
with belief_scope("AuthenticationError.__init__"):
|
||||
super().__init__(message, type="authentication", **context)
|
||||
# [/DEF:__init__:Function]
|
||||
# [/DEF:AuthenticationError:Class]
|
||||
|
||||
# [DEF:PermissionDeniedError:Class]
|
||||
# @PURPOSE: Exception raised when access is denied.
|
||||
class PermissionDeniedError(AuthenticationError):
|
||||
# [DEF:__init__:Function]
|
||||
# @PURPOSE: Initializes the permission denied error.
|
||||
# @PRE: message is a string, context is a dict.
|
||||
# @POST: PermissionDeniedError is initialized.
|
||||
def __init__(self, message: str = "Permission denied", **context: Any):
|
||||
super().__init__(message, **context)
|
||||
with belief_scope("PermissionDeniedError.__init__"):
|
||||
super().__init__(message, **context)
|
||||
# [/DEF:__init__:Function]
|
||||
# [/DEF:PermissionDeniedError:Class]
|
||||
|
||||
# [DEF:DashboardNotFoundError:Class]
|
||||
# @PURPOSE: Exception raised when a dashboard cannot be found.
|
||||
class DashboardNotFoundError(SupersetAPIError):
|
||||
# [DEF:__init__:Function]
|
||||
# @PURPOSE: Initializes the not found error with resource ID.
|
||||
# @PRE: resource_id is provided.
|
||||
# @POST: DashboardNotFoundError is initialized.
|
||||
def __init__(self, resource_id: Union[int, str], message: str = "Dashboard not found", **context: Any):
|
||||
super().__init__(f"Dashboard '{resource_id}' {message}", subtype="not_found", resource_id=resource_id, **context)
|
||||
with belief_scope("DashboardNotFoundError.__init__"):
|
||||
super().__init__(f"Dashboard '{resource_id}' {message}", subtype="not_found", resource_id=resource_id, **context)
|
||||
# [/DEF:__init__:Function]
|
||||
# [/DEF:DashboardNotFoundError:Class]
|
||||
|
||||
# [DEF:NetworkError:Class]
|
||||
# @PURPOSE: Exception raised when a network level error occurs.
|
||||
class NetworkError(Exception):
|
||||
# [DEF:__init__:Function]
|
||||
# @PURPOSE: Initializes the network error.
|
||||
# @PRE: message is a string.
|
||||
# @POST: NetworkError is initialized.
|
||||
def __init__(self, message: str = "Network connection failed", **context: Any):
|
||||
self.context = context
|
||||
super().__init__(f"[NETWORK_FAILURE] {message} | Context: {self.context}")
|
||||
with belief_scope("NetworkError.__init__"):
|
||||
self.context = context
|
||||
super().__init__(f"[NETWORK_FAILURE] {message} | Context: {self.context}")
|
||||
# [/DEF:__init__:Function]
|
||||
# [/DEF:NetworkError:Class]
|
||||
|
||||
# [DEF:APIClient:Class]
|
||||
# @PURPOSE: Инкапсулирует HTTP-логику для работы с API, включая сессии, аутентификацию, и обработку запросов.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""
|
||||
[DEF:GitModels:Module]
|
||||
Git-specific SQLAlchemy models for configuration and repository tracking.
|
||||
@RELATION: specs/011-git-integration-dashboard/data-model.md
|
||||
"""
|
||||
# [DEF:GitModels:Module]
|
||||
# @SEMANTICS: git, models, sqlalchemy, database, schema
|
||||
# @PURPOSE: Git-specific SQLAlchemy models for configuration and repository tracking.
|
||||
# @LAYER: Model
|
||||
# @RELATION: specs/011-git-integration-dashboard/data-model.md
|
||||
|
||||
import enum
|
||||
from datetime import datetime
|
||||
|
||||
31
backend/src/models/storage.py
Normal file
31
backend/src/models/storage.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
# [DEF:FileCategory:Class]
|
||||
# @PURPOSE: Enumeration of supported file categories in the storage system.
|
||||
class FileCategory(str, Enum):
|
||||
BACKUP = "backups"
|
||||
REPOSITORY = "repositorys"
|
||||
# [/DEF:FileCategory:Class]
|
||||
|
||||
# [DEF:StorageConfig:Class]
|
||||
# @PURPOSE: Configuration model for the storage system, defining paths and naming patterns.
|
||||
class StorageConfig(BaseModel):
|
||||
root_path: str = Field(default="backups", description="Absolute path to the storage root directory.")
|
||||
backup_structure_pattern: str = Field(default="{category}/", description="Pattern for backup directory structure.")
|
||||
repo_structure_pattern: str = Field(default="{category}/", description="Pattern for repository directory structure.")
|
||||
filename_pattern: str = Field(default="{name}_{timestamp}", description="Pattern for filenames.")
|
||||
# [/DEF:StorageConfig:Class]
|
||||
|
||||
# [DEF:StoredFile:Class]
|
||||
# @PURPOSE: Data model representing metadata for a file stored in the system.
|
||||
class StoredFile(BaseModel):
|
||||
name: str = Field(..., description="Name of the file (including extension).")
|
||||
path: str = Field(..., description="Relative path from storage root.")
|
||||
size: int = Field(..., ge=0, description="Size of the file in bytes.")
|
||||
created_at: datetime = Field(..., description="Creation timestamp.")
|
||||
category: FileCategory = Field(..., description="Category of the file.")
|
||||
mime_type: Optional[str] = Field(None, description="MIME type of the file.")
|
||||
# [/DEF:StoredFile:Class]
|
||||
@@ -84,7 +84,7 @@ class BackupPlugin(PluginBase):
|
||||
with belief_scope("get_schema"):
|
||||
config_manager = get_config_manager()
|
||||
envs = [e.name for e in config_manager.get_environments()]
|
||||
default_path = config_manager.get_config().settings.backup_path
|
||||
default_path = config_manager.get_config().settings.storage.root_path
|
||||
|
||||
return {
|
||||
"type": "object",
|
||||
@@ -95,14 +95,8 @@ class BackupPlugin(PluginBase):
|
||||
"description": "The Superset environment to back up.",
|
||||
"enum": envs if envs else [],
|
||||
},
|
||||
"backup_path": {
|
||||
"type": "string",
|
||||
"title": "Backup Path",
|
||||
"description": "The root directory to save backups to.",
|
||||
"default": default_path
|
||||
}
|
||||
},
|
||||
"required": ["env", "backup_path"],
|
||||
"required": ["env"],
|
||||
}
|
||||
# [/DEF:get_schema:Function]
|
||||
|
||||
@@ -126,8 +120,9 @@ class BackupPlugin(PluginBase):
|
||||
if not env:
|
||||
raise KeyError("env")
|
||||
|
||||
backup_path_str = params.get("backup_path") or config_manager.get_config().settings.backup_path
|
||||
backup_path = Path(backup_path_str)
|
||||
storage_settings = config_manager.get_config().settings.storage
|
||||
# Use 'backups' subfolder within the storage root
|
||||
backup_path = Path(storage_settings.root_path) / "backups"
|
||||
|
||||
from ..core.logger import logger as app_logger
|
||||
app_logger.info(f"[BackupPlugin][Entry] Starting backup for {env}.")
|
||||
|
||||
@@ -31,6 +31,7 @@ class GitPlugin(PluginBase):
|
||||
|
||||
# [DEF:__init__:Function]
|
||||
# @PURPOSE: Инициализирует плагин и его зависимости.
|
||||
# @PRE: config.json exists or shared config_manager is available.
|
||||
# @POST: Инициализированы git_service и config_manager.
|
||||
def __init__(self):
|
||||
with belief_scope("GitPlugin.__init__"):
|
||||
@@ -59,23 +60,49 @@ class GitPlugin(PluginBase):
|
||||
# [/DEF:__init__:Function]
|
||||
|
||||
@property
|
||||
# [DEF:id:Function]
|
||||
# @PURPOSE: Returns the plugin identifier.
|
||||
# @PRE: GitPlugin is initialized.
|
||||
# @POST: Returns 'git-integration'.
|
||||
def id(self) -> str:
|
||||
return "git-integration"
|
||||
with belief_scope("GitPlugin.id"):
|
||||
return "git-integration"
|
||||
# [/DEF:id:Function]
|
||||
|
||||
@property
|
||||
# [DEF:name:Function]
|
||||
# @PURPOSE: Returns the plugin name.
|
||||
# @PRE: GitPlugin is initialized.
|
||||
# @POST: Returns the human-readable name.
|
||||
def name(self) -> str:
|
||||
return "Git Integration"
|
||||
with belief_scope("GitPlugin.name"):
|
||||
return "Git Integration"
|
||||
# [/DEF:name:Function]
|
||||
|
||||
@property
|
||||
# [DEF:description:Function]
|
||||
# @PURPOSE: Returns the plugin description.
|
||||
# @PRE: GitPlugin is initialized.
|
||||
# @POST: Returns the plugin's purpose description.
|
||||
def description(self) -> str:
|
||||
return "Version control for Superset dashboards"
|
||||
with belief_scope("GitPlugin.description"):
|
||||
return "Version control for Superset dashboards"
|
||||
# [/DEF:description:Function]
|
||||
|
||||
@property
|
||||
# [DEF:version:Function]
|
||||
# @PURPOSE: Returns the plugin version.
|
||||
# @PRE: GitPlugin is initialized.
|
||||
# @POST: Returns the version string.
|
||||
def version(self) -> str:
|
||||
return "0.1.0"
|
||||
with belief_scope("GitPlugin.version"):
|
||||
return "0.1.0"
|
||||
# [/DEF:version:Function]
|
||||
|
||||
# [DEF:get_schema:Function]
|
||||
# @PURPOSE: Возвращает JSON-схему параметров для выполнения задач плагина.
|
||||
# @PRE: GitPlugin is initialized.
|
||||
# @POST: Returns a JSON schema dictionary.
|
||||
# @RETURN: Dict[str, Any] - Схема параметров.
|
||||
def get_schema(self) -> Dict[str, Any]:
|
||||
with belief_scope("GitPlugin.get_schema"):
|
||||
@@ -93,6 +120,7 @@ class GitPlugin(PluginBase):
|
||||
|
||||
# [DEF:initialize:Function]
|
||||
# @PURPOSE: Выполняет начальную настройку плагина.
|
||||
# @PRE: GitPlugin is initialized.
|
||||
# @POST: Плагин готов к выполнению задач.
|
||||
async def initialize(self):
|
||||
with belief_scope("GitPlugin.initialize"):
|
||||
@@ -281,6 +309,8 @@ class GitPlugin(PluginBase):
|
||||
# [DEF:_get_env:Function]
|
||||
# @PURPOSE: Вспомогательный метод для получения конфигурации окружения.
|
||||
# @PARAM: env_id (Optional[str]) - ID окружения.
|
||||
# @PRE: env_id is a string or None.
|
||||
# @POST: Returns an Environment object from config or DB.
|
||||
# @RETURN: Environment - Объект конфигурации окружения.
|
||||
def _get_env(self, env_id: Optional[str] = None):
|
||||
with belief_scope("GitPlugin._get_env"):
|
||||
@@ -341,5 +371,6 @@ class GitPlugin(PluginBase):
|
||||
raise ValueError("No environments configured. Please add a Superset Environment in Settings.")
|
||||
# [/DEF:_get_env:Function]
|
||||
|
||||
# [/DEF:initialize:Function]
|
||||
# [/DEF:GitPlugin:Class]
|
||||
# [/DEF:backend.src.plugins.git_plugin:Module]
|
||||
3
backend/src/plugins/storage/__init__.py
Normal file
3
backend/src/plugins/storage/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .plugin import StoragePlugin
|
||||
|
||||
__all__ = ["StoragePlugin"]
|
||||
324
backend/src/plugins/storage/plugin.py
Normal file
324
backend/src/plugins/storage/plugin.py
Normal file
@@ -0,0 +1,324 @@
|
||||
# [DEF:StoragePlugin:Module]
|
||||
#
|
||||
# @SEMANTICS: storage, files, filesystem, plugin
|
||||
# @PURPOSE: Provides core filesystem operations for managing backups and repositories.
|
||||
# @LAYER: App
|
||||
# @RELATION: IMPLEMENTS -> PluginBase
|
||||
# @RELATION: DEPENDS_ON -> backend.src.models.storage
|
||||
#
|
||||
# @INVARIANT: All file operations must be restricted to the configured storage root.
|
||||
|
||||
# [SECTION: IMPORTS]
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, List, Optional
|
||||
from fastapi import UploadFile
|
||||
|
||||
from ...core.plugin_base import PluginBase
|
||||
from ...core.logger import belief_scope, logger
|
||||
from ...models.storage import StoredFile, FileCategory, StorageConfig
|
||||
from ...dependencies import get_config_manager
|
||||
# [/SECTION]
|
||||
|
||||
# [DEF:StoragePlugin:Class]
|
||||
# @PURPOSE: Implementation of the storage management plugin.
|
||||
class StoragePlugin(PluginBase):
|
||||
"""
|
||||
Plugin for managing local file storage for backups and repositories.
|
||||
"""
|
||||
|
||||
# [DEF:__init__:Function]
|
||||
# @PURPOSE: Initializes the StoragePlugin and ensures required directories exist.
|
||||
# @PRE: Configuration manager must be accessible.
|
||||
# @POST: Storage root and category directories are created on disk.
|
||||
def __init__(self):
|
||||
with belief_scope("StoragePlugin:init"):
|
||||
self.ensure_directories()
|
||||
# [/DEF:__init__:Function]
|
||||
|
||||
@property
|
||||
# [DEF:id:Function]
|
||||
# @PURPOSE: Returns the unique identifier for the storage plugin.
|
||||
# @PRE: None.
|
||||
# @POST: Returns the plugin ID string.
|
||||
# @RETURN: str - "storage-manager"
|
||||
def id(self) -> str:
|
||||
with belief_scope("StoragePlugin:id"):
|
||||
return "storage-manager"
|
||||
# [/DEF:id:Function]
|
||||
|
||||
@property
|
||||
# [DEF:name:Function]
|
||||
# @PURPOSE: Returns the human-readable name of the storage plugin.
|
||||
# @PRE: None.
|
||||
# @POST: Returns the plugin name string.
|
||||
# @RETURN: str - "Storage Manager"
|
||||
def name(self) -> str:
|
||||
with belief_scope("StoragePlugin:name"):
|
||||
return "Storage Manager"
|
||||
# [/DEF:name:Function]
|
||||
|
||||
@property
|
||||
# [DEF:description:Function]
|
||||
# @PURPOSE: Returns a description of the storage plugin.
|
||||
# @PRE: None.
|
||||
# @POST: Returns the plugin description string.
|
||||
# @RETURN: str - Plugin description.
|
||||
def description(self) -> str:
|
||||
with belief_scope("StoragePlugin:description"):
|
||||
return "Manages local file storage for backups and repositories."
|
||||
# [/DEF:description:Function]
|
||||
|
||||
@property
|
||||
# [DEF:version:Function]
|
||||
# @PURPOSE: Returns the version of the storage plugin.
|
||||
# @PRE: None.
|
||||
# @POST: Returns the version string.
|
||||
# @RETURN: str - "1.0.0"
|
||||
def version(self) -> str:
|
||||
with belief_scope("StoragePlugin:version"):
|
||||
return "1.0.0"
|
||||
# [/DEF:version:Function]
|
||||
|
||||
# [DEF:get_schema:Function]
|
||||
# @PURPOSE: Returns the JSON schema for storage plugin parameters.
|
||||
# @PRE: None.
|
||||
# @POST: Returns a dictionary representing the JSON schema.
|
||||
# @RETURN: Dict[str, Any] - JSON schema.
|
||||
def get_schema(self) -> Dict[str, Any]:
|
||||
with belief_scope("StoragePlugin:get_schema"):
|
||||
return {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"category": {
|
||||
"type": "string",
|
||||
"enum": [c.value for c in FileCategory],
|
||||
"title": "Category"
|
||||
}
|
||||
},
|
||||
"required": ["category"]
|
||||
}
|
||||
# [/DEF:get_schema:Function]
|
||||
|
||||
# [DEF:execute:Function]
|
||||
# @PURPOSE: Executes storage-related tasks (placeholder for PluginBase compliance).
|
||||
# @PRE: params must match the plugin schema.
|
||||
# @POST: Task is executed and logged.
|
||||
async def execute(self, params: Dict[str, Any]):
|
||||
with belief_scope("StoragePlugin:execute"):
|
||||
logger.info(f"[StoragePlugin][Action] Executing with params: {params}")
|
||||
# [/DEF:execute:Function]
|
||||
|
||||
# [DEF:get_storage_root:Function]
|
||||
# @PURPOSE: Resolves the absolute path to the storage root.
|
||||
# @PRE: Settings must define a storage root path.
|
||||
# @POST: Returns a Path object representing the storage root.
|
||||
def get_storage_root(self) -> Path:
|
||||
with belief_scope("StoragePlugin:get_storage_root"):
|
||||
config_manager = get_config_manager()
|
||||
global_settings = config_manager.get_config().settings
|
||||
|
||||
# Use storage.root_path as the source of truth for storage UI
|
||||
root = Path(global_settings.storage.root_path)
|
||||
|
||||
if not root.is_absolute():
|
||||
# Resolve relative to the backend directory
|
||||
# Path(__file__) is backend/src/plugins/storage/plugin.py
|
||||
# parents[3] is the project root (ss-tools)
|
||||
# We need to ensure it's relative to where backend/ is
|
||||
project_root = Path(__file__).parents[3]
|
||||
root = (project_root / root).resolve()
|
||||
return root
|
||||
# [/DEF:get_storage_root:Function]
|
||||
|
||||
# [DEF:resolve_path:Function]
|
||||
# @PURPOSE: Resolves a dynamic path pattern using provided variables.
|
||||
# @PARAM: pattern (str) - The path pattern to resolve.
|
||||
# @PARAM: variables (Dict[str, str]) - Variables to substitute in the pattern.
|
||||
# @PRE: pattern must be a valid format string.
|
||||
# @POST: Returns the resolved path string.
|
||||
# @RETURN: str - The resolved path.
|
||||
def resolve_path(self, pattern: str, variables: Dict[str, str]) -> str:
|
||||
with belief_scope("StoragePlugin:resolve_path"):
|
||||
# Add common variables
|
||||
vars_with_defaults = {
|
||||
"timestamp": datetime.now().strftime("%Y%m%dT%H%M%S"),
|
||||
**variables
|
||||
}
|
||||
try:
|
||||
resolved = pattern.format(**vars_with_defaults)
|
||||
# Clean up any double slashes or leading/trailing slashes for relative path
|
||||
return os.path.normpath(resolved).strip("/")
|
||||
except KeyError as e:
|
||||
logger.warning(f"[StoragePlugin][Coherence:Failed] Missing variable for path resolution: {e}")
|
||||
# Fallback to literal pattern if formatting fails partially (or handle as needed)
|
||||
return pattern.replace("{", "").replace("}", "")
|
||||
# [/DEF:resolve_path:Function]
|
||||
|
||||
# [DEF:ensure_directories:Function]
|
||||
# @PURPOSE: Creates the storage root and category subdirectories if they don't exist.
|
||||
# @PRE: Storage root must be resolvable.
|
||||
# @POST: Directories are created on the filesystem.
|
||||
# @SIDE_EFFECT: Creates directories on the filesystem.
|
||||
def ensure_directories(self):
|
||||
with belief_scope("StoragePlugin:ensure_directories"):
|
||||
root = self.get_storage_root()
|
||||
for category in FileCategory:
|
||||
# Use singular name for consistency with BackupPlugin and GitService
|
||||
path = root / category.value
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
logger.debug(f"[StoragePlugin][Action] Ensured directory: {path}")
|
||||
# [/DEF:ensure_directories:Function]
|
||||
|
||||
# [DEF:validate_path:Function]
|
||||
# @PURPOSE: Prevents path traversal attacks by ensuring the path is within the storage root.
|
||||
# @PRE: path must be a Path object.
|
||||
# @POST: Returns the resolved absolute path if valid, otherwise raises ValueError.
|
||||
def validate_path(self, path: Path) -> Path:
|
||||
with belief_scope("StoragePlugin:validate_path"):
|
||||
root = self.get_storage_root().resolve()
|
||||
resolved = path.resolve()
|
||||
try:
|
||||
resolved.relative_to(root)
|
||||
except ValueError:
|
||||
logger.error(f"[StoragePlugin][Coherence:Failed] Path traversal detected: {resolved} is not under {root}")
|
||||
raise ValueError("Access denied: Path is outside of storage root.")
|
||||
return resolved
|
||||
# [/DEF:validate_path:Function]
|
||||
|
||||
# [DEF:list_files:Function]
|
||||
# @PURPOSE: Lists all files and directories in a specific category and subpath.
|
||||
# @PARAM: category (Optional[FileCategory]) - The category to list.
|
||||
# @PARAM: subpath (Optional[str]) - Nested path within the category.
|
||||
# @PRE: Storage root must exist.
|
||||
# @POST: Returns a list of StoredFile objects.
|
||||
# @RETURN: List[StoredFile] - List of file and directory metadata objects.
|
||||
def list_files(self, category: Optional[FileCategory] = None, subpath: Optional[str] = None) -> List[StoredFile]:
|
||||
with belief_scope("StoragePlugin:list_files"):
|
||||
root = self.get_storage_root()
|
||||
logger.info(f"[StoragePlugin][Action] Listing files in root: {root}, category: {category}, subpath: {subpath}")
|
||||
files = []
|
||||
|
||||
categories = [category] if category else list(FileCategory)
|
||||
|
||||
for cat in categories:
|
||||
# Scan the category subfolder + optional subpath
|
||||
base_dir = root / cat.value
|
||||
if subpath:
|
||||
target_dir = self.validate_path(base_dir / subpath)
|
||||
else:
|
||||
target_dir = base_dir
|
||||
|
||||
if not target_dir.exists():
|
||||
continue
|
||||
|
||||
logger.debug(f"[StoragePlugin][Action] Scanning directory: {target_dir}")
|
||||
|
||||
# Use os.scandir for better performance and to distinguish files vs dirs
|
||||
with os.scandir(target_dir) as it:
|
||||
for entry in it:
|
||||
# Skip logs
|
||||
if "Logs" in entry.path:
|
||||
continue
|
||||
|
||||
stat = entry.stat()
|
||||
is_dir = entry.is_dir()
|
||||
|
||||
files.append(StoredFile(
|
||||
name=entry.name,
|
||||
path=str(Path(entry.path).relative_to(root)),
|
||||
size=stat.st_size if not is_dir else 0,
|
||||
created_at=datetime.fromtimestamp(stat.st_ctime),
|
||||
category=cat,
|
||||
mime_type="directory" if is_dir else None
|
||||
))
|
||||
|
||||
# Sort: directories first, then by name
|
||||
return sorted(files, key=lambda x: (x.mime_type != "directory", x.name))
|
||||
# [/DEF:list_files:Function]
|
||||
|
||||
# [DEF:save_file:Function]
|
||||
# @PURPOSE: Saves an uploaded file to the specified category and optional subpath.
|
||||
# @PARAM: file (UploadFile) - The uploaded file.
|
||||
# @PARAM: category (FileCategory) - The target category.
|
||||
# @PARAM: subpath (Optional[str]) - The target subpath.
|
||||
# @PRE: file must be a valid UploadFile; category must be valid.
|
||||
# @POST: File is written to disk and metadata is returned.
|
||||
# @RETURN: StoredFile - Metadata of the saved file.
|
||||
# @SIDE_EFFECT: Writes file to disk.
|
||||
async def save_file(self, file: UploadFile, category: FileCategory, subpath: Optional[str] = None) -> StoredFile:
|
||||
with belief_scope("StoragePlugin:save_file"):
|
||||
root = self.get_storage_root()
|
||||
dest_dir = root / category.value
|
||||
if subpath:
|
||||
dest_dir = dest_dir / subpath
|
||||
|
||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
dest_path = self.validate_path(dest_dir / file.filename)
|
||||
|
||||
with dest_path.open("wb") as buffer:
|
||||
shutil.copyfileobj(file.file, buffer)
|
||||
|
||||
stat = dest_path.stat()
|
||||
return StoredFile(
|
||||
name=dest_path.name,
|
||||
path=str(dest_path.relative_to(root)),
|
||||
size=stat.st_size,
|
||||
created_at=datetime.fromtimestamp(stat.st_ctime),
|
||||
category=category,
|
||||
mime_type=file.content_type
|
||||
)
|
||||
# [/DEF:save_file:Function]
|
||||
|
||||
# [DEF:delete_file:Function]
|
||||
# @PURPOSE: Deletes a file or directory from the specified category and path.
|
||||
# @PARAM: category (FileCategory) - The category.
|
||||
# @PARAM: path (str) - The relative path of the file or directory.
|
||||
# @PRE: path must belong to the specified category and exist on disk.
|
||||
# @POST: The file or directory is removed from disk.
|
||||
# @SIDE_EFFECT: Removes item from disk.
|
||||
def delete_file(self, category: FileCategory, path: str):
|
||||
with belief_scope("StoragePlugin:delete_file"):
|
||||
root = self.get_storage_root()
|
||||
# path is relative to root, but we ensure it starts with category
|
||||
full_path = self.validate_path(root / path)
|
||||
|
||||
if not str(Path(path)).startswith(category.value):
|
||||
raise ValueError(f"Path {path} does not belong to category {category}")
|
||||
|
||||
if full_path.exists():
|
||||
if full_path.is_dir():
|
||||
shutil.rmtree(full_path)
|
||||
else:
|
||||
full_path.unlink()
|
||||
logger.info(f"[StoragePlugin][Action] Deleted: {full_path}")
|
||||
else:
|
||||
raise FileNotFoundError(f"Item {path} not found")
|
||||
# [/DEF:delete_file:Function]
|
||||
|
||||
# [DEF:get_file_path:Function]
|
||||
# @PURPOSE: Returns the absolute path of a file for download.
|
||||
# @PARAM: category (FileCategory) - The category.
|
||||
# @PARAM: path (str) - The relative path of the file.
|
||||
# @PRE: path must belong to the specified category and be a file.
|
||||
# @POST: Returns the absolute Path to the file.
|
||||
# @RETURN: Path - Absolute path to the file.
|
||||
def get_file_path(self, category: FileCategory, path: str) -> Path:
|
||||
with belief_scope("StoragePlugin:get_file_path"):
|
||||
root = self.get_storage_root()
|
||||
file_path = self.validate_path(root / path)
|
||||
|
||||
if not str(Path(path)).startswith(category.value):
|
||||
raise ValueError(f"Path {path} does not belong to category {category}")
|
||||
|
||||
if not file_path.exists() or file_path.is_dir():
|
||||
raise FileNotFoundError(f"File {path} not found")
|
||||
|
||||
return file_path
|
||||
# [/DEF:get_file_path:Function]
|
||||
|
||||
# [/DEF:StoragePlugin:Class]
|
||||
# [/DEF:StoragePlugin:Module]
|
||||
@@ -29,9 +29,17 @@ class GitService:
|
||||
# [DEF:__init__:Function]
|
||||
# @PURPOSE: Initializes the GitService with a base path for repositories.
|
||||
# @PARAM: base_path (str) - Root directory for all Git clones.
|
||||
def __init__(self, base_path: str = "backend/git_repos"):
|
||||
# @PRE: base_path is a valid string path.
|
||||
# @POST: GitService is initialized; base_path directory exists.
|
||||
def __init__(self, base_path: str = "git_repos"):
|
||||
with belief_scope("GitService.__init__"):
|
||||
self.base_path = base_path
|
||||
# Resolve relative to the backend directory
|
||||
# Path(__file__) is backend/src/services/git_service.py
|
||||
# parents[2] is backend/
|
||||
from pathlib import Path
|
||||
backend_root = Path(__file__).parents[2]
|
||||
|
||||
self.base_path = str((backend_root / base_path).resolve())
|
||||
if not os.path.exists(self.base_path):
|
||||
os.makedirs(self.base_path)
|
||||
# [/DEF:__init__:Function]
|
||||
@@ -39,9 +47,12 @@ class GitService:
|
||||
# [DEF:_get_repo_path:Function]
|
||||
# @PURPOSE: Resolves the local filesystem path for a dashboard's repository.
|
||||
# @PARAM: dashboard_id (int)
|
||||
# @PRE: dashboard_id is an integer.
|
||||
# @POST: Returns the absolute or relative path to the dashboard's repo.
|
||||
# @RETURN: str
|
||||
def _get_repo_path(self, dashboard_id: int) -> str:
|
||||
return os.path.join(self.base_path, str(dashboard_id))
|
||||
with belief_scope("GitService._get_repo_path"):
|
||||
return os.path.join(self.base_path, str(dashboard_id))
|
||||
# [/DEF:_get_repo_path:Function]
|
||||
|
||||
# [DEF:init_repo:Function]
|
||||
@@ -49,6 +60,8 @@ class GitService:
|
||||
# @PARAM: dashboard_id (int)
|
||||
# @PARAM: remote_url (str)
|
||||
# @PARAM: pat (str) - Personal Access Token for authentication.
|
||||
# @PRE: dashboard_id is int, remote_url is valid Git URL, pat is provided.
|
||||
# @POST: Repository is cloned or opened at the local path.
|
||||
# @RETURN: Repo - GitPython Repo object.
|
||||
def init_repo(self, dashboard_id: int, remote_url: str, pat: str) -> Repo:
|
||||
with belief_scope("GitService.init_repo"):
|
||||
@@ -71,7 +84,8 @@ class GitService:
|
||||
|
||||
# [DEF:get_repo:Function]
|
||||
# @PURPOSE: Get Repo object for a dashboard.
|
||||
# @PRE: Repository must exist on disk.
|
||||
# @PRE: Repository must exist on disk for the given dashboard_id.
|
||||
# @POST: Returns a GitPython Repo instance for the dashboard.
|
||||
# @RETURN: Repo
|
||||
def get_repo(self, dashboard_id: int) -> Repo:
|
||||
with belief_scope("GitService.get_repo"):
|
||||
@@ -88,6 +102,8 @@ class GitService:
|
||||
|
||||
# [DEF:list_branches:Function]
|
||||
# @PURPOSE: List all branches for a dashboard's repository.
|
||||
# @PRE: Repository for dashboard_id exists.
|
||||
# @POST: Returns a list of branch metadata dictionaries.
|
||||
# @RETURN: List[dict]
|
||||
def list_branches(self, dashboard_id: int) -> List[dict]:
|
||||
with belief_scope("GitService.list_branches"):
|
||||
@@ -142,6 +158,8 @@ class GitService:
|
||||
# @PURPOSE: Create a new branch from an existing one.
|
||||
# @PARAM: name (str) - New branch name.
|
||||
# @PARAM: from_branch (str) - Source branch.
|
||||
# @PRE: Repository exists; name is valid; from_branch exists or repo is empty.
|
||||
# @POST: A new branch is created in the repository.
|
||||
def create_branch(self, dashboard_id: int, name: str, from_branch: str = "main"):
|
||||
with belief_scope("GitService.create_branch"):
|
||||
repo = self.get_repo(dashboard_id)
|
||||
@@ -171,10 +189,11 @@ class GitService:
|
||||
logger.error(f"[create_branch][Coherence:Failed] {e}")
|
||||
raise
|
||||
# [/DEF:create_branch:Function]
|
||||
# [/DEF:create_branch:Function]
|
||||
|
||||
# [DEF:checkout_branch:Function]
|
||||
# @PURPOSE: Switch to a specific branch.
|
||||
# @PRE: Repository exists and the specified branch name exists.
|
||||
# @POST: The repository working directory is updated to the specified branch.
|
||||
def checkout_branch(self, dashboard_id: int, name: str):
|
||||
with belief_scope("GitService.checkout_branch"):
|
||||
repo = self.get_repo(dashboard_id)
|
||||
@@ -186,6 +205,8 @@ class GitService:
|
||||
# @PURPOSE: Stage and commit changes.
|
||||
# @PARAM: message (str) - Commit message.
|
||||
# @PARAM: files (List[str]) - Optional list of specific files to stage.
|
||||
# @PRE: Repository exists and has changes (dirty) or files are specified.
|
||||
# @POST: Changes are staged and a new commit is created.
|
||||
def commit_changes(self, dashboard_id: int, message: str, files: List[str] = None):
|
||||
with belief_scope("GitService.commit_changes"):
|
||||
repo = self.get_repo(dashboard_id)
|
||||
@@ -208,6 +229,8 @@ class GitService:
|
||||
|
||||
# [DEF:push_changes:Function]
|
||||
# @PURPOSE: Push local commits to remote.
|
||||
# @PRE: Repository exists and has an 'origin' remote.
|
||||
# @POST: Local branch commits are pushed to origin.
|
||||
def push_changes(self, dashboard_id: int):
|
||||
with belief_scope("GitService.push_changes"):
|
||||
repo = self.get_repo(dashboard_id)
|
||||
@@ -240,6 +263,8 @@ class GitService:
|
||||
|
||||
# [DEF:pull_changes:Function]
|
||||
# @PURPOSE: Pull changes from remote.
|
||||
# @PRE: Repository exists and has an 'origin' remote.
|
||||
# @POST: Changes from origin are pulled and merged into the active branch.
|
||||
def pull_changes(self, dashboard_id: int):
|
||||
with belief_scope("GitService.pull_changes"):
|
||||
repo = self.get_repo(dashboard_id)
|
||||
@@ -261,6 +286,8 @@ class GitService:
|
||||
|
||||
# [DEF:get_status:Function]
|
||||
# @PURPOSE: Get current repository status (dirty files, untracked, etc.)
|
||||
# @PRE: Repository for dashboard_id exists.
|
||||
# @POST: Returns a dictionary representing the Git status.
|
||||
# @RETURN: dict
|
||||
def get_status(self, dashboard_id: int) -> dict:
|
||||
with belief_scope("GitService.get_status"):
|
||||
@@ -287,6 +314,8 @@ class GitService:
|
||||
# @PURPOSE: Generate diff for a file or the whole repository.
|
||||
# @PARAM: file_path (str) - Optional specific file.
|
||||
# @PARAM: staged (bool) - Whether to show staged changes.
|
||||
# @PRE: Repository for dashboard_id exists.
|
||||
# @POST: Returns the diff text as a string.
|
||||
# @RETURN: str
|
||||
def get_diff(self, dashboard_id: int, file_path: str = None, staged: bool = False) -> str:
|
||||
with belief_scope("GitService.get_diff"):
|
||||
@@ -303,6 +332,8 @@ class GitService:
|
||||
# [DEF:get_commit_history:Function]
|
||||
# @PURPOSE: Retrieve commit history for a repository.
|
||||
# @PARAM: limit (int) - Max number of commits to return.
|
||||
# @PRE: Repository for dashboard_id exists.
|
||||
# @POST: Returns a list of dictionaries for each commit in history.
|
||||
# @RETURN: List[dict]
|
||||
def get_commit_history(self, dashboard_id: int, limit: int = 50) -> List[dict]:
|
||||
with belief_scope("GitService.get_commit_history"):
|
||||
@@ -333,6 +364,8 @@ class GitService:
|
||||
# @PARAM: provider (GitProvider)
|
||||
# @PARAM: url (str)
|
||||
# @PARAM: pat (str)
|
||||
# @PRE: provider is valid; url is a valid HTTP(S) URL; pat is provided.
|
||||
# @POST: Returns True if connection to the provider's API succeeds.
|
||||
# @RETURN: bool
|
||||
async def test_connection(self, provider: GitProvider, url: str, pat: str) -> bool:
|
||||
with belief_scope("GitService.test_connection"):
|
||||
|
||||
BIN
backend/tasks.db
BIN
backend/tasks.db
Binary file not shown.
@@ -18,6 +18,4 @@ def test_environment_model():
|
||||
assert env.id == "test-id"
|
||||
assert env.name == "test-env"
|
||||
assert env.url == "http://localhost:8088/api/v1"
|
||||
# [/DEF:test_superset_config_url_normalization:Function]
|
||||
|
||||
# [/DEF:test_superset_config_invalid_url:Function]
|
||||
# [/DEF:test_environment_model:Function]
|
||||
|
||||
@@ -13,7 +13,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). The `base_url` is automatically normalized to include the `/api/v1` suffix if missing.
|
||||
- `GlobalSettings`: Global application parameters (e.g., `backup_path`).
|
||||
- `GlobalSettings`: Global application parameters (e.g., `storage.root_path`).
|
||||
- `AppConfig`: The root configuration object.
|
||||
|
||||
### Configuration Manager
|
||||
@@ -43,4 +43,4 @@ The settings page is located at `frontend/src/pages/Settings.svelte`. It provide
|
||||
|
||||
Existing plugins and utilities use the `ConfigManager` to fetch configuration:
|
||||
- `superset_tool/utils/init_clients.py`: Dynamically initializes Superset clients from the configured environments.
|
||||
- `BackupPlugin`: Uses the configured `backup_path` as the default storage location.
|
||||
- `BackupPlugin`: Uses the configured `storage.root_path` as the default storage location.
|
||||
|
||||
26
frontend/.gitignore
vendored
Executable file
26
frontend/.gitignore
vendored
Executable file
@@ -0,0 +1,26 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
.svelte-kit
|
||||
build
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@@ -12,6 +12,8 @@
|
||||
// [SECTION: IMPORTS]
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import type { DashboardMetadata } from '../types/dashboard';
|
||||
import { t } from '../lib/i18n';
|
||||
import { Button, Input } from '../lib/ui';
|
||||
import GitManager from './git/GitManager.svelte';
|
||||
// [/SECTION]
|
||||
|
||||
@@ -143,64 +145,66 @@
|
||||
<!-- [SECTION: TEMPLATE] -->
|
||||
<div class="dashboard-grid">
|
||||
<!-- Filter Input -->
|
||||
<div class="mb-4">
|
||||
<input
|
||||
type="text"
|
||||
<div class="mb-6">
|
||||
<Input
|
||||
bind:value={filterText}
|
||||
placeholder="Search dashboards..."
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder={$t.dashboard.search}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Grid/Table -->
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full bg-white border border-gray-300">
|
||||
<div class="overflow-x-auto rounded-lg border border-gray-200">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-4 py-2 border-b">
|
||||
<th class="px-6 py-3 text-left">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={allSelected}
|
||||
indeterminate={someSelected && !allSelected}
|
||||
on:change={(e) => handleSelectAll((e.target as HTMLInputElement).checked)}
|
||||
class="h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
|
||||
/>
|
||||
</th>
|
||||
<th class="px-4 py-2 border-b cursor-pointer" on:click={() => handleSort('title')}>
|
||||
Title {sortColumn === 'title' ? (sortDirection === 'asc' ? '↑' : '↓') : ''}
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:text-gray-700 transition-colors" on:click={() => handleSort('title')}>
|
||||
{$t.dashboard.title} {sortColumn === 'title' ? (sortDirection === 'asc' ? '↑' : '↓') : ''}
|
||||
</th>
|
||||
<th class="px-4 py-2 border-b cursor-pointer" on:click={() => handleSort('last_modified')}>
|
||||
Last Modified {sortColumn === 'last_modified' ? (sortDirection === 'asc' ? '↑' : '↓') : ''}
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:text-gray-700 transition-colors" on:click={() => handleSort('last_modified')}>
|
||||
{$t.dashboard.last_modified} {sortColumn === 'last_modified' ? (sortDirection === 'asc' ? '↑' : '↓') : ''}
|
||||
</th>
|
||||
<th class="px-4 py-2 border-b cursor-pointer" on:click={() => handleSort('status')}>
|
||||
Status {sortColumn === 'status' ? (sortDirection === 'asc' ? '↑' : '↓') : ''}
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:text-gray-700 transition-colors" on:click={() => handleSort('status')}>
|
||||
{$t.dashboard.status} {sortColumn === 'status' ? (sortDirection === 'asc' ? '↑' : '↓') : ''}
|
||||
</th>
|
||||
<th class="px-4 py-2 border-b">Git</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{$t.dashboard.git}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
{#each paginatedDashboards as dashboard (dashboard.id)}
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-2 border-b">
|
||||
<tr class="hover:bg-gray-50 transition-colors">
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedIds.includes(dashboard.id)}
|
||||
on:change={(e) => handleSelectionChange(dashboard.id, (e.target as HTMLInputElement).checked)}
|
||||
class="h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2 border-b">{dashboard.title}</td>
|
||||
<td class="px-4 py-2 border-b">{new Date(dashboard.last_modified).toLocaleDateString()}</td>
|
||||
<td class="px-4 py-2 border-b">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{dashboard.title}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{new Date(dashboard.last_modified).toLocaleDateString()}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
<span class="px-2 py-1 text-xs font-medium rounded-full {dashboard.status === 'published' ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'}">
|
||||
{dashboard.status}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-2 border-b">
|
||||
<button
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
on:click={() => openGit(dashboard)}
|
||||
class="text-indigo-600 hover:text-indigo-900 text-sm font-medium"
|
||||
class="text-blue-600 hover:text-blue-900"
|
||||
>
|
||||
Manage Git
|
||||
</button>
|
||||
{$t.git.manage}
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
@@ -209,25 +213,30 @@
|
||||
</div>
|
||||
|
||||
<!-- Pagination Controls -->
|
||||
<div class="flex items-center justify-between mt-4">
|
||||
<div class="text-sm text-gray-700">
|
||||
Showing {currentPage * pageSize + 1} to {Math.min((currentPage + 1) * pageSize, sortedDashboards.length)} of {sortedDashboards.length} dashboards
|
||||
<div class="flex items-center justify-between mt-6">
|
||||
<div class="text-sm text-gray-500">
|
||||
{($t.dashboard?.showing || "")
|
||||
.replace('{start}', (currentPage * pageSize + 1).toString())
|
||||
.replace('{end}', Math.min((currentPage + 1) * pageSize, sortedDashboards.length).toString())
|
||||
.replace('{total}', sortedDashboards.length.toString())}
|
||||
</div>
|
||||
<div class="flex space-x-2">
|
||||
<button
|
||||
class="px-3 py-1 text-sm border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
<div class="flex gap-2">
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
disabled={currentPage === 0}
|
||||
on:click={() => goToPage(currentPage - 1)}
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
<button
|
||||
class="px-3 py-1 text-sm border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
{$t.dashboard.previous}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
disabled={currentPage >= totalPages - 1}
|
||||
on:click={() => goToPage(currentPage + 1)}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
{$t.dashboard.next}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,61 +7,65 @@
|
||||
-->
|
||||
<script>
|
||||
import { page } from '$app/stores';
|
||||
import { t } from '$lib/i18n';
|
||||
import { LanguageSwitcher } from '$lib/ui';
|
||||
</script>
|
||||
|
||||
<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"
|
||||
class="text-2xl font-bold text-gray-800 focus:outline-none"
|
||||
>
|
||||
Superset Tools
|
||||
</a>
|
||||
<nav class="space-x-4">
|
||||
<nav class="flex items-center space-x-4">
|
||||
<a
|
||||
href="/"
|
||||
class="text-gray-600 hover:text-blue-600 font-medium {$page.url.pathname === '/' ? 'text-blue-600 border-b-2 border-blue-600' : ''}"
|
||||
>
|
||||
Dashboard
|
||||
{$t.nav.dashboard}
|
||||
</a>
|
||||
<a
|
||||
href="/migration"
|
||||
class="text-gray-600 hover:text-blue-600 font-medium {$page.url.pathname.startsWith('/migration') ? 'text-blue-600 border-b-2 border-blue-600' : ''}"
|
||||
>
|
||||
Migration
|
||||
{$t.nav.migration}
|
||||
</a>
|
||||
<a
|
||||
href="/git"
|
||||
class="text-gray-600 hover:text-blue-600 font-medium {$page.url.pathname.startsWith('/git') ? 'text-blue-600 border-b-2 border-blue-600' : ''}"
|
||||
>
|
||||
Git
|
||||
{$t.nav.git}
|
||||
</a>
|
||||
<a
|
||||
href="/tasks"
|
||||
class="text-gray-600 hover:text-blue-600 font-medium {$page.url.pathname.startsWith('/tasks') ? 'text-blue-600 border-b-2 border-blue-600' : ''}"
|
||||
>
|
||||
Tasks
|
||||
{$t.nav.tasks}
|
||||
</a>
|
||||
<div class="relative inline-block group">
|
||||
<button class="text-gray-600 hover:text-blue-600 font-medium pb-1 {$page.url.pathname.startsWith('/tools') ? 'text-blue-600 border-b-2 border-blue-600' : ''}">
|
||||
Tools
|
||||
{$t.nav.tools}
|
||||
</button>
|
||||
<div class="absolute hidden group-hover:block bg-white shadow-lg rounded-md mt-1 py-2 w-48 z-10 border border-gray-100 before:absolute before:-top-2 before:left-0 before:right-0 before:h-2 before:content-[''] right-0">
|
||||
<a href="/tools/search" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-600">Dataset Search</a>
|
||||
<a href="/tools/mapper" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-600">Dataset Mapper</a>
|
||||
<a href="/tools/debug" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-600">System Debug</a>
|
||||
<a href="/tools/search" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-600">{$t.nav.tools_search}</a>
|
||||
<a href="/tools/mapper" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-600">{$t.nav.tools_mapper}</a>
|
||||
<a href="/tools/debug" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-600">{$t.nav.tools_debug}</a>
|
||||
<a href="/tools/storage" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-600">{$t.nav.tools_storage}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative inline-block group">
|
||||
<button class="text-gray-600 hover:text-blue-600 font-medium pb-1 {$page.url.pathname.startsWith('/settings') ? 'text-blue-600 border-b-2 border-blue-600' : ''}">
|
||||
Settings
|
||||
{$t.nav.settings}
|
||||
</button>
|
||||
<div class="absolute hidden group-hover:block bg-white shadow-lg rounded-md mt-1 py-2 w-48 z-10 border border-gray-100 before:absolute before:-top-2 before:left-0 before:right-0 before:h-2 before:content-[''] right-0">
|
||||
<a href="/settings" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-600">General Settings</a>
|
||||
<a href="/settings/connections" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-600">Connections</a>
|
||||
<a href="/settings/git" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-600">Git Integration</a>
|
||||
<a href="/settings/environments" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-600">Environments</a>
|
||||
<a href="/settings" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-600">{$t.nav.settings_general}</a>
|
||||
<a href="/settings/connections" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-600">{$t.nav.settings_connections}</a>
|
||||
<a href="/settings/git" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-600">{$t.nav.settings_git}</a>
|
||||
<a href="/settings/environments" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-600">{$t.nav.settings_environments}</a>
|
||||
</div>
|
||||
</div>
|
||||
<LanguageSwitcher />
|
||||
</nav>
|
||||
</header>
|
||||
<!-- [/DEF:Navbar:Component] -->
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { t } from '../lib/i18n';
|
||||
|
||||
export let tasks: Array<any> = [];
|
||||
export let loading: boolean = false;
|
||||
@@ -58,9 +59,9 @@
|
||||
|
||||
<div class="bg-white shadow overflow-hidden sm:rounded-md">
|
||||
{#if loading && tasks.length === 0}
|
||||
<div class="p-4 text-center text-gray-500">Loading tasks...</div>
|
||||
<div class="p-4 text-center text-gray-500">{$t.tasks?.loading || 'Loading...'}</div>
|
||||
{:else if tasks.length === 0}
|
||||
<div class="p-4 text-center text-gray-500">No tasks found.</div>
|
||||
<div class="p-4 text-center text-gray-500">{$t.tasks?.no_tasks || 'No tasks found.'}</div>
|
||||
{:else}
|
||||
<ul class="divide-y divide-gray-200">
|
||||
{#each tasks as task (task.id)}
|
||||
@@ -94,7 +95,7 @@
|
||||
<path fill-rule="evenodd" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
<p>
|
||||
Started {formatTime(task.started_at)}
|
||||
{($t.tasks?.started || "").replace('{time}', formatTime(task.started_at))}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
<script>
|
||||
import { createEventDispatcher, onMount, onDestroy } from 'svelte';
|
||||
import { getTaskLogs } from '../services/taskService.js';
|
||||
import { t } from '../lib/i18n';
|
||||
import { Button } from '../lib/ui';
|
||||
|
||||
export let show = false;
|
||||
export let inline = false;
|
||||
@@ -143,20 +145,20 @@
|
||||
<div class="flex flex-col h-full w-full p-4">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-medium text-gray-900">
|
||||
Task Logs <span class="text-sm text-gray-500 font-normal">({taskId})</span>
|
||||
{$t.tasks?.logs_title} <span class="text-sm text-gray-500 font-normal">({taskId})</span>
|
||||
</h3>
|
||||
<button on:click={fetchLogs} class="text-sm text-indigo-600 hover:text-indigo-900">Refresh</button>
|
||||
<Button variant="ghost" size="sm" on:click={fetchLogs} class="text-blue-600">{$t.tasks?.refresh}</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 border rounded-md bg-gray-50 p-4 overflow-y-auto font-mono text-sm"
|
||||
<div class="flex-1 border rounded-md bg-gray-50 p-4 overflow-y-auto font-mono text-sm"
|
||||
bind:this={logContainer}
|
||||
on:scroll={handleScroll}>
|
||||
{#if loading && logs.length === 0}
|
||||
<p class="text-gray-500 text-center">Loading logs...</p>
|
||||
<p class="text-gray-500 text-center">{$t.tasks?.loading}</p>
|
||||
{:else if error}
|
||||
<p class="text-red-500 text-center">{error}</p>
|
||||
{:else if logs.length === 0}
|
||||
<p class="text-gray-500 text-center">No logs available.</p>
|
||||
<p class="text-gray-500 text-center">{$t.tasks?.no_logs}</p>
|
||||
{:else}
|
||||
{#each logs as log}
|
||||
<div class="mb-1 hover:bg-gray-100 p-1 rounded">
|
||||
@@ -192,19 +194,19 @@
|
||||
<div class="sm:flex sm:items-start">
|
||||
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900 flex justify-between items-center" id="modal-title">
|
||||
<span>Task Logs <span class="text-sm text-gray-500 font-normal">({taskId})</span></span>
|
||||
<button on:click={fetchLogs} class="text-sm text-indigo-600 hover:text-indigo-900">Refresh</button>
|
||||
<span>{$t.tasks.logs_title} <span class="text-sm text-gray-500 font-normal">({taskId})</span></span>
|
||||
<Button variant="ghost" size="sm" on:click={fetchLogs} class="text-blue-600">{$t.tasks.refresh}</Button>
|
||||
</h3>
|
||||
|
||||
<div class="mt-4 border rounded-md bg-gray-50 p-4 h-96 overflow-y-auto font-mono text-sm"
|
||||
<div class="mt-4 border rounded-md bg-gray-50 p-4 h-96 overflow-y-auto font-mono text-sm"
|
||||
bind:this={logContainer}
|
||||
on:scroll={handleScroll}>
|
||||
{#if loading && logs.length === 0}
|
||||
<p class="text-gray-500 text-center">Loading logs...</p>
|
||||
<p class="text-gray-500 text-center">{$t.tasks.loading}</p>
|
||||
{:else if error}
|
||||
<p class="text-red-500 text-center">{error}</p>
|
||||
{:else if logs.length === 0}
|
||||
<p class="text-gray-500 text-center">No logs available.</p>
|
||||
<p class="text-gray-500 text-center">{$t.tasks.no_logs}</p>
|
||||
{:else}
|
||||
{#each logs as log}
|
||||
<div class="mb-1 hover:bg-gray-100 p-1 rounded">
|
||||
@@ -230,13 +232,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
||||
<button
|
||||
type="button"
|
||||
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
|
||||
on:click={close}
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
<Button variant="secondary" on:click={close}>
|
||||
{$t.common.cancel}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
import { onMount, createEventDispatcher } from 'svelte';
|
||||
import { gitService } from '../../services/gitService';
|
||||
import { addToast as toast } from '../../lib/toasts.js';
|
||||
import { t } from '../../lib/i18n';
|
||||
import { Button, Select, Input } from '../../lib/ui';
|
||||
// [/SECTION]
|
||||
|
||||
// [SECTION: PROPS]
|
||||
@@ -31,6 +33,11 @@
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
// [DEF:onMount:Function]
|
||||
/**
|
||||
* @purpose Load branches when component is mounted.
|
||||
* @pre Component is initialized.
|
||||
* @post loadBranches is called.
|
||||
*/
|
||||
onMount(async () => {
|
||||
await loadBranches();
|
||||
});
|
||||
@@ -39,6 +46,7 @@
|
||||
// [DEF:loadBranches:Function]
|
||||
/**
|
||||
* @purpose Загружает список веток для дашборда.
|
||||
* @pre dashboardId is provided.
|
||||
* @post branches обновлен.
|
||||
*/
|
||||
async function loadBranches() {
|
||||
@@ -57,6 +65,11 @@
|
||||
// [/DEF:loadBranches:Function]
|
||||
|
||||
// [DEF:handleSelect:Function]
|
||||
/**
|
||||
* @purpose Handles branch selection from dropdown.
|
||||
* @pre event contains branch name.
|
||||
* @post handleCheckout is called with selected branch.
|
||||
*/
|
||||
function handleSelect(event) {
|
||||
handleCheckout(event.target.value);
|
||||
}
|
||||
@@ -86,7 +99,8 @@
|
||||
// [DEF:handleCreate:Function]
|
||||
/**
|
||||
* @purpose Создает новую ветку.
|
||||
* @post Новая ветка создана и загружена.
|
||||
* @pre newBranchName is not empty.
|
||||
* @post Новая ветка создана и загружена; showCreate reset.
|
||||
*/
|
||||
async function handleCreate() {
|
||||
if (!newBranchName) return;
|
||||
@@ -107,61 +121,55 @@
|
||||
</script>
|
||||
|
||||
<!-- [SECTION: TEMPLATE] -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="relative">
|
||||
<select
|
||||
value={currentBranch}
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex-grow">
|
||||
<Select
|
||||
bind:value={currentBranch}
|
||||
on:change={handleSelect}
|
||||
disabled={loading}
|
||||
class="bg-white border rounded px-3 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
{#each branches as branch}
|
||||
<option value={branch.name}>{branch.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#if loading}
|
||||
<span class="absolute -right-6 top-1">
|
||||
<svg class="animate-spin h-4 w-4 text-blue-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
</span>
|
||||
{/if}
|
||||
options={branches.map(b => ({ value: b.name, label: b.name }))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
on:click={() => showCreate = !showCreate}
|
||||
disabled={loading}
|
||||
class="text-blue-600 hover:text-blue-800 text-sm font-medium disabled:opacity-50"
|
||||
class="text-blue-600"
|
||||
>
|
||||
+ New Branch
|
||||
</button>
|
||||
+ {$t.git.new_branch}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{#if showCreate}
|
||||
<div class="flex items-center space-x-1 bg-gray-50 p-2 rounded border border-dashed">
|
||||
<input
|
||||
type="text"
|
||||
bind:value={newBranchName}
|
||||
placeholder="branch-name"
|
||||
disabled={loading}
|
||||
class="border rounded px-2 py-1 text-sm w-full max-w-[150px]"
|
||||
/>
|
||||
<button
|
||||
<div class="flex items-end gap-2 bg-gray-50 p-3 rounded-lg border border-dashed border-gray-200">
|
||||
<div class="flex-grow">
|
||||
<Input
|
||||
bind:value={newBranchName}
|
||||
placeholder="branch-name"
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
on:click={handleCreate}
|
||||
disabled={loading || !newBranchName}
|
||||
class="bg-green-600 text-white px-3 py-1 rounded text-xs font-medium hover:bg-green-700 disabled:opacity-50"
|
||||
isLoading={loading}
|
||||
class="bg-green-600 hover:bg-green-700"
|
||||
>
|
||||
{loading ? '...' : 'Create'}
|
||||
</button>
|
||||
<button
|
||||
{$t.git.create}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
on:click={() => showCreate = false}
|
||||
disabled={loading}
|
||||
class="text-gray-500 hover:text-gray-700 text-xs px-2 py-1 disabled:opacity-50"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
{$t.common.cancel}
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { gitService } from '../../services/gitService';
|
||||
import { addToast as toast } from '../../lib/toasts.js';
|
||||
import { t } from '../../lib/i18n';
|
||||
import { Button } from '../../lib/ui';
|
||||
// [/SECTION]
|
||||
|
||||
// [SECTION: PROPS]
|
||||
@@ -25,6 +27,8 @@
|
||||
// [DEF:onMount:Function]
|
||||
/**
|
||||
* @purpose Load history when component is mounted.
|
||||
* @pre Component is initialized with dashboardId.
|
||||
* @post loadHistory is called.
|
||||
*/
|
||||
onMount(async () => {
|
||||
await loadHistory();
|
||||
@@ -34,6 +38,7 @@
|
||||
// [DEF:loadHistory:Function]
|
||||
/**
|
||||
* @purpose Fetch commit history from the backend.
|
||||
* @pre dashboardId is valid.
|
||||
* @post history state is updated.
|
||||
*/
|
||||
async function loadHistory() {
|
||||
@@ -53,22 +58,22 @@
|
||||
</script>
|
||||
|
||||
<!-- [SECTION: TEMPLATE] -->
|
||||
<div class="mt-6">
|
||||
<h3 class="text-lg font-semibold mb-4 flex justify-between items-center">
|
||||
Commit History
|
||||
<button on:click={loadHistory} class="text-sm text-blue-600 hover:underline">Refresh</button>
|
||||
</h3>
|
||||
<div class="mt-2">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider">
|
||||
{$t.git.history}
|
||||
</h3>
|
||||
<Button variant="ghost" size="sm" on:click={loadHistory} class="text-blue-600">
|
||||
{$t.git.refresh}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{#if loading}
|
||||
<div class="flex items-center space-x-2 text-gray-500">
|
||||
<svg class="animate-spin h-4 w-4 text-blue-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<span>Loading history...</span>
|
||||
<div class="flex items-center justify-center py-12">
|
||||
<div class="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600"></div>
|
||||
</div>
|
||||
{:else if history.length === 0}
|
||||
<p class="text-gray-500 italic">No commits yet.</p>
|
||||
<p class="text-gray-500 italic text-center py-12">{$t.git.no_commits}</p>
|
||||
{:else}
|
||||
<div class="space-y-3 max-h-96 overflow-y-auto pr-2">
|
||||
{#each history as commit}
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
|
||||
// [DEF:loadStatus:Watcher]
|
||||
$: if (show) loadEnvironments();
|
||||
// [/DEF:loadStatus:Watcher]
|
||||
|
||||
// [DEF:loadEnvironments:Function]
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { gitService } from '../../services/gitService';
|
||||
import { addToast as toast } from '../../lib/toasts.js';
|
||||
import { t } from '../../lib/i18n';
|
||||
import { Button, Card, PageHeader, Select, Input } from '../../lib/ui';
|
||||
import BranchSelector from './BranchSelector.svelte';
|
||||
import CommitModal from './CommitModal.svelte';
|
||||
import CommitHistory from './CommitHistory.svelte';
|
||||
@@ -49,6 +51,8 @@
|
||||
// [DEF:checkStatus:Function]
|
||||
/**
|
||||
* @purpose Проверяет, инициализирован ли репозиторий для данного дашборда.
|
||||
* @pre Component is mounted and has dashboardId.
|
||||
* @post initialized state is set; configs loaded if not initialized.
|
||||
*/
|
||||
async function checkStatus() {
|
||||
checkingStatus = true;
|
||||
@@ -70,6 +74,8 @@
|
||||
// [DEF:handleInit:Function]
|
||||
/**
|
||||
* @purpose Инициализирует репозиторий для дашборда.
|
||||
* @pre selectedConfigId and remoteUrl are provided.
|
||||
* @post Repository is created on backend; initialized set to true.
|
||||
*/
|
||||
async function handleInit() {
|
||||
if (!selectedConfigId || !remoteUrl) {
|
||||
@@ -92,6 +98,8 @@
|
||||
// [DEF:handleSync:Function]
|
||||
/**
|
||||
* @purpose Синхронизирует состояние Superset с локальным Git-репозиторием.
|
||||
* @pre Repository is initialized.
|
||||
* @post Dashboard YAMLs are exported to Git and staged.
|
||||
*/
|
||||
async function handleSync() {
|
||||
loading = true;
|
||||
@@ -109,6 +117,11 @@
|
||||
// [/DEF:handleSync:Function]
|
||||
|
||||
// [DEF:handlePush:Function]
|
||||
/**
|
||||
* @purpose Pushes local commits to the remote repository.
|
||||
* @pre Repository is initialized and has commits.
|
||||
* @post Changes are pushed to origin.
|
||||
*/
|
||||
async function handlePush() {
|
||||
loading = true;
|
||||
try {
|
||||
@@ -123,6 +136,11 @@
|
||||
// [/DEF:handlePush:Function]
|
||||
|
||||
// [DEF:handlePull:Function]
|
||||
/**
|
||||
* @purpose Pulls changes from the remote repository.
|
||||
* @pre Repository is initialized.
|
||||
* @post Local branch is updated with remote changes.
|
||||
*/
|
||||
async function handlePull() {
|
||||
loading = true;
|
||||
try {
|
||||
@@ -143,17 +161,16 @@
|
||||
{#if show}
|
||||
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div class="bg-white p-6 rounded-lg shadow-2xl w-full max-w-4xl max-h-[90vh] overflow-y-auto">
|
||||
<div class="flex justify-between items-center mb-6 border-b pb-4">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold">Git Management: {dashboardTitle}</h2>
|
||||
<p class="text-sm text-gray-500">ID: {dashboardId}</p>
|
||||
<PageHeader title="{$t.git.management}: {dashboardTitle}">
|
||||
<div slot="subtitle" class="text-sm text-gray-500">ID: {dashboardId}</div>
|
||||
<div slot="actions">
|
||||
<button on:click={() => show = false} class="text-gray-400 hover:text-gray-600 transition-colors">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<button on:click={() => show = false} class="text-gray-500 hover:text-gray-700">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
{#if checkingStatus}
|
||||
<div class="flex justify-center py-12">
|
||||
@@ -161,95 +178,94 @@
|
||||
</div>
|
||||
{:else if !initialized}
|
||||
<div class="max-w-md mx-auto py-8">
|
||||
<div class="bg-blue-50 border-l-4 border-blue-400 p-4 mb-6">
|
||||
<p class="text-sm text-blue-700">
|
||||
This dashboard is not yet linked to a Git repository.
|
||||
Please configure the repository details below.
|
||||
<Card>
|
||||
<p class="text-sm text-gray-600 mb-6">
|
||||
{$t.git.not_linked}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Git Server</label>
|
||||
<select bind:value={selectedConfigId} class="mt-1 block w-full border rounded p-2">
|
||||
{#each configs as config}
|
||||
<option value={config.id}>{config.name} ({config.provider})</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#if configs.length === 0}
|
||||
<p class="text-xs text-red-500 mt-1">No Git servers configured. Go to Settings -> Git to add one.</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Remote Repository URL</label>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={remoteUrl}
|
||||
placeholder="https://github.com/org/repo.git"
|
||||
class="mt-1 block w-full border rounded p-2"
|
||||
|
||||
<div class="space-y-6">
|
||||
<Select
|
||||
label={$t.git.server}
|
||||
bind:value={selectedConfigId}
|
||||
options={configs.map(c => ({ value: c.id, label: `${c.name} (${c.provider})` }))}
|
||||
/>
|
||||
{#if configs.length === 0}
|
||||
<p class="text-xs text-red-500 -mt-4">No Git servers configured. Go to Settings -> Git to add one.</p>
|
||||
{/if}
|
||||
|
||||
<Input
|
||||
label={$t.git.remote_url}
|
||||
bind:value={remoteUrl}
|
||||
placeholder="https://github.com/org/repo.git"
|
||||
/>
|
||||
|
||||
<Button
|
||||
on:click={handleInit}
|
||||
disabled={loading || configs.length === 0}
|
||||
isLoading={loading}
|
||||
class="w-full"
|
||||
>
|
||||
{$t.git.init_repo}
|
||||
</Button>
|
||||
</div>
|
||||
<button
|
||||
on:click={handleInit}
|
||||
disabled={loading || configs.length === 0}
|
||||
class="w-full bg-blue-600 text-white py-2 rounded font-medium hover:bg-blue-700 disabled:opacity-50"
|
||||
>
|
||||
{loading ? 'Initializing...' : 'Initialize Repository'}
|
||||
</button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<!-- Left Column: Controls -->
|
||||
<div class="md:col-span-1 space-y-6">
|
||||
<section>
|
||||
<h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-2">Branch</h3>
|
||||
<h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-3">{$t.git.branch}</h3>
|
||||
<BranchSelector {dashboardId} bind:currentBranch />
|
||||
</section>
|
||||
|
||||
<section class="space-y-2">
|
||||
<h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-2">Actions</h3>
|
||||
<button
|
||||
<section class="space-y-3">
|
||||
<h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-3">{$t.git.actions}</h3>
|
||||
<Button
|
||||
variant="secondary"
|
||||
on:click={handleSync}
|
||||
disabled={loading}
|
||||
class="w-full flex items-center justify-center px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded text-sm font-medium transition"
|
||||
class="w-full"
|
||||
>
|
||||
Sync from Superset
|
||||
</button>
|
||||
<button
|
||||
{$t.git.sync}
|
||||
</Button>
|
||||
<Button
|
||||
on:click={() => showCommitModal = true}
|
||||
disabled={loading}
|
||||
class="w-full flex items-center justify-center px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded text-sm font-medium transition"
|
||||
class="w-full"
|
||||
>
|
||||
Commit Changes
|
||||
</button>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<button
|
||||
{$t.git.commit}
|
||||
</Button>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<Button
|
||||
variant="ghost"
|
||||
on:click={handlePull}
|
||||
disabled={loading}
|
||||
class="flex items-center justify-center px-4 py-2 border hover:bg-gray-50 rounded text-sm font-medium transition"
|
||||
class="border border-gray-200"
|
||||
>
|
||||
Pull
|
||||
</button>
|
||||
<button
|
||||
{$t.git.pull}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
on:click={handlePush}
|
||||
disabled={loading}
|
||||
class="flex items-center justify-center px-4 py-2 border hover:bg-gray-50 rounded text-sm font-medium transition"
|
||||
class="border border-gray-200"
|
||||
>
|
||||
Push
|
||||
</button>
|
||||
{$t.git.push}
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-2">Deployment</h3>
|
||||
<button
|
||||
<h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-3">{$t.git.deployment}</h3>
|
||||
<Button
|
||||
variant="primary"
|
||||
on:click={() => showDeployModal = true}
|
||||
disabled={loading}
|
||||
class="w-full flex items-center justify-center px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded text-sm font-medium transition"
|
||||
class="w-full bg-green-600 hover:bg-green-700 focus-visible:ring-green-500"
|
||||
>
|
||||
Deploy to Environment
|
||||
</button>
|
||||
{$t.git.deploy}
|
||||
</Button>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
|
||||
134
frontend/src/components/storage/FileList.svelte
Normal file
134
frontend/src/components/storage/FileList.svelte
Normal file
@@ -0,0 +1,134 @@
|
||||
<!-- [DEF:FileList:Component] -->
|
||||
<!--
|
||||
@SEMANTICS: storage, files, list, table
|
||||
@PURPOSE: Displays a table of files with metadata and actions.
|
||||
@LAYER: Component
|
||||
@RELATION: DEPENDS_ON -> storageService
|
||||
|
||||
@PROPS: files (Array) - List of StoredFile objects.
|
||||
@EVENTS: delete (filename) - Dispatched when a file is deleted.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
// [SECTION: IMPORTS]
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { downloadFileUrl } from '../../services/storageService';
|
||||
import { t } from '../../lib/i18n';
|
||||
// [/SECTION: IMPORTS]
|
||||
|
||||
export let files = [];
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
// [DEF:isDirectory:Function]
|
||||
/**
|
||||
* @purpose Checks if a file object represents a directory.
|
||||
* @param {Object} file - The file object to check.
|
||||
* @return {boolean} True if it's a directory, false otherwise.
|
||||
*/
|
||||
function isDirectory(file) {
|
||||
return file.mime_type === 'directory';
|
||||
}
|
||||
// [/DEF:isDirectory:Function]
|
||||
|
||||
// [DEF:formatSize:Function]
|
||||
/**
|
||||
* @purpose Formats file size in bytes into a human-readable string.
|
||||
* @param {number} bytes - The size in bytes.
|
||||
* @return {string} Formatted size (e.g., "1.2 MB").
|
||||
*/
|
||||
function formatSize(bytes) {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
// [/DEF:formatSize:Function]
|
||||
|
||||
// [DEF:formatDate:Function]
|
||||
/**
|
||||
* @purpose Formats an ISO date string into a localized readable format.
|
||||
* @param {string} dateStr - The date string to format.
|
||||
* @return {string} Localized date and time.
|
||||
*/
|
||||
function formatDate(dateStr) {
|
||||
return new Date(dateStr).toLocaleString();
|
||||
}
|
||||
// [/DEF:formatDate:Function]
|
||||
</script>
|
||||
|
||||
<!-- [SECTION: TEMPLATE] -->
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full bg-white border border-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{$t.storage.table.name}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{$t.storage.table.category}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{$t.storage.table.size}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{$t.storage.table.created_at}</th>
|
||||
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">{$t.storage.table.actions}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
{#each files as file}
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
||||
{#if isDirectory(file)}
|
||||
<button
|
||||
on:click={() => dispatch('navigate', file.path)}
|
||||
class="flex items-center text-indigo-600 hover:text-indigo-900"
|
||||
>
|
||||
<svg class="h-5 w-5 mr-2 text-yellow-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" />
|
||||
</svg>
|
||||
{file.name}
|
||||
</button>
|
||||
{:else}
|
||||
<div class="flex items-center">
|
||||
<svg class="h-5 w-5 mr-2 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
||||
</svg>
|
||||
{file.name}
|
||||
</div>
|
||||
{/if}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 capitalize">{file.category}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
{isDirectory(file) ? '--' : formatSize(file.size)}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{formatDate(file.created_at)}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
{#if !isDirectory(file)}
|
||||
<a
|
||||
href={downloadFileUrl(file.category, file.path)}
|
||||
download={file.name}
|
||||
class="text-indigo-600 hover:text-indigo-900 mr-4"
|
||||
>
|
||||
{$t.storage.table.download}
|
||||
</a>
|
||||
{/if}
|
||||
<button
|
||||
on:click={() => dispatch('delete', { category: file.category, path: file.path, name: file.name })}
|
||||
class="text-red-600 hover:text-red-900"
|
||||
>
|
||||
{$t.storage.table.delete}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{:else}
|
||||
<tr>
|
||||
<td colspan="5" class="px-6 py-10 text-center text-sm text-gray-500">
|
||||
{$t.storage.no_files}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- [/SECTION: TEMPLATE] -->
|
||||
|
||||
<style>
|
||||
/* ... */
|
||||
</style>
|
||||
|
||||
<!-- [/DEF:FileList:Component] -->
|
||||
134
frontend/src/components/storage/FileUpload.svelte
Normal file
134
frontend/src/components/storage/FileUpload.svelte
Normal file
@@ -0,0 +1,134 @@
|
||||
<!-- [DEF:FileUpload:Component] -->
|
||||
<!--
|
||||
@SEMANTICS: storage, upload, files
|
||||
@PURPOSE: Provides a form for uploading files to a specific category.
|
||||
@LAYER: Component
|
||||
@RELATION: DEPENDS_ON -> storageService
|
||||
|
||||
@PROPS: None
|
||||
@EVENTS: uploaded - Dispatched when a file is successfully uploaded.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
// [SECTION: IMPORTS]
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { uploadFile } from '../../services/storageService';
|
||||
import { addToast } from '../../lib/toasts';
|
||||
import { t } from '../../lib/i18n';
|
||||
// [/SECTION: IMPORTS]
|
||||
|
||||
// [DEF:handleUpload:Function]
|
||||
/**
|
||||
* @purpose Handles the file upload process.
|
||||
* @pre A file must be selected in the file input.
|
||||
* @post The file is uploaded to the server and a success toast is shown.
|
||||
*/
|
||||
const dispatch = createEventDispatcher();
|
||||
let fileInput;
|
||||
export let category = 'backups';
|
||||
export let path = '';
|
||||
let isUploading = false;
|
||||
let dragOver = false;
|
||||
|
||||
async function handleUpload() {
|
||||
const file = fileInput.files[0];
|
||||
if (!file) return;
|
||||
|
||||
isUploading = true;
|
||||
try {
|
||||
// path is relative to root, but upload endpoint expects path within category
|
||||
// FileList.path is like "backup/folder", we need just "folder"
|
||||
const subpath = path.startsWith(category)
|
||||
? path.substring(category.length).replace(/^\/+/, '')
|
||||
: path;
|
||||
|
||||
await uploadFile(file, category, subpath);
|
||||
addToast($t.storage.messages.upload_success.replace('{name}', file.name), 'success');
|
||||
fileInput.value = '';
|
||||
dispatch('uploaded');
|
||||
} catch (error) {
|
||||
addToast($t.storage.messages.upload_failed.replace('{error}', error.message), 'error');
|
||||
} finally {
|
||||
isUploading = false;
|
||||
}
|
||||
}
|
||||
// [/DEF:handleUpload:Function]
|
||||
|
||||
// [DEF:handleDrop:Function]
|
||||
/**
|
||||
* @purpose Handles the file drop event for drag-and-drop.
|
||||
* @param {DragEvent} event - The drop event.
|
||||
*/
|
||||
function handleDrop(event) {
|
||||
event.preventDefault();
|
||||
dragOver = false;
|
||||
const files = event.dataTransfer.files;
|
||||
if (files.length > 0) {
|
||||
fileInput.files = files;
|
||||
handleUpload();
|
||||
}
|
||||
}
|
||||
// [/DEF:handleDrop:Function]
|
||||
</script>
|
||||
|
||||
<!-- [SECTION: TEMPLATE] -->
|
||||
<div class="bg-white p-6 rounded-lg border border-gray-200 shadow-sm">
|
||||
<h2 class="text-lg font-semibold mb-4">{$t.storage.upload_title}</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">{$t.storage.target_category}</label>
|
||||
<select
|
||||
bind:value={category}
|
||||
class="block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
||||
>
|
||||
<option value="backups">{$t.storage.backups}</option>
|
||||
<option value="repositorys">{$t.storage.repositories}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-dashed rounded-md transition-colors
|
||||
{dragOver ? 'border-indigo-500 bg-indigo-50' : 'border-gray-300'}"
|
||||
on:dragover|preventDefault={() => dragOver = true}
|
||||
on:dragleave|preventDefault={() => dragOver = false}
|
||||
on:drop|preventDefault={handleDrop}
|
||||
>
|
||||
<div class="space-y-1 text-center">
|
||||
<svg class="mx-auto h-12 w-12 text-gray-400" stroke="currentColor" fill="none" viewBox="0 0 48 48" aria-hidden="true">
|
||||
<path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
<div class="flex text-sm text-gray-600">
|
||||
<label for="file-upload" class="relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500">
|
||||
<span>{$t.storage.upload_button}</span>
|
||||
<input
|
||||
id="file-upload"
|
||||
name="file-upload"
|
||||
type="file"
|
||||
class="sr-only"
|
||||
bind:this={fileInput}
|
||||
on:change={handleUpload}
|
||||
disabled={isUploading}
|
||||
>
|
||||
</label>
|
||||
<p class="pl-1">{$t.storage.drag_drop}</p>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500">{$t.storage.supported_formats}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if isUploading}
|
||||
<div class="flex items-center justify-center space-x-2 text-indigo-600">
|
||||
<div class="animate-spin rounded-full h-4 w-4 border-b-2 border-indigo-600"></div>
|
||||
<span class="text-sm font-medium">{$t.storage.uploading}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<!-- [/SECTION: TEMPLATE] -->
|
||||
|
||||
<style>
|
||||
/* ... */
|
||||
</style>
|
||||
|
||||
<!-- [/DEF:FileUpload:Component] -->
|
||||
@@ -10,6 +10,8 @@
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { createConnection } from '../../services/connectionService.js';
|
||||
import { addToast } from '../../lib/toasts.js';
|
||||
import { t } from '../../lib/i18n';
|
||||
import { Button, Input, Card } from '../../lib/ui';
|
||||
// [/SECTION]
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
@@ -17,7 +19,7 @@
|
||||
let name = '';
|
||||
let type = 'postgres';
|
||||
let host = '';
|
||||
let port = 5432;
|
||||
let port = "5432";
|
||||
let database = '';
|
||||
let username = '';
|
||||
let password = '';
|
||||
@@ -36,7 +38,7 @@
|
||||
isSubmitting = true;
|
||||
try {
|
||||
const newConnection = await createConnection({
|
||||
name, type, host, port, database, username, password
|
||||
name, type, host, port: Number(port), database, username, password
|
||||
});
|
||||
addToast('Connection created successfully', 'success');
|
||||
dispatch('success', newConnection);
|
||||
@@ -57,7 +59,7 @@
|
||||
function resetForm() {
|
||||
name = '';
|
||||
host = '';
|
||||
port = 5432;
|
||||
port = "5432";
|
||||
database = '';
|
||||
username = '';
|
||||
password = '';
|
||||
@@ -66,43 +68,28 @@
|
||||
</script>
|
||||
|
||||
<!-- [SECTION: TEMPLATE] -->
|
||||
<div class="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-4">Add New Connection</h3>
|
||||
<form on:submit|preventDefault={handleSubmit} class="space-y-4">
|
||||
<div>
|
||||
<label for="conn-name" class="block text-sm font-medium text-gray-700">Connection Name</label>
|
||||
<input type="text" id="conn-name" bind:value={name} placeholder="e.g. Production DWH" class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" />
|
||||
<Card title={$t.connections?.add_new || "Add New Connection"}>
|
||||
<form on:submit|preventDefault={handleSubmit} class="space-y-6">
|
||||
<Input label={$t.connections?.name || "Connection Name"} bind:value={name} placeholder="e.g. Production DWH" />
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<Input label={$t.connections?.host || "Host"} bind:value={host} placeholder="10.0.0.1" />
|
||||
<Input label={$t.connections?.port || "Port"} type="number" bind:value={port} />
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label for="conn-host" class="block text-sm font-medium text-gray-700">Host</label>
|
||||
<input type="text" id="conn-host" bind:value={host} placeholder="10.0.0.1" class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="conn-port" class="block text-sm font-medium text-gray-700">Port</label>
|
||||
<input type="number" id="conn-port" bind:value={port} class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="conn-db" class="block text-sm font-medium text-gray-700">Database Name</label>
|
||||
<input type="text" id="conn-db" bind:value={database} class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" />
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label for="conn-user" class="block text-sm font-medium text-gray-700">Username</label>
|
||||
<input type="text" id="conn-user" bind:value={username} class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="conn-pass" class="block text-sm font-medium text-gray-700">Password</label>
|
||||
<input type="password" id="conn-pass" bind:value={password} class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" />
|
||||
</div>
|
||||
|
||||
<Input label={$t.connections?.db_name || "Database Name"} bind:value={database} />
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<Input label={$t.connections?.user || "Username"} bind:value={username} />
|
||||
<Input label={$t.connections?.pass || "Password"} type="password" bind:value={password} />
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end pt-2">
|
||||
<button type="submit" disabled={isSubmitting} class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50">
|
||||
{isSubmitting ? 'Creating...' : 'Create Connection'}
|
||||
</button>
|
||||
<Button type="submit" disabled={isSubmitting} isLoading={isSubmitting}>
|
||||
{$t.connections?.create || "Create Connection"}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Card>
|
||||
<!-- [/SECTION] -->
|
||||
<!-- [/DEF:ConnectionForm:Component] -->
|
||||
@@ -10,6 +10,8 @@
|
||||
import { onMount, createEventDispatcher } from 'svelte';
|
||||
import { getConnections, deleteConnection } from '../../services/connectionService.js';
|
||||
import { addToast } from '../../lib/toasts.js';
|
||||
import { t } from '../../lib/i18n';
|
||||
import { Button, Card } from '../../lib/ui';
|
||||
// [/SECTION]
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
@@ -57,32 +59,30 @@
|
||||
</script>
|
||||
|
||||
<!-- [SECTION: TEMPLATE] -->
|
||||
<div class="bg-white shadow overflow-hidden sm:rounded-md border border-gray-200">
|
||||
<div class="px-4 py-5 sm:px-6 bg-gray-50 border-b border-gray-200">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">Saved Connections</h3>
|
||||
</div>
|
||||
<ul class="divide-y divide-gray-200">
|
||||
<Card title={$t.connections?.saved || "Saved Connections"} padding="none">
|
||||
<ul class="divide-y divide-gray-100">
|
||||
{#if isLoading}
|
||||
<li class="p-4 text-center text-gray-500">Loading...</li>
|
||||
<li class="p-6 text-center text-gray-500">{$t.common.loading}</li>
|
||||
{:else if connections.length === 0}
|
||||
<li class="p-8 text-center text-gray-500 italic">No connections saved yet.</li>
|
||||
<li class="p-12 text-center text-gray-500 italic">{$t.connections?.no_saved || "No connections saved yet."}</li>
|
||||
{:else}
|
||||
{#each connections as conn}
|
||||
<li class="p-4 flex items-center justify-between hover:bg-gray-50">
|
||||
<li class="p-6 flex items-center justify-between hover:bg-gray-50 transition-colors">
|
||||
<div>
|
||||
<div class="text-sm font-medium text-indigo-600 truncate">{conn.name}</div>
|
||||
<div class="text-xs text-gray-500">{conn.type}://{conn.username}@{conn.host}:{conn.port}/{conn.database}</div>
|
||||
<div class="text-sm font-medium text-blue-600 truncate">{conn.name}</div>
|
||||
<div class="text-xs text-gray-400 mt-1 font-mono">{conn.type}://{conn.username}@{conn.host}:{conn.port}/{conn.database}</div>
|
||||
</div>
|
||||
<button
|
||||
<Button
|
||||
variant="danger"
|
||||
size="sm"
|
||||
on:click={() => handleDelete(conn.id)}
|
||||
class="ml-2 inline-flex items-center px-2 py-1 border border-transparent text-xs font-medium rounded text-red-700 bg-red-100 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
{$t.connections?.delete || "Delete"}
|
||||
</Button>
|
||||
</li>
|
||||
{/each}
|
||||
{/if}
|
||||
</ul>
|
||||
</div>
|
||||
</Card>
|
||||
<!-- [/SECTION] -->
|
||||
<!-- [/DEF:ConnectionList:Component] -->
|
||||
@@ -123,6 +123,8 @@ export const api = {
|
||||
deleteEnvironment: (id) => requestApi(`/settings/environments/${id}`, 'DELETE'),
|
||||
testEnvironmentConnection: (id) => postApi(`/settings/environments/${id}/test`, {}),
|
||||
updateEnvironmentSchedule: (id, schedule) => requestApi(`/environments/${id}/schedule`, 'PUT', schedule),
|
||||
getStorageSettings: () => fetchApi('/settings/storage'),
|
||||
updateStorageSettings: (storage) => requestApi('/settings/storage', 'PUT', storage),
|
||||
getEnvironmentsList: () => fetchApi('/environments'),
|
||||
};
|
||||
// [/DEF:api:Data]
|
||||
@@ -143,3 +145,5 @@ export const deleteEnvironment = api.deleteEnvironment;
|
||||
export const testEnvironmentConnection = api.testEnvironmentConnection;
|
||||
export const updateEnvironmentSchedule = api.updateEnvironmentSchedule;
|
||||
export const getEnvironmentsList = api.getEnvironmentsList;
|
||||
export const getStorageSettings = api.getStorageSettings;
|
||||
export const updateStorageSettings = api.updateStorageSettings;
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
let settings = {
|
||||
environments: [],
|
||||
settings: {
|
||||
backup_path: '',
|
||||
default_environment_id: null,
|
||||
logging: {
|
||||
level: 'INFO',
|
||||
@@ -204,12 +203,6 @@
|
||||
|
||||
<section class="mb-8 bg-white p-6 rounded shadow">
|
||||
<h2 class="text-xl font-semibold mb-4">Global Settings</h2>
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<div>
|
||||
<label for="backup_path" class="block text-sm font-medium text-gray-700">Backup Storage Path</label>
|
||||
<input type="text" id="backup_path" bind:value={settings.settings.backup_path} class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="text-lg font-medium mb-4 mt-6">Logging Configuration</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
import { api } from '../lib/api.js';
|
||||
import { get } from 'svelte/store';
|
||||
import { goto } from '$app/navigation';
|
||||
import { t } from '$lib/i18n';
|
||||
import { Button, Card, PageHeader } from '$lib/ui';
|
||||
|
||||
/** @type {import('./$types').PageData} */
|
||||
export let data;
|
||||
@@ -55,34 +57,43 @@
|
||||
<div class="container mx-auto p-4">
|
||||
{#if $selectedTask}
|
||||
<TaskRunner />
|
||||
<button on:click={() => selectedTask.set(null)} class="mt-4 bg-blue-500 text-white p-2 rounded">
|
||||
Back to Task List
|
||||
</button>
|
||||
<div class="mt-4">
|
||||
<Button variant="primary" on:click={() => selectedTask.set(null)}>
|
||||
{$t.common.cancel}
|
||||
</Button>
|
||||
</div>
|
||||
{:else if $selectedPlugin}
|
||||
<h2 class="text-2xl font-bold mb-4">{$selectedPlugin.name}</h2>
|
||||
<DynamicForm schema={$selectedPlugin.schema} on:submit={handleFormSubmit} />
|
||||
<button on:click={() => selectedPlugin.set(null)} class="mt-4 bg-gray-500 text-white p-2 rounded">
|
||||
Back to Dashboard
|
||||
</button>
|
||||
<PageHeader title={$selectedPlugin.name} />
|
||||
<Card>
|
||||
<DynamicForm schema={$selectedPlugin.schema} on:submit={handleFormSubmit} />
|
||||
</Card>
|
||||
<div class="mt-4">
|
||||
<Button variant="secondary" on:click={() => selectedPlugin.set(null)}>
|
||||
{$t.common.cancel}
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<h1 class="text-2xl font-bold mb-4">Available Tools</h1>
|
||||
<PageHeader title={$t.nav.dashboard} />
|
||||
|
||||
{#if data.error}
|
||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
|
||||
{data.error}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{#each data.plugins as plugin}
|
||||
<div
|
||||
class="border rounded-lg p-4 cursor-pointer hover:bg-gray-100"
|
||||
<div
|
||||
on:click={() => selectPlugin(plugin)}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
on:keydown={(e) => e.key === 'Enter' && selectPlugin(plugin)}
|
||||
class="cursor-pointer transition-transform hover:scale-[1.02]"
|
||||
>
|
||||
<h2 class="text-xl font-semibold">{plugin.name}</h2>
|
||||
<p class="text-gray-600">{plugin.description}</p>
|
||||
<span class="text-sm text-gray-400">v{plugin.version}</span>
|
||||
<Card title={plugin.name}>
|
||||
<p class="text-gray-600 mb-4">{plugin.description}</p>
|
||||
<span class="text-xs font-mono text-gray-400 bg-gray-50 px-2 py-1 rounded">v{plugin.version}</span>
|
||||
</Card>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
<!-- [DEF:GitDashboardPage:Component] -->
|
||||
<!--
|
||||
@PURPOSE: Dashboard management page for Git integration.
|
||||
@LAYER: Page
|
||||
@SEMANTICS: git, dashboard, management, ui
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import DashboardGrid from '../../components/DashboardGrid.svelte';
|
||||
import { addToast as toast } from '../../lib/toasts.js';
|
||||
import type { DashboardMetadata } from '../../types/dashboard';
|
||||
import { t } from '$lib/i18n';
|
||||
import { Button, Card, PageHeader, Select } from '$lib/ui';
|
||||
|
||||
let environments: any[] = [];
|
||||
let selectedEnvId = "";
|
||||
@@ -11,6 +18,10 @@
|
||||
let loading = true;
|
||||
let fetchingDashboards = false;
|
||||
|
||||
// [DEF:fetchEnvironments:Function]
|
||||
// @PURPOSE: Fetches the list of deployment environments from the API.
|
||||
// @PRE: Component is mounted.
|
||||
// @POST: `environments` array is populated with data from /api/environments.
|
||||
async function fetchEnvironments() {
|
||||
try {
|
||||
const response = await fetch('/api/environments');
|
||||
@@ -25,7 +36,12 @@
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
// [/DEF:fetchEnvironments:Function]
|
||||
|
||||
// [DEF:fetchDashboards:Function]
|
||||
// @PURPOSE: Fetches dashboards for a specific environment.
|
||||
// @PRE: `envId` is a valid environment ID.
|
||||
// @POST: `dashboards` array is updated with results from the environment.
|
||||
async function fetchDashboards(envId: string) {
|
||||
if (!envId) return;
|
||||
fetchingDashboards = true;
|
||||
@@ -40,6 +56,7 @@
|
||||
fetchingDashboards = false;
|
||||
}
|
||||
}
|
||||
// [/DEF:fetchDashboards:Function]
|
||||
|
||||
onMount(fetchEnvironments);
|
||||
|
||||
@@ -50,29 +67,22 @@
|
||||
</script>
|
||||
|
||||
<div class="max-w-6xl mx-auto p-6">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-800">Git Dashboard Management</h1>
|
||||
<div class="flex items-center space-x-4">
|
||||
<label for="env-select" class="text-sm font-medium text-gray-700">Environment:</label>
|
||||
<select
|
||||
id="env-select"
|
||||
<PageHeader title="Git Dashboard Management">
|
||||
<div slot="actions" class="flex items-center space-x-4">
|
||||
<Select
|
||||
label="Environment"
|
||||
bind:value={selectedEnvId}
|
||||
class="border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 p-2 border bg-white"
|
||||
>
|
||||
{#each environments as env}
|
||||
<option value={env.id}>{env.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
options={environments.map(e => ({ value: e.id, label: e.name }))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
{#if loading}
|
||||
<div class="flex justify-center py-12">
|
||||
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="bg-white rounded-lg shadow p-6">
|
||||
<h2 class="text-lg font-medium mb-4">Select Dashboard to Manage</h2>
|
||||
<Card title="Select Dashboard to Manage">
|
||||
{#if fetchingDashboards}
|
||||
<p class="text-gray-500">Loading dashboards...</p>
|
||||
{:else if dashboards.length > 0}
|
||||
@@ -80,7 +90,7 @@
|
||||
{:else}
|
||||
<p class="text-gray-500 italic">No dashboards found in this environment.</p>
|
||||
{/if}
|
||||
</div>
|
||||
</Card>
|
||||
{/if}
|
||||
</div>
|
||||
<!-- [/DEF:GitDashboardPage:Component] -->
|
||||
@@ -21,6 +21,8 @@
|
||||
import { selectedTask } from '../../lib/stores.js';
|
||||
import { resumeTask } from '../../services/taskService.js';
|
||||
import type { DashboardMetadata, DashboardSelection } from '../../types/dashboard';
|
||||
import { t } from '$lib/i18n';
|
||||
import { Button, Card, PageHeader } from '$lib/ui';
|
||||
// [/SECTION]
|
||||
|
||||
// [SECTION: STATE]
|
||||
@@ -294,19 +296,18 @@
|
||||
|
||||
<!-- [SECTION: TEMPLATE] -->
|
||||
<div class="max-w-4xl mx-auto p-6">
|
||||
<h1 class="text-2xl font-bold mb-6">Migration Dashboard</h1>
|
||||
<PageHeader title={$t.nav.migration} />
|
||||
|
||||
<TaskHistory on:viewLogs={handleViewLogs} />
|
||||
|
||||
{#if $selectedTask}
|
||||
<div class="mt-6">
|
||||
<TaskRunner />
|
||||
<button
|
||||
on:click={() => selectedTask.set(null)}
|
||||
class="mt-4 inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
Back to New Migration
|
||||
</button>
|
||||
<div class="mt-4">
|
||||
<Button variant="secondary" on:click={() => selectedTask.set(null)}>
|
||||
{$t.common.cancel}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
{#if loading}
|
||||
@@ -383,13 +384,12 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<button
|
||||
<Button
|
||||
on:click={startMigration}
|
||||
disabled={!sourceEnvId || !targetEnvId || sourceEnvId === targetEnvId || selectedDashboardIds.length === 0}
|
||||
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:bg-gray-400"
|
||||
>
|
||||
Start Migration
|
||||
</button>
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
import { onMount } from 'svelte';
|
||||
import EnvSelector from '../../../components/EnvSelector.svelte';
|
||||
import MappingTable from '../../../components/MappingTable.svelte';
|
||||
import { t } from '$lib/i18n';
|
||||
import { Button, PageHeader } from '$lib/ui';
|
||||
// [/SECTION]
|
||||
|
||||
// [SECTION: STATE]
|
||||
@@ -128,7 +130,7 @@
|
||||
|
||||
<!-- [SECTION: TEMPLATE] -->
|
||||
<div class="max-w-6xl mx-auto p-6">
|
||||
<h1 class="text-2xl font-bold mb-6">Database Mapping Management</h1>
|
||||
<PageHeader title="Database Mapping Management" />
|
||||
|
||||
{#if loading}
|
||||
<p>Loading environments...</p>
|
||||
@@ -149,13 +151,13 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-8">
|
||||
<button
|
||||
<Button
|
||||
on:click={fetchDatabases}
|
||||
disabled={!sourceEnvId || !targetEnvId || sourceEnvId === targetEnvId || fetchingDbs}
|
||||
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:bg-gray-400"
|
||||
isLoading={fetchingDbs}
|
||||
>
|
||||
{fetchingDbs ? 'Fetching...' : 'Fetch Databases & Suggestions'}
|
||||
</button>
|
||||
Fetch Databases & Suggestions
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { updateGlobalSettings, addEnvironment, updateEnvironment, deleteEnvironment, testEnvironmentConnection } from '../../lib/api';
|
||||
import { updateGlobalSettings, addEnvironment, updateEnvironment, deleteEnvironment, testEnvironmentConnection, updateStorageSettings } from '../../lib/api';
|
||||
import { addToast } from '../../lib/toasts';
|
||||
import { t } from '$lib/i18n';
|
||||
import { Button, Input, Card, PageHeader } from '$lib/ui';
|
||||
|
||||
/** @type {import('./$types').PageData} */
|
||||
export let data;
|
||||
@@ -39,6 +41,24 @@
|
||||
}
|
||||
// [/DEF:handleSaveGlobal:Function]
|
||||
|
||||
// [DEF:handleSaveStorage:Function]
|
||||
/* @PURPOSE: Saves storage-specific settings.
|
||||
@PRE: settings.settings.storage must contain valid configuration.
|
||||
@POST: Storage settings are updated via API.
|
||||
*/
|
||||
async function handleSaveStorage() {
|
||||
try {
|
||||
console.log("[Settings.handleSaveStorage][Action] Saving storage settings.");
|
||||
await updateStorageSettings(settings.settings.storage);
|
||||
addToast('Storage settings saved', 'success');
|
||||
console.log("[Settings.handleSaveStorage][Coherence:OK] Storage settings saved.");
|
||||
} catch (error) {
|
||||
console.error("[Settings.handleSaveStorage][Coherence:Failed] Failed to save storage settings:", error);
|
||||
addToast(error.message || 'Failed to save storage settings', 'error');
|
||||
}
|
||||
}
|
||||
// [/DEF:handleSaveStorage:Function]
|
||||
|
||||
// [DEF:handleAddOrUpdateEnv:Function]
|
||||
/* @PURPOSE: Adds a new environment or updates an existing one.
|
||||
@PRE: newEnv must contain valid environment details.
|
||||
@@ -142,7 +162,7 @@
|
||||
</script>
|
||||
|
||||
<div class="container mx-auto p-4">
|
||||
<h1 class="text-2xl font-bold mb-6">Settings</h1>
|
||||
<PageHeader title={$t.settings.title} />
|
||||
|
||||
{#if data.error}
|
||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
|
||||
@@ -150,38 +170,62 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<section class="mb-8 bg-white p-6 rounded shadow">
|
||||
<h2 class="text-xl font-semibold mb-4">Global Settings</h2>
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<div>
|
||||
<label for="backup_path" class="block text-sm font-medium text-gray-700">Backup Storage Path</label>
|
||||
<input type="text" id="backup_path" bind:value={settings.settings.backup_path} class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" />
|
||||
|
||||
<div class="mb-8">
|
||||
<Card title={$t.settings?.storage_title || "File Storage Configuration"}>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="md:col-span-2">
|
||||
<Input
|
||||
label={$t.settings?.storage_root || "Storage Root Path"}
|
||||
bind:value={settings.settings.storage.root_path}
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
label={$t.settings?.storage_backup_pattern || "Backup Directory Pattern"}
|
||||
bind:value={settings.settings.storage.backup_structure_pattern}
|
||||
/>
|
||||
<Input
|
||||
label={$t.settings?.storage_repo_pattern || "Repository Directory Pattern"}
|
||||
bind:value={settings.settings.storage.repo_structure_pattern}
|
||||
/>
|
||||
<Input
|
||||
label={$t.settings?.storage_filename_pattern || "Filename Pattern"}
|
||||
bind:value={settings.settings.storage.filename_pattern}
|
||||
/>
|
||||
<div class="bg-gray-50 p-4 rounded border border-gray-200">
|
||||
<span class="block text-xs font-semibold text-gray-500 uppercase mb-2">{$t.settings?.storage_preview || "Path Preview"}</span>
|
||||
<code class="text-sm text-indigo-600">
|
||||
{settings.settings.storage.root_path}/backups/sample_backup.zip
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
<button on:click={handleSaveGlobal} class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 w-max">
|
||||
Save Global Settings
|
||||
</button>
|
||||
<div class="mt-6">
|
||||
<Button on:click={handleSaveStorage}>
|
||||
{$t.common.save}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<section class="mb-8">
|
||||
<Card title={$t.settings?.env_title || "Superset Environments"}>
|
||||
|
||||
{#if settings.environments.length === 0}
|
||||
<div class="mb-4 p-4 bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700">
|
||||
<p class="font-bold">Warning</p>
|
||||
<p>{$t.settings?.env_warning || "No Superset environments configured."}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="mb-8 bg-white p-6 rounded shadow">
|
||||
<h2 class="text-xl font-semibold mb-4">Superset Environments</h2>
|
||||
|
||||
{#if settings.environments.length === 0}
|
||||
<div class="mb-4 p-4 bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700">
|
||||
<p class="font-bold">Warning</p>
|
||||
<p>No Superset environments configured. You must add at least one environment to perform backups or migrations.</p>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<div class="mb-6 overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{$t.connections?.name || "Name"}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">URL</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Username</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{$t.connections?.user || "Username"}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Default</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{$t.git?.actions || "Actions"}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
@@ -192,9 +236,9 @@
|
||||
<td class="px-6 py-4 whitespace-nowrap">{env.username}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">{env.is_default ? 'Yes' : 'No'}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<button on:click={() => handleTestEnv(env.id)} class="text-green-600 hover:text-green-900 mr-4">Test</button>
|
||||
<button on:click={() => editEnv(env)} class="text-indigo-600 hover:text-indigo-900 mr-4">Edit</button>
|
||||
<button on:click={() => handleDeleteEnv(env.id)} class="text-red-600 hover:text-red-900">Delete</button>
|
||||
<button on:click={() => handleTestEnv(env.id)} class="text-green-600 hover:text-green-900 mr-4">{$t.settings?.env_test || "Test"}</button>
|
||||
<button on:click={() => editEnv(env)} class="text-indigo-600 hover:text-indigo-900 mr-4">{$t.common.edit}</button>
|
||||
<button on:click={() => handleDeleteEnv(env.id)} class="text-red-600 hover:text-red-900">{$t.settings?.env_delete || "Delete"}</button>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
@@ -202,44 +246,30 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-50 p-4 rounded">
|
||||
<h3 class="text-lg font-medium mb-4">{editingEnvId ? 'Edit' : 'Add'} Environment</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label for="env_id" class="block text-sm font-medium text-gray-700">ID</label>
|
||||
<input type="text" id="env_id" bind:value={newEnv.id} disabled={!!editingEnvId} class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="env_name" class="block text-sm font-medium text-gray-700">Name</label>
|
||||
<input type="text" id="env_name" bind:value={newEnv.name} class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="env_url" class="block text-sm font-medium text-gray-700">URL</label>
|
||||
<input type="text" id="env_url" bind:value={newEnv.url} class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="env_user" class="block text-sm font-medium text-gray-700">Username</label>
|
||||
<input type="text" id="env_user" bind:value={newEnv.username} class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="env_pass" class="block text-sm font-medium text-gray-700">Password</label>
|
||||
<input type="password" id="env_pass" bind:value={newEnv.password} class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" />
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input type="checkbox" id="env_default" bind:checked={newEnv.is_default} class="h-4 w-4 text-blue-600 border-gray-300 rounded" />
|
||||
<label for="env_default" class="ml-2 block text-sm text-gray-900">Default Environment</label>
|
||||
<div class="mt-8 bg-gray-50 p-6 rounded-lg border border-gray-100">
|
||||
<h3 class="text-lg font-medium mb-6">{editingEnvId ? ($t.settings?.env_edit || "Edit Environment") : ($t.settings?.env_add || "Add Environment")}</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<Input label="ID" bind:value={newEnv.id} disabled={!!editingEnvId} />
|
||||
<Input label={$t.connections?.name || "Name"} bind:value={newEnv.name} />
|
||||
<Input label="URL" bind:value={newEnv.url} />
|
||||
<Input label={$t.connections?.user || "Username"} bind:value={newEnv.username} />
|
||||
<Input label={$t.connections?.pass || "Password"} type="password" bind:value={newEnv.password} />
|
||||
<div class="flex items-center gap-2 h-10 mt-auto">
|
||||
<input type="checkbox" id="env_default" bind:checked={newEnv.is_default} class="h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500" />
|
||||
<label for="env_default" class="text-sm font-medium text-gray-700">{$t.settings?.env_default || "Default Environment"}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex gap-2">
|
||||
<button on:click={handleAddOrUpdateEnv} class="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600">
|
||||
{editingEnvId ? 'Update' : 'Add'} Environment
|
||||
</button>
|
||||
<div class="mt-8 flex gap-3">
|
||||
<Button on:click={handleAddOrUpdateEnv}>
|
||||
{editingEnvId ? $t.common.save : ($t.settings?.env_add || "Add Environment")}
|
||||
</Button>
|
||||
{#if editingEnvId}
|
||||
<button on:click={resetEnvForm} class="bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600">
|
||||
Cancel
|
||||
</button>
|
||||
<Button variant="secondary" on:click={resetEnvForm}>
|
||||
{$t.common.cancel}
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,6 @@ export async function load() {
|
||||
settings: {
|
||||
environments: [],
|
||||
settings: {
|
||||
backup_path: '',
|
||||
default_environment_id: null
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,8 +1,24 @@
|
||||
<script>
|
||||
<!-- [DEF:GitSettingsPage:Component] -->
|
||||
<!--
|
||||
@SEMANTICS: git, settings, configuration, integration
|
||||
@PURPOSE: Manage Git server configurations for dashboard versioning.
|
||||
@LAYER: Page
|
||||
@RELATION: USES -> gitService
|
||||
@RELATION: USES -> Button, Input, Card, PageHeader, Select
|
||||
|
||||
@INVARIANT: All configurations must be validated via connection test.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
// [SECTION: IMPORTS]
|
||||
import { onMount } from 'svelte';
|
||||
import { gitService } from '../../../services/gitService';
|
||||
import { addToast as toast } from '../../../lib/toasts.js';
|
||||
import { t } from '$lib/i18n';
|
||||
import { Button, Input, Card, PageHeader, Select } from '$lib/ui';
|
||||
// [/SECTION: IMPORTS]
|
||||
|
||||
// [SECTION: STATE]
|
||||
let configs = [];
|
||||
let newConfig = {
|
||||
name: '',
|
||||
@@ -12,15 +28,31 @@
|
||||
default_repository: ''
|
||||
};
|
||||
let testing = false;
|
||||
// [/SECTION: STATE]
|
||||
|
||||
onMount(async () => {
|
||||
// [DEF:loadConfigs:Function]
|
||||
/**
|
||||
* @purpose Fetches existing git configurations.
|
||||
* @pre Component is mounted.
|
||||
* @post configs state is populated.
|
||||
*/
|
||||
async function loadConfigs() {
|
||||
try {
|
||||
configs = await gitService.getConfigs();
|
||||
} catch (e) {
|
||||
toast(e.message, 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
// [/DEF:loadConfigs:Function]
|
||||
|
||||
onMount(loadConfigs);
|
||||
|
||||
// [DEF:handleTest:Function]
|
||||
/**
|
||||
* @purpose Tests connection to a git server with current form data.
|
||||
* @pre newConfig contains valid provider, url, and pat.
|
||||
* @post testing state is managed; toast shown with result.
|
||||
*/
|
||||
async function handleTest() {
|
||||
testing = true;
|
||||
try {
|
||||
@@ -36,7 +68,14 @@
|
||||
testing = false;
|
||||
}
|
||||
}
|
||||
// [/DEF:handleTest:Function]
|
||||
|
||||
// [DEF:handleSave:Function]
|
||||
/**
|
||||
* @purpose Saves a new git configuration.
|
||||
* @pre newConfig is valid and tested.
|
||||
* @post New config is saved to DB and added to configs list.
|
||||
*/
|
||||
async function handleSave() {
|
||||
try {
|
||||
const saved = await gitService.createConfig(newConfig);
|
||||
@@ -47,7 +86,15 @@
|
||||
toast(e.message, 'error');
|
||||
}
|
||||
}
|
||||
// [/DEF:handleSave:Function]
|
||||
|
||||
// [DEF:handleDelete:Function]
|
||||
/**
|
||||
* @purpose Deletes a git configuration by ID.
|
||||
* @param {string} id - Configuration ID.
|
||||
* @pre id is valid; user confirmed deletion.
|
||||
* @post Configuration is removed from DB and local state.
|
||||
*/
|
||||
async function handleDelete(id) {
|
||||
if (!confirm('Are you sure you want to delete this Git configuration?')) return;
|
||||
try {
|
||||
@@ -58,31 +105,34 @@
|
||||
toast(e.message, 'error');
|
||||
}
|
||||
}
|
||||
// [/DEF:handleDelete:Function]
|
||||
</script>
|
||||
|
||||
<div class="p-6">
|
||||
<h1 class="text-2xl font-bold mb-6">Git Integration Settings</h1>
|
||||
<!-- [SECTION: TEMPLATE] -->
|
||||
<div class="p-6 max-w-6xl mx-auto">
|
||||
<PageHeader title="Git Integration Settings" />
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<!-- List of Configs -->
|
||||
<div class="bg-white p-6 rounded shadow">
|
||||
<h2 class="text-xl font-semibold mb-4">Configured Servers</h2>
|
||||
<Card title="Configured Servers">
|
||||
{#if configs.length === 0}
|
||||
<p class="text-gray-500">No Git servers configured.</p>
|
||||
{:else}
|
||||
<ul class="divide-y">
|
||||
<ul class="divide-y divide-gray-100">
|
||||
{#each configs as config}
|
||||
<li class="py-3 flex justify-between items-center">
|
||||
<li class="py-4 flex justify-between items-center">
|
||||
<div>
|
||||
<span class="font-medium">{config.name}</span>
|
||||
<span class="text-sm text-gray-500 ml-2">({config.provider})</span>
|
||||
<div class="text-xs text-gray-400">{config.url}</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium text-gray-900">{config.name}</span>
|
||||
<span class="text-xs font-mono bg-gray-50 text-gray-500 px-1.5 py-0.5 rounded">{config.provider}</span>
|
||||
</div>
|
||||
<div class="text-xs text-gray-400 mt-1">{config.url}</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="px-2 py-1 text-xs rounded {config.status === 'CONNECTED' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}">
|
||||
<span class="px-2 py-1 text-xs font-medium rounded {config.status === 'CONNECTED' ? 'bg-green-50 text-green-700' : 'bg-red-50 text-red-700'}">
|
||||
{config.status}
|
||||
</span>
|
||||
<button on:click={() => handleDelete(config.id)} class="text-red-600 hover:text-red-800 p-1" title="Delete">
|
||||
<button on:click={() => handleDelete(config.id)} class="text-gray-400 hover:text-red-600 transition-colors" title="Delete">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
@@ -92,45 +142,41 @@
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<!-- Add New Config -->
|
||||
<div class="bg-white p-6 rounded shadow">
|
||||
<h2 class="text-xl font-semibold mb-4">Add Git Server</h2>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Display Name</label>
|
||||
<input type="text" bind:value={newConfig.name} class="mt-1 block w-full border rounded p-2" placeholder="e.g. My GitHub" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Provider</label>
|
||||
<select bind:value={newConfig.provider} class="mt-1 block w-full border rounded p-2">
|
||||
<option value="GITHUB">GitHub</option>
|
||||
<option value="GITLAB">GitLab</option>
|
||||
<option value="GITEA">Gitea</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Server URL</label>
|
||||
<input type="text" bind:value={newConfig.url} class="mt-1 block w-full border rounded p-2" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Personal Access Token (PAT)</label>
|
||||
<input type="password" bind:value={newConfig.pat} class="mt-1 block w-full border rounded p-2" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700">Default Repository (Optional)</label>
|
||||
<input type="text" bind:value={newConfig.default_repository} class="mt-1 block w-full border rounded p-2" placeholder="org/repo" />
|
||||
</div>
|
||||
<div class="flex space-x-4 pt-4">
|
||||
<button on:click={handleTest} disabled={testing} class="bg-gray-200 px-4 py-2 rounded hover:bg-gray-300 disabled:opacity-50">
|
||||
{testing ? 'Testing...' : 'Test Connection'}
|
||||
</button>
|
||||
<button on:click={handleSave} class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700">
|
||||
<Card title="Add Git Server">
|
||||
<div class="space-y-6">
|
||||
<Input label="Display Name" bind:value={newConfig.name} placeholder="e.g. My GitHub" />
|
||||
<Select
|
||||
label="Provider"
|
||||
bind:value={newConfig.provider}
|
||||
options={[
|
||||
{ value: 'GITHUB', label: 'GitHub' },
|
||||
{ value: 'GITLAB', label: 'GitLab' },
|
||||
{ value: 'GITEA', label: 'Gitea' }
|
||||
]}
|
||||
/>
|
||||
<Input label="Server URL" bind:value={newConfig.url} />
|
||||
<Input label="Personal Access Token (PAT)" type="password" bind:value={newConfig.pat} />
|
||||
<Input label="Default Repository (Optional)" bind:value={newConfig.default_repository} placeholder="org/repo" />
|
||||
|
||||
<div class="flex gap-3 pt-2">
|
||||
<Button variant="secondary" on:click={handleTest} isLoading={testing}>
|
||||
Test Connection
|
||||
</Button>
|
||||
<Button variant="primary" on:click={handleSave}>
|
||||
Save Configuration
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- [/SECTION: TEMPLATE] -->
|
||||
|
||||
<style>
|
||||
/* Styles are handled by Tailwind */
|
||||
</style>
|
||||
|
||||
<!-- [/DEF:GitSettingsPage:Component] -->
|
||||
@@ -12,6 +12,8 @@
|
||||
import { addToast } from '../../lib/toasts';
|
||||
import TaskList from '../../components/TaskList.svelte';
|
||||
import TaskLogViewer from '../../components/TaskLogViewer.svelte';
|
||||
import { t } from '$lib/i18n';
|
||||
import { Button, Card, PageHeader, Select } from '$lib/ui';
|
||||
|
||||
let tasks = [];
|
||||
let environments = [];
|
||||
@@ -114,35 +116,35 @@
|
||||
</script>
|
||||
|
||||
<div class="container mx-auto p-4 max-w-6xl">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-800">Task Management</h1>
|
||||
<button
|
||||
on:click={() => showBackupModal = true}
|
||||
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md shadow-sm transition duration-150 font-medium"
|
||||
>
|
||||
Run Backup
|
||||
</button>
|
||||
</div>
|
||||
<PageHeader title={$t.tasks.management}>
|
||||
<div slot="actions">
|
||||
<Button on:click={() => showBackupModal = true}>
|
||||
{$t.tasks.run_backup}
|
||||
</Button>
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<div class="lg:col-span-1">
|
||||
<h2 class="text-lg font-semibold mb-3 text-gray-700">Recent Tasks</h2>
|
||||
<h2 class="text-lg font-semibold mb-3 text-gray-700">{$t.tasks.recent}</h2>
|
||||
<TaskList {tasks} {loading} on:select={handleSelectTask} />
|
||||
</div>
|
||||
|
||||
<div class="lg:col-span-2">
|
||||
<h2 class="text-lg font-semibold mb-3 text-gray-700">Task Details & Logs</h2>
|
||||
<h2 class="text-lg font-semibold mb-3 text-gray-700">{$t.tasks.details_logs}</h2>
|
||||
{#if selectedTaskId}
|
||||
<div class="bg-white rounded-lg shadow-lg h-[600px] flex flex-col">
|
||||
<TaskLogViewer
|
||||
taskId={selectedTaskId}
|
||||
taskStatus={tasks.find(t => t.id === selectedTaskId)?.status}
|
||||
inline={true}
|
||||
/>
|
||||
</div>
|
||||
<Card padding="none">
|
||||
<div class="h-[600px] flex flex-col overflow-hidden rounded-lg">
|
||||
<TaskLogViewer
|
||||
taskId={selectedTaskId}
|
||||
taskStatus={tasks.find(t => t.id === selectedTaskId)?.status}
|
||||
inline={true}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
{:else}
|
||||
<div class="bg-gray-50 border-2 border-dashed border-gray-300 rounded-lg h-[600px] flex items-center justify-center text-gray-500">
|
||||
<p>Select a task to view logs and details</p>
|
||||
<div class="bg-gray-50 border-2 border-dashed border-gray-100 rounded-lg h-[600px] flex items-center justify-center text-gray-400">
|
||||
<p>{$t.tasks.select_task}</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -150,36 +152,29 @@
|
||||
</div>
|
||||
|
||||
{#if showBackupModal}
|
||||
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
|
||||
<div class="bg-white rounded-lg shadow-xl p-6 w-full max-w-md">
|
||||
<h3 class="text-xl font-bold mb-4">Run Manual Backup</h3>
|
||||
<div class="mb-4">
|
||||
<label for="env-select" class="block text-sm font-medium text-gray-700 mb-1">Target Environment</label>
|
||||
<select
|
||||
id="env-select"
|
||||
bind:value={selectedEnvId}
|
||||
class="w-full border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 p-2 border"
|
||||
>
|
||||
<option value="" disabled>-- Select Environment --</option>
|
||||
{#each environments as env}
|
||||
<option value={env.id}>{env.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex justify-end space-x-3">
|
||||
<button
|
||||
on:click={() => showBackupModal = false}
|
||||
class="px-4 py-2 text-gray-700 hover:bg-gray-100 rounded-md transition"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
on:click={handleRunBackup}
|
||||
class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition"
|
||||
>
|
||||
Start Backup
|
||||
</button>
|
||||
</div>
|
||||
<div class="fixed inset-0 z-50 flex items-center justify-center bg-gray-900/50 backdrop-blur-sm p-4">
|
||||
<div class="w-full max-w-md">
|
||||
<Card title={$t.tasks.manual_backup}>
|
||||
<div class="space-y-6">
|
||||
<Select
|
||||
label={$t.tasks.target_env}
|
||||
bind:value={selectedEnvId}
|
||||
options={[
|
||||
{ value: '', label: $t.tasks.select_env },
|
||||
...environments.map(e => ({ value: e.id, label: e.name }))
|
||||
]}
|
||||
/>
|
||||
|
||||
<div class="flex justify-end gap-3 pt-2">
|
||||
<Button variant="secondary" on:click={() => showBackupModal = false}>
|
||||
{$t.common.cancel}
|
||||
</Button>
|
||||
<Button variant="primary" on:click={handleRunBackup}>
|
||||
Start Backup
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -7,19 +7,18 @@
|
||||
<script>
|
||||
import DebugTool from '../../../components/tools/DebugTool.svelte';
|
||||
import TaskRunner from '../../../components/TaskRunner.svelte';
|
||||
import { PageHeader } from '$lib/ui';
|
||||
</script>
|
||||
|
||||
<div class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
||||
<div class="px-4 py-6 sm:px-0">
|
||||
<h1 class="text-2xl font-semibold text-gray-900 mb-6">System Diagnostics</h1>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<div class="lg:col-span-2">
|
||||
<DebugTool />
|
||||
</div>
|
||||
<div class="lg:col-span-1">
|
||||
<TaskRunner />
|
||||
</div>
|
||||
<div class="max-w-7xl mx-auto p-6">
|
||||
<PageHeader title="System Diagnostics" />
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<div class="lg:col-span-2">
|
||||
<DebugTool />
|
||||
</div>
|
||||
<div class="lg:col-span-1">
|
||||
<TaskRunner />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,19 +7,18 @@
|
||||
<script>
|
||||
import MapperTool from '../../../components/tools/MapperTool.svelte';
|
||||
import TaskRunner from '../../../components/TaskRunner.svelte';
|
||||
import { PageHeader } from '$lib/ui';
|
||||
</script>
|
||||
|
||||
<div class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
||||
<div class="px-4 py-6 sm:px-0">
|
||||
<h1 class="text-2xl font-semibold text-gray-900 mb-6">Dataset Column Mapper</h1>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<div class="lg:col-span-2">
|
||||
<MapperTool />
|
||||
</div>
|
||||
<div class="lg:col-span-1">
|
||||
<TaskRunner />
|
||||
</div>
|
||||
<div class="max-w-7xl mx-auto p-6">
|
||||
<PageHeader title="Dataset Column Mapper" />
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<div class="lg:col-span-2">
|
||||
<MapperTool />
|
||||
</div>
|
||||
<div class="lg:col-span-1">
|
||||
<TaskRunner />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,19 +7,18 @@
|
||||
<script>
|
||||
import SearchTool from '../../../components/tools/SearchTool.svelte';
|
||||
import TaskRunner from '../../../components/TaskRunner.svelte';
|
||||
import { PageHeader } from '$lib/ui';
|
||||
</script>
|
||||
|
||||
<div class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
||||
<div class="px-4 py-6 sm:px-0">
|
||||
<h1 class="text-2xl font-semibold text-gray-900 mb-6">Dataset Search</h1>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<div class="lg:col-span-2">
|
||||
<SearchTool />
|
||||
</div>
|
||||
<div class="lg:col-span-1">
|
||||
<TaskRunner />
|
||||
</div>
|
||||
<div class="max-w-7xl mx-auto p-6">
|
||||
<PageHeader title="Dataset Search" />
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<div class="lg:col-span-2">
|
||||
<SearchTool />
|
||||
</div>
|
||||
<div class="lg:col-span-1">
|
||||
<TaskRunner />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
198
frontend/src/routes/tools/storage/+page.svelte
Normal file
198
frontend/src/routes/tools/storage/+page.svelte
Normal file
@@ -0,0 +1,198 @@
|
||||
<!-- [DEF:StoragePage:Component] -->
|
||||
<!--
|
||||
@SEMANTICS: storage, files, management
|
||||
@PURPOSE: Main page for file storage management.
|
||||
@LAYER: Feature
|
||||
@RELATION: DEPENDS_ON -> storageService
|
||||
@RELATION: CONTAINS -> FileList
|
||||
@RELATION: CONTAINS -> FileUpload
|
||||
|
||||
@INVARIANT: Always displays tabs for Backups and Repositories.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
// [SECTION: IMPORTS]
|
||||
import { onMount } from 'svelte';
|
||||
import { listFiles, deleteFile } from '../../../services/storageService';
|
||||
import { addToast } from '../../../lib/toasts';
|
||||
import { t } from '../../../lib/i18n';
|
||||
import FileList from '../../../components/storage/FileList.svelte';
|
||||
import FileUpload from '../../../components/storage/FileUpload.svelte';
|
||||
// [/SECTION: IMPORTS]
|
||||
|
||||
// [DEF:loadFiles:Function]
|
||||
/**
|
||||
* @purpose Fetches the list of files from the server.
|
||||
* @post Updates the `files` array with the latest data.
|
||||
*/
|
||||
let files = [];
|
||||
let isLoading = false;
|
||||
let activeTab = 'backups';
|
||||
let currentPath = 'backups'; // Relative to storage root
|
||||
|
||||
async function loadFiles() {
|
||||
isLoading = true;
|
||||
try {
|
||||
const category = activeTab;
|
||||
|
||||
// If we have a currentPath, we use it.
|
||||
// But if user switched tabs, we should reset currentPath to category root
|
||||
let effectivePath = currentPath;
|
||||
if (category && !currentPath.startsWith(category)) {
|
||||
effectivePath = category;
|
||||
currentPath = category;
|
||||
}
|
||||
|
||||
// API expects path relative to category root if category is provided
|
||||
const subpath = (category && effectivePath.startsWith(category))
|
||||
? effectivePath.substring(category.length).replace(/^\/+/, '')
|
||||
: effectivePath;
|
||||
|
||||
files = await listFiles(category, subpath);
|
||||
} catch (error) {
|
||||
addToast($t.storage.messages.load_failed.replace('{error}', error.message), 'error');
|
||||
} finally {
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
// [/DEF:loadFiles:Function]
|
||||
|
||||
// [DEF:handleDelete:Function]
|
||||
/**
|
||||
* @purpose Handles the file deletion process.
|
||||
* @param {CustomEvent} event - The delete event containing category and path.
|
||||
*/
|
||||
async function handleDelete(event) {
|
||||
const { category, path, name } = event.detail;
|
||||
if (!confirm($t.storage.messages.delete_confirm.replace('{name}', name))) return;
|
||||
|
||||
try {
|
||||
await deleteFile(category, path);
|
||||
addToast($t.storage.messages.delete_success.replace('{name}', name), 'success');
|
||||
await loadFiles();
|
||||
} catch (error) {
|
||||
addToast($t.storage.messages.delete_failed.replace('{error}', error.message), 'error');
|
||||
}
|
||||
}
|
||||
// [/DEF:handleDelete:Function]
|
||||
|
||||
// [DEF:handleNavigate:Function]
|
||||
/**
|
||||
* @purpose Updates the current path and reloads files when navigating into a directory.
|
||||
* @param {CustomEvent} event - The navigation event containing the new path.
|
||||
*/
|
||||
function handleNavigate(event) {
|
||||
currentPath = event.detail;
|
||||
loadFiles();
|
||||
}
|
||||
// [/DEF:handleNavigate:Function]
|
||||
|
||||
// [DEF:navigateUp:Function]
|
||||
/**
|
||||
* @purpose Navigates one level up in the directory structure.
|
||||
*/
|
||||
function navigateUp() {
|
||||
if (!currentPath || currentPath === activeTab) return;
|
||||
const parts = currentPath.split('/');
|
||||
parts.pop();
|
||||
currentPath = parts.join('/') || '';
|
||||
loadFiles();
|
||||
}
|
||||
// [/DEF:navigateUp:Function]
|
||||
|
||||
onMount(loadFiles);
|
||||
|
||||
$: if (activeTab) {
|
||||
// Reset path when switching tabs
|
||||
if (!currentPath.startsWith(activeTab)) {
|
||||
currentPath = activeTab;
|
||||
}
|
||||
loadFiles();
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- [SECTION: TEMPLATE] -->
|
||||
<div class="container mx-auto p-4 max-w-6xl">
|
||||
<div class="mb-6">
|
||||
<h1 class="text-2xl font-bold text-gray-900">{$t.storage.management}</h1>
|
||||
{#if currentPath}
|
||||
<div class="flex items-center mt-2 text-sm text-gray-500">
|
||||
<button on:click={() => { currentPath = activeTab; loadFiles(); }} class="hover:text-indigo-600">{$t.storage.root}</button>
|
||||
{#each currentPath.split('/').slice(1) as part, i}
|
||||
<span class="mx-2">/</span>
|
||||
<button
|
||||
on:click={() => { currentPath = currentPath.split('/').slice(0, i + 1).join('/'); loadFiles(); }}
|
||||
class="hover:text-indigo-600 capitalize"
|
||||
>
|
||||
{part}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end mb-4">
|
||||
<button
|
||||
on:click={loadFiles}
|
||||
class="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50"
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? $t.storage.refreshing : $t.storage.refresh}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<!-- Main Content: File List -->
|
||||
<div class="lg:col-span-2 space-y-4">
|
||||
<!-- Tabs -->
|
||||
<div class="border-b border-gray-200">
|
||||
<nav class="-mb-px flex space-x-8">
|
||||
<button
|
||||
on:click={() => activeTab = 'backups'}
|
||||
class="py-4 px-1 border-b-2 font-medium text-sm {activeTab === 'backups' ? 'border-indigo-500 text-indigo-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'}"
|
||||
>
|
||||
{$t.storage.backups}
|
||||
</button>
|
||||
<button
|
||||
on:click={() => activeTab = 'repositorys'}
|
||||
class="py-4 px-1 border-b-2 font-medium text-sm {activeTab === 'repositorys' ? 'border-indigo-500 text-indigo-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'}"
|
||||
>
|
||||
{$t.storage.repositories}
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center mb-2">
|
||||
{#if currentPath && currentPath !== activeTab}
|
||||
<button
|
||||
on:click={navigateUp}
|
||||
class="mr-4 inline-flex items-center px-3 py-1 border border-gray-300 shadow-sm text-xs font-medium rounded text-gray-700 bg-white hover:bg-gray-50"
|
||||
>
|
||||
<svg class="h-4 w-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
||||
</svg>
|
||||
Back
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<FileList {files} on:delete={handleDelete} on:navigate={handleNavigate} />
|
||||
</div>
|
||||
|
||||
<!-- Sidebar: Upload -->
|
||||
<div class="lg:col-span-1">
|
||||
<FileUpload
|
||||
category={activeTab}
|
||||
path={currentPath}
|
||||
on:uploaded={loadFiles}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- [/SECTION: TEMPLATE] -->
|
||||
|
||||
<style>
|
||||
/* ... */
|
||||
</style>
|
||||
|
||||
<!-- [/DEF:StoragePage:Component] -->
|
||||
@@ -1,5 +1,5 @@
|
||||
// [DEF:GitServiceClient:Module]
|
||||
/**
|
||||
* [DEF:GitServiceClient:Module]
|
||||
* @SEMANTICS: git, service, api, client
|
||||
* @PURPOSE: API client for Git operations, managing the communication between frontend and backend.
|
||||
* @LAYER: Service
|
||||
|
||||
109
frontend/src/services/storageService.js
Normal file
109
frontend/src/services/storageService.js
Normal file
@@ -0,0 +1,109 @@
|
||||
// [DEF:storageService:Module]
|
||||
/**
|
||||
* @purpose Frontend API client for file storage management.
|
||||
* @layer Service
|
||||
* @relation DEPENDS_ON -> backend.api.storage
|
||||
* @SEMANTICS: storage, api, client
|
||||
*/
|
||||
|
||||
const API_BASE = '/api/storage';
|
||||
|
||||
// [DEF:listFiles:Function]
|
||||
/**
|
||||
* @purpose Fetches the list of files for a given category and subpath.
|
||||
* @param {string} [category] - Optional category filter.
|
||||
* @param {string} [path] - Optional subpath filter.
|
||||
* @returns {Promise<Array>}
|
||||
* @PRE category and path should be valid strings if provided.
|
||||
* @POST Returns a promise resolving to an array of StoredFile objects.
|
||||
*/
|
||||
export async function listFiles(category, path) {
|
||||
const params = new URLSearchParams();
|
||||
if (category) {
|
||||
params.append('category', category);
|
||||
}
|
||||
if (path) {
|
||||
params.append('path', path);
|
||||
}
|
||||
const response = await fetch(`${API_BASE}/files?${params.toString()}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch files: ${response.statusText}`);
|
||||
}
|
||||
return await response.json();
|
||||
}
|
||||
// [/DEF:listFiles:Function]
|
||||
|
||||
// [DEF:uploadFile:Function]
|
||||
/**
|
||||
* @purpose Uploads a file to the storage system.
|
||||
* @param {File} file - The file to upload.
|
||||
* @param {string} category - Target category.
|
||||
* @param {string} [path] - Target subpath.
|
||||
* @returns {Promise<Object>}
|
||||
* @PRE file must be a valid File object; category must be specified.
|
||||
* @POST Returns a promise resolving to the metadata of the uploaded file.
|
||||
*/
|
||||
export async function uploadFile(file, category, path) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('category', category);
|
||||
if (path) {
|
||||
formData.append('path', path);
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_BASE}/upload`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(errorData.detail || `Failed to upload file: ${response.statusText}`);
|
||||
}
|
||||
return await response.json();
|
||||
}
|
||||
// [/DEF:uploadFile:Function]
|
||||
|
||||
// [DEF:deleteFile:Function]
|
||||
/**
|
||||
* @purpose Deletes a file or directory from storage.
|
||||
* @param {string} category - File category.
|
||||
* @param {string} path - Relative path of the item.
|
||||
* @returns {Promise<void>}
|
||||
* @PRE category and path must identify an existing file or directory.
|
||||
* @POST The specified file or directory is removed from storage.
|
||||
*/
|
||||
export async function deleteFile(category, path) {
|
||||
const response = await fetch(`${API_BASE}/files/${category}/${path}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(errorData.detail || `Failed to delete: ${response.statusText}`);
|
||||
}
|
||||
}
|
||||
// [/DEF:deleteFile:Function]
|
||||
|
||||
// [DEF:downloadFileUrl:Function]
|
||||
/**
|
||||
* @purpose Returns the URL for downloading a file.
|
||||
* @param {string} category - File category.
|
||||
* @param {string} path - Relative path of the file.
|
||||
* @returns {string}
|
||||
* @PRE category and path must identify an existing file.
|
||||
* @POST Returns a valid API URL for file download.
|
||||
*/
|
||||
export function downloadFileUrl(category, path) {
|
||||
return `${API_BASE}/download/${category}/${path}`;
|
||||
}
|
||||
// [/DEF:downloadFileUrl:Function]
|
||||
|
||||
export default {
|
||||
listFiles,
|
||||
uploadFile,
|
||||
deleteFile,
|
||||
downloadFileUrl
|
||||
};
|
||||
|
||||
// [/DEF:storageService:Module]
|
||||
@@ -130,7 +130,8 @@ class SemanticEntity:
|
||||
self.compliance_issues.append(f"Missing Mandatory Tag: @{req_tag}")
|
||||
|
||||
# 3. Check for Belief State Logging (Python only)
|
||||
if self.type == "Function" and self.file_path.endswith(".py"):
|
||||
# Skip check for logger.py to avoid circular dependencies
|
||||
if self.type == "Function" and self.file_path.endswith(".py") and "backend/src/core/logger.py" not in self.file_path:
|
||||
if not getattr(self, 'has_belief_scope', False):
|
||||
self.compliance_issues.append("Missing Belief State Logging: Function should use belief_scope context manager.")
|
||||
|
||||
|
||||
126
semantics/reports/semantic_report_20260123_214352.md
Normal file
126
semantics/reports/semantic_report_20260123_214352.md
Normal file
File diff suppressed because one or more lines are too long
109
semantics/reports/semantic_report_20260123_214938.md
Normal file
109
semantics/reports/semantic_report_20260123_214938.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# Semantic Compliance Report
|
||||
|
||||
**Generated At:** 2026-01-23T21:49:38.910491
|
||||
**Global Compliance Score:** 96.7%
|
||||
**Scanned Files:** 93
|
||||
|
||||
## Critical Parsing Errors
|
||||
- 🔴 backend/src/core/utils/network.py:27 Function '__init__' implementation found without matching [DEF:__init__:Function] contract.
|
||||
- 🔴 backend/src/core/utils/network.py:38 Function '__init__' implementation found without matching [DEF:__init__:Function] contract.
|
||||
- 🔴 backend/src/core/utils/network.py:48 Function '__init__' implementation found without matching [DEF:__init__:Function] contract.
|
||||
- 🔴 backend/src/core/utils/network.py:58 Function '__init__' implementation found without matching [DEF:__init__:Function] contract.
|
||||
- 🔴 backend/src/core/utils/network.py:68 Function '__init__' implementation found without matching [DEF:__init__:Function] contract.
|
||||
|
||||
## File Compliance Status
|
||||
| File | Score | Issues |
|
||||
|------|-------|--------|
|
||||
| backend/src/api/routes/git_schemas.py | 🟡 54% | [GitServerConfigBase] Missing Mandatory Tag: @PURPOSE<br>[GitServerConfigBase] Missing Mandatory Tag: @PURPOSE<br>[GitServerConfigCreate] Missing Mandatory Tag: @PURPOSE<br>[GitServerConfigCreate] Missing Mandatory Tag: @PURPOSE<br>[GitServerConfigSchema] Missing Mandatory Tag: @PURPOSE<br>[GitServerConfigSchema] Missing Mandatory Tag: @PURPOSE<br>[GitRepositorySchema] Missing Mandatory Tag: @PURPOSE<br>[GitRepositorySchema] Missing Mandatory Tag: @PURPOSE<br>[BranchSchema] Missing Mandatory Tag: @PURPOSE<br>[BranchSchema] Missing Mandatory Tag: @PURPOSE<br>[CommitSchema] Missing Mandatory Tag: @PURPOSE<br>[CommitSchema] Missing Mandatory Tag: @PURPOSE<br>[BranchCreate] Missing Mandatory Tag: @PURPOSE<br>[BranchCreate] Missing Mandatory Tag: @PURPOSE<br>[BranchCheckout] Missing Mandatory Tag: @PURPOSE<br>[BranchCheckout] Missing Mandatory Tag: @PURPOSE<br>[CommitCreate] Missing Mandatory Tag: @PURPOSE<br>[CommitCreate] Missing Mandatory Tag: @PURPOSE<br>[ConflictResolution] Missing Mandatory Tag: @PURPOSE<br>[ConflictResolution] Missing Mandatory Tag: @PURPOSE<br>[DeploymentEnvironmentSchema] Missing Mandatory Tag: @PURPOSE<br>[DeploymentEnvironmentSchema] Missing Mandatory Tag: @PURPOSE<br>[DeployRequest] Missing Mandatory Tag: @PURPOSE<br>[DeployRequest] Missing Mandatory Tag: @PURPOSE<br>[RepoInitRequest] Missing Mandatory Tag: @PURPOSE<br>[RepoInitRequest] Missing Mandatory Tag: @PURPOSE |
|
||||
| frontend/src/components/git/GitManager.svelte | 🟡 67% | [checkStatus] Missing Mandatory Tag: @PRE<br>[checkStatus] Missing Mandatory Tag: @POST<br>[checkStatus] Missing Mandatory Tag: @PRE<br>[checkStatus] Missing Mandatory Tag: @POST<br>[handleInit] Missing Mandatory Tag: @PRE<br>[handleInit] Missing Mandatory Tag: @POST<br>[handleInit] Missing Mandatory Tag: @PRE<br>[handleInit] Missing Mandatory Tag: @POST<br>[handleSync] Missing Mandatory Tag: @PRE<br>[handleSync] Missing Mandatory Tag: @POST<br>[handleSync] Missing Mandatory Tag: @PRE<br>[handleSync] Missing Mandatory Tag: @POST<br>[handlePush] Missing Mandatory Tag: @PURPOSE<br>[handlePush] Missing Mandatory Tag: @PRE<br>[handlePush] Missing Mandatory Tag: @POST<br>[handlePush] Missing Mandatory Tag: @PURPOSE<br>[handlePush] Missing Mandatory Tag: @PRE<br>[handlePush] Missing Mandatory Tag: @POST<br>[handlePull] Missing Mandatory Tag: @PURPOSE<br>[handlePull] Missing Mandatory Tag: @PRE<br>[handlePull] Missing Mandatory Tag: @POST<br>[handlePull] Missing Mandatory Tag: @PURPOSE<br>[handlePull] Missing Mandatory Tag: @PRE<br>[handlePull] Missing Mandatory Tag: @POST |
|
||||
| frontend/src/routes/git/+page.svelte | 🟡 67% | [fetchEnvironments] Missing Mandatory Tag: @PURPOSE<br>[fetchEnvironments] Missing Mandatory Tag: @PRE<br>[fetchEnvironments] Missing Mandatory Tag: @POST<br>[fetchEnvironments] Missing Mandatory Tag: @PURPOSE<br>[fetchEnvironments] Missing Mandatory Tag: @PRE<br>[fetchEnvironments] Missing Mandatory Tag: @POST<br>[fetchDashboards] Missing Mandatory Tag: @PURPOSE<br>[fetchDashboards] Missing Mandatory Tag: @PRE<br>[fetchDashboards] Missing Mandatory Tag: @POST<br>[fetchDashboards] Missing Mandatory Tag: @PURPOSE<br>[fetchDashboards] Missing Mandatory Tag: @PRE<br>[fetchDashboards] Missing Mandatory Tag: @POST |
|
||||
| backend/src/api/routes/git.py | 🟡 69% | [get_git_configs] Missing Mandatory Tag: @PRE<br>[get_git_configs] Missing Mandatory Tag: @POST<br>[get_git_configs] Missing Mandatory Tag: @PRE<br>[get_git_configs] Missing Mandatory Tag: @POST<br>[create_git_config] Missing Mandatory Tag: @PRE<br>[create_git_config] Missing Mandatory Tag: @POST<br>[create_git_config] Missing Mandatory Tag: @PRE<br>[create_git_config] Missing Mandatory Tag: @POST<br>[delete_git_config] Missing Mandatory Tag: @PRE<br>[delete_git_config] Missing Mandatory Tag: @POST<br>[delete_git_config] Missing Mandatory Tag: @PRE<br>[delete_git_config] Missing Mandatory Tag: @POST<br>[test_git_config] Missing Mandatory Tag: @PRE<br>[test_git_config] Missing Mandatory Tag: @POST<br>[test_git_config] Missing Mandatory Tag: @PRE<br>[test_git_config] Missing Mandatory Tag: @POST<br>[init_repository] Missing Mandatory Tag: @PRE<br>[init_repository] Missing Mandatory Tag: @POST<br>[init_repository] Missing Mandatory Tag: @PRE<br>[init_repository] Missing Mandatory Tag: @POST<br>[get_branches] Missing Mandatory Tag: @PRE<br>[get_branches] Missing Mandatory Tag: @POST<br>[get_branches] Missing Mandatory Tag: @PRE<br>[get_branches] Missing Mandatory Tag: @POST<br>[create_branch] Missing Mandatory Tag: @PRE<br>[create_branch] Missing Mandatory Tag: @POST<br>[create_branch] Missing Mandatory Tag: @PRE<br>[create_branch] Missing Mandatory Tag: @POST<br>[checkout_branch] Missing Mandatory Tag: @PRE<br>[checkout_branch] Missing Mandatory Tag: @POST<br>[checkout_branch] Missing Mandatory Tag: @PRE<br>[checkout_branch] Missing Mandatory Tag: @POST<br>[commit_changes] Missing Mandatory Tag: @PRE<br>[commit_changes] Missing Mandatory Tag: @POST<br>[commit_changes] Missing Mandatory Tag: @PRE<br>[commit_changes] Missing Mandatory Tag: @POST<br>[push_changes] Missing Mandatory Tag: @PRE<br>[push_changes] Missing Mandatory Tag: @POST<br>[push_changes] Missing Mandatory Tag: @PRE<br>[push_changes] Missing Mandatory Tag: @POST<br>[pull_changes] Missing Mandatory Tag: @PRE<br>[pull_changes] Missing Mandatory Tag: @POST<br>[pull_changes] Missing Mandatory Tag: @PRE<br>[pull_changes] Missing Mandatory Tag: @POST<br>[sync_dashboard] Missing Mandatory Tag: @PRE<br>[sync_dashboard] Missing Mandatory Tag: @POST<br>[sync_dashboard] Missing Mandatory Tag: @PRE<br>[sync_dashboard] Missing Mandatory Tag: @POST<br>[get_environments] Missing Mandatory Tag: @PRE<br>[get_environments] Missing Mandatory Tag: @POST<br>[get_environments] Missing Mandatory Tag: @PRE<br>[get_environments] Missing Mandatory Tag: @POST<br>[deploy_dashboard] Missing Mandatory Tag: @PRE<br>[deploy_dashboard] Missing Mandatory Tag: @POST<br>[deploy_dashboard] Missing Mandatory Tag: @PRE<br>[deploy_dashboard] Missing Mandatory Tag: @POST<br>[get_history] Missing Mandatory Tag: @PRE<br>[get_history] Missing Mandatory Tag: @POST<br>[get_history] Missing Mandatory Tag: @PRE<br>[get_history] Missing Mandatory Tag: @POST<br>[get_repository_status] Missing Mandatory Tag: @PRE<br>[get_repository_status] Missing Mandatory Tag: @POST<br>[get_repository_status] Missing Mandatory Tag: @PRE<br>[get_repository_status] Missing Mandatory Tag: @POST<br>[get_repository_diff] Missing Mandatory Tag: @PRE<br>[get_repository_diff] Missing Mandatory Tag: @POST<br>[get_repository_diff] Missing Mandatory Tag: @PRE<br>[get_repository_diff] Missing Mandatory Tag: @POST |
|
||||
| backend/src/services/git_service.py | 🟡 72% | [__init__] Missing Mandatory Tag: @PRE<br>[__init__] Missing Mandatory Tag: @POST<br>[__init__] Missing Mandatory Tag: @PRE<br>[__init__] Missing Mandatory Tag: @POST<br>[__init__] Missing Mandatory Tag: @PRE<br>[__init__] Missing Mandatory Tag: @POST<br>[_get_repo_path] Missing Mandatory Tag: @PRE<br>[_get_repo_path] Missing Mandatory Tag: @POST<br>[_get_repo_path] Missing Belief State Logging: Function should use belief_scope context manager.<br>[_get_repo_path] Missing Mandatory Tag: @PRE<br>[_get_repo_path] Missing Mandatory Tag: @POST<br>[_get_repo_path] Missing Belief State Logging: Function should use belief_scope context manager.<br>[_get_repo_path] Missing Mandatory Tag: @PRE<br>[_get_repo_path] Missing Mandatory Tag: @POST<br>[_get_repo_path] Missing Belief State Logging: Function should use belief_scope context manager.<br>[init_repo] Missing Mandatory Tag: @PRE<br>[init_repo] Missing Mandatory Tag: @POST<br>[init_repo] Missing Mandatory Tag: @PRE<br>[init_repo] Missing Mandatory Tag: @POST<br>[init_repo] Missing Mandatory Tag: @PRE<br>[init_repo] Missing Mandatory Tag: @POST<br>[get_repo] Missing Mandatory Tag: @POST<br>[get_repo] Missing Mandatory Tag: @POST<br>[get_repo] Missing Mandatory Tag: @POST<br>[list_branches] Missing Mandatory Tag: @PRE<br>[list_branches] Missing Mandatory Tag: @POST<br>[list_branches] Missing Mandatory Tag: @PRE<br>[list_branches] Missing Mandatory Tag: @POST<br>[list_branches] Missing Mandatory Tag: @PRE<br>[list_branches] Missing Mandatory Tag: @POST<br>[create_branch] Missing Mandatory Tag: @PRE<br>[create_branch] Missing Mandatory Tag: @POST<br>[create_branch] Missing Mandatory Tag: @PRE<br>[create_branch] Missing Mandatory Tag: @POST<br>[create_branch] Missing Mandatory Tag: @PRE<br>[create_branch] Missing Mandatory Tag: @POST<br>[checkout_branch] Missing Mandatory Tag: @PRE<br>[checkout_branch] Missing Mandatory Tag: @POST<br>[checkout_branch] Missing Mandatory Tag: @PRE<br>[checkout_branch] Missing Mandatory Tag: @POST<br>[checkout_branch] Missing Mandatory Tag: @PRE<br>[checkout_branch] Missing Mandatory Tag: @POST<br>[commit_changes] Missing Mandatory Tag: @PRE<br>[commit_changes] Missing Mandatory Tag: @POST<br>[commit_changes] Missing Mandatory Tag: @PRE<br>[commit_changes] Missing Mandatory Tag: @POST<br>[commit_changes] Missing Mandatory Tag: @PRE<br>[commit_changes] Missing Mandatory Tag: @POST<br>[push_changes] Missing Mandatory Tag: @PRE<br>[push_changes] Missing Mandatory Tag: @POST<br>[push_changes] Missing Mandatory Tag: @PRE<br>[push_changes] Missing Mandatory Tag: @POST<br>[push_changes] Missing Mandatory Tag: @PRE<br>[push_changes] Missing Mandatory Tag: @POST<br>[pull_changes] Missing Mandatory Tag: @PRE<br>[pull_changes] Missing Mandatory Tag: @POST<br>[pull_changes] Missing Mandatory Tag: @PRE<br>[pull_changes] Missing Mandatory Tag: @POST<br>[pull_changes] Missing Mandatory Tag: @PRE<br>[pull_changes] Missing Mandatory Tag: @POST<br>[get_status] Missing Mandatory Tag: @PRE<br>[get_status] Missing Mandatory Tag: @POST<br>[get_status] Missing Mandatory Tag: @PRE<br>[get_status] Missing Mandatory Tag: @POST<br>[get_status] Missing Mandatory Tag: @PRE<br>[get_status] Missing Mandatory Tag: @POST<br>[get_diff] Missing Mandatory Tag: @PRE<br>[get_diff] Missing Mandatory Tag: @POST<br>[get_diff] Missing Mandatory Tag: @PRE<br>[get_diff] Missing Mandatory Tag: @POST<br>[get_diff] Missing Mandatory Tag: @PRE<br>[get_diff] Missing Mandatory Tag: @POST<br>[get_commit_history] Missing Mandatory Tag: @PRE<br>[get_commit_history] Missing Mandatory Tag: @POST<br>[get_commit_history] Missing Mandatory Tag: @PRE<br>[get_commit_history] Missing Mandatory Tag: @POST<br>[get_commit_history] Missing Mandatory Tag: @PRE<br>[get_commit_history] Missing Mandatory Tag: @POST<br>[test_connection] Missing Mandatory Tag: @PRE<br>[test_connection] Missing Mandatory Tag: @POST<br>[test_connection] Missing Mandatory Tag: @PRE<br>[test_connection] Missing Mandatory Tag: @POST<br>[test_connection] Missing Mandatory Tag: @PRE<br>[test_connection] Missing Mandatory Tag: @POST |
|
||||
| frontend/src/routes/settings/git/+page.svelte | 🟡 73% | [loadConfigs] Missing Mandatory Tag: @PRE<br>[loadConfigs] Missing Mandatory Tag: @POST<br>[loadConfigs] Missing Mandatory Tag: @PRE<br>[loadConfigs] Missing Mandatory Tag: @POST<br>[handleTest] Missing Mandatory Tag: @PRE<br>[handleTest] Missing Mandatory Tag: @POST<br>[handleTest] Missing Mandatory Tag: @PRE<br>[handleTest] Missing Mandatory Tag: @POST<br>[handleSave] Missing Mandatory Tag: @PRE<br>[handleSave] Missing Mandatory Tag: @POST<br>[handleSave] Missing Mandatory Tag: @PRE<br>[handleSave] Missing Mandatory Tag: @POST<br>[handleDelete] Missing Mandatory Tag: @PRE<br>[handleDelete] Missing Mandatory Tag: @POST<br>[handleDelete] Missing Mandatory Tag: @PRE<br>[handleDelete] Missing Mandatory Tag: @POST |
|
||||
| frontend/src/components/git/BranchSelector.svelte | 🟡 75% | [onMount] Missing Mandatory Tag: @PURPOSE<br>[onMount] Missing Mandatory Tag: @PRE<br>[onMount] Missing Mandatory Tag: @POST<br>[onMount] Missing Mandatory Tag: @PURPOSE<br>[onMount] Missing Mandatory Tag: @PRE<br>[onMount] Missing Mandatory Tag: @POST<br>[loadBranches] Missing Mandatory Tag: @PRE<br>[loadBranches] Missing Mandatory Tag: @PRE<br>[handleSelect] Missing Mandatory Tag: @PURPOSE<br>[handleSelect] Missing Mandatory Tag: @PRE<br>[handleSelect] Missing Mandatory Tag: @POST<br>[handleSelect] Missing Mandatory Tag: @PURPOSE<br>[handleSelect] Missing Mandatory Tag: @PRE<br>[handleSelect] Missing Mandatory Tag: @POST<br>[handleCheckout] Missing Mandatory Tag: @PRE<br>[handleCheckout] Missing Mandatory Tag: @PRE<br>[handleCreate] Missing Mandatory Tag: @PRE<br>[handleCreate] Missing Mandatory Tag: @PRE |
|
||||
| backend/src/plugins/git_plugin.py | 🟡 82% | [__init__] Missing Mandatory Tag: @PRE<br>[__init__] Missing Mandatory Tag: @PRE<br>[__init__] Missing Mandatory Tag: @PRE<br>[id] Missing Mandatory Tag: @PRE<br>[id] Missing Mandatory Tag: @POST<br>[id] Missing Belief State Logging: Function should use belief_scope context manager.<br>[id] Missing Mandatory Tag: @PRE<br>[id] Missing Mandatory Tag: @POST<br>[id] Missing Belief State Logging: Function should use belief_scope context manager.<br>[id] Missing Mandatory Tag: @PRE<br>[id] Missing Mandatory Tag: @POST<br>[id] Missing Belief State Logging: Function should use belief_scope context manager.<br>[name] Missing Mandatory Tag: @PRE<br>[name] Missing Mandatory Tag: @POST<br>[name] Missing Belief State Logging: Function should use belief_scope context manager.<br>[name] Missing Mandatory Tag: @PRE<br>[name] Missing Mandatory Tag: @POST<br>[name] Missing Belief State Logging: Function should use belief_scope context manager.<br>[name] Missing Mandatory Tag: @PRE<br>[name] Missing Mandatory Tag: @POST<br>[name] Missing Belief State Logging: Function should use belief_scope context manager.<br>[description] Missing Mandatory Tag: @PRE<br>[description] Missing Mandatory Tag: @POST<br>[description] Missing Belief State Logging: Function should use belief_scope context manager.<br>[description] Missing Mandatory Tag: @PRE<br>[description] Missing Mandatory Tag: @POST<br>[description] Missing Belief State Logging: Function should use belief_scope context manager.<br>[description] Missing Mandatory Tag: @PRE<br>[description] Missing Mandatory Tag: @POST<br>[description] Missing Belief State Logging: Function should use belief_scope context manager.<br>[version] Missing Mandatory Tag: @PRE<br>[version] Missing Mandatory Tag: @POST<br>[version] Missing Belief State Logging: Function should use belief_scope context manager.<br>[version] Missing Mandatory Tag: @PRE<br>[version] Missing Mandatory Tag: @POST<br>[version] Missing Belief State Logging: Function should use belief_scope context manager.<br>[version] Missing Mandatory Tag: @PRE<br>[version] Missing Mandatory Tag: @POST<br>[version] Missing Belief State Logging: Function should use belief_scope context manager.<br>[get_schema] Missing Mandatory Tag: @PRE<br>[get_schema] Missing Mandatory Tag: @POST<br>[get_schema] Missing Mandatory Tag: @PRE<br>[get_schema] Missing Mandatory Tag: @POST<br>[get_schema] Missing Mandatory Tag: @PRE<br>[get_schema] Missing Mandatory Tag: @POST<br>[initialize] Missing Mandatory Tag: @PRE<br>[initialize] Missing Mandatory Tag: @PRE<br>[initialize] Missing Mandatory Tag: @PRE<br>[_get_env] Missing Mandatory Tag: @PRE<br>[_get_env] Missing Mandatory Tag: @POST<br>[_get_env] Missing Mandatory Tag: @PRE<br>[_get_env] Missing Mandatory Tag: @POST<br>[_get_env] Missing Mandatory Tag: @PRE<br>[_get_env] Missing Mandatory Tag: @POST<br>[_get_env] Missing Mandatory Tag: @PRE<br>[_get_env] Missing Mandatory Tag: @POST |
|
||||
| backend/src/models/git.py | 🟡 83% | [GitModels] Missing Mandatory Tag: @SEMANTICS |
|
||||
| frontend/src/components/git/CommitHistory.svelte | 🟡 83% | [onMount] Missing Mandatory Tag: @PRE<br>[onMount] Missing Mandatory Tag: @POST<br>[onMount] Missing Mandatory Tag: @PRE<br>[onMount] Missing Mandatory Tag: @POST<br>[loadHistory] Missing Mandatory Tag: @PRE<br>[loadHistory] Missing Mandatory Tag: @PRE |
|
||||
| backend/src/core/utils/network.py | 🟡 93% | [SupersetAPIError.__init__] Missing Mandatory Tag: @PRE<br>[SupersetAPIError.__init__] Missing Mandatory Tag: @POST<br>[SupersetAPIError.__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[SupersetAPIError.__init__] Missing Mandatory Tag: @PRE<br>[SupersetAPIError.__init__] Missing Mandatory Tag: @POST<br>[SupersetAPIError.__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[SupersetAPIError.__init__] Missing Mandatory Tag: @PRE<br>[SupersetAPIError.__init__] Missing Mandatory Tag: @POST<br>[SupersetAPIError.__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[AuthenticationError.__init__] Missing Mandatory Tag: @PRE<br>[AuthenticationError.__init__] Missing Mandatory Tag: @POST<br>[AuthenticationError.__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[AuthenticationError.__init__] Missing Mandatory Tag: @PRE<br>[AuthenticationError.__init__] Missing Mandatory Tag: @POST<br>[AuthenticationError.__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[AuthenticationError.__init__] Missing Mandatory Tag: @PRE<br>[AuthenticationError.__init__] Missing Mandatory Tag: @POST<br>[AuthenticationError.__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[PermissionDeniedError.__init__] Missing Mandatory Tag: @PRE<br>[PermissionDeniedError.__init__] Missing Mandatory Tag: @POST<br>[PermissionDeniedError.__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[PermissionDeniedError.__init__] Missing Mandatory Tag: @PRE<br>[PermissionDeniedError.__init__] Missing Mandatory Tag: @POST<br>[PermissionDeniedError.__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[PermissionDeniedError.__init__] Missing Mandatory Tag: @PRE<br>[PermissionDeniedError.__init__] Missing Mandatory Tag: @POST<br>[PermissionDeniedError.__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[DashboardNotFoundError.__init__] Missing Mandatory Tag: @PRE<br>[DashboardNotFoundError.__init__] Missing Mandatory Tag: @POST<br>[DashboardNotFoundError.__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[DashboardNotFoundError.__init__] Missing Mandatory Tag: @PRE<br>[DashboardNotFoundError.__init__] Missing Mandatory Tag: @POST<br>[DashboardNotFoundError.__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[DashboardNotFoundError.__init__] Missing Mandatory Tag: @PRE<br>[DashboardNotFoundError.__init__] Missing Mandatory Tag: @POST<br>[DashboardNotFoundError.__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[NetworkError.__init__] Missing Mandatory Tag: @PRE<br>[NetworkError.__init__] Missing Mandatory Tag: @POST<br>[NetworkError.__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[NetworkError.__init__] Missing Mandatory Tag: @PRE<br>[NetworkError.__init__] Missing Mandatory Tag: @POST<br>[NetworkError.__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[NetworkError.__init__] Missing Mandatory Tag: @PRE<br>[NetworkError.__init__] Missing Mandatory Tag: @POST<br>[NetworkError.__init__] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/core/logger.py | 🟡 93% | [format] Missing Belief State Logging: Function should use belief_scope context manager.<br>[format] Missing Belief State Logging: Function should use belief_scope context manager.<br>[format] Missing Belief State Logging: Function should use belief_scope context manager.<br>[belief_scope] Missing Belief State Logging: Function should use belief_scope context manager.<br>[belief_scope] Missing Belief State Logging: Function should use belief_scope context manager.<br>[configure_logger] Missing Belief State Logging: Function should use belief_scope context manager.<br>[configure_logger] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[emit] Missing Belief State Logging: Function should use belief_scope context manager.<br>[emit] Missing Belief State Logging: Function should use belief_scope context manager.<br>[emit] Missing Belief State Logging: Function should use belief_scope context manager.<br>[get_recent_logs] Missing Belief State Logging: Function should use belief_scope context manager.<br>[get_recent_logs] Missing Belief State Logging: Function should use belief_scope context manager.<br>[get_recent_logs] Missing Belief State Logging: Function should use belief_scope context manager.<br>[believed] Missing Mandatory Tag: @PRE<br>[believed] Missing Mandatory Tag: @POST<br>[believed] Missing Belief State Logging: Function should use belief_scope context manager.<br>[believed] Missing Mandatory Tag: @PRE<br>[believed] Missing Mandatory Tag: @POST<br>[believed] Missing Belief State Logging: Function should use belief_scope context manager.<br>[believed] Missing Mandatory Tag: @PRE<br>[believed] Missing Mandatory Tag: @POST<br>[believed] Missing Belief State Logging: Function should use belief_scope context manager.<br>[decorator] Missing Mandatory Tag: @PRE<br>[decorator] Missing Mandatory Tag: @POST<br>[decorator] Missing Belief State Logging: Function should use belief_scope context manager.<br>[decorator] Missing Mandatory Tag: @PRE<br>[decorator] Missing Mandatory Tag: @POST<br>[decorator] Missing Belief State Logging: Function should use belief_scope context manager.<br>[decorator] Missing Mandatory Tag: @PRE<br>[decorator] Missing Mandatory Tag: @POST<br>[decorator] Missing Belief State Logging: Function should use belief_scope context manager.<br>[decorator] Missing Mandatory Tag: @PRE<br>[decorator] Missing Mandatory Tag: @POST<br>[decorator] Missing Belief State Logging: Function should use belief_scope context manager.<br>[wrapper] Missing Mandatory Tag: @PRE<br>[wrapper] Missing Mandatory Tag: @POST<br>[wrapper] Missing Mandatory Tag: @PRE<br>[wrapper] Missing Mandatory Tag: @POST<br>[wrapper] Missing Mandatory Tag: @PRE<br>[wrapper] Missing Mandatory Tag: @POST<br>[wrapper] Missing Mandatory Tag: @PRE<br>[wrapper] Missing Mandatory Tag: @POST<br>[wrapper] Missing Mandatory Tag: @PRE<br>[wrapper] Missing Mandatory Tag: @POST |
|
||||
| frontend/src/components/git/CommitModal.svelte | 🟡 94% | [loadStatus] Missing Mandatory Tag: @POST<br>[loadStatus] Missing Mandatory Tag: @POST |
|
||||
| frontend/src/components/DashboardGrid.svelte | 🟡 94% | [openGit] Missing Mandatory Tag: @PRE<br>[openGit] Missing Mandatory Tag: @POST<br>[openGit] Missing Mandatory Tag: @PRE<br>[openGit] Missing Mandatory Tag: @POST |
|
||||
| frontend/src/components/git/DeploymentModal.svelte | 🟡 96% | [loadEnvironments] Missing Mandatory Tag: @PRE<br>[loadEnvironments] Missing Mandatory Tag: @PRE |
|
||||
| backend/src/core/utils/dataset_mapper.py | 🟡 97% | [__init__] Missing Mandatory Tag: @PRE<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Mandatory Tag: @PRE<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Mandatory Tag: @PRE<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| generate_semantic_map.py | 🟢 100% | [__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__enter__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__enter__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__exit__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__exit__] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| frontend/src/components/TaskHistory.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/MappingTable.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/EnvSelector.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/TaskList.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/DynamicForm.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/Footer.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/Navbar.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/TaskRunner.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/TaskLogViewer.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/PasswordPrompt.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/MissingMappingModal.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/Toast.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/git/ConflictResolver.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/tools/DebugTool.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/tools/ConnectionForm.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/tools/MapperTool.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/tools/SearchTool.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/tools/ConnectionList.svelte | 🟢 100% | OK |
|
||||
| frontend/src/pages/Settings.svelte | 🟢 100% | OK |
|
||||
| frontend/src/pages/Dashboard.svelte | 🟢 100% | OK |
|
||||
| frontend/src/services/gitService.js | 🟢 100% | OK |
|
||||
| frontend/src/services/connectionService.js | 🟢 100% | OK |
|
||||
| frontend/src/services/taskService.js | 🟢 100% | OK |
|
||||
| frontend/src/services/toolsService.js | 🟢 100% | OK |
|
||||
| frontend/src/lib/stores.js | 🟢 100% | OK |
|
||||
| frontend/src/lib/toasts.js | 🟢 100% | OK |
|
||||
| frontend/src/lib/api.js | 🟢 100% | OK |
|
||||
| frontend/src/routes/+page.ts | 🟢 100% | OK |
|
||||
| frontend/src/routes/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/tasks/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/settings/+page.ts | 🟢 100% | OK |
|
||||
| frontend/src/routes/settings/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/settings/connections/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/tools/mapper/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/tools/debug/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/tools/search/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/migration/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/migration/mappings/+page.svelte | 🟢 100% | OK |
|
||||
| backend/delete_running_tasks.py | 🟢 100% | [delete_running_tasks] Missing Belief State Logging: Function should use belief_scope context manager.<br>[delete_running_tasks] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/dependencies.py | 🟢 100% | OK |
|
||||
| backend/src/app.py | 🟢 100% | OK |
|
||||
| backend/src/models/mapping.py | 🟢 100% | OK |
|
||||
| backend/src/models/dashboard.py | 🟢 100% | OK |
|
||||
| backend/src/models/task.py | 🟢 100% | OK |
|
||||
| backend/src/models/connection.py | 🟢 100% | OK |
|
||||
| backend/src/services/mapping_service.py | 🟢 100% | OK |
|
||||
| backend/src/core/config_manager.py | 🟢 100% | OK |
|
||||
| backend/src/core/superset_client.py | 🟢 100% | OK |
|
||||
| backend/src/core/migration_engine.py | 🟢 100% | [_transform_yaml] Missing Belief State Logging: Function should use belief_scope context manager.<br>[_transform_yaml] Missing Belief State Logging: Function should use belief_scope context manager.<br>[_transform_yaml] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/core/database.py | 🟢 100% | OK |
|
||||
| backend/src/core/config_models.py | 🟢 100% | OK |
|
||||
| backend/src/core/scheduler.py | 🟢 100% | OK |
|
||||
| backend/src/core/plugin_loader.py | 🟢 100% | OK |
|
||||
| backend/src/core/plugin_base.py | 🟢 100% | OK |
|
||||
| backend/src/core/utils/matching.py | 🟢 100% | [suggest_mappings] Missing Belief State Logging: Function should use belief_scope context manager.<br>[suggest_mappings] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/core/utils/fileio.py | 🟢 100% | [replacer] Missing Belief State Logging: Function should use belief_scope context manager.<br>[replacer] Missing Belief State Logging: Function should use belief_scope context manager.<br>[replacer] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/core/task_manager/manager.py | 🟢 100% | OK |
|
||||
| backend/src/core/task_manager/__init__.py | 🟢 100% | OK |
|
||||
| backend/src/core/task_manager/cleanup.py | 🟢 100% | [__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/core/task_manager/models.py | 🟢 100% | [__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/core/task_manager/persistence.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/search.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/backup.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/debug.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/migration.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/mapper.py | 🟢 100% | OK |
|
||||
| backend/src/api/auth.py | 🟢 100% | [get_current_user] Missing Belief State Logging: Function should use belief_scope context manager.<br>[get_current_user] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/api/routes/settings.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/connections.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/tasks.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/environments.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/plugins.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/migration.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/mappings.py | 🟢 100% | OK |
|
||||
| backend/tests/test_models.py | 🟢 100% | OK |
|
||||
| backend/tests/test_logger.py | 🟢 100% | OK |
|
||||
102
semantics/reports/semantic_report_20260123_215313.md
Normal file
102
semantics/reports/semantic_report_20260123_215313.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# Semantic Compliance Report
|
||||
|
||||
**Generated At:** 2026-01-23T21:53:13.473176
|
||||
**Global Compliance Score:** 98.0%
|
||||
**Scanned Files:** 93
|
||||
|
||||
## File Compliance Status
|
||||
| File | Score | Issues |
|
||||
|------|-------|--------|
|
||||
| frontend/src/components/git/GitManager.svelte | 🟡 67% | [checkStatus] Missing Mandatory Tag: @PRE<br>[checkStatus] Missing Mandatory Tag: @POST<br>[checkStatus] Missing Mandatory Tag: @PRE<br>[checkStatus] Missing Mandatory Tag: @POST<br>[handleInit] Missing Mandatory Tag: @PRE<br>[handleInit] Missing Mandatory Tag: @POST<br>[handleInit] Missing Mandatory Tag: @PRE<br>[handleInit] Missing Mandatory Tag: @POST<br>[handleSync] Missing Mandatory Tag: @PRE<br>[handleSync] Missing Mandatory Tag: @POST<br>[handleSync] Missing Mandatory Tag: @PRE<br>[handleSync] Missing Mandatory Tag: @POST<br>[handlePush] Missing Mandatory Tag: @PURPOSE<br>[handlePush] Missing Mandatory Tag: @PRE<br>[handlePush] Missing Mandatory Tag: @POST<br>[handlePush] Missing Mandatory Tag: @PURPOSE<br>[handlePush] Missing Mandatory Tag: @PRE<br>[handlePush] Missing Mandatory Tag: @POST<br>[handlePull] Missing Mandatory Tag: @PURPOSE<br>[handlePull] Missing Mandatory Tag: @PRE<br>[handlePull] Missing Mandatory Tag: @POST<br>[handlePull] Missing Mandatory Tag: @PURPOSE<br>[handlePull] Missing Mandatory Tag: @PRE<br>[handlePull] Missing Mandatory Tag: @POST |
|
||||
| backend/src/services/git_service.py | 🟡 72% | [__init__] Missing Mandatory Tag: @PRE<br>[__init__] Missing Mandatory Tag: @POST<br>[__init__] Missing Mandatory Tag: @PRE<br>[__init__] Missing Mandatory Tag: @POST<br>[__init__] Missing Mandatory Tag: @PRE<br>[__init__] Missing Mandatory Tag: @POST<br>[_get_repo_path] Missing Mandatory Tag: @PRE<br>[_get_repo_path] Missing Mandatory Tag: @POST<br>[_get_repo_path] Missing Belief State Logging: Function should use belief_scope context manager.<br>[_get_repo_path] Missing Mandatory Tag: @PRE<br>[_get_repo_path] Missing Mandatory Tag: @POST<br>[_get_repo_path] Missing Belief State Logging: Function should use belief_scope context manager.<br>[_get_repo_path] Missing Mandatory Tag: @PRE<br>[_get_repo_path] Missing Mandatory Tag: @POST<br>[_get_repo_path] Missing Belief State Logging: Function should use belief_scope context manager.<br>[init_repo] Missing Mandatory Tag: @PRE<br>[init_repo] Missing Mandatory Tag: @POST<br>[init_repo] Missing Mandatory Tag: @PRE<br>[init_repo] Missing Mandatory Tag: @POST<br>[init_repo] Missing Mandatory Tag: @PRE<br>[init_repo] Missing Mandatory Tag: @POST<br>[get_repo] Missing Mandatory Tag: @POST<br>[get_repo] Missing Mandatory Tag: @POST<br>[get_repo] Missing Mandatory Tag: @POST<br>[list_branches] Missing Mandatory Tag: @PRE<br>[list_branches] Missing Mandatory Tag: @POST<br>[list_branches] Missing Mandatory Tag: @PRE<br>[list_branches] Missing Mandatory Tag: @POST<br>[list_branches] Missing Mandatory Tag: @PRE<br>[list_branches] Missing Mandatory Tag: @POST<br>[create_branch] Missing Mandatory Tag: @PRE<br>[create_branch] Missing Mandatory Tag: @POST<br>[create_branch] Missing Mandatory Tag: @PRE<br>[create_branch] Missing Mandatory Tag: @POST<br>[create_branch] Missing Mandatory Tag: @PRE<br>[create_branch] Missing Mandatory Tag: @POST<br>[checkout_branch] Missing Mandatory Tag: @PRE<br>[checkout_branch] Missing Mandatory Tag: @POST<br>[checkout_branch] Missing Mandatory Tag: @PRE<br>[checkout_branch] Missing Mandatory Tag: @POST<br>[checkout_branch] Missing Mandatory Tag: @PRE<br>[checkout_branch] Missing Mandatory Tag: @POST<br>[commit_changes] Missing Mandatory Tag: @PRE<br>[commit_changes] Missing Mandatory Tag: @POST<br>[commit_changes] Missing Mandatory Tag: @PRE<br>[commit_changes] Missing Mandatory Tag: @POST<br>[commit_changes] Missing Mandatory Tag: @PRE<br>[commit_changes] Missing Mandatory Tag: @POST<br>[push_changes] Missing Mandatory Tag: @PRE<br>[push_changes] Missing Mandatory Tag: @POST<br>[push_changes] Missing Mandatory Tag: @PRE<br>[push_changes] Missing Mandatory Tag: @POST<br>[push_changes] Missing Mandatory Tag: @PRE<br>[push_changes] Missing Mandatory Tag: @POST<br>[pull_changes] Missing Mandatory Tag: @PRE<br>[pull_changes] Missing Mandatory Tag: @POST<br>[pull_changes] Missing Mandatory Tag: @PRE<br>[pull_changes] Missing Mandatory Tag: @POST<br>[pull_changes] Missing Mandatory Tag: @PRE<br>[pull_changes] Missing Mandatory Tag: @POST<br>[get_status] Missing Mandatory Tag: @PRE<br>[get_status] Missing Mandatory Tag: @POST<br>[get_status] Missing Mandatory Tag: @PRE<br>[get_status] Missing Mandatory Tag: @POST<br>[get_status] Missing Mandatory Tag: @PRE<br>[get_status] Missing Mandatory Tag: @POST<br>[get_diff] Missing Mandatory Tag: @PRE<br>[get_diff] Missing Mandatory Tag: @POST<br>[get_diff] Missing Mandatory Tag: @PRE<br>[get_diff] Missing Mandatory Tag: @POST<br>[get_diff] Missing Mandatory Tag: @PRE<br>[get_diff] Missing Mandatory Tag: @POST<br>[get_commit_history] Missing Mandatory Tag: @PRE<br>[get_commit_history] Missing Mandatory Tag: @POST<br>[get_commit_history] Missing Mandatory Tag: @PRE<br>[get_commit_history] Missing Mandatory Tag: @POST<br>[get_commit_history] Missing Mandatory Tag: @PRE<br>[get_commit_history] Missing Mandatory Tag: @POST<br>[test_connection] Missing Mandatory Tag: @PRE<br>[test_connection] Missing Mandatory Tag: @POST<br>[test_connection] Missing Mandatory Tag: @PRE<br>[test_connection] Missing Mandatory Tag: @POST<br>[test_connection] Missing Mandatory Tag: @PRE<br>[test_connection] Missing Mandatory Tag: @POST |
|
||||
| frontend/src/routes/settings/git/+page.svelte | 🟡 73% | [loadConfigs] Missing Mandatory Tag: @PRE<br>[loadConfigs] Missing Mandatory Tag: @POST<br>[loadConfigs] Missing Mandatory Tag: @PRE<br>[loadConfigs] Missing Mandatory Tag: @POST<br>[handleTest] Missing Mandatory Tag: @PRE<br>[handleTest] Missing Mandatory Tag: @POST<br>[handleTest] Missing Mandatory Tag: @PRE<br>[handleTest] Missing Mandatory Tag: @POST<br>[handleSave] Missing Mandatory Tag: @PRE<br>[handleSave] Missing Mandatory Tag: @POST<br>[handleSave] Missing Mandatory Tag: @PRE<br>[handleSave] Missing Mandatory Tag: @POST<br>[handleDelete] Missing Mandatory Tag: @PRE<br>[handleDelete] Missing Mandatory Tag: @POST<br>[handleDelete] Missing Mandatory Tag: @PRE<br>[handleDelete] Missing Mandatory Tag: @POST |
|
||||
| frontend/src/components/git/BranchSelector.svelte | 🟡 75% | [onMount] Missing Mandatory Tag: @PURPOSE<br>[onMount] Missing Mandatory Tag: @PRE<br>[onMount] Missing Mandatory Tag: @POST<br>[onMount] Missing Mandatory Tag: @PURPOSE<br>[onMount] Missing Mandatory Tag: @PRE<br>[onMount] Missing Mandatory Tag: @POST<br>[loadBranches] Missing Mandatory Tag: @PRE<br>[loadBranches] Missing Mandatory Tag: @PRE<br>[handleSelect] Missing Mandatory Tag: @PURPOSE<br>[handleSelect] Missing Mandatory Tag: @PRE<br>[handleSelect] Missing Mandatory Tag: @POST<br>[handleSelect] Missing Mandatory Tag: @PURPOSE<br>[handleSelect] Missing Mandatory Tag: @PRE<br>[handleSelect] Missing Mandatory Tag: @POST<br>[handleCheckout] Missing Mandatory Tag: @PRE<br>[handleCheckout] Missing Mandatory Tag: @PRE<br>[handleCreate] Missing Mandatory Tag: @PRE<br>[handleCreate] Missing Mandatory Tag: @PRE |
|
||||
| backend/src/plugins/git_plugin.py | 🟡 82% | [__init__] Missing Mandatory Tag: @PRE<br>[__init__] Missing Mandatory Tag: @PRE<br>[__init__] Missing Mandatory Tag: @PRE<br>[id] Missing Mandatory Tag: @PRE<br>[id] Missing Mandatory Tag: @POST<br>[id] Missing Belief State Logging: Function should use belief_scope context manager.<br>[id] Missing Mandatory Tag: @PRE<br>[id] Missing Mandatory Tag: @POST<br>[id] Missing Belief State Logging: Function should use belief_scope context manager.<br>[id] Missing Mandatory Tag: @PRE<br>[id] Missing Mandatory Tag: @POST<br>[id] Missing Belief State Logging: Function should use belief_scope context manager.<br>[name] Missing Mandatory Tag: @PRE<br>[name] Missing Mandatory Tag: @POST<br>[name] Missing Belief State Logging: Function should use belief_scope context manager.<br>[name] Missing Mandatory Tag: @PRE<br>[name] Missing Mandatory Tag: @POST<br>[name] Missing Belief State Logging: Function should use belief_scope context manager.<br>[name] Missing Mandatory Tag: @PRE<br>[name] Missing Mandatory Tag: @POST<br>[name] Missing Belief State Logging: Function should use belief_scope context manager.<br>[description] Missing Mandatory Tag: @PRE<br>[description] Missing Mandatory Tag: @POST<br>[description] Missing Belief State Logging: Function should use belief_scope context manager.<br>[description] Missing Mandatory Tag: @PRE<br>[description] Missing Mandatory Tag: @POST<br>[description] Missing Belief State Logging: Function should use belief_scope context manager.<br>[description] Missing Mandatory Tag: @PRE<br>[description] Missing Mandatory Tag: @POST<br>[description] Missing Belief State Logging: Function should use belief_scope context manager.<br>[version] Missing Mandatory Tag: @PRE<br>[version] Missing Mandatory Tag: @POST<br>[version] Missing Belief State Logging: Function should use belief_scope context manager.<br>[version] Missing Mandatory Tag: @PRE<br>[version] Missing Mandatory Tag: @POST<br>[version] Missing Belief State Logging: Function should use belief_scope context manager.<br>[version] Missing Mandatory Tag: @PRE<br>[version] Missing Mandatory Tag: @POST<br>[version] Missing Belief State Logging: Function should use belief_scope context manager.<br>[get_schema] Missing Mandatory Tag: @PRE<br>[get_schema] Missing Mandatory Tag: @POST<br>[get_schema] Missing Mandatory Tag: @PRE<br>[get_schema] Missing Mandatory Tag: @POST<br>[get_schema] Missing Mandatory Tag: @PRE<br>[get_schema] Missing Mandatory Tag: @POST<br>[initialize] Missing Mandatory Tag: @PRE<br>[initialize] Missing Mandatory Tag: @PRE<br>[initialize] Missing Mandatory Tag: @PRE<br>[_get_env] Missing Mandatory Tag: @PRE<br>[_get_env] Missing Mandatory Tag: @POST<br>[_get_env] Missing Mandatory Tag: @PRE<br>[_get_env] Missing Mandatory Tag: @POST<br>[_get_env] Missing Mandatory Tag: @PRE<br>[_get_env] Missing Mandatory Tag: @POST<br>[_get_env] Missing Mandatory Tag: @PRE<br>[_get_env] Missing Mandatory Tag: @POST |
|
||||
| backend/src/models/git.py | 🟡 83% | [GitModels] Missing Mandatory Tag: @SEMANTICS |
|
||||
| frontend/src/components/git/CommitHistory.svelte | 🟡 83% | [onMount] Missing Mandatory Tag: @PRE<br>[onMount] Missing Mandatory Tag: @POST<br>[onMount] Missing Mandatory Tag: @PRE<br>[onMount] Missing Mandatory Tag: @POST<br>[loadHistory] Missing Mandatory Tag: @PRE<br>[loadHistory] Missing Mandatory Tag: @PRE |
|
||||
| backend/src/core/logger.py | 🟡 93% | [format] Missing Belief State Logging: Function should use belief_scope context manager.<br>[format] Missing Belief State Logging: Function should use belief_scope context manager.<br>[format] Missing Belief State Logging: Function should use belief_scope context manager.<br>[belief_scope] Missing Belief State Logging: Function should use belief_scope context manager.<br>[belief_scope] Missing Belief State Logging: Function should use belief_scope context manager.<br>[configure_logger] Missing Belief State Logging: Function should use belief_scope context manager.<br>[configure_logger] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[emit] Missing Belief State Logging: Function should use belief_scope context manager.<br>[emit] Missing Belief State Logging: Function should use belief_scope context manager.<br>[emit] Missing Belief State Logging: Function should use belief_scope context manager.<br>[get_recent_logs] Missing Belief State Logging: Function should use belief_scope context manager.<br>[get_recent_logs] Missing Belief State Logging: Function should use belief_scope context manager.<br>[get_recent_logs] Missing Belief State Logging: Function should use belief_scope context manager.<br>[believed] Missing Mandatory Tag: @PRE<br>[believed] Missing Mandatory Tag: @POST<br>[believed] Missing Belief State Logging: Function should use belief_scope context manager.<br>[believed] Missing Mandatory Tag: @PRE<br>[believed] Missing Mandatory Tag: @POST<br>[believed] Missing Belief State Logging: Function should use belief_scope context manager.<br>[believed] Missing Mandatory Tag: @PRE<br>[believed] Missing Mandatory Tag: @POST<br>[believed] Missing Belief State Logging: Function should use belief_scope context manager.<br>[decorator] Missing Mandatory Tag: @PRE<br>[decorator] Missing Mandatory Tag: @POST<br>[decorator] Missing Belief State Logging: Function should use belief_scope context manager.<br>[decorator] Missing Mandatory Tag: @PRE<br>[decorator] Missing Mandatory Tag: @POST<br>[decorator] Missing Belief State Logging: Function should use belief_scope context manager.<br>[decorator] Missing Mandatory Tag: @PRE<br>[decorator] Missing Mandatory Tag: @POST<br>[decorator] Missing Belief State Logging: Function should use belief_scope context manager.<br>[decorator] Missing Mandatory Tag: @PRE<br>[decorator] Missing Mandatory Tag: @POST<br>[decorator] Missing Belief State Logging: Function should use belief_scope context manager.<br>[wrapper] Missing Mandatory Tag: @PRE<br>[wrapper] Missing Mandatory Tag: @POST<br>[wrapper] Missing Mandatory Tag: @PRE<br>[wrapper] Missing Mandatory Tag: @POST<br>[wrapper] Missing Mandatory Tag: @PRE<br>[wrapper] Missing Mandatory Tag: @POST<br>[wrapper] Missing Mandatory Tag: @PRE<br>[wrapper] Missing Mandatory Tag: @POST<br>[wrapper] Missing Mandatory Tag: @PRE<br>[wrapper] Missing Mandatory Tag: @POST |
|
||||
| frontend/src/components/git/CommitModal.svelte | 🟡 94% | [loadStatus] Missing Mandatory Tag: @POST<br>[loadStatus] Missing Mandatory Tag: @POST |
|
||||
| frontend/src/components/DashboardGrid.svelte | 🟡 94% | [openGit] Missing Mandatory Tag: @PRE<br>[openGit] Missing Mandatory Tag: @POST<br>[openGit] Missing Mandatory Tag: @PRE<br>[openGit] Missing Mandatory Tag: @POST |
|
||||
| frontend/src/components/git/DeploymentModal.svelte | 🟡 96% | [loadEnvironments] Missing Mandatory Tag: @PRE<br>[loadEnvironments] Missing Mandatory Tag: @PRE |
|
||||
| backend/src/core/utils/dataset_mapper.py | 🟡 97% | [__init__] Missing Mandatory Tag: @PRE<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Mandatory Tag: @PRE<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Mandatory Tag: @PRE<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| generate_semantic_map.py | 🟢 100% | [__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__enter__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__enter__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__exit__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__exit__] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| frontend/src/components/TaskHistory.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/MappingTable.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/EnvSelector.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/TaskList.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/DynamicForm.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/Footer.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/Navbar.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/TaskRunner.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/TaskLogViewer.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/PasswordPrompt.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/MissingMappingModal.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/Toast.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/git/ConflictResolver.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/tools/DebugTool.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/tools/ConnectionForm.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/tools/MapperTool.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/tools/SearchTool.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/tools/ConnectionList.svelte | 🟢 100% | OK |
|
||||
| frontend/src/pages/Settings.svelte | 🟢 100% | OK |
|
||||
| frontend/src/pages/Dashboard.svelte | 🟢 100% | OK |
|
||||
| frontend/src/services/gitService.js | 🟢 100% | OK |
|
||||
| frontend/src/services/connectionService.js | 🟢 100% | OK |
|
||||
| frontend/src/services/taskService.js | 🟢 100% | OK |
|
||||
| frontend/src/services/toolsService.js | 🟢 100% | OK |
|
||||
| frontend/src/lib/stores.js | 🟢 100% | OK |
|
||||
| frontend/src/lib/toasts.js | 🟢 100% | OK |
|
||||
| frontend/src/lib/api.js | 🟢 100% | OK |
|
||||
| frontend/src/routes/+page.ts | 🟢 100% | OK |
|
||||
| frontend/src/routes/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/tasks/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/git/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/settings/+page.ts | 🟢 100% | OK |
|
||||
| frontend/src/routes/settings/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/settings/connections/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/tools/mapper/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/tools/debug/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/tools/search/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/migration/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/migration/mappings/+page.svelte | 🟢 100% | OK |
|
||||
| backend/delete_running_tasks.py | 🟢 100% | [delete_running_tasks] Missing Belief State Logging: Function should use belief_scope context manager.<br>[delete_running_tasks] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/dependencies.py | 🟢 100% | OK |
|
||||
| backend/src/app.py | 🟢 100% | OK |
|
||||
| backend/src/models/mapping.py | 🟢 100% | OK |
|
||||
| backend/src/models/dashboard.py | 🟢 100% | OK |
|
||||
| backend/src/models/task.py | 🟢 100% | OK |
|
||||
| backend/src/models/connection.py | 🟢 100% | OK |
|
||||
| backend/src/services/mapping_service.py | 🟢 100% | OK |
|
||||
| backend/src/core/config_manager.py | 🟢 100% | OK |
|
||||
| backend/src/core/superset_client.py | 🟢 100% | OK |
|
||||
| backend/src/core/migration_engine.py | 🟢 100% | [_transform_yaml] Missing Belief State Logging: Function should use belief_scope context manager.<br>[_transform_yaml] Missing Belief State Logging: Function should use belief_scope context manager.<br>[_transform_yaml] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/core/database.py | 🟢 100% | OK |
|
||||
| backend/src/core/config_models.py | 🟢 100% | OK |
|
||||
| backend/src/core/scheduler.py | 🟢 100% | OK |
|
||||
| backend/src/core/plugin_loader.py | 🟢 100% | OK |
|
||||
| backend/src/core/plugin_base.py | 🟢 100% | OK |
|
||||
| backend/src/core/utils/matching.py | 🟢 100% | [suggest_mappings] Missing Belief State Logging: Function should use belief_scope context manager.<br>[suggest_mappings] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/core/utils/network.py | 🟢 100% | OK |
|
||||
| backend/src/core/utils/fileio.py | 🟢 100% | [replacer] Missing Belief State Logging: Function should use belief_scope context manager.<br>[replacer] Missing Belief State Logging: Function should use belief_scope context manager.<br>[replacer] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/core/task_manager/manager.py | 🟢 100% | OK |
|
||||
| backend/src/core/task_manager/__init__.py | 🟢 100% | OK |
|
||||
| backend/src/core/task_manager/cleanup.py | 🟢 100% | [__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/core/task_manager/models.py | 🟢 100% | [__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/core/task_manager/persistence.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/search.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/backup.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/debug.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/migration.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/mapper.py | 🟢 100% | OK |
|
||||
| backend/src/api/auth.py | 🟢 100% | [get_current_user] Missing Belief State Logging: Function should use belief_scope context manager.<br>[get_current_user] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/api/routes/git_schemas.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/settings.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/connections.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/tasks.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/git.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/environments.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/plugins.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/migration.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/mappings.py | 🟢 100% | OK |
|
||||
| backend/tests/test_models.py | 🟢 100% | OK |
|
||||
| backend/tests/test_logger.py | 🟢 100% | OK |
|
||||
102
semantics/reports/semantic_report_20260123_215507.md
Normal file
102
semantics/reports/semantic_report_20260123_215507.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# Semantic Compliance Report
|
||||
|
||||
**Generated At:** 2026-01-23T21:55:07.969831
|
||||
**Global Compliance Score:** 98.6%
|
||||
**Scanned Files:** 93
|
||||
|
||||
## File Compliance Status
|
||||
| File | Score | Issues |
|
||||
|------|-------|--------|
|
||||
| frontend/src/components/git/GitManager.svelte | 🟡 67% | [checkStatus] Missing Mandatory Tag: @PRE<br>[checkStatus] Missing Mandatory Tag: @POST<br>[checkStatus] Missing Mandatory Tag: @PRE<br>[checkStatus] Missing Mandatory Tag: @POST<br>[handleInit] Missing Mandatory Tag: @PRE<br>[handleInit] Missing Mandatory Tag: @POST<br>[handleInit] Missing Mandatory Tag: @PRE<br>[handleInit] Missing Mandatory Tag: @POST<br>[handleSync] Missing Mandatory Tag: @PRE<br>[handleSync] Missing Mandatory Tag: @POST<br>[handleSync] Missing Mandatory Tag: @PRE<br>[handleSync] Missing Mandatory Tag: @POST<br>[handlePush] Missing Mandatory Tag: @PURPOSE<br>[handlePush] Missing Mandatory Tag: @PRE<br>[handlePush] Missing Mandatory Tag: @POST<br>[handlePush] Missing Mandatory Tag: @PURPOSE<br>[handlePush] Missing Mandatory Tag: @PRE<br>[handlePush] Missing Mandatory Tag: @POST<br>[handlePull] Missing Mandatory Tag: @PURPOSE<br>[handlePull] Missing Mandatory Tag: @PRE<br>[handlePull] Missing Mandatory Tag: @POST<br>[handlePull] Missing Mandatory Tag: @PURPOSE<br>[handlePull] Missing Mandatory Tag: @PRE<br>[handlePull] Missing Mandatory Tag: @POST |
|
||||
| frontend/src/routes/settings/git/+page.svelte | 🟡 73% | [loadConfigs] Missing Mandatory Tag: @PRE<br>[loadConfigs] Missing Mandatory Tag: @POST<br>[loadConfigs] Missing Mandatory Tag: @PRE<br>[loadConfigs] Missing Mandatory Tag: @POST<br>[handleTest] Missing Mandatory Tag: @PRE<br>[handleTest] Missing Mandatory Tag: @POST<br>[handleTest] Missing Mandatory Tag: @PRE<br>[handleTest] Missing Mandatory Tag: @POST<br>[handleSave] Missing Mandatory Tag: @PRE<br>[handleSave] Missing Mandatory Tag: @POST<br>[handleSave] Missing Mandatory Tag: @PRE<br>[handleSave] Missing Mandatory Tag: @POST<br>[handleDelete] Missing Mandatory Tag: @PRE<br>[handleDelete] Missing Mandatory Tag: @POST<br>[handleDelete] Missing Mandatory Tag: @PRE<br>[handleDelete] Missing Mandatory Tag: @POST |
|
||||
| frontend/src/components/git/BranchSelector.svelte | 🟡 75% | [onMount] Missing Mandatory Tag: @PURPOSE<br>[onMount] Missing Mandatory Tag: @PRE<br>[onMount] Missing Mandatory Tag: @POST<br>[onMount] Missing Mandatory Tag: @PURPOSE<br>[onMount] Missing Mandatory Tag: @PRE<br>[onMount] Missing Mandatory Tag: @POST<br>[loadBranches] Missing Mandatory Tag: @PRE<br>[loadBranches] Missing Mandatory Tag: @PRE<br>[handleSelect] Missing Mandatory Tag: @PURPOSE<br>[handleSelect] Missing Mandatory Tag: @PRE<br>[handleSelect] Missing Mandatory Tag: @POST<br>[handleSelect] Missing Mandatory Tag: @PURPOSE<br>[handleSelect] Missing Mandatory Tag: @PRE<br>[handleSelect] Missing Mandatory Tag: @POST<br>[handleCheckout] Missing Mandatory Tag: @PRE<br>[handleCheckout] Missing Mandatory Tag: @PRE<br>[handleCreate] Missing Mandatory Tag: @PRE<br>[handleCreate] Missing Mandatory Tag: @PRE |
|
||||
| frontend/src/components/git/CommitHistory.svelte | 🟡 83% | [onMount] Missing Mandatory Tag: @PRE<br>[onMount] Missing Mandatory Tag: @POST<br>[onMount] Missing Mandatory Tag: @PRE<br>[onMount] Missing Mandatory Tag: @POST<br>[loadHistory] Missing Mandatory Tag: @PRE<br>[loadHistory] Missing Mandatory Tag: @PRE |
|
||||
| backend/src/core/logger.py | 🟡 93% | [format] Missing Belief State Logging: Function should use belief_scope context manager.<br>[format] Missing Belief State Logging: Function should use belief_scope context manager.<br>[format] Missing Belief State Logging: Function should use belief_scope context manager.<br>[belief_scope] Missing Belief State Logging: Function should use belief_scope context manager.<br>[belief_scope] Missing Belief State Logging: Function should use belief_scope context manager.<br>[configure_logger] Missing Belief State Logging: Function should use belief_scope context manager.<br>[configure_logger] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[emit] Missing Belief State Logging: Function should use belief_scope context manager.<br>[emit] Missing Belief State Logging: Function should use belief_scope context manager.<br>[emit] Missing Belief State Logging: Function should use belief_scope context manager.<br>[get_recent_logs] Missing Belief State Logging: Function should use belief_scope context manager.<br>[get_recent_logs] Missing Belief State Logging: Function should use belief_scope context manager.<br>[get_recent_logs] Missing Belief State Logging: Function should use belief_scope context manager.<br>[believed] Missing Mandatory Tag: @PRE<br>[believed] Missing Mandatory Tag: @POST<br>[believed] Missing Belief State Logging: Function should use belief_scope context manager.<br>[believed] Missing Mandatory Tag: @PRE<br>[believed] Missing Mandatory Tag: @POST<br>[believed] Missing Belief State Logging: Function should use belief_scope context manager.<br>[believed] Missing Mandatory Tag: @PRE<br>[believed] Missing Mandatory Tag: @POST<br>[believed] Missing Belief State Logging: Function should use belief_scope context manager.<br>[decorator] Missing Mandatory Tag: @PRE<br>[decorator] Missing Mandatory Tag: @POST<br>[decorator] Missing Belief State Logging: Function should use belief_scope context manager.<br>[decorator] Missing Mandatory Tag: @PRE<br>[decorator] Missing Mandatory Tag: @POST<br>[decorator] Missing Belief State Logging: Function should use belief_scope context manager.<br>[decorator] Missing Mandatory Tag: @PRE<br>[decorator] Missing Mandatory Tag: @POST<br>[decorator] Missing Belief State Logging: Function should use belief_scope context manager.<br>[decorator] Missing Mandatory Tag: @PRE<br>[decorator] Missing Mandatory Tag: @POST<br>[decorator] Missing Belief State Logging: Function should use belief_scope context manager.<br>[wrapper] Missing Mandatory Tag: @PRE<br>[wrapper] Missing Mandatory Tag: @POST<br>[wrapper] Missing Mandatory Tag: @PRE<br>[wrapper] Missing Mandatory Tag: @POST<br>[wrapper] Missing Mandatory Tag: @PRE<br>[wrapper] Missing Mandatory Tag: @POST<br>[wrapper] Missing Mandatory Tag: @PRE<br>[wrapper] Missing Mandatory Tag: @POST<br>[wrapper] Missing Mandatory Tag: @PRE<br>[wrapper] Missing Mandatory Tag: @POST |
|
||||
| frontend/src/components/git/CommitModal.svelte | 🟡 94% | [loadStatus] Missing Mandatory Tag: @POST<br>[loadStatus] Missing Mandatory Tag: @POST |
|
||||
| frontend/src/components/DashboardGrid.svelte | 🟡 94% | [openGit] Missing Mandatory Tag: @PRE<br>[openGit] Missing Mandatory Tag: @POST<br>[openGit] Missing Mandatory Tag: @PRE<br>[openGit] Missing Mandatory Tag: @POST |
|
||||
| frontend/src/components/git/DeploymentModal.svelte | 🟡 96% | [loadEnvironments] Missing Mandatory Tag: @PRE<br>[loadEnvironments] Missing Mandatory Tag: @PRE |
|
||||
| backend/src/core/utils/dataset_mapper.py | 🟡 97% | [__init__] Missing Mandatory Tag: @PRE<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Mandatory Tag: @PRE<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Mandatory Tag: @PRE<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| generate_semantic_map.py | 🟢 100% | [__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__enter__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__enter__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__exit__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__exit__] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| frontend/src/components/TaskHistory.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/MappingTable.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/EnvSelector.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/TaskList.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/DynamicForm.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/Footer.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/Navbar.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/TaskRunner.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/TaskLogViewer.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/PasswordPrompt.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/MissingMappingModal.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/Toast.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/git/ConflictResolver.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/tools/DebugTool.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/tools/ConnectionForm.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/tools/MapperTool.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/tools/SearchTool.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/tools/ConnectionList.svelte | 🟢 100% | OK |
|
||||
| frontend/src/pages/Settings.svelte | 🟢 100% | OK |
|
||||
| frontend/src/pages/Dashboard.svelte | 🟢 100% | OK |
|
||||
| frontend/src/services/gitService.js | 🟢 100% | OK |
|
||||
| frontend/src/services/connectionService.js | 🟢 100% | OK |
|
||||
| frontend/src/services/taskService.js | 🟢 100% | OK |
|
||||
| frontend/src/services/toolsService.js | 🟢 100% | OK |
|
||||
| frontend/src/lib/stores.js | 🟢 100% | OK |
|
||||
| frontend/src/lib/toasts.js | 🟢 100% | OK |
|
||||
| frontend/src/lib/api.js | 🟢 100% | OK |
|
||||
| frontend/src/routes/+page.ts | 🟢 100% | OK |
|
||||
| frontend/src/routes/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/tasks/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/git/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/settings/+page.ts | 🟢 100% | OK |
|
||||
| frontend/src/routes/settings/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/settings/connections/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/tools/mapper/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/tools/debug/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/tools/search/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/migration/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/migration/mappings/+page.svelte | 🟢 100% | OK |
|
||||
| backend/delete_running_tasks.py | 🟢 100% | [delete_running_tasks] Missing Belief State Logging: Function should use belief_scope context manager.<br>[delete_running_tasks] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/dependencies.py | 🟢 100% | OK |
|
||||
| backend/src/app.py | 🟢 100% | OK |
|
||||
| backend/src/models/mapping.py | 🟢 100% | OK |
|
||||
| backend/src/models/git.py | 🟢 100% | OK |
|
||||
| backend/src/models/dashboard.py | 🟢 100% | OK |
|
||||
| backend/src/models/task.py | 🟢 100% | OK |
|
||||
| backend/src/models/connection.py | 🟢 100% | OK |
|
||||
| backend/src/services/mapping_service.py | 🟢 100% | OK |
|
||||
| backend/src/services/git_service.py | 🟢 100% | OK |
|
||||
| backend/src/core/config_manager.py | 🟢 100% | OK |
|
||||
| backend/src/core/superset_client.py | 🟢 100% | OK |
|
||||
| backend/src/core/migration_engine.py | 🟢 100% | [_transform_yaml] Missing Belief State Logging: Function should use belief_scope context manager.<br>[_transform_yaml] Missing Belief State Logging: Function should use belief_scope context manager.<br>[_transform_yaml] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/core/database.py | 🟢 100% | OK |
|
||||
| backend/src/core/config_models.py | 🟢 100% | OK |
|
||||
| backend/src/core/scheduler.py | 🟢 100% | OK |
|
||||
| backend/src/core/plugin_loader.py | 🟢 100% | OK |
|
||||
| backend/src/core/plugin_base.py | 🟢 100% | OK |
|
||||
| backend/src/core/utils/matching.py | 🟢 100% | [suggest_mappings] Missing Belief State Logging: Function should use belief_scope context manager.<br>[suggest_mappings] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/core/utils/network.py | 🟢 100% | OK |
|
||||
| backend/src/core/utils/fileio.py | 🟢 100% | [replacer] Missing Belief State Logging: Function should use belief_scope context manager.<br>[replacer] Missing Belief State Logging: Function should use belief_scope context manager.<br>[replacer] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/core/task_manager/manager.py | 🟢 100% | OK |
|
||||
| backend/src/core/task_manager/__init__.py | 🟢 100% | OK |
|
||||
| backend/src/core/task_manager/cleanup.py | 🟢 100% | [__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/core/task_manager/models.py | 🟢 100% | [__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/core/task_manager/persistence.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/search.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/backup.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/debug.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/migration.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/git_plugin.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/mapper.py | 🟢 100% | OK |
|
||||
| backend/src/api/auth.py | 🟢 100% | [get_current_user] Missing Belief State Logging: Function should use belief_scope context manager.<br>[get_current_user] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/api/routes/git_schemas.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/settings.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/connections.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/tasks.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/git.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/environments.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/plugins.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/migration.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/mappings.py | 🟢 100% | OK |
|
||||
| backend/tests/test_models.py | 🟢 100% | OK |
|
||||
| backend/tests/test_logger.py | 🟢 100% | OK |
|
||||
102
semantics/reports/semantic_report_20260123_215737.md
Normal file
102
semantics/reports/semantic_report_20260123_215737.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# Semantic Compliance Report
|
||||
|
||||
**Generated At:** 2026-01-23T21:57:37.610032
|
||||
**Global Compliance Score:** 99.7%
|
||||
**Scanned Files:** 93
|
||||
|
||||
## File Compliance Status
|
||||
| File | Score | Issues |
|
||||
|------|-------|--------|
|
||||
| backend/src/core/logger.py | 🟡 93% | [format] Missing Belief State Logging: Function should use belief_scope context manager.<br>[format] Missing Belief State Logging: Function should use belief_scope context manager.<br>[format] Missing Belief State Logging: Function should use belief_scope context manager.<br>[belief_scope] Missing Belief State Logging: Function should use belief_scope context manager.<br>[belief_scope] Missing Belief State Logging: Function should use belief_scope context manager.<br>[configure_logger] Missing Belief State Logging: Function should use belief_scope context manager.<br>[configure_logger] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[emit] Missing Belief State Logging: Function should use belief_scope context manager.<br>[emit] Missing Belief State Logging: Function should use belief_scope context manager.<br>[emit] Missing Belief State Logging: Function should use belief_scope context manager.<br>[get_recent_logs] Missing Belief State Logging: Function should use belief_scope context manager.<br>[get_recent_logs] Missing Belief State Logging: Function should use belief_scope context manager.<br>[get_recent_logs] Missing Belief State Logging: Function should use belief_scope context manager.<br>[believed] Missing Mandatory Tag: @PRE<br>[believed] Missing Mandatory Tag: @POST<br>[believed] Missing Belief State Logging: Function should use belief_scope context manager.<br>[believed] Missing Mandatory Tag: @PRE<br>[believed] Missing Mandatory Tag: @POST<br>[believed] Missing Belief State Logging: Function should use belief_scope context manager.<br>[believed] Missing Mandatory Tag: @PRE<br>[believed] Missing Mandatory Tag: @POST<br>[believed] Missing Belief State Logging: Function should use belief_scope context manager.<br>[decorator] Missing Mandatory Tag: @PRE<br>[decorator] Missing Mandatory Tag: @POST<br>[decorator] Missing Belief State Logging: Function should use belief_scope context manager.<br>[decorator] Missing Mandatory Tag: @PRE<br>[decorator] Missing Mandatory Tag: @POST<br>[decorator] Missing Belief State Logging: Function should use belief_scope context manager.<br>[decorator] Missing Mandatory Tag: @PRE<br>[decorator] Missing Mandatory Tag: @POST<br>[decorator] Missing Belief State Logging: Function should use belief_scope context manager.<br>[decorator] Missing Mandatory Tag: @PRE<br>[decorator] Missing Mandatory Tag: @POST<br>[decorator] Missing Belief State Logging: Function should use belief_scope context manager.<br>[wrapper] Missing Mandatory Tag: @PRE<br>[wrapper] Missing Mandatory Tag: @POST<br>[wrapper] Missing Mandatory Tag: @PRE<br>[wrapper] Missing Mandatory Tag: @POST<br>[wrapper] Missing Mandatory Tag: @PRE<br>[wrapper] Missing Mandatory Tag: @POST<br>[wrapper] Missing Mandatory Tag: @PRE<br>[wrapper] Missing Mandatory Tag: @POST<br>[wrapper] Missing Mandatory Tag: @PRE<br>[wrapper] Missing Mandatory Tag: @POST |
|
||||
| frontend/src/components/git/CommitModal.svelte | 🟡 94% | [loadStatus] Missing Mandatory Tag: @POST<br>[loadStatus] Missing Mandatory Tag: @POST |
|
||||
| frontend/src/components/DashboardGrid.svelte | 🟡 94% | [openGit] Missing Mandatory Tag: @PRE<br>[openGit] Missing Mandatory Tag: @POST<br>[openGit] Missing Mandatory Tag: @PRE<br>[openGit] Missing Mandatory Tag: @POST |
|
||||
| frontend/src/components/git/DeploymentModal.svelte | 🟡 96% | [loadEnvironments] Missing Mandatory Tag: @PRE<br>[loadEnvironments] Missing Mandatory Tag: @PRE |
|
||||
| frontend/src/components/git/BranchSelector.svelte | 🟡 97% | [handleCheckout] Missing Mandatory Tag: @PRE<br>[handleCheckout] Missing Mandatory Tag: @PRE |
|
||||
| backend/src/core/utils/dataset_mapper.py | 🟡 97% | [__init__] Missing Mandatory Tag: @PRE<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Mandatory Tag: @PRE<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Mandatory Tag: @PRE<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| generate_semantic_map.py | 🟢 100% | [__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__enter__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__enter__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__exit__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__exit__] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| frontend/src/components/TaskHistory.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/MappingTable.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/EnvSelector.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/TaskList.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/DynamicForm.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/Footer.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/Navbar.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/TaskRunner.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/TaskLogViewer.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/PasswordPrompt.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/MissingMappingModal.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/Toast.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/git/GitManager.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/git/ConflictResolver.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/git/CommitHistory.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/tools/DebugTool.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/tools/ConnectionForm.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/tools/MapperTool.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/tools/SearchTool.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/tools/ConnectionList.svelte | 🟢 100% | OK |
|
||||
| frontend/src/pages/Settings.svelte | 🟢 100% | OK |
|
||||
| frontend/src/pages/Dashboard.svelte | 🟢 100% | OK |
|
||||
| frontend/src/services/gitService.js | 🟢 100% | OK |
|
||||
| frontend/src/services/connectionService.js | 🟢 100% | OK |
|
||||
| frontend/src/services/taskService.js | 🟢 100% | OK |
|
||||
| frontend/src/services/toolsService.js | 🟢 100% | OK |
|
||||
| frontend/src/lib/stores.js | 🟢 100% | OK |
|
||||
| frontend/src/lib/toasts.js | 🟢 100% | OK |
|
||||
| frontend/src/lib/api.js | 🟢 100% | OK |
|
||||
| frontend/src/routes/+page.ts | 🟢 100% | OK |
|
||||
| frontend/src/routes/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/tasks/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/git/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/settings/+page.ts | 🟢 100% | OK |
|
||||
| frontend/src/routes/settings/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/settings/git/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/settings/connections/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/tools/mapper/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/tools/debug/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/tools/search/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/migration/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/migration/mappings/+page.svelte | 🟢 100% | OK |
|
||||
| backend/delete_running_tasks.py | 🟢 100% | [delete_running_tasks] Missing Belief State Logging: Function should use belief_scope context manager.<br>[delete_running_tasks] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/dependencies.py | 🟢 100% | OK |
|
||||
| backend/src/app.py | 🟢 100% | OK |
|
||||
| backend/src/models/mapping.py | 🟢 100% | OK |
|
||||
| backend/src/models/git.py | 🟢 100% | OK |
|
||||
| backend/src/models/dashboard.py | 🟢 100% | OK |
|
||||
| backend/src/models/task.py | 🟢 100% | OK |
|
||||
| backend/src/models/connection.py | 🟢 100% | OK |
|
||||
| backend/src/services/mapping_service.py | 🟢 100% | OK |
|
||||
| backend/src/services/git_service.py | 🟢 100% | OK |
|
||||
| backend/src/core/config_manager.py | 🟢 100% | OK |
|
||||
| backend/src/core/superset_client.py | 🟢 100% | OK |
|
||||
| backend/src/core/migration_engine.py | 🟢 100% | [_transform_yaml] Missing Belief State Logging: Function should use belief_scope context manager.<br>[_transform_yaml] Missing Belief State Logging: Function should use belief_scope context manager.<br>[_transform_yaml] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/core/database.py | 🟢 100% | OK |
|
||||
| backend/src/core/config_models.py | 🟢 100% | OK |
|
||||
| backend/src/core/scheduler.py | 🟢 100% | OK |
|
||||
| backend/src/core/plugin_loader.py | 🟢 100% | OK |
|
||||
| backend/src/core/plugin_base.py | 🟢 100% | OK |
|
||||
| backend/src/core/utils/matching.py | 🟢 100% | [suggest_mappings] Missing Belief State Logging: Function should use belief_scope context manager.<br>[suggest_mappings] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/core/utils/network.py | 🟢 100% | OK |
|
||||
| backend/src/core/utils/fileio.py | 🟢 100% | [replacer] Missing Belief State Logging: Function should use belief_scope context manager.<br>[replacer] Missing Belief State Logging: Function should use belief_scope context manager.<br>[replacer] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/core/task_manager/manager.py | 🟢 100% | OK |
|
||||
| backend/src/core/task_manager/__init__.py | 🟢 100% | OK |
|
||||
| backend/src/core/task_manager/cleanup.py | 🟢 100% | [__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/core/task_manager/models.py | 🟢 100% | [__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/core/task_manager/persistence.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/search.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/backup.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/debug.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/migration.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/git_plugin.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/mapper.py | 🟢 100% | OK |
|
||||
| backend/src/api/auth.py | 🟢 100% | [get_current_user] Missing Belief State Logging: Function should use belief_scope context manager.<br>[get_current_user] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/api/routes/git_schemas.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/settings.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/connections.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/tasks.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/git.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/environments.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/plugins.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/migration.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/mappings.py | 🟢 100% | OK |
|
||||
| backend/tests/test_models.py | 🟢 100% | OK |
|
||||
| backend/tests/test_logger.py | 🟢 100% | OK |
|
||||
124
semantics/reports/semantic_report_20260126_112020.md
Normal file
124
semantics/reports/semantic_report_20260126_112020.md
Normal file
File diff suppressed because one or more lines are too long
117
semantics/reports/semantic_report_20260126_114128.md
Normal file
117
semantics/reports/semantic_report_20260126_114128.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# Semantic Compliance Report
|
||||
|
||||
**Generated At:** 2026-01-26T11:41:28.355350
|
||||
**Global Compliance Score:** 99.2%
|
||||
**Scanned Files:** 108
|
||||
|
||||
## File Compliance Status
|
||||
| File | Score | Issues |
|
||||
|------|-------|--------|
|
||||
| frontend/src/components/storage/FileList.svelte | 🟡 75% | [isDirectory] Missing Mandatory Tag: @PRE<br>[isDirectory] Missing Mandatory Tag: @POST<br>[isDirectory] Missing Mandatory Tag: @PRE<br>[isDirectory] Missing Mandatory Tag: @POST<br>[formatSize] Missing Mandatory Tag: @PRE<br>[formatSize] Missing Mandatory Tag: @POST<br>[formatSize] Missing Mandatory Tag: @PRE<br>[formatSize] Missing Mandatory Tag: @POST<br>[formatDate] Missing Mandatory Tag: @PRE<br>[formatDate] Missing Mandatory Tag: @POST<br>[formatDate] Missing Mandatory Tag: @PRE<br>[formatDate] Missing Mandatory Tag: @POST |
|
||||
| frontend/src/routes/tools/storage/+page.svelte | 🟡 77% | [loadFiles] Missing Mandatory Tag: @PRE<br>[loadFiles] Missing Mandatory Tag: @PRE<br>[handleDelete] Missing Mandatory Tag: @PRE<br>[handleDelete] Missing Mandatory Tag: @POST<br>[handleDelete] Missing Mandatory Tag: @PRE<br>[handleDelete] Missing Mandatory Tag: @POST<br>[handleNavigate] Missing Mandatory Tag: @PRE<br>[handleNavigate] Missing Mandatory Tag: @POST<br>[handleNavigate] Missing Mandatory Tag: @PRE<br>[handleNavigate] Missing Mandatory Tag: @POST<br>[navigateUp] Missing Mandatory Tag: @PRE<br>[navigateUp] Missing Mandatory Tag: @POST<br>[navigateUp] Missing Mandatory Tag: @PRE<br>[navigateUp] Missing Mandatory Tag: @POST |
|
||||
| frontend/src/components/storage/FileUpload.svelte | 🟡 89% | [handleDrop] Missing Mandatory Tag: @PRE<br>[handleDrop] Missing Mandatory Tag: @POST<br>[handleDrop] Missing Mandatory Tag: @PRE<br>[handleDrop] Missing Mandatory Tag: @POST |
|
||||
| frontend/src/components/git/CommitModal.svelte | 🟡 94% | [loadStatus] Missing Mandatory Tag: @POST<br>[loadStatus] Missing Mandatory Tag: @POST |
|
||||
| frontend/src/components/DashboardGrid.svelte | 🟡 94% | [openGit] Missing Mandatory Tag: @PRE<br>[openGit] Missing Mandatory Tag: @POST<br>[openGit] Missing Mandatory Tag: @PRE<br>[openGit] Missing Mandatory Tag: @POST |
|
||||
| backend/src/api/routes/settings.py | 🟡 95% | [get_storage_settings] Missing Mandatory Tag: @PRE<br>[get_storage_settings] Missing Mandatory Tag: @POST<br>[get_storage_settings] Missing Mandatory Tag: @PRE<br>[get_storage_settings] Missing Mandatory Tag: @POST<br>[update_storage_settings] Missing Mandatory Tag: @PRE<br>[update_storage_settings] Missing Mandatory Tag: @PRE |
|
||||
| frontend/src/components/git/DeploymentModal.svelte | 🟡 96% | [loadEnvironments] Missing Mandatory Tag: @PRE<br>[loadEnvironments] Missing Mandatory Tag: @PRE |
|
||||
| frontend/src/components/git/BranchSelector.svelte | 🟡 97% | [handleCheckout] Missing Mandatory Tag: @PRE<br>[handleCheckout] Missing Mandatory Tag: @PRE |
|
||||
| backend/src/core/utils/dataset_mapper.py | 🟡 97% | [__init__] Missing Mandatory Tag: @PRE<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Mandatory Tag: @PRE<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Mandatory Tag: @PRE<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| generate_semantic_map.py | 🟢 100% | [__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__enter__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__enter__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__exit__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__exit__] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| frontend/src/lib/stores.js | 🟢 100% | OK |
|
||||
| frontend/src/lib/toasts.js | 🟢 100% | OK |
|
||||
| frontend/src/lib/api.js | 🟢 100% | OK |
|
||||
| frontend/src/lib/ui/Select.svelte | 🟢 100% | OK |
|
||||
| frontend/src/lib/ui/index.ts | 🟢 100% | OK |
|
||||
| frontend/src/lib/ui/PageHeader.svelte | 🟢 100% | OK |
|
||||
| frontend/src/lib/ui/Card.svelte | 🟢 100% | OK |
|
||||
| frontend/src/lib/ui/Button.svelte | 🟢 100% | OK |
|
||||
| frontend/src/lib/ui/Input.svelte | 🟢 100% | OK |
|
||||
| frontend/src/lib/ui/LanguageSwitcher.svelte | 🟢 100% | OK |
|
||||
| frontend/src/lib/i18n/index.ts | 🟢 100% | OK |
|
||||
| frontend/src/routes/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/+page.ts | 🟢 100% | OK |
|
||||
| frontend/src/routes/tasks/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/migration/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/migration/mappings/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/tools/search/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/tools/mapper/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/tools/debug/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/settings/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/settings/+page.ts | 🟢 100% | OK |
|
||||
| frontend/src/routes/settings/connections/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/settings/git/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/routes/git/+page.svelte | 🟢 100% | OK |
|
||||
| frontend/src/pages/Dashboard.svelte | 🟢 100% | OK |
|
||||
| frontend/src/pages/Settings.svelte | 🟢 100% | OK |
|
||||
| frontend/src/services/connectionService.js | 🟢 100% | OK |
|
||||
| frontend/src/services/gitService.js | 🟢 100% | OK |
|
||||
| frontend/src/services/toolsService.js | 🟢 100% | OK |
|
||||
| frontend/src/services/taskService.js | 🟢 100% | OK |
|
||||
| frontend/src/services/storageService.js | 🟢 100% | OK |
|
||||
| frontend/src/components/PasswordPrompt.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/MappingTable.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/TaskLogViewer.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/Footer.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/MissingMappingModal.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/Navbar.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/TaskHistory.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/Toast.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/TaskRunner.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/TaskList.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/DynamicForm.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/EnvSelector.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/tools/ConnectionForm.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/tools/ConnectionList.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/tools/MapperTool.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/tools/DebugTool.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/tools/SearchTool.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/git/CommitHistory.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/git/ConflictResolver.svelte | 🟢 100% | OK |
|
||||
| frontend/src/components/git/GitManager.svelte | 🟢 100% | OK |
|
||||
| backend/delete_running_tasks.py | 🟢 100% | [delete_running_tasks] Missing Belief State Logging: Function should use belief_scope context manager.<br>[delete_running_tasks] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/app.py | 🟢 100% | OK |
|
||||
| backend/src/dependencies.py | 🟢 100% | OK |
|
||||
| backend/src/core/superset_client.py | 🟢 100% | OK |
|
||||
| backend/src/core/config_manager.py | 🟢 100% | OK |
|
||||
| backend/src/core/scheduler.py | 🟢 100% | OK |
|
||||
| backend/src/core/config_models.py | 🟢 100% | OK |
|
||||
| backend/src/core/database.py | 🟢 100% | OK |
|
||||
| backend/src/core/logger.py | 🟢 100% | OK |
|
||||
| backend/src/core/plugin_loader.py | 🟢 100% | OK |
|
||||
| backend/src/core/migration_engine.py | 🟢 100% | [_transform_yaml] Missing Belief State Logging: Function should use belief_scope context manager.<br>[_transform_yaml] Missing Belief State Logging: Function should use belief_scope context manager.<br>[_transform_yaml] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/core/plugin_base.py | 🟢 100% | OK |
|
||||
| backend/src/core/utils/fileio.py | 🟢 100% | [replacer] Missing Belief State Logging: Function should use belief_scope context manager.<br>[replacer] Missing Belief State Logging: Function should use belief_scope context manager.<br>[replacer] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/core/utils/network.py | 🟢 100% | OK |
|
||||
| backend/src/core/utils/matching.py | 🟢 100% | [suggest_mappings] Missing Belief State Logging: Function should use belief_scope context manager.<br>[suggest_mappings] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/core/task_manager/persistence.py | 🟢 100% | OK |
|
||||
| backend/src/core/task_manager/manager.py | 🟢 100% | OK |
|
||||
| backend/src/core/task_manager/models.py | 🟢 100% | [__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/core/task_manager/cleanup.py | 🟢 100% | [__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager.<br>[__init__] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/core/task_manager/__init__.py | 🟢 100% | OK |
|
||||
| backend/src/api/auth.py | 🟢 100% | [get_current_user] Missing Belief State Logging: Function should use belief_scope context manager.<br>[get_current_user] Missing Belief State Logging: Function should use belief_scope context manager. |
|
||||
| backend/src/api/routes/git.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/connections.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/environments.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/migration.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/plugins.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/mappings.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/git_schemas.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/storage.py | 🟢 100% | OK |
|
||||
| backend/src/api/routes/tasks.py | 🟢 100% | OK |
|
||||
| backend/src/models/git.py | 🟢 100% | OK |
|
||||
| backend/src/models/task.py | 🟢 100% | OK |
|
||||
| backend/src/models/connection.py | 🟢 100% | OK |
|
||||
| backend/src/models/mapping.py | 🟢 100% | OK |
|
||||
| backend/src/models/storage.py | 🟢 100% | OK |
|
||||
| backend/src/models/dashboard.py | 🟢 100% | OK |
|
||||
| backend/src/services/git_service.py | 🟢 100% | OK |
|
||||
| backend/src/services/mapping_service.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/backup.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/debug.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/search.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/mapper.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/git_plugin.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/migration.py | 🟢 100% | OK |
|
||||
| backend/src/plugins/storage/plugin.py | 🟢 100% | OK |
|
||||
| backend/tests/test_models.py | 🟢 100% | OK |
|
||||
| backend/tests/test_logger.py | 🟢 100% | OK |
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,7 @@
|
||||
## Phase 3: [US1] Scheduled Backups
|
||||
- [x] T009 [US1] Implement schedule loading and registration logic in `SchedulerService`
|
||||
- [x] T010 [US1] Update `Environment` settings API to handle `backup_schedule` updates in `backend/src/api/routes/environments.py`
|
||||
- [x] T011 [P] [US1] Add schedule configuration fields to Environment edit form in `frontend/src/components/EnvSelector.svelte` (or appropriate component)
|
||||
- [ ] T011 [P] [US1] Add schedule configuration fields to Environment edit form in `frontend/src/components/EnvSelector.svelte` (or appropriate component)
|
||||
- [x] T012 [US1] Implement validation for Cron expressions in backend and frontend
|
||||
|
||||
## Phase 4: [US2] Unified Task Management UI
|
||||
@@ -34,7 +34,7 @@
|
||||
- [x] T021 [US4] Integrate log viewer into TaskList or as a separate modal/page
|
||||
|
||||
## Final Phase: Polish & Cross-cutting concerns
|
||||
- [x] T022 Implement task cleanup/retention policy (e.g., delete tasks older than 30 days)
|
||||
- [ ] T022 Implement task cleanup/retention policy (e.g., delete tasks older than 30 days)
|
||||
- [ ] T023 Add real-time updates for task status using WebSockets (optional/refinement)
|
||||
- [x] T024 Ensure consistent error handling and logging across scheduler and task manager
|
||||
|
||||
|
||||
42
specs/013-unify-frontend-css/checklists/requirements.md
Normal file
42
specs/013-unify-frontend-css/checklists/requirements.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Checklist: Frontend UI & i18n Requirements Quality
|
||||
|
||||
**Purpose**: Validate the quality, clarity, and completeness of the requirements for the unified frontend CSS and localization feature.
|
||||
|
||||
**Meta**:
|
||||
- **Feature**: 013-unify-frontend-css
|
||||
- **Created**: 2026-01-23
|
||||
- **Focus**: UX Consistency, Component Contracts, i18n Completeness
|
||||
|
||||
## Requirement Completeness
|
||||
- [x] CHK001 Are all necessary UI components (Button, Input, Select, Card, PageHeader) explicitly identified for standardization? [Completeness, Spec §FR-002]
|
||||
- [x] CHK002 Are the supported languages (RU, EN) and default language (RU) explicitly defined? [Completeness, Spec §FR-008, §FR-009]
|
||||
- [x] CHK003 Is the persistence mechanism for language selection (LocalStorage) specified? [Completeness, Spec §FR-010]
|
||||
- [x] CHK004 Are the specific pages (Dashboard, Settings) targeted for migration identified? [Completeness, Spec §FR-005]
|
||||
|
||||
## Requirement Clarity
|
||||
- [x] CHK005 Is the "consistent spacing" requirement quantified with specific values or a system (e.g., Tailwind scale)? [Clarity, Spec §FR-004]
|
||||
- [x] CHK006 Are the specific visual properties (color, padding, hover state) that must be consistent defined in the design system configuration? [Clarity, Spec §SC-001]
|
||||
- [x] CHK007 Is the behavior of "legacy components" clearly defined (refactor vs. approximate)? [Clarity, Spec Edge Cases]
|
||||
|
||||
## Requirement Consistency
|
||||
- [x] CHK008 Do the component contracts in `contracts/ui-components.md` align with the visual requirements in the spec? [Consistency]
|
||||
- [x] CHK009 Is the decision to use Svelte stores for i18n consistent with the requirement for client-side persistence? [Consistency, Research §2]
|
||||
|
||||
## Acceptance Criteria Quality
|
||||
- [x] CHK010 Can the "consistent visual experience" be objectively verified through the defined independent tests? [Measurability, Spec §User Story 1]
|
||||
- [x] CHK011 Is the "0 instances of arbitrary hardcoded color values" criterion measurable via static analysis or search? [Measurability, Spec §SC-003]
|
||||
- [x] CHK012 Is the language persistence requirement testable by reloading the page? [Measurability, Spec §SC-006]
|
||||
|
||||
## Scenario Coverage
|
||||
- [x] CHK013 Are requirements defined for the scenario where a translation key is missing? [Coverage, Spec Edge Cases]
|
||||
- [x] CHK014 Are requirements defined for the initial load state (default language)? [Coverage, Spec §User Story 3]
|
||||
- [x] CHK015 Are requirements defined for switching languages while on a page with dynamic content? [Coverage, Gap]
|
||||
|
||||
## Edge Case Coverage
|
||||
- [x] CHK016 Is the behavior defined for text that overflows when translated to a longer language (e.g., RU vs EN)? [Edge Case, Gap]
|
||||
- [x] CHK017 Is the behavior defined if LocalStorage is disabled or inaccessible? [Edge Case, Gap]
|
||||
- [x] CHK018 Are requirements defined for responsive behavior on mobile devices? [Edge Case, Spec §Edge Cases]
|
||||
|
||||
## Non-Functional Requirements
|
||||
- [x] CHK019 Are there specific performance targets for language switching (e.g., "instant", "no layout shift")? [NFR, Research §Technical Context]
|
||||
- [x] CHK020 Are accessibility requirements (ARIA labels, keyboard nav) defined for the new components? [NFR, Gap]
|
||||
50
specs/013-unify-frontend-css/contracts/ui-components.md
Normal file
50
specs/013-unify-frontend-css/contracts/ui-components.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# UI Component Contracts
|
||||
|
||||
This document defines the strict API contracts for the standardized UI components.
|
||||
|
||||
## Button Component
|
||||
|
||||
### Description
|
||||
A standard interactive button element that triggers an action.
|
||||
|
||||
### Props
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `variant` | `'primary' \| 'secondary' \| 'danger' \| 'ghost'` | `'primary'` | Visual style of the button. |
|
||||
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size of the button. |
|
||||
| `isLoading` | `boolean` | `false` | If true, shows a spinner and disables interaction. |
|
||||
| `disabled` | `boolean` | `false` | If true, prevents interaction. |
|
||||
| `type` | `'button' \| 'submit' \| 'reset'` | `'button'` | HTML button type. |
|
||||
| `onclick` | `(event: MouseEvent) => void` | `undefined` | Click handler. |
|
||||
| `class` | `string` | `''` | Additional CSS classes (use sparingly). |
|
||||
|
||||
### Slots
|
||||
- `default`: The content of the button (text or icon).
|
||||
|
||||
## Input Component
|
||||
|
||||
### Description
|
||||
A standard text input field with support for labels and error messages.
|
||||
|
||||
### Props
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `label` | `string` | `undefined` | Text label displayed above the input. |
|
||||
| `value` | `string` | `''` | The current value (bindable). |
|
||||
| `placeholder` | `string` | `''` | Placeholder text. |
|
||||
| `error` | `string` | `undefined` | Error message displayed below the input. |
|
||||
| `disabled` | `boolean` | `false` | If true, prevents interaction. |
|
||||
| `type` | `'text' \| 'password' \| 'email' \| 'number'` | `'text'` | HTML input type. |
|
||||
|
||||
## Select Component
|
||||
|
||||
### Description
|
||||
A standard dropdown selection component.
|
||||
|
||||
### Props
|
||||
| Prop | Type | Default | Description |
|
||||
|------|------|---------|-------------|
|
||||
| `label` | `string` | `undefined` | Text label displayed above the select. |
|
||||
| `value` | `string \| number` | `''` | The selected value (bindable). |
|
||||
| `options` | `{ value: string \| number, label: string }[]` | `[]` | List of options to display. |
|
||||
| `disabled` | `boolean` | `false` | If true, prevents interaction. |
|
||||
59
specs/013-unify-frontend-css/data-model.md
Normal file
59
specs/013-unify-frontend-css/data-model.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Data Model: Frontend State & Components
|
||||
|
||||
## 1. i18n State (Client-Side)
|
||||
|
||||
The application state for internationalization is transient (in-memory) but initialized from and persisted to `localStorage`.
|
||||
|
||||
### Entities
|
||||
|
||||
#### `Locale`
|
||||
- **Type**: String Enum
|
||||
- **Values**: `"ru"`, `"en"`
|
||||
- **Default**: `"ru"`
|
||||
- **Persistence**: `localStorage.getItem("locale")`
|
||||
|
||||
#### `TranslationDictionary`
|
||||
- **Type**: JSON Object
|
||||
- **Structure**: Nested key-value pairs where values are strings.
|
||||
- **Example**:
|
||||
```json
|
||||
{
|
||||
"common": {
|
||||
"save": "Сохранить",
|
||||
"cancel": "Отмена"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Панель управления"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Component Props (Contracts)
|
||||
|
||||
These define the "API" for the standardized UI components.
|
||||
|
||||
### `Button`
|
||||
- **variant**: `"primary" | "secondary" | "danger" | "ghost"` (default: "primary")
|
||||
- **size**: `"sm" | "md" | "lg"` (default: "md")
|
||||
- **isLoading**: `boolean` (default: false)
|
||||
- **disabled**: `boolean` (default: false)
|
||||
- **type**: `"button" | "submit" | "reset"` (default: "button")
|
||||
- **onClick**: `() => void` (optional)
|
||||
|
||||
### `Input`
|
||||
- **label**: `string` (optional)
|
||||
- **value**: `string` (two-way binding)
|
||||
- **placeholder**: `string` (optional)
|
||||
- **error**: `string` (optional)
|
||||
- **disabled**: `boolean` (default: false)
|
||||
- **type**: `"text" | "password" | "email" | "number"` (default: "text")
|
||||
|
||||
### `Card`
|
||||
- **title**: `string` (optional)
|
||||
- **padding**: `"none" | "sm" | "md" | "lg"` (default: "md")
|
||||
|
||||
### `Select`
|
||||
- **options**: `Array<{ value: string | number, label: string }>`
|
||||
- **value**: `string | number` (two-way binding)
|
||||
- **label**: `string` (optional)
|
||||
- **disabled**: `boolean` (default: false)
|
||||
70
specs/013-unify-frontend-css/plan.md
Normal file
70
specs/013-unify-frontend-css/plan.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Implementation Plan: [FEATURE]
|
||||
|
||||
**Branch**: `[###-feature-name]` | **Date**: [DATE] | **Spec**: [link]
|
||||
**Input**: Feature specification from `/specs/[###-feature-name]/spec.md`
|
||||
|
||||
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.
|
||||
|
||||
## Summary
|
||||
|
||||
Unify frontend styling using a centralized Tailwind CSS configuration and a set of standardized Svelte wrapper components. Implement internationalization (i18n) with support for Russian (default) and English, persisting language preference in LocalStorage.
|
||||
|
||||
## Technical Context
|
||||
|
||||
**Language/Version**: Node.js 18+ (Frontend Build), Svelte 5.x
|
||||
**Primary Dependencies**: SvelteKit, Tailwind CSS, `date-fns` (existing)
|
||||
**Storage**: LocalStorage (for language preference)
|
||||
**Testing**: Manual verification per User Scenarios (Visual Regression)
|
||||
**Target Platform**: Web Browser (Responsive)
|
||||
**Project Type**: Web application (Frontend)
|
||||
**Performance Goals**: Instant language switching, zero layout shift
|
||||
**Constraints**: Must support existing pages without breaking layout
|
||||
**Scale/Scope**: ~10-15 core UI components, global CSS update
|
||||
|
||||
## Constitution Check
|
||||
|
||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||
|
||||
- **Semantic Protocol Compliance**: PASS. New Svelte components must follow the Component Header standard.
|
||||
- **Causal Validity**: PASS. Design System (Tokens) and Component Contracts will be defined before implementation.
|
||||
- **Immutability of Architecture**: PASS. No changes to backend architecture; strictly frontend presentation layer.
|
||||
- **Design by Contract**: PASS. Components will define strict props/interfaces.
|
||||
- **Everything is a Plugin**: N/A. UI components are not backend plugins, but will be modular.
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Documentation (this feature)
|
||||
|
||||
```text
|
||||
specs/[###-feature]/
|
||||
├── plan.md # This file (/speckit.plan command output)
|
||||
├── research.md # Phase 0 output (/speckit.plan command)
|
||||
├── data-model.md # Phase 1 output (/speckit.plan command)
|
||||
├── quickstart.md # Phase 1 output (/speckit.plan command)
|
||||
├── contracts/ # Phase 1 output (/speckit.plan command)
|
||||
└── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
|
||||
```
|
||||
|
||||
### Source Code (repository root)
|
||||
|
||||
```text
|
||||
frontend/
|
||||
├── src/
|
||||
│ ├── lib/
|
||||
│ │ ├── i18n/ # [NEW] Translation dictionaries and stores
|
||||
│ │ └── ui/ # [NEW] Standardized Svelte components
|
||||
│ ├── components/ # [EXISTING] To be refactored to use lib/ui
|
||||
│ ├── routes/ # [EXISTING] Pages
|
||||
│ └── app.css # [EXISTING] Global styles (Tailwind directives)
|
||||
```
|
||||
|
||||
**Structure Decision**: Adopting a standard SvelteKit structure where reusable UI components and logic (i18n) reside in `src/lib`, accessible via `$lib` alias. Existing `components` directory will be gradually refactored or moved to `lib/ui` if generic enough.
|
||||
|
||||
## Complexity Tracking
|
||||
|
||||
> **Fill ONLY if Constitution Check has violations that must be justified**
|
||||
|
||||
| Violation | Why Needed | Simpler Alternative Rejected Because |
|
||||
|-----------|------------|-------------------------------------|
|
||||
| [e.g., 4th project] | [current need] | [why 3 projects insufficient] |
|
||||
| [e.g., Repository pattern] | [specific problem] | [why direct DB access insufficient] |
|
||||
63
specs/013-unify-frontend-css/quickstart.md
Normal file
63
specs/013-unify-frontend-css/quickstart.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Quickstart: Frontend Development
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js 18+
|
||||
- npm
|
||||
|
||||
## Setup
|
||||
|
||||
1. Navigate to the frontend directory:
|
||||
```bash
|
||||
cd frontend
|
||||
```
|
||||
|
||||
2. Install dependencies:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
## Running the Development Server
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
The application will be available at `http://localhost:5173`.
|
||||
|
||||
## Using Standard UI Components
|
||||
|
||||
Import components from `$lib/ui`:
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { Button, Input } from '$lib/ui';
|
||||
</script>
|
||||
|
||||
<Input label="Username" bind:value={username} />
|
||||
<Button variant="primary" onclick={submit}>Submit</Button>
|
||||
```
|
||||
|
||||
## Using Internationalization
|
||||
|
||||
Import the `t` store from `$lib/i18n`:
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { t } from '$lib/i18n';
|
||||
</script>
|
||||
|
||||
<h1>{$t.dashboard.title}</h1>
|
||||
<button>{$t.common.save}</button>
|
||||
```
|
||||
|
||||
## Adding New Translations
|
||||
|
||||
1. Open `frontend/src/lib/i18n/locales/ru.json` and add the new key-value pair.
|
||||
2. Open `frontend/src/lib/i18n/locales/en.json` and add the corresponding English translation.
|
||||
|
||||
## Adding New Components
|
||||
|
||||
1. Create a new `.svelte` file in `frontend/src/lib/ui/`.
|
||||
2. Define props and styles using Tailwind CSS.
|
||||
3. Export the component in `frontend/src/lib/ui/index.ts`.
|
||||
51
specs/013-unify-frontend-css/research.md
Normal file
51
specs/013-unify-frontend-css/research.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Research: Unify Frontend CSS & Localization
|
||||
|
||||
## 1. Design System & Theming
|
||||
|
||||
**Decision**: Use Tailwind CSS as the engine for the design system, but encapsulate usage within reusable Svelte components (`src/lib/ui/*`).
|
||||
|
||||
**Rationale**:
|
||||
- **Consistency**: Centralizing styles in components ensures that a "Button" always looks like a "Button" without copy-pasting utility classes.
|
||||
- **Maintainability**: Tailwind configuration (`tailwind.config.js`) will hold the "Design Tokens" (colors, spacing), while components hold the "Structure".
|
||||
- **Speed**: Tailwind is already integrated and allows for rapid development.
|
||||
|
||||
**Alternatives Considered**:
|
||||
- *Pure CSS/SCSS Modules*: Rejected because it requires more boilerplate and context switching between files.
|
||||
- *Component Library (e.g., Skeleton, Flowbite)*: Rejected to maintain full control over the visual identity and avoid bloat, but we will use the "Headless UI" concept where we build our own accessible components.
|
||||
|
||||
## 2. Internationalization (i18n)
|
||||
|
||||
**Decision**: Implement a lightweight, store-based i18n solution using Svelte stores and JSON dictionaries.
|
||||
|
||||
**Rationale**:
|
||||
- **Simplicity**: For just two languages (RU, EN) and a moderate number of strings, a full-blown library like `svelte-i18n` or `i18next` adds unnecessary complexity and bundle size.
|
||||
- **Control**: A custom store allows us to easily handle `localStorage` persistence and reactive updates across the app without fighting library-specific lifecycle issues.
|
||||
- **Performance**: Direct object lookups are extremely fast.
|
||||
|
||||
**Implementation Detail**:
|
||||
- `src/lib/i18n/index.ts`: Exports a derived store `t` that reacts to the current `locale` store.
|
||||
- `src/lib/i18n/locales/ru.json`: Default dictionary.
|
||||
- `src/lib/i18n/locales/en.json`: English dictionary.
|
||||
|
||||
**Alternatives Considered**:
|
||||
- *svelte-i18n*: Good library, but overkill for current requirements.
|
||||
- *Server-side i18n*: Rejected because the requirement specifies client-side persistence (LocalStorage) and immediate switching without page reloads.
|
||||
|
||||
## 3. Component Architecture
|
||||
|
||||
**Decision**: Create "Atomic" components in `src/lib/ui` that expose props for variants (e.g., `variant="primary" | "secondary"`).
|
||||
|
||||
**Proposed Components**:
|
||||
- `Button.svelte`: Handles variants, sizes, loading states.
|
||||
- `Card.svelte`: Standard container with padding/shadow.
|
||||
- `Input.svelte`: Standardized text input with label and error state.
|
||||
- `Select.svelte`: Standardized dropdown.
|
||||
- `PageHeader.svelte`: Unified title and actions area for pages.
|
||||
|
||||
**Rationale**: These cover 80% of the UI inconsistency issues identified in the spec.
|
||||
|
||||
## 4. Migration Strategy
|
||||
|
||||
**Decision**: "Strangler Fig" pattern - create new components first, then incrementally replace usage in `src/routes` and existing `src/components`.
|
||||
|
||||
**Rationale**: Allows the application to remain functional at all times. We can migrate one page or one section at a time.
|
||||
102
specs/013-unify-frontend-css/spec.md
Normal file
102
specs/013-unify-frontend-css/spec.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# Feature Specification: Unify Frontend CSS & Localization
|
||||
|
||||
**Feature Branch**: `013-unify-frontend-css`
|
||||
**Created**: 2026-01-23
|
||||
**Status**: Draft
|
||||
**Input**: User description: "Я хочу унифицировать CSS для фронта. Добавь в спецификацию требование, что я хочу l18n для всех текстовых элементов. Плюс, должен быть переключатель языков. Для начала два языка - русский и английский, русский по умолчанию."
|
||||
|
||||
## Clarifications
|
||||
|
||||
### Session 2026-01-23
|
||||
|
||||
- Q: Should we refactor existing UI elements into reusable Svelte components or just apply standardized CSS utility classes? → A: Create thin wrapper Svelte components (e.g., `<Button variant="primary">`) that apply utility classes internally, using Svelte's reactivity for states.
|
||||
- Q: How should the selected language persist across sessions? → A: Use a simple client-side persistence (LocalStorage) for now; migrate to user profile settings later when a full user system is implemented.
|
||||
|
||||
## User Scenarios & Testing
|
||||
|
||||
### User Story 1 - Consistent Visual Experience (Priority: P1)
|
||||
|
||||
As a user, I want the application to look consistent across all pages (Dashboard, Settings, Tools) so that I have a cohesive user experience without jarring visual differences.
|
||||
|
||||
**Why this priority**: Inconsistent UI makes the application feel unprofessional and harder to use. Unifying styles ensures a polished look and predictable interactions.
|
||||
|
||||
**Independent Test**: Navigate to at least 3 different pages (e.g., Dashboard, Settings, a Tool page) and verify that buttons, inputs, and layout spacing share the same visual properties (colors, padding, border radius).
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** I am on the Dashboard, **When** I look at a primary action button, **Then** it should have the same color, padding, and hover state as a primary action button on the Settings page.
|
||||
2. **Given** I am viewing a data table in "Migration", **When** I compare it to a table in "Tasks", **Then** the headers, row spacing, and borders should be identical in style.
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 - Efficient Design Updates (Priority: P2)
|
||||
|
||||
As a designer or developer, I want to change a core brand color in one place and have it reflect across the entire application, so that maintaining the visual identity is fast and error-free.
|
||||
|
||||
**Why this priority**: Reduces maintenance time and prevents "drift" where slightly different styles are introduced over time.
|
||||
|
||||
**Independent Test**: Change a primary color value in the central configuration and verify it updates on multiple distinct pages without individual code changes.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** the primary brand color is updated in the central theme, **When** I reload the application, **Then** all primary buttons, links, and active states should reflect the new color immediately.
|
||||
2. **Given** a new UI element is added, **When** standard style tokens are applied, **Then** it should automatically match the existing design system without manual pixel adjustments.
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 - Multi-language Support (Priority: P2)
|
||||
|
||||
As a user, I want to switch the interface language between Russian and English so that I can work in my preferred language.
|
||||
|
||||
**Why this priority**: Expands accessibility and usability for non-Russian speakers (or English speakers).
|
||||
|
||||
**Independent Test**: Switch the language using the UI toggle and verify that static text elements on the page update immediately.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** I am on any page, **When** I switch the language to "English", **Then** all menu items, labels, and button text should change to English.
|
||||
2. **Given** I open the application for the first time, **Then** the interface should be in Russian (default).
|
||||
3. **Given** I have selected "English", **When** I refresh the page, **Then** the interface should remain in English (persisted via LocalStorage).
|
||||
|
||||
## Edge Cases
|
||||
|
||||
- **Legacy Components**: What happens to older components that don't fit the new system?
|
||||
- *Handling*: They should be refactored to use the new system. If refactoring is too complex, they should visually approximate the new system as closely as possible until fully replaced.
|
||||
- **Theme Conflicts**: What happens if a specific page requires unique styling that contradicts the global theme?
|
||||
- *Handling*: The system should allow for local overrides, but these should be used sparingly and documented.
|
||||
- **Browser Compatibility**: How does the new styling behave on different screen sizes?
|
||||
- *Handling*: The unified styles must support responsive behavior, adapting spacing and font sizes appropriately for standard breakpoints (mobile, tablet, desktop).
|
||||
- **Missing Translations**: What happens if a translation key is missing for the selected language?
|
||||
- *Handling*: The system should fallback to the default language (Russian) or display the key itself in a non-breaking way.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- **FR-001**: The system MUST use a centralized configuration for all design tokens (colors, typography, spacing, breakpoints).
|
||||
- **FR-002**: The system MUST provide standardized, **thin wrapper Svelte components** (e.g., `<Button variant="primary">`) that encapsulate the design system.
|
||||
- **FR-003**: The system MUST NOT use hardcoded color values or "magic numbers" for spacing in individual page views; all styling must reference the central design tokens.
|
||||
- **FR-004**: The system MUST ensure consistent spacing (margins and padding) between elements across all layouts.
|
||||
- **FR-005**: All existing pages (Dashboard, Settings, Tools) MUST be updated to utilize the new centralized design system components.
|
||||
- **FR-006**: The system MUST support **Internationalization (i18n)** for all static text elements.
|
||||
- **FR-007**: The system MUST provide a **Language Switcher** in the UI (e.g., Navbar or Settings).
|
||||
- **FR-008**: Supported languages MUST be **Russian (RU)** and **English (EN)**.
|
||||
- **FR-009**: The default language MUST be **Russian**.
|
||||
- **FR-010**: The selected language MUST be persisted in **LocalStorage** to survive page reloads (until a user profile system is implemented).
|
||||
|
||||
### Key Entities
|
||||
|
||||
- **Design System Configuration**: The single source of truth for visual values (colors, fonts, spacing).
|
||||
- **Standardized UI Components**: Reusable Svelte components that implement the design tokens.
|
||||
- **Translation Dictionary**: A collection of key-value pairs for text strings in each supported language.
|
||||
|
||||
## Success Criteria
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001**: Central design configuration contains definitions for all primary, secondary, background, and status (success/error/warning) colors.
|
||||
- **SC-002**: 100% of primary UI elements (buttons, inputs, cards) on key pages (Dashboard, Settings) use the standardized Svelte components.
|
||||
- **SC-003**: 0 instances of arbitrary hardcoded color values for primary UI elements remain in the codebase.
|
||||
- **SC-004**: Visual regression check confirms that Dashboard, Settings, and Task pages share identical styling for common elements (buttons, tables, headers).
|
||||
- **SC-005**: User can successfully switch between Russian and English, with 100% of navigation and primary action text translating correctly.
|
||||
- **SC-006**: Selected language persists after a page reload 100% of the time.
|
||||
71
specs/013-unify-frontend-css/tasks.md
Normal file
71
specs/013-unify-frontend-css/tasks.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Implementation Tasks: Unify Frontend CSS & Localization
|
||||
|
||||
**Branch**: `013-unify-frontend-css`
|
||||
|
||||
## Phase 1: Setup & Infrastructure
|
||||
|
||||
**Goal**: Initialize the i18n system and component structure.
|
||||
|
||||
- [ ] T001 Initialize i18n locales directory and JSON files in `frontend/src/lib/i18n/locales/ru.json` and `frontend/src/lib/i18n/locales/en.json`
|
||||
- [ ] T002 Implement i18n store with LocalStorage persistence in `frontend/src/lib/i18n/index.ts`
|
||||
- [ ] T003 Create UI component directory structure in `frontend/src/lib/ui/` and `frontend/src/lib/ui/index.ts`
|
||||
- [ ] T004 [P] Configure Tailwind utility classes for design tokens (if strictly needed beyond defaults) in `frontend/tailwind.config.js`
|
||||
|
||||
## Phase 2: Foundational Components (User Stories 1 & 2)
|
||||
|
||||
**Goal**: Create the core standardized components required for visual consistency.
|
||||
**User Story**: US1 (Consistent Visual Experience) & US2 (Efficient Design Updates)
|
||||
|
||||
- [ ] T005 [P] [US1] Create `Button` component with variants in `frontend/src/lib/ui/Button.svelte`
|
||||
- [ ] T006 [P] [US1] Create `Input` component with label/error support in `frontend/src/lib/ui/Input.svelte`
|
||||
- [ ] T007 [P] [US1] Create `Select` component in `frontend/src/lib/ui/Select.svelte`
|
||||
- [ ] T008 [P] [US1] Create `Card` container component in `frontend/src/lib/ui/Card.svelte`
|
||||
- [ ] T009 [P] [US1] Create `PageHeader` component in `frontend/src/lib/ui/PageHeader.svelte`
|
||||
- [ ] T010 [US1] Export all components from `frontend/src/lib/ui/index.ts`
|
||||
|
||||
## Phase 3: Internationalization Support (User Story 3)
|
||||
|
||||
**Goal**: Enable language switching and translation.
|
||||
**User Story**: US3 (Multi-language Support)
|
||||
|
||||
- [ ] T011 [US3] Create `LanguageSwitcher` component in `frontend/src/lib/ui/LanguageSwitcher.svelte`
|
||||
- [ ] T012 [US3] Integrate `LanguageSwitcher` into the main layout `frontend/src/routes/+layout.svelte`
|
||||
- [ ] T013 [US3] Populate `ru.json` and `en.json` with initial common terms (Save, Cancel, Dashboard, Settings)
|
||||
|
||||
## Phase 4: Migration - Dashboard & Settings (User Story 1)
|
||||
|
||||
**Goal**: Apply new components to key pages to prove consistency.
|
||||
**User Story**: US1 (Consistent Visual Experience)
|
||||
|
||||
- [ ] T014 [US1] Refactor `frontend/src/routes/+page.svelte` (Dashboard) to use `PageHeader` and `Card`
|
||||
- [ ] T015 [US1] Refactor `frontend/src/routes/settings/+page.svelte` (Settings) to use `Button`, `Input`, and `Select`
|
||||
- [ ] T016 [US1] Refactor `frontend/src/routes/tools/*` pages to use standardized components
|
||||
- [ ] T017 [US1] Replace hardcoded text in Dashboard, Settings, and Tools with `$t` store keys
|
||||
|
||||
## Phase 5: Polish & Cross-Cutting Concerns
|
||||
|
||||
**Goal**: Final cleanup and verification.
|
||||
|
||||
- [ ] T018 Verify all hardcoded colors are removed from refactored pages
|
||||
- [ ] T019 Ensure LocalStorage persistence works for language selection
|
||||
- [ ] T020 Check responsiveness of new components on mobile view
|
||||
- [ ] T021 [US3] Verify UI stability and text alignment with long RU translations (overflow check)
|
||||
- [ ] T022 [US3] Implement fallback to default language if translation key or LocalStorage is missing
|
||||
|
||||
## Dependencies
|
||||
|
||||
1. **Phase 1** (Infrastructure) MUST be completed first.
|
||||
2. **Phase 2** (Components) depends on Phase 1.
|
||||
3. **Phase 3** (i18n UI) depends on Phase 1 (Store) and Phase 2 (Select/Button components).
|
||||
4. **Phase 4** (Migration) depends on Phase 2 and Phase 3.
|
||||
|
||||
## Parallel Execution Examples
|
||||
|
||||
- **Components**: T005 (Button), T006 (Input), T007 (Select), and T008 (Card) can be built simultaneously by different developers.
|
||||
- **Migration**: T014 (Dashboard) and T015 (Settings) can be refactored in parallel once components are ready.
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
1. **MVP**: Build the i18n store and the `Button` component. Refactor one small page to prove the concept.
|
||||
2. **Expansion**: Build remaining components (`Input`, `Select`).
|
||||
3. **Rollout**: Systematically refactor pages one by one.
|
||||
34
specs/014-file-storage-ui/checklists/requirements.md
Normal file
34
specs/014-file-storage-ui/checklists/requirements.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Specification Quality Checklist: File Storage Management & UI
|
||||
|
||||
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||
**Created**: 2026-01-24
|
||||
**Feature**: [Link to 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
|
||||
|
||||
- Items marked incomplete require spec updates before `/speckit.clarify` or `/speckit.plan`
|
||||
35
specs/014-file-storage-ui/checklists/ux.md
Normal file
35
specs/014-file-storage-ui/checklists/ux.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Checklist: File Storage UX & Configuration
|
||||
|
||||
**Purpose**: Validate implementation of User Experience and Configuration Flexibility requirements.
|
||||
**Created**: 2026-01-24
|
||||
**Feature**: [File Storage Management & UI](../spec.md)
|
||||
|
||||
## User Experience (File Management)
|
||||
|
||||
- [x] CHK001 Are loading states displayed while fetching the file list? [Completeness]
|
||||
- [x] CHK002 Is visual feedback provided immediately after file upload starts? [Clarity]
|
||||
- [x] CHK003 Are error messages user-friendly when upload fails (e.g., "File too large" vs "Error 413")? [Clarity]
|
||||
- [x] CHK004 Is a confirmation modal shown before permanently deleting a file? [Safety]
|
||||
- [x] CHK005 Does the UI clearly distinguish between "Backups" and "Repositories" tabs? [Clarity]
|
||||
- [x] CHK006 Is the file list sortable by Date and Name? [Usability]
|
||||
- [x] CHK007 Are file sizes formatted in human-readable units (KB, MB, GB)? [Usability]
|
||||
- [x] CHK008 Is the download action easily accessible for each file item? [Accessibility]
|
||||
- [x] CHK009 Does the upload component support drag-and-drop interactions? [Usability]
|
||||
- [x] CHK010 Is the "Upload" button disabled or hidden when no category is selected? [Consistency]
|
||||
|
||||
## Configuration Flexibility
|
||||
|
||||
- [x] CHK011 Can the storage root path be configured to any writable directory on the server? [Flexibility]
|
||||
- [x] CHK012 Does the system support defining custom directory structures using variables like `{environment}`? [Flexibility]
|
||||
- [x] CHK013 Does the system support defining custom filename patterns using variables like `{timestamp}`? [Flexibility]
|
||||
- [ ] CHK014 Are the supported pattern variables (e.g., `{dashboard_name}`) clearly documented in the UI? [Clarity]
|
||||
- [x] CHK015 Can the configuration be updated without restarting the application? [Usability]
|
||||
- [ ] CHK016 Is the current resolved path shown as a preview when editing patterns? [Usability]
|
||||
- [ ] CHK017 Does the system allow reverting configuration to default values? [Recovery]
|
||||
|
||||
## Edge Cases & Error Handling
|
||||
|
||||
- [x] CHK018 Is the UI behavior defined for an empty file list (zero state)? [Coverage]
|
||||
- [ ] CHK019 Is the behavior defined when the configured storage path becomes inaccessible? [Resilience]
|
||||
- [x] CHK020 Are long filenames handled gracefully in the UI (e.g., truncation with tooltip)? [Layout]
|
||||
- [x] CHK021 Is the behavior defined for uploading a file with a duplicate name? [Conflict Resolution]
|
||||
74
specs/014-file-storage-ui/contracts/api.md
Normal file
74
specs/014-file-storage-ui/contracts/api.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# API Contracts: File Storage Management & UI
|
||||
|
||||
## Endpoints
|
||||
|
||||
### GET /api/storage/files
|
||||
List all files in the storage system.
|
||||
|
||||
**Query Parameters:**
|
||||
- `category` (optional): Filter by category (`backup` or `repository`).
|
||||
|
||||
**Response:**
|
||||
- `200 OK`: List of `StoredFile` objects.
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "dashboard_backup_20260124.zip",
|
||||
"path": "backups/dashboard_backup_20260124.zip",
|
||||
"size": 102400,
|
||||
"created_at": "2026-01-24T12:00:00Z",
|
||||
"category": "backup",
|
||||
"mime_type": "application/zip"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### POST /api/storage/upload
|
||||
Upload a file to the storage system.
|
||||
|
||||
**Form Data:**
|
||||
- `file`: The file content.
|
||||
- `category`: Target category (`backup` or `repository`).
|
||||
|
||||
**Response:**
|
||||
- `201 Created`: The uploaded `StoredFile` object.
|
||||
- `400 Bad Request`: Invalid category or file.
|
||||
|
||||
### DELETE /api/storage/files/{category}/{filename}
|
||||
Delete a file from storage.
|
||||
|
||||
**Path Parameters:**
|
||||
- `category`: `backup` or `repository`.
|
||||
- `filename`: Name of the file to delete.
|
||||
|
||||
**Response:**
|
||||
- `204 No Content`: File deleted successfully.
|
||||
- `404 Not Found`: File does not exist.
|
||||
|
||||
### GET /api/storage/download/{category}/{filename}
|
||||
Download a file.
|
||||
|
||||
**Path Parameters:**
|
||||
- `category`: `backup` or `repository`.
|
||||
- `filename`: Name of the file to download.
|
||||
|
||||
**Response:**
|
||||
- `200 OK`: File stream.
|
||||
- `404 Not Found`: File does not exist.
|
||||
|
||||
### GET /api/settings/storage
|
||||
Get current storage configuration.
|
||||
|
||||
**Response:**
|
||||
- `200 OK`: `StorageConfig` object.
|
||||
|
||||
### PUT /api/settings/storage
|
||||
Update storage configuration.
|
||||
|
||||
**Body:**
|
||||
- `StorageConfig` object.
|
||||
|
||||
**Response:**
|
||||
- `200 OK`: Updated `StorageConfig`.
|
||||
- `400 Bad Request`: Invalid path or not writable.
|
||||
34
specs/014-file-storage-ui/data-model.md
Normal file
34
specs/014-file-storage-ui/data-model.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Data Model: File Storage Management & UI
|
||||
|
||||
## Entities
|
||||
|
||||
### StorageConfig
|
||||
*Configuration for the storage system.*
|
||||
|
||||
| Field | Type | Description | Constraints |
|
||||
|---|---|---|---|
|
||||
| `root_path` | `string` | Absolute path to the storage root directory. | Must be a valid, writable path. Default: `../ss-tools-storage` |
|
||||
| `backup_structure_pattern` | `string` | Pattern for backup directory structure. | Default: `{category}/` |
|
||||
| `repo_structure_pattern` | `string` | Pattern for repository directory structure. | Default: `{category}/` |
|
||||
| `filename_pattern` | `string` | Pattern for filenames. | Default: `{name}_{timestamp}` |
|
||||
|
||||
### StoredFile
|
||||
*Representation of a file in the storage system.*
|
||||
|
||||
| Field | Type | Description | Constraints |
|
||||
|---|---|---|---|
|
||||
| `name` | `string` | Name of the file (including extension). | No path separators. |
|
||||
| `path` | `string` | Relative path from storage root. | |
|
||||
| `size` | `integer` | Size of the file in bytes. | >= 0 |
|
||||
| `created_at` | `datetime` | Creation timestamp. | |
|
||||
| `category` | `enum` | Category of the file. | `backup`, `repository` |
|
||||
| `mime_type` | `string` | MIME type of the file. | Optional |
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```text
|
||||
{root_path}/
|
||||
├── backups/
|
||||
│ └── {filename}.zip
|
||||
└── repositories/
|
||||
└── {filename}.zip
|
||||
106
specs/014-file-storage-ui/plan.md
Normal file
106
specs/014-file-storage-ui/plan.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# Implementation Plan: File Storage Management & UI
|
||||
|
||||
**Branch**: `014-file-storage-ui` | **Date**: 2026-01-24 | **Spec**: [specs/014-file-storage-ui/spec.md](../014-file-storage-ui/spec.md)
|
||||
**Input**: Feature specification from `specs/014-file-storage-ui/spec.md`
|
||||
|
||||
## Summary
|
||||
|
||||
This feature implements a managed file storage system for dashboard backups and exported repositories. It introduces a configurable storage root (defaulting to outside the workspace) and a Web UI to list, upload, download, and delete files. The system enforces a structured layout with `backups/` and `repositories/` subdirectories to keep artifacts organized. The UI supports hierarchical folder navigation (e.g., `backups/SS2/DashboardName`), allowing users to browse, download, and manage files within nested directories.
|
||||
|
||||
## Technical Context
|
||||
|
||||
**Language/Version**: Python 3.9+ (Backend), Node.js 18+ (Frontend)
|
||||
**Primary Dependencies**: FastAPI (Backend), SvelteKit (Frontend)
|
||||
**Storage**: Local Filesystem (for artifacts), Config (for storage path)
|
||||
**Testing**: pytest (Backend), vitest/playwright (Frontend - implied)
|
||||
**Target Platform**: Linux server
|
||||
**Project Type**: Web application
|
||||
**Performance Goals**: File list load < 1s for 100 files, supports 50MB+ uploads
|
||||
**Constraints**: Must prevent path traversal, must not pollute git repo
|
||||
**Scale/Scope**: ~2-3 backend endpoints, 1-2 frontend pages/components
|
||||
|
||||
## Constitution Check
|
||||
|
||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||
|
||||
- **I. Semantic Protocol Compliance**:
|
||||
- **Status**: PASSED
|
||||
- **Check**: Will use `[DEF]` anchors and `@RELATION` tags.
|
||||
- **Check**: Will follow File Structure Standard.
|
||||
|
||||
- **II. Causal Validity (Contracts First)**:
|
||||
- **Status**: PASSED
|
||||
- **Check**: Contracts will be defined in `specs/014-file-storage-ui/contracts/` before implementation.
|
||||
|
||||
- **III. Immutability of Architecture**:
|
||||
- **Status**: PASSED
|
||||
- **Check**: No changes to immutable architectural constraints expected.
|
||||
|
||||
- **IV. Design by Contract (DbC)**:
|
||||
- **Status**: PASSED
|
||||
- **Check**: Functions will define `@PRE` and `@POST` conditions.
|
||||
|
||||
- **V. Belief State Logging**:
|
||||
- **Status**: PASSED
|
||||
- **Check**: Will use standard logging patterns.
|
||||
|
||||
- **VI. Fractal Complexity Limit**:
|
||||
- **Status**: PASSED
|
||||
- **Check**: Feature scope is small, unlikely to exceed complexity limits.
|
||||
|
||||
- **VII. Everything is a Plugin**:
|
||||
- **Status**: PASSED
|
||||
- **Check**: New functionality will be implemented as a `StoragePlugin` (or similar) inheriting from `PluginBase`.
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Documentation (this feature)
|
||||
|
||||
```text
|
||||
specs/014-file-storage-ui/
|
||||
├── 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/
|
||||
│ ├── api/
|
||||
│ │ └── routes/
|
||||
│ │ └── storage.py # New route handler
|
||||
│ ├── plugins/
|
||||
│ │ └── storage.py # New plugin implementation
|
||||
│ └── models/
|
||||
│ └── storage.py # Pydantic models (StoredFile, StorageConfig)
|
||||
└── tests/
|
||||
└── test_storage.py # Backend tests
|
||||
|
||||
frontend/
|
||||
├── src/
|
||||
│ ├── routes/
|
||||
│ │ └── storage/
|
||||
│ │ └── +page.svelte # Main storage UI
|
||||
│ ├── components/
|
||||
│ │ └── storage/
|
||||
│ │ ├── FileList.svelte # Component for listing files and folders (explorer view)
|
||||
│ │ ├── Breadcrumbs.svelte # Component for navigation
|
||||
│ │ └── FileUpload.svelte # Component for uploading
|
||||
│ └── services/
|
||||
│ └── storageService.js # Frontend API client
|
||||
```
|
||||
|
||||
**Structure Decision**: Standard Web Application structure (Backend + Frontend) with Plugin architecture for the backend logic.
|
||||
|
||||
## Complexity Tracking
|
||||
|
||||
> **Fill ONLY if Constitution Check has violations that must be justified**
|
||||
|
||||
| Violation | Why Needed | Simpler Alternative Rejected Because |
|
||||
|-----------|------------|-------------------------------------|
|
||||
| N/A | | |
|
||||
31
specs/014-file-storage-ui/quickstart.md
Normal file
31
specs/014-file-storage-ui/quickstart.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Quickstart: File Storage Management & UI
|
||||
|
||||
## Usage Guide
|
||||
|
||||
1. **Access File Storage**: Navigate to the "Tools" > "File Storage" section in the main navigation.
|
||||
2. **View Files**: You will see two tabs: "Backups" and "Repositories". Click on a tab to view files in that category.
|
||||
3. **Upload File**:
|
||||
- Click the "Upload" button.
|
||||
- Select a file from your computer.
|
||||
- Choose the target category (Backup or Repository).
|
||||
- Click "Upload" to start the transfer.
|
||||
4. **Download File**: Click the "Download" icon next to any file in the list.
|
||||
5. **Delete File**: Click the "Trash" icon next to any file to delete it permanently.
|
||||
6. **Configure Storage Path**:
|
||||
- Go to "Settings".
|
||||
- Locate the "File Storage" section.
|
||||
- Enter a new absolute path for the storage root.
|
||||
- Click "Save". The system will verify write access to the new path.
|
||||
|
||||
## Development
|
||||
|
||||
### Backend
|
||||
|
||||
- **Plugin**: `backend/src/plugins/storage.py`
|
||||
- **API Routes**: `backend/src/api/routes/storage.py`
|
||||
- **Models**: `backend/src/models/storage.py`
|
||||
|
||||
### Frontend
|
||||
|
||||
- **Page**: `frontend/src/routes/tools/storage/+page.svelte`
|
||||
- **Components**: `frontend/src/components/storage/*`
|
||||
17
specs/014-file-storage-ui/research.md
Normal file
17
specs/014-file-storage-ui/research.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Research: File Storage Management & UI
|
||||
|
||||
**Decision**: Use Python's built-in `pathlib` and `shutil` for filesystem operations.
|
||||
**Rationale**: Standard library, robust, cross-platform (though target is Linux), and sufficient for local file management. No external dependencies needed.
|
||||
**Alternatives considered**: `os` module (lower level, less ergonomic), `pyfilesystem2` (external dependency, unnecessary overhead for simple local storage).
|
||||
|
||||
**Decision**: Use `pydantic` for configuration and file metadata models.
|
||||
**Rationale**: Already used in the project, provides validation and serialization.
|
||||
|
||||
**Decision**: Use `multipart/form-data` for file uploads.
|
||||
**Rationale**: Standard web practice for file uploads. FastAPI supports `UploadFile` natively.
|
||||
|
||||
**Decision**: Default storage path strategy.
|
||||
**Rationale**: The default path will be `../ss-tools-storage` (relative to workspace root) or similar to ensure it sits outside the git repository by default, but allows configuration override.
|
||||
|
||||
**Decision**: Path Traversal Prevention.
|
||||
**Rationale**: Will use `os.path.commonpath` or `pathlib.Path.resolve()` to strictly validate that any accessed file path is a child of the configured storage root.
|
||||
97
specs/014-file-storage-ui/spec.md
Normal file
97
specs/014-file-storage-ui/spec.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# Feature Specification: File Storage Management & UI
|
||||
|
||||
**Feature Branch**: `014-file-storage-ui`
|
||||
**Created**: 2026-01-24
|
||||
**Status**: Draft
|
||||
**Input**: User description: "Я хочу проработать механизм хранения файлов и доступа к ним - бекапов дашбордов и репозиториев дашбордов. Во первых, нужно иметь указывать место хранения, по умолчанию оно должно быть за файловой системой сервера (чтобы не влиять на git репозиторий. Во вторых, нужен web ui для базового доступа ко всем файлам - возможность скачивания/удаления, информация о датах создания, возможность загрузки"
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
### User Story 1 - File Management Dashboard (Priority: P1)
|
||||
|
||||
Users need a visual interface to manage the artifacts generated by the system (dashboard backups, exported repositories) without needing direct server access. Users must be able to navigate through the folder structure (e.g., `backups/SS2/Sales Dashboard`) to locate specific files.
|
||||
|
||||
**Why this priority**: Core functionality requested. Without the UI, the storage mechanism is opaque and hard to use.
|
||||
|
||||
**Independent Test**: Can be fully tested by opening the new "File Storage" page, navigating into a subdirectory, uploading a test file, verifying it appears in the list, downloading it, and then deleting it.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** the File Storage page is open, **When** I view the list, **Then** I see the top-level folders (e.g., `backups`, `repositories`) or files.
|
||||
2. **Given** I am viewing a folder, **When** I click a subfolder name, **Then** the view updates to show the contents of that subfolder.
|
||||
3. **Given** I am in a subfolder, **When** I click "Download" on a file, **Then** the file is downloaded to my local machine.
|
||||
4. **Given** a file exists in the list, **When** I click "Delete" and confirm, **Then** the file is removed from the list and the server filesystem.
|
||||
5. **Given** I have a file locally, **When** I drag and drop it or use the "Upload" button, **Then** the file is uploaded to the current directory and appears in the list.
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 - Storage Location Configuration (Priority: P2)
|
||||
|
||||
Administrators need to control where potentially large or sensitive files are stored. Crucially, these files must not accidentally pollute the source code repository or the application's working directory.
|
||||
|
||||
**Why this priority**: Essential for system stability and cleanliness (preventing git pollution), but the system could theoretically start with a hardcoded safe default.
|
||||
|
||||
**Independent Test**: Change the storage path in Settings, generate a file (or upload one), and verify it exists in the new location on the server disk.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** I am in the Settings page, **When** I enter a new absolute path for "File Storage Path" and save, **Then** the system updates the configuration.
|
||||
2. **Given** the default configuration, **When** the system starts, **Then** the storage path defaults to a location outside the project's git scope (or is properly ignored).
|
||||
3. **Given** an invalid path (e.g., no write permissions), **When** I try to save, **Then** the system shows an error message.
|
||||
|
||||
---
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- **File Name Conflicts**: What happens when uploading a file that already exists? (System should likely rename or ask to overwrite).
|
||||
- **Storage Quota/Disk Space**: What happens if the disk is full during upload?
|
||||
- **Path Traversal**: Ensure users cannot access files outside the configured storage directory via the API.
|
||||
- **Large Files**: Handling uploads/downloads of large backup archives (e.g., > 100MB).
|
||||
|
||||
## Clarifications
|
||||
|
||||
### Session 2026-01-24
|
||||
|
||||
- Q: Should the system enforce a structure to keep backups and repositories separate? → A: **Structured**: System creates and enforces `backups/` and `repositories/` folders; UI separates them.
|
||||
|
||||
### Session 2026-01-24 (Update)
|
||||
|
||||
- Q: Should the system allow advanced configuration of file structure and naming conventions? → A: **Yes**: Users should be able to configure the directory structure (e.g., include dashboard/environment names) and file naming patterns (e.g., include timestamps).
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- **FR-001**: System MUST allow configuring a local filesystem root path for storing artifacts.
|
||||
- **FR-002**: The default storage path MUST be configured such that it does not interfere with the application's git repository (e.g., a directory outside the workspace or explicitly git-ignored).
|
||||
- **FR-003**: System MUST enforce a directory structure within the storage root: `backups/` for dashboard backups and `repositories/` for exported repositories.
|
||||
- **FR-004**: System MUST provide a Web UI to list files and folders, organized by their type (Backup vs Repository).
|
||||
- **FR-005**: System MUST display file metadata in the UI: Filename, Size, Creation Date.
|
||||
- **FR-006**: System MUST allow users to download files from the storage directory (including subdirectories) via the Web UI.
|
||||
- **FR-007**: System MUST allow users to delete files from the storage directory via the Web UI.
|
||||
- **FR-008**: System MUST allow users to upload files to the specific folder in the storage directory via the Web UI.
|
||||
- **FR-013**: System MUST support navigating through the directory hierarchy within the allowed categories.
|
||||
- **FR-014**: System MUST display breadcrumbs or similar navigation aid to show current path.
|
||||
- **FR-009**: System MUST validate that the configured storage path is accessible and writable.
|
||||
- **FR-010**: System MUST prevent access to files outside the configured storage directory (Path Traversal protection).
|
||||
- **FR-011**: System MUST allow configuring the directory structure pattern for backups and repositories (e.g., `{environment}/{dashboard_name}/`).
|
||||
- **FR-012**: System MUST allow configuring the filename pattern for generated files (e.g., `{dashboard_name}_{timestamp}.zip`).
|
||||
|
||||
### Key Entities
|
||||
|
||||
- **StorageConfig**: Settings defining the root directory path.
|
||||
- **StoredFile**: Conceptual representation of a file (Name, Path, Size, CreatedAt, MimeType).
|
||||
|
||||
### Assumptions
|
||||
|
||||
- The application server has access to a writable local filesystem.
|
||||
- Users utilizing this feature have appropriate permissions within the application to manage system-wide storage settings and files.
|
||||
|
||||
## Success Criteria *(mandatory)*
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001**: Users can successfully upload and then download a file of at least 50MB size.
|
||||
- **SC-002**: Files created or uploaded via the system do not appear in the application's `git status` output by default.
|
||||
- **SC-003**: File list loads in under 1 second for a directory containing 100 files.
|
||||
- **SC-004**: Users can delete a file via UI and confirm it is physically removed from the disk.
|
||||
96
specs/014-file-storage-ui/tasks.md
Normal file
96
specs/014-file-storage-ui/tasks.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# Tasks: File Storage Management & UI
|
||||
|
||||
**Branch**: `014-file-storage-ui` | **Spec**: [specs/014-file-storage-ui/spec.md](../014-file-storage-ui/spec.md)
|
||||
|
||||
## Phase 1: Setup
|
||||
*Goal: Initialize backend plugin structure and frontend route scaffolding.*
|
||||
|
||||
- [x] T001 Create storage plugin directory and `__init__.py` in `backend/src/plugins/storage/`
|
||||
- [x] T002 Create storage models file `backend/src/models/storage.py` with `StorageConfig` and `StoredFile` Pydantic models
|
||||
- [x] T003 Create empty storage route handler `backend/src/api/routes/storage.py` and register in `backend/src/api/routes/__init__.py`
|
||||
- [x] T004 Create frontend storage route directory `frontend/src/routes/tools/storage/` and empty `+page.svelte`
|
||||
- [x] T005 Create frontend service `frontend/src/services/storageService.js` stub
|
||||
|
||||
## Phase 2: Foundational
|
||||
*Goal: Implement core backend logic for storage management, configuration, and security.*
|
||||
|
||||
- [x] T006 Implement `StoragePlugin` class in `backend/src/plugins/storage/plugin.py` inheriting from `PluginBase`
|
||||
- [x] T007 Implement `get_storage_root()` method in `StoragePlugin` with default path logic (`../ss-tools-storage`)
|
||||
- [x] T008 Implement `ensure_directories()` method to create `backups/` and `repositories/` subfolders on init
|
||||
- [x] T009 Implement path traversal protection helper `validate_path(path)` in `StoragePlugin`
|
||||
- [x] T010 Implement `list_files(category)` method in `StoragePlugin` returning `StoredFile` objects
|
||||
- [x] T011 Implement `save_file(file, category)` method in `StoragePlugin` handling uploads
|
||||
- [x] T012 Implement `delete_file(category, filename)` method in `StoragePlugin`
|
||||
- [x] T013 Implement `get_file_path(category, filename)` method in `StoragePlugin` for downloads
|
||||
- [x] T014 Register `StoragePlugin` in `backend/src/core/plugin_loader.py` (if manual registration needed)
|
||||
|
||||
## Phase 3: User Story 1 - File Management Dashboard (Priority: P1)
|
||||
*Goal: Enable users to list, upload, download, and delete files via Web UI.*
|
||||
|
||||
### Backend Endpoints
|
||||
- [x] T015 [US1] Implement `GET /api/storage/files` endpoint in `backend/src/api/routes/storage.py` using `StoragePlugin.list_files`
|
||||
- [x] T016 [US1] Implement `POST /api/storage/upload` endpoint in `backend/src/api/routes/storage.py` using `StoragePlugin.save_file`
|
||||
- [x] T017 [US1] Implement `DELETE /api/storage/files/{category}/{filename}` endpoint in `backend/src/api/routes/storage.py`
|
||||
- [x] T018 [US1] Implement `GET /api/storage/download/{category}/{filename}` endpoint in `backend/src/api/routes/storage.py`
|
||||
|
||||
### Frontend Implementation
|
||||
- [x] T019 [US1] Implement `listFiles`, `uploadFile`, `deleteFile`, `downloadFileUrl` in `frontend/src/services/storageService.js`
|
||||
- [x] T020 [US1] Create `frontend/src/components/storage/FileList.svelte` to display files in a table with metadata
|
||||
- [x] T021 [US1] Create `frontend/src/components/storage/FileUpload.svelte` with category selection and drag-drop support
|
||||
- [x] T022 [US1] Implement main logic in `frontend/src/routes/tools/storage/+page.svelte` to fetch files and handle tabs (Backups vs Repositories)
|
||||
- [x] T023 [US1] Integrate `FileList` and `FileUpload` components into `+page.svelte`
|
||||
|
||||
## Phase 4: User Story 2 - Storage Location Configuration (Priority: P2)
|
||||
*Goal: Allow administrators to configure the storage root path via Settings.*
|
||||
|
||||
### Backend
|
||||
- [x] T024 [US2] Add `storage_path` field to main configuration model in `backend/src/core/config_models.py` (if not using separate storage config)
|
||||
- [x] T025 [US2] Implement `GET /api/settings/storage` and `PUT /api/settings/storage` endpoints in `backend/src/api/routes/settings.py` (or `storage.py`)
|
||||
- [x] T026 [US2] Update `StoragePlugin` to read root path from global configuration instead of hardcoded default
|
||||
- [x] T027 [US2] Add validation logic to `PUT` endpoint to ensure new path is writable
|
||||
|
||||
### Frontend
|
||||
- [x] T028 [US2] Add `getStorageConfig` and `updateStorageConfig` to `frontend/src/services/storageService.js`
|
||||
- [x] T029 [US2] Create configuration section in `frontend/src/routes/settings/+page.svelte` (or dedicated Storage Settings component)
|
||||
- [x] T030 [US2] Implement form to update storage path with validation feedback
|
||||
- [x] T031 [US2] Add configuration fields for directory structure and filename patterns in `backend/src/models/storage.py` and `frontend/src/routes/settings/+page.svelte`
|
||||
- [x] T032 [US2] Implement logic in `StoragePlugin` to resolve dynamic paths based on configured patterns
|
||||
|
||||
## Phase 5: Polish & Cross-Cutting
|
||||
*Goal: Finalize UI/UX and ensure robustness.*
|
||||
|
||||
- [x] T033 Add link to "File Storage" in main navigation `frontend/src/components/Navbar.svelte`
|
||||
- [x] T034 Add error handling toasts for failed uploads or file operations
|
||||
- [x] T035 Verify large file upload support (50MB+) in Nginx/FastAPI config if applicable
|
||||
- [x] T036 Add confirmation modal for file deletion
|
||||
|
||||
## Phase 6: Folder Structure Support (Refactor)
|
||||
*Goal: Enable hierarchical navigation, nested file management, and downloading.*
|
||||
|
||||
- [x] T037 Refactor `StoragePlugin.list_files` in `backend/src/plugins/storage/plugin.py` to accept `subpath` and return directories/files
|
||||
- [x] T038 Refactor `StoragePlugin` methods (`save_file`, `delete_file`, `get_file_path`) to support nested paths
|
||||
- [x] T039 Update backend endpoints in `backend/src/api/routes/storage.py` (`GET /files`, `POST /upload`, `DELETE /files`, `GET /download`) to accept `path` parameter
|
||||
- [x] T040 Update `frontend/src/services/storageService.js` to pass `path` argument in all API calls
|
||||
- [x] T041 Update `frontend/src/components/storage/FileList.svelte` to display folder icons, handle navigation events, and show breadcrumbs
|
||||
- [x] T042 Update `frontend/src/components/storage/FileUpload.svelte` to upload to the currently active directory
|
||||
- [x] T043 Update `frontend/src/routes/tools/storage/+page.svelte` to manage current path state and handle navigation logic
|
||||
|
||||
## Dependencies
|
||||
|
||||
1. **Phase 1 (Setup)**: No dependencies.
|
||||
2. **Phase 2 (Foundational)**: Depends on Phase 1.
|
||||
3. **Phase 3 (US1)**: Depends on Phase 2.
|
||||
4. **Phase 4 (US2)**: Depends on Phase 2. Can run parallel to Phase 3.
|
||||
5. **Phase 5 (Polish)**: Depends on Phase 3 and 4.
|
||||
6. **Phase 6 (Refactor)**: Depends on Phase 3.
|
||||
|
||||
## Parallel Execution Examples
|
||||
|
||||
- **Backend/Frontend Split**: T015-T018 (Backend Endpoints) can be developed in parallel with T020-T021 (Frontend Components) using mock data.
|
||||
- **Story Split**: US1 (File Management) and US2 (Configuration) are largely independent after Phase 2 is complete.
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
1. **MVP**: Complete Phases 1, 2, and 3. This delivers a working file manager with a default storage location.
|
||||
2. **Full Feature**: Complete Phase 4 to allow path configuration.
|
||||
3. **Polish**: Complete Phase 5 for better UX.
|
||||
@@ -51,18 +51,6 @@
|
||||
- 📝 Generates the token-optimized project map.
|
||||
- ƒ **_write_entity_md** (`Function`)
|
||||
- 📝 Recursive helper to write entity tree to Markdown.
|
||||
- 📦 **main** (`Module`)
|
||||
- 📝 Entry point for the Svelte application.
|
||||
- 🏗️ Layer: UI-Entry
|
||||
- 📦 **app_instance** (`Data`)
|
||||
- 📝 Initialized Svelte app instance.
|
||||
- 🧩 **App** (`Component`)
|
||||
- 📝 The root component of the frontend application. Manages navigation and layout.
|
||||
- 🏗️ Layer: UI
|
||||
- ƒ **handleFormSubmit** (`Function`)
|
||||
- 📝 Handles form submission for task creation.
|
||||
- ƒ **navigate** (`Function`)
|
||||
- 📝 Changes the current page and resets state.
|
||||
- 📦 **stores_module** (`Module`)
|
||||
- 📝 Global state management using Svelte stores.
|
||||
- 🏗️ Layer: UI-State
|
||||
@@ -104,6 +92,36 @@
|
||||
- 📝 Generic request wrapper.
|
||||
- 📦 **api** (`Data`)
|
||||
- 📝 API client object with specific methods.
|
||||
- 🧩 **Select** (`Component`)
|
||||
- 📝 Standardized dropdown selection component.
|
||||
- 🏗️ Layer: Atom
|
||||
- 📦 **ui** (`Module`)
|
||||
- 📝 Central export point for standardized UI components.
|
||||
- 🏗️ Layer: Atom
|
||||
- 🧩 **PageHeader** (`Component`)
|
||||
- 📝 Standardized page header with title and action area.
|
||||
- 🏗️ Layer: Atom
|
||||
- 🧩 **Card** (`Component`)
|
||||
- 📝 Standardized container with padding and elevation.
|
||||
- 🏗️ Layer: Atom
|
||||
- 🧩 **Button** (`Component`)
|
||||
- 📝 Define component interface and default values.
|
||||
- 🏗️ Layer: Atom
|
||||
- 🧩 **Input** (`Component`)
|
||||
- 📝 Standardized text input component with label and error handling.
|
||||
- 🏗️ Layer: Atom
|
||||
- 🧩 **LanguageSwitcher** (`Component`)
|
||||
- 📝 Dropdown component to switch between supported languages.
|
||||
- 🏗️ Layer: Atom
|
||||
- 📦 **i18n** (`Module`)
|
||||
- 📝 Determines the starting locale.
|
||||
- 🏗️ Layer: Infra
|
||||
- 🔗 DEPENDS_ON -> `locales/ru.json`
|
||||
- 🔗 DEPENDS_ON -> `locales/en.json`
|
||||
- 📦 **locale** (`Store`)
|
||||
- 📝 Holds the current active locale string.
|
||||
- 📦 **t** (`Store`)
|
||||
- 📝 Derived store providing the translation dictionary.
|
||||
- ƒ **selectPlugin** (`Function`)
|
||||
- 📝 Handles plugin selection and navigation.
|
||||
- ƒ **handleFormSubmit** (`Function`)
|
||||
@@ -149,6 +167,17 @@
|
||||
- 📝 Fetches databases from both environments and gets suggestions.
|
||||
- ƒ **handleUpdate** (`Function`)
|
||||
- 📝 Saves a mapping to the backend.
|
||||
- 🧩 **StoragePage** (`Component`)
|
||||
- 📝 Main page for file storage management.
|
||||
- 🏗️ Layer: Feature
|
||||
- ƒ **loadFiles** (`Function`)
|
||||
- 📝 Fetches the list of files from the server.
|
||||
- ƒ **handleDelete** (`Function`)
|
||||
- 📝 Handles the file deletion process.
|
||||
- ƒ **handleNavigate** (`Function`)
|
||||
- 📝 Updates the current path and reloads files when navigating into a directory.
|
||||
- ƒ **navigateUp** (`Function`)
|
||||
- 📝 Navigates one level up in the directory structure.
|
||||
- 🧩 **SearchPage** (`Component`)
|
||||
- 📝 Page for the dataset search tool.
|
||||
- 🏗️ Layer: UI
|
||||
@@ -160,6 +189,8 @@
|
||||
- 🏗️ Layer: UI
|
||||
- ƒ **handleSaveGlobal** (`Function`)
|
||||
- 📝 Saves global application settings.
|
||||
- ƒ **handleSaveStorage** (`Function`)
|
||||
- 📝 Saves storage-specific settings.
|
||||
- ƒ **handleAddOrUpdateEnv** (`Function`)
|
||||
- 📝 Adds a new environment or updates an existing one.
|
||||
- ƒ **handleDeleteEnv** (`Function`)
|
||||
@@ -177,6 +208,24 @@
|
||||
- 🏗️ Layer: UI
|
||||
- ƒ **handleSuccess** (`Function`)
|
||||
- 📝 Refreshes the connection list after a successful creation.
|
||||
- 🧩 **GitSettingsPage** (`Component`)
|
||||
- 📝 Manage Git server configurations for dashboard versioning.
|
||||
- 🏗️ Layer: Page
|
||||
- ƒ **loadConfigs** (`Function`)
|
||||
- 📝 Fetches existing git configurations.
|
||||
- ƒ **handleTest** (`Function`)
|
||||
- 📝 Tests connection to a git server with current form data.
|
||||
- ƒ **handleSave** (`Function`)
|
||||
- 📝 Saves a new git configuration.
|
||||
- ƒ **handleDelete** (`Function`)
|
||||
- 📝 Deletes a git configuration by ID.
|
||||
- 🧩 **GitDashboardPage** (`Component`)
|
||||
- 📝 Dashboard management page for Git integration.
|
||||
- 🏗️ Layer: Page
|
||||
- ƒ **fetchEnvironments** (`Function`)
|
||||
- 📝 Fetches the list of deployment environments from the API.
|
||||
- ƒ **fetchDashboards** (`Function`)
|
||||
- 📝 Fetches dashboards for a specific environment.
|
||||
- 🧩 **Dashboard** (`Component`)
|
||||
- 📝 Displays the list of available plugins and allows selecting one.
|
||||
- 🏗️ Layer: UI
|
||||
@@ -207,6 +256,11 @@
|
||||
- 📝 Create a new connection configuration.
|
||||
- ƒ **deleteConnection** (`Function`)
|
||||
- 📝 Delete a connection configuration.
|
||||
- 📦 **GitServiceClient** (`Module`)
|
||||
- 📝 API client for Git operations, managing the communication between frontend and backend.
|
||||
- 🏗️ Layer: Service
|
||||
- 📦 **gitService** (`Action`)
|
||||
- 📝 Retrieves the diff for specific files or the whole repository.
|
||||
- ƒ **runTask** (`Function`)
|
||||
- 📝 Start a new task for a given plugin.
|
||||
- ƒ **getTaskStatus** (`Function`)
|
||||
@@ -223,6 +277,17 @@
|
||||
- 📝 Resolve a task that is awaiting mapping.
|
||||
- ƒ **clearTasks** (`Function`)
|
||||
- 📝 Clear tasks based on status.
|
||||
- 📦 **storageService** (`Module`)
|
||||
- 📝 Frontend API client for file storage management.
|
||||
- 🏗️ Layer: Service
|
||||
- ƒ **listFiles** (`Function`)
|
||||
- 📝 Fetches the list of files for a given category and subpath.
|
||||
- ƒ **uploadFile** (`Function`)
|
||||
- 📝 Uploads a file to the storage system.
|
||||
- ƒ **deleteFile** (`Function`)
|
||||
- 📝 Deletes a file or directory from storage.
|
||||
- ƒ **downloadFileUrl** (`Function`)
|
||||
- 📝 Returns the URL for downloading a file.
|
||||
- 🧩 **PasswordPrompt** (`Component`)
|
||||
- 📝 A modal component to prompt the user for database passwords when a migration task is paused.
|
||||
- 🏗️ Layer: UI
|
||||
@@ -273,6 +338,8 @@
|
||||
- 📝 Handles select all checkbox.
|
||||
- ƒ **goToPage** (`Function`)
|
||||
- 📝 Changes current page.
|
||||
- ƒ **openGit** (`Function`)
|
||||
- 📝 Opens the Git management modal for a dashboard.
|
||||
- 🧩 **Navbar** (`Component`)
|
||||
- 📝 Main navigation bar for the application.
|
||||
- 🏗️ Layer: UI
|
||||
@@ -334,6 +401,22 @@
|
||||
- 🏗️ Layer: Feature
|
||||
- ƒ **handleSelect** (`Function`)
|
||||
- 📝 Dispatches the selection change event.
|
||||
- 🧩 **FileList** (`Component`)
|
||||
- 📝 Displays a table of files with metadata and actions.
|
||||
- 🏗️ Layer: Component
|
||||
- ƒ **isDirectory** (`Function`)
|
||||
- 📝 Checks if a file object represents a directory.
|
||||
- ƒ **formatSize** (`Function`)
|
||||
- 📝 Formats file size in bytes into a human-readable string.
|
||||
- ƒ **formatDate** (`Function`)
|
||||
- 📝 Formats an ISO date string into a localized readable format.
|
||||
- 🧩 **FileUpload** (`Component`)
|
||||
- 📝 Provides a form for uploading files to a specific category.
|
||||
- 🏗️ Layer: Component
|
||||
- ƒ **handleUpload** (`Function`)
|
||||
- 📝 Handles the file upload process.
|
||||
- ƒ **handleDrop** (`Function`)
|
||||
- 📝 Handles the file drop event for drag-and-drop.
|
||||
- 🧩 **ConnectionForm** (`Component`)
|
||||
- 📝 UI component for creating a new database connection configuration.
|
||||
- 🏗️ Layer: UI
|
||||
@@ -373,6 +456,66 @@
|
||||
- 📝 Triggers the SearchPlugin task.
|
||||
- ƒ **startPolling** (`Function`)
|
||||
- 📝 Polls for task completion and results.
|
||||
- 🧩 **CommitHistory** (`Component`)
|
||||
- 📝 Displays the commit history for a specific dashboard.
|
||||
- 🏗️ Layer: Component
|
||||
- ƒ **onMount** (`Function`)
|
||||
- 📝 Load history when component is mounted.
|
||||
- ƒ **loadHistory** (`Function`)
|
||||
- 📝 Fetch commit history from the backend.
|
||||
- 🧩 **DeploymentModal** (`Component`)
|
||||
- 📝 Modal for deploying a dashboard to a target environment.
|
||||
- 🏗️ Layer: Component
|
||||
- 📦 **loadStatus** (`Watcher`)
|
||||
- ƒ **loadEnvironments** (`Function`)
|
||||
- 📝 Fetch available environments from API.
|
||||
- ƒ **handleDeploy** (`Function`)
|
||||
- 📝 Trigger deployment to selected environment.
|
||||
- 🧩 **ConflictResolver** (`Component`)
|
||||
- 📝 UI for resolving merge conflicts (Keep Mine / Keep Theirs).
|
||||
- 🏗️ Layer: Component
|
||||
- ƒ **resolve** (`Function`)
|
||||
- 📝 Set resolution strategy for a file.
|
||||
- ƒ **handleSave** (`Function`)
|
||||
- 📝 Validate and submit resolutions.
|
||||
- 🧩 **CommitModal** (`Component`)
|
||||
- 📝 Модальное окно для создания коммита с просмотром изменений (diff).
|
||||
- 🏗️ Layer: Component
|
||||
- ƒ **loadStatus** (`Function`)
|
||||
- 📝 Загружает текущий статус репозитория и diff.
|
||||
- ƒ **handleCommit** (`Function`)
|
||||
- 📝 Создает коммит с указанным сообщением.
|
||||
- 🧩 **BranchSelector** (`Component`)
|
||||
- 📝 UI для выбора и создания веток Git.
|
||||
- 🏗️ Layer: Component
|
||||
- ƒ **onMount** (`Function`)
|
||||
- 📝 Load branches when component is mounted.
|
||||
- ƒ **loadBranches** (`Function`)
|
||||
- 📝 Загружает список веток для дашборда.
|
||||
- ƒ **handleSelect** (`Function`)
|
||||
- 📝 Handles branch selection from dropdown.
|
||||
- ƒ **handleCheckout** (`Function`)
|
||||
- 📝 Переключает текущую ветку.
|
||||
- ƒ **handleCreate** (`Function`)
|
||||
- 📝 Создает новую ветку.
|
||||
- 🧩 **GitManager** (`Component`)
|
||||
- 📝 Центральный компонент для управления Git-операциями конкретного дашборда.
|
||||
- 🏗️ Layer: Component
|
||||
- ƒ **checkStatus** (`Function`)
|
||||
- 📝 Проверяет, инициализирован ли репозиторий для данного дашборда.
|
||||
- ƒ **handleInit** (`Function`)
|
||||
- 📝 Инициализирует репозиторий для дашборда.
|
||||
- ƒ **handleSync** (`Function`)
|
||||
- 📝 Синхронизирует состояние Superset с локальным Git-репозиторием.
|
||||
- ƒ **handlePush** (`Function`)
|
||||
- 📝 Pushes local commits to the remote repository.
|
||||
- ƒ **handlePull** (`Function`)
|
||||
- 📝 Pulls changes from the remote repository.
|
||||
- 📦 **backend.delete_running_tasks** (`Module`)
|
||||
- 📝 Script to delete tasks with RUNNING status from the database.
|
||||
- 🏗️ Layer: Utility
|
||||
- ƒ **delete_running_tasks** (`Function`)
|
||||
- 📝 Delete all tasks with RUNNING status from the database.
|
||||
- 📦 **AppModule** (`Module`)
|
||||
- 📝 The main entry point for the FastAPI application. It initializes the app, configures CORS, sets up dependencies, includes API routers, and defines the WebSocket endpoint for log streaming.
|
||||
- 🏗️ Layer: UI (API)
|
||||
@@ -439,13 +582,21 @@
|
||||
- ƒ **get_database_by_uuid** (`Function`)
|
||||
- 📝 Find a database by its UUID.
|
||||
- ƒ **_resolve_target_id_for_delete** (`Function`)
|
||||
- 📝 Resolves a dashboard ID from either an ID or a slug.
|
||||
- ƒ **_do_import** (`Function`)
|
||||
- 📝 Performs the actual multipart upload for import.
|
||||
- ƒ **_validate_export_response** (`Function`)
|
||||
- 📝 Validates that the export response is a non-empty ZIP archive.
|
||||
- ƒ **_resolve_export_filename** (`Function`)
|
||||
- 📝 Determines the filename for an exported dashboard.
|
||||
- ƒ **_validate_query_params** (`Function`)
|
||||
- 📝 Ensures query parameters have default page and page_size.
|
||||
- ƒ **_fetch_total_object_count** (`Function`)
|
||||
- 📝 Fetches the total number of items for a given endpoint.
|
||||
- ƒ **_fetch_all_pages** (`Function`)
|
||||
- 📝 Iterates through all pages to collect all data items.
|
||||
- ƒ **_validate_import_file** (`Function`)
|
||||
- 📝 Validates that the file to be imported is a valid ZIP with metadata.yaml.
|
||||
- 📦 **ConfigManagerModule** (`Module`)
|
||||
- 📝 Manages application configuration, including loading/saving to JSON and CRUD for environments.
|
||||
- 🏗️ Layer: Core
|
||||
@@ -603,44 +754,89 @@
|
||||
- 🔗 DEPENDS_ON -> `backend.src.core.logger`
|
||||
- 🔗 DEPENDS_ON -> `pyyaml`
|
||||
- ℂ **InvalidZipFormatError** (`Class`)
|
||||
- ƒ **create_temp_file** (`Function`)
|
||||
- 📝 Контекстный менеджер для создания временного файла или директории с гарантированным удалением.
|
||||
- ƒ **remove_empty_directories** (`Function`)
|
||||
- 📝 Рекурсивно удаляет все пустые поддиректории, начиная с указанного пути.
|
||||
- ƒ **read_dashboard_from_disk** (`Function`)
|
||||
- 📝 Читает бинарное содержимое файла с диска.
|
||||
- ƒ **calculate_crc32** (`Function`)
|
||||
- 📝 Вычисляет контрольную сумму CRC32 для файла.
|
||||
- 📦 **RetentionPolicy** (`DataClass`)
|
||||
- 📝 Определяет политику хранения для архивов (ежедневные, еженедельные, ежемесячные).
|
||||
- ƒ **archive_exports** (`Function`)
|
||||
- 📝 Управляет архивом экспортированных файлов, применяя политику хранения и дедупликацию.
|
||||
- 🔗 CALLS -> `apply_retention_policy`
|
||||
- 🔗 CALLS -> `calculate_crc32`
|
||||
- ƒ **apply_retention_policy** (`Function`)
|
||||
- 📝 (Helper) Применяет политику хранения к списку файлов, возвращая те, что нужно сохранить.
|
||||
- ƒ **save_and_unpack_dashboard** (`Function`)
|
||||
- 📝 Сохраняет бинарное содержимое ZIP-архива на диск и опционально распаковывает его.
|
||||
- ƒ **update_yamls** (`Function`)
|
||||
- 📝 Обновляет конфигурации в YAML-файлах, заменяя значения или применяя regex.
|
||||
- 🔗 CALLS -> `_update_yaml_file`
|
||||
- ƒ **_update_yaml_file** (`Function`)
|
||||
- 📝 (Helper) Обновляет один YAML файл.
|
||||
- ƒ **create_dashboard_export** (`Function`)
|
||||
- 📝 Создает ZIP-архив из указанных исходных путей.
|
||||
- ƒ **sanitize_filename** (`Function`)
|
||||
- 📝 Очищает строку от символов, недопустимых в именах файлов.
|
||||
- ƒ **get_filename_from_headers** (`Function`)
|
||||
- 📝 Извлекает имя файла из HTTP заголовка 'Content-Disposition'.
|
||||
- ƒ **consolidate_archive_folders** (`Function`)
|
||||
- 📝 Консолидирует директории архивов на основе общего слага в имени.
|
||||
- 📝 Exception raised when a file is not a valid ZIP archive.
|
||||
- ƒ **create_temp_file** (`Function`)
|
||||
- 📝 Контекстный менеджер для создания временного файла или директории с гарантированным удалением.
|
||||
- ƒ **remove_empty_directories** (`Function`)
|
||||
- 📝 Рекурсивно удаляет все пустые поддиректории, начиная с указанного пути.
|
||||
- ƒ **read_dashboard_from_disk** (`Function`)
|
||||
- 📝 Читает бинарное содержимое файла с диска.
|
||||
- ƒ **calculate_crc32** (`Function`)
|
||||
- 📝 Вычисляет контрольную сумму CRC32 для файла.
|
||||
- 📦 **RetentionPolicy** (`DataClass`)
|
||||
- 📝 Определяет политику хранения для архивов (ежедневные, еженедельные, ежемесячные).
|
||||
- ƒ **archive_exports** (`Function`)
|
||||
- 📝 Управляет архивом экспортированных файлов, применяя политику хранения и дедупликацию.
|
||||
- 🔗 CALLS -> `apply_retention_policy`
|
||||
- 🔗 CALLS -> `calculate_crc32`
|
||||
- ƒ **apply_retention_policy** (`Function`)
|
||||
- 📝 (Helper) Применяет политику хранения к списку файлов, возвращая те, что нужно сохранить.
|
||||
- ƒ **save_and_unpack_dashboard** (`Function`)
|
||||
- 📝 Сохраняет бинарное содержимое ZIP-архива на диск и опционально распаковывает его.
|
||||
- ƒ **update_yamls** (`Function`)
|
||||
- 📝 Обновляет конфигурации в YAML-файлах, заменяя значения или применяя regex.
|
||||
- 🔗 CALLS -> `_update_yaml_file`
|
||||
- ƒ **_update_yaml_file** (`Function`)
|
||||
- 📝 (Helper) Обновляет один YAML файл.
|
||||
- ƒ **replacer** (`Function`)
|
||||
- 📝 Функция замены, сохраняющая кавычки если они были.
|
||||
- ƒ **create_dashboard_export** (`Function`)
|
||||
- 📝 Создает ZIP-архив из указанных исходных путей.
|
||||
- ƒ **sanitize_filename** (`Function`)
|
||||
- 📝 Очищает строку от символов, недопустимых в именах файлов.
|
||||
- ƒ **get_filename_from_headers** (`Function`)
|
||||
- 📝 Извлекает имя файла из HTTP заголовка 'Content-Disposition'.
|
||||
- ƒ **consolidate_archive_folders** (`Function`)
|
||||
- 📝 Консолидирует директории архивов на основе общего слага в имени.
|
||||
- 📦 **backend.core.utils.network** (`Module`)
|
||||
- 📝 Инкапсулирует низкоуровневую HTTP-логику для взаимодействия с Superset API, включая аутентификацию, управление сессией, retry-логику и обработку ошибок.
|
||||
- 🏗️ Layer: Infra
|
||||
- 🔗 DEPENDS_ON -> `backend.src.core.logger`
|
||||
- 🔗 DEPENDS_ON -> `requests`
|
||||
- ℂ **SupersetAPIError** (`Class`)
|
||||
- ℂ **AuthenticationError** (`Class`)
|
||||
- 📝 Base exception for all Superset API related errors.
|
||||
- ƒ **__init__** (`Function`)
|
||||
- 📝 Initializes the exception with a message and context.
|
||||
- ℂ **AuthenticationError** (`Class`)
|
||||
- 📝 Exception raised when authentication fails.
|
||||
- ƒ **__init__** (`Function`)
|
||||
- 📝 Initializes the authentication error.
|
||||
- ℂ **PermissionDeniedError** (`Class`)
|
||||
- 📝 Exception raised when access is denied.
|
||||
- ƒ **__init__** (`Function`)
|
||||
- 📝 Initializes the permission denied error.
|
||||
- ℂ **DashboardNotFoundError** (`Class`)
|
||||
- 📝 Exception raised when a dashboard cannot be found.
|
||||
- ƒ **__init__** (`Function`)
|
||||
- 📝 Initializes the not found error with resource ID.
|
||||
- ℂ **NetworkError** (`Class`)
|
||||
- 📝 Exception raised when a network level error occurs.
|
||||
- ƒ **__init__** (`Function`)
|
||||
- 📝 Initializes the network error.
|
||||
- ℂ **APIClient** (`Class`)
|
||||
- 📝 Инкапсулирует HTTP-логику для работы с API, включая сессии, аутентификацию, и обработку запросов.
|
||||
- ƒ **__init__** (`Function`)
|
||||
- 📝 Инициализирует API клиент с конфигурацией, сессией и логгером.
|
||||
- ƒ **_init_session** (`Function`)
|
||||
- 📝 Создает и настраивает `requests.Session` с retry-логикой.
|
||||
- ƒ **authenticate** (`Function`)
|
||||
- 📝 Выполняет аутентификацию в Superset API и получает access и CSRF токены.
|
||||
- ƒ **headers** (`Function`)
|
||||
- 📝 Возвращает HTTP-заголовки для аутентифицированных запросов.
|
||||
- ƒ **request** (`Function`)
|
||||
- 📝 Выполняет универсальный HTTP-запрос к API.
|
||||
- ƒ **_handle_http_error** (`Function`)
|
||||
- 📝 (Helper) Преобразует HTTP ошибки в кастомные исключения.
|
||||
- ƒ **_handle_network_error** (`Function`)
|
||||
- 📝 (Helper) Преобразует сетевые ошибки в `NetworkError`.
|
||||
- ƒ **upload_file** (`Function`)
|
||||
- 📝 Загружает файл на сервер через multipart/form-data.
|
||||
- ƒ **_perform_upload** (`Function`)
|
||||
- 📝 (Helper) Выполняет POST запрос с файлом.
|
||||
- ƒ **fetch_paginated_count** (`Function`)
|
||||
- 📝 Получает общее количество элементов для пагинации.
|
||||
- ƒ **fetch_paginated_data** (`Function`)
|
||||
- 📝 Автоматически собирает данные со всех страниц пагинированного эндпоинта.
|
||||
- 📦 **backend.src.core.utils.matching** (`Module`)
|
||||
- 📝 Provides utility functions for fuzzy matching database names.
|
||||
- 🏗️ Layer: Core
|
||||
@@ -749,6 +945,43 @@
|
||||
- 🏗️ Layer: UI (API)
|
||||
- ƒ **get_current_user** (`Function`)
|
||||
- 📝 Dependency to get the current user from the ADFS token.
|
||||
- 📦 **backend.src.api.routes.git** (`Module`)
|
||||
- 📝 Provides FastAPI endpoints for Git integration operations.
|
||||
- 🏗️ Layer: API
|
||||
- ƒ **get_git_configs** (`Function`)
|
||||
- 📝 List all configured Git servers.
|
||||
- ƒ **create_git_config** (`Function`)
|
||||
- 📝 Register a new Git server configuration.
|
||||
- ƒ **delete_git_config** (`Function`)
|
||||
- 📝 Remove a Git server configuration.
|
||||
- ƒ **test_git_config** (`Function`)
|
||||
- 📝 Validate connection to a Git server using provided credentials.
|
||||
- ƒ **init_repository** (`Function`)
|
||||
- 📝 Link a dashboard to a Git repository and perform initial clone/init.
|
||||
- ƒ **get_branches** (`Function`)
|
||||
- 📝 List all branches for a dashboard's repository.
|
||||
- ƒ **create_branch** (`Function`)
|
||||
- 📝 Create a new branch in the dashboard's repository.
|
||||
- ƒ **checkout_branch** (`Function`)
|
||||
- 📝 Switch the dashboard's repository to a specific branch.
|
||||
- ƒ **commit_changes** (`Function`)
|
||||
- 📝 Stage and commit changes in the dashboard's repository.
|
||||
- ƒ **push_changes** (`Function`)
|
||||
- 📝 Push local commits to the remote repository.
|
||||
- ƒ **pull_changes** (`Function`)
|
||||
- 📝 Pull changes from the remote repository.
|
||||
- ƒ **sync_dashboard** (`Function`)
|
||||
- 📝 Sync dashboard state from Superset to Git using the GitPlugin.
|
||||
- ƒ **get_environments** (`Function`)
|
||||
- 📝 List all deployment environments.
|
||||
- ƒ **deploy_dashboard** (`Function`)
|
||||
- 📝 Deploy dashboard from Git to a target environment.
|
||||
- ƒ **get_history** (`Function`)
|
||||
- 📝 View commit history for a dashboard's repository.
|
||||
- ƒ **get_repository_status** (`Function`)
|
||||
- 📝 Get current Git status for a dashboard repository.
|
||||
- ƒ **get_repository_diff** (`Function`)
|
||||
- 📝 Get Git diff for a dashboard repository.
|
||||
- 📦 **ConnectionsRouter** (`Module`)
|
||||
- 📝 Defines the FastAPI router for managing external database connections.
|
||||
- 🏗️ Layer: UI (API)
|
||||
@@ -814,6 +1047,10 @@
|
||||
- 📝 Retrieves all application settings.
|
||||
- ƒ **update_global_settings** (`Function`)
|
||||
- 📝 Updates global application settings.
|
||||
- ƒ **get_storage_settings** (`Function`)
|
||||
- 📝 Retrieves storage-specific settings.
|
||||
- ƒ **update_storage_settings** (`Function`)
|
||||
- 📝 Updates storage-specific settings.
|
||||
- ƒ **get_environments** (`Function`)
|
||||
- 📝 Lists all configured Superset environments.
|
||||
- ƒ **add_environment** (`Function`)
|
||||
@@ -824,8 +1061,52 @@
|
||||
- 📝 Deletes a Superset environment.
|
||||
- ƒ **test_environment_connection** (`Function`)
|
||||
- 📝 Tests the connection to a Superset environment.
|
||||
- ƒ **validate_backup_path** (`Function`)
|
||||
- 📝 Validates if a backup path exists and is writable.
|
||||
- 📦 **backend.src.api.routes.git_schemas** (`Module`)
|
||||
- 📝 Defines Pydantic models for the Git integration API layer.
|
||||
- 🏗️ Layer: API
|
||||
- 🔗 DEPENDS_ON -> `backend.src.models.git`
|
||||
- ℂ **GitServerConfigBase** (`Class`)
|
||||
- 📝 Base schema for Git server configuration attributes.
|
||||
- ℂ **GitServerConfigCreate** (`Class`)
|
||||
- 📝 Schema for creating a new Git server configuration.
|
||||
- ℂ **GitServerConfigSchema** (`Class`)
|
||||
- 📝 Schema for representing a Git server configuration with metadata.
|
||||
- ℂ **GitRepositorySchema** (`Class`)
|
||||
- 📝 Schema for tracking a local Git repository linked to a dashboard.
|
||||
- ℂ **BranchSchema** (`Class`)
|
||||
- 📝 Schema for representing a Git branch metadata.
|
||||
- ℂ **CommitSchema** (`Class`)
|
||||
- 📝 Schema for representing Git commit details.
|
||||
- ℂ **BranchCreate** (`Class`)
|
||||
- 📝 Schema for branch creation requests.
|
||||
- ℂ **BranchCheckout** (`Class`)
|
||||
- 📝 Schema for branch checkout requests.
|
||||
- ℂ **CommitCreate** (`Class`)
|
||||
- 📝 Schema for staging and committing changes.
|
||||
- ℂ **ConflictResolution** (`Class`)
|
||||
- 📝 Schema for resolving merge conflicts.
|
||||
- ℂ **DeploymentEnvironmentSchema** (`Class`)
|
||||
- 📝 Schema for representing a target deployment environment.
|
||||
- ℂ **DeployRequest** (`Class`)
|
||||
- 📝 Schema for dashboard deployment requests.
|
||||
- ℂ **RepoInitRequest** (`Class`)
|
||||
- 📝 Schema for repository initialization requests.
|
||||
- 📦 **storage_routes** (`Module`)
|
||||
- 📝 API endpoints for file storage management (backups and repositories).
|
||||
- 🏗️ Layer: API
|
||||
- 🔗 DEPENDS_ON -> `backend.src.models.storage`
|
||||
- ƒ **list_files** (`Function`)
|
||||
- 📝 List all files and directories in the storage system.
|
||||
- 🔗 CALLS -> `StoragePlugin.list_files`
|
||||
- ƒ **upload_file** (`Function`)
|
||||
- 📝 Upload a file to the storage system.
|
||||
- 🔗 CALLS -> `StoragePlugin.save_file`
|
||||
- ƒ **delete_file** (`Function`)
|
||||
- 📝 Delete a specific file or directory.
|
||||
- 🔗 CALLS -> `StoragePlugin.delete_file`
|
||||
- ƒ **download_file** (`Function`)
|
||||
- 📝 Retrieve a file for download.
|
||||
- 🔗 CALLS -> `StoragePlugin.get_file_path`
|
||||
- 📦 **TasksRouter** (`Module`)
|
||||
- 📝 Defines the FastAPI router for task-related endpoints, allowing clients to create, list, and get the status of tasks.
|
||||
- 🏗️ Layer: UI (API)
|
||||
@@ -843,6 +1124,9 @@
|
||||
- 📝 Resume a task that is awaiting input (e.g., passwords).
|
||||
- ƒ **clear_tasks** (`Function`)
|
||||
- 📝 Clear tasks matching the status filter.
|
||||
- 📦 **GitModels** (`Module`)
|
||||
- 📝 Git-specific SQLAlchemy models for configuration and repository tracking.
|
||||
- 🏗️ Layer: Model
|
||||
- 📦 **backend.src.models.task** (`Module`)
|
||||
- 📝 Defines the database schema for task execution records.
|
||||
- 🏗️ Layer: Domain
|
||||
@@ -867,6 +1151,12 @@
|
||||
- 📝 Represents a mapping between source and target databases.
|
||||
- ℂ **MigrationJob** (`Class`)
|
||||
- 📝 Represents a single migration execution job.
|
||||
- ℂ **FileCategory** (`Class`)
|
||||
- 📝 Enumeration of supported file categories in the storage system.
|
||||
- ℂ **StorageConfig** (`Class`)
|
||||
- 📝 Configuration model for the storage system, defining paths and naming patterns.
|
||||
- ℂ **StoredFile** (`Class`)
|
||||
- 📝 Data model representing metadata for a file stored in the system.
|
||||
- 📦 **backend.src.models.dashboard** (`Module`)
|
||||
- 📝 Defines data models for dashboard metadata and selection.
|
||||
- 🏗️ Layer: Model
|
||||
@@ -874,6 +1164,40 @@
|
||||
- 📝 Represents a dashboard available for migration.
|
||||
- ℂ **DashboardSelection** (`Class`)
|
||||
- 📝 Represents the user's selection of dashboards to migrate.
|
||||
- 📦 **backend.src.services.git_service** (`Module`)
|
||||
- 📝 Core Git logic using GitPython to manage dashboard repositories.
|
||||
- 🏗️ Layer: Service
|
||||
- 🔗 INHERITS_FROM -> `None`
|
||||
- ℂ **GitService** (`Class`)
|
||||
- 📝 Wrapper for GitPython operations with semantic logging and error handling.
|
||||
- ƒ **__init__** (`Function`)
|
||||
- 📝 Initializes the GitService with a base path for repositories.
|
||||
- ƒ **_get_repo_path** (`Function`)
|
||||
- 📝 Resolves the local filesystem path for a dashboard's repository.
|
||||
- ƒ **init_repo** (`Function`)
|
||||
- 📝 Initialize or clone a repository for a dashboard.
|
||||
- ƒ **get_repo** (`Function`)
|
||||
- 📝 Get Repo object for a dashboard.
|
||||
- ƒ **list_branches** (`Function`)
|
||||
- 📝 List all branches for a dashboard's repository.
|
||||
- ƒ **create_branch** (`Function`)
|
||||
- 📝 Create a new branch from an existing one.
|
||||
- ƒ **checkout_branch** (`Function`)
|
||||
- 📝 Switch to a specific branch.
|
||||
- ƒ **commit_changes** (`Function`)
|
||||
- 📝 Stage and commit changes.
|
||||
- ƒ **push_changes** (`Function`)
|
||||
- 📝 Push local commits to remote.
|
||||
- ƒ **pull_changes** (`Function`)
|
||||
- 📝 Pull changes from remote.
|
||||
- ƒ **get_status** (`Function`)
|
||||
- 📝 Get current repository status (dirty files, untracked, etc.)
|
||||
- ƒ **get_diff** (`Function`)
|
||||
- 📝 Generate diff for a file or the whole repository.
|
||||
- ƒ **get_commit_history** (`Function`)
|
||||
- 📝 Retrieve commit history for a repository.
|
||||
- ƒ **test_connection** (`Function`)
|
||||
- 📝 Test connection to Git provider using PAT.
|
||||
- 📦 **backend.src.services.mapping_service** (`Module`)
|
||||
- 📝 Orchestrates database fetching and fuzzy matching suggestions.
|
||||
- 🏗️ Layer: Service
|
||||
@@ -963,6 +1287,26 @@
|
||||
- 📝 Returns the JSON schema for the mapper plugin parameters.
|
||||
- ƒ **execute** (`Function`)
|
||||
- 📝 Executes the dataset mapping logic.
|
||||
- 📦 **backend.src.plugins.git_plugin** (`Module`)
|
||||
- 📝 Предоставляет плагин для версионирования и развертывания дашбордов Superset.
|
||||
- 🏗️ Layer: Plugin
|
||||
- 🔗 INHERITS_FROM -> `src.core.plugin_base.PluginBase`
|
||||
- ℂ **GitPlugin** (`Class`)
|
||||
- 📝 Реализация плагина Git Integration для управления версиями дашбордов.
|
||||
- ƒ **__init__** (`Function`)
|
||||
- 📝 Инициализирует плагин и его зависимости.
|
||||
- ƒ **id** (`Function`)
|
||||
- 📝 Returns the plugin identifier.
|
||||
- ƒ **name** (`Function`)
|
||||
- 📝 Returns the plugin name.
|
||||
- ƒ **description** (`Function`)
|
||||
- 📝 Returns the plugin description.
|
||||
- ƒ **version** (`Function`)
|
||||
- 📝 Returns the plugin version.
|
||||
- ƒ **get_schema** (`Function`)
|
||||
- 📝 Возвращает JSON-схему параметров для выполнения задач плагина.
|
||||
- ƒ **initialize** (`Function`)
|
||||
- 📝 Выполняет начальную настройку плагина.
|
||||
- 📦 **MigrationPlugin** (`Module`)
|
||||
- 📝 A plugin that provides functionality to migrate Superset dashboards between environments.
|
||||
- 🏗️ Layer: App
|
||||
@@ -982,6 +1326,42 @@
|
||||
- 📝 Returns the JSON schema for migration plugin parameters.
|
||||
- ƒ **execute** (`Function`)
|
||||
- 📝 Executes the dashboard migration logic.
|
||||
- 📦 **StoragePlugin** (`Module`)
|
||||
- 📝 Provides core filesystem operations for managing backups and repositories.
|
||||
- 🏗️ Layer: App
|
||||
- 🔗 DEPENDS_ON -> `backend.src.models.storage`
|
||||
- ℂ **StoragePlugin** (`Class`)
|
||||
- 📝 Implementation of the storage management plugin.
|
||||
- ƒ **__init__** (`Function`)
|
||||
- 📝 Initializes the StoragePlugin and ensures required directories exist.
|
||||
- ƒ **id** (`Function`)
|
||||
- 📝 Returns the unique identifier for the storage plugin.
|
||||
- ƒ **name** (`Function`)
|
||||
- 📝 Returns the human-readable name of the storage plugin.
|
||||
- ƒ **description** (`Function`)
|
||||
- 📝 Returns a description of the storage plugin.
|
||||
- ƒ **version** (`Function`)
|
||||
- 📝 Returns the version of the storage plugin.
|
||||
- ƒ **get_schema** (`Function`)
|
||||
- 📝 Returns the JSON schema for storage plugin parameters.
|
||||
- ƒ **execute** (`Function`)
|
||||
- 📝 Executes storage-related tasks (placeholder for PluginBase compliance).
|
||||
- ƒ **get_storage_root** (`Function`)
|
||||
- 📝 Resolves the absolute path to the storage root.
|
||||
- ƒ **resolve_path** (`Function`)
|
||||
- 📝 Resolves a dynamic path pattern using provided variables.
|
||||
- ƒ **ensure_directories** (`Function`)
|
||||
- 📝 Creates the storage root and category subdirectories if they don't exist.
|
||||
- ƒ **validate_path** (`Function`)
|
||||
- 📝 Prevents path traversal attacks by ensuring the path is within the storage root.
|
||||
- ƒ **list_files** (`Function`)
|
||||
- 📝 Lists all files and directories in a specific category and subpath.
|
||||
- ƒ **save_file** (`Function`)
|
||||
- 📝 Saves an uploaded file to the specified category and optional subpath.
|
||||
- ƒ **delete_file** (`Function`)
|
||||
- 📝 Deletes a file or directory from the specified category and path.
|
||||
- ƒ **get_file_path** (`Function`)
|
||||
- 📝 Returns the absolute path of a file for download.
|
||||
- ƒ **test_environment_model** (`Function`)
|
||||
- 📝 Tests that Environment model correctly stores values.
|
||||
- ƒ **test_belief_scope_logs_entry_action_exit** (`Function`)
|
||||
|
||||
Reference in New Issue
Block a user