diff --git a/apps/api/alembic/versions/0010_tool_type_default_port.py b/apps/api/alembic/versions/0010_tool_type_default_port.py new file mode 100644 index 0000000..7d4be26 --- /dev/null +++ b/apps/api/alembic/versions/0010_tool_type_default_port.py @@ -0,0 +1,28 @@ +"""add default_port to tool_types + +Revision ID: 0010_tool_type_default_port +Revises: 0009_tool_configs +Create Date: 2026-05-20 10:00:00.000000 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision: str = "0010_tool_type_default_port" +down_revision: Union[str, None] = "0009_tool_configs" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.add_column( + "tool_types", + sa.Column("default_port", sa.Integer(), nullable=True) + ) + + +def downgrade() -> None: + op.drop_column("tool_types", "default_port") diff --git a/apps/api/src/api/instance_proxy.py b/apps/api/src/api/instance_proxy.py index 106bd3b..b629abf 100644 --- a/apps/api/src/api/instance_proxy.py +++ b/apps/api/src/api/instance_proxy.py @@ -10,6 +10,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from src.auth.dependencies import get_current_user_id, get_db_session from src.models.tool_instance import ToolInstance +from src.models.tool_type import ToolType logger = logging.getLogger(__name__) @@ -43,8 +44,12 @@ async def _proxy_request( detail="instance is not running", ) - # Build target URL - target_url = f"http://{instance.container_name}:{instance.port}" + # Get the tool type to find the internal port + tool_type = await session.get(ToolType, instance.tool_type_id) + internal_port = tool_type.default_port if tool_type and tool_type.default_port else instance.port + + # Build target URL using internal port + target_url = f"http://{instance.container_name}:{internal_port}" if path: target_url += f"/{path}" diff --git a/apps/api/src/api/tool_types.py b/apps/api/src/api/tool_types.py index b16e848..e4cf7c5 100644 --- a/apps/api/src/api/tool_types.py +++ b/apps/api/src/api/tool_types.py @@ -37,6 +37,7 @@ class ToolTypeCreate(BaseModel): name: str display_name: str description: str | None = None + default_port: int | None = None compose_template: str required_variables: list[str] = [] @@ -117,6 +118,7 @@ class ToolTypeResponse(BaseModel): description: str | None category: str interfaces: list[str] + default_port: int | None compose_template: str required_variables: list[str] is_builtin: bool @@ -159,6 +161,7 @@ async def create_tool_type( name=data.name, display_name=data.display_name, description=data.description, + default_port=data.default_port, compose_template=data.compose_template, required_variables=data.required_variables, is_builtin=False, diff --git a/apps/api/src/main.py b/apps/api/src/main.py index dcd7002..d1ee675 100644 --- a/apps/api/src/main.py +++ b/apps/api/src/main.py @@ -123,6 +123,7 @@ services: ports: - "8443:8443" restart: unless-stopped""", + "default_port": 8443, "required_variables": ["REPO_PATH", "TOOL_NAME"], }, { @@ -131,6 +132,7 @@ services: "description": "Jupyter Lab for interactive development", "category": "notebook", "interfaces": ["web"], + "default_port": 8888, "compose_template": """version: "3.8" services: jupyter: @@ -187,6 +189,7 @@ volumes: interfaces=tool_data["interfaces"], compose_template=tool_data["compose_template"], required_variables=tool_data["required_variables"], + default_port=tool_data.get("default_port"), is_builtin=True, ) session.add(tool_type) diff --git a/apps/api/src/models/tool_type.py b/apps/api/src/models/tool_type.py index 5680604..e0c40fe 100644 --- a/apps/api/src/models/tool_type.py +++ b/apps/api/src/models/tool_type.py @@ -19,6 +19,7 @@ class ToolType(UUIDPrimaryKeyMixin, TimestampMixin, Base): description: Mapped[str | None] = mapped_column(Text, nullable=True) category: Mapped[str] = mapped_column(String(50), nullable=False, default="other") interfaces: Mapped[list[str]] = mapped_column(JSON, default=list, nullable=False) + default_port: Mapped[int | None] = mapped_column(nullable=True) compose_template: Mapped[str] = mapped_column(Text, nullable=False) required_variables: Mapped[list[str]] = mapped_column(JSON, default=list, nullable=False) is_builtin: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) diff --git a/apps/web/src/api/tool_types.ts b/apps/web/src/api/tool_types.ts index adbd8db..64aa104 100644 --- a/apps/web/src/api/tool_types.ts +++ b/apps/web/src/api/tool_types.ts @@ -7,6 +7,7 @@ export interface ToolType { description: string | null; category: string; interfaces: string[]; + default_port: number | null; compose_template: string; required_variables: string[]; is_builtin: boolean;