3 Commits

Author SHA1 Message Date
45c077b928 +api rework 2025-12-30 20:08:48 +03:00
9ed3a5992d cleaned 2025-12-30 18:20:40 +03:00
a032fe8457 Password promt 2025-12-30 17:21:12 +03:00
73 changed files with 874 additions and 10718 deletions

25
.kilocodemodes Normal file
View File

@@ -0,0 +1,25 @@
customModes:
- slug: tester
name: Tester
description: QA and Plan Verification Specialist
roleDefinition: >-
You are Kilo Code, acting as a QA and Verification Specialist. Your primary goal is to validate that the project implementation aligns strictly with the defined specifications and task plans.
Your responsibilities include:
- Reading and analyzing task plans and specifications (typically in the `specs/` directory).
- Verifying that implemented code matches the requirements.
- Executing tests and validating system behavior via CLI or Browser.
- Updating the status of tasks in the plan files (e.g., marking checkboxes [x]) as they are verified.
- Identifying and reporting missing features or bugs.
whenToUse: >-
Use this mode when you need to audit the progress of a project, verify completed tasks against the plan, run quality assurance checks, or update the status of task lists in specification documents.
groups:
- read
- edit
- command
- browser
- mcp
customInstructions: >-
1. Always begin by loading the relevant plan or task list from the `specs/` directory.
2. Do not assume a task is done just because it is checked; verify the code or functionality first if asked to audit.
3. When updating task lists, ensure you only mark items as complete if you have verified them.

Binary file not shown.

View File

@@ -39,6 +39,9 @@ class DatabaseResponse(BaseModel):
@router.get("", response_model=List[EnvironmentResponse]) @router.get("", response_model=List[EnvironmentResponse])
async def get_environments(config_manager=Depends(get_config_manager)): async def get_environments(config_manager=Depends(get_config_manager)):
envs = config_manager.get_environments() envs = config_manager.get_environments()
# Ensure envs is a list
if not isinstance(envs, list):
envs = []
return [EnvironmentResponse(id=e.id, name=e.name, url=e.url) for e in envs] return [EnvironmentResponse(id=e.id, name=e.name, url=e.url) for e in envs]
# [/DEF:get_environments] # [/DEF:get_environments]

View File

