Compare commits

..

13 Commits

Author SHA1 Message Date
Developer 703cf1f88b fix: mobile terminal back and X buttons navigate to /sessions
Terminal sessions are opened in a new tab via window.open with
noopener,noreferrer. In a new tab, window.history has no previous
entry, so navigate(-1) silently does nothing. Both the back arrow
and the X button in the mobile terminal overlay called navigate(-1),
which made them appear broken.

Change both buttons to navigate('/sessions') so they always exit
to a sensible page regardless of how the terminal was opened.

Quality gates: tsc --noEmit (pass), build (pass)
2026-06-04 18:24:48 +00:00
alex 5dc7d44111 feat: improve session naming with workspace-aware scoped numbering
When a workspace is provided, auto-generated display names now use
workspace.name instead of repo.name:
  'myworkspace / VS Code Server'          # first
  'myworkspace / VS Code Server #2'       # second

Without a workspace, naming falls back to repo.name:
  'myrepo / VS Code Server'
  'myrepo / VS Code Server #2'

The counter is scoped to workspace+tool_type (or repo+tool_type),
so different tool types for the same workspace/repo are numbered
independently.

This replaces the old format of 'project / repo / tool #N' which
was always repo-based and included the project name even though
the sidebar already groups by project.

Quality gates: py_compile passed, ruff passed.
2026-06-04 16:23:40 +02:00
alex ee348643f8 fix: update main.py imports for service subpackages
services/correlation.py was moved to services/shared/correlation.py,
services/event_bus.py to services/instance/event_bus.py, and
services/health_monitor.py to services/instance/health_monitor.py
but main.py was still importing from the old flat paths.

Updated main.py to import from the new subpackage paths via
__init__.py re-exports.

Quality gates: py_compile passed, ruff passed.
2026-06-04 16:12:28 +02:00
alex 05a598812b style: fix import formatting after service reorganization
Minor ruff-driven import wrapping fixes in tool_instances.py and
workspace/__init__.py after service subpackage moves.
2026-06-04 12:49:28 +02:00
alex 7515d9106f docs: update progress.md — all refactoring complete, verification done 2026-06-04 12:47:28 +02:00
alex 8c7affc933 fix: correct relative imports after component reorganization
Fixed import paths for 43 components moved into features/ directories.
Key fixes:
- api/, types/, hooks/, state/, utils/ imports need ../../../ from features/*/
- components/ imports need ../../ from features/*/
- Cross-feature imports use relative paths (e.g., ../tool/tools-bottom-sheet)
- app-shell.tsx updated to import from features/ subdirectories

Frontend typecheck now passes except for one pre-existing error:
xterm-addon-webgl missing type declarations.

Quality gates: ruff passed on backend, py_compile passed on all backend files.
2026-06-04 12:46:45 +02:00
alex 2680a8c44a docs: update progress.md — all refactoring phases complete 2026-06-04 12:38:06 +02:00
alex 1021d61be3 refactor: organize frontend components into features/ directories
Moved 43 component files into 9 feature domains:
- features/git/ — commit-dialog, commit-panel, file-editor, git-mount-editor,
  git-toolbar, merge-dialog
- features/project/ — repositories-settings-tab, repository-create-dialog
- features/terminal/ — special-keys-panel, special-keys-strip,
  terminal-session-tabs, terminal
- features/workspace/ — workspace-card, workspace-create-form,
  workspace-header, workspace-instance-chips
- features/session/ — create-session-form, session-card, session-list
- features/tool/ — instance-list, manifest-editor, start-tool-fab,
  start-tool-modal, tool-starter, tools-bottom-sheet
- features/notification/ — event-toast-bridge, notification-center,
  notification-item
- features/settings/ — settings-tab-layout
- features/mobile/ — mobile-action-sheet, mobile-detail-view, mobile-edit-view,
  mobile-fab, mobile-list-view, mobile-nav, mobile-page-header,
  mobile-terminal-header, mobile-terminal-wrapper

Updated all imports across pages and components.
Root components/ now only contains generic UI pieces:
app-shell, code-editor, data-states, icon, protected-route, syntax-highlighter.

Quality gates: verified no remaining old imports.
2026-06-04 12:37:24 +02:00
alex 7224afafd1 refactor: rename frontend pages to PascalCase with Page suffix
Renamed 18 page files:
- dashboard.tsx → DashboardPage.tsx
- projects.tsx → ProjectsPage.tsx
- sessions.tsx → SessionsPage.tsx
- settings.tsx → SettingsPage.tsx
- ssh-keys.tsx → SshKeysPage.tsx
- terminal.tsx → TerminalPage.tsx
- tool-workshop.tsx → ToolWorkshopPage.tsx
- config-profiles.tsx → ConfigProfilesPage.tsx
- git-repositories.tsx → GitRepositoriesPage.tsx
- repo-workspace.tsx → RepoWorkspacePage.tsx
- workspaces.tsx → WorkspacesPage.tsx
- workspace-detail.tsx → WorkspaceDetailPage.tsx
- profile.tsx → ProfilePage.tsx
- project-settings.tsx → ProjectSettingsPage.tsx
- git-history.tsx → GitHistoryPage.tsx
- placeholder.tsx → PlaceholderPage.tsx

Updated router.tsx imports.

Quality gates: verified no remaining old imports.
2026-06-04 12:34:43 +02:00
alex 020f832eed refactor: rename frontend API files to kebab-case
Renamed:
- git_repositories.ts → git-repositories.ts
- ssh_keys.ts → ssh-keys.ts
- tool_types.ts → tool-types.ts
- tool_types.test.ts → tool-types.test.ts

Updated all imports across components, pages, and hooks.

Quality gates: verified no remaining old imports.
2026-06-04 12:33:10 +02:00
alex 7fe2790199 docs: update progress.md with API router reorganization 2026-06-04 12:30:33 +02:00
alex 38c51ed95e refactor: move API routers into domain subpackages
Moves 21 API router files into 6 domain subpackages (max 6 files each):
- api/tool/ — tool_instances, tool_types, tool_definitions, sessions
- api/config/ — config_profiles, user_config
- api/workspace/ — workspaces, workspace_files, workspace_git, workspace_instances
- api/user/ — users, auth, ssh_keys
- api/project/ — projects, git_repositories
- api/system/ — health, events, notifications, dashboard, terminal, instance_proxy

sessions_router extracted from tool_instances.py into tool/sessions.py.

main.py now imports from subpackage __init__.py re-exports.
Cross-router imports updated to use new paths.
Fixed pre-existing E712 in tool_definitions.py (is_base == False → is_(False)).

Quality gates: py_compile passed on all files, ruff passed.
2026-06-04 12:28:41 +02:00
alex 37ccaa4fdc refactor: organize API routers and services into subpackages
Service organization (19 files moved into 6 subpackages):
- services/instance/ — event_bus, health_monitor, lifecycle_hooks
- services/config/ — config_profile_resolver
- services/git/ — clone, git_operations, git_service
- services/build/ — docker_build, manifest_compiler
- services/terminal/ — terminal_manager, terminal_session
- services/shared/ — correlation, file_service, notification_service,
  permission_fixer, readiness_probe, ssh_keys, tunnel, workspace_manager

