feat(tool-configs): add frontend tool config management page
- Create ToolConfigsPage with tool type selector, config list, and add/edit form - Support both env and file config types - Add route /tool-configs and navigation item - Update API client with tool config endpoints - Build passes successfully
This commit is contained in:
@@ -18,6 +18,7 @@ from src.auth.dependencies import get_current_user_id
|
||||
from src.auth.dependencies import get_db_session
|
||||
from src.models.git_repository import GitRepository
|
||||
from src.models.project import Project
|
||||
from src.models.tool_config import ToolConfig
|
||||
from src.models.tool_instance import ToolInstance
|
||||
from src.models.tool_type import ToolType
|
||||
from src.models.user import User
|
||||
@@ -31,6 +32,8 @@ from src.services.docker import (
|
||||
get_container_status,
|
||||
render_compose_template,
|
||||
write_compose_file,
|
||||
write_config_files,
|
||||
write_env_file,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/projects", tags=["tool-instances"])
|
||||
@@ -342,9 +345,39 @@ async def start_instance(
|
||||
instance.status = "building"
|
||||
await session.commit()
|
||||
|
||||
# Execute docker compose up
|
||||
# Fetch tool configs for this tool type
|
||||
env_vars = {}
|
||||
config_files = {}
|
||||
|
||||
config_query = select(ToolConfig).where(
|
||||
ToolConfig.user_id == user_id,
|
||||
ToolConfig.tool_type_id == instance.tool_type_id,
|
||||
).where(
|
||||
(ToolConfig.project_id == project_id) | (ToolConfig.project_id.is_(None))
|
||||
)
|
||||
|
||||
config_result = await session.execute(config_query)
|
||||
configs = config_result.scalars().all()
|
||||
|
||||
for config in configs:
|
||||
if config.config_type == "env":
|
||||
env_vars[config.key] = config.value
|
||||
elif config.config_type == "file" and config.file_path:
|
||||
config_files[config.file_path] = config.value
|
||||
|
||||
# Write env file and config files
|
||||
instance_dir = os.path.dirname(instance.compose_path)
|
||||
env_file_path = None
|
||||
|
||||
if env_vars:
|
||||
env_file_path = write_env_file(instance_dir, env_vars)
|
||||
|
||||
if config_files:
|
||||
write_config_files(instance_dir, config_files)
|
||||
|
||||
# Execute docker compose up with env file
|
||||
returncode, stdout, stderr = execute_compose_command(
|
||||
instance.compose_path, "up"
|
||||
instance.compose_path, "up", env_file=env_file_path
|
||||
)
|
||||
|
||||
if returncode != 0:
|
||||
|
||||
@@ -56,8 +56,44 @@ def write_compose_file(instance_dir: str, content: str) -> str:
|
||||
return str(compose_path)
|
||||
|
||||
|
||||
def write_env_file(instance_dir: str, env_vars: dict[str, str]) -> str:
|
||||
"""Write environment variables to a .env file.
|
||||
|
||||
Args:
|
||||
instance_dir: Path to instance directory
|
||||
env_vars: Dictionary of env var names to values
|
||||
|
||||
Returns:
|
||||
Path to the env file
|
||||
"""
|
||||
env_path = Path(instance_dir) / ".env"
|
||||
lines = [f'{key}="{value}"' for key, value in env_vars.items()]
|
||||
env_path.write_text("\n".join(lines) + "\n")
|
||||
return str(env_path)
|
||||
|
||||
|
||||
def write_config_files(instance_dir: str, files: dict[str, str]) -> None:
|
||||
"""Write config files to the instance directory.
|
||||
|
||||
Args:
|
||||
instance_dir: Path to instance directory
|
||||
files: Dictionary of file paths (relative to instance dir) to content
|
||||
"""
|
||||
instance_path = Path(instance_dir)
|
||||
for file_path, content in files.items():
|
||||
# Ensure the path is within the instance directory (security)
|
||||
full_path = instance_path / file_path
|
||||
try:
|
||||
full_path.resolve().relative_to(instance_path.resolve())
|
||||
except ValueError:
|
||||
raise ValueError(f"File path '{file_path}' escapes instance directory")
|
||||
|
||||
full_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
full_path.write_text(content)
|
||||
|
||||
|
||||
def execute_compose_command(
|
||||
compose_path: str, action: str, timeout: int = 60
|
||||
compose_path: str, action: str, timeout: int = 60, env_file: str | None = None
|
||||
) -> tuple[int, str, str]:
|
||||
"""Execute a docker compose command.
|
||||
|
||||
@@ -65,6 +101,7 @@ def execute_compose_command(
|
||||
compose_path: Path to docker-compose.yml
|
||||
action: The compose action (up, down, start, stop, restart)
|
||||
timeout: Command timeout in seconds
|
||||
env_file: Optional path to .env file for environment variables
|
||||
|
||||
Returns:
|
||||
Tuple of (returncode, stdout, stderr)
|
||||
@@ -72,6 +109,9 @@ def execute_compose_command(
|
||||
instance_dir = Path(compose_path).parent
|
||||
|
||||
cmd = ["docker", "compose", "-f", compose_path]
|
||||
|
||||
if env_file:
|
||||
cmd.extend(["--env-file", env_file])
|
||||
|
||||
if action == "up":
|
||||
cmd.extend(["up", "-d"])
|
||||
|
||||
Reference in New Issue
Block a user