Merge branch 'fix/container-name-case-sensitivity' into dev

This commit is contained in:
Alex Blank
2026-05-28 10:58:18 +02:00
45 changed files with 1022 additions and 866 deletions
@@ -7,8 +7,6 @@ Create Date: 2026-05-22 21:50:00.000000
""" """
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = "0014_merge_heads" revision: str = "0014_merge_heads"
@@ -10,7 +10,6 @@ from typing import Sequence, Union
from alembic import op from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
from sqlalchemy.dialects import postgresql from sqlalchemy.dialects import postgresql
from sqlalchemy import inspect
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = "0015_single_interface" revision: str = "0015_single_interface"
@@ -1,5 +1,3 @@
import json
"""add pi agent tool type """add pi agent tool type
Revision ID: 20260527_160017_add_pi_agent Revision ID: 20260527_160017_add_pi_agent
@@ -7,6 +5,8 @@ Revises: f3d2dc90ba3a
Create Date: 2026-05-27T16:00:17 Create Date: 2026-05-27T16:00:17
""" """
import json
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op from alembic import op
@@ -5,8 +5,6 @@ Revises: 2026_05_23_remove_is_builtin, 2026_05_24_add_config_profiles
Create Date: 2026-05-24 18:00:43.990361 Create Date: 2026-05-24 18:00:43.990361
""" """
from alembic import op
import sqlalchemy as sa
@@ -7,8 +7,6 @@ Create Date: 2026-05-24 10:43:14.000000
""" """
from typing import Sequence, Union from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = "f3d2dc90ba3a" revision: str = "f3d2dc90ba3a"
-1
View File
@@ -12,7 +12,6 @@ from sqlalchemy.orm import selectinload
from src.api.shared_validators import validate_env_vars as _validate_env_vars from src.api.shared_validators import validate_env_vars as _validate_env_vars
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.config_profile import ConfigProfile, ConfigProfileInclude from src.models.config_profile import ConfigProfile, ConfigProfileInclude
from src.models.git_repository import GitRepository
from src.models.project import Project from src.models.project import Project
from src.models.tool_type import ToolType from src.models.tool_type import ToolType
from src.services.config_profile_resolver import ( from src.services.config_profile_resolver import (
-2
View File
@@ -13,9 +13,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from src.auth.dependencies import _get_owned_project, _get_user, get_current_user_id, get_db_session from src.auth.dependencies import _get_owned_project, _get_user, get_current_user_id, get_db_session
from src.config import Settings from src.config import Settings
from src.models.git_repository import GitRepository from src.models.git_repository import GitRepository
from src.models.project import Project
from src.models.ssh_key import SSHKey from src.models.ssh_key import SSHKey
from src.models.user import User
from src.utils.git_files import ( from src.utils.git_files import (
commit_file, commit_file,
get_file_content, get_file_content,
+1 -2
View File
@@ -4,11 +4,10 @@ import time
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Any from typing import Any
from fastapi import APIRouter, status from fastapi import APIRouter
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from sqlalchemy import text from sqlalchemy import text
from src.config import Settings
from src.database import SessionLocal from src.database import SessionLocal
router = APIRouter() router = APIRouter()
-1
View File
@@ -2,7 +2,6 @@
import logging import logging
import uuid import uuid
from typing import Any
import httpx import httpx
from fastapi import APIRouter, Depends, HTTPException, Request, Response, status from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
-1
View File
@@ -11,7 +11,6 @@ from src.auth.dependencies import _get_owned_project, _get_user, get_current_use
from src.models.git_repository import GitRepository from src.models.git_repository import GitRepository
from src.models.project import Project from src.models.project import Project
from src.models.ssh_key import SSHKey from src.models.ssh_key import SSHKey
from src.models.user import User
router = APIRouter(prefix="/projects", tags=["projects"]) router = APIRouter(prefix="/projects", tags=["projects"])
-1
View File
@@ -1,6 +1,5 @@
"""Shared Pydantic validators for API schemas.""" """Shared Pydantic validators for API schemas."""
from typing import Any
MAX_FOLDER_SIZE_MB = 10 MAX_FOLDER_SIZE_MB = 10
MAX_FOLDER_SIZE_BYTES = MAX_FOLDER_SIZE_MB * 1024 * 1024 MAX_FOLDER_SIZE_BYTES = MAX_FOLDER_SIZE_MB * 1024 * 1024
-1
View File
@@ -13,7 +13,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
from src.auth.dependencies import _get_user, get_current_user_id, get_db_session from src.auth.dependencies import _get_user, get_current_user_id, get_db_session
from src.config import Settings from src.config import Settings
from src.models.ssh_key import SSHKey from src.models.ssh_key import SSHKey
from src.models.user import User
router = APIRouter(prefix="/ssh-keys", tags=["ssh-keys"]) router = APIRouter(prefix="/ssh-keys", tags=["ssh-keys"])
+1 -1
View File
@@ -4,7 +4,7 @@ import asyncio
import logging import logging
import uuid import uuid
from fastapi import APIRouter, Depends, HTTPException, WebSocket, WebSocketDisconnect, status from fastapi import APIRouter, Depends, HTTPException, WebSocket, status
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from src.auth.dependencies import get_db_session from src.auth.dependencies import get_db_session
+1 -1
View File
@@ -1,6 +1,5 @@
"""Tool configuration API endpoints.""" """Tool configuration API endpoints."""
import logging
import uuid import uuid
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
@@ -11,6 +10,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from src.api.shared_validators import validate_env_vars as _validate_env_vars, validate_volumes as _validate_volumes from src.api.shared_validators import validate_env_vars as _validate_env_vars, validate_volumes as _validate_volumes
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.tool_config import ToolConfig from src.models.tool_config import ToolConfig
from src.models.tool_type import ToolType
router = APIRouter(prefix="/tool-configs", tags=["tool-configs"]) router = APIRouter(prefix="/tool-configs", tags=["tool-configs"])
File diff suppressed because it is too large Load Diff
-2
View File
@@ -1,7 +1,6 @@
import uuid import uuid
from datetime import datetime from datetime import datetime
import yaml
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, ConfigDict, field_validator, model_validator from pydantic import BaseModel, ConfigDict, field_validator, model_validator
from sqlalchemy import select from sqlalchemy import select
@@ -9,7 +8,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
from src.api.tool_types_validation import ( from src.api.tool_types_validation import (
check_port_exposed, check_port_exposed,
sanitize_template_vars,
validate_compose_yaml, validate_compose_yaml,
validate_required_variables, validate_required_variables,
) )
+3 -4
View File
@@ -1,17 +1,16 @@
import logging import logging
import uuid import uuid
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends
logger = logging.getLogger(__name__)
from pydantic import BaseModel, ConfigDict from pydantic import BaseModel, ConfigDict
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from src.auth.dependencies import _get_user, get_current_user_id, get_db_session from src.auth.dependencies import _get_user, get_current_user_id, get_db_session
from src.models.user import User
from src.models.user_config import UserConfig from src.models.user_config import UserConfig
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/users/me", tags=["user-config"]) router = APIRouter(prefix="/users/me", tags=["user-config"])
+1
View File
@@ -7,6 +7,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from src.auth.session import decode_session_cookie from src.auth.session import decode_session_cookie
from src.config import Settings from src.config import Settings
from src.database import SessionLocal from src.database import SessionLocal
from src.models.project import Project
from src.models.user import User from src.models.user import User
-2
View File
@@ -1,4 +1,3 @@
import json
import logging import logging
import os import os
@@ -7,7 +6,6 @@ from fastapi.exceptions import RequestValidationError
from fastapi.middleware.cors import CORSMiddleware 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 sqlalchemy import text
from src.api.auth import router as auth_router from src.api.auth import router as auth_router
from src.api.dashboard import router as dashboard_router from src.api.dashboard import router as dashboard_router
+8 -10
View File
@@ -1,7 +1,9 @@
"""Docker service for managing tool instances.""" """Docker service for managing tool instances."""
import os import os
import re
import subprocess import subprocess
import time
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@@ -137,6 +139,8 @@ def execute_compose_command(
def get_container_id(instance_name: str) -> str | None: def get_container_id(instance_name: str) -> str | None:
"""Get the container ID for a compose service. """Get the container ID for a compose service.
Searches all containers including stopped/exited ones.
Args: Args:
instance_name: The service name in compose instance_name: The service name in compose
@@ -145,7 +149,7 @@ def get_container_id(instance_name: str) -> str | None:
""" """
# Docker container names are lowercase internally; normalize to ensure match # Docker container names are lowercase internally; normalize to ensure match
result = subprocess.run( result = subprocess.run(
["docker", "ps", "-q", "--filter", f"name={instance_name.lower()}"], ["docker", "ps", "-a", "-q", "--filter", f"name={instance_name.lower()}"],
capture_output=True, capture_output=True,
text=True, text=True,
) )
@@ -158,6 +162,8 @@ def get_container_id(instance_name: str) -> str | None:
def get_container_name(instance_name: str) -> str | None: def get_container_name(instance_name: str) -> str | None:
"""Get the full container name for a compose service. """Get the full container name for a compose service.
Searches all containers including stopped/exited ones.
Args: Args:
instance_name: The service name in compose instance_name: The service name in compose
@@ -169,6 +175,7 @@ def get_container_name(instance_name: str) -> str | None:
[ [
"docker", "docker",
"ps", "ps",
"-a",
"--format", "--format",
"{{.Names}}", "{{.Names}}",
"--filter", "--filter",
@@ -252,7 +259,6 @@ def wait_for_container_running(
Dict with 'success' (bool), 'status' (str), 'exit_code' (int or None), Dict with 'success' (bool), 'status' (str), 'exit_code' (int or None),
and 'waited_seconds' (float) and 'waited_seconds' (float)
""" """
import time
start_time = time.time() start_time = time.time()
@@ -336,11 +342,6 @@ def find_free_port(start: int = 10000, end: int = 20000) -> int:
raise RuntimeError(f"No free port found in range {start}-{end}") raise RuntimeError(f"No free port found in range {start}-{end}")
import subprocess
import time
import re
def start_cloudflared_tunnel( def start_cloudflared_tunnel(
container_name: str, port: int, timeout: int = 30 container_name: str, port: int, timeout: int = 30
) -> dict[str, str]: ) -> dict[str, str]:
@@ -358,8 +359,6 @@ def start_cloudflared_tunnel(
Dict with 'url' (the public tunnel URL) and 'pid' (process ID) Dict with 'url' (the public tunnel URL) and 'pid' (process ID)
""" """
import subprocess import subprocess
import time
import re
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -441,7 +440,6 @@ def stop_cloudflared_tunnel(pid: str) -> None:
Args: Args:
pid: Process ID of the cloudflared tunnel pid: Process ID of the cloudflared tunnel
""" """
import os
import signal import signal
try: try:
-1
View File
@@ -18,7 +18,6 @@ def build_image(instance_dir: str, dockerfile: str, tag: str, build_context: dic
Returns: Returns:
Tuple of (returncode, stdout, stderr) Tuple of (returncode, stdout, stderr)
""" """
import os
from pathlib import Path from pathlib import Path
# Write Dockerfile # Write Dockerfile
@@ -3,7 +3,6 @@
import asyncio import asyncio
import logging import logging
import uuid import uuid
from typing import Any
from fastapi import WebSocket from fastapi import WebSocket
-1
View File
@@ -2,7 +2,6 @@
import subprocess import subprocess
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Any
def _run_git_command(repo_path: str, *args: str) -> str: def _run_git_command(repo_path: str, *args: str) -> str:
+1 -3
View File
@@ -8,16 +8,14 @@ from unittest.mock import patch
import pytest import pytest
import pytest_asyncio import pytest_asyncio
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from sqlalchemy import create_engine, text
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.orm import sessionmaker
# Set test environment BEFORE importing app modules # Set test environment BEFORE importing app modules
os.environ["APP_ENV"] = "testing" os.environ["APP_ENV"] = "testing"
os.environ["SECRET_KEY"] = "test-secret-key-for-testing-only-do-not-use-in-production" os.environ["SECRET_KEY"] = "test-secret-key-for-testing-only-do-not-use-in-production"
os.environ["DATABASE_URL"] = "sqlite+aiosqlite:///:memory:" os.environ["DATABASE_URL"] = "sqlite+aiosqlite:///:memory:"
from src.config import Settings, build_database_url from src.config import Settings
from src.models.base import Base from src.models.base import Base
from src.main import app from src.main import app
from src.auth.dependencies import get_db_session from src.auth.dependencies import get_db_session
-22
View File
@@ -82,28 +82,6 @@ def test_repository_and_user_config_relationships_are_registered() -> None:
assert UserConfig.user.property.mapper.class_ is User assert UserConfig.user.property.mapper.class_ is User
@pytest.mark.integration
def test_refresh_token_table_has_required_columns_and_relationships() -> None:
columns = RefreshToken.__table__.columns
user_fk = next(iter(RefreshToken.__table__.c.user_id.foreign_keys))
assert set(columns.keys()) == {
"id",
"user_id",
"token_hash",
"expires_at",
"revoked_at",
"user_agent",
"ip_address",
"created_at",
}
assert columns["token_hash"].unique is True
assert columns["revoked_at"].nullable is True
assert user_fk.target_fullname == "users.id"
assert RefreshToken.user.property.mapper.class_ is User
@pytest.mark.asyncio @pytest.mark.asyncio
@pytest.mark.integration @pytest.mark.integration
@@ -3,6 +3,7 @@ from datetime import UTC, datetime, timedelta
import asyncio import asyncio
import pytest import pytest
from fastapi.testclient import TestClient
from sqlalchemy import text from sqlalchemy import text
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
@@ -1,4 +1,3 @@
import uuid
import pytest import pytest
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
@@ -1,4 +1,3 @@
import uuid
import pytest import pytest
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
@@ -221,7 +220,7 @@ class TestToolTypesAPIExtended:
}, },
) )
assert response.status_code == 422 assert response.status_code == 422
data = response.json() _ = response.json()
def test_create_tool_type_with_startup_command(self, authenticated_client: TestClient) -> None: def test_create_tool_type_with_startup_command(self, authenticated_client: TestClient) -> None:
"""Test creating a tool type with startup_command.""" """Test creating a tool type with startup_command."""
@@ -6,7 +6,6 @@ from src.models.config_profile import ConfigProfile, ConfigProfileInclude
from src.services.config_profile_resolver import ( from src.services.config_profile_resolver import (
ConfigProfileCycleError, ConfigProfileCycleError,
ConfigProfileNotFoundError, ConfigProfileNotFoundError,
ResolvedProfile,
check_include_cycle, check_include_cycle,
resolve_profile, resolve_profile,
_merge_env_vars, _merge_env_vars,
@@ -63,7 +62,6 @@ class TestMergeFunctions:
def test_merge_mounts_basic(self) -> None: def test_merge_mounts_basic(self) -> None:
"""Test basic mount merging.""" """Test basic mount merging."""
from src.services.config_profile_resolver import ResolvedMount
result = _merge_mounts( result = _merge_mounts(
{}, {},
[{"target": "/app", "mode": "rw", "files": {"a.txt": "content"}}], [{"target": "/app", "mode": "rw", "files": {"a.txt": "content"}}],
@@ -1,7 +1,6 @@
"""Unit tests for git mount resolution in tool instances.""" """Unit tests for git mount resolution in tool instances."""
import os import os
import tempfile
from pathlib import Path from pathlib import Path
import pytest import pytest
@@ -11,7 +10,6 @@ from src.api.tool_instances import (
_expand_glob_source, _expand_glob_source,
_resolve_single_git_mount, _resolve_single_git_mount,
) )
from src.services.config_profile_resolver import ResolvedProfile
class TestExpandGlobSource: class TestExpandGlobSource:
@@ -1,6 +1,5 @@
"""Tests for git URL parsing utilities.""" """Tests for git URL parsing utilities."""
import pytest
from src.utils.git_url_parser import extract_base_repo_url, is_valid_clone_url, parse_git_url from src.utils.git_url_parser import extract_base_repo_url, is_valid_clone_url, parse_git_url
@@ -1,9 +1,7 @@
"""Unit tests for readiness probe service.""" """Unit tests for readiness probe service."""
import asyncio
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
import pytest
from src.services.readiness_probe import execute_probe from src.services.readiness_probe import execute_probe
@@ -3,10 +3,7 @@
import os import os
import subprocess import subprocess
import tempfile import tempfile
from unittest.mock import MagicMock, patch
import pytest
from fastapi.testclient import TestClient
from src.api.tool_instances import CreateInstanceRequest from src.api.tool_instances import CreateInstanceRequest
+2 -2
View File
@@ -1,4 +1,4 @@
import axios from "axios"; import axios, { type AxiosRequestConfig } from "axios";
const BASE_URL = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8000"; const BASE_URL = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8000";
@@ -19,7 +19,7 @@ const MAX_RETRIES = 2;
const RETRY_DELAY_MS = 1000; const RETRY_DELAY_MS = 1000;
// Track retry count per request // Track retry count per request
const retryCount = new WeakMap<any, number>(); const retryCount = new WeakMap<AxiosRequestConfig, number>();
apiClient.interceptors.response.use( apiClient.interceptors.response.use(
(response) => response, (response) => response,
+7 -4
View File
@@ -1,3 +1,4 @@
import { AxiosError } from "axios";
import { apiClient } from "./client"; import { apiClient } from "./client";
export interface ToolInstance { export interface ToolInstance {
@@ -80,9 +81,10 @@ export async function startInstance(
{ config_profile_id: configProfileId } { config_profile_id: configProfileId }
); );
return response.data; return response.data;
} catch (error: any) { } catch (error) {
// Retry on network errors (e.g. Docker creating network interfaces) // Retry on network errors (e.g. Docker creating network interfaces)
if (retries > 0 && !error.response) { const axiosError = error as AxiosError;
if (retries > 0 && !axiosError.response) {
await new Promise((r) => setTimeout(r, 1500)); await new Promise((r) => setTimeout(r, 1500));
return startInstance(projectId, repoId, instanceId, configProfileId, retries - 1); return startInstance(projectId, repoId, instanceId, configProfileId, retries - 1);
} }
@@ -114,9 +116,10 @@ export async function restartInstance(
{ config_profile_id: configProfileId } { config_profile_id: configProfileId }
); );
return response.data; return response.data;
} catch (error: any) { } catch (error) {
// Retry on network errors (e.g. Docker creating network interfaces) // Retry on network errors (e.g. Docker creating network interfaces)
if (retries > 0 && !error.response) { const axiosError = error as AxiosError;
if (retries > 0 && !axiosError.response) {
await new Promise((r) => setTimeout(r, 1500)); await new Promise((r) => setTimeout(r, 1500));
return restartInstance(projectId, repoId, instanceId, configProfileId, retries - 1); return restartInstance(projectId, repoId, instanceId, configProfileId, retries - 1);
} }
@@ -1,5 +1,4 @@
import { useState } from "react"; import { useState } from "react";
import { Icon } from "./icon";
interface FormField { interface FormField {
name: string; name: string;
@@ -22,8 +22,6 @@ interface MobileListViewProps {
export const MobileListView: React.FC<MobileListViewProps> = ({ export const MobileListView: React.FC<MobileListViewProps> = ({
items, items,
onItemClick, onItemClick,
onItemDelete,
onItemDuplicate,
emptyMessage = "No items found", emptyMessage = "No items found",
searchPlaceholder = "Search...", searchPlaceholder = "Search...",
onSearch, onSearch,
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { EmptyState, ErrorState, LoadingState } from "../components/data-states"; import { ErrorState, LoadingState } from "../components/data-states";
import { Icon } from "../components/icon"; import { Icon } from "../components/icon";
import { useMobileViewport } from "../hooks/use-mobile-viewport"; import { useMobileViewport } from "../hooks/use-mobile-viewport";
import { extractErrorMessage } from "../utils/errors"; import { extractErrorMessage } from "../utils/errors";
-1
View File
@@ -9,7 +9,6 @@ import { listToolTypes, type ToolType } from "../api/tool_types";
import { updateUserConfig } from "../api/settings"; import { updateUserConfig } from "../api/settings";
import type { Project } from "../types"; import type { Project } from "../types";
import { EmptyState, ErrorState, LoadingState } from "../components/data-states"; import { EmptyState, ErrorState, LoadingState } from "../components/data-states";
import { Icon } from "../components/icon";
import { CreateSessionForm } from "../components/create-session-form"; import { CreateSessionForm } from "../components/create-session-form";
import { SessionList } from "../components/session-list"; import { SessionList } from "../components/session-list";
import { useInstanceActions } from "../hooks/use-instance-actions"; import { useInstanceActions } from "../hooks/use-instance-actions";
+3 -3
View File
@@ -1,8 +1,8 @@
import { useCallback, useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { getCommitDetail, getRepositoryHistory, type CommitDetail, type CommitHistoryEntry, type CommitHistoryResponse } from "../api/git_repositories"; import { getCommitDetail, getRepositoryHistory, type CommitDetail, type CommitHistoryResponse } from "../api/git_repositories";
import { EmptyState, ErrorState, LoadingState } from "../components/data-states"; import { ErrorState, LoadingState } from "../components/data-states";
import { Icon } from "../components/icon"; import { Icon } from "../components/icon";
import { useAsyncData } from "../hooks/use-async-data"; import { useAsyncData } from "../hooks/use-async-data";
+1 -1
View File
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { getProfile, updateProfile, uploadAvatar } from "../api/profile"; import { getProfile, updateProfile, uploadAvatar } from "../api/profile";
import { EmptyState, ErrorState, LoadingState } from "../components/data-states"; import { ErrorState, LoadingState } from "../components/data-states";
import { Icon } from "../components/icon"; import { Icon } from "../components/icon";
import { useAuth } from "../state/auth"; import { useAuth } from "../state/auth";
import { useAsyncData } from "../hooks/use-async-data"; import { useAsyncData } from "../hooks/use-async-data";
+1 -3
View File
@@ -1,5 +1,4 @@
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";
import { listProjects } from "../api/projects"; import { listProjects } from "../api/projects";
import type { Project } from "../types"; import type { Project } from "../types";
@@ -11,7 +10,7 @@ import {
} from "../api/sessions"; } from "../api/sessions";
import { listToolTypes, type ToolType } from "../api/tool_types"; import { listToolTypes, type ToolType } from "../api/tool_types";
import { getUserConfig, updateUserConfig } from "../api/settings"; import { getUserConfig, updateUserConfig } from "../api/settings";
import { EmptyState, ErrorState, LoadingState } from "../components/data-states"; import { ErrorState, LoadingState } from "../components/data-states";
import { CreateSessionForm } from "../components/create-session-form"; import { CreateSessionForm } from "../components/create-session-form";
import { SessionList } from "../components/session-list"; import { SessionList } from "../components/session-list";
import { SessionCard } from "../components/session-card"; import { SessionCard } from "../components/session-card";
@@ -21,7 +20,6 @@ import type { InstanceHealth } from "../api/sessions";
type SessionsStatus = "loading" | "ready" | "error"; type SessionsStatus = "loading" | "ready" | "error";
export const SessionsPage = () => { export const SessionsPage = () => {
const navigate = useNavigate();
const [status, setStatus] = useState<SessionsStatus>("loading"); const [status, setStatus] = useState<SessionsStatus>("loading");
const [sessions, setSessions] = useState<Session[]>([]); const [sessions, setSessions] = useState<Session[]>([]);
const [lastSessionId, setLastSessionId] = useState<string | null>(null); const [lastSessionId, setLastSessionId] = useState<string | null>(null);
+1 -1
View File
@@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
import { Link, Outlet, useLocation, useOutletContext } from "react-router-dom"; import { Link, Outlet, useLocation, useOutletContext } from "react-router-dom";
import { getUserConfig, updateUserConfig, type UserConfig, type UserConfigUpdate } from "../api/settings"; import { getUserConfig, updateUserConfig, type UserConfig, type UserConfigUpdate } from "../api/settings";
import { EmptyState, ErrorState, LoadingState } from "../components/data-states"; import { ErrorState, LoadingState } from "../components/data-states";
import { Icon } from "../components/icon"; import { Icon } from "../components/icon";
import { useAsyncData } from "../hooks/use-async-data"; import { useAsyncData } from "../hooks/use-async-data";
+1 -1
View File
@@ -7,7 +7,7 @@ import { useAsyncData } from "../hooks/use-async-data";
export const SSHKeysPage = () => { export const SSHKeysPage = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const { data: keys, status, error, reload: loadKeys } = useAsyncData<SSHKey[]>(listSSHKeys, []); const { data: keys, status, reload: loadKeys } = useAsyncData<SSHKey[]>(listSSHKeys, []);
const [newKeyName, setNewKeyName] = useState(""); const [newKeyName, setNewKeyName] = useState("");
const [generating, setGenerating] = useState(false); const [generating, setGenerating] = useState(false);
const [signPayloads, setSignPayloads] = useState<Record<string, string>>({}); const [signPayloads, setSignPayloads] = useState<Record<string, string>>({});