From edf92860715fddb4482bdfffa7fd137ed6cdf8aa Mon Sep 17 00:00:00 2001 From: busya Date: Mon, 26 Jan 2026 11:08:18 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A4=D0=B0=D0=B9=D0=BB=D0=BE=D0=B2=D0=BE?= =?UTF-8?q?=D0=B5=20=D1=85=D1=80=D0=B0=D0=BD=D0=B8=D0=BB=D0=B8=D1=89=D0=B5?= =?UTF-8?q?=20=D0=B3=D0=BE=D1=82=D0=BE=D0=B2=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/backend/git_repos/12 | 2 +- .../backups/Logs/superset_tool_20251220.log | 269 ------------------ .../dashboard_export_20251220T203038.zip | Bin 23334 -> 0 bytes .../dashboard_export_20251220T203037.zip | Bin 83535 -> 0 bytes .../dashboard_export_20251220T203039.zip | Bin 59657 -> 0 bytes .../dashboard_export_20251220T203040.zip | Bin 124435 -> 0 bytes .../dashboard_export_20251220T203038.zip | Bin 57077 -> 0 bytes .../dashboard_export_20251220T203039.zip | Bin 45212 -> 0 bytes .../dashboard_export_20251220T203040.zip | Bin 19316 -> 0 bytes .../dashboard_export_20251220T203038.zip | Bin 4704 -> 0 bytes .../dashboard_export_20251220T203038.zip | Bin 29213 -> 0 bytes .../dashboard_export_20251220T203040.zip | Bin 123495 -> 0 bytes .../dashboard_export_20251220T203039.zip | Bin 32641 -> 0 bytes backend/mappings.db | Bin 73728 -> 73728 bytes backend/src/api/routes/settings.py | 26 +- backend/src/api/routes/storage.py | 54 ++-- backend/src/core/config_manager.py | 10 +- backend/src/core/config_models.py | 1 - backend/src/models/storage.py | 6 +- backend/src/plugins/backup.py | 15 +- backend/src/plugins/storage/plugin.py | 132 ++++++--- backend/src/services/git_service.py | 10 +- backend/tasks.db | Bin 86016 -> 90112 bytes docs/settings.md | 4 +- .../src/components/storage/FileList.svelte | 64 +++-- .../src/components/storage/FileUpload.svelte | 36 ++- frontend/src/pages/Settings.svelte | 7 - frontend/src/routes/settings/+page.svelte | 13 - frontend/src/routes/settings/+page.ts | 1 - .../src/routes/tools/storage/+page.svelte | 122 ++++++-- frontend/src/services/storageService.js | 30 +- specs/014-file-storage-ui/checklists/ux.md | 34 +-- specs/014-file-storage-ui/plan.md | 5 +- specs/014-file-storage-ui/spec.md | 21 +- specs/014-file-storage-ui/tasks.md | 12 + 35 files changed, 377 insertions(+), 497 deletions(-) delete mode 100644 backend/backups/Logs/superset_tool_20251220.log delete mode 100644 backend/backups/SUPERSET/COVID Vaccine Dashboard/dashboard_export_20251220T203038.zip delete mode 100644 backend/backups/SUPERSET/FCC New Coder Survey 2018/dashboard_export_20251220T203037.zip delete mode 100644 backend/backups/SUPERSET/Featured Charts/dashboard_export_20251220T203039.zip delete mode 100644 backend/backups/SUPERSET/Misc Charts/dashboard_export_20251220T203040.zip delete mode 100644 backend/backups/SUPERSET/Sales Dashboard/dashboard_export_20251220T203038.zip delete mode 100644 backend/backups/SUPERSET/Slack Dashboard/dashboard_export_20251220T203039.zip delete mode 100644 backend/backups/SUPERSET/USA Births Names/dashboard_export_20251220T203040.zip delete mode 100644 backend/backups/SUPERSET/Unicode Test/dashboard_export_20251220T203038.zip delete mode 100644 backend/backups/SUPERSET/Video Game Sales/dashboard_export_20251220T203038.zip delete mode 100644 backend/backups/SUPERSET/World Bank's Data/dashboard_export_20251220T203040.zip delete mode 100644 backend/backups/SUPERSET/deck.gl Demo/dashboard_export_20251220T203039.zip diff --git a/backend/backend/git_repos/12 b/backend/backend/git_repos/12 index d592fa7..f467724 160000 --- a/backend/backend/git_repos/12 +++ b/backend/backend/git_repos/12 @@ -1 +1 @@ -Subproject commit d592fa7ed5420d6132c208acd93b8b5c8ead3f27 +Subproject commit f46772443ad76fb5f35d2e478cf71078389dd2b7 diff --git a/backend/backups/Logs/superset_tool_20251220.log b/backend/backups/Logs/superset_tool_20251220.log deleted file mode 100644 index 92ab671..0000000 --- a/backend/backups/Logs/superset_tool_20251220.log +++ /dev/null @@ -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. diff --git a/backend/backups/SUPERSET/COVID Vaccine Dashboard/dashboard_export_20251220T203038.zip b/backend/backups/SUPERSET/COVID Vaccine Dashboard/dashboard_export_20251220T203038.zip deleted file mode 100644 index 053eefe0fdf61e93ffe256e91f64ce72b1ab88ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23334 zcmeHP&2Jk?cGv9eCV=vR0GV4ZH8e2tOxzZm-(+@^P$V_x+LmN}*z<`-e-y=*n%#7F zlag!_$vIufW?m_;A9D?MQ>z;B5a>*sXS5@6?wn&K{)4Ma{kxhWC zu8&u*-m7}=_g?kE!*_r9-iG{pf8!ti=db?vF@DhJK0b~a4XwZo9fMzvgD^Hq#Zpx- zm5RruV!2puY>#+sI%aI%zcNSOl!uWU_${X2FWxV1##dwBV%?;R&Db6BC^kpq7TcnJ zG`*yWo?7K%tK7J+H){1t;}46)R?RSl* zrfs`EH&S{Gy(p+oYZ|c|d!SgvtSj-%mIby+?F`|wz+m95_7@~r##r!AB*W)EG@2{{(y^DjjK z^_{OO9?%lXHq^=t<34KYdR07RY{=cyVccT1qAG|y*XDcX2%JiI)G>X>g&6RNjd{pA z!NiZlt9<=^$VccpX!qD0vtY2GxoRRg;W&0vcLp_WU{#7*rBSMBmSdZmUb7libD-PB zQbjemaGiMAVh!B5SnT@KTNyvADtcSOcsYp~;~OPW zo3S-8{y5}(w2b)+)=n-2FAb_@!>nrqZebw?^`>UoHA^e%1759K&4yLB6^&{k1s}i2Gmzyh=ZXS@%*yQt2)diige8pwujL4V!97tCV%xMk=~yH%oP^R;?9l zW^NmiTi>sihfe2Y+_n#*Gxt^=&Z^$P!%`v_2@hX=8(|-?qu4yXL5?4AGgjM)-1A+W z03Wc9=lZT~da`+>7E`W&;5j)Qe5-Bx?3++)D1b6)@GXlms5R|swKmZ7>VRt% zvr*E_hGl8BrcU6ueF2=0tO^Z>>EygRTMteLC z1JfSfz8V?bNE+R$97VfXE!&NXhA(9MH5|?2&4E^GG%HTYEZLkVYW>F6hJFv4||`s*pxK(<3kwP{hO9Z)ey|TVRMxy3{^(W2fcQ;cbN7|Mr>Ln zI?A3PF~5YNy3Bkht626edqDHY?Zb!N{bzgH)3YZ}sza#O!!b5pb;a(b|cuP%l>-wJc+0Xlg7fNV||#XYOZ}U2UBfkpe;7fxu0(| zFtMod5VV$(c}Hw$f(xb>a?`m|(na0Syacr-8-l?hcNAU0B*G+SG6Z7B5FGN*7W5@a zgD2@=3Mm`z+_^)d0ADKUK~6*st$a+xclJK!|kbi(cO6f3v>5u)Zh7}dif`o7ue^~Pb_!*+3t(s zPwN*C4yN_X=8F$Myq^yJj6+SJm7xt>fN?r$lGRG3Lx`y9klY3n$k>ICb$}?HPIwey zEulXo4{4uLDz2#Gao|TTW+Cd#^$rOKzH>jrT4OH&YsB))-9oa2U6>K`NOUJ-a`{1o z&x-YJD6DQ|+ixiX5(6}KpWfKfwa8ORHf<22fPkD4A*fu83(#qkMGxfZK_#NMXIgqz zF77iA5U8PKan6d@6r7N=U?d5dkwhQaLvO3NHBTjDmT8}#hS+yN|4zH}@Z-b%lfABS zeAwPQI%psE_K1E4HCzWyk}&0&;z?@H-Ncg_S~bU$oGqP9KJD@wKlxMw7f(JB#*Do& zH~$^3jntaw6>TpgtX1ihG4rq`X%SAy^xC`)NL+N36QLebcSsyjx=2iV?+ zhC3Qf0CEec5Gb?2eqkKq1V*K#RE(9hK)dG??%P1C$+vL$z?^uoF>t*Ya6W9=mtWCN z?h9K~6&o->2JY!36c!*+p)s1?Ui-NJv}f%0A077&wZ_?zKDew5Jv;}r8~_szyr-sz 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 diff --git a/backend/backups/SUPERSET/Featured Charts/dashboard_export_20251220T203039.zip b/backend/backups/SUPERSET/Featured Charts/dashboard_export_20251220T203039.zip deleted file mode 100644 index f18a1e58cd86d143d16cf97cbfea1d23d573bc5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59657 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 diff --git a/backend/backups/SUPERSET/Misc Charts/dashboard_export_20251220T203040.zip b/backend/backups/SUPERSET/Misc Charts/dashboard_export_20251220T203040.zip deleted file mode 100644 index 13589c17ee7aeeeb1d46808d25fbd1d8655879ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 124435 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 diff --git a/backend/backups/SUPERSET/Slack Dashboard/dashboard_export_20251220T203039.zip b/backend/backups/SUPERSET/Slack Dashboard/dashboard_export_20251220T203039.zip deleted file mode 100644 index 478eda1c9c2550d95e9e86c641d0b61c2f475679..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45212 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 diff --git a/backend/backups/SUPERSET/USA Births Names/dashboard_export_20251220T203040.zip b/backend/backups/SUPERSET/USA Births Names/dashboard_export_20251220T203040.zip deleted file mode 100644 index 137ca3245b9c0416768cc599fe0925bc597edea0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19316 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 RvGED~_ebEI{-uxK{vX6&elY+5 diff --git a/backend/backups/SUPERSET/World Bank's Data/dashboard_export_20251220T203040.zip b/backend/backups/SUPERSET/World Bank's Data/dashboard_export_20251220T203040.zip deleted file mode 100644 index 9a83f8c560c28a6dddd2419db22f2cf97e941da1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 123495 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 Bq~QPn diff --git a/backend/backups/SUPERSET/deck.gl Demo/dashboard_export_20251220T203039.zip b/backend/backups/SUPERSET/deck.gl Demo/dashboard_export_20251220T203039.zip deleted file mode 100644 index c07b2a56fee526def37f5064d021448e5f790365..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32641 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 z StoragePlugin.list_files @router.get("/files", response_model=List[StoredFile]) -async def list_files(category: Optional[FileCategory] = None, plugin_loader=Depends(get_plugin_loader)): +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) + return storage_plugin.list_files(category, path) # [/DEF:list_files:Function] # [DEF:upload_file:Function] -# @PURPOSE: Upload a file to the storage system under a specific category. +# @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. # @@ -55,6 +61,7 @@ async def list_files(category: Optional[FileCategory] = None, plugin_loader=Depe @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) ): @@ -63,33 +70,32 @@ async def upload_file( if not storage_plugin: raise HTTPException(status_code=500, detail="Storage plugin not loaded") try: - return await storage_plugin.save_file(file, category) + 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 from the storage system. +# @PURPOSE: Delete a specific file or directory. # # @PRE: category must be a valid FileCategory. -# @PRE: filename must not contain path separators. -# @POST: File is removed from storage. +# @POST: Item is removed from storage. # # @PARAM: category (FileCategory) - File category. -# @PARAM: filename (str) - Name of the file. +# @PARAM: path (str) - Relative path of the item. # @RETURN: None # -# @SIDE_EFFECT: Deletes file from the filesystem. +# @SIDE_EFFECT: Deletes item from the filesystem. # # @RELATION: CALLS -> StoragePlugin.delete_file -@router.delete("/files/{category}/{filename}", status_code=204) -async def delete_file(category: FileCategory, filename: str, plugin_loader=Depends(get_plugin_loader)): +@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, filename) + storage_plugin.delete_file(category, path) except FileNotFoundError: raise HTTPException(status_code=404, detail="File not found") except ValueError as e: @@ -100,23 +106,23 @@ async def delete_file(category: FileCategory, filename: str, plugin_loader=Depen # @PURPOSE: Retrieve a file for download. # # @PRE: category must be a valid FileCategory. -# @PRE: filename must exist in the specified category. # @POST: Returns a FileResponse. # # @PARAM: category (FileCategory) - File category. -# @PARAM: filename (str) - Name of the file. +# @PARAM: path (str) - Relative path of the file. # @RETURN: FileResponse - The file content. # # @RELATION: CALLS -> StoragePlugin.get_file_path -@router.get("/download/{category}/{filename}") -async def download_file(category: FileCategory, filename: str, plugin_loader=Depends(get_plugin_loader)): +@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: - path = storage_plugin.get_file_path(category, filename) - return FileResponse(path=path, filename=filename) + 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: diff --git a/backend/src/core/config_manager.py b/backend/src/core/config_manager.py index 741aeb6..25e491d 100755 --- a/backend/src/core/config_manager.py +++ b/backend/src/core/config_manager.py @@ -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] diff --git a/backend/src/core/config_models.py b/backend/src/core/config_models.py index 7e96b23..e326904 100755 --- a/backend/src/core/config_models.py +++ b/backend/src/core/config_models.py @@ -43,7 +43,6 @@ 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) diff --git a/backend/src/models/storage.py b/backend/src/models/storage.py index 75edcac..fc71eb4 100644 --- a/backend/src/models/storage.py +++ b/backend/src/models/storage.py @@ -5,13 +5,13 @@ from pydantic import BaseModel, Field # [DEF:FileCategory:Class] class FileCategory(str, Enum): - BACKUP = "backup" - REPOSITORY = "repository" + BACKUP = "backups" + REPOSITORY = "repositorys" # [/DEF:FileCategory:Class] # [DEF:StorageConfig:Class] class StorageConfig(BaseModel): - root_path: str = Field(default="../ss-tools-storage", description="Absolute path to the storage root directory.") + 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.") diff --git a/backend/src/plugins/backup.py b/backend/src/plugins/backup.py index 534d472..1d68d8a 100755 --- a/backend/src/plugins/backup.py +++ b/backend/src/plugins/backup.py @@ -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}.") diff --git a/backend/src/plugins/storage/plugin.py b/backend/src/plugins/storage/plugin.py index 89c38d2..67a8d0e 100644 --- a/backend/src/plugins/storage/plugin.py +++ b/backend/src/plugins/storage/plugin.py @@ -98,14 +98,43 @@ class StoragePlugin(PluginBase): def get_storage_root(self) -> Path: with belief_scope("StoragePlugin:get_storage_root"): config_manager = get_config_manager() - storage_config = config_manager.get_config().settings.storage - root = Path(storage_config.root_path) + 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 workspace root (ss-tools) - root = (Path(__file__).parents[4] / root).resolve() + # 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. + # @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. # @SIDE_EFFECT: Creates directories on the filesystem. @@ -113,7 +142,8 @@ class StoragePlugin(PluginBase): with belief_scope("StoragePlugin:ensure_directories"): root = self.get_storage_root() for category in FileCategory: - path = root / f"{category.value}s" + # 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] @@ -135,46 +165,68 @@ class StoragePlugin(PluginBase): # [/DEF:validate_path:Function] # [DEF:list_files:Function] - # @PURPOSE: Lists all files in a specific category. + # @PURPOSE: Lists all files and directories in a specific category and subpath. # @PARAM: category (Optional[FileCategory]) - The category to list. - # @RETURN: List[StoredFile] - List of file metadata objects. - def list_files(self, category: Optional[FileCategory] = None) -> List[StoredFile]: + # @PARAM: subpath (Optional[str]) - Nested path within the category. + # @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: - cat_dir = root / f"{cat.value}s" - if not cat_dir.exists(): + # 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 - for item in cat_dir.iterdir(): - if item.is_file(): - stat = item.stat() + 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=item.name, - path=str(item.relative_to(root)), - size=stat.st_size, + 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=None # Could use python-magic here if needed + mime_type="directory" if is_dir else None )) - return sorted(files, key=lambda x: x.created_at, reverse=True) + # 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. + # @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. # @RETURN: StoredFile - Metadata of the saved file. # @SIDE_EFFECT: Writes file to disk. - async def save_file(self, file: UploadFile, category: FileCategory) -> StoredFile: + 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 / f"{category.value}s" + 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) @@ -194,34 +246,44 @@ class StoragePlugin(PluginBase): # [/DEF:save_file:Function] # [DEF:delete_file:Function] - # @PURPOSE: Deletes a file from the specified category. + # @PURPOSE: Deletes a file or directory from the specified category and path. # @PARAM: category (FileCategory) - The category. - # @PARAM: filename (str) - The name of the file. - # @SIDE_EFFECT: Removes file from disk. - def delete_file(self, category: FileCategory, filename: str): + # @PARAM: path (str) - The relative path of the file or directory. + # @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() - file_path = self.validate_path(root / f"{category.value}s" / filename) + # path is relative to root, but we ensure it starts with category + full_path = self.validate_path(root / path) - if file_path.exists(): - file_path.unlink() - logger.info(f"[StoragePlugin][Action] Deleted file: {file_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"File {filename} not found in {category.value}s") + 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: filename (str) - The name of the file. + # @PARAM: path (str) - The relative path of the file. # @RETURN: Path - Absolute path to the file. - def get_file_path(self, category: FileCategory, filename: str) -> Path: + 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 / f"{category.value}s" / filename) + file_path = self.validate_path(root / path) - if not file_path.exists(): - raise FileNotFoundError(f"File {filename} not found in {category.value}s") + 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] diff --git a/backend/src/services/git_service.py b/backend/src/services/git_service.py index 468d4fd..ed82d78 100644 --- a/backend/src/services/git_service.py +++ b/backend/src/services/git_service.py @@ -31,9 +31,15 @@ class GitService: # @PARAM: base_path (str) - Root directory for all Git clones. # @PRE: base_path is a valid string path. # @POST: GitService is initialized; base_path directory exists. - def __init__(self, base_path: str = "backend/git_repos"): + 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] diff --git a/backend/tasks.db b/backend/tasks.db index 99c5258983c7a174035298f74220194dcd50e8b3..64a674b459179182610fc54fccd1630d921fef1b 100644 GIT binary patch delta 1227 zcma)5J7^S96rCkmS=ad_o9vW-;~!sl{cr$8%kGGGUK3)@J^z66sJjoWVB?EDG!m3DMhJJltB_oCDlZZRB;STX@n7xKoE;b zf^;MmlBrCiGzo=dP;0Ne%x;dAOHHft`NXesd1TJKo@s5N5_|Tr=G>p3o@h3oFI#k> zKFqxA)`!_EgwJ3ea2-(WGsUpt1c*RjloTE910jNoto816jqrPY1PvUr=KjU)_ltcU zL%B!4S+(4A_O`iv6u2NY6|38vhO#?fAn2|##uV}Cv0X%}87ofuF$ z%q^MBhcDDaYBhnE+~=i%Vu zU(dkLTgbqFollgvkl%#Qj&CP_8}BUsSl&1M++ufp0lztD+5gvX5OrP`ECsW>Z>8X diff --git a/docs/settings.md b/docs/settings.md index f539471..ab73cfa 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -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. diff --git a/frontend/src/components/storage/FileList.svelte b/frontend/src/components/storage/FileList.svelte index 8939fac..c5a7903 100644 --- a/frontend/src/components/storage/FileList.svelte +++ b/frontend/src/components/storage/FileList.svelte @@ -13,11 +13,16 @@ // [SECTION: IMPORTS] import { createEventDispatcher } from 'svelte'; import { downloadFileUrl } from '../../services/storageService'; + import { t } from '../../lib/i18n'; // [/SECTION: IMPORTS] export let files = []; const dispatch = createEventDispatcher(); + function isDirectory(file) { + return file.mime_type === 'directory'; + } + function formatSize(bytes) { if (bytes === 0) return '0 B'; const k = 1024; @@ -36,40 +41,63 @@ - - - - - + + + + + {#each files as file} - + - + {:else} {/each} diff --git a/frontend/src/components/storage/FileUpload.svelte b/frontend/src/components/storage/FileUpload.svelte index 92c3a9f..dab9b4b 100644 --- a/frontend/src/components/storage/FileUpload.svelte +++ b/frontend/src/components/storage/FileUpload.svelte @@ -14,6 +14,7 @@ import { createEventDispatcher } from 'svelte'; import { uploadFile } from '../../services/storageService'; import { addToast } from '../../lib/toasts'; + import { t } from '../../lib/i18n'; // [/SECTION: IMPORTS] // [DEF:handleUpload:Function] @@ -24,7 +25,8 @@ */ const dispatch = createEventDispatcher(); let fileInput; - let category = 'backup'; + export let category = 'backups'; + export let path = ''; let isUploading = false; let dragOver = false; @@ -34,12 +36,18 @@ isUploading = true; try { - await uploadFile(file, category); - addToast(`File ${file.name} uploaded successfully.`, 'success'); + // 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(`Upload failed: ${error.message}`, 'error'); + addToast($t.storage.messages.upload_failed.replace('{error}', error.message), 'error'); } finally { isUploading = false; } @@ -65,17 +73,17 @@
-

