Compare commits
3 Commits
4c9d554432
...
008-migrat
| Author | SHA1 | Date | |
|---|---|---|---|
| 45c077b928 | |||
| 9ed3a5992d | |||
| a032fe8457 |
25
.kilocodemodes
Normal file
25
.kilocodemodes
Normal 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.
@@ -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]
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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]
|
||||||
@@ -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}")
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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]
|
||||||
@@ -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()
|
||||||
|
|
||||||
cursor.execute("SELECT id, status, created_at, input_request, context FROM persistent_tasks")
|
# 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")
|
||||||
|
|
||||||
rows = cursor.fetchall()
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
loaded_tasks = []
|
loaded_tasks = []
|
||||||
for row in rows:
|
for row in rows:
|
||||||
task_id, status, created_at, input_request_json, context_json = row
|
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
|
||||||
|
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]
|
||||||
@@ -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]
|
||||||
@@ -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)
|
||||||
|
# The error message from Superset is often a JSON string inside a string.
|
||||||
|
# We need to robustly detect the password requirement.
|
||||||
|
# Typical error: "Error importing dashboard: databases/PostgreSQL.yaml: {'_schema': ['Must provide a password for the database']}"
|
||||||
|
|
||||||
if "Must provide a password for the database" in error_msg:
|
if "Must provide a password for the database" in error_msg:
|
||||||
# Extract database name (assuming format: "Must provide a password for the database 'PostgreSQL'")
|
# Extract database name
|
||||||
|
# Try to find "databases/DBNAME.yaml" pattern
|
||||||
import re
|
import re
|
||||||
match = re.search(r"database '([^']+)'", error_msg)
|
db_name = "unknown"
|
||||||
db_name = match.group(1) if match else "unknown"
|
match = re.search(r"databases/([^.]+)\.yaml", error_msg)
|
||||||
|
if match:
|
||||||
# Get task manager
|
db_name = match.group(1)
|
||||||
from ..dependencies import get_task_manager
|
else:
|
||||||
tm = get_task_manager()
|
# Fallback: try to find 'database 'NAME'' pattern
|
||||||
task_id = params.get("_task_id")
|
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)
|
||||||
|
|||||||
242
frontend/.svelte-kit/ambient.d.ts
vendored
242
frontend/.svelte-kit/ambient.d.ts
vendored
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export const matchers = {};
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import * as universal from "../../../../src/routes/+layout.ts";
|
|
||||||
export { universal };
|
|
||||||
export { default as component } from "../../../../src/routes/+layout.svelte";
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { default as component } from "../../../../src/routes/+error.svelte";
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import * as universal from "../../../../src/routes/+page.ts";
|
|
||||||
export { universal };
|
|
||||||
export { default as component } from "../../../../src/routes/+page.svelte";
|
|
||||||
@@ -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";
|
|
||||||
@@ -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';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export const matchers = {};
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import * as universal from "../../../../src/routes/+layout.ts";
|
|
||||||
export { universal };
|
|
||||||
export { default as component } from "../../../../src/routes/+layout.svelte";
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { default as component } from "../../../../src/routes/+error.svelte";
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import * as universal from "../../../../src/routes/+page.ts";
|
|
||||||
export { universal };
|
|
||||||
export { default as component } from "../../../../src/routes/+page.svelte";
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { default as component } from "../../../../src/routes/migration/+page.svelte";
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import { asClassComponent } from 'svelte/legacy';
|
|
||||||
import Root from './root.svelte';
|
|
||||||
export default asClassComponent(Root);
|
|
||||||
@@ -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}
|
|
||||||
@@ -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 };
|
|
||||||
44
frontend/.svelte-kit/non-ambient.d.ts
vendored
44
frontend/.svelte-kit/non-ambient.d.ts
vendored
@@ -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 & {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":"1766262590857"}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export const env={"PUBLIC_WS_URL":"ws://localhost:8000"}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
};
|
|
||||||
@@ -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
|
|
||||||
};
|
|
||||||
@@ -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
|
|
||||||
};
|
|
||||||
@@ -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
|
|
||||||
};
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
const BROWSER = false;
|
|
||||||
export {
|
|
||||||
BROWSER as B
|
|
||||||
};
|
|
||||||
@@ -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
@@ -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
|
|
||||||
};
|
|
||||||
@@ -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
|
|
||||||
};
|
|
||||||
@@ -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
|
|
||||||
};
|
|
||||||
@@ -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
|
|
||||||
};
|
|
||||||
@@ -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
|
|
||||||
};
|
|
||||||
@@ -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
|
|
||||||
};
|
|
||||||
@@ -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
|
|
||||||
};
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
const ssr = false;
|
|
||||||
const prerender = false;
|
|
||||||
export {
|
|
||||||
prerender,
|
|
||||||
ssr
|
|
||||||
};
|
|
||||||
@@ -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
|
|
||||||
};
|
|
||||||
@@ -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
|
|
||||||
};
|
|
||||||
@@ -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
|
|
||||||
};
|
|
||||||
@@ -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
@@ -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
|
|
||||||
};
|
|
||||||
@@ -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: {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
@@ -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: {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
@@ -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 = [];
|
|
||||||
@@ -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 = [];
|
|
||||||
@@ -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 = [];
|
|
||||||
@@ -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 = [];
|
|
||||||
@@ -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
|
|
||||||
};
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -20,6 +20,13 @@
|
|||||||
const res = await fetch('/api/tasks?limit=10');
|
const res = await fetch('/api/tasks?limit=10');
|
||||||
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) {
|
||||||
@@ -35,8 +42,37 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectTask(task) {
|
async function clearTasks(status = null) {
|
||||||
selectedTask.set(task);
|
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);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to fetch full task details:", e);
|
||||||
|
selectedTask.set(task);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStatusColor(status) {
|
function getStatusColor(status) {
|
||||||
@@ -65,12 +101,29 @@
|
|||||||
<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>
|
||||||
<button
|
<div class="flex space-x-4 items-center">
|
||||||
on:click={fetchTasks}
|
<div class="relative inline-block text-left group">
|
||||||
class="text-sm text-indigo-600 hover:text-indigo-900 focus:outline-none"
|
<button class="text-sm text-red-600 hover:text-red-900 focus:outline-none flex items-center py-2">
|
||||||
>
|
Clear Tasks
|
||||||
Refresh
|
<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>
|
</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
|
||||||
|
on:click={fetchTasks}
|
||||||
|
class="text-sm text-indigo-600 hover:text-indigo-900 focus:outline-none"
|
||||||
|
>
|
||||||
|
Refresh
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if loading && tasks.length === 0}
|
{#if loading && tasks.length === 0}
|
||||||
|
|||||||
153
frontend/src/components/TaskLogViewer.svelte
Normal file
153
frontend/src/components/TaskLogViewer.svelte
Normal 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">​</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] -->
|
||||||
@@ -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';
|
||||||
taskLogs.set([]);
|
|
||||||
|
// 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([]);
|
||||||
|
}
|
||||||
|
|
||||||
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>
|
||||||
|
|||||||
@@ -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,90 +277,119 @@
|
|||||||
<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 loading}
|
{#if $selectedTask}
|
||||||
<p>Loading environments...</p>
|
<div class="mt-6">
|
||||||
{:else if error}
|
<TaskRunner />
|
||||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
|
<button
|
||||||
{error}
|
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>
|
</div>
|
||||||
{/if}
|
{:else}
|
||||||
|
{#if loading}
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
|
<p>Loading environments...</p>
|
||||||
<EnvSelector
|
{:else if error}
|
||||||
label="Source Environment"
|
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
|
||||||
bind:selectedId={sourceEnvId}
|
{error}
|
||||||
{environments}
|
</div>
|
||||||
/>
|
|
||||||
<EnvSelector
|
|
||||||
label="Target Environment"
|
|
||||||
bind:selectedId={targetEnvId}
|
|
||||||
{environments}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- [DEF:DashboardSelectionSection] -->
|
|
||||||
<div class="mb-8">
|
|
||||||
<h2 class="text-lg font-medium mb-4">Select Dashboards</h2>
|
|
||||||
|
|
||||||
{#if sourceEnvId}
|
|
||||||
<DashboardGrid
|
|
||||||
{dashboards}
|
|
||||||
bind:selectedIds={selectedDashboardIds}
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<p class="text-gray-500 italic">Select a source environment to view dashboards.</p>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
|
||||||
<!-- [/DEF:DashboardSelectionSection] -->
|
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
|
||||||
|
<EnvSelector
|
||||||
|
label="Source Environment"
|
||||||
|
bind:selectedId={sourceEnvId}
|
||||||
|
{environments}
|
||||||
|
/>
|
||||||
|
<EnvSelector
|
||||||
|
label="Target Environment"
|
||||||
|
bind:selectedId={targetEnvId}
|
||||||
|
{environments}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center mb-4">
|
<!-- [DEF:DashboardSelectionSection] -->
|
||||||
<input
|
<div class="mb-8">
|
||||||
id="replace-db"
|
<h2 class="text-lg font-medium mb-4">Select Dashboards</h2>
|
||||||
type="checkbox"
|
|
||||||
bind:checked={replaceDb}
|
{#if sourceEnvId}
|
||||||
on:change={() => { if (replaceDb && sourceDatabases.length === 0) fetchDatabases(); }}
|
<DashboardGrid
|
||||||
class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded"
|
{dashboards}
|
||||||
/>
|
bind:selectedIds={selectedDashboardIds}
|
||||||
<label for="replace-db" class="ml-2 block text-sm text-gray-900">
|
|
||||||
Replace Database (Apply Mappings)
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if replaceDb}
|
|
||||||
<div class="mb-8 p-4 border rounded-md bg-gray-50">
|
|
||||||
<h3 class="text-md font-medium mb-4">Database Mappings</h3>
|
|
||||||
{#if fetchingDbs}
|
|
||||||
<p>Loading databases and suggestions...</p>
|
|
||||||
{:else if sourceDatabases.length > 0}
|
|
||||||
<MappingTable
|
|
||||||
{sourceDatabases}
|
|
||||||
{targetDatabases}
|
|
||||||
{mappings}
|
|
||||||
{suggestions}
|
|
||||||
on:update={handleMappingUpdate}
|
|
||||||
/>
|
/>
|
||||||
{:else if sourceEnvId && targetEnvId}
|
{:else}
|
||||||
<button
|
<p class="text-gray-500 italic">Select a source environment to view dashboards.</p>
|
||||||
on:click={fetchDatabases}
|
|
||||||
class="text-indigo-600 hover:text-indigo-500 text-sm font-medium"
|
|
||||||
>
|
|
||||||
Refresh Databases & Suggestions
|
|
||||||
</button>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
<!-- [/DEF:DashboardSelectionSection] -->
|
||||||
|
|
||||||
<button
|
|
||||||
on:click={startMigration}
|
<div class="flex items-center mb-4">
|
||||||
disabled={!sourceEnvId || !targetEnvId || sourceEnvId === targetEnvId || selectedDashboardIds.length === 0}
|
<input
|
||||||
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:bg-gray-400"
|
id="replace-db"
|
||||||
>
|
type="checkbox"
|
||||||
Start Migration
|
bind:checked={replaceDb}
|
||||||
</button>
|
on:change={() => { if (replaceDb && sourceDatabases.length === 0) fetchDatabases(); }}
|
||||||
|
class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded"
|
||||||
|
/>
|
||||||
|
<label for="replace-db" class="ml-2 block text-sm text-gray-900">
|
||||||
|
Replace Database (Apply Mappings)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if replaceDb}
|
||||||
|
<div class="mb-8 p-4 border rounded-md bg-gray-50">
|
||||||
|
<h3 class="text-md font-medium mb-4">Database Mappings</h3>
|
||||||
|
{#if fetchingDbs}
|
||||||
|
<p>Loading databases and suggestions...</p>
|
||||||
|
{:else if sourceDatabases.length > 0}
|
||||||
|
<MappingTable
|
||||||
|
{sourceDatabases}
|
||||||
|
{targetDatabases}
|
||||||
|
{mappings}
|
||||||
|
{suggestions}
|
||||||
|
on:update={handleMappingUpdate}
|
||||||
|
/>
|
||||||
|
{:else if sourceEnvId && targetEnvId}
|
||||||
|
<button
|
||||||
|
on:click={fetchDatabases}
|
||||||
|
class="text-indigo-600 hover:text-indigo-500 text-sm font-medium"
|
||||||
|
>
|
||||||
|
Refresh Databases & Suggestions
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<button
|
||||||
|
on:click={startMigration}
|
||||||
|
disabled={!sourceEnvId || !targetEnvId || sourceEnvId === targetEnvId || selectedDashboardIds.length === 0}
|
||||||
|
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:bg-gray-400"
|
||||||
|
>
|
||||||
|
Start Migration
|
||||||
|
</button>
|
||||||
|
{/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>
|
||||||
|
|||||||
120
frontend/src/services/taskService.js
Normal file
120
frontend/src/services/taskService.js
Normal 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}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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`
|
||||||
|
|||||||
@@ -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
0
tests/output.txt
Normal file
0
tests/output_safe.txt
Normal file
0
tests/output_safe.txt
Normal file
Reference in New Issue
Block a user