@@ -55,12 +55,17 @@ async def execute_migration(selection: DashboardSelection, config_manager=Depend
# Create migration task with debug logging # Create migration task with debug logging
from ...core.logger import logger from ...core.logger import logger
logger.info(f"Creating migration task with selection: {selection.dict()}")
# Include replace_db_config in the task parameters
task_params = selection.dict()
task_params['replace_db_config'] = selection.replace_db_config
logger.info(f"Creating migration task with params: {task_params}")
logger.info(f"Available environments: {env_ids}") logger.info(f"Available environments: {env_ids}")
logger.info(f"Source env: {selection.source_env_id}, Target env: {selection.target_env_id}") logger.info(f"Source env: {selection.source_env_id}, Target env: {selection.target_env_id}")
try: try:
task = await task_manager.create_task("superset-migration", selection.dict()) task = await task_manager.create_task("superset-migration", task_params)
logger.info(f"Task created successfully: {task.id}") logger.info(f"Task created successfully: {task.id}")
return {"task_id": task.id, "message": "Migration initiated"} return {"task_id": task.id, "message": "Migration initiated"}
except Exception as e: except Exception as e:

View File

@@ -22,7 +22,7 @@ class ResolveTaskRequest(BaseModel):
class ResumeTaskRequest(BaseModel): class ResumeTaskRequest(BaseModel):
passwords: Dict[str, str] passwords: Dict[str, str]
@router.post("/", response_model=Task, status_code=status.HTTP_201_CREATED) @router.post("", response_model=Task, status_code=status.HTTP_201_CREATED)
async def create_task( async def create_task(
request: CreateTaskRequest, request: CreateTaskRequest,
task_manager: TaskManager = Depends(get_task_manager) task_manager: TaskManager = Depends(get_task_manager)
@@ -39,14 +39,17 @@ async def create_task(
except ValueError as e: except ValueError as e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
@router.get("/", response_model=List[Task]) @router.get("", response_model=List[Task])
async def list_tasks( async def list_tasks(
limit: int = 10,
offset: int = 0,
status: Optional[TaskStatus] = None,
task_manager: TaskManager = Depends(get_task_manager) task_manager: TaskManager = Depends(get_task_manager)
): ):
""" """
Retrieve a list of all tasks. Retrieve a list of tasks with pagination and optional status filter.
""" """
return task_manager.get_all_tasks() return task_manager.get_tasks(limit=limit, offset=offset, status=status)
@router.get("/{task_id}", response_model=Task) @router.get("/{task_id}", response_model=Task)
async def get_task( async def get_task(
@@ -61,6 +64,19 @@ async def get_task(
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Task not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Task not found")
return task return task
@router.get("/{task_id}/logs", response_model=List[LogEntry])
async def get_task_logs(
task_id: str,
task_manager: TaskManager = Depends(get_task_manager)
):
"""
Retrieve logs for a specific task.
"""
task = task_manager.get_task(task_id)
if not task:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Task not found")
return task_manager.get_task_logs(task_id)
@router.post("/{task_id}/resolve", response_model=Task) @router.post("/{task_id}/resolve", response_model=Task)
async def resolve_task( async def resolve_task(
task_id: str, task_id: str,
@@ -90,4 +106,15 @@ async def resume_task(
return task_manager.get_task(task_id) return task_manager.get_task(task_id)
except ValueError as e: except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
@router.delete("", status_code=status.HTTP_204_NO_CONTENT)
async def clear_tasks(
status: Optional[TaskStatus] = None,
task_manager: TaskManager = Depends(get_task_manager)
):
"""
Clear tasks matching the status filter. If no filter, clears all non-running tasks.
"""
task_manager.clear_tasks(status)
return
# [/DEF] # [/DEF]

View File

@@ -11,7 +11,7 @@ from pathlib import Path
project_root = Path(__file__).resolve().parent.parent.parent project_root = Path(__file__).resolve().parent.parent.parent
sys.path.append(str(project_root)) sys.path.append(str(project_root))
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends, Request
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse from fastapi.responses import FileResponse
@@ -45,6 +45,13 @@ app.add_middleware(
) )
@app.middleware("http")
async def log_requests(request: Request, call_next):
logger.info(f"[DEBUG] Incoming request: {request.method} {request.url.path}")
response = await call_next(request)
logger.info(f"[DEBUG] Response status: {response.status_code} for {request.url.path}")
return response
# Include API routes # Include API routes
app.include_router(plugins.router, prefix="/api/plugins", tags=["Plugins"]) app.include_router(plugins.router, prefix="/api/plugins", tags=["Plugins"])
app.include_router(tasks.router, prefix="/api/tasks", tags=["Tasks"]) app.include_router(tasks.router, prefix="/api/tasks", tags=["Tasks"])
@@ -63,16 +70,30 @@ async def websocket_endpoint(websocket: WebSocket, task_id: str):
task_manager = get_task_manager() task_manager = get_task_manager()
queue = await task_manager.subscribe_logs(task_id) queue = await task_manager.subscribe_logs(task_id)
try: try:
# Send initial logs if any # Stream new logs
logger.info(f"Starting log stream for task {task_id}")
# Send initial logs first to build context
initial_logs = task_manager.get_task_logs(task_id) initial_logs = task_manager.get_task_logs(task_id)
for log_entry in initial_logs: for log_entry in initial_logs:
# Convert datetime to string for JSON serialization
log_dict = log_entry.dict() log_dict = log_entry.dict()
log_dict['timestamp'] = log_dict['timestamp'].isoformat() log_dict['timestamp'] = log_dict['timestamp'].isoformat()
await websocket.send_json(log_dict) await websocket.send_json(log_dict)
# Stream new logs # Force a check for AWAITING_INPUT status immediately upon connection
logger.info(f"Starting log stream for task {task_id}") # This ensures that if the task is already waiting when the user connects, they get the prompt.
task = task_manager.get_task(task_id)
if task and task.status == "AWAITING_INPUT" and task.input_request:
# Construct a synthetic log entry to trigger the frontend handler
# This is a bit of a hack but avoids changing the websocket protocol significantly
synthetic_log = {
"timestamp": task.logs[-1].timestamp.isoformat() if task.logs else "2024-01-01T00:00:00",
"level": "INFO",
"message": "Task paused for user input (Connection Re-established)",
"context": {"input_request": task.input_request}
}
await websocket.send_json(synthetic_log)
while True: while True:
log_entry = await queue.get() log_entry = await queue.get()
log_dict = log_entry.dict() log_dict = log_entry.dict()
@@ -84,7 +105,9 @@ async def websocket_endpoint(websocket: WebSocket, task_id: str):
if "Task completed successfully" in log_entry.message or "Task failed" in log_entry.message: if "Task completed successfully" in log_entry.message or "Task failed" in log_entry.message:
# Wait a bit to ensure client receives the last message # Wait a bit to ensure client receives the last message
await asyncio.sleep(2) await asyncio.sleep(2)
break # DO NOT BREAK here - allow client to keep connection open if they want to review logs
# or until they disconnect. Breaking closes the socket immediately.
# break
except WebSocketDisconnect: except WebSocketDisconnect:
logger.info(f"WebSocket connection disconnected for task {task_id}") logger.info(f"WebSocket connection disconnected for task {task_id}")

View File

@@ -72,6 +72,8 @@ class ConfigManager:
return config return config
except Exception as e: except Exception as e:
logger.error(f"[_load_config][Coherence:Failed] Error loading config: {e}") logger.error(f"[_load_config][Coherence:Failed] Error loading config: {e}")
# Fallback but try to preserve existing settings if possible?
# For now, return default to be safe, but log the error prominently.
return AppConfig( return AppConfig(
environments=[], environments=[],
settings=GlobalSettings(backup_path="backups") settings=GlobalSettings(backup_path="backups")

View File

@@ -35,6 +35,11 @@ class GlobalSettings(BaseModel):
backup_path: str backup_path: str
default_environment_id: Optional[str] = None default_environment_id: Optional[str] = None
logging: LoggingConfig = Field(default_factory=LoggingConfig) logging: LoggingConfig = Field(default_factory=LoggingConfig)
# Task retention settings
task_retention_days: int = 30
task_retention_limit: int = 100
pagination_limit: int = 10
# [/DEF:GlobalSettings] # [/DEF:GlobalSettings]
# [DEF:AppConfig:DataClass] # [DEF:AppConfig:DataClass]

View File

@@ -43,6 +43,9 @@ class TaskManager:
except RuntimeError: except RuntimeError:
self.loop = asyncio.get_event_loop() self.loop = asyncio.get_event_loop()
self.task_futures: Dict[str, asyncio.Future] = {} self.task_futures: Dict[str, asyncio.Future] = {}
# Load persisted tasks on startup
self.load_persisted_tasks()
# [/DEF:TaskManager.__init__:Function] # [/DEF:TaskManager.__init__:Function]
# [DEF:TaskManager.create_task:Function] # [DEF:TaskManager.create_task:Function]
@@ -328,8 +331,49 @@ class TaskManager:
if task_id in self.task_futures: if task_id in self.task_futures:
self.task_futures[task_id].set_result(True) self.task_futures[task_id].set_result(True)
self.persist_awaiting_input_tasks() # Remove from persistence as it's no longer awaiting input
self.persistence_service.delete_tasks([task_id])
# [/DEF:TaskManager.resume_task_with_password:Function] # [/DEF:TaskManager.resume_task_with_password:Function]
# [DEF:TaskManager.clear_tasks:Function]
# @PURPOSE: Clears tasks based on status filter.
# @PARAM: status (Optional[TaskStatus]) - Filter by task status.
# @RETURN: int - Number of tasks cleared.
def clear_tasks(self, status: Optional[TaskStatus] = None) -> int:
with belief_scope("TaskManager.clear_tasks"):
tasks_to_remove = []
for task_id, task in list(self.tasks.items()):
# If status is provided, match it.
# If status is None, match everything EXCEPT RUNNING (unless they are awaiting input/mapping which are technically running but paused?)
# Actually, AWAITING_INPUT and AWAITING_MAPPING are distinct statuses in TaskStatus enum.
# RUNNING is active execution.
should_remove = False
if status:
if task.status == status:
should_remove = True
else:
# Clear all non-active tasks (keep RUNNING, AWAITING_INPUT, AWAITING_MAPPING)
if task.status not in [TaskStatus.RUNNING, TaskStatus.AWAITING_INPUT, TaskStatus.AWAITING_MAPPING]:
should_remove = True
if should_remove:
tasks_to_remove.append(task_id)
for tid in tasks_to_remove:
# Cancel future if exists (e.g. for AWAITING_INPUT/MAPPING)
if tid in self.task_futures:
self.task_futures[tid].cancel()
del self.task_futures[tid]
del self.tasks[tid]
# Remove from persistence
self.persistence_service.delete_tasks(tasks_to_remove)
logger.info(f"Cleared {len(tasks_to_remove)} tasks.")
return len(tasks_to_remove)
# [/DEF:TaskManager.clear_tasks:Function]
# [/DEF:TaskManager:Class] # [/DEF:TaskManager:Class]
# [/DEF:TaskManagerModule:Module] # [/DEF:TaskManagerModule:Module]

View File

@@ -42,6 +42,7 @@ class TaskPersistenceService:
cursor.execute(""" cursor.execute("""
CREATE TABLE IF NOT EXISTS persistent_tasks ( CREATE TABLE IF NOT EXISTS persistent_tasks (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
plugin_id TEXT NOT NULL,
status TEXT NOT NULL, status TEXT NOT NULL,
created_at TEXT NOT NULL, created_at TEXT NOT NULL,
updated_at TEXT NOT NULL, updated_at TEXT NOT NULL,
@@ -68,10 +69,11 @@ class TaskPersistenceService:
if task.status == TaskStatus.AWAITING_INPUT: if task.status == TaskStatus.AWAITING_INPUT:
cursor.execute(""" cursor.execute("""
INSERT OR REPLACE INTO persistent_tasks INSERT OR REPLACE INTO persistent_tasks
(id, status, created_at, updated_at, input_request, context) (id, plugin_id, status, created_at, updated_at, input_request, context)
VALUES (?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?)
""", ( """, (
task.id, task.id,
task.plugin_id,
task.status.value, task.status.value,
task.started_at.isoformat() if task.started_at else datetime.utcnow().isoformat(), task.started_at.isoformat() if task.started_at else datetime.utcnow().isoformat(),
datetime.utcnow().isoformat(), datetime.utcnow().isoformat(),
@@ -98,16 +100,30 @@ class TaskPersistenceService:
conn = sqlite3.connect(str(self.db_path)) conn = sqlite3.connect(str(self.db_path))
cursor = conn.cursor() cursor = conn.cursor()
# Check if plugin_id column exists (migration for existing db)
cursor.execute("PRAGMA table_info(persistent_tasks)")
columns = [info[1] for info in cursor.fetchall()]
has_plugin_id = "plugin_id" in columns
if has_plugin_id:
cursor.execute("SELECT id, plugin_id, status, created_at, input_request, context FROM persistent_tasks")
else:
cursor.execute("SELECT id, status, created_at, input_request, context FROM persistent_tasks") cursor.execute("SELECT id, status, created_at, input_request, context FROM persistent_tasks")
rows = cursor.fetchall() rows = cursor.fetchall()
loaded_tasks = [] loaded_tasks = []
for row in rows: for row in rows:
if has_plugin_id:
task_id, plugin_id, status, created_at, input_request_json, context_json = row
else:
task_id, status, created_at, input_request_json, context_json = row task_id, status, created_at, input_request_json, context_json = row
plugin_id = "superset-migration" # Default fallback
try: try:
task = Task( task = Task(
id=task_id, id=task_id,
plugin_id="migration", # Default, assumes migration context for now plugin_id=plugin_id,
status=TaskStatus(status), status=TaskStatus(status),
started_at=datetime.fromisoformat(created_at), started_at=datetime.fromisoformat(created_at),
input_required=True, input_required=True,
@@ -122,6 +138,21 @@ class TaskPersistenceService:
return loaded_tasks return loaded_tasks
# [/DEF:TaskPersistenceService.load_tasks:Function] # [/DEF:TaskPersistenceService.load_tasks:Function]
# [DEF:TaskPersistenceService.delete_tasks:Function]
# @PURPOSE: Deletes specific tasks from the database.
# @PARAM: task_ids (List[str]) - List of task IDs to delete.
def delete_tasks(self, task_ids: List[str]) -> None:
if not task_ids:
return
with belief_scope("TaskPersistenceService.delete_tasks"):
conn = sqlite3.connect(str(self.db_path))
cursor = conn.cursor()
placeholders = ', '.join('?' for _ in task_ids)
cursor.execute(f"DELETE FROM persistent_tasks WHERE id IN ({placeholders})", task_ids)
conn.commit()
conn.close()
# [/DEF:TaskPersistenceService.delete_tasks:Function]
# [/DEF:TaskPersistenceService:Class] # [/DEF:TaskPersistenceService:Class]
# [/DEF:TaskPersistenceModule:Module] # [/DEF:TaskPersistenceModule:Module]

View File

@@ -22,6 +22,7 @@ class DashboardSelection(BaseModel):
selected_ids: List[int] selected_ids: List[int]
source_env_id: str source_env_id: str
target_env_id: str target_env_id: str
replace_db_config: bool = False
# [/DEF:DashboardSelection] # [/DEF:DashboardSelection]
# [/DEF:backend.src.models.dashboard] # [/DEF:backend.src.models.dashboard]

View File

@@ -100,7 +100,31 @@ class MigrationPlugin(PluginBase):
from_db_id = params.get("from_db_id") from_db_id = params.get("from_db_id")
to_db_id = params.get("to_db_id") to_db_id = params.get("to_db_id")
logger = SupersetLogger(log_dir=Path.cwd() / "logs", console=True) # [DEF:MigrationPlugin.execute:Action]
# @PURPOSE: Execute the migration logic with proper task logging.
task_id = params.get("_task_id")
from ..dependencies import get_task_manager
tm = get_task_manager()
class TaskLoggerProxy(SupersetLogger):
def __init__(self):
# Initialize parent with dummy values since we override methods
super().__init__(console=False)
def debug(self, msg, *args, extra=None, **kwargs):
if task_id: tm._add_log(task_id, "DEBUG", msg, extra or {})
def info(self, msg, *args, extra=None, **kwargs):
if task_id: tm._add_log(task_id, "INFO", msg, extra or {})
def warning(self, msg, *args, extra=None, **kwargs):
if task_id: tm._add_log(task_id, "WARNING", msg, extra or {})
def error(self, msg, *args, extra=None, **kwargs):
if task_id: tm._add_log(task_id, "ERROR", msg, extra or {})
def critical(self, msg, *args, extra=None, **kwargs):
if task_id: tm._add_log(task_id, "ERROR", msg, extra or {})
def exception(self, msg, *args, **kwargs):
if task_id: tm._add_log(task_id, "ERROR", msg, {"exception": True})
logger = TaskLoggerProxy()
logger.info(f"[MigrationPlugin][Entry] Starting migration task.") logger.info(f"[MigrationPlugin][Entry] Starting migration task.")
logger.info(f"[MigrationPlugin][Action] Params: {params}") logger.info(f"[MigrationPlugin][Action] Params: {params}")
@@ -188,10 +212,7 @@ class MigrationPlugin(PluginBase):
if not success and replace_db_config: if not success and replace_db_config:
# Signal missing mapping and wait (only if we care about mappings) # Signal missing mapping and wait (only if we care about mappings)
task_id = params.get("_task_id")
if task_id: if task_id:
from ..dependencies import get_task_manager
tm = get_task_manager()
logger.info(f"[MigrationPlugin][Action] Pausing for missing mapping in task {task_id}") logger.info(f"[MigrationPlugin][Action] Pausing for missing mapping in task {task_id}")
# In a real scenario, we'd pass the missing DB info to the frontend # In a real scenario, we'd pass the missing DB info to the frontend
# For this task, we'll just simulate the wait # For this task, we'll just simulate the wait
@@ -220,16 +241,25 @@ class MigrationPlugin(PluginBase):
except Exception as exc: except Exception as exc:
# Check for password error # Check for password error
error_msg = str(exc) error_msg = str(exc)
if "Must provide a password for the database" in error_msg: # The error message from Superset is often a JSON string inside a string.
# Extract database name (assuming format: "Must provide a password for the database 'PostgreSQL'") # We need to robustly detect the password requirement.
import re # Typical error: "Error importing dashboard: databases/PostgreSQL.yaml: {'_schema': ['Must provide a password for the database']}"
match = re.search(r"database '([^']+)'", error_msg)
db_name = match.group(1) if match else "unknown"
# Get task manager if "Must provide a password for the database" in error_msg:
from ..dependencies import get_task_manager # Extract database name
tm = get_task_manager() # Try to find "databases/DBNAME.yaml" pattern
task_id = params.get("_task_id") import re
db_name = "unknown"
match = re.search(r"databases/([^.]+)\.yaml", error_msg)
if match:
db_name = match.group(1)
else:
# Fallback: try to find 'database 'NAME'' pattern
match_alt = re.search(r"database '([^']+)'", error_msg)
if match_alt:
db_name = match_alt.group(1)
logger.warning(f"[MigrationPlugin][Action] Detected missing password for database: {db_name}")
if task_id: if task_id:
input_request = { input_request = {
@@ -251,6 +281,9 @@ class MigrationPlugin(PluginBase):
logger.info(f"[MigrationPlugin][Action] Retrying import for {title} with provided passwords.") logger.info(f"[MigrationPlugin][Action] Retrying import for {title} with provided passwords.")
to_c.import_dashboard(file_name=tmp_new_zip, dash_id=dash_id, dash_slug=dash_slug, passwords=passwords) to_c.import_dashboard(file_name=tmp_new_zip, dash_id=dash_id, dash_slug=dash_slug, passwords=passwords)
logger.info(f"[MigrationPlugin][Success] Dashboard {title} imported after password injection.") logger.info(f"[MigrationPlugin][Success] Dashboard {title} imported after password injection.")
# Clear passwords from params after use for security
if "passwords" in task.params:
del task.params["passwords"]
continue continue
logger.error(f"[MigrationPlugin][Failure] Failed to migrate dashboard {title}: {exc}", exc_info=True) logger.error(f"[MigrationPlugin][Failure] Failed to migrate dashboard {title}: {exc}", exc_info=True)

View File

@@ -1,242 +0,0 @@
// this file is generated — do not edit it
/// <reference types="@sveltejs/kit" />
/**
* Environment variables [loaded by Vite](https://vitejs.dev/guide/env-and-mode.html#env-files) from `.env` files and `process.env`. Like [`$env/dynamic/private`](https://svelte.dev/docs/kit/$env-dynamic-private), this module cannot be imported into client-side code. This module only includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) _and do_ start with [`config.kit.env.privatePrefix`](https://svelte.dev/docs/kit/configuration#env) (if configured).
*
* _Unlike_ [`$env/dynamic/private`](https://svelte.dev/docs/kit/$env-dynamic-private), the values exported from this module are statically injected into your bundle at build time, enabling optimisations like dead code elimination.
*
* ```ts
* import { API_KEY } from '$env/static/private';
* ```
*
* Note that all environment variables referenced in your code should be declared (for example in an `.env` file), even if they don't have a value until the app is deployed:
*
* ```
* MY_FEATURE_FLAG=""
* ```
*
* You can override `.env` values from the command line like so:
*
* ```sh
* MY_FEATURE_FLAG="enabled" npm run dev
* ```
*/
declare module '$env/static/private' {
export const USER: string;
export const npm_config_user_agent: string;
export const XDG_SESSION_TYPE: string;
export const npm_node_execpath: string;
export const SHLVL: string;
export const npm_config_noproxy: string;
export const LESS: string;
export const HOME: string;
export const OLDPWD: string;
export const DESKTOP_SESSION: string;
export const npm_package_json: string;
export const LSCOLORS: string;
export const ZSH: string;
export const GNOME_SHELL_SESSION_MODE: string;
export const GTK_MODULES: string;
export const PAGER: string;
export const PS1: string;
export const npm_config_userconfig: string;
export const npm_config_local_prefix: string;
export const SYSTEMD_EXEC_PID: string;
export const DBUS_SESSION_BUS_ADDRESS: string;
export const COLORTERM: string;
export const COLOR: string;
export const npm_config_metrics_registry: string;
export const WAYLAND_DISPLAY: string;
export const LOGNAME: string;
export const SDKMAN_CANDIDATES_API: string;
export const _: string;
export const npm_config_prefix: string;
export const MEMORY_PRESSURE_WATCH: string;
export const XDG_SESSION_CLASS: string;
export const USERNAME: string;
export const TERM: string;
export const npm_config_cache: string;
export const GNOME_DESKTOP_SESSION_ID: string;
export const npm_config_node_gyp: string;
export const PATH: string;
export const SDKMAN_CANDIDATES_DIR: string;
export const NODE: string;
export const npm_package_name: string;
export const XDG_MENU_PREFIX: string;
export const SDKMAN_BROKER_API: string;
export const GNOME_TERMINAL_SCREEN: string;
export const GNOME_SETUP_DISPLAY: string;
export const XDG_RUNTIME_DIR: string;
export const DISPLAY: string;
export const LANG: string;
export const XDG_CURRENT_DESKTOP: string;
export const VIRTUAL_ENV_PROMPT: string;
export const XMODIFIERS: string;
export const XDG_SESSION_DESKTOP: string;
export const XAUTHORITY: string;
export const LS_COLORS: string;
export const GNOME_TERMINAL_SERVICE: string;
export const SDKMAN_DIR: string;
export const SDKMAN_PLATFORM: string;
export const npm_lifecycle_script: string;
export const SSH_AUTH_SOCK: string;
export const SHELL: string;
export const npm_package_version: string;
export const npm_lifecycle_event: string;
export const QT_ACCESSIBILITY: string;
export const GDMSESSION: string;
export const GOOGLE_CLOUD_PROJECT: string;
export const GPG_AGENT_INFO: string;
export const VIRTUAL_ENV: string;
export const QT_IM_MODULE: string;
export const npm_config_globalconfig: string;
export const npm_config_init_module: string;
export const JAVA_HOME: string;
export const PWD: string;
export const npm_config_globalignorefile: string;
export const npm_execpath: string;
export const XDG_DATA_DIRS: string;
export const npm_config_global_prefix: string;
export const npm_command: string;
export const QT_IM_MODULES: string;
export const MEMORY_PRESSURE_WRITE: string;
export const VTE_VERSION: string;
export const INIT_CWD: string;
export const EDITOR: string;
export const NODE_ENV: string;
}
/**
* Similar to [`$env/static/private`](https://svelte.dev/docs/kit/$env-static-private), except that it only includes environment variables that begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) (which defaults to `PUBLIC_`), and can therefore safely be exposed to client-side code.
*
* Values are replaced statically at build time.
*
* ```ts
* import { PUBLIC_BASE_URL } from '$env/static/public';
* ```
*/
declare module '$env/static/public' {
export const PUBLIC_WS_URL: string;
}
/**
* This module provides access to runtime environment variables, as defined by the platform you're running on. For example if you're using [`adapter-node`](https://github.com/sveltejs/kit/tree/main/packages/adapter-node) (or running [`vite preview`](https://svelte.dev/docs/kit/cli)), this is equivalent to `process.env`. This module only includes variables that _do not_ begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) _and do_ start with [`config.kit.env.privatePrefix`](https://svelte.dev/docs/kit/configuration#env) (if configured).
*
* This module cannot be imported into client-side code.
*
* ```ts
* import { env } from '$env/dynamic/private';
* console.log(env.DEPLOYMENT_SPECIFIC_VARIABLE);
* ```
*
* > [!NOTE] In `dev`, `$env/dynamic` always includes environment variables from `.env`. In `prod`, this behavior will depend on your adapter.
*/
declare module '$env/dynamic/private' {
export const env: {
USER: string;
npm_config_user_agent: string;
XDG_SESSION_TYPE: string;
npm_node_execpath: string;
SHLVL: string;
npm_config_noproxy: string;
LESS: string;
HOME: string;
OLDPWD: string;
DESKTOP_SESSION: string;
npm_package_json: string;
LSCOLORS: string;
ZSH: string;
GNOME_SHELL_SESSION_MODE: string;
GTK_MODULES: string;
PAGER: string;
PS1: string;
npm_config_userconfig: string;
npm_config_local_prefix: string;
SYSTEMD_EXEC_PID: string;
DBUS_SESSION_BUS_ADDRESS: string;
COLORTERM: string;
COLOR: string;
npm_config_metrics_registry: string;
WAYLAND_DISPLAY: string;
LOGNAME: string;
SDKMAN_CANDIDATES_API: string;
_: string;
npm_config_prefix: string;
MEMORY_PRESSURE_WATCH: string;
XDG_SESSION_CLASS: string;
USERNAME: string;
TERM: string;
npm_config_cache: string;
GNOME_DESKTOP_SESSION_ID: string;
npm_config_node_gyp: string;
PATH: string;
SDKMAN_CANDIDATES_DIR: string;
NODE: string;
npm_package_name: string;
XDG_MENU_PREFIX: string;
SDKMAN_BROKER_API: string;
GNOME_TERMINAL_SCREEN: string;
GNOME_SETUP_DISPLAY: string;
XDG_RUNTIME_DIR: string;
DISPLAY: string;
LANG: string;
XDG_CURRENT_DESKTOP: string;
VIRTUAL_ENV_PROMPT: string;
XMODIFIERS: string;
XDG_SESSION_DESKTOP: string;
XAUTHORITY: string;
LS_COLORS: string;
GNOME_TERMINAL_SERVICE: string;
SDKMAN_DIR: string;
SDKMAN_PLATFORM: string;
npm_lifecycle_script: string;
SSH_AUTH_SOCK: string;
SHELL: string;
npm_package_version: string;
npm_lifecycle_event: string;
QT_ACCESSIBILITY: string;
GDMSESSION: string;
GOOGLE_CLOUD_PROJECT: string;
GPG_AGENT_INFO: string;
VIRTUAL_ENV: string;
QT_IM_MODULE: string;
npm_config_globalconfig: string;
npm_config_init_module: string;
JAVA_HOME: string;
PWD: string;
npm_config_globalignorefile: string;
npm_execpath: string;
XDG_DATA_DIRS: string;
npm_config_global_prefix: string;
npm_command: string;
QT_IM_MODULES: string;
MEMORY_PRESSURE_WRITE: string;
VTE_VERSION: string;
INIT_CWD: string;
EDITOR: string;
NODE_ENV: string;
[key: `PUBLIC_${string}`]: undefined;
[key: `${string}`]: string | undefined;
}
}
/**
* Similar to [`$env/dynamic/private`](https://svelte.dev/docs/kit/$env-dynamic-private), but only includes variables that begin with [`config.kit.env.publicPrefix`](https://svelte.dev/docs/kit/configuration#env) (which defaults to `PUBLIC_`), and can therefore safely be exposed to client-side code.
*
* Note that public dynamic environment variables must all be sent from the server to the client, causing larger network requests — when possible, use `$env/static/public` instead.
*
* ```ts
* import { env } from '$env/dynamic/public';
* console.log(env.PUBLIC_DEPLOYMENT_SPECIFIC_VARIABLE);
* ```
*/
declare module '$env/dynamic/public' {
export const env: {
PUBLIC_WS_URL: string;
[key: `PUBLIC_${string}`]: string | undefined;
}
}

View File

@@ -1,31 +0,0 @@
export { matchers } from './matchers.js';
export const nodes = [
() => import('./nodes/0'),
() => import('./nodes/1'),
() => import('./nodes/2'),
() => import('./nodes/3')
];
export const server_loads = [];
export const dictionary = {
"/": [2],
"/settings": [3]
};
export const hooks = {
handleError: (({ error }) => { console.error(error) }),
reroute: (() => {}),
transport: {}
};
export const decoders = Object.fromEntries(Object.entries(hooks.transport).map(([k, v]) => [k, v.decode]));
export const encoders = Object.fromEntries(Object.entries(hooks.transport).map(([k, v]) => [k, v.encode]));
export const hash = false;
export const decode = (type, value) => decoders[type](value);
export { default as root } from '../root.js';

View File

@@ -1 +0,0 @@
export const matchers = {};

View File

@@ -1,3 +0,0 @@
import * as universal from "../../../../src/routes/+layout.ts";
export { universal };
export { default as component } from "../../../../src/routes/+layout.svelte";

View File

@@ -1 +0,0 @@
export { default as component } from "../../../../src/routes/+error.svelte";

View File

@@ -1,3 +0,0 @@
import * as universal from "../../../../src/routes/+page.ts";
export { universal };
export { default as component } from "../../../../src/routes/+page.svelte";

View File

@@ -1,3 +0,0 @@
import * as universal from "../../../../src/routes/settings/+page.ts";
export { universal };
export { default as component } from "../../../../src/routes/settings/+page.svelte";

View File

@@ -1,35 +0,0 @@
export { matchers } from './matchers.js';
export const nodes = [
() => import('./nodes/0'),
() => import('./nodes/1'),
() => import('./nodes/2'),
() => import('./nodes/3'),
() => import('./nodes/4'),
() => import('./nodes/5')
];
export const server_loads = [];
export const dictionary = {
"/": [2],
"/migration": [3],
"/migration/mappings": [4],
"/settings": [5]
};
export const hooks = {
handleError: (({ error }) => { console.error(error) }),
reroute: (() => {}),
transport: {}
};
export const decoders = Object.fromEntries(Object.entries(hooks.transport).map(([k, v]) => [k, v.decode]));
export const encoders = Object.fromEntries(Object.entries(hooks.transport).map(([k, v]) => [k, v.encode]));
export const hash = false;
export const decode = (type, value) => decoders[type](value);
export { default as root } from '../root.js';

View File

@@ -1 +0,0 @@
export const matchers = {};

View File

@@ -1,3 +0,0 @@
import * as universal from "../../../../src/routes/+layout.ts";
export { universal };
export { default as component } from "../../../../src/routes/+layout.svelte";

View File

@@ -1 +0,0 @@
export { default as component } from "../../../../src/routes/+error.svelte";

View File

@@ -1,3 +0,0 @@
import * as universal from "../../../../src/routes/+page.ts";
export { universal };
export { default as component } from "../../../../src/routes/+page.svelte";

View File

@@ -1 +0,0 @@
export { default as component } from "../../../../src/routes/migration/+page.svelte";

View File

@@ -1,3 +0,0 @@
import { asClassComponent } from 'svelte/legacy';
import Root from './root.svelte';
export default asClassComponent(Root);

View File

@@ -1,68 +0,0 @@
<!-- This file is generated by @sveltejs/kit — do not edit it! -->
<svelte:options runes={true} />
<script>
import { setContext, onMount, tick } from 'svelte';
import { browser } from '$app/environment';
// stores
let { stores, page, constructors, components = [], form, data_0 = null, data_1 = null } = $props();
if (!browser) {
// svelte-ignore state_referenced_locally
setContext('__svelte__', stores);
}
if (browser) {
$effect.pre(() => stores.page.set(page));
} else {
// svelte-ignore state_referenced_locally
stores.page.set(page);
}
$effect(() => {
stores;page;constructors;components;form;data_0;data_1;
stores.page.notify();
});
let mounted = $state(false);
let navigated = $state(false);
let title = $state(null);
onMount(() => {
const unsubscribe = stores.page.subscribe(() => {
if (mounted) {
navigated = true;
tick().then(() => {
title = document.title || 'untitled page';
});
}
});
mounted = true;
return unsubscribe;
});
const Pyramid_1=$derived(constructors[1])
</script>
{#if constructors[1]}
{@const Pyramid_0 = constructors[0]}
<!-- svelte-ignore binding_property_non_reactive -->
<Pyramid_0 bind:this={components[0]} data={data_0} {form} params={page.params}>
<!-- svelte-ignore binding_property_non_reactive -->
<Pyramid_1 bind:this={components[1]} data={data_1} {form} params={page.params} />
</Pyramid_0>
{:else}
{@const Pyramid_0 = constructors[0]}
<!-- svelte-ignore binding_property_non_reactive -->
<Pyramid_0 bind:this={components[0]} data={data_0} {form} params={page.params} />
{/if}
{#if mounted}
<div id="svelte-announcer" aria-live="assertive" aria-atomic="true" style="position: absolute; left: 0; top: 0; clip: rect(0 0 0 0); clip-path: inset(50%); overflow: hidden; white-space: nowrap; width: 1px; height: 1px">
{#if navigated}
{title}
{/if}
</div>
{/if}

View File

@@ -1,53 +0,0 @@
import root from '../root.js';
import { set_building, set_prerendering } from '__sveltekit/environment';
import { set_assets } from '$app/paths/internal/server';
import { set_manifest, set_read_implementation } from '__sveltekit/server';
import { set_private_env, set_public_env } from '../../../node_modules/@sveltejs/kit/src/runtime/shared-server.js';
export const options = {
app_template_contains_nonce: false,
async: false,
csp: {"mode":"auto","directives":{"upgrade-insecure-requests":false,"block-all-mixed-content":false},"reportOnly":{"upgrade-insecure-requests":false,"block-all-mixed-content":false}},
csrf_check_origin: true,
csrf_trusted_origins: [],
embedded: false,
env_public_prefix: 'PUBLIC_',
env_private_prefix: '',
hash_routing: false,
hooks: null, // added lazily, via `get_hooks`
preload_strategy: "modulepreload",
root,
service_worker: false,
service_worker_options: undefined,
templates: {
app: ({ head, body, assets, nonce, env }) => "<!DOCTYPE html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<link rel=\"icon\" href=\"" + assets + "/favicon.png\" />\n\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n\t\t" + head + "\n\t</head>\n\t<body data-sveltekit-preload-data=\"hover\">\n\t\t<div style=\"display: contents\">" + body + "</div>\n\t</body>\n</html>\n",
error: ({ status, message }) => "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<title>" + message + "</title>\n\n\t\t<style>\n\t\t\tbody {\n\t\t\t\t--bg: white;\n\t\t\t\t--fg: #222;\n\t\t\t\t--divider: #ccc;\n\t\t\t\tbackground: var(--bg);\n\t\t\t\tcolor: var(--fg);\n\t\t\t\tfont-family:\n\t\t\t\t\tsystem-ui,\n\t\t\t\t\t-apple-system,\n\t\t\t\t\tBlinkMacSystemFont,\n\t\t\t\t\t'Segoe UI',\n\t\t\t\t\tRoboto,\n\t\t\t\t\tOxygen,\n\t\t\t\t\tUbuntu,\n\t\t\t\t\tCantarell,\n\t\t\t\t\t'Open Sans',\n\t\t\t\t\t'Helvetica Neue',\n\t\t\t\t\tsans-serif;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\theight: 100vh;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t.error {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tmax-width: 32rem;\n\t\t\t\tmargin: 0 1rem;\n\t\t\t}\n\n\t\t\t.status {\n\t\t\t\tfont-weight: 200;\n\t\t\t\tfont-size: 3rem;\n\t\t\t\tline-height: 1;\n\t\t\t\tposition: relative;\n\t\t\t\ttop: -0.05rem;\n\t\t\t}\n\n\t\t\t.message {\n\t\t\t\tborder-left: 1px solid var(--divider);\n\t\t\t\tpadding: 0 0 0 1rem;\n\t\t\t\tmargin: 0 0 0 1rem;\n\t\t\t\tmin-height: 2.5rem;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t}\n\n\t\t\t.message h1 {\n\t\t\t\tfont-weight: 400;\n\t\t\t\tfont-size: 1em;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\tbody {\n\t\t\t\t\t--bg: #222;\n\t\t\t\t\t--fg: #ddd;\n\t\t\t\t\t--divider: #666;\n\t\t\t\t}\n\t\t\t}\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<div class=\"error\">\n\t\t\t<span class=\"status\">" + status + "</span>\n\t\t\t<div class=\"message\">\n\t\t\t\t<h1>" + message + "</h1>\n\t\t\t</div>\n\t\t</div>\n\t</body>\n</html>\n"
},
version_hash: "oj9twc"
};
export async function get_hooks() {
let handle;
let handleFetch;
let handleError;
let handleValidationError;
let init;
let reroute;
let transport;
return {
handle,
handleFetch,
handleError,
handleValidationError,
init,
reroute,
transport
};
}
export { set_assets, set_building, set_manifest, set_prerendering, set_private_env, set_public_env, set_read_implementation };

View File

@@ -1,44 +0,0 @@
// this file is generated — do not edit it
declare module "svelte/elements" {
export interface HTMLAttributes<T> {
'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null;
'data-sveltekit-noscroll'?: true | '' | 'off' | undefined | null;
'data-sveltekit-preload-code'?:
| true
| ''
| 'eager'
| 'viewport'
| 'hover'
| 'tap'
| 'off'
| undefined
| null;
'data-sveltekit-preload-data'?: true | '' | 'hover' | 'tap' | 'off' | undefined | null;
'data-sveltekit-reload'?: true | '' | 'off' | undefined | null;
'data-sveltekit-replacestate'?: true | '' | 'off' | undefined | null;
}
}
export {};
declare module "$app/types" {
export interface AppTypes {
RouteId(): "/" | "/migration" | "/migration/mappings" | "/settings";
RouteParams(): {
};
LayoutParams(): {
"/": Record<string, never>;
"/migration": Record<string, never>;
"/migration/mappings": Record<string, never>;
"/settings": Record<string, never>
};
Pathname(): "/" | "/migration" | "/migration/" | "/migration/mappings" | "/migration/mappings/" | "/settings" | "/settings/";
ResolvedPathname(): `${"" | `/${string}`}${ReturnType<AppTypes['Pathname']>}`;
Asset(): string & {};
}
}

View File

@@ -1,162 +0,0 @@
{
".svelte-kit/generated/client-optimized/app.js": {
"file": "_app/immutable/entry/app.BXnpILpp.js",
"name": "entry/app",
"src": ".svelte-kit/generated/client-optimized/app.js",
"isEntry": true,
"imports": [
"_BtL0wB3H.js",
"_cv2LK44M.js",
"_BxZpmA7Z.js",
"_vVxDbqKK.js"
],
"dynamicImports": [
".svelte-kit/generated/client-optimized/nodes/0.js",
".svelte-kit/generated/client-optimized/nodes/1.js",
".svelte-kit/generated/client-optimized/nodes/2.js",
".svelte-kit/generated/client-optimized/nodes/3.js"
]
},
".svelte-kit/generated/client-optimized/nodes/0.js": {
"file": "_app/immutable/nodes/0.DZdF_zz-.js",
"name": "nodes/0",
"src": ".svelte-kit/generated/client-optimized/nodes/0.js",
"isEntry": true,
"isDynamicEntry": true,
"imports": [
"_cv2LK44M.js",
"_CRLlKr96.js",
"_BtL0wB3H.js",
"_xdjHc-A2.js",
"_DXE57cnx.js",
"_Dbod7Wv8.js"
],
"css": [
"_app/immutable/assets/0.RZHRvmcL.css"
]
},
".svelte-kit/generated/client-optimized/nodes/1.js": {
"file": "_app/immutable/nodes/1.Bh-fCbID.js",
"name": "nodes/1",
"src": ".svelte-kit/generated/client-optimized/nodes/1.js",
"isEntry": true,
"isDynamicEntry": true,
"imports": [
"_cv2LK44M.js",
"_CRLlKr96.js",
"_BtL0wB3H.js",
"_DXE57cnx.js"
]
},
".svelte-kit/generated/client-optimized/nodes/2.js": {
"file": "_app/immutable/nodes/2.BmiXdPHI.js",
"name": "nodes/2",
"src": ".svelte-kit/generated/client-optimized/nodes/2.js",
"isEntry": true,
"isDynamicEntry": true,
"imports": [
"_DyPeVqDG.js",
"_cv2LK44M.js",
"_CRLlKr96.js",
"_BtL0wB3H.js",
"_vVxDbqKK.js",
"_Dbod7Wv8.js",
"_BxZpmA7Z.js",
"_xdjHc-A2.js"
]
},
".svelte-kit/generated/client-optimized/nodes/3.js": {
"file": "_app/immutable/nodes/3.guWMyWpk.js",
"name": "nodes/3",
"src": ".svelte-kit/generated/client-optimized/nodes/3.js",
"isEntry": true,
"isDynamicEntry": true,
"imports": [
"_DyPeVqDG.js",
"_cv2LK44M.js",
"_CRLlKr96.js",
"_BtL0wB3H.js",
"_vVxDbqKK.js",
"_Dbod7Wv8.js"
]
},
"_BtL0wB3H.js": {
"file": "_app/immutable/chunks/BtL0wB3H.js",
"name": "index"
},
"_BxZpmA7Z.js": {
"file": "_app/immutable/chunks/BxZpmA7Z.js",
"name": "index-client",
"imports": [
"_BtL0wB3H.js"
]
},
"_CRLlKr96.js": {
"file": "_app/immutable/chunks/CRLlKr96.js",
"name": "legacy",
"imports": [
"_BtL0wB3H.js"
]
},
"_D0iaTcAo.js": {
"file": "_app/immutable/chunks/D0iaTcAo.js",
"name": "entry",
"imports": [
"_BtL0wB3H.js",
"_BxZpmA7Z.js"
]
},
"_DXE57cnx.js": {
"file": "_app/immutable/chunks/DXE57cnx.js",
"name": "stores",
"imports": [
"_D0iaTcAo.js"
]
},
"_Dbod7Wv8.js": {
"file": "_app/immutable/chunks/Dbod7Wv8.js",
"name": "toasts",
"imports": [
"_BtL0wB3H.js"
]
},
"_DyPeVqDG.js": {
"file": "_app/immutable/chunks/DyPeVqDG.js",
"name": "api",
"imports": [
"_BtL0wB3H.js",
"_Dbod7Wv8.js"
]
},
"_cv2LK44M.js": {
"file": "_app/immutable/chunks/cv2LK44M.js",
"name": "disclose-version",
"imports": [
"_BtL0wB3H.js"
]
},
"_vVxDbqKK.js": {
"file": "_app/immutable/chunks/vVxDbqKK.js",
"name": "props",
"imports": [
"_BtL0wB3H.js",
"_cv2LK44M.js"
]
},
"_xdjHc-A2.js": {
"file": "_app/immutable/chunks/xdjHc-A2.js",
"name": "class",
"imports": [
"_BtL0wB3H.js"
]
},
"node_modules/@sveltejs/kit/src/runtime/client/entry.js": {
"file": "_app/immutable/entry/start.BHAeOrfR.js",
"name": "entry/start",
"src": "node_modules/@sveltejs/kit/src/runtime/client/entry.js",
"isEntry": true,
"imports": [
"_D0iaTcAo.js"
]
}
}

View File

@@ -1 +0,0 @@
{"version":"1766262590857"}

View File

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

View File

@@ -1,180 +0,0 @@
{
".svelte-kit/generated/server/internal.js": {
"file": "internal.js",
"name": "internal",
"src": ".svelte-kit/generated/server/internal.js",
"isEntry": true,
"imports": [
"_internal.js",
"_environment.js"
]
},
"_api.js": {
"file": "chunks/api.js",
"name": "api",
"imports": [
"_toasts.js"
]
},
"_environment.js": {
"file": "chunks/environment.js",
"name": "environment"
},
"_equality.js": {
"file": "chunks/equality.js",
"name": "equality"
},
"_exports.js": {
"file": "chunks/exports.js",
"name": "exports"
},
"_false.js": {
"file": "chunks/false.js",
"name": "false"
},
"_index.js": {
"file": "chunks/index.js",
"name": "index",
"imports": [
"_equality.js"
]
},
"_index2.js": {
"file": "chunks/index2.js",
"name": "index",
"imports": [
"_false.js",
"_equality.js"
]
},
"_internal.js": {
"file": "chunks/internal.js",
"name": "internal",
"imports": [
"_index2.js",
"_equality.js",
"_environment.js"
]
},
"_shared.js": {
"file": "chunks/shared.js",
"name": "shared",
"imports": [
"_utils.js"
]
},
"_stores.js": {
"file": "chunks/stores.js",
"name": "stores",
"imports": [
"_index2.js",
"_exports.js",
"_utils.js",
"_equality.js"
]
},
"_toasts.js": {
"file": "chunks/toasts.js",
"name": "toasts",
"imports": [
"_index.js"
]
},
"_utils.js": {
"file": "chunks/utils.js",
"name": "utils"
},
"node_modules/@sveltejs/kit/src/runtime/app/server/remote/index.js": {
"file": "remote-entry.js",
"name": "remote-entry",
"src": "node_modules/@sveltejs/kit/src/runtime/app/server/remote/index.js",
"isEntry": true,
"imports": [
"_shared.js",
"_false.js",
"_environment.js"
]
},
"node_modules/@sveltejs/kit/src/runtime/server/index.js": {
"file": "index.js",
"name": "index",
"src": "node_modules/@sveltejs/kit/src/runtime/server/index.js",
"isEntry": true,
"imports": [
"_false.js",
"_environment.js",
"_shared.js",
"_exports.js",
"_utils.js",
"_index.js",
"_internal.js"
]
},
"src/routes/+error.svelte": {
"file": "entries/pages/_error.svelte.js",
"name": "entries/pages/_error.svelte",
"src": "src/routes/+error.svelte",
"isEntry": true,
"imports": [
"_index2.js",
"_stores.js"
]
},
"src/routes/+layout.svelte": {
"file": "entries/pages/_layout.svelte.js",
"name": "entries/pages/_layout.svelte",
"src": "src/routes/+layout.svelte",
"isEntry": true,
"imports": [
"_index2.js",
"_stores.js",
"_toasts.js"
],
"css": [
"_app/immutable/assets/_layout.RZHRvmcL.css"
]
},
"src/routes/+layout.ts": {
"file": "entries/pages/_layout.ts.js",
"name": "entries/pages/_layout.ts",
"src": "src/routes/+layout.ts",
"isEntry": true
},
"src/routes/+page.svelte": {
"file": "entries/pages/_page.svelte.js",
"name": "entries/pages/_page.svelte",
"src": "src/routes/+page.svelte",
"isEntry": true,
"imports": [
"_index2.js",
"_index.js"
]
},
"src/routes/+page.ts": {
"file": "entries/pages/_page.ts.js",
"name": "entries/pages/_page.ts",
"src": "src/routes/+page.ts",
"isEntry": true,
"imports": [
"_api.js"
]
},
"src/routes/settings/+page.svelte": {
"file": "entries/pages/settings/_page.svelte.js",
"name": "entries/pages/settings/_page.svelte",
"src": "src/routes/settings/+page.svelte",
"isEntry": true,
"imports": [
"_index2.js"
]
},
"src/routes/settings/+page.ts": {
"file": "entries/pages/settings/_page.ts.js",
"name": "entries/pages/settings/_page.ts",
"src": "src/routes/settings/+page.ts",
"isEntry": true,
"imports": [
"_api.js"
]
}
}

View File

@@ -1,77 +0,0 @@
import { a as addToast } from "./toasts.js";
const API_BASE_URL = "/api";
async function fetchApi(endpoint) {
try {
console.log(`[api.fetchApi][Action] Fetching from context={{'endpoint': '${endpoint}'}}`);
const response = await fetch(`${API_BASE_URL}${endpoint}`);
if (!response.ok) {
throw new Error(`API request failed with status ${response.status}`);
}
return await response.json();
} catch (error) {
console.error(`[api.fetchApi][Coherence:Failed] Error fetching from ${endpoint}:`, error);
addToast(error.message, "error");
throw error;
}
}
async function postApi(endpoint, body) {
try {
console.log(`[api.postApi][Action] Posting to context={{'endpoint': '${endpoint}'}}`);
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(body)
});
if (!response.ok) {
throw new Error(`API request failed with status ${response.status}`);
}
return await response.json();
} catch (error) {
console.error(`[api.postApi][Coherence:Failed] Error posting to ${endpoint}:`, error);
addToast(error.message, "error");
throw error;
}
}
async function requestApi(endpoint, method = "GET", body = null) {
try {
console.log(`[api.requestApi][Action] ${method} to context={{'endpoint': '${endpoint}'}}`);
const options = {
method,
headers: {
"Content-Type": "application/json"
}
};
if (body) {
options.body = JSON.stringify(body);
}
const response = await fetch(`${API_BASE_URL}${endpoint}`, options);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || `API request failed with status ${response.status}`);
}
return await response.json();
} catch (error) {
console.error(`[api.requestApi][Coherence:Failed] Error ${method} to ${endpoint}:`, error);
addToast(error.message, "error");
throw error;
}
}
const api = {
getPlugins: () => fetchApi("/plugins/"),
getTasks: () => fetchApi("/tasks/"),
getTask: (taskId) => fetchApi(`/tasks/${taskId}`),
createTask: (pluginId, params) => postApi("/tasks/", { plugin_id: pluginId, params }),
// Settings
getSettings: () => fetchApi("/settings/"),
updateGlobalSettings: (settings) => requestApi("/settings/global", "PATCH", settings),
getEnvironments: () => fetchApi("/settings/environments"),
addEnvironment: (env) => postApi("/settings/environments", env),
updateEnvironment: (id, env) => requestApi(`/settings/environments/${id}`, "PUT", env),
deleteEnvironment: (id) => requestApi(`/settings/environments/${id}`, "DELETE"),
testEnvironmentConnection: (id) => postApi(`/settings/environments/${id}/test`, {})
};
export {
api as a
};

View File

@@ -1,34 +0,0 @@
let base = "";
let assets = base;
const app_dir = "_app";
const relative = true;
const initial = { base, assets };
function override(paths) {
base = paths.base;
assets = paths.assets;
}
function reset() {
base = initial.base;
assets = initial.assets;
}
function set_assets(path) {
assets = initial.assets = path;
}
let prerendering = false;
function set_building() {
}
function set_prerendering() {
prerendering = true;
}
export {
assets as a,
base as b,
app_dir as c,
reset as d,
set_building as e,
set_prerendering as f,
override as o,
prerendering as p,
relative as r,
set_assets as s
};

View File

@@ -1,51 +0,0 @@
var is_array = Array.isArray;
var index_of = Array.prototype.indexOf;
var array_from = Array.from;
var define_property = Object.defineProperty;
var get_descriptor = Object.getOwnPropertyDescriptor;
var object_prototype = Object.prototype;
var array_prototype = Array.prototype;
var get_prototype_of = Object.getPrototypeOf;
var is_extensible = Object.isExtensible;
const noop = () => {
};
function run_all(arr) {
for (var i = 0; i < arr.length; i++) {
arr[i]();
}
}
function deferred() {
var resolve;
var reject;
var promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
}
function equals(value) {
return value === this.v;
}
function safe_not_equal(a, b) {
return a != a ? b == b : a !== b || a !== null && typeof a === "object" || typeof a === "function";
}
function safe_equals(value) {
return !safe_not_equal(value, this.v);
}
export {
array_from as a,
deferred as b,
array_prototype as c,
define_property as d,
equals as e,
get_prototype_of as f,
get_descriptor as g,
is_extensible as h,
is_array as i,
index_of as j,
safe_not_equal as k,
noop as n,
object_prototype as o,
run_all as r,
safe_equals as s
};

View File

@@ -1,174 +0,0 @@
const SCHEME = /^[a-z][a-z\d+\-.]+:/i;
const internal = new URL("sveltekit-internal://");
function resolve(base, path) {
if (path[0] === "/" && path[1] === "/") return path;
let url = new URL(base, internal);
url = new URL(path, url);
return url.protocol === internal.protocol ? url.pathname + url.search + url.hash : url.href;
}
function normalize_path(path, trailing_slash) {
if (path === "/" || trailing_slash === "ignore") return path;
if (trailing_slash === "never") {
return path.endsWith("/") ? path.slice(0, -1) : path;
} else if (trailing_slash === "always" && !path.endsWith("/")) {
return path + "/";
}
return path;
}
function decode_pathname(pathname) {
return pathname.split("%25").map(decodeURI).join("%25");
}
function decode_params(params) {
for (const key in params) {
params[key] = decodeURIComponent(params[key]);
}
return params;
}
function make_trackable(url, callback, search_params_callback, allow_hash = false) {
const tracked = new URL(url);
Object.defineProperty(tracked, "searchParams", {
value: new Proxy(tracked.searchParams, {
get(obj, key) {
if (key === "get" || key === "getAll" || key === "has") {
return (param) => {
search_params_callback(param);
return obj[key](param);
};
}
callback();
const value = Reflect.get(obj, key);
return typeof value === "function" ? value.bind(obj) : value;
}
}),
enumerable: true,
configurable: true
});
const tracked_url_properties = ["href", "pathname", "search", "toString", "toJSON"];
if (allow_hash) tracked_url_properties.push("hash");
for (const property of tracked_url_properties) {
Object.defineProperty(tracked, property, {
get() {
callback();
return url[property];
},
enumerable: true,
configurable: true
});
}
{
tracked[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")] = (depth, opts, inspect) => {
return inspect(url, opts);
};
tracked.searchParams[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")] = (depth, opts, inspect) => {
return inspect(url.searchParams, opts);
};
}
if (!allow_hash) {
disable_hash(tracked);
}
return tracked;
}
function disable_hash(url) {
allow_nodejs_console_log(url);
Object.defineProperty(url, "hash", {
get() {
throw new Error(
"Cannot access event.url.hash. Consider using `page.url.hash` inside a component instead"
);
}
});
}
function disable_search(url) {
allow_nodejs_console_log(url);
for (const property of ["search", "searchParams"]) {
Object.defineProperty(url, property, {
get() {
throw new Error(`Cannot access url.${property} on a page with prerendering enabled`);
}
});
}
}
function allow_nodejs_console_log(url) {
{
url[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")] = (depth, opts, inspect) => {
return inspect(new URL(url), opts);
};
}
}
function validator(expected) {
function validate(module, file) {
if (!module) return;
for (const key in module) {
if (key[0] === "_" || expected.has(key)) continue;
const values = [...expected.values()];
const hint = hint_for_supported_files(key, file?.slice(file.lastIndexOf("."))) ?? `valid exports are ${values.join(", ")}, or anything with a '_' prefix`;
throw new Error(`Invalid export '${key}'${file ? ` in ${file}` : ""} (${hint})`);
}
}
return validate;
}
function hint_for_supported_files(key, ext = ".js") {
const supported_files = [];
if (valid_layout_exports.has(key)) {
supported_files.push(`+layout${ext}`);
}
if (valid_page_exports.has(key)) {
supported_files.push(`+page${ext}`);
}
if (valid_layout_server_exports.has(key)) {
supported_files.push(`+layout.server${ext}`);
}
if (valid_page_server_exports.has(key)) {
supported_files.push(`+page.server${ext}`);
}
if (valid_server_exports.has(key)) {
supported_files.push(`+server${ext}`);
}
if (supported_files.length > 0) {
return `'${key}' is a valid export in ${supported_files.slice(0, -1).join(", ")}${supported_files.length > 1 ? " or " : ""}${supported_files.at(-1)}`;
}
}
const valid_layout_exports = /* @__PURE__ */ new Set([
"load",
"prerender",
"csr",
"ssr",
"trailingSlash",
"config"
]);
const valid_page_exports = /* @__PURE__ */ new Set([...valid_layout_exports, "entries"]);
const valid_layout_server_exports = /* @__PURE__ */ new Set([...valid_layout_exports]);
const valid_page_server_exports = /* @__PURE__ */ new Set([...valid_layout_server_exports, "actions", "entries"]);
const valid_server_exports = /* @__PURE__ */ new Set([
"GET",
"POST",
"PATCH",
"PUT",
"DELETE",
"OPTIONS",
"HEAD",
"fallback",
"prerender",
"trailingSlash",
"config",
"entries"
]);
const validate_layout_exports = validator(valid_layout_exports);
const validate_page_exports = validator(valid_page_exports);
const validate_layout_server_exports = validator(valid_layout_server_exports);
const validate_page_server_exports = validator(valid_page_server_exports);
const validate_server_exports = validator(valid_server_exports);
export {
SCHEME as S,
decode_params as a,
validate_layout_exports as b,
validate_page_server_exports as c,
disable_search as d,
validate_page_exports as e,
decode_pathname as f,
validate_server_exports as g,
make_trackable as m,
normalize_path as n,
resolve as r,
validate_layout_server_exports as v
};

View File

@@ -1,4 +0,0 @@
const BROWSER = false;
export {
BROWSER as B
};

View File

@@ -1,59 +0,0 @@
import { n as noop, k as safe_not_equal } from "./equality.js";
import "clsx";
const subscriber_queue = [];
function readable(value, start) {
return {
subscribe: writable(value, start).subscribe
};
}
function writable(value, start = noop) {
let stop = null;
const subscribers = /* @__PURE__ */ new Set();
function set(new_value) {
if (safe_not_equal(value, new_value)) {
value = new_value;
if (stop) {
const run_queue = !subscriber_queue.length;
for (const subscriber of subscribers) {
subscriber[1]();
subscriber_queue.push(subscriber, value);
}
if (run_queue) {
for (let i = 0; i < subscriber_queue.length; i += 2) {
subscriber_queue[i][0](subscriber_queue[i + 1]);
}
subscriber_queue.length = 0;
}
}
}
}
function update(fn) {
set(fn(
/** @type {T} */
value
));
}
function subscribe(run, invalidate = noop) {
const subscriber = [run, invalidate];
subscribers.add(subscriber);
if (subscribers.size === 1) {
stop = start(set, update) || noop;
}
run(
/** @type {T} */
value
);
return () => {
subscribers.delete(subscriber);
if (subscribers.size === 0 && stop) {
stop();
stop = null;
}
};
}
return { set, update, subscribe };
}
export {
readable as r,
writable as w
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,982 +0,0 @@
import { H as HYDRATION_ERROR, C as COMMENT_NODE, a as HYDRATION_END, g as get_next_sibling, b as HYDRATION_START, c as HYDRATION_START_ELSE, e as effect_tracking, d as get, s as source, r as render_effect, u as untrack, i as increment, q as queue_micro_task, f as active_effect, h as block, j as branch, B as Batch, p as pause_effect, k as create_text, l as set_active_effect, m as set_active_reaction, n as set_component_context, o as handle_error, t as active_reaction, v as component_context, w as move_effect, x as internal_set, y as destroy_effect, z as invoke_error_boundary, A as svelte_boundary_reset_onerror, E as EFFECT_TRANSPARENT, D as EFFECT_PRESERVED, F as BOUNDARY_EFFECT, G as init_operations, I as get_first_child, J as hydration_failed, K as clear_text_content, L as component_root, M as is_passive_event, N as push, O as pop, P as set, Q as LEGACY_PROPS, R as flushSync, S as mutable_source, T as render, U as setContext } from "./index2.js";
import { d as define_property, a as array_from } from "./equality.js";
import "clsx";
import "./environment.js";
let public_env = {};
function set_private_env(environment) {
}
function set_public_env(environment) {
public_env = environment;
}
function hydration_mismatch(location) {
{
console.warn(`https://svelte.dev/e/hydration_mismatch`);
}
}
function svelte_boundary_reset_noop() {
{
console.warn(`https://svelte.dev/e/svelte_boundary_reset_noop`);
}
}
let hydrating = false;
function set_hydrating(value) {
hydrating = value;
}
let hydrate_node;
function set_hydrate_node(node) {
if (node === null) {
hydration_mismatch();
throw HYDRATION_ERROR;
}
return hydrate_node = node;
}
function hydrate_next() {
return set_hydrate_node(get_next_sibling(hydrate_node));
}
function next(count = 1) {
if (hydrating) {
var i = count;
var node = hydrate_node;
while (i--) {
node = /** @type {TemplateNode} */
get_next_sibling(node);
}
hydrate_node = node;
}
}
function skip_nodes(remove = true) {
var depth = 0;
var node = hydrate_node;
while (true) {
if (node.nodeType === COMMENT_NODE) {
var data = (
/** @type {Comment} */
node.data
);
if (data === HYDRATION_END) {
if (depth === 0) return node;
depth -= 1;
} else if (data === HYDRATION_START || data === HYDRATION_START_ELSE) {
depth += 1;
}
}
var next2 = (
/** @type {TemplateNode} */
get_next_sibling(node)
);
if (remove) node.remove();
node = next2;
}
}
function createSubscriber(start) {
let subscribers = 0;
let version = source(0);
let stop;
return () => {
if (effect_tracking()) {
get(version);
render_effect(() => {
if (subscribers === 0) {
stop = untrack(() => start(() => increment(version)));
}
subscribers += 1;
return () => {
queue_micro_task(() => {
subscribers -= 1;
if (subscribers === 0) {
stop?.();
stop = void 0;
increment(version);
}
});
};
});
}
};
}
var flags = EFFECT_TRANSPARENT | EFFECT_PRESERVED | BOUNDARY_EFFECT;
function boundary(node, props, children) {
new Boundary(node, props, children);
}
class Boundary {
/** @type {Boundary | null} */
parent;
#pending = false;
/** @type {TemplateNode} */
#anchor;
/** @type {TemplateNode | null} */
#hydrate_open = hydrating ? hydrate_node : null;
/** @type {BoundaryProps} */
#props;
/** @type {((anchor: Node) => void)} */
#children;
/** @type {Effect} */
#effect;
/** @type {Effect | null} */
#main_effect = null;
/** @type {Effect | null} */
#pending_effect = null;
/** @type {Effect | null} */
#failed_effect = null;
/** @type {DocumentFragment | null} */
#offscreen_fragment = null;
/** @type {TemplateNode | null} */
#pending_anchor = null;
#local_pending_count = 0;
#pending_count = 0;
#is_creating_fallback = false;
/**
* A source containing the number of pending async deriveds/expressions.
* Only created if `$effect.pending()` is used inside the boundary,
* otherwise updating the source results in needless `Batch.ensure()`
* calls followed by no-op flushes
* @type {Source<number> | null}
*/
#effect_pending = null;
#effect_pending_subscriber = createSubscriber(() => {
this.#effect_pending = source(this.#local_pending_count);
return () => {
this.#effect_pending = null;
};
});
/**
* @param {TemplateNode} node
* @param {BoundaryProps} props
* @param {((anchor: Node) => void)} children
*/
constructor(node, props, children) {
this.#anchor = node;
this.#props = props;
this.#children = children;
this.parent = /** @type {Effect} */
active_effect.b;
this.#pending = !!this.#props.pending;
this.#effect = block(() => {
active_effect.b = this;
if (hydrating) {
const comment = this.#hydrate_open;
hydrate_next();
const server_rendered_pending = (
/** @type {Comment} */
comment.nodeType === COMMENT_NODE && /** @type {Comment} */
comment.data === HYDRATION_START_ELSE
);
if (server_rendered_pending) {
this.#hydrate_pending_content();
} else {
this.#hydrate_resolved_content();
}
} else {
var anchor = this.#get_anchor();
try {
this.#main_effect = branch(() => children(anchor));
} catch (error) {
this.error(error);
}
if (this.#pending_count > 0) {
this.#show_pending_snippet();
} else {
this.#pending = false;
}
}
return () => {
this.#pending_anchor?.remove();
};
}, flags);
if (hydrating) {
this.#anchor = hydrate_node;
}
}
#hydrate_resolved_content() {
try {
this.#main_effect = branch(() => this.#children(this.#anchor));
} catch (error) {
this.error(error);
}
this.#pending = false;
}
#hydrate_pending_content() {
const pending = this.#props.pending;
if (!pending) {
return;
}
this.#pending_effect = branch(() => pending(this.#anchor));
Batch.enqueue(() => {
var anchor = this.#get_anchor();
this.#main_effect = this.#run(() => {
Batch.ensure();
return branch(() => this.#children(anchor));
});
if (this.#pending_count > 0) {
this.#show_pending_snippet();
} else {
pause_effect(
/** @type {Effect} */
this.#pending_effect,
() => {
this.#pending_effect = null;
}
);
this.#pending = false;
}
});
}
#get_anchor() {
var anchor = this.#anchor;
if (this.#pending) {
this.#pending_anchor = create_text();
this.#anchor.before(this.#pending_anchor);
anchor = this.#pending_anchor;
}
return anchor;
}
/**
* Returns `true` if the effect exists inside a boundary whose pending snippet is shown
* @returns {boolean}
*/
is_pending() {
return this.#pending || !!this.parent && this.parent.is_pending();
}
has_pending_snippet() {
return !!this.#props.pending;
}
/**
* @param {() => Effect | null} fn
*/
#run(fn) {
var previous_effect = active_effect;
var previous_reaction = active_reaction;
var previous_ctx = component_context;
set_active_effect(this.#effect);
set_active_reaction(this.#effect);
set_component_context(this.#effect.ctx);
try {
return fn();
} catch (e) {
handle_error(e);
return null;
} finally {
set_active_effect(previous_effect);
set_active_reaction(previous_reaction);
set_component_context(previous_ctx);
}
}
#show_pending_snippet() {
const pending = (
/** @type {(anchor: Node) => void} */
this.#props.pending
);
if (this.#main_effect !== null) {
this.#offscreen_fragment = document.createDocumentFragment();
this.#offscreen_fragment.append(
/** @type {TemplateNode} */
this.#pending_anchor
);
move_effect(this.#main_effect, this.#offscreen_fragment);
}
if (this.#pending_effect === null) {
this.#pending_effect = branch(() => pending(this.#anchor));
}
}
/**
* Updates the pending count associated with the currently visible pending snippet,
* if any, such that we can replace the snippet with content once work is done
* @param {1 | -1} d
*/
#update_pending_count(d) {
if (!this.has_pending_snippet()) {
if (this.parent) {
this.parent.#update_pending_count(d);
}
return;
}
this.#pending_count += d;
if (this.#pending_count === 0) {
this.#pending = false;
if (this.#pending_effect) {
pause_effect(this.#pending_effect, () => {
this.#pending_effect = null;
});
}
if (this.#offscreen_fragment) {
this.#anchor.before(this.#offscreen_fragment);
this.#offscreen_fragment = null;
}
}
}
/**
* Update the source that powers `$effect.pending()` inside this boundary,
* and controls when the current `pending` snippet (if any) is removed.
* Do not call from inside the class
* @param {1 | -1} d
*/
update_pending_count(d) {
this.#update_pending_count(d);
this.#local_pending_count += d;
if (this.#effect_pending) {
internal_set(this.#effect_pending, this.#local_pending_count);
}
}
get_effect_pending() {
this.#effect_pending_subscriber();
return get(
/** @type {Source<number>} */
this.#effect_pending
);
}
/** @param {unknown} error */
error(error) {
var onerror = this.#props.onerror;
let failed = this.#props.failed;
if (this.#is_creating_fallback || !onerror && !failed) {
throw error;
}
if (this.#main_effect) {
destroy_effect(this.#main_effect);
this.#main_effect = null;
}
if (this.#pending_effect) {
destroy_effect(this.#pending_effect);
this.#pending_effect = null;
}
if (this.#failed_effect) {
destroy_effect(this.#failed_effect);
this.#failed_effect = null;
}
if (hydrating) {
set_hydrate_node(
/** @type {TemplateNode} */
this.#hydrate_open
);
next();
set_hydrate_node(skip_nodes());
}
var did_reset = false;
var calling_on_error = false;
const reset = () => {
if (did_reset) {
svelte_boundary_reset_noop();
return;
}
did_reset = true;
if (calling_on_error) {
svelte_boundary_reset_onerror();
}
Batch.ensure();
this.#local_pending_count = 0;
if (this.#failed_effect !== null) {
pause_effect(this.#failed_effect, () => {
this.#failed_effect = null;
});
}
this.#pending = this.has_pending_snippet();
this.#main_effect = this.#run(() => {
this.#is_creating_fallback = false;
return branch(() => this.#children(this.#anchor));
});
if (this.#pending_count > 0) {
this.#show_pending_snippet();
} else {
this.#pending = false;
}
};
var previous_reaction = active_reaction;
try {
set_active_reaction(null);
calling_on_error = true;
onerror?.(error, reset);
calling_on_error = false;
} catch (error2) {
invoke_error_boundary(error2, this.#effect && this.#effect.parent);
} finally {
set_active_reaction(previous_reaction);
}
if (failed) {
queue_micro_task(() => {
this.#failed_effect = this.#run(() => {
Batch.ensure();
this.#is_creating_fallback = true;
try {
return branch(() => {
failed(
this.#anchor,
() => error,
() => reset
);
});
} catch (error2) {
invoke_error_boundary(
error2,
/** @type {Effect} */
this.#effect.parent
);
return null;
} finally {
this.#is_creating_fallback = false;
}
});
});
}
}
}
const all_registered_events = /* @__PURE__ */ new Set();
const root_event_handles = /* @__PURE__ */ new Set();
let last_propagated_event = null;
function handle_event_propagation(event) {
var handler_element = this;
var owner_document = (
/** @type {Node} */
handler_element.ownerDocument
);
var event_name = event.type;
var path = event.composedPath?.() || [];
var current_target = (
/** @type {null | Element} */
path[0] || event.target
);
last_propagated_event = event;
var path_idx = 0;
var handled_at = last_propagated_event === event && event.__root;
if (handled_at) {
var at_idx = path.indexOf(handled_at);
if (at_idx !== -1 && (handler_element === document || handler_element === /** @type {any} */
window)) {
event.__root = handler_element;
return;
}
var handler_idx = path.indexOf(handler_element);
if (handler_idx === -1) {
return;
}
if (at_idx <= handler_idx) {
path_idx = at_idx;
}
}
current_target = /** @type {Element} */
path[path_idx] || event.target;
if (current_target === handler_element) return;
define_property(event, "currentTarget", {
configurable: true,
get() {
return current_target || owner_document;
}
});
var previous_reaction = active_reaction;
var previous_effect = active_effect;
set_active_reaction(null);
set_active_effect(null);
try {
var throw_error;
var other_errors = [];
while (current_target !== null) {
var parent_element = current_target.assignedSlot || current_target.parentNode || /** @type {any} */
current_target.host || null;
try {
var delegated = current_target["__" + event_name];
if (delegated != null && (!/** @type {any} */
current_target.disabled || // DOM could've been updated already by the time this is reached, so we check this as well
// -> the target could not have been disabled because it emits the event in the first place
event.target === current_target)) {
delegated.call(current_target, event);
}
} catch (error) {
if (throw_error) {
other_errors.push(error);
} else {
throw_error = error;
}
}
if (event.cancelBubble || parent_element === handler_element || parent_element === null) {
break;
}
current_target = parent_element;
}
if (throw_error) {
for (let error of other_errors) {
queueMicrotask(() => {
throw error;
});
}
throw throw_error;
}
} finally {
event.__root = handler_element;
delete event.currentTarget;
set_active_reaction(previous_reaction);
set_active_effect(previous_effect);
}
}
function assign_nodes(start, end) {
var effect = (
/** @type {Effect} */
active_effect
);
if (effect.nodes === null) {
effect.nodes = { start, end, a: null, t: null };
}
}
function mount(component, options2) {
return _mount(component, options2);
}
function hydrate(component, options2) {
init_operations();
options2.intro = options2.intro ?? false;
const target = options2.target;
const was_hydrating = hydrating;
const previous_hydrate_node = hydrate_node;
try {
var anchor = get_first_child(target);
while (anchor && (anchor.nodeType !== COMMENT_NODE || /** @type {Comment} */
anchor.data !== HYDRATION_START)) {
anchor = get_next_sibling(anchor);
}
if (!anchor) {
throw HYDRATION_ERROR;
}
set_hydrating(true);
set_hydrate_node(
/** @type {Comment} */
anchor
);
const instance = _mount(component, { ...options2, anchor });
set_hydrating(false);
return (
/** @type {Exports} */
instance
);
} catch (error) {
if (error instanceof Error && error.message.split("\n").some((line) => line.startsWith("https://svelte.dev/e/"))) {
throw error;
}
if (error !== HYDRATION_ERROR) {
console.warn("Failed to hydrate: ", error);
}
if (options2.recover === false) {
hydration_failed();
}
init_operations();
clear_text_content(target);
set_hydrating(false);
return mount(component, options2);
} finally {
set_hydrating(was_hydrating);
set_hydrate_node(previous_hydrate_node);
}
}
const document_listeners = /* @__PURE__ */ new Map();
function _mount(Component, { target, anchor, props = {}, events, context, intro = true }) {
init_operations();
var registered_events = /* @__PURE__ */ new Set();
var event_handle = (events2) => {
for (var i = 0; i < events2.length; i++) {
var event_name = events2[i];
if (registered_events.has(event_name)) continue;
registered_events.add(event_name);
var passive = is_passive_event(event_name);
target.addEventListener(event_name, handle_event_propagation, { passive });
var n = document_listeners.get(event_name);
if (n === void 0) {
document.addEventListener(event_name, handle_event_propagation, { passive });
document_listeners.set(event_name, 1);
} else {
document_listeners.set(event_name, n + 1);
}
}
};
event_handle(array_from(all_registered_events));
root_event_handles.add(event_handle);
var component = void 0;
var unmount2 = component_root(() => {
var anchor_node = anchor ?? target.appendChild(create_text());
boundary(
/** @type {TemplateNode} */
anchor_node,
{
pending: () => {
}
},
(anchor_node2) => {
if (context) {
push({});
var ctx = (
/** @type {ComponentContext} */
component_context
);
ctx.c = context;
}
if (events) {
props.$$events = events;
}
if (hydrating) {
assign_nodes(
/** @type {TemplateNode} */
anchor_node2,
null
);
}
component = Component(anchor_node2, props) || {};
if (hydrating) {
active_effect.nodes.end = hydrate_node;
if (hydrate_node === null || hydrate_node.nodeType !== COMMENT_NODE || /** @type {Comment} */
hydrate_node.data !== HYDRATION_END) {
hydration_mismatch();
throw HYDRATION_ERROR;
}
}
if (context) {
pop();
}
}
);
return () => {
for (var event_name of registered_events) {
target.removeEventListener(event_name, handle_event_propagation);
var n = (
/** @type {number} */
document_listeners.get(event_name)
);
if (--n === 0) {
document.removeEventListener(event_name, handle_event_propagation);
document_listeners.delete(event_name);
} else {
document_listeners.set(event_name, n);
}
}
root_event_handles.delete(event_handle);
if (anchor_node !== anchor) {
anchor_node.parentNode?.removeChild(anchor_node);
}
};
});
mounted_components.set(component, unmount2);
return component;
}
let mounted_components = /* @__PURE__ */ new WeakMap();
function unmount(component, options2) {
const fn = mounted_components.get(component);
if (fn) {
mounted_components.delete(component);
return fn(options2);
}
return Promise.resolve();
}
function asClassComponent$1(component) {
return class extends Svelte4Component {
/** @param {any} options */
constructor(options2) {
super({
component,
...options2
});
}
};
}
class Svelte4Component {
/** @type {any} */
#events;
/** @type {Record<string, any>} */
#instance;
/**
* @param {ComponentConstructorOptions & {
* component: any;
* }} options
*/
constructor(options2) {
var sources = /* @__PURE__ */ new Map();
var add_source = (key, value) => {
var s = mutable_source(value, false, false);
sources.set(key, s);
return s;
};
const props = new Proxy(
{ ...options2.props || {}, $$events: {} },
{
get(target, prop) {
return get(sources.get(prop) ?? add_source(prop, Reflect.get(target, prop)));
},
has(target, prop) {
if (prop === LEGACY_PROPS) return true;
get(sources.get(prop) ?? add_source(prop, Reflect.get(target, prop)));
return Reflect.has(target, prop);
},
set(target, prop, value) {
set(sources.get(prop) ?? add_source(prop, value), value);
return Reflect.set(target, prop, value);
}
}
);
this.#instance = (options2.hydrate ? hydrate : mount)(options2.component, {
target: options2.target,
anchor: options2.anchor,
props,
context: options2.context,
intro: options2.intro ?? false,
recover: options2.recover
});
if (!options2?.props?.$$host || options2.sync === false) {
flushSync();
}
this.#events = props.$$events;
for (const key of Object.keys(this.#instance)) {
if (key === "$set" || key === "$destroy" || key === "$on") continue;
define_property(this, key, {
get() {
return this.#instance[key];
},
/** @param {any} value */
set(value) {
this.#instance[key] = value;
},
enumerable: true
});
}
this.#instance.$set = /** @param {Record<string, any>} next */
(next2) => {
Object.assign(props, next2);
};
this.#instance.$destroy = () => {
unmount(this.#instance);
};
}
/** @param {Record<string, any>} props */
$set(props) {
this.#instance.$set(props);
}
/**
* @param {string} event
* @param {(...args: any[]) => any} callback
* @returns {any}
*/
$on(event, callback) {
this.#events[event] = this.#events[event] || [];
const cb = (...args) => callback.call(this, ...args);
this.#events[event].push(cb);
return () => {
this.#events[event] = this.#events[event].filter(
/** @param {any} fn */
(fn) => fn !== cb
);
};
}
$destroy() {
this.#instance.$destroy();
}
}
let read_implementation = null;
function set_read_implementation(fn) {
read_implementation = fn;
}
function set_manifest(_) {
}
function asClassComponent(component) {
const component_constructor = asClassComponent$1(component);
const _render = (props, { context, csp } = {}) => {
const result = render(component, { props, context, csp });
const munged = Object.defineProperties(
/** @type {LegacyRenderResult & PromiseLike<LegacyRenderResult>} */
{},
{
css: {
value: { code: "", map: null }
},
head: {
get: () => result.head
},
html: {
get: () => result.body
},
then: {
/**
* this is not type-safe, but honestly it's the best I can do right now, and it's a straightforward function.
*
* @template TResult1
* @template [TResult2=never]
* @param { (value: LegacyRenderResult) => TResult1 } onfulfilled
* @param { (reason: unknown) => TResult2 } onrejected
*/
value: (onfulfilled, onrejected) => {
{
const user_result = onfulfilled({
css: munged.css,
head: munged.head,
html: munged.html
});
return Promise.resolve(user_result);
}
}
}
}
);
return munged;
};
component_constructor.render = _render;
return component_constructor;
}
function Root($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
let {
stores,
page,
constructors,
components = [],
form,
data_0 = null,
data_1 = null
} = $$props;
{
setContext("__svelte__", stores);
}
{
stores.page.set(page);
}
const Pyramid_1 = constructors[1];
if (constructors[1]) {
$$renderer2.push("<!--[-->");
const Pyramid_0 = constructors[0];
$$renderer2.push(`<!---->`);
Pyramid_0($$renderer2, {
data: data_0,
form,
params: page.params,
children: ($$renderer3) => {
$$renderer3.push(`<!---->`);
Pyramid_1($$renderer3, { data: data_1, form, params: page.params });
$$renderer3.push(`<!---->`);
},
$$slots: { default: true }
});
$$renderer2.push(`<!---->`);
} else {
$$renderer2.push("<!--[!-->");
const Pyramid_0 = constructors[0];
$$renderer2.push(`<!---->`);
Pyramid_0($$renderer2, { data: data_0, form, params: page.params });
$$renderer2.push(`<!---->`);
}
$$renderer2.push(`<!--]--> `);
{
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]-->`);
});
}
const root = asClassComponent(Root);
const options = {
app_template_contains_nonce: false,
async: false,
csp: { "mode": "auto", "directives": { "upgrade-insecure-requests": false, "block-all-mixed-content": false }, "reportOnly": { "upgrade-insecure-requests": false, "block-all-mixed-content": false } },
csrf_check_origin: true,
csrf_trusted_origins: [],
embedded: false,
env_public_prefix: "PUBLIC_",
env_private_prefix: "",
hash_routing: false,
hooks: null,
// added lazily, via `get_hooks`
preload_strategy: "modulepreload",
root,
service_worker: false,
service_worker_options: void 0,
templates: {
app: ({ head, body, assets, nonce, env }) => '<!DOCTYPE html>\n<html lang="en">\n <head>\n <meta charset="utf-8" />\n <link rel="icon" href="' + assets + '/favicon.png" />\n <meta name="viewport" content="width=device-width, initial-scale=1" />\n ' + head + '\n </head>\n <body data-sveltekit-preload-data="hover">\n <div style="display: contents">' + body + "</div>\n </body>\n</html>\n",
error: ({ status, message }) => '<!doctype html>\n<html lang="en">\n <head>\n <meta charset="utf-8" />\n <title>' + message + `</title>
<style>
body {
--bg: white;
--fg: #222;
--divider: #ccc;
background: var(--bg);
color: var(--fg);
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Open Sans',
'Helvetica Neue',
sans-serif;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
}
.error {
display: flex;
align-items: center;
max-width: 32rem;
margin: 0 1rem;
}
.status {
font-weight: 200;
font-size: 3rem;
line-height: 1;
position: relative;
top: -0.05rem;
}
.message {
border-left: 1px solid var(--divider);
padding: 0 0 0 1rem;
margin: 0 0 0 1rem;
min-height: 2.5rem;
display: flex;
align-items: center;
}
.message h1 {
font-weight: 400;
font-size: 1em;
margin: 0;
}
@media (prefers-color-scheme: dark) {
body {
--bg: #222;
--fg: #ddd;
--divider: #666;
}
}
</style>
</head>
<body>
<div class="error">
<span class="status">` + status + '</span>\n <div class="message">\n <h1>' + message + "</h1>\n </div>\n </div>\n </body>\n</html>\n"
},
version_hash: "1ootf77"
};
async function get_hooks() {
let handle;
let handleFetch;
let handleError;
let handleValidationError;
let init;
let reroute;
let transport;
return {
handle,
handleFetch,
handleError,
handleValidationError,
init,
reroute,
transport
};
}
export {
set_public_env as a,
set_read_implementation as b,
set_manifest as c,
get_hooks as g,
options as o,
public_env as p,
read_implementation as r,
set_private_env as s
};

View File

@@ -1,522 +0,0 @@
import * as devalue from "devalue";
import { t as text_decoder, b as base64_encode, c as base64_decode } from "./utils.js";
function set_nested_value(object, path_string, value) {
if (path_string.startsWith("n:")) {
path_string = path_string.slice(2);
value = value === "" ? void 0 : parseFloat(value);
} else if (path_string.startsWith("b:")) {
path_string = path_string.slice(2);
value = value === "on";
}
deep_set(object, split_path(path_string), value);
}
function convert_formdata(data) {
const result = {};
for (let key of data.keys()) {
const is_array = key.endsWith("[]");
let values = data.getAll(key);
if (is_array) key = key.slice(0, -2);
if (values.length > 1 && !is_array) {
throw new Error(`Form cannot contain duplicated keys — "${key}" has ${values.length} values`);
}
values = values.filter(
(entry) => typeof entry === "string" || entry.name !== "" || entry.size > 0
);
if (key.startsWith("n:")) {
key = key.slice(2);
values = values.map((v) => v === "" ? void 0 : parseFloat(
/** @type {string} */
v
));
} else if (key.startsWith("b:")) {
key = key.slice(2);
values = values.map((v) => v === "on");
}
set_nested_value(result, key, is_array ? values : values[0]);
}
return result;
}
const BINARY_FORM_CONTENT_TYPE = "application/x-sveltekit-formdata";
const BINARY_FORM_VERSION = 0;
async function deserialize_binary_form(request) {
if (request.headers.get("content-type") !== BINARY_FORM_CONTENT_TYPE) {
const form_data = await request.formData();
return { data: convert_formdata(form_data), meta: {}, form_data };
}
if (!request.body) {
throw new Error("Could not deserialize binary form: no body");
}
const reader = request.body.getReader();
const chunks = [];
async function get_chunk(index) {
if (index in chunks) return chunks[index];
let i = chunks.length;
while (i <= index) {
chunks[i] = reader.read().then((chunk) => chunk.value);
i++;
}
return chunks[index];
}
async function get_buffer(offset, length) {
let start_chunk;
let chunk_start = 0;
let chunk_index;
for (chunk_index = 0; ; chunk_index++) {
const chunk = await get_chunk(chunk_index);
if (!chunk) return null;
const chunk_end = chunk_start + chunk.byteLength;
if (offset >= chunk_start && offset < chunk_end) {
start_chunk = chunk;
break;
}
chunk_start = chunk_end;
}
if (offset + length <= chunk_start + start_chunk.byteLength) {
return start_chunk.subarray(offset - chunk_start, offset + length - chunk_start);
}
const buffer = new Uint8Array(length);
buffer.set(start_chunk.subarray(offset - chunk_start));
let cursor = start_chunk.byteLength - offset + chunk_start;
while (cursor < length) {
chunk_index++;
let chunk = await get_chunk(chunk_index);
if (!chunk) return null;
if (chunk.byteLength > length - cursor) {
chunk = chunk.subarray(0, length - cursor);
}
buffer.set(chunk, cursor);
cursor += chunk.byteLength;
}
return buffer;
}
const header = await get_buffer(0, 1 + 4 + 2);
if (!header) throw new Error("Could not deserialize binary form: too short");
if (header[0] !== BINARY_FORM_VERSION) {
throw new Error(
`Could not deserialize binary form: got version ${header[0]}, expected version ${BINARY_FORM_VERSION}`
);
}
const header_view = new DataView(header.buffer, header.byteOffset, header.byteLength);
const data_length = header_view.getUint32(1, true);
const file_offsets_length = header_view.getUint16(5, true);
const data_buffer = await get_buffer(1 + 4 + 2, data_length);
if (!data_buffer) throw new Error("Could not deserialize binary form: data too short");
let file_offsets;
let files_start_offset;
if (file_offsets_length > 0) {
const file_offsets_buffer = await get_buffer(1 + 4 + 2 + data_length, file_offsets_length);
if (!file_offsets_buffer)
throw new Error("Could not deserialize binary form: file offset table too short");
file_offsets = /** @type {Array<number>} */
JSON.parse(text_decoder.decode(file_offsets_buffer));
files_start_offset = 1 + 4 + 2 + data_length + file_offsets_length;
}
const [data, meta] = devalue.parse(text_decoder.decode(data_buffer), {
File: ([name, type, size, last_modified, index]) => {
return new Proxy(
new LazyFile(
name,
type,
size,
last_modified,
get_chunk,
files_start_offset + file_offsets[index]
),
{
getPrototypeOf() {
return File.prototype;
}
}
);
}
});
void (async () => {
let has_more = true;
while (has_more) {
const chunk = await get_chunk(chunks.length);
has_more = !!chunk;
}
})();
return { data, meta, form_data: null };
}
class LazyFile {
/** @type {(index: number) => Promise<Uint8Array<ArrayBuffer> | undefined>} */
#get_chunk;
/** @type {number} */
#offset;
/**
* @param {string} name
* @param {string} type
* @param {number} size
* @param {number} last_modified
* @param {(index: number) => Promise<Uint8Array<ArrayBuffer> | undefined>} get_chunk
* @param {number} offset
*/
constructor(name, type, size, last_modified, get_chunk, offset) {
this.name = name;
this.type = type;
this.size = size;
this.lastModified = last_modified;
this.webkitRelativePath = "";
this.#get_chunk = get_chunk;
this.#offset = offset;
this.arrayBuffer = this.arrayBuffer.bind(this);
this.bytes = this.bytes.bind(this);
this.slice = this.slice.bind(this);
this.stream = this.stream.bind(this);
this.text = this.text.bind(this);
}
/** @type {ArrayBuffer | undefined} */
#buffer;
async arrayBuffer() {
this.#buffer ??= await new Response(this.stream()).arrayBuffer();
return this.#buffer;
}
async bytes() {
return new Uint8Array(await this.arrayBuffer());
}
/**
* @param {number=} start
* @param {number=} end
* @param {string=} contentType
*/
slice(start = 0, end = this.size, contentType = this.type) {
if (start < 0) {
start = Math.max(this.size + start, 0);
} else {
start = Math.min(start, this.size);
}
if (end < 0) {
end = Math.max(this.size + end, 0);
} else {
end = Math.min(end, this.size);
}
const size = Math.max(end - start, 0);
const file = new LazyFile(
this.name,
contentType,
size,
this.lastModified,
this.#get_chunk,
this.#offset + start
);
return file;
}
stream() {
let cursor = 0;
let chunk_index = 0;
return new ReadableStream({
start: async (controller) => {
let chunk_start = 0;
let start_chunk = null;
for (chunk_index = 0; ; chunk_index++) {
const chunk = await this.#get_chunk(chunk_index);
if (!chunk) return null;
const chunk_end = chunk_start + chunk.byteLength;
if (this.#offset >= chunk_start && this.#offset < chunk_end) {
start_chunk = chunk;
break;
}
chunk_start = chunk_end;
}
if (this.#offset + this.size <= chunk_start + start_chunk.byteLength) {
controller.enqueue(
start_chunk.subarray(this.#offset - chunk_start, this.#offset + this.size - chunk_start)
);
controller.close();
} else {
controller.enqueue(start_chunk.subarray(this.#offset - chunk_start));
cursor = start_chunk.byteLength - this.#offset + chunk_start;
}
},
pull: async (controller) => {
chunk_index++;
let chunk = await this.#get_chunk(chunk_index);
if (!chunk) {
controller.error("Could not deserialize binary form: incomplete file data");
controller.close();
return;
}
if (chunk.byteLength > this.size - cursor) {
chunk = chunk.subarray(0, this.size - cursor);
}
controller.enqueue(chunk);
cursor += chunk.byteLength;
if (cursor >= this.size) {
controller.close();
}
}
});
}
async text() {
return text_decoder.decode(await this.arrayBuffer());
}
}
const path_regex = /^[a-zA-Z_$]\w*(\.[a-zA-Z_$]\w*|\[\d+\])*$/;
function split_path(path) {
if (!path_regex.test(path)) {
throw new Error(`Invalid path ${path}`);
}
return path.split(/\.|\[|\]/).filter(Boolean);
}
function check_prototype_pollution(key) {
if (key === "__proto__" || key === "constructor" || key === "prototype") {
throw new Error(
`Invalid key "${key}"`
);
}
}
function deep_set(object, keys, value) {
let current = object;
for (let i = 0; i < keys.length - 1; i += 1) {
const key = keys[i];
check_prototype_pollution(key);
const is_array = /^\d+$/.test(keys[i + 1]);
const exists = key in current;
const inner = current[key];
if (exists && is_array !== Array.isArray(inner)) {
throw new Error(`Invalid array key ${keys[i + 1]}`);
}
if (!exists) {
current[key] = is_array ? [] : {};
}
current = current[key];
}
const final_key = keys[keys.length - 1];
check_prototype_pollution(final_key);
current[final_key] = value;
}
function normalize_issue(issue, server = false) {
const normalized = { name: "", path: [], message: issue.message, server };
if (issue.path !== void 0) {
let name = "";
for (const segment of issue.path) {
const key = (
/** @type {string | number} */
typeof segment === "object" ? segment.key : segment
);
normalized.path.push(key);
if (typeof key === "number") {
name += `[${key}]`;
} else if (typeof key === "string") {
name += name === "" ? key : "." + key;
}
}
normalized.name = name;
}
return normalized;
}
function flatten_issues(issues) {
const result = {};
for (const issue of issues) {
(result.$ ??= []).push(issue);
let name = "";
if (issue.path !== void 0) {
for (const key of issue.path) {
if (typeof key === "number") {
name += `[${key}]`;
} else if (typeof key === "string") {
name += name === "" ? key : "." + key;
}
(result[name] ??= []).push(issue);
}
}
}
return result;
}
function deep_get(object, path) {
let current = object;
for (const key of path) {
if (current == null || typeof current !== "object") {
return current;
}
current = current[key];
}
return current;
}
function create_field_proxy(target, get_input, set_input, get_issues, path = []) {
const get_value = () => {
return deep_get(get_input(), path);
};
return new Proxy(target, {
get(target2, prop) {
if (typeof prop === "symbol") return target2[prop];
if (/^\d+$/.test(prop)) {
return create_field_proxy({}, get_input, set_input, get_issues, [
...path,
parseInt(prop, 10)
]);
}
const key = build_path_string(path);
if (prop === "set") {
const set_func = function(newValue) {
set_input(path, newValue);
return newValue;
};
return create_field_proxy(set_func, get_input, set_input, get_issues, [...path, prop]);
}
if (prop === "value") {
return create_field_proxy(get_value, get_input, set_input, get_issues, [...path, prop]);
}
if (prop === "issues" || prop === "allIssues") {
const issues_func = () => {
const all_issues = get_issues()[key === "" ? "$" : key];
if (prop === "allIssues") {
return all_issues?.map((issue) => ({
path: issue.path,
message: issue.message
}));
}
return all_issues?.filter((issue) => issue.name === key)?.map((issue) => ({
path: issue.path,
message: issue.message
}));
};
return create_field_proxy(issues_func, get_input, set_input, get_issues, [...path, prop]);
}
if (prop === "as") {
const as_func = (type, input_value) => {
const is_array = type === "file multiple" || type === "select multiple" || type === "checkbox" && typeof input_value === "string";
const prefix = type === "number" || type === "range" ? "n:" : type === "checkbox" && !is_array ? "b:" : "";
const base_props = {
name: prefix + key + (is_array ? "[]" : ""),
get "aria-invalid"() {
const issues = get_issues();
return key in issues ? "true" : void 0;
}
};
if (type !== "text" && type !== "select" && type !== "select multiple") {
base_props.type = type === "file multiple" ? "file" : type;
}
if (type === "submit" || type === "hidden") {
return Object.defineProperties(base_props, {
value: { value: input_value, enumerable: true }
});
}
if (type === "select" || type === "select multiple") {
return Object.defineProperties(base_props, {
multiple: { value: is_array, enumerable: true },
value: {
enumerable: true,
get() {
return get_value();
}
}
});
}
if (type === "checkbox" || type === "radio") {
return Object.defineProperties(base_props, {
value: { value: input_value ?? "on", enumerable: true },
checked: {
enumerable: true,
get() {
const value = get_value();
if (type === "radio") {
return value === input_value;
}
if (is_array) {
return (value ?? []).includes(input_value);
}
return value;
}
}
});
}
if (type === "file" || type === "file multiple") {
return Object.defineProperties(base_props, {
multiple: { value: is_array, enumerable: true },
files: {
enumerable: true,
get() {
const value = get_value();
if (value instanceof File) {
if (typeof DataTransfer !== "undefined") {
const fileList = new DataTransfer();
fileList.items.add(value);
return fileList.files;
}
return { 0: value, length: 1 };
}
if (Array.isArray(value) && value.every((f) => f instanceof File)) {
if (typeof DataTransfer !== "undefined") {
const fileList = new DataTransfer();
value.forEach((file) => fileList.items.add(file));
return fileList.files;
}
const fileListLike = { length: value.length };
value.forEach((file, index) => {
fileListLike[index] = file;
});
return fileListLike;
}
return null;
}
}
});
}
return Object.defineProperties(base_props, {
value: {
enumerable: true,
get() {
const value = get_value();
return value != null ? String(value) : "";
}
}
});
};
return create_field_proxy(as_func, get_input, set_input, get_issues, [...path, "as"]);
}
return create_field_proxy({}, get_input, set_input, get_issues, [...path, prop]);
}
});
}
function build_path_string(path) {
let result = "";
for (const segment of path) {
if (typeof segment === "number") {
result += `[${segment}]`;
} else {
result += result === "" ? segment : "." + segment;
}
}
return result;
}
const INVALIDATED_PARAM = "x-sveltekit-invalidated";
const TRAILING_SLASH_PARAM = "x-sveltekit-trailing-slash";
function stringify(data, transport) {
const encoders = Object.fromEntries(Object.entries(transport).map(([k, v]) => [k, v.encode]));
return devalue.stringify(data, encoders);
}
function stringify_remote_arg(value, transport) {
if (value === void 0) return "";
const json_string = stringify(value, transport);
const bytes = new TextEncoder().encode(json_string);
return base64_encode(bytes).replaceAll("=", "").replaceAll("+", "-").replaceAll("/", "_");
}
function parse_remote_arg(string, transport) {
if (!string) return void 0;
const json_string = text_decoder.decode(
// no need to add back `=` characters, atob can handle it
base64_decode(string.replaceAll("-", "+").replaceAll("_", "/"))
);
const decoders = Object.fromEntries(Object.entries(transport).map(([k, v]) => [k, v.decode]));
return devalue.parse(json_string, decoders);
}
function create_remote_key(id, payload) {
return id + "/" + payload;
}
export {
BINARY_FORM_CONTENT_TYPE as B,
INVALIDATED_PARAM as I,
TRAILING_SLASH_PARAM as T,
stringify_remote_arg as a,
create_field_proxy as b,
create_remote_key as c,
deserialize_binary_form as d,
set_nested_value as e,
flatten_issues as f,
deep_set as g,
normalize_issue as n,
parse_remote_arg as p,
stringify as s
};

View File

@@ -1,44 +0,0 @@
import { a0 as getContext } from "./index2.js";
import "clsx";
import "@sveltejs/kit/internal";
import "./exports.js";
import "./utils.js";
import "@sveltejs/kit/internal/server";
import { n as noop } from "./equality.js";
const is_legacy = noop.toString().includes("$$") || /function \w+\(\) \{\}/.test(noop.toString());
if (is_legacy) {
({
data: {},
form: null,
error: null,
params: {},
route: { id: null },
state: {},
status: -1,
url: new URL("https://example.com")
});
}
const getStores = () => {
const stores = getContext("__svelte__");
return {
/** @type {typeof page} */
page: {
subscribe: stores.page.subscribe
},
/** @type {typeof navigating} */
navigating: {
subscribe: stores.navigating.subscribe
},
/** @type {typeof updated} */
updated: stores.updated
};
};
const page = {
subscribe(fn) {
const store = getStores().page;
return store.subscribe(fn);
}
};
export {
page as p
};

View File

@@ -1,16 +0,0 @@
import { w as writable } from "./index.js";
const toasts = writable([]);
function addToast(message, type = "info", duration = 3e3) {
const id = Math.random().toString(36).substr(2, 9);
console.log(`[toasts.addToast][Action] Adding toast context={{'id': '${id}', 'type': '${type}', 'message': '${message}'}}`);
toasts.update((all) => [...all, { id, message, type }]);
setTimeout(() => removeToast(id), duration);
}
function removeToast(id) {
console.log(`[toasts.removeToast][Action] Removing toast context={{'id': '${id}'}}`);
toasts.update((all) => all.filter((t) => t.id !== id));
}
export {
addToast as a,
toasts as t
};

View File

@@ -1,43 +0,0 @@
const text_encoder = new TextEncoder();
const text_decoder = new TextDecoder();
function get_relative_path(from, to) {
const from_parts = from.split(/[/\\]/);
const to_parts = to.split(/[/\\]/);
from_parts.pop();
while (from_parts[0] === to_parts[0]) {
from_parts.shift();
to_parts.shift();
}
let i = from_parts.length;
while (i--) from_parts[i] = "..";
return from_parts.concat(to_parts).join("/");
}
function base64_encode(bytes) {
if (globalThis.Buffer) {
return globalThis.Buffer.from(bytes).toString("base64");
}
let binary = "";
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
function base64_decode(encoded) {
if (globalThis.Buffer) {
const buffer = globalThis.Buffer.from(encoded, "base64");
return new Uint8Array(buffer);
}
const binary = atob(encoded);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes;
}
export {
text_encoder as a,
base64_encode as b,
base64_decode as c,
get_relative_path as g,
text_decoder as t
};

View File

@@ -1,12 +0,0 @@
import { _ as escape_html, X as store_get, Y as unsubscribe_stores } from "../../chunks/index2.js";
import { p as page } from "../../chunks/stores.js";
function _error($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
$$renderer2.push(`<div class="container mx-auto p-4 text-center mt-20"><h1 class="text-6xl font-bold text-gray-800 mb-4">${escape_html(store_get($$store_subs ??= {}, "$page", page).status)}</h1> <p class="text-2xl text-gray-600 mb-8">${escape_html(store_get($$store_subs ??= {}, "$page", page).error?.message || "Page not found")}</p> <a href="/" class="bg-blue-500 text-white px-6 py-3 rounded-lg hover:bg-blue-600 transition-colors">Back to Dashboard</a></div>`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
export {
_error as default
};

View File

@@ -1,38 +0,0 @@
import { V as attr_class, W as stringify, X as store_get, Y as unsubscribe_stores, Z as ensure_array_like, _ as escape_html, $ as slot } from "../../chunks/index2.js";
import { p as page } from "../../chunks/stores.js";
import "clsx";
import { t as toasts } from "../../chunks/toasts.js";
function Navbar($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
$$renderer2.push(`<header class="bg-white shadow-md p-4 flex justify-between items-center"><a href="/" class="text-3xl font-bold text-gray-800 focus:outline-none">Superset Tools</a> <nav class="space-x-4"><a href="/"${attr_class(`text-gray-600 hover:text-blue-600 font-medium ${stringify(store_get($$store_subs ??= {}, "$page", page).url.pathname === "/" ? "text-blue-600 border-b-2 border-blue-600" : "")}`)}>Dashboard</a> <a href="/settings"${attr_class(`text-gray-600 hover:text-blue-600 font-medium ${stringify(store_get($$store_subs ??= {}, "$page", page).url.pathname === "/settings" ? "text-blue-600 border-b-2 border-blue-600" : "")}`)}>Settings</a></nav></header>`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
function Footer($$renderer) {
$$renderer.push(`<footer class="bg-white border-t p-4 mt-8 text-center text-gray-500 text-sm">© 2025 Superset Tools. All rights reserved.</footer>`);
}
function Toast($$renderer) {
var $$store_subs;
$$renderer.push(`<div class="fixed bottom-0 right-0 p-4 space-y-2"><!--[-->`);
const each_array = ensure_array_like(store_get($$store_subs ??= {}, "$toasts", toasts));
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
let toast = each_array[$$index];
$$renderer.push(`<div${attr_class(`p-4 rounded-md shadow-lg text-white ${stringify(toast.type === "info" && "bg-blue-500")} ${stringify(toast.type === "success" && "bg-green-500")} ${stringify(toast.type === "error" && "bg-red-500")} `)}>${escape_html(toast.message)}</div>`);
}
$$renderer.push(`<!--]--></div>`);
if ($$store_subs) unsubscribe_stores($$store_subs);
}
function _layout($$renderer, $$props) {
Toast($$renderer);
$$renderer.push(`<!----> <main class="bg-gray-50 min-h-screen flex flex-col">`);
Navbar($$renderer);
$$renderer.push(`<!----> <div class="p-4 flex-grow"><!--[-->`);
slot($$renderer, $$props, "default", {});
$$renderer.push(`<!--]--></div> `);
Footer($$renderer);
$$renderer.push(`<!----></main>`);
}
export {
_layout as default
};

View File

@@ -1,6 +0,0 @@
const ssr = false;
const prerender = false;
export {
prerender,
ssr
};

View File

@@ -1,132 +0,0 @@
import { a1 as ssr_context, X as store_get, _ as escape_html, Z as ensure_array_like, V as attr_class, Y as unsubscribe_stores, a2 as attr, a3 as bind_props } from "../../chunks/index2.js";
import { w as writable } from "../../chunks/index.js";
import "clsx";
function onDestroy(fn) {
/** @type {SSRContext} */
ssr_context.r.on_destroy(fn);
}
const plugins = writable([]);
const selectedPlugin = writable(null);
const selectedTask = writable(null);
const taskLogs = writable([]);
function TaskRunner($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
onDestroy(() => {
});
$$renderer2.push(`<div class="p-4 border rounded-lg bg-white shadow-md">`);
if (store_get($$store_subs ??= {}, "$selectedTask", selectedTask)) {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<h2 class="text-xl font-semibold mb-2">Task: ${escape_html(store_get($$store_subs ??= {}, "$selectedTask", selectedTask).plugin_id)}</h2> <div class="bg-gray-900 text-white font-mono text-sm p-4 rounded-md h-96 overflow-y-auto"><!--[-->`);
const each_array = ensure_array_like(store_get($$store_subs ??= {}, "$taskLogs", taskLogs));
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
let log = each_array[$$index];
$$renderer2.push(`<div><span class="text-gray-400">${escape_html(new Date(log.timestamp).toLocaleTimeString())}</span> <span${attr_class(log.level === "ERROR" ? "text-red-500" : "text-green-400")}>[${escape_html(log.level)}]</span> <span>${escape_html(log.message)}</span></div>`);
}
$$renderer2.push(`<!--]--></div>`);
} else {
$$renderer2.push("<!--[!-->");
$$renderer2.push(`<p>No task selected.</p>`);
}
$$renderer2.push(`<!--]--></div>`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
function DynamicForm($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
let schema = $$props["schema"];
let formData = {};
function initializeForm() {
if (schema && schema.properties) {
for (const key in schema.properties) {
formData[key] = schema.properties[key].default || "";
}
}
}
initializeForm();
$$renderer2.push(`<form class="space-y-4">`);
if (schema && schema.properties) {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<!--[-->`);
const each_array = ensure_array_like(Object.entries(schema.properties));
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
let [key, prop] = each_array[$$index];
$$renderer2.push(`<div class="flex flex-col"><label${attr("for", key)} class="mb-1 font-semibold text-gray-700">${escape_html(prop.title || key)}</label> `);
if (prop.type === "string") {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<input type="text"${attr("id", key)}${attr("value", formData[key])}${attr("placeholder", prop.description || "")} class="p-2 border rounded-md"/>`);
} else {
$$renderer2.push("<!--[!-->");
if (prop.type === "number" || prop.type === "integer") {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<input type="number"${attr("id", key)}${attr("value", formData[key])}${attr("placeholder", prop.description || "")} class="p-2 border rounded-md"/>`);
} else {
$$renderer2.push("<!--[!-->");
if (prop.type === "boolean") {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<input type="checkbox"${attr("id", key)}${attr("checked", formData[key], true)} class="h-5 w-5"/>`);
} else {
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]-->`);
}
$$renderer2.push(`<!--]-->`);
}
$$renderer2.push(`<!--]--></div>`);
}
$$renderer2.push(`<!--]--> <button type="submit" class="w-full bg-green-500 text-white p-2 rounded-md hover:bg-green-600">Run Task</button>`);
} else {
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--></form>`);
bind_props($$props, { schema });
});
}
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
let data = $$props["data"];
if (data.plugins) {
plugins.set(data.plugins);
}
$$renderer2.push(`<div class="container mx-auto p-4">`);
if (store_get($$store_subs ??= {}, "$selectedTask", selectedTask)) {
$$renderer2.push("<!--[-->");
TaskRunner($$renderer2);
$$renderer2.push(`<!----> <button class="mt-4 bg-blue-500 text-white p-2 rounded">Back to Task List</button>`);
} else {
$$renderer2.push("<!--[!-->");
if (store_get($$store_subs ??= {}, "$selectedPlugin", selectedPlugin)) {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<h2 class="text-2xl font-bold mb-4">${escape_html(store_get($$store_subs ??= {}, "$selectedPlugin", selectedPlugin).name)}</h2> `);
DynamicForm($$renderer2, {
schema: store_get($$store_subs ??= {}, "$selectedPlugin", selectedPlugin).schema
});
$$renderer2.push(`<!----> <button class="mt-4 bg-gray-500 text-white p-2 rounded">Back to Dashboard</button>`);
} else {
$$renderer2.push("<!--[!-->");
$$renderer2.push(`<h1 class="text-2xl font-bold mb-4">Available Tools</h1> `);
if (data.error) {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">${escape_html(data.error)}</div>`);
} else {
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"><!--[-->`);
const each_array = ensure_array_like(data.plugins);
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
let plugin = each_array[$$index];
$$renderer2.push(`<div class="border rounded-lg p-4 cursor-pointer hover:bg-gray-100" role="button" tabindex="0"><h2 class="text-xl font-semibold">${escape_html(plugin.name)}</h2> <p class="text-gray-600">${escape_html(plugin.description)}</p> <span class="text-sm text-gray-400">v${escape_html(plugin.version)}</span></div>`);
}
$$renderer2.push(`<!--]--></div>`);
}
$$renderer2.push(`<!--]-->`);
}
$$renderer2.push(`<!--]--></div>`);
if ($$store_subs) unsubscribe_stores($$store_subs);
bind_props($$props, { data });
});
}
export {
_page as default
};