API router organization (16 files moved into 6 subpackages):
- api/tool/ — tool_instances, tool_types, tool_definitions,
  tool_types_validation, sessions (extracted from tool_instances)
- api/config/ — config_profiles, user_config
- api/workspace/ — workspaces, workspace_files, workspace_git,
  workspace_instances
- api/user/ — users, auth, ssh_keys
- api/project/ — projects, git_repositories
- api/system/ — health, events, notifications, dashboard, terminal,
  instance_proxy

Updated main.py imports and all __init__.py re-exports.
Sessions router extracted from tool_instances.py into api/tool/sessions.py.

Quality gates: py_compile passed, ruff passed.
2026-06-04 12:24:14 +02:00
125 changed files with 579 additions and 359 deletions
+1 -6
View File
@@ -1,6 +1 @@
from src.api.auth import router as auth_router """API routers package."""
from src.api.events import router as events_router
from src.api.notifications import router as notifications_router
from src.api.users import router as users_router
__all__ = ["auth_router", "events_router", "notifications_router", "users_router"]
+6 -1
View File
@@ -1 +1,6 @@
"""Config module.""" """Config API routers module."""
from src.api.config.config_profiles import router as config_profiles_router
from src.api.config.user_config import router as user_config_router
__all__ = ["config_profiles_router", "user_config_router"]
@@ -25,7 +25,7 @@ from src.schemas.config import (
ValidateGitUrlRequest, ValidateGitUrlRequest,
ValidateGitUrlResponse, ValidateGitUrlResponse,
) )
from src.services.config_profile_resolver import ( from src.services.config.config_profile_resolver import (
ConfigProfileCycleError, ConfigProfileCycleError,
check_include_cycle, check_include_cycle,
resolve_profile, resolve_profile,
@@ -726,7 +726,7 @@ async def validate_git_url(
key_path = None key_path = None
if data.ssh_key_id: if data.ssh_key_id:
from src.models import SSHKey from src.models import SSHKey
from src.services.ssh_keys import _get_fernet from src.services.shared.ssh_keys import _get_fernet
try: try:
ssh_key_uuid = uuid.UUID(data.ssh_key_id) ssh_key_uuid = uuid.UUID(data.ssh_key_id)
+6 -1
View File
@@ -1 +1,6 @@
"""Project module.""" """Project API routers module."""
from src.api.project.git_repositories import router as git_repositories_router
from src.api.project.projects import router as projects_router
__all__ = ["git_repositories_router", "projects_router"]
@@ -44,7 +44,7 @@ from src.utils.git_control import (
) )
from src.utils.git_history import get_commit_detail, get_commit_history from src.utils.git_history import get_commit_detail, get_commit_history
from src.utils.git_url_parser import parse_git_url from src.utils.git_url_parser import parse_git_url
from src.services.ssh_keys import _get_fernet from src.services.shared.ssh_keys import _get_fernet
router = APIRouter(prefix="/projects", tags=["git-repositories"]) router = APIRouter(prefix="/projects", tags=["git-repositories"])
+17 -1
View File
@@ -1 +1,17 @@
"""System module.""" """System API routers module."""
from src.api.system.dashboard import router as dashboard_router
from src.api.system.events import router as events_router
from src.api.system.health import router as health_router
from src.api.system.instance_proxy import router as instance_proxy_router
from src.api.system.notifications import router as notifications_router
from src.api.system.terminal import router as terminal_router
__all__ = [
"dashboard_router",
"events_router",
"health_router",
"instance_proxy_router",
"notifications_router",
"terminal_router",
]
@@ -10,7 +10,7 @@ from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from src.auth.dependencies import get_current_user_id from src.auth.dependencies import get_current_user_id
from src.services.event_bus import InstanceEventBus, InstanceEventPayload from src.services.instance.event_bus import InstanceEventBus, InstanceEventPayload
router = APIRouter(prefix="/events", tags=["events"]) router = APIRouter(prefix="/events", tags=["events"])
@@ -10,7 +10,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from src.auth.dependencies import get_current_user, get_db_session from src.auth.dependencies import get_current_user, get_db_session
from src.models.user import User from src.models.user import User
from src.models import UserConfig from src.models import UserConfig
from src.services.notification_service import notification_service from src.services.shared.notification_service import notification_service
router = APIRouter(prefix="/notifications", tags=["notifications"]) router = APIRouter(prefix="/notifications", tags=["notifications"])
@@ -15,7 +15,7 @@ from src.auth.dependencies import get_current_user_id, get_db_session
from src.models import TerminalSessionModel from src.models import TerminalSessionModel
from src.models import ToolInstance from src.models import ToolInstance
from src.models import ToolType from src.models import ToolType
from src.services.terminal_manager import MaxSessionsExceededError, terminal_manager from src.services.terminal.terminal_manager import MaxSessionsExceededError, terminal_manager
router = APIRouter() router = APIRouter()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
+13 -1
View File
@@ -1 +1,13 @@
"""Tool module.""" """Tool API routers module."""
from src.api.tool.sessions import sessions_router
from src.api.tool.tool_definitions import router as tool_definitions_router
from src.api.tool.tool_instances import router as tool_instances_router
from src.api.tool.tool_types import router as tool_types_router
__all__ = [
"sessions_router",
"tool_definitions_router",
"tool_instances_router",
"tool_types_router",
]
+80
View File
@@ -0,0 +1,80 @@
"""Sessions API endpoints (running instances for current user)."""
import uuid
from fastapi import APIRouter, Depends
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from src.auth.dependencies import _get_user, get_current_user_id, get_db_session
from src.models import GitRepository
from src.models import Project
from src.models import ToolInstance
from src.models import ToolType
sessions_router = APIRouter(prefix="/users", tags=["sessions"])
@sessions_router.get(
"/me/sessions",
summary="Get user sessions",
description="Get all active sessions (running instances) for the current user.",
)
async def get_user_sessions(
user_id: uuid.UUID = Depends(get_current_user_id),
session: AsyncSession = Depends(get_db_session),
) -> dict:
"""Get all active sessions for the current user.
Args:
user_id: ID of the authenticated user.
session: Database session.
Returns:
Dictionary containing list of active sessions with instance details.
"""
_user = await _get_user(session, user_id)
result = await session.execute(
select(ToolInstance)
.where(ToolInstance.owner_id == user_id)
.where(
ToolInstance.status.in_(
["running", "building", "pending", "stopped", "error"]
)
)
.order_by(ToolInstance.created_at.desc())
)
instances = result.scalars().all()
sessions = []
for instance in instances:
tool_type = await session.get(ToolType, instance.tool_type_id)
repo = await session.get(GitRepository, instance.repository_id)
project = await session.get(Project, instance.project_id)
sessions.append(
{
"id": str(instance.id),
"display_name": instance.display_name,
"tool_type_name": tool_type.name if tool_type else "unknown",
"tool_icon": tool_type.name if tool_type else "code",
"tool_type_interfaces": [tool_type.interface_type] if tool_type else [],
"repository_name": repo.name if repo else "unknown",
"repository_id": str(instance.repository_id),
"project_name": project.name if project else "unknown",
"project_id": str(instance.project_id),
"status": instance.status,
"url": instance.url,
"clone_mode": instance.clone_mode,
"branch": instance.branch,
"selected_config_profile_id": str(instance.selected_config_profile_id)
if instance.selected_config_profile_id
else None,
"created_at": instance.created_at.isoformat()
if instance.created_at
else None,
}
)
return {"sessions": sessions}
@@ -11,7 +11,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from src.auth.dependencies import get_current_user_id, get_db_session from src.auth.dependencies import get_current_user_id, get_db_session
from src.models import ToolDefinitionManifest from src.models import ToolDefinitionManifest
from src.models import ToolType from src.models import ToolType
from src.services.manifest_compiler import ( from src.services.build.manifest_compiler import (
compile_compose, compile_compose,
compile_dockerfile, compile_dockerfile,
compile_entrypoint, compile_entrypoint,
@@ -173,7 +173,7 @@ async def list_tool_definitions(
""" """
query = select(ToolDefinitionManifest) query = select(ToolDefinitionManifest)
if not include_bases: if not include_bases:
query = query.where(ToolDefinitionManifest.is_base == False) query = query.where(ToolDefinitionManifest.is_base.is_(False))
result = await session.execute( result = await session.execute(
query.order_by(ToolDefinitionManifest.created_at.desc()) query.order_by(ToolDefinitionManifest.created_at.desc())
@@ -11,7 +11,6 @@ from datetime import datetime
import httpx import httpx
from fastapi import ( from fastapi import (
APIRouter, APIRouter,
APIRouter as FastAPIRouter,
Depends, Depends,
HTTPException, HTTPException,
Request, Request,
@@ -27,16 +26,15 @@ from src.auth.dependencies import (
get_current_user_id, get_current_user_id,
get_db_session, get_db_session,
) )
from src.services.event_bus import InstanceEventBus from src.services.instance.event_bus import InstanceEventBus
from src.services.lifecycle_hooks import publish_lifecycle_event from src.services.instance.lifecycle_hooks import publish_lifecycle_event
from src.models import ConfigProfile from src.models import ConfigProfile
from src.models import GitRepository from src.models import GitRepository
from src.models.project import Project
from src.models import SSHKey from src.models import SSHKey
from src.models import ToolInstance from src.models import ToolInstance
from src.models import ToolType from src.models import ToolType
from src.services.clone import check_dirty_state, clone_repository from src.services.git.clone import check_dirty_state, clone_repository
from src.services.config_profile_resolver import ( from src.services.config.config_profile_resolver import (
ConfigProfileCycleError, ConfigProfileCycleError,
ResolvedProfile, ResolvedProfile,
apply_resolved_profile, apply_resolved_profile,
@@ -61,14 +59,14 @@ from src.services.docker import (
write_config_files, write_config_files,
write_env_file, write_env_file,
) )
from src.services.tunnel import ( from src.services.shared.tunnel import (
check_tunnel_health, check_tunnel_health,
recreate_tunnel, recreate_tunnel,
start_tunnel, start_tunnel,
stop_tunnel, stop_tunnel,
) )
from src.services.docker_build import build_image from src.services.build.docker_build import build_image
from src.services.manifest_compiler import ( from src.services.build.manifest_compiler import (
compile_compose, compile_compose,
compile_dockerfile, compile_dockerfile,
compile_entrypoint, compile_entrypoint,
@@ -78,9 +76,12 @@ from src.services.manifest_compiler import (
merge_with_config, merge_with_config,
resolve_base, resolve_base,
) )
from src.services.permission_fixer import apply_mount_permissions, apply_ssh_permissions from src.services.shared.permission_fixer import (
from src.services.readiness_probe import execute_probe apply_mount_permissions,
from src.services.ssh_keys import cleanup_ssh_key_files, prepare_ssh_key_files apply_ssh_permissions,
)
from src.services.shared.readiness_probe import execute_probe
from src.services.shared.ssh_keys import cleanup_ssh_key_files, prepare_ssh_key_files
from src.schemas.tool import CreateInstanceRequest, StartInstanceRequest from src.schemas.tool import CreateInstanceRequest, StartInstanceRequest
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -852,19 +853,31 @@ async def create_instance(
# Generate unique name # Generate unique name
instance_name = f"{tool_type.name}-{repo.name}-{uuid.uuid4().hex[:8]}" instance_name = f"{tool_type.name}-{repo.name}-{uuid.uuid4().hex[:8]}"
# Auto-generate display name with numbering when duplicates exist # Auto-generate display name with scoped numbering.
# When a workspace is provided, use workspace name + tool type.
# Otherwise fall back to repo name + tool type.
if data.display_name: if data.display_name:
instance_display = data.display_name instance_display = data.display_name
else: else:
auto_name = f"{_project.name} / {repo.name} / {tool_type.display_name}" scope_name = workspace.name if workspace else repo.name
result = await session.execute( auto_name = f"{scope_name} / {tool_type.display_name}"
select(ToolInstance).where(
ToolInstance.project_id == project_id, if workspace:
ToolInstance.repository_id == repo_id, count_query = (
ToolInstance.tool_type_id == tool_type_id, select(ToolInstance)
ToolInstance.owner_id == user_id, .where(ToolInstance.workspace_id == workspace_id)
.where(ToolInstance.tool_type_id == tool_type_id)
.where(ToolInstance.owner_id == user_id)
) )
) else:
count_query = (
select(ToolInstance)
.where(ToolInstance.repository_id == repo_id)
.where(ToolInstance.tool_type_id == tool_type_id)
.where(ToolInstance.owner_id == user_id)
)
result = await session.execute(count_query)
existing_count = len(result.scalars().all()) existing_count = len(result.scalars().all())
if existing_count > 0: if existing_count > 0:
instance_display = f"{auto_name} #{existing_count + 1}" instance_display = f"{auto_name} #{existing_count + 1}"
@@ -1585,7 +1598,7 @@ async def start_instance(
# Mount selected SSH keys into container home dir # Mount selected SSH keys into container home dir
if instance.ssh_key_ids: if instance.ssh_key_ids:
from src.services.ssh_keys import write_ssh_config, _sanitize_filename from src.services.shared.ssh_keys import write_ssh_config, _sanitize_filename
# Collect all valid keys first # Collect all valid keys first
ssh_keys_to_mount = [] ssh_keys_to_mount = []
@@ -2885,71 +2898,3 @@ async def proxy_to_instance(
status_code=response.status_code, status_code=response.status_code,
headers=response_headers, headers=response_headers,
) )
sessions_router = FastAPIRouter(prefix="/users", tags=["sessions"])
@sessions_router.get(
"/me/sessions",
summary="Get user sessions",
description="Get all active sessions (running instances) for the current user.",
)
async def get_user_sessions(
user_id: uuid.UUID = Depends(get_current_user_id),
session: AsyncSession = Depends(get_db_session),
) -> dict:
"""Get all active sessions for the current user.
Args:
user_id: ID of the authenticated user.
session: Database session.
Returns:
Dictionary containing list of active sessions with instance details.
"""
_user = await _get_user(session, user_id)
result = await session.execute(
select(ToolInstance)
.where(ToolInstance.owner_id == user_id)
.where(
ToolInstance.status.in_(
["running", "building", "pending", "stopped", "error"]
)
)
.order_by(ToolInstance.created_at.desc())
)
instances = result.scalars().all()
sessions = []
for instance in instances:
tool_type = await session.get(ToolType, instance.tool_type_id)
repo = await session.get(GitRepository, instance.repository_id)
project = await session.get(Project, instance.project_id)
sessions.append(
{
"id": str(instance.id),
"display_name": instance.display_name,
"tool_type_name": tool_type.name if tool_type else "unknown",
"tool_icon": tool_type.name if tool_type else "code",
"tool_type_interfaces": [tool_type.interface_type] if tool_type else [],
"repository_name": repo.name if repo else "unknown",
"repository_id": str(instance.repository_id),
"project_name": project.name if project else "unknown",
"project_id": str(instance.project_id),
"status": instance.status,
"url": instance.url,
"clone_mode": instance.clone_mode,
"branch": instance.branch,
"selected_config_profile_id": str(instance.selected_config_profile_id)
if instance.selected_config_profile_id
else None,
"created_at": instance.created_at.isoformat()
if instance.created_at
else None,
}
)
return {"sessions": sessions}
@@ -4,7 +4,7 @@ from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from src.api.tool_types_validation import ( from src.api.tool.tool_types_validation import (
check_port_exposed, check_port_exposed,
validate_compose_yaml, validate_compose_yaml,
validate_required_variables, validate_required_variables,
+7 -1
View File
@@ -1 +1,7 @@
"""User module.""" """User API routers module."""
from src.api.user.auth import router as auth_router
from src.api.user.ssh_keys import router as ssh_keys_router
from src.api.user.users import router as users_router
__all__ = ["auth_router", "ssh_keys_router", "users_router"]
+17 -1
View File
@@ -1 +1,17 @@
"""Workspace module.""" """Workspace API routers module."""
from src.api.workspace.workspace_files import router as workspace_files_router
from src.api.workspace.workspace_git import router as workspace_git_router
from src.api.workspace.workspace_instances import router as workspace_instances_router
from src.api.workspace.workspaces import (
all_workspaces_router,
router as workspaces_router,
)
__all__ = [
"all_workspaces_router",
"workspace_files_router",
"workspace_git_router",
"workspace_instances_router",
"workspaces_router",
]
@@ -7,7 +7,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from src.auth.dependencies import get_current_user_id, get_db_session from src.auth.dependencies import get_current_user_id, get_db_session
from src.models import Workspace from src.models import Workspace
from src.services.file_service import FileService from src.services.shared.file_service import FileService
router = APIRouter(prefix="/workspaces/{workspace_id}/files") router = APIRouter(prefix="/workspaces/{workspace_id}/files")
@@ -103,7 +103,7 @@ async def write_file(
raise HTTPException(status_code=400, detail=str(exc)) from exc raise HTTPException(status_code=400, detail=str(exc)) from exc
if commit_message: if commit_message:
from src.services.git_operations import GitOperations from src.services.git.git_operations import GitOperations
git = GitOperations(workspace) git = GitOperations(workspace)
try: try:
@@ -7,7 +7,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from src.auth.dependencies import get_current_user_id, get_db_session from src.auth.dependencies import get_current_user_id, get_db_session
from src.models import Workspace from src.models import Workspace
from src.services.git_operations import GitOperations from src.services.git.git_operations import GitOperations
router = APIRouter(prefix="/workspaces/{workspace_id}/git") router = APIRouter(prefix="/workspaces/{workspace_id}/git")
@@ -12,7 +12,7 @@ from src.auth.dependencies import get_current_user_id, get_db_session
from src.models import GitRepository from src.models import GitRepository
from src.models import ToolInstance from src.models import ToolInstance
from src.models import Workspace from src.models import Workspace
from src.services.workspace_manager import WorkspaceHasInstancesError, WorkspaceManager from src.services.shared.workspace_manager import WorkspaceHasInstancesError, WorkspaceManager
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
+1 -1
View File
@@ -10,7 +10,7 @@ from collections.abc import Callable
from fastapi import Request, Response from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware from starlette.middleware.base import BaseHTTPMiddleware
from src.services.correlation import get_correlation_id from src.services.shared.correlation import get_correlation_id
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
+26 -24
View File
@@ -7,27 +7,30 @@ from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from src.api.auth import router as auth_router from src.api.config import config_profiles_router, user_config_router
from src.api.dashboard import router as dashboard_router from src.api.project import git_repositories_router, projects_router
from src.api.events import router as events_router from src.api.system import (
from src.api.git_repositories import router as git_repositories_router dashboard_router,
from src.api.health import router as health_router events_router,
from src.api.projects import router as projects_router health_router,
from src.api.ssh_keys import router as ssh_keys_router instance_proxy_router,
from src.api.terminal import router as terminal_router notifications_router,
from src.api.instance_proxy import router as instance_proxy_router terminal_router,
from src.api.config_profiles import router as config_profiles_router )
from src.api.tool_definitions import router as tool_definitions_router from src.api.tool import (
from src.api.tool_instances import router as tool_instances_router sessions_router,
from src.api.tool_instances import sessions_router tool_definitions_router,
from src.api.tool_types import router as tool_types_router tool_instances_router,
from src.api.notifications import router as notifications_router tool_types_router,
from src.api.user_config import router as user_config_router )
from src.api.users import router as users_router from src.api.user import auth_router, ssh_keys_router, users_router
from src.api.workspace_files import router as workspace_files_router from src.api.workspace import (
from src.api.workspace_git import router as workspace_git_router all_workspaces_router,
from src.api.workspace_instances import router as workspace_instances_router workspace_files_router,
from src.api.workspaces import all_workspaces_router, router as workspaces_router workspace_git_router,
workspace_instances_router,
workspaces_router,
)
from src.config import Settings from src.config import Settings
from src.models import Notification # noqa: F401 Alembic model discovery from src.models import Notification # noqa: F401 Alembic model discovery
from src.models import TerminalSessionModel # noqa: F401 Alembic model discovery from src.models import TerminalSessionModel # noqa: F401 Alembic model discovery
@@ -38,9 +41,8 @@ from src.logging_config import (
configure_logging, configure_logging,
) )
from src.seeds.builtin_tool_types import seed_builtin_tool_types from src.seeds.builtin_tool_types import seed_builtin_tool_types
from src.services.correlation import CorrelationIdMiddleware from src.services.instance import InstanceEventBus, HealthMonitor
from src.services.event_bus import InstanceEventBus from src.services.shared import CorrelationIdMiddleware
from src.services.health_monitor import HealthMonitor
# Configure logging early # Configure logging early
log_level = os.getenv("LOG_LEVEL", "INFO").upper() log_level = os.getenv("LOG_LEVEL", "INFO").upper()
+1 -1
View File
@@ -6,7 +6,7 @@ from datetime import datetime
from pydantic import BaseModel, ConfigDict, field_validator, model_validator from pydantic import BaseModel, ConfigDict, field_validator, model_validator
from src.api.tool_types_validation import ( from src.api.tool.tool_types_validation import (
check_port_exposed, check_port_exposed,
validate_compose_yaml, validate_compose_yaml,
validate_required_variables, validate_required_variables,
+25 -1
View File
@@ -1 +1,25 @@
"""Config module.""" """Config profile services module."""
from src.services.config.config_profile_resolver import (
ConfigProfileCycleError,
ConfigProfileNotFoundError,
ResolvedMount,
ResolvedProfile,
apply_resolved_profile,
check_include_cycle,
expand_container_path,
resolve_profile,
resolved_profile_to_dict,
)
__all__ = [
"ConfigProfileCycleError",
"ConfigProfileNotFoundError",
"ResolvedMount",
"ResolvedProfile",
"apply_resolved_profile",
"check_include_cycle",
"expand_container_path",
"resolve_profile",
"resolved_profile_to_dict",
]
+19 -1
View File
@@ -1 +1,19 @@
"""Git module.""" """Git services module."""
from src.services.git.clone import (
check_dirty_state,
clone_repository,
remove_clone_directory,
)
from src.services.git.git_operations import Commit, GitOperations, GitStatus
from src.services.git.git_service import GitService
__all__ = [
"check_dirty_state",
"clone_repository",
"remove_clone_directory",
"Commit",
"GitOperations",
"GitStatus",
"GitService",
]
+7 -1
View File
@@ -1 +1,7 @@
"""Instance module.""" """Instance lifecycle services module."""
from src.services.instance.event_bus import InstanceEventBus
from src.services.instance.health_monitor import HealthMonitor
from src.services.instance.lifecycle_hooks import publish_lifecycle_event
__all__ = ["InstanceEventBus", "HealthMonitor", "publish_lifecycle_event"]
@@ -12,11 +12,11 @@ from sqlalchemy.ext.asyncio import AsyncSession
from src.database import SessionLocal from src.database import SessionLocal
from src.models import HealthCheck from src.models import HealthCheck
from src.models import ToolInstance from src.models import ToolInstance
from src.services.correlation import get_correlation_id from src.services.shared.correlation import get_correlation_id
from src.services.docker import get_container_status from src.services.docker import get_container_status
from src.services.tunnel import check_tunnel_health from src.services.shared.tunnel import check_tunnel_health
from src.services.event_bus import InstanceEventBus, InstanceEventPayload from src.services.instance.event_bus import InstanceEventBus, InstanceEventPayload
from src.services.notification_service import notification_service from src.services.shared.notification_service import notification_service
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -8,9 +8,9 @@ from sqlalchemy.ext.asyncio import AsyncSession
from src.models import InstanceEvent from src.models import InstanceEvent
from src.models import ToolInstance from src.models import ToolInstance
from src.services.correlation import get_correlation_id from src.services.shared.correlation import get_correlation_id
from src.services.event_bus import InstanceEventBus, InstanceEventPayload from src.services.instance.event_bus import InstanceEventBus, InstanceEventPayload
from src.services.notification_service import notification_service from src.services.shared.notification_service import notification_service
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
+51 -1
View File
@@ -1 +1,51 @@
"""Shared module.""" """Shared services module."""
from src.services.shared.correlation import CorrelationIdMiddleware, get_correlation_id
from src.services.shared.file_service import FileEntry, FileService
from src.services.shared.notification_service import NotificationService
from src.services.shared.permission_fixer import (
PermissionFixError,
apply_mount_permissions,
apply_ssh_permissions,
check_root_user_available,
)
from src.services.shared.readiness_probe import execute_probe
from src.services.shared.ssh_keys import (
cleanup_ssh_key_files,
prepare_ssh_key_files,
write_ssh_config,
)
from src.services.shared.tunnel import (
check_tunnel_health,
recreate_tunnel,
start_tunnel,
stop_tunnel,
)
from src.services.shared.workspace_manager import (
SyncResult,
WorkspaceHasInstancesError,
WorkspaceManager,
)
__all__ = [
"CorrelationIdMiddleware",
"FileEntry",
"FileService",
"NotificationService",
"PermissionFixError",
"SyncResult",
"WorkspaceHasInstancesError",
"WorkspaceManager",
"apply_mount_permissions",
"apply_ssh_permissions",
"check_root_user_available",
"check_tunnel_health",
"cleanup_ssh_key_files",
"execute_probe",
"get_correlation_id",
"prepare_ssh_key_files",
"recreate_tunnel",
"start_tunnel",
"stop_tunnel",
"write_ssh_config",
]
@@ -15,8 +15,8 @@ from typing import TYPE_CHECKING
from sqlalchemy import select from sqlalchemy import select
from src.models import Workspace from src.models import Workspace
from src.services.git_service import GitService from src.services.git.git_service import GitService
from src.services.ssh_keys import _get_fernet from src.services.shared.ssh_keys import _get_fernet
if TYPE_CHECKING: if TYPE_CHECKING:
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
+9 -1
View File
@@ -1 +1,9 @@
"""Terminal module.""" """Terminal services module."""
from src.services.terminal.terminal_manager import (
MaxSessionsExceededError,
TerminalManager,
)
from src.services.terminal.terminal_session import TerminalSession
__all__ = ["MaxSessionsExceededError", "TerminalManager", "TerminalSession"]
@@ -10,7 +10,7 @@ from sqlalchemy.dialects.postgresql import insert as pg_insert
from src.database import SessionLocal from src.database import SessionLocal
from src.models import TerminalSessionModel from src.models import TerminalSessionModel
from src.services.terminal_session import TerminalSession from src.services.terminal.terminal_session import TerminalSession
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -6,7 +6,7 @@ import {
listToolTypes, listToolTypes,
updateToolType, updateToolType,
validateToolType, validateToolType,
} from "../api/tool_types"; } from "../api/tool-types";
const mockGet = vi.fn(); const mockGet = vi.fn();
const mockPost = vi.fn(); const mockPost = vi.fn();
+4 -4
View File
@@ -10,11 +10,11 @@ import { useMobileViewport } from "../hooks/use-mobile-viewport";
import { EventProvider } from "../state/events"; import { EventProvider } from "../state/events";
import { ToastProvider } from "../state/toast"; import { ToastProvider } from "../state/toast";
import { NotificationProvider } from "../state/notifications"; import { NotificationProvider } from "../state/notifications";
import { EventToastBridge } from "./event-toast-bridge"; import { EventToastBridge } from "./features/notification/event-toast-bridge";
import { NotificationCenter } from "./notification-center"; import { NotificationCenter } from "./features/notification/notification-center";
import { Icon } from "./icon"; import { Icon } from "./icon";
import { MobileNav } from "./mobile-nav"; import { MobileNav } from "./features/mobile/mobile-nav";
import { StartToolFAB } from "./start-tool-fab"; import { StartToolFAB } from "./features/tool/start-tool-fab";
import type { IconName } from "../utils/icons"; import type { IconName } from "../utils/icons";
const NAV_ITEMS: { const NAV_ITEMS: {
@@ -1,6 +1,6 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Icon } from "./icon"; import { Icon } from "../../icon";
interface CommitDialogProps { interface CommitDialogProps {
isOpen: boolean; isOpen: boolean;
@@ -1,6 +1,6 @@
import { useState } from "react"; import { useState } from "react";
import { commitChanges } from "../api/git_repositories"; import { commitChanges } from "../../../api/git-repositories";
interface CommitPanelProps { interface CommitPanelProps {
projectId: string; projectId: string;
@@ -1,12 +1,12 @@
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import { useSearchParams } from "react-router-dom"; import { useSearchParams } from "react-router-dom";
import { apiClient } from "../api/client"; import { apiClient } from "../../../api/client";
import { useAuth } from "../state/auth"; import { useAuth } from "../../../state/auth";
import { CodeEditor } from "../components/code-editor"; import { CodeEditor } from "../../code-editor";
import { CommitDialog } from "../components/commit-dialog"; import { CommitDialog } from "./commit-dialog";
import { Icon } from "../components/icon"; import { Icon } from "../../icon";
import { SyntaxHighlighter } from "../components/syntax-highlighter"; import { SyntaxHighlighter } from "../../syntax-highlighter";
import { detectLanguage } from "../utils/language"; import { detectLanguage } from "../../../utils/language";
interface FileEditorProps { interface FileEditorProps {
projectId: string; projectId: string;
@@ -1,7 +1,7 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { Icon } from "./icon"; import { Icon } from "../../icon";
import { validateGitUrl } from "../api/config_profiles"; import { validateGitUrl } from "../../../api/config_profiles";
import type { GitMount, GitMountMapping } from "../api/config_profiles"; import type { GitMount, GitMountMapping } from "../../../api/config_profiles";
interface GitMountEditorProps { interface GitMountEditorProps {
mounts: GitMount[]; mounts: GitMount[];
@@ -8,8 +8,8 @@ import {
pullRepository, pullRepository,
pushRepository, pushRepository,
type GitStatus, type GitStatus,
} from "../api/git_repositories"; } from "../../../api/git-repositories";
import { Icon } from "./icon"; import { Icon } from "../../icon";
import { MergeDialog } from "./merge-dialog"; import { MergeDialog } from "./merge-dialog";
interface GitToolbarProps { interface GitToolbarProps {
@@ -1,7 +1,7 @@
import { useState } from "react"; import { useState } from "react";
import { mergeBranches } from "../api/git_repositories"; import { mergeBranches } from "../../../api/git-repositories";
import { Icon } from "./icon"; import { Icon } from "../../icon";
interface MergeDialogProps { interface MergeDialogProps {
projectId: string; projectId: string;
@@ -1,6 +1,6 @@
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { Icon } from "./icon"; import { Icon } from "../../icon";
import type { IconName } from "./icon"; import type { IconName } from "../../icon";
export interface MobileActionSheetItem { export interface MobileActionSheetItem {
id: string; id: string;
@@ -1,4 +1,4 @@
import { Icon } from "./icon"; import { Icon } from "../../icon";
interface Field { interface Field {
label: string; label: string;
@@ -1,4 +1,4 @@
import { Icon } from "./icon"; import { Icon } from "../../icon";
interface MobileFABProps { interface MobileFABProps {
onClick: () => void; onClick: () => void;
@@ -1,5 +1,5 @@
import { Icon } from "./icon"; import { Icon } from "../../icon";
import type { IconName } from "../utils/icons"; import type { IconName } from "../../../utils/icons";
interface MobileListItem { interface MobileListItem {
id: string; id: string;
@@ -1,8 +1,8 @@
import { useState } from "react"; import { useState } from "react";
import { NavLink, useLocation } from "react-router-dom"; import { NavLink, useLocation } from "react-router-dom";
import { Icon } from "./icon"; import { Icon } from "../../icon";
import { ToolsBottomSheet } from "./tools-bottom-sheet"; import { ToolsBottomSheet } from "../tool/tools-bottom-sheet";
import type { IconName } from "../utils/icons"; import type { IconName } from "../../../utils/icons";
interface MobileNavProps { interface MobileNavProps {
sessionCount?: number; sessionCount?: number;
@@ -1,6 +1,6 @@
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useMobileViewport } from "../hooks/use-mobile-viewport"; import { useMobileViewport } from "../../../hooks/use-mobile-viewport";
import { Icon } from "./icon"; import { Icon } from "../../icon";
interface MobilePageHeaderProps { interface MobilePageHeaderProps {
title: string; title: string;
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { Icon } from "./icon"; import { Icon } from "../../icon";
interface MobileTerminalHeaderProps { interface MobileTerminalHeaderProps {
instanceName?: string; instanceName?: string;
@@ -1,12 +1,12 @@
import React, { useState, useCallback } from "react"; import React, { useState, useCallback } from "react";
import { TerminalComponent } from "./terminal"; import { TerminalComponent } from "../terminal/terminal";
import { MobileTerminalHeader } from "./mobile-terminal-header"; import { MobileTerminalHeader } from "./mobile-terminal-header";
import { SpecialKeysStrip } from "./special-keys-strip"; import { SpecialKeysStrip } from "../terminal/special-keys-strip";
import { SpecialKeysPanel } from "./special-keys-panel"; import { SpecialKeysPanel } from "../terminal/special-keys-panel";
import { useMobileViewport } from "../hooks/use-mobile-viewport"; import { useMobileViewport } from "../../../hooks/use-mobile-viewport";
import { useVirtualKeyboard } from "../hooks/use-virtual-keyboard"; import { useVirtualKeyboard } from "../../../hooks/use-virtual-keyboard";
import { useAutoHide } from "../hooks/use-auto-hide"; import { useAutoHide } from "../../../hooks/use-auto-hide";
import type { ModifierKey } from "../hooks/use-special-keys"; import type { ModifierKey } from "../../../hooks/use-special-keys";
interface MobileTerminalWrapperProps { interface MobileTerminalWrapperProps {
instanceId: string; instanceId: string;
@@ -2,15 +2,15 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render, act } from "@testing-library/react"; import { render, act } from "@testing-library/react";
import { EventToastBridge } from "./event-toast-bridge"; import { EventToastBridge } from "./event-toast-bridge";
import { useEventContext } from "../state/events"; import { useEventContext } from "../state/events";
import { getUserConfig } from "../api/settings"; import { getUserConfig } from "../../../api/settings";
import { handleEventToast } from "./toast-rules"; import { handleEventToast } from "./toast-rules";
import type { InstanceEventPayload } from "../types/events"; import type { InstanceEventPayload } from "../../../types/events";
vi.mock("../state/events", () => ({ vi.mock("../state/events", () => ({
useEventContext: vi.fn(), useEventContext: vi.fn(),
})); }));
vi.mock("../api/settings", () => ({ vi.mock("../../../api/settings", () => ({
getUserConfig: vi.fn(), getUserConfig: vi.fn(),
})); }));
@@ -1,12 +1,12 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { useEventContext } from "../state/events"; import { useEventContext } from "../../../state/events";
import { import {
handleEventToast, handleEventToast,
mapEventToCategory, mapEventToCategory,
mapEventToSeverity, mapEventToSeverity,
} from "./toast-rules"; } from "../../toast-rules";
import { getUserConfig } from "../api/settings"; import { getUserConfig } from "../../../api/settings";
import type { UserConfig } from "../api/settings"; import type { UserConfig } from "../../../api/settings";
interface ToastConfig { interface ToastConfig {
notification_toast_level: string; notification_toast_level: string;
@@ -3,7 +3,7 @@ import { render, screen, fireEvent, cleanup } from "@testing-library/react";
import { NotificationCenter } from "./notification-center"; import { NotificationCenter } from "./notification-center";
import { NotificationProvider } from "../state/notifications"; import { NotificationProvider } from "../state/notifications";
vi.mock("../api/notifications", () => ({ vi.mock("../../../api/notifications", () => ({
getNotifications: vi.fn(), getNotifications: vi.fn(),
getUnreadCount: vi.fn(), getUnreadCount: vi.fn(),
markNotificationRead: vi.fn(), markNotificationRead: vi.fn(),
@@ -12,7 +12,7 @@ vi.mock("../api/notifications", () => ({
clearAllNotifications: vi.fn(), clearAllNotifications: vi.fn(),
})); }));
import { getNotifications, getUnreadCount } from "../api/notifications"; import { getNotifications, getUnreadCount } from "../../../api/notifications";
const mockedGetNotifications = vi.mocked(getNotifications); const mockedGetNotifications = vi.mocked(getNotifications);
const mockedGetUnreadCount = vi.mocked(getUnreadCount); const mockedGetUnreadCount = vi.mocked(getUnreadCount);
@@ -142,7 +142,7 @@ describe("NotificationCenter", () => {
fireEvent.click(screen.getByRole("button", { name: /mark all as read/i })); fireEvent.click(screen.getByRole("button", { name: /mark all as read/i }));
const { markAllNotificationsRead: mockMarkAll } = await import( const { markAllNotificationsRead: mockMarkAll } = await import(
"../api/notifications" "../../../api/notifications"
); );
expect(vi.mocked(mockMarkAll)).toHaveBeenCalled(); expect(vi.mocked(mockMarkAll)).toHaveBeenCalled();
}); });
@@ -162,7 +162,7 @@ describe("NotificationCenter", () => {
fireEvent.click(screen.getByRole("button", { name: /clear all/i })); fireEvent.click(screen.getByRole("button", { name: /clear all/i }));
const { clearAllNotifications: mockClearAll } = await import( const { clearAllNotifications: mockClearAll } = await import(
"../api/notifications" "../../../api/notifications"
); );
expect(vi.mocked(mockClearAll)).toHaveBeenCalled(); expect(vi.mocked(mockClearAll)).toHaveBeenCalled();
}); });
@@ -1,7 +1,7 @@
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { useNotifications } from "../hooks/use-notifications"; import { useNotifications } from "../../../hooks/use-notifications";
import { NotificationItem } from "./notification-item"; import { NotificationItem } from "./notification-item";
import { Icon } from "./icon"; import { Icon } from "../../icon";
interface NotificationCenterProps { interface NotificationCenterProps {
isMobileTerminal?: boolean; isMobileTerminal?: boolean;
@@ -1,6 +1,6 @@
import { Icon } from "./icon"; import { Icon } from "../../icon";
import { formatRelativeTime } from "../utils/time"; import { formatRelativeTime } from "../../../utils/time";
import type { NotificationItem as NotificationItemType } from "../api/notifications"; import type { NotificationItem as NotificationItemType } from "../../../api/notifications";
export interface NotificationItemProps { export interface NotificationItemProps {
notification: NotificationItemType; notification: NotificationItemType;
@@ -8,7 +8,7 @@ export interface NotificationItemProps {
onDismiss: (id: string) => void; onDismiss: (id: string) => void;
} }
import type { IconName } from "../utils/icons"; import type { IconName } from "../../../utils/icons";
const severityIconMap: Record<string, IconName> = { const severityIconMap: Record<string, IconName> = {
info: "info", info: "info",
@@ -2,7 +2,7 @@ import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/re
import { afterEach, describe, expect, it, vi } from "vitest"; import { afterEach, describe, expect, it, vi } from "vitest";
import { RepositoriesSettingsTab } from "./repositories-settings-tab"; import { RepositoriesSettingsTab } from "./repositories-settings-tab";
import * as gitRepositoriesApi from "../api/git_repositories"; import * as gitRepositoriesApi from "../../../api/git-repositories";
const mockRepositories = [ const mockRepositories = [
{ {
@@ -1,9 +1,9 @@
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { deleteRepository, listRepositories, type GitRepository } from "../api/git_repositories"; import { deleteRepository, listRepositories, type GitRepository } from "../../../api/git-repositories";
import { RepositoryCreateDialog } from "./repository-create-dialog"; import { RepositoryCreateDialog } from "./repository-create-dialog";
import { Icon } from "./icon"; import { Icon } from "../../icon";
export const RepositoriesSettingsTab: React.FC = () => { export const RepositoriesSettingsTab: React.FC = () => {
const { projectId } = useParams<{ projectId: string }>(); const { projectId } = useParams<{ projectId: string }>();
@@ -1,8 +1,8 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { createRepository, parseGitUrl, type GitRepositoryCreate, type URLParseResult } from "../api/git_repositories"; import { createRepository, parseGitUrl, type GitRepositoryCreate, type URLParseResult } from "../../../api/git-repositories";
import { listSSHKeys, type SSHKey } from "../api/ssh_keys"; import { listSSHKeys, type SSHKey } from "../../../api/ssh-keys";
import { Icon } from "./icon"; import { Icon } from "../../icon";
type CreateMode = "clone" | "blank"; type CreateMode = "clone" | "blank";
type UrlValidationStatus = "idle" | "validating" | "valid" | "needs-parsing" | "invalid"; type UrlValidationStatus = "idle" | "validating" | "valid" | "needs-parsing" | "invalid";
@@ -1,11 +1,11 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { Icon } from "./icon"; import { Icon } from "../../icon";
import { createInstance, startInstance, type ToolInstance } from "../api/sessions"; import { createInstance, startInstance, type ToolInstance } from "../../../api/sessions";
import type { Project } from "../types"; import type { Project } from "../../../types";
import { listRepositoryBranches, type GitRepository, type Branch } from "../api/git_repositories"; import { listRepositoryBranches, type GitRepository, type Branch } from "../../../api/git-repositories";
import type { ToolType } from "../api/tool_types"; import type { ToolType } from "../../../api/tool-types";
import { listSSHKeys, type SSHKey } from "../api/ssh_keys"; import { listSSHKeys, type SSHKey } from "../../../api/ssh-keys";
import { listConfigProfiles, type ConfigProfile } from "../api/config_profiles"; import { listConfigProfiles, type ConfigProfile } from "../../../api/config_profiles";
interface CreateSessionFormProps { interface CreateSessionFormProps {
projects: Project[]; projects: Project[];
@@ -1,9 +1,9 @@
import { useState } from "react"; import { useState } from "react";
import type { Session } from "../api/sessions"; import type { Session } from "../../../api/sessions";
import { Icon } from "./icon"; import { Icon } from "../../icon";
import { useMobileViewport } from "../hooks/use-mobile-viewport"; import { useMobileViewport } from "../../../hooks/use-mobile-viewport";
import { MobileActionSheet } from "./mobile-action-sheet"; import { MobileActionSheet } from "../mobile/mobile-action-sheet";
import type { IconName } from "./icon"; import type { IconName } from "../../icon";
export interface SessionCardProps { export interface SessionCardProps {
session: Session; session: Session;
@@ -1,6 +1,6 @@
import type { Session } from "../api/sessions"; import type { Session } from "../../../api/sessions";
import { SessionCard } from "./session-card"; import { SessionCard } from "./session-card";
import type { InstanceHealth } from "../api/sessions"; import type { InstanceHealth } from "../../../api/sessions";
export interface SessionListProps { export interface SessionListProps {
sessions: Session[]; sessions: Session[];
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { getSequenceWithModifier, type SpecialKey, type ModifierKey } from "../hooks/use-special-keys"; import { getSequenceWithModifier, type SpecialKey, type ModifierKey } from "../../../hooks/use-special-keys";
interface SpecialKeysPanelProps { interface SpecialKeysPanelProps {
onSend: (data: string) => void; onSend: (data: string) => void;
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { getSequenceWithModifier, type SpecialKey, type ModifierKey } from "../hooks/use-special-keys"; import { getSequenceWithModifier, type SpecialKey, type ModifierKey } from "../../../hooks/use-special-keys";
interface SpecialKeysStripProps { interface SpecialKeysStripProps {
onSend: (data: string) => void; onSend: (data: string) => void;
@@ -14,7 +14,7 @@ import "xterm/css/xterm.css";
import { import {
applyModifierToChar, applyModifierToChar,
type ModifierKey, type ModifierKey,
} from "../hooks/use-special-keys"; } from "../../../hooks/use-special-keys";
export interface TerminalProps { export interface TerminalProps {
instanceId: string; instanceId: string;
@@ -1,19 +1,19 @@
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { Icon } from "./icon"; import { Icon } from "../../icon";
import type { ToolInstance } from "../api/sessions"; import type { ToolInstance } from "../../../api/sessions";
import { import {
deleteInstance, deleteInstance,
listInstances, listInstances,
restartInstance, restartInstance,
startInstance, startInstance,
stopInstance, stopInstance,
} from "../api/sessions"; } from "../../../api/sessions";
import type { ToolType } from "../api/tool_types"; import type { ToolType } from "../../../api/tool-types";
import { CreateSessionForm } from "./create-session-form"; import { CreateSessionForm } from "../session/create-session-form";
import { listConfigProfiles, type ConfigProfile } from "../api/config_profiles"; import { listConfigProfiles, type ConfigProfile } from "../../../api/config_profiles";
import { listSSHKeys, type SSHKey } from "../api/ssh_keys"; import { listSSHKeys, type SSHKey } from "../../../api/ssh-keys";
import { useEventContext } from "../state/events"; import { useEventContext } from "../../../state/events";
const API_BASE_URL = const API_BASE_URL =
import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8000"; import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8000";
@@ -1,10 +1,10 @@
import { useState, useEffect, useCallback, useRef } from "react"; import { useState, useEffect, useCallback, useRef } from "react";
import { Icon } from "./icon"; import { Icon } from "../../icon";
import { extractErrorMessage } from "../utils/errors"; import { extractErrorMessage } from "../../../utils/errors";
import { import {
compileToolDefinition, compileToolDefinition,
type ToolDefinitionManifest, type ToolDefinitionManifest,
} from "../api/tool_definitions"; } from "../../../api/tool_definitions";
interface PackageEntry { interface PackageEntry {
name: string; name: string;
@@ -1,10 +1,10 @@
/** Floating action button to start a tool from any page. */ /** Floating action button to start a tool from any page. */
import { useState } from "react"; import { useState } from "react";
import { Icon } from "./icon"; import { Icon } from "../../icon";
import { ToolStarter } from "./tool-starter"; import { ToolStarter } from "./tool-starter";
import type { Workspace } from "../types/workspace"; import type { Workspace } from "../../../types/workspace";
import { listAllWorkspaces } from "../api/workspaces"; import { listAllWorkspaces } from "../../../api/workspaces";
export function StartToolFAB() { export function StartToolFAB() {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
@@ -1,10 +1,10 @@
/** Modal for starting a tool on a workspace. */ /** Modal for starting a tool on a workspace. */
import { useState } from "react"; import { useState } from "react";
import { Icon } from "./icon"; import { Icon } from "../../icon";
import { listToolTypes, type ToolType } from "../api/tool_types"; import { listToolTypes, type ToolType } from "../../../api/tool-types";
import { useAsyncData } from "../hooks/use-async-data"; import { useAsyncData } from "../../../hooks/use-async-data";
import type { Workspace } from "../types/workspace"; import type { Workspace } from "../../../types/workspace";
export interface StartToolModalProps { export interface StartToolModalProps {
workspace: Workspace; workspace: Workspace;
@@ -1,12 +1,12 @@
/** Unified tool starter — workspace-first, fetches real tool types and config profiles. */ /** Unified tool starter — workspace-first, fetches real tool types and config profiles. */
import { useState, useEffect, useCallback } from "react"; import { useState, useEffect, useCallback } from "react";
import { Icon } from "./icon"; import { Icon } from "../../icon";
import { listToolTypes, type ToolType } from "../api/tool_types"; import { listToolTypes, type ToolType } from "../../../api/tool-types";
import { listConfigProfiles, type ConfigProfile } from "../api/config_profiles"; import { listConfigProfiles, type ConfigProfile } from "../../../api/config_profiles";
import { listSSHKeys, type SSHKey } from "../api/ssh_keys"; import { listSSHKeys, type SSHKey } from "../../../api/ssh-keys";
import type { Workspace } from "../types/workspace"; import type { Workspace } from "../../../types/workspace";
import type { ToolInstance } from "../api/sessions"; import type { ToolInstance } from "../../../api/sessions";
export interface ToolStarterProps { export interface ToolStarterProps {
workspace: Workspace; workspace: Workspace;
@@ -114,7 +114,7 @@ export function ToolStarter({
setStarting(true); setStarting(true);
setError(null); setError(null);
try { try {
const { createInstance, startInstance } = await import("../api/sessions"); const { createInstance, startInstance } = await import("../../../api/sessions");
const instance = await createInstance( const instance = await createInstance(
workspace.project_id, workspace.project_id,
workspace.repo_id, workspace.repo_id,
@@ -1,5 +1,5 @@
import { useLocation, useNavigate } from "react-router-dom"; import { useLocation, useNavigate } from "react-router-dom";
import { Icon } from "./icon"; import { Icon } from "../../icon";
interface ToolsBottomSheetProps { interface ToolsBottomSheetProps {
isOpen: boolean; isOpen: boolean;

Some files were not shown because too many files have changed in this diff Show More