Upload File

+

{$t.storage.upload_title}

- - - - + +
@@ -92,8 +100,8 @@
-

or drag and drop

+

{$t.storage.drag_drop}

-

ZIP, YAML, JSON up to 50MB

+

{$t.storage.supported_formats}

{#if isUploading}
- Uploading... + {$t.storage.uploading}
{/if} diff --git a/frontend/src/pages/Settings.svelte b/frontend/src/pages/Settings.svelte index ae74096..d0fd8ab 100755 --- a/frontend/src/pages/Settings.svelte +++ b/frontend/src/pages/Settings.svelte @@ -20,7 +20,6 @@ let settings = { environments: [], settings: { - backup_path: '', default_environment_id: null, logging: { level: 'INFO', @@ -204,12 +203,6 @@

Global Settings

-
-
- - -
-

Logging Configuration

diff --git a/frontend/src/routes/settings/+page.svelte b/frontend/src/routes/settings/+page.svelte index c95c96f..bbc7031 100644 --- a/frontend/src/routes/settings/+page.svelte +++ b/frontend/src/routes/settings/+page.svelte @@ -170,19 +170,6 @@
{/if} -
- -
- - -
-
-
diff --git a/frontend/src/routes/settings/+page.ts b/frontend/src/routes/settings/+page.ts index 0ffa558..a4f4780 100644 --- a/frontend/src/routes/settings/+page.ts +++ b/frontend/src/routes/settings/+page.ts @@ -18,7 +18,6 @@ export async function load() { settings: { environments: [], settings: { - backup_path: '', default_environment_id: null } }, diff --git a/frontend/src/routes/tools/storage/+page.svelte b/frontend/src/routes/tools/storage/+page.svelte index bd86c96..76f1f63 100644 --- a/frontend/src/routes/tools/storage/+page.svelte +++ b/frontend/src/routes/tools/storage/+page.svelte @@ -15,6 +15,7 @@ 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] @@ -26,15 +27,30 @@ */ let files = []; let isLoading = false; - let activeTab = 'all'; + let activeTab = 'backups'; + let currentPath = 'backups'; // Relative to storage root async function loadFiles() { isLoading = true; try { - const category = activeTab === 'all' ? null : activeTab; - files = await listFiles(category); + 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(`Failed to load files: ${error.message}`, 'error'); + addToast($t.storage.messages.load_failed.replace('{error}', error.message), 'error'); } finally { isLoading = false; } @@ -44,39 +60,73 @@ // [DEF:handleDelete:Function] /** * @purpose Handles the file deletion process. - * @param {CustomEvent} event - The delete event containing category and filename. + * @param {CustomEvent} event - The delete event containing category and path. */ async function handleDelete(event) { - const { category, filename } = event.detail; - if (!confirm(`Are you sure you want to delete ${filename}?`)) return; + const { category, path, name } = event.detail; + if (!confirm($t.storage.messages.delete_confirm.replace('{name}', name))) return; try { - await deleteFile(category, filename); - addToast(`File ${filename} deleted.`, 'success'); + await deleteFile(category, path); + addToast($t.storage.messages.delete_success.replace('{name}', name), 'success'); await loadFiles(); } catch (error) { - addToast(`Delete failed: ${error.message}`, 'error'); + addToast($t.storage.messages.delete_failed.replace('{error}', error.message), 'error'); } } // [/DEF:handleDelete:Function] + function handleNavigate(event) { + currentPath = event.detail; + loadFiles(); + } + + function navigateUp() { + if (!currentPath || currentPath === activeTab) return; + const parts = currentPath.split('/'); + parts.pop(); + currentPath = parts.join('/') || ''; + loadFiles(); + } + onMount(loadFiles); $: if (activeTab) { + // Reset path when switching tabs + if (!currentPath.startsWith(activeTab)) { + currentPath = activeTab; + } loadFiles(); }
-
-

File Storage Management

- + {#each currentPath.split('/').slice(1) as part, i} + / + + {/each} +
+ {/if} +
+ +
+
@@ -86,33 +136,45 @@
- +
+ {#if currentPath && currentPath !== activeTab} + + {/if} +
+ +
- +
diff --git a/frontend/src/services/storageService.js b/frontend/src/services/storageService.js index 20ba690..fab7e41 100644 --- a/frontend/src/services/storageService.js +++ b/frontend/src/services/storageService.js @@ -9,15 +9,19 @@ const API_BASE = '/api/storage'; // [DEF:listFiles:Function] /** - * @purpose Fetches the list of files for a given category. + * @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} */ -export async function listFiles(category) { +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}`); @@ -31,12 +35,16 @@ export async function listFiles(category) { * @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} */ -export async function uploadFile(file, category) { +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', @@ -53,19 +61,19 @@ export async function uploadFile(file, category) { // [DEF:deleteFile:Function] /** - * @purpose Deletes a file from storage. + * @purpose Deletes a file or directory from storage. * @param {string} category - File category. - * @param {string} filename - Name of the file. + * @param {string} path - Relative path of the item. * @returns {Promise} */ -export async function deleteFile(category, filename) { - const response = await fetch(`${API_BASE}/files/${category}/${filename}`, { +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 file: ${response.statusText}`); + throw new Error(errorData.detail || `Failed to delete: ${response.statusText}`); } } // [/DEF:deleteFile:Function] @@ -74,11 +82,11 @@ export async function deleteFile(category, filename) { /** * @purpose Returns the URL for downloading a file. * @param {string} category - File category. - * @param {string} filename - Name of the file. + * @param {string} path - Relative path of the file. * @returns {string} */ -export function downloadFileUrl(category, filename) { - return `${API_BASE}/download/${category}/${filename}`; +export function downloadFileUrl(category, path) { + return `${API_BASE}/download/${category}/${path}`; } // [/DEF:downloadFileUrl:Function] diff --git a/specs/014-file-storage-ui/checklists/ux.md b/specs/014-file-storage-ui/checklists/ux.md index 6e337ca..145c73d 100644 --- a/specs/014-file-storage-ui/checklists/ux.md +++ b/specs/014-file-storage-ui/checklists/ux.md @@ -6,30 +6,30 @@ ## User Experience (File Management) -- [ ] CHK001 Are loading states displayed while fetching the file list? [Completeness] -- [ ] CHK002 Is visual feedback provided immediately after file upload starts? [Clarity] -- [ ] CHK003 Are error messages user-friendly when upload fails (e.g., "File too large" vs "Error 413")? [Clarity] -- [ ] CHK004 Is a confirmation modal shown before permanently deleting a file? [Safety] -- [ ] CHK005 Does the UI clearly distinguish between "Backups" and "Repositories" tabs? [Clarity] -- [ ] CHK006 Is the file list sortable by Date and Name? [Usability] -- [ ] CHK007 Are file sizes formatted in human-readable units (KB, MB, GB)? [Usability] -- [ ] CHK008 Is the download action easily accessible for each file item? [Accessibility] -- [ ] CHK009 Does the upload component support drag-and-drop interactions? [Usability] -- [ ] CHK010 Is the "Upload" button disabled or hidden when no category is selected? [Consistency] +- [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 -- [ ] CHK011 Can the storage root path be configured to any writable directory on the server? [Flexibility] -- [ ] CHK012 Does the system support defining custom directory structures using variables like `{environment}`? [Flexibility] -- [ ] CHK013 Does the system support defining custom filename patterns using variables like `{timestamp}`? [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] -- [ ] CHK015 Can the configuration be updated without restarting the application? [Usability] +- [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 -- [ ] CHK018 Is the UI behavior defined for an empty file list (zero state)? [Coverage] +- [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] -- [ ] CHK020 Are long filenames handled gracefully in the UI (e.g., truncation with tooltip)? [Layout] -- [ ] CHK021 Is the behavior defined for uploading a file with a duplicate name? [Conflict Resolution] \ No newline at end of file +- [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] \ No newline at end of file diff --git a/specs/014-file-storage-ui/plan.md b/specs/014-file-storage-ui/plan.md index 9928b4b..0ef82da 100644 --- a/specs/014-file-storage-ui/plan.md +++ b/specs/014-file-storage-ui/plan.md @@ -5,7 +5,7 @@ ## 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, while allowing flexible configuration of directory structures and filename patterns. +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 @@ -88,7 +88,8 @@ frontend/ │ │ └── +page.svelte # Main storage UI │ ├── components/ │ │ └── storage/ -│ │ ├── FileList.svelte # Component for listing files +│ │ ├── 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 diff --git a/specs/014-file-storage-ui/spec.md b/specs/014-file-storage-ui/spec.md index 7649db4..2366e93 100644 --- a/specs/014-file-storage-ui/spec.md +++ b/specs/014-file-storage-ui/spec.md @@ -9,18 +9,19 @@ ### 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. This ensures that non-technical users or users without SSH access can still retrieve or clean up data. +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, uploading a test file, verifying it appears in the list with correct metadata, downloading it, and then deleting it. +**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 all files in the configured storage directory with their names, sizes, and creation dates. -2. **Given** a file exists in the list, **When** I click "Download", **Then** the file is downloaded to my local machine. -3. **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. -4. **Given** I have a file locally, **When** I drag and drop it or use the "Upload" button, **Then** the file is uploaded to the server storage and appears in the list. +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. --- @@ -64,11 +65,13 @@ Administrators need to control where potentially large or sensitive files are st - **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, organized by their type (Backup vs Repository). +- **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 via the Web UI. +- **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 storage directory via the Web UI, requiring them to select the target category (Backup or Repository). +- **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}/`). diff --git a/specs/014-file-storage-ui/tasks.md b/specs/014-file-storage-ui/tasks.md index 87769c8..d2c9e34 100644 --- a/specs/014-file-storage-ui/tasks.md +++ b/specs/014-file-storage-ui/tasks.md @@ -64,6 +64,17 @@ - [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. @@ -71,6 +82,7 @@ 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
NameCategorySizeCreated AtActions{$t.storage.table.name}{$t.storage.table.category}{$t.storage.table.size}{$t.storage.table.created_at}{$t.storage.table.actions}
{file.name} + {#if isDirectory(file)} + + {:else} +
+ + + + {file.name} +
+ {/if} +
{file.category}{formatSize(file.size)} + {isDirectory(file) ? '--' : formatSize(file.size)} + {formatDate(file.created_at)} - - Download - -
- No files found. + {$t.storage.no_files}