View File

@@ -1,18 +0,0 @@
import { a as api } from "../../chunks/api.js";
async function load() {
try {
const plugins = await api.getPlugins();
return {
plugins
};
} catch (error) {
console.error("Failed to load plugins:", error);
return {
plugins: [],
error: "Failed to load plugins"
};
}
}
export {
load
};

View File

@@ -1,45 +0,0 @@
import { _ as escape_html, a2 as attr, Z as ensure_array_like, a3 as bind_props } from "../../../chunks/index2.js";
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
let data = $$props["data"];
let settings = data.settings;
let newEnv = {
id: "",
name: "",
url: "",
username: "",
password: "",
is_default: false
};
settings = data.settings;
$$renderer2.push(`<div class="container mx-auto p-4"><h1 class="text-2xl font-bold mb-6">Settings</h1> `);
if (data.error) {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">${escape_html(data.error)}</div>`);
} else {
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--> <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"${attr("value", settings.settings.backup_path)} class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2"/></div> <button class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 w-max">Save Global Settings</button></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) {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<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>`);
} else {
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--> <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">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">Default</th><th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th></tr></thead><tbody class="bg-white divide-y divide-gray-200"><!--[-->`);
const each_array = ensure_array_like(settings.environments);
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
let env = each_array[$$index];
$$renderer2.push(`<tr><td class="px-6 py-4 whitespace-nowrap">${escape_html(env.name)}</td><td class="px-6 py-4 whitespace-nowrap">${escape_html(env.url)}</td><td class="px-6 py-4 whitespace-nowrap">${escape_html(env.username)}</td><td class="px-6 py-4 whitespace-nowrap">${escape_html(env.is_default ? "Yes" : "No")}</td><td class="px-6 py-4 whitespace-nowrap"><button class="text-green-600 hover:text-green-900 mr-4">Test</button> <button class="text-indigo-600 hover:text-indigo-900 mr-4">Edit</button> <button class="text-red-600 hover:text-red-900">Delete</button></td></tr>`);
}
$$renderer2.push(`<!--]--></tbody></table></div> <div class="bg-gray-50 p-4 rounded"><h3 class="text-lg font-medium mb-4">${escape_html("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"${attr("value", newEnv.id)}${attr("disabled", false, true)} 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"${attr("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"${attr("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"${attr("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"${attr("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"${attr("checked", newEnv.is_default, true)} 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></div> <div class="mt-4 flex gap-2"><button class="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600">${escape_html("Add")} Environment</button> `);
{
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--></div></div></section></div>`);
bind_props($$props, { data });
});
}
export {
_page as default
};

View File

@@ -1,24 +0,0 @@
import { a as api } from "../../../chunks/api.js";
async function load() {
try {
const settings = await api.getSettings();
return {
settings
};
} catch (error) {
console.error("Failed to load settings:", error);
return {
settings: {
environments: [],
settings: {
backup_path: "",
default_environment_id: null
}
},
error: "Failed to load settings"
};
}
}
export {
load
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +0,0 @@
import { g, o, c, s, a, b } from "./chunks/internal.js";
import { s as s2, e, f } from "./chunks/environment.js";
export {
g as get_hooks,
o as options,
s2 as set_assets,
e as set_building,
c as set_manifest,
f as set_prerendering,
s as set_private_env,
a as set_public_env,
b as set_read_implementation
};

View File

@@ -1,47 +0,0 @@
export const manifest = (() => {
function __memo(fn) {
let value;
return () => value ??= (value = fn());
}
return {
appDir: "_app",
appPath: "_app",
assets: new Set([]),
mimeTypes: {},
_: {
client: {start:"_app/immutable/entry/start.BHAeOrfR.js",app:"_app/immutable/entry/app.BXnpILpp.js",imports:["_app/immutable/entry/start.BHAeOrfR.js","_app/immutable/chunks/D0iaTcAo.js","_app/immutable/chunks/BtL0wB3H.js","_app/immutable/chunks/BxZpmA7Z.js","_app/immutable/entry/app.BXnpILpp.js","_app/immutable/chunks/BtL0wB3H.js","_app/immutable/chunks/cv2LK44M.js","_app/immutable/chunks/BxZpmA7Z.js","_app/immutable/chunks/vVxDbqKK.js"],stylesheets:[],fonts:[],uses_env_dynamic_public:false},
nodes: [
__memo(() => import('./nodes/0.js')),
__memo(() => import('./nodes/1.js')),
__memo(() => import('./nodes/2.js')),
__memo(() => import('./nodes/3.js'))
],
remotes: {
},
routes: [
{
id: "/",
pattern: /^\/$/,
params: [],
page: { layouts: [0,], errors: [1,], leaf: 2 },
endpoint: null
},
{
id: "/settings",
pattern: /^\/settings\/?$/,
params: [],
page: { layouts: [0,], errors: [1,], leaf: 3 },
endpoint: null
}
],
prerendered_routes: new Set([]),
matchers: async () => {
return { };
},
server_assets: {}
}
}
})();

View File

@@ -1,47 +0,0 @@
export const manifest = (() => {
function __memo(fn) {
let value;
return () => value ??= (value = fn());
}
return {
appDir: "_app",
appPath: "_app",
assets: new Set([]),
mimeTypes: {},
_: {
client: {start:"_app/immutable/entry/start.BHAeOrfR.js",app:"_app/immutable/entry/app.BXnpILpp.js",imports:["_app/immutable/entry/start.BHAeOrfR.js","_app/immutable/chunks/D0iaTcAo.js","_app/immutable/chunks/BtL0wB3H.js","_app/immutable/chunks/BxZpmA7Z.js","_app/immutable/entry/app.BXnpILpp.js","_app/immutable/chunks/BtL0wB3H.js","_app/immutable/chunks/cv2LK44M.js","_app/immutable/chunks/BxZpmA7Z.js","_app/immutable/chunks/vVxDbqKK.js"],stylesheets:[],fonts:[],uses_env_dynamic_public:false},
nodes: [
__memo(() => import('./nodes/0.js')),
__memo(() => import('./nodes/1.js')),
__memo(() => import('./nodes/2.js')),
__memo(() => import('./nodes/3.js'))
],
remotes: {
},
routes: [
{
id: "/",
pattern: /^\/$/,
params: [],
page: { layouts: [0,], errors: [1,], leaf: 2 },
endpoint: null
},
{
id: "/settings",
pattern: /^\/settings\/?$/,
params: [],
page: { layouts: [0,], errors: [1,], leaf: 3 },
endpoint: null
}
],
prerendered_routes: new Set([]),
matchers: async () => {
return { };
},
server_assets: {}
}
}
})();

View File

@@ -1,13 +0,0 @@
export const index = 0;
let component_cache;
export const component = async () => component_cache ??= (await import('../entries/pages/_layout.svelte.js')).default;
export const universal = {
"ssr": false,
"prerender": false
};
export const universal_id = "src/routes/+layout.ts";
export const imports = ["_app/immutable/nodes/0.DZdF_zz-.js","_app/immutable/chunks/cv2LK44M.js","_app/immutable/chunks/BtL0wB3H.js","_app/immutable/chunks/CRLlKr96.js","_app/immutable/chunks/xdjHc-A2.js","_app/immutable/chunks/DXE57cnx.js","_app/immutable/chunks/D0iaTcAo.js","_app/immutable/chunks/BxZpmA7Z.js","_app/immutable/chunks/Dbod7Wv8.js"];
export const stylesheets = ["_app/immutable/assets/0.RZHRvmcL.css"];
export const fonts = [];

View File

@@ -1,8 +0,0 @@
export const index = 1;
let component_cache;
export const component = async () => component_cache ??= (await import('../entries/pages/_error.svelte.js')).default;
export const imports = ["_app/immutable/nodes/1.Bh-fCbID.js","_app/immutable/chunks/cv2LK44M.js","_app/immutable/chunks/BtL0wB3H.js","_app/immutable/chunks/CRLlKr96.js","_app/immutable/chunks/DXE57cnx.js","_app/immutable/chunks/D0iaTcAo.js","_app/immutable/chunks/BxZpmA7Z.js"];
export const stylesheets = [];
export const fonts = [];

View File

@@ -1,14 +0,0 @@
export const index = 2;
let component_cache;
export const component = async () => component_cache ??= (await import('../entries/pages/_page.svelte.js')).default;
export const universal = {
"ssr": false,
"prerender": false,
"load": null
};
export const universal_id = "src/routes/+page.ts";
export const imports = ["_app/immutable/nodes/2.BmiXdPHI.js","_app/immutable/chunks/DyPeVqDG.js","_app/immutable/chunks/BtL0wB3H.js","_app/immutable/chunks/Dbod7Wv8.js","_app/immutable/chunks/cv2LK44M.js","_app/immutable/chunks/CRLlKr96.js","_app/immutable/chunks/vVxDbqKK.js","_app/immutable/chunks/BxZpmA7Z.js","_app/immutable/chunks/xdjHc-A2.js"];
export const stylesheets = [];
export const fonts = [];

View File

@@ -1,14 +0,0 @@
export const index = 3;
let component_cache;
export const component = async () => component_cache ??= (await import('../entries/pages/settings/_page.svelte.js')).default;
export const universal = {
"ssr": false,
"prerender": false,
"load": null
};
export const universal_id = "src/routes/settings/+page.ts";
export const imports = ["_app/immutable/nodes/3.guWMyWpk.js","_app/immutable/chunks/DyPeVqDG.js","_app/immutable/chunks/BtL0wB3H.js","_app/immutable/chunks/Dbod7Wv8.js","_app/immutable/chunks/cv2LK44M.js","_app/immutable/chunks/CRLlKr96.js","_app/immutable/chunks/vVxDbqKK.js"];
export const stylesheets = [];
export const fonts = [];

View File

@@ -1,562 +0,0 @@
import { get_request_store, with_request_store } from "@sveltejs/kit/internal/server";
import { parse } from "devalue";
import { error, json } from "@sveltejs/kit";
import { a as stringify_remote_arg, f as flatten_issues, b as create_field_proxy, n as normalize_issue, e as set_nested_value, g as deep_set, s as stringify, c as create_remote_key } from "./chunks/shared.js";
import { ValidationError } from "@sveltejs/kit/internal";
import { B as BROWSER } from "./chunks/false.js";
import { b as base, c as app_dir, p as prerendering } from "./chunks/environment.js";
function create_validator(validate_or_fn, maybe_fn) {
if (!maybe_fn) {
return (arg) => {
if (arg !== void 0) {
error(400, "Bad Request");
}
};
}
if (validate_or_fn === "unchecked") {
return (arg) => arg;
}
if ("~standard" in validate_or_fn) {
return async (arg) => {
const { event, state } = get_request_store();
const result = await validate_or_fn["~standard"].validate(arg);
if (result.issues) {
error(
400,
await state.handleValidationError({
issues: result.issues,
event
})
);
}
return result.value;
};
}
throw new Error(
'Invalid validator passed to remote function. Expected "unchecked" or a Standard Schema (https://standardschema.dev)'
);
}
async function get_response(info, arg, state, get_result) {
await 0;
const cache = get_cache(info, state);
return cache[stringify_remote_arg(arg, state.transport)] ??= get_result();
}
function parse_remote_response(data, transport) {
const revivers = {};
for (const key in transport) {
revivers[key] = transport[key].decode;
}
return parse(data, revivers);
}
async function run_remote_function(event, state, allow_cookies, arg, validate, fn) {
const store = {
event: {
...event,
setHeaders: () => {
throw new Error("setHeaders is not allowed in remote functions");
},
cookies: {
...event.cookies,
set: (name, value, opts) => {
if (!allow_cookies) {
throw new Error("Cannot set cookies in `query` or `prerender` functions");
}
if (opts.path && !opts.path.startsWith("/")) {
throw new Error("Cookies set in remote functions must have an absolute path");
}
return event.cookies.set(name, value, opts);
},
delete: (name, opts) => {
if (!allow_cookies) {
throw new Error("Cannot delete cookies in `query` or `prerender` functions");
}
if (opts.path && !opts.path.startsWith("/")) {
throw new Error("Cookies deleted in remote functions must have an absolute path");
}
return event.cookies.delete(name, opts);
}
}
},
state: {
...state,
is_in_remote_function: true
}
};
const validated = await with_request_store(store, () => validate(arg));
return with_request_store(store, () => fn(validated));
}
function get_cache(info, state = get_request_store().state) {
let cache = state.remote_data?.get(info);
if (cache === void 0) {
cache = {};
(state.remote_data ??= /* @__PURE__ */ new Map()).set(info, cache);
}
return cache;
}
// @__NO_SIDE_EFFECTS__
function command(validate_or_fn, maybe_fn) {
const fn = maybe_fn ?? validate_or_fn;
const validate = create_validator(validate_or_fn, maybe_fn);
const __ = { type: "command", id: "", name: "" };
const wrapper = (arg) => {
const { event, state } = get_request_store();
if (state.is_endpoint_request) {
if (!["POST", "PUT", "PATCH", "DELETE"].includes(event.request.method)) {
throw new Error(
`Cannot call a command (\`${__.name}(${maybe_fn ? "..." : ""})\`) from a ${event.request.method} handler`
);
}
} else if (!event.isRemoteRequest) {
throw new Error(
`Cannot call a command (\`${__.name}(${maybe_fn ? "..." : ""})\`) during server-side rendering`
);
}
state.refreshes ??= {};
const promise = Promise.resolve(run_remote_function(event, state, true, arg, validate, fn));
promise.updates = () => {
throw new Error(`Cannot call '${__.name}(...).updates(...)' on the server`);
};
return (
/** @type {ReturnType<RemoteCommand<Input, Output>>} */
promise
);
};
Object.defineProperty(wrapper, "__", { value: __ });
Object.defineProperty(wrapper, "pending", {
get: () => 0
});
return wrapper;
}
// @__NO_SIDE_EFFECTS__
function form(validate_or_fn, maybe_fn) {
const fn = maybe_fn ?? validate_or_fn;
const schema = !maybe_fn || validate_or_fn === "unchecked" ? null : (
/** @type {any} */
validate_or_fn
);
function create_instance(key) {
const instance = {};
instance.method = "POST";
Object.defineProperty(instance, "enhance", {
value: () => {
return { action: instance.action, method: instance.method };
}
});
const button_props = {
type: "submit",
onclick: () => {
}
};
Object.defineProperty(button_props, "enhance", {
value: () => {
return { type: "submit", formaction: instance.buttonProps.formaction, onclick: () => {
} };
}
});
Object.defineProperty(instance, "buttonProps", {
value: button_props
});
const __ = {
type: "form",
name: "",
id: "",
fn: async (data, meta, form_data) => {
const output = {};
output.submission = true;
const { event, state } = get_request_store();
const validated = await schema?.["~standard"].validate(data);
if (meta.validate_only) {
return validated?.issues?.map((issue) => normalize_issue(issue, true)) ?? [];
}
if (validated?.issues !== void 0) {
handle_issues(output, validated.issues, form_data);
} else {
if (validated !== void 0) {
data = validated.value;
}
state.refreshes ??= {};
const issue = create_issues();
try {
output.result = await run_remote_function(
event,
state,
true,
data,
(d) => d,
(data2) => !maybe_fn ? fn() : fn(data2, issue)
);
} catch (e) {
if (e instanceof ValidationError) {
handle_issues(output, e.issues, form_data);
} else {
throw e;
}
}
}
if (!event.isRemoteRequest) {
get_cache(__, state)[""] ??= output;
}
return output;
}
};
Object.defineProperty(instance, "__", { value: __ });
Object.defineProperty(instance, "action", {
get: () => `?/remote=${__.id}`,
enumerable: true
});
Object.defineProperty(button_props, "formaction", {
get: () => `?/remote=${__.id}`,
enumerable: true
});
Object.defineProperty(instance, "fields", {
get() {
const data = get_cache(__)?.[""];
const issues = flatten_issues(data?.issues ?? []);
return create_field_proxy(
{},
() => data?.input ?? {},
(path, value) => {
if (data?.submission) {
return;
}
const input = path.length === 0 ? value : deep_set(data?.input ?? {}, path.map(String), value);
(get_cache(__)[""] ??= {}).input = input;
},
() => issues
);
}
});
Object.defineProperty(instance, "result", {
get() {
try {
return get_cache(__)?.[""]?.result;
} catch {
return void 0;
}
}
});
Object.defineProperty(instance, "pending", {
get: () => 0
});
Object.defineProperty(button_props, "pending", {
get: () => 0
});
Object.defineProperty(instance, "preflight", {
// preflight is a noop on the server
value: () => instance
});
Object.defineProperty(instance, "validate", {
value: () => {
throw new Error("Cannot call validate() on the server");
}
});
if (key == void 0) {
Object.defineProperty(instance, "for", {
/** @type {RemoteForm<any, any>['for']} */
value: (key2) => {
const { state } = get_request_store();
const cache_key = __.id + "|" + JSON.stringify(key2);
let instance2 = (state.form_instances ??= /* @__PURE__ */ new Map()).get(cache_key);
if (!instance2) {
instance2 = create_instance(key2);
instance2.__.id = `${__.id}/${encodeURIComponent(JSON.stringify(key2))}`;
instance2.__.name = __.name;
state.form_instances.set(cache_key, instance2);
}
return instance2;
}
});
}
return instance;
}
return create_instance();
}
function handle_issues(output, issues, form_data) {
output.issues = issues.map((issue) => normalize_issue(issue, true));
if (form_data) {
output.input = {};
for (let key of form_data.keys()) {
if (/^[.\]]?_/.test(key)) continue;
const is_array = key.endsWith("[]");
const values = form_data.getAll(key).filter((value) => typeof value === "string");
if (is_array) key = key.slice(0, -2);
set_nested_value(
/** @type {Record<string, any>} */
output.input,
key,
is_array ? values : values[0]
);
}
}
}
function create_issues() {
return (
/** @type {InvalidField<any>} */
new Proxy(
/** @param {string} message */
(message) => {
if (typeof message !== "string") {
throw new Error(
"`invalid` should now be imported from `@sveltejs/kit` to throw validation issues. The second parameter provided to the form function (renamed to `issue`) is still used to construct issues, e.g. `invalid(issue.field('message'))`. For more info see https://github.com/sveltejs/kit/pulls/14768"
);
}
return create_issue(message);
},
{
get(target, prop) {
if (typeof prop === "symbol") return (
/** @type {any} */
target[prop]
);
return create_issue_proxy(prop, []);
}
}
)
);
function create_issue(message, path = []) {
return {
message,
path
};
}
function create_issue_proxy(key, path) {
const new_path = [...path, key];
const issue_func = (message) => create_issue(message, new_path);
return new Proxy(issue_func, {
get(target, prop) {
if (typeof prop === "symbol") return (
/** @type {any} */
target[prop]
);
if (/^\d+$/.test(prop)) {
return create_issue_proxy(parseInt(prop, 10), new_path);
}
return create_issue_proxy(prop, new_path);
}
});
}
}
// @__NO_SIDE_EFFECTS__
function prerender(validate_or_fn, fn_or_options, maybe_options) {
const maybe_fn = typeof fn_or_options === "function" ? fn_or_options : void 0;
const options = maybe_options ?? (maybe_fn ? void 0 : fn_or_options);
const fn = maybe_fn ?? validate_or_fn;
const validate = create_validator(validate_or_fn, maybe_fn);
const __ = {
type: "prerender",
id: "",
name: "",
has_arg: !!maybe_fn,
inputs: options?.inputs,
dynamic: options?.dynamic
};
const wrapper = (arg) => {
const promise = (async () => {
const { event, state } = get_request_store();
const payload = stringify_remote_arg(arg, state.transport);
const id = __.id;
const url = `${base}/${app_dir}/remote/${id}${payload ? `/${payload}` : ""}`;
if (!state.prerendering && !BROWSER && !event.isRemoteRequest) {
try {
return await get_response(__, arg, state, async () => {
const key = stringify_remote_arg(arg, state.transport);
const cache = get_cache(__, state);
const promise3 = cache[key] ??= fetch(new URL(url, event.url.origin).href).then(
async (response) => {
if (!response.ok) {
throw new Error("Prerendered response not found");
}
const prerendered = await response.json();
if (prerendered.type === "error") {
error(prerendered.status, prerendered.error);
}
return prerendered.result;
}
);
return parse_remote_response(await promise3, state.transport);
});
} catch {
}
}
if (state.prerendering?.remote_responses.has(url)) {
return (
/** @type {Promise<any>} */
state.prerendering.remote_responses.get(url)
);
}
const promise2 = get_response(
__,
arg,
state,
() => run_remote_function(event, state, false, arg, validate, fn)
);
if (state.prerendering) {
state.prerendering.remote_responses.set(url, promise2);
}
const result = await promise2;
if (state.prerendering) {
const body = { type: "result", result: stringify(result, state.transport) };
state.prerendering.dependencies.set(url, {
body: JSON.stringify(body),
response: json(body)
});
}
return result;
})();
promise.catch(() => {
});
return (
/** @type {RemoteResource<Output>} */
promise
);
};
Object.defineProperty(wrapper, "__", { value: __ });
return wrapper;
}
// @__NO_SIDE_EFFECTS__
function query(validate_or_fn, maybe_fn) {
const fn = maybe_fn ?? validate_or_fn;
const validate = create_validator(validate_or_fn, maybe_fn);
const __ = { type: "query", id: "", name: "" };
const wrapper = (arg) => {
if (prerendering) {
throw new Error(
`Cannot call query '${__.name}' while prerendering, as prerendered pages need static data. Use 'prerender' from $app/server instead`
);
}
const { event, state } = get_request_store();
const get_remote_function_result = () => run_remote_function(event, state, false, arg, validate, fn);
const promise = get_response(__, arg, state, get_remote_function_result);
promise.catch(() => {
});
promise.set = (value) => update_refresh_value(get_refresh_context(__, "set", arg), value);
promise.refresh = () => {
const refresh_context = get_refresh_context(__, "refresh", arg);
const is_immediate_refresh = !refresh_context.cache[refresh_context.cache_key];
const value = is_immediate_refresh ? promise : get_remote_function_result();
return update_refresh_value(refresh_context, value, is_immediate_refresh);
};
promise.withOverride = () => {
throw new Error(`Cannot call '${__.name}.withOverride()' on the server`);
};
return (
/** @type {RemoteQuery<Output>} */
promise
);
};
Object.defineProperty(wrapper, "__", { value: __ });
return wrapper;
}
// @__NO_SIDE_EFFECTS__
function batch(validate_or_fn, maybe_fn) {
const fn = maybe_fn ?? validate_or_fn;
const validate = create_validator(validate_or_fn, maybe_fn);
const __ = {
type: "query_batch",
id: "",
name: "",
run: (args) => {
const { event, state } = get_request_store();
return run_remote_function(
event,
state,
false,
args,
(array) => Promise.all(array.map(validate)),
fn
);
}
};
let batching = { args: [], resolvers: [] };
const wrapper = (arg) => {
if (prerendering) {
throw new Error(
`Cannot call query.batch '${__.name}' while prerendering, as prerendered pages need static data. Use 'prerender' from $app/server instead`
);
}
const { event, state } = get_request_store();
const get_remote_function_result = () => {
return new Promise((resolve, reject) => {
batching.args.push(arg);
batching.resolvers.push({ resolve, reject });
if (batching.args.length > 1) return;
setTimeout(async () => {
const batched = batching;
batching = { args: [], resolvers: [] };
try {
const get_result = await run_remote_function(
event,
state,
false,
batched.args,
(array) => Promise.all(array.map(validate)),
fn
);
for (let i = 0; i < batched.resolvers.length; i++) {
try {
batched.resolvers[i].resolve(get_result(batched.args[i], i));
} catch (error2) {
batched.resolvers[i].reject(error2);
}
}
} catch (error2) {
for (const resolver of batched.resolvers) {
resolver.reject(error2);
}
}
}, 0);
});
};
const promise = get_response(__, arg, state, get_remote_function_result);
promise.catch(() => {
});
promise.set = (value) => update_refresh_value(get_refresh_context(__, "set", arg), value);
promise.refresh = () => {
const refresh_context = get_refresh_context(__, "refresh", arg);
const is_immediate_refresh = !refresh_context.cache[refresh_context.cache_key];
const value = is_immediate_refresh ? promise : get_remote_function_result();
return update_refresh_value(refresh_context, value, is_immediate_refresh);
};
promise.withOverride = () => {
throw new Error(`Cannot call '${__.name}.withOverride()' on the server`);
};
return (
/** @type {RemoteQuery<Output>} */
promise
);
};
Object.defineProperty(wrapper, "__", { value: __ });
return wrapper;
}
Object.defineProperty(query, "batch", { value: batch, enumerable: true });
function get_refresh_context(__, action, arg) {
const { state } = get_request_store();
const { refreshes } = state;
if (!refreshes) {
const name = __.type === "query_batch" ? `query.batch '${__.name}'` : `query '${__.name}'`;
throw new Error(
`Cannot call ${action} on ${name} because it is not executed in the context of a command/form remote function`
);
}
const cache = get_cache(__, state);
const cache_key = stringify_remote_arg(arg, state.transport);
const refreshes_key = create_remote_key(__.id, cache_key);
return { __, state, refreshes, refreshes_key, cache, cache_key };
}
function update_refresh_value({ __, refreshes, refreshes_key, cache, cache_key }, value, is_immediate_refresh = false) {
const promise = Promise.resolve(value);
if (!is_immediate_refresh) {
cache[cache_key] = promise;
}
if (__.id) {
refreshes[refreshes_key] = promise;
}
return promise.then(() => {
});
}
export {
command,
form,
prerender,
query
};

View File

@@ -1,52 +0,0 @@
{
"compilerOptions": {
"paths": {
"$lib": [
"../src/lib"
],
"$lib/*": [
"../src/lib/*"
],
"$app/types": [
"./types/index.d.ts"
]
},
"rootDirs": [
"..",
"./types"
],
"verbatimModuleSyntax": true,
"isolatedModules": true,
"lib": [
"esnext",
"DOM",
"DOM.Iterable"
],
"moduleResolution": "bundler",
"module": "esnext",
"noEmit": true,
"target": "esnext"
},
"include": [
"ambient.d.ts",
"non-ambient.d.ts",
"./types/**/$types.d.ts",
"../vite.config.js",
"../vite.config.ts",
"../src/**/*.js",
"../src/**/*.ts",
"../src/**/*.svelte",
"../tests/**/*.js",
"../tests/**/*.ts",
"../tests/**/*.svelte"
],
"exclude": [
"../node_modules/**",
"../src/service-worker.js",
"../src/service-worker/**/*.js",
"../src/service-worker.ts",
"../src/service-worker/**/*.ts",
"../src/service-worker.d.ts",
"../src/service-worker/**/*.d.ts"
]
}

View File

@@ -21,6 +21,13 @@
if (!res.ok) throw new Error('Failed to fetch tasks'); if (!res.ok) throw new Error('Failed to fetch tasks');
tasks = await res.json(); tasks = await res.json();
// [DEBUG] Check for tasks requiring attention
tasks.forEach(t => {
if (t.status === 'AWAITING_MAPPING' || t.status === 'AWAITING_INPUT') {
console.log(`[TaskHistory] Task ${t.id} is in state ${t.status}. Input required: ${t.input_required}`);
}
});
// Update selected task if it exists in the list (for status updates) // Update selected task if it exists in the list (for status updates)
if ($selectedTask) { if ($selectedTask) {
const updatedTask = tasks.find(t => t.id === $selectedTask.id); const updatedTask = tasks.find(t => t.id === $selectedTask.id);
@@ -35,9 +42,38 @@
} }
} }
function selectTask(task) { async function clearTasks(status = null) {
if (!confirm('Are you sure you want to clear tasks?')) return;
try {
let url = '/api/tasks';
const params = new URLSearchParams();
if (status) params.append('status', status);
const res = await fetch(`${url}?${params.toString()}`, { method: 'DELETE' });
if (!res.ok) throw new Error('Failed to clear tasks');
await fetchTasks();
} catch (e) {
error = e.message;
}
}
async function selectTask(task) {
try {
// Fetch the full task details (including logs) before setting it as selected
const res = await fetch(`/api/tasks/${task.id}`);
if (res.ok) {
const fullTask = await res.json();
selectedTask.set(fullTask);
} else {
// Fallback to the list version if fetch fails
selectedTask.set(task); selectedTask.set(task);
} }
} catch (e) {
console.error("Failed to fetch full task details:", e);
selectedTask.set(task);
}
}
function getStatusColor(status) { function getStatusColor(status) {
switch (status) { switch (status) {
@@ -65,6 +101,22 @@
<h3 class="text-lg leading-6 font-medium text-gray-900"> <h3 class="text-lg leading-6 font-medium text-gray-900">
Recent Tasks Recent Tasks
</h3> </h3>
<div class="flex space-x-4 items-center">
<div class="relative inline-block text-left group">
<button class="text-sm text-red-600 hover:text-red-900 focus:outline-none flex items-center py-2">
Clear Tasks
<svg class="ml-1 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
</button>
<!-- Added a transparent bridge to prevent menu closing when moving cursor -->
<div class="absolute h-2 w-full top-full left-0"></div>
<div class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none hidden group-hover:block z-50">
<div class="py-1">
<button on:click={() => clearTasks()} class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Clear All Non-Running</button>
<button on:click={() => clearTasks('FAILED')} class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Clear Failed</button>
<button on:click={() => clearTasks('AWAITING_INPUT')} class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Clear Awaiting Input</button>
</div>
</div>
</div>
<button <button
on:click={fetchTasks} on:click={fetchTasks}
class="text-sm text-indigo-600 hover:text-indigo-900 focus:outline-none" class="text-sm text-indigo-600 hover:text-indigo-900 focus:outline-none"
@@ -72,6 +124,7 @@
Refresh Refresh
</button> </button>
</div> </div>
</div>
{#if loading && tasks.length === 0} {#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">Loading tasks...</div>

View File

@@ -0,0 +1,153 @@
<!-- [DEF:TaskLogViewer:Component] -->
<!--
@SEMANTICS: task, log, viewer, modal
@PURPOSE: Displays detailed logs for a specific task in a modal.
@LAYER: UI
@RELATION: USES -> frontend/src/lib/api.js (inferred)
-->
<script>
import { createEventDispatcher, onMount, onDestroy } from 'svelte';
import { getTaskLogs } from '../services/taskService.js';
export let show = false;
export let taskId = null;
export let taskStatus = null; // To know if we should poll
const dispatch = createEventDispatcher();
let logs = [];
let loading = false;
let error = "";
let interval;
let autoScroll = true;
let logContainer;
async function fetchLogs() {
if (!taskId) return;
try {
logs = await getTaskLogs(taskId);
if (autoScroll) {
scrollToBottom();
}
} catch (e) {
error = e.message;
} finally {
loading = false;
}
}
function scrollToBottom() {
if (logContainer) {
setTimeout(() => {
logContainer.scrollTop = logContainer.scrollHeight;
}, 0);
}
}
function handleScroll() {
if (!logContainer) return;
// If user scrolls up, disable auto-scroll
const { scrollTop, scrollHeight, clientHeight } = logContainer;
const atBottom = scrollHeight - scrollTop - clientHeight < 50;
autoScroll = atBottom;
}
function close() {
dispatch('close');
show = false;
}
function getLogLevelColor(level) {
switch (level) {
case 'INFO': return 'text-blue-600';
case 'WARNING': return 'text-yellow-600';
case 'ERROR': return 'text-red-600';
case 'DEBUG': return 'text-gray-500';
default: return 'text-gray-800';
}
}
// React to changes in show/taskId
$: if (show && taskId) {
logs = [];
loading = true;
error = "";
fetchLogs();
// Poll if task is running
if (taskStatus === 'RUNNING' || taskStatus === 'AWAITING_INPUT' || taskStatus === 'AWAITING_MAPPING') {
interval = setInterval(fetchLogs, 3000);
}
} else {
if (interval) clearInterval(interval);
}
onDestroy(() => {
if (interval) clearInterval(interval);
});
</script>
{#if show}
<div class="fixed inset-0 z-50 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<!-- Background overlay -->
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true" on:click={close}></div>
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
<div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-4xl sm:w-full">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<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>
</h3>
<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>
{: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>
{:else}
{#each logs as log}
<div class="mb-1 hover:bg-gray-100 p-1 rounded">
<span class="text-gray-400 text-xs mr-2">
{new Date(log.timestamp).toLocaleTimeString()}
</span>
<span class="font-bold text-xs mr-2 w-16 inline-block {getLogLevelColor(log.level)}">
[{log.level}]
</span>
<span class="text-gray-800 break-words">
{log.message}
</span>
{#if log.context}
<div class="ml-24 text-xs text-gray-500 mt-1 bg-gray-100 p-1 rounded overflow-x-auto">
<pre>{JSON.stringify(log.context, null, 2)}</pre>
</div>
{/if}
</div>
{/each}
{/if}
</div>
</div>
</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>
</div>
</div>
</div>
</div>
{/if}
<!-- [/DEF:TaskLogViewer] -->

View File

@@ -93,6 +93,17 @@
} }
}; };
// Check if task is already awaiting input (e.g. when re-selecting task)
// We use the 'task' variable from the outer scope (connect function)
if (task && task.status === 'AWAITING_INPUT' && task.input_request && task.input_request.type === 'database_password') {
connectionStatus = 'awaiting_input';
passwordPromptData = {
databases: task.input_request.databases || [],
errorMessage: task.input_request.error_message || ''
};
showPasswordPrompt = true;
}
ws.onerror = (error) => { ws.onerror = (error) => {
console.error('[TaskRunner][Coherence:Failed] WebSocket error:', error); console.error('[TaskRunner][Coherence:Failed] WebSocket error:', error);
connectionStatus = 'disconnected'; connectionStatus = 'disconnected';
@@ -221,7 +232,15 @@
clearTimeout(reconnectTimeout); clearTimeout(reconnectTimeout);
reconnectAttempts = 0; reconnectAttempts = 0;
connectionStatus = 'disconnected'; connectionStatus = 'disconnected';
// Initialize logs from the task object if available
if (task.logs && Array.isArray(task.logs)) {
console.log(`[TaskRunner] Loaded ${task.logs.length} existing logs.`);
taskLogs.set(task.logs);
} else {
taskLogs.set([]); taskLogs.set([]);
}
connect(); connect();
} }
}); });
@@ -275,18 +294,46 @@
</div> </div>
</div> </div>
<div class="bg-gray-900 text-white font-mono text-sm p-4 rounded-md h-96 overflow-y-auto relative"> <!-- Task Info Section -->
<div class="mb-4 bg-gray-50 p-3 rounded text-sm border border-gray-200">
<details open>
<summary class="cursor-pointer font-medium text-gray-700 focus:outline-none hover:text-indigo-600">Task Details & Parameters</summary>
<div class="mt-2 pl-2 border-l-2 border-indigo-200">
<div class="grid grid-cols-2 gap-2 mb-2">
<div><span class="font-semibold">ID:</span> <span class="text-gray-600">{$selectedTask.id}</span></div>
<div><span class="font-semibold">Status:</span> <span class="text-gray-600">{$selectedTask.status}</span></div>
<div><span class="font-semibold">Started:</span> <span class="text-gray-600">{new Date($selectedTask.started_at || $selectedTask.created_at || Date.now()).toLocaleString()}</span></div>
<div><span class="font-semibold">Plugin:</span> <span class="text-gray-600">{$selectedTask.plugin_id}</span></div>
</div>
<div class="mt-1">
<span class="font-semibold">Parameters:</span>
<pre class="text-xs bg-gray-100 p-2 rounded mt-1 overflow-x-auto border border-gray-200">{JSON.stringify($selectedTask.params, null, 2)}</pre>
</div>
</div>
</details>
</div>
<div class="bg-gray-900 text-white font-mono text-sm p-4 rounded-md h-96 overflow-y-auto relative shadow-inner">
{#if $taskLogs.length === 0}
<div class="text-gray-500 italic text-center mt-10">No logs available for this task.</div>
{/if}
{#each $taskLogs as log} {#each $taskLogs as log}
<div> <div class="hover:bg-gray-800 px-1 rounded">
<span class="text-gray-400">{new Date(log.timestamp).toLocaleTimeString()}</span> <span class="text-gray-500 select-none text-xs w-20 inline-block">{new Date(log.timestamp).toLocaleTimeString()}</span>
<span class="{log.level === 'ERROR' ? 'text-red-500' : 'text-green-400'}">[{log.level}]</span> <span class="{log.level === 'ERROR' ? 'text-red-500 font-bold' : log.level === 'WARNING' ? 'text-yellow-400' : 'text-green-400'} w-16 inline-block">[{log.level}]</span>
<span>{log.message}</span> <span>{log.message}</span>
{#if log.context}
<details class="ml-24">
<summary class="text-xs text-gray-500 cursor-pointer hover:text-gray-300">Context</summary>
<pre class="text-xs text-gray-400 pl-2 border-l border-gray-700 mt-1">{JSON.stringify(log.context, null, 2)}</pre>
</details>
{/if}
</div> </div>
{/each} {/each}
{#if waitingForData} {#if waitingForData && connectionStatus === 'connected'}
<div class="text-gray-500 italic mt-2 animate-pulse"> <div class="text-gray-500 italic mt-2 animate-pulse border-t border-gray-800 pt-2">
Waiting for data... Waiting for new logs...
</div> </div>
{/if} {/if}
</div> </div>

View File

@@ -14,8 +14,12 @@
import EnvSelector from '../../components/EnvSelector.svelte'; import EnvSelector from '../../components/EnvSelector.svelte';
import DashboardGrid from '../../components/DashboardGrid.svelte'; import DashboardGrid from '../../components/DashboardGrid.svelte';
import MappingTable from '../../components/MappingTable.svelte'; import MappingTable from '../../components/MappingTable.svelte';
import MissingMappingModal from '../../components/MissingMappingModal.svelte'; import TaskRunner from '../../components/TaskRunner.svelte';
import TaskHistory from '../../components/TaskHistory.svelte'; import TaskHistory from '../../components/TaskHistory.svelte';
import TaskLogViewer from '../../components/TaskLogViewer.svelte';
import PasswordPrompt from '../../components/PasswordPrompt.svelte';
import { selectedTask } from '../../lib/stores.js';
import { resumeTask } from '../../services/taskService.js';
import type { DashboardMetadata, DashboardSelection } from '../../types/dashboard'; import type { DashboardMetadata, DashboardSelection } from '../../types/dashboard';
// [/SECTION] // [/SECTION]
@@ -33,6 +37,15 @@
let mappings: any[] = []; let mappings: any[] = [];
let suggestions: any[] = []; let suggestions: any[] = [];
let fetchingDbs = false; let fetchingDbs = false;
// UI State for Modals
let showLogViewer = false;
let logViewerTaskId: string | null = null;
let logViewerTaskStatus: string | null = null;
let showPasswordPrompt = false;
let passwordPromptDatabases: string[] = [];
let passwordPromptErrorMessage = "";
// [/SECTION] // [/SECTION]
// [DEF:fetchEnvironments:Function] // [DEF:fetchEnvironments:Function]
@@ -147,6 +160,50 @@
} }
// [/DEF:handleMappingUpdate] // [/DEF:handleMappingUpdate]
// [DEF:handleViewLogs:Function]
function handleViewLogs(event: CustomEvent) {
const task = event.detail;
logViewerTaskId = task.id;
logViewerTaskStatus = task.status;
showLogViewer = true;
}
// [/DEF:handleViewLogs]
// [DEF:handlePasswordPrompt:Function]
// This is triggered by TaskRunner or TaskHistory when a task needs input
// For now, we rely on the WebSocket or manual check.
// Ideally, TaskHistory or TaskRunner emits an event when input is needed.
// Or we watch selectedTask.
$: if ($selectedTask && $selectedTask.status === 'AWAITING_INPUT' && $selectedTask.input_request) {
const req = $selectedTask.input_request;
if (req.type === 'database_password') {
passwordPromptDatabases = req.databases || [];
passwordPromptErrorMessage = req.error_message || "";
showPasswordPrompt = true;
}
} else if (!$selectedTask || $selectedTask.status !== 'AWAITING_INPUT') {
// Close prompt if task is no longer waiting (e.g. resumed)
// But only if we are viewing this task.
// showPasswordPrompt = false;
// Actually, don't auto-close, let the user or success handler close it.
}
async function handleResumeMigration(event: CustomEvent) {
if (!$selectedTask) return;
const { passwords } = event.detail;
try {
await resumeTask($selectedTask.id, passwords);
showPasswordPrompt = false;
// Task status update will be handled by store/websocket
} catch (e) {
console.error("Failed to resume task:", e);
passwordPromptErrorMessage = e.message;
// Keep prompt open
}
}
// [DEF:startMigration:Function] // [DEF:startMigration:Function]
/** /**
* @purpose Starts the migration process. * @purpose Starts the migration process.
@@ -171,7 +228,8 @@
const selection: DashboardSelection = { const selection: DashboardSelection = {
selected_ids: selectedDashboardIds, selected_ids: selectedDashboardIds,
source_env_id: sourceEnvId, source_env_id: sourceEnvId,
target_env_id: targetEnvId target_env_id: targetEnvId,
replace_db_config: replaceDb
}; };
console.log(`[MigrationDashboard][Action] Starting migration with selection:`, selection); console.log(`[MigrationDashboard][Action] Starting migration with selection:`, selection);
const response = await fetch('/api/migration/execute', { const response = await fetch('/api/migration/execute', {
@@ -183,7 +241,30 @@
if (!response.ok) throw new Error(`Failed to start migration: ${response.status} ${response.statusText}`); if (!response.ok) throw new Error(`Failed to start migration: ${response.status} ${response.statusText}`);
const result = await response.json(); const result = await response.json();
console.log(`[MigrationDashboard][Action] Migration started: ${result.task_id} - ${result.message}`); console.log(`[MigrationDashboard][Action] Migration started: ${result.task_id} - ${result.message}`);
// TODO: Show success message or redirect to task status
// Wait a brief moment for the backend to ensure the task is retrievable
await new Promise(r => setTimeout(r, 500));
// Fetch full task details and switch to TaskRunner view
try {
const taskRes = await fetch(`/api/tasks/${result.task_id}`);
if (taskRes.ok) {
const task = await taskRes.json();
selectedTask.set(task);
} else {
// Fallback: create a temporary task object to switch view immediately
console.warn("Could not fetch task details immediately, using placeholder.");
selectedTask.set({
id: result.task_id,
plugin_id: 'superset-migration',
status: 'RUNNING',
logs: [],
params: {}
});
}
} catch (fetchErr) {
console.error("Failed to fetch new task details:", fetchErr);
}
} catch (e) { } catch (e) {
console.error(`[MigrationDashboard][Failure] Migration failed:`, e); console.error(`[MigrationDashboard][Failure] Migration failed:`, e);
error = e.message; error = e.message;
@@ -196,8 +277,19 @@
<div class="max-w-4xl mx-auto p-6"> <div class="max-w-4xl mx-auto p-6">
<h1 class="text-2xl font-bold mb-6">Migration Dashboard</h1> <h1 class="text-2xl font-bold mb-6">Migration Dashboard</h1>
<TaskHistory /> <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>
{:else}
{#if loading} {#if loading}
<p>Loading environments...</p> <p>Loading environments...</p>
{:else if error} {:else if error}
@@ -279,7 +371,25 @@
> >
Start Migration Start Migration
</button> </button>
{/if}
</div> </div>
<!-- Modals -->
<TaskLogViewer
bind:show={showLogViewer}
taskId={logViewerTaskId}
taskStatus={logViewerTaskStatus}
on:close={() => showLogViewer = false}
/>
<PasswordPrompt
bind:show={showPasswordPrompt}
databases={passwordPromptDatabases}
errorMessage={passwordPromptErrorMessage}
on:resume={handleResumeMigration}
on:cancel={() => showPasswordPrompt = false}
/>
<!-- [/SECTION] --> <!-- [/SECTION] -->
<style> <style>

View File

@@ -0,0 +1,120 @@
/**
* Service for interacting with the Task Management API.
*/
const API_BASE = '/api/tasks';
/**
* Fetch a list of tasks with pagination and optional status filter.
* @param {number} limit - Maximum number of tasks to return.
* @param {number} offset - Number of tasks to skip.
* @param {string|null} status - Filter by task status (optional).
* @returns {Promise<Array>} List of tasks.
*/
export async function getTasks(limit = 10, offset = 0, status = null) {
const params = new URLSearchParams({
limit: limit.toString(),
offset: offset.toString()
});
if (status) {
params.append('status', status);
}
const response = await fetch(`${API_BASE}?${params.toString()}`);
if (!response.ok) {
throw new Error(`Failed to fetch tasks: ${response.statusText}`);
}
return await response.json();
}
/**
* Fetch details for a specific task.
* @param {string} taskId - The ID of the task.
* @returns {Promise<Object>} Task details.
*/
export async function getTask(taskId) {
const response = await fetch(`${API_BASE}/${taskId}`);
if (!response.ok) {
throw new Error(`Failed to fetch task ${taskId}: ${response.statusText}`);
}
return await response.json();
}
/**
* Fetch logs for a specific task.
* @param {string} taskId - The ID of the task.
* @returns {Promise<Array>} List of log entries.
*/
export async function getTaskLogs(taskId) {
// Currently, logs are included in the task object, but we might have a separate endpoint later.
// For now, we fetch the task and return its logs.
// Or if we implement T017 (GET /api/tasks/{task_id}/logs), we would use that.
// The current backend implementation in tasks.py does NOT have a separate /logs endpoint yet.
// T017 is in Phase 3.
// So for now, we'll fetch the task.
const task = await getTask(taskId);
return task.logs || [];
}
/**
* Resume a task that is awaiting input (e.g., passwords).
* @param {string} taskId - The ID of the task.
* @param {Object} passwords - Map of database names to passwords.
* @returns {Promise<Object>} Updated task object.
*/
export async function resumeTask(taskId, passwords) {
const response = await fetch(`${API_BASE}/${taskId}/resume`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ passwords })
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || `Failed to resume task: ${response.statusText}`);
}
return await response.json();
}
/**
* Resolve a task that is awaiting mapping.
* @param {string} taskId - The ID of the task.
* @param {Object} resolutionParams - Resolution parameters.
* @returns {Promise<Object>} Updated task object.
*/
export async function resolveTask(taskId, resolutionParams) {
const response = await fetch(`${API_BASE}/${taskId}/resolve`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ resolution_params: resolutionParams })
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || `Failed to resolve task: ${response.statusText}`);
}
return await response.json();
}
/**
* Clear tasks based on status.
* @param {string|null} status - Filter by task status (optional).
*/
export async function clearTasks(status = null) {
const params = new URLSearchParams();
if (status) {
params.append('status', status);
}
const response = await fetch(`${API_BASE}?${params.toString()}`, {
method: 'DELETE'
});
if (!response.ok) {
throw new Error(`Failed to clear tasks: ${response.statusText}`);
}
}

View File

@@ -9,4 +9,5 @@ export interface DashboardSelection {
selected_ids: number[]; selected_ids: number[];
source_env_id: string; source_env_id: string;
target_env_id: string; target_env_id: string;
replace_db_config?: boolean;
} }

View File

@@ -6,13 +6,16 @@ export default defineConfig({
server: { server: {
proxy: { proxy: {
'/api': { '/api': {
target: 'http://localhost:8000', target: 'http://127.0.0.1:8000',
changeOrigin: true changeOrigin: true,
secure: false,
rewrite: (path) => path.replace(/^\/api/, '/api')
}, },
'/ws': { '/ws': {
target: 'ws://localhost:8000', target: 'ws://127.0.0.1:8000',
ws: true, ws: true,
changeOrigin: true changeOrigin: true,
secure: false
} }
} }
} }

View File

@@ -1,6 +1,6 @@
# API Contracts: Migration UI Improvements # API Contracts: Migration UI Improvements
**Date**: 2025-12-27 | **Status**: Draft **Date**: 2025-12-27 | **Status**: Implemented
## Overview ## Overview
@@ -18,7 +18,7 @@ All endpoints require authentication using the existing session mechanism.
### 1. List Migration Tasks ### 1. List Migration Tasks
**Endpoint**: `GET /tasks` **Endpoint**: `GET /api/tasks`
**Purpose**: Retrieve a paginated list of migration tasks **Purpose**: Retrieve a paginated list of migration tasks
@@ -26,7 +26,7 @@ All endpoints require authentication using the existing session mechanism.
``` ```
limit: integer (query, optional) - Number of tasks to return (default: 10, max: 50) limit: integer (query, optional) - Number of tasks to return (default: 10, max: 50)
offset: integer (query, optional) - Pagination offset (default: 0) offset: integer (query, optional) - Pagination offset (default: 0)
status: string (query, optional) - Filter by task status (PENDING, RUNNING, SUCCESS, FAILED, AWAITING_INPUT) status: string (query, optional) - Filter by task status (PENDING, RUNNING, SUCCESS, FAILED, AWAITING_INPUT, AWAITING_MAPPING)
``` ```
**Response**: `200 OK` **Response**: `200 OK`

View File

@@ -55,20 +55,20 @@ This document provides actionable, dependency-ordered tasks for implementing the
## Phase 1: Setup (Project Initialization) ## Phase 1: Setup (Project Initialization)
- [ ] T001 Verify project structure and create missing directories - [x] T001 Verify project structure and create missing directories
- Check backend/src/api/routes/ exists - Check backend/src/api/routes/ exists
- Check backend/src/models/ exists - Check backend/src/models/ exists
- Check frontend/src/components/ exists - Check frontend/src/components/ exists
- Check frontend/src/services/ exists - Check frontend/src/services/ exists
- Create any missing directories per plan.md structure - Create any missing directories per plan.md structure
- [ ] T002 Verify Python 3.9+ and Node.js 18+ dependencies - [x] T002 Verify Python 3.9+ and Node.js 18+ dependencies
- Check Python version >= 3.9 - Check Python version >= 3.9
- Check Node.js version >= 18 - Check Node.js version >= 18
- Verify FastAPI, Pydantic, SQLAlchemy installed - Verify FastAPI, Pydantic, SQLAlchemy installed
- Verify SvelteKit, Tailwind CSS configured - Verify SvelteKit, Tailwind CSS configured
- [ ] T003 Initialize task tracking for this implementation - [x] T003 Initialize task tracking for this implementation
- Create implementation log in backend/logs/implementation.log - Create implementation log in backend/logs/implementation.log
- Document start time and initial state - Document start time and initial state
@@ -78,35 +78,35 @@ This document provides actionable, dependency-ordered tasks for implementing the
### Backend Core Extensions ### Backend Core Extensions
- [ ] T004 [P] Extend TaskStatus enum in backend/src/core/task_manager.py - [x] T004 [P] Extend TaskStatus enum in backend/src/core/task_manager/models.py
- Add new state: `AWAITING_INPUT` - Add new state: `AWAITING_INPUT`
- Update state transition logic - Update state transition logic
- Add validation for new state - Add validation for new state
- [ ] T005 [P] Extend Task class in backend/src/core/task_manager.py - [x] T005 [P] Extend Task class in backend/src/core/task_manager/models.py
- Add `input_required: bool` field - Add `input_required: bool` field
- Add `input_request: Dict | None` field - Add `input_request: Dict | None` field
- Add `logs: List[LogEntry]` field - Add `logs: List[LogEntry]` field
- Update constructor and validation - Update constructor and validation
- [ ] T006 [P] Implement task history retrieval in TaskManager - [x] T006 [P] Implement task history retrieval in TaskManager (backend/src/core/task_manager/manager.py)
- Add `get_tasks(limit, offset, status)` method - Add `get_tasks(limit, offset, status)` method
- Add `get_task_logs(task_id)` method - Add `get_task_logs(task_id)` method
- Add `persist_awaiting_input_tasks()` method - Add `persist_awaiting_input_tasks()` method
- Add `load_persisted_tasks()` method - Add `load_persisted_tasks()` method
- [ ] T007 [P] Create SQLite schema for persistent tasks - [x] T007 [P] Verify/Update SQLite schema in backend/src/core/task_manager/persistence.py
- Create migration script for `persistent_tasks` table - Ensure `persistent_tasks` table exists with required fields
- Add indexes for status and created_at - Add indexes for status and created_at if missing
- Test schema creation - Verify schema creation in `_ensure_db_exists`
- [ ] T008 [P] Extend MigrationPlugin error handling - [x] T008 [P] Extend MigrationPlugin error handling
- Add pattern matching for Superset password errors - Add pattern matching for Superset password errors
- Detect "Must provide a password for the database" message - Detect "Must provide a password for the database" message
- Extract database name from error context - Extract database name from error context
- Transition task to AWAITING_INPUT state - Transition task to AWAITING_INPUT state
- [ ] T009 [P] Implement password injection mechanism - [x] T009 [P] Implement password injection mechanism
- Add method to resume task with credentials - Add method to resume task with credentials
- Validate password format - Validate password format
- Handle multiple database passwords - Handle multiple database passwords
@@ -114,19 +114,19 @@ This document provides actionable, dependency-ordered tasks for implementing the
### Backend API Routes ### Backend API Routes
- [ ] T010 [P] Create backend/src/api/routes/tasks.py - [x] T010 [P] Create backend/src/api/routes/tasks.py
- Create file with basic route definitions - Create file with basic route definitions
- Add route handlers with stubbed responses - Add route handlers with stubbed responses
- Register router in __init__.py (covered by T011) - Register router in __init__.py (covered by T011)
- Add basic error handling structure - Add basic error handling structure
- [ ] T011 [P] Add task routes to backend/src/api/routes/__init__.py - [x] T011 [P] Add task routes to backend/src/api/routes/__init__.py
- Import and register tasks router - Import and register tasks router
- Verify route registration - Verify route registration
### Frontend Services ### Frontend Services
- [ ] T012 [P] Create frontend/src/services/taskService.js - [x] T012 [P] Create frontend/src/services/taskService.js
- Implement `getTasks(limit, offset, status)` function - Implement `getTasks(limit, offset, status)` function
- Implement `getTaskLogs(taskId)` function - Implement `getTaskLogs(taskId)` function
- Implement `resumeTask(taskId, passwords)` function - Implement `resumeTask(taskId, passwords)` function
@@ -134,19 +134,19 @@ This document provides actionable, dependency-ordered tasks for implementing the
### Frontend Components ### Frontend Components
- [ ] T013 [P] Create frontend/src/components/TaskHistory.svelte - [x] T013 [P] Create frontend/src/components/TaskHistory.svelte
- Display task list with ID, status, start time - Display task list with ID, status, start time
- Add "View Logs" action button - Add "View Logs" action button
- Handle empty state - Handle empty state
- Support real-time updates - Support real-time updates
- [ ] T014 [P] Create frontend/src/components/TaskLogViewer.svelte - [x] T014 [P] Create frontend/src/components/TaskLogViewer.svelte
- Display modal with log entries - Display modal with log entries
- Show timestamp, level, message, context - Show timestamp, level, message, context
- Auto-scroll to latest logs - Auto-scroll to latest logs
- Close button functionality - Close button functionality
- [ ] T015 [P] Create frontend/src/components/PasswordPrompt.svelte - [x] T015 [P] Create frontend/src/components/PasswordPrompt.svelte
- Display database name and error message - Display database name and error message
- Show password input fields (dynamic for multiple databases) - Show password input fields (dynamic for multiple databases)
- Submit button with validation - Submit button with validation
@@ -162,50 +162,50 @@ This document provides actionable, dependency-ordered tasks for implementing the
### Backend Implementation ### Backend Implementation
- [ ] T016 [US1] Implement GET /api/tasks endpoint logic - [x] T016 [US1] Implement GET /api/tasks endpoint logic
- Query TaskManager for task list - Query TaskManager for task list
- Apply limit/offset pagination - Apply limit/offset pagination
- Apply status filter if provided - Apply status filter if provided
- Return TaskListResponse format - Return TaskListResponse format
- [ ] T017 [US1] Implement GET /api/tasks/{task_id}/logs endpoint logic - [x] T017 [US1] Implement GET /api/tasks/{task_id}/logs endpoint logic
- Validate task_id exists - Validate task_id exists
- Retrieve logs from TaskManager - Retrieve logs from TaskManager
- Return TaskLogResponse format - Return TaskLogResponse format
- Handle not found errors - Handle not found errors
- [ ] T018 [US1] Add task status update WebSocket support - [x] T018 [US1] Add task status update WebSocket support
- Extend existing WebSocket infrastructure - Extend existing WebSocket infrastructure
- Broadcast status changes to /ws/tasks/{task_id}/status - Broadcast status changes to /ws/tasks/{task_id}/status
- Broadcast log updates to /ws/tasks/{task_id}/logs - Broadcast log updates to /ws/tasks/{task_id}/logs
### Frontend Implementation ### Frontend Implementation
- [ ] T019 [US1] Integrate TaskHistory component into migration page - [x] T019 [US1] Integrate TaskHistory component into migration page
- Add component to frontend/src/routes/migration/+page.svelte - Add component to frontend/src/routes/migration/+page.svelte
- Fetch tasks on page load - Fetch tasks on page load
- Handle loading state - Handle loading state
- Display error messages - Display error messages
- [ ] T020 [US1] Implement real-time status updates - [x] T020 [US1] Implement real-time status updates
- Subscribe to WebSocket channel for task updates - Subscribe to WebSocket channel for task updates
- Update task list on status change - Update task list on status change
- Add visual indicators for running tasks - Add visual indicators for running tasks
- [ ] T021 [US1] Add task list pagination - [x] T021 [US1] Add task list pagination
- Implement "Load More" button - Implement "Load More" button
- Handle offset updates - Handle offset updates
- Maintain current task list while loading more - Maintain current task list while loading more
### Testing ### Testing
- [ ] T022 [US1] Test task list retrieval - [x] T022 [US1] Test task list retrieval
- Create test migration tasks - Create test migration tasks
- Verify API returns correct format - Verify API returns correct format
- Verify pagination works - Verify pagination works
- Verify status filtering works - Verify status filtering works
- [ ] T023 [US1] Test real-time updates - [x] T023 [US1] Test real-time updates
- Start a migration - Start a migration
- Verify task appears in list - Verify task appears in list
- Verify status updates in real-time - Verify status updates in real-time
@@ -221,39 +221,39 @@ This document provides actionable, dependency-ordered tasks for implementing the
### Backend Implementation ### Backend Implementation
- [ ] T024 [P] [US2] Enhance log storage in TaskManager - [x] T024 [P] [US2] Enhance log storage in TaskManager
- Ensure logs are retained for all task states - Ensure logs are retained for all task states
- Add log context preservation - Add log context preservation
- Implement log cleanup on task retention - Implement log cleanup on task retention
### Frontend Implementation ### Frontend Implementation
- [ ] T025 [US2] Implement TaskLogViewer modal integration - [x] T025 [US2] Implement TaskLogViewer modal integration
- Add "View Logs" button to TaskHistory component - Add "View Logs" button to TaskHistory component
- Wire button to open TaskLogViewer modal - Wire button to open TaskLogViewer modal
- Pass task_id to modal - Pass task_id to modal
- Fetch logs when modal opens - Fetch logs when modal opens
- [ ] T026 [US2] Implement log display formatting - [x] T026 [US2] Implement log display formatting
- Color-code by log level (INFO=blue, WARNING=yellow, ERROR=red) - Color-code by log level (INFO=blue, WARNING=yellow, ERROR=red)
- Format timestamps nicely - Format timestamps nicely
- Display context as JSON or formatted text - Display context as JSON or formatted text
- Auto-scroll to bottom on new logs - Auto-scroll to bottom on new logs
- [ ] T027 [US2] Add log refresh functionality - [x] T027 [US2] Add log refresh functionality
- Add refresh button in modal - Add refresh button in modal
- Poll for new logs every 5 seconds while modal open - Poll for new logs every 5 seconds while modal open
- Show "new logs available" indicator - Show "new logs available" indicator
### Testing ### Testing
- [ ] T028 [US2] Test log retrieval - [x] T028 [US2] Test log retrieval
- Create task with various log entries - Create task with various log entries
- Verify logs are returned correctly - Verify logs are returned correctly
- Verify log context is preserved - Verify log context is preserved
- Test with large log files - Test with large log files
- [ ] T029 [US2] Test log viewer UI - [x] T029 [US2] Test log viewer UI
- Open logs for completed task - Open logs for completed task
- Open logs for running task - Open logs for running task
- Verify formatting and readability - Verify formatting and readability
@@ -269,19 +269,19 @@ This document provides actionable, dependency-ordered tasks for implementing the
### Backend Implementation ### Backend Implementation
- [ ] T030 [US3] Implement password error detection in MigrationPlugin - [x] T030 [US3] Implement password error detection in MigrationPlugin
- Add error pattern matching for Superset 422 errors - Add error pattern matching for Superset 422 errors
- Extract database name from error message - Extract database name from error message
- Create DatabasePasswordRequest object - Create DatabasePasswordRequest object
- Transition task to AWAITING_INPUT state - Transition task to AWAITING_INPUT state
- [ ] T031 [US3] Implement task resumption with passwords - [x] T031 [US3] Implement task resumption with passwords
- Add validation for password format - Add validation for password format
- Inject passwords into migration context - Inject passwords into migration context
- Resume task execution from failure point - Resume task execution from failure point
- Handle multiple database passwords - Handle multiple database passwords
- [ ] T032 [US3] Add task persistence for AWAITING_INPUT state - [x] T032 [US3] Add task persistence for AWAITING_INPUT state
- Persist task context and input_request - Persist task context and input_request
- Load persisted tasks on backend restart - Load persisted tasks on backend restart
- Clear persisted data on task completion - Clear persisted data on task completion
@@ -289,44 +289,44 @@ This document provides actionable, dependency-ordered tasks for implementing the
### Frontend Implementation ### Frontend Implementation
- [ ] T033 [US3] Implement password prompt detection - [x] T033 [US3] Implement password prompt detection
- Monitor task status changes - Monitor task status changes
- Detect AWAITING_INPUT state - Detect AWAITING_INPUT state
- Show notification to user - Show notification to user
- Update task list to show "Requires Input" indicator - Update task list to show "Requires Input" indicator
- [ ] T034 [US3] Wire PasswordPrompt to task resumption - [x] T034 [US3] Wire PasswordPrompt to task resumption
- Connect form submission to taskService.resumeTask() - Connect form submission to taskService.resumeTask()
- Handle success (close prompt, resume task) - Handle success (close prompt, resume task)
- Handle failure (show error, keep prompt open) - Handle failure (show error, keep prompt open)
- Support multiple database inputs - Support multiple database inputs
- [ ] T035 [US3] Add visual indicators for password-required tasks - [x] T035 [US3] Add visual indicators for password-required tasks
- Highlight tasks needing input in task list - Highlight tasks needing input in task list
- Add badge or icon - Add badge or icon
- Show count of pending inputs - Show count of pending inputs
### Testing ### Testing
- [ ] T036 [US3] Test password error detection - [x] T036 [US3] Test password error detection
- Mock Superset password error - Mock Superset password error
- Verify error is detected - Verify error is detected
- Verify task transitions to AWAITING_INPUT - Verify task transitions to AWAITING_INPUT
- Verify DatabasePasswordRequest is created - Verify DatabasePasswordRequest is created
- [ ] T037 [US3] Test password resumption - [x] T037 [US3] Test password resumption
- Provide correct password - Provide correct password
- Verify task resumes - Verify task resumes
- Verify task completes successfully - Verify task completes successfully
- Test with incorrect password (should prompt again) - Test with incorrect password (should prompt again)
- [ ] T038 [US3] Test persistence across restarts - [x] T038 [US3] Test persistence across restarts
- Create AWAITING_INPUT task - Create AWAITING_INPUT task
- Restart backend - Restart backend
- Verify task is loaded - Verify task is loaded
- Verify password prompt still works - Verify password prompt still works
- [ ] T039 [US3] Test multiple database passwords - [x] T039 [US3] Test multiple database passwords
- Create migration requiring 2+ databases - Create migration requiring 2+ databases
- Verify all databases listed in prompt - Verify all databases listed in prompt
- Verify all passwords submitted - Verify all passwords submitted
@@ -338,13 +338,13 @@ This document provides actionable, dependency-ordered tasks for implementing the
### Integration & E2E ### Integration & E2E
- [ ] T040 [P] Integrate all components on migration page - [x] T040 [P] Integrate all components on migration page
- Add TaskHistory to migration page - Add TaskHistory to migration page
- Add password prompt handling - Add password prompt handling
- Ensure WebSocket connections work - Ensure WebSocket connections work
- Test complete user flow - Test complete user flow
- [ ] T041 [P] Add loading states and error boundaries - [x] T041 [P] Add loading states and error boundaries
- Show loading spinners during API calls - Show loading spinners during API calls
- Handle API errors gracefully - Handle API errors gracefully
- Show user-friendly error messages - Show user-friendly error messages
@@ -352,13 +352,14 @@ This document provides actionable, dependency-ordered tasks for implementing the
### Configuration & Security ### Configuration & Security
- [ ] T042 [P] Add configuration options - [x] T042 [P] Add configuration options to backend/src/core/config_models.py
- Task retention days (default: 30) - Extend `GlobalSettings` with `task_retention` fields:
- Task retention limit (default: 100) - `retention_days` (default: 30)
- Pagination limits (default: 10, max: 50) - `retention_limit` (default: 100)
- Password complexity requirements - Add `pagination_limit` to settings (default: 10)
- Update `ConfigManager` to handle new fields
- [ ] T043 [P] Security review - [x] T043 [P] Security review
- Verify passwords are not logged - Verify passwords are not logged
- Verify passwords are not stored permanently - Verify passwords are not stored permanently
- Verify input validation on all endpoints - Verify input validation on all endpoints
@@ -366,12 +367,12 @@ This document provides actionable, dependency-ordered tasks for implementing the
### Documentation ### Documentation
- [ ] T044 [P] Update API documentation - [x] T044 [P] Update API documentation
- Add new endpoints to OpenAPI spec - Add new endpoints to OpenAPI spec
- Update API contract examples - Update API contract examples
- Document WebSocket channels - Document WebSocket channels
- [ ] T045 [P] Update quickstart guide - [x] T045 [P] Update quickstart guide
- Add task history section - Add task history section
- Add log viewing section - Add log viewing section
- Add password prompt section - Add password prompt section
@@ -379,31 +380,31 @@ This document provides actionable, dependency-ordered tasks for implementing the
### Testing & Quality ### Testing & Quality
- [ ] T046 [P] Write unit tests for backend - [x] T046 [P] Write unit tests for backend
- Test TaskManager extensions - Test TaskManager extensions
- Test MigrationPlugin error detection - Test MigrationPlugin error detection
- Test API endpoints - Test API endpoints
- Test password validation - Test password validation
- [ ] T047 [P] Write unit tests for frontend - [x] T047 [P] Write unit tests for frontend
- Test taskService functions - Test taskService functions
- Test TaskHistory component - Test TaskHistory component
- Test TaskLogViewer component - Test TaskLogViewer component
- Test PasswordPrompt component - Test PasswordPrompt component
- [ ] T048 [P] Write integration tests - [x] T048 [P] Write integration tests
- Test complete password flow - Test complete password flow
- Test task persistence - Test task persistence
- Test WebSocket updates - Test WebSocket updates
- Test error recovery - Test error recovery
- [ ] T049 [P] Run full test suite - [x] T049 [P] Run full test suite
- Execute pytest for backend - Execute pytest for backend
- Execute frontend tests - Execute frontend tests
- Fix any failing tests - Fix any failing tests
- Verify all acceptance criteria met - Verify all acceptance criteria met
- [ ] T050 [P] Final validation - [x] T050 [P] Final validation
- Verify all user stories work independently - Verify all user stories work independently
- Verify all acceptance scenarios pass - Verify all acceptance scenarios pass
- Check performance (pagination, real-time updates) - Check performance (pagination, real-time updates)

0
tests/output.txt Normal file
View File

0
tests/output_safe.txt Normal file
View File