feat: implement tool instances backend and session navigation
Backend: - Create ToolInstance model with status tracking - Add Alembic migration for tool_instances table - Create Docker service for compose template rendering and container execution - Add CRUD API endpoints for tool instances - Add lifecycle endpoints (start/stop/restart) - Add user sessions endpoint for navigation - Register routers in main.py Frontend: - Create SessionsProvider with React context - Create sessions API client - Update AppShell with sessions section in navigation - Add session status indicators and polling - Add CSS for session navigation Quality gates: typecheck ✓, lint ✓, build ✓
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
name: tool-instances
|
||||
@@ -0,0 +1,146 @@
|
||||
# Tool Instances - Design
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Tool Instance System
|
||||
├── Backend
|
||||
│ ├── ToolInstance Model
|
||||
│ ├── Session API (CRUD + lifecycle)
|
||||
│ ├── Docker Service (compose execution)
|
||||
│ └── Status Polling
|
||||
├── Frontend
|
||||
│ ├── SessionStore (active sessions)
|
||||
│ ├── AppShell Integration (nav entries)
|
||||
│ ├── Instance Manager (repo page)
|
||||
│ └── Session Launcher (create dialog)
|
||||
└── Docker
|
||||
├── Compose Template Rendering
|
||||
├── Container Execution
|
||||
└── Volume Management
|
||||
```
|
||||
|
||||
## Data Model
|
||||
|
||||
### ToolInstance
|
||||
```python
|
||||
class ToolInstance(Base):
|
||||
id: UUID
|
||||
name: str # Generated: "vscode-myrepo-abc123"
|
||||
display_name: str # User-friendly name
|
||||
tool_type_id: UUID -> ToolType
|
||||
repository_id: UUID -> GitRepository
|
||||
project_id: UUID -> Project
|
||||
owner_id: UUID -> User
|
||||
status: str # pending, building, running, stopped, error
|
||||
container_id: str | None
|
||||
compose_path: str | None # Path to rendered compose file
|
||||
url: str | None # Access URL
|
||||
port: int | None
|
||||
last_started_at: datetime | None
|
||||
last_stopped_at: datetime | None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
```
|
||||
|
||||
## API Design
|
||||
|
||||
### Endpoints
|
||||
```
|
||||
POST /projects/{id}/repositories/{id}/instances
|
||||
GET /projects/{id}/repositories/{id}/instances
|
||||
GET /projects/{id}/repositories/{id}/instances/{id}
|
||||
PUT /projects/{id}/repositories/{id}/instances/{id}
|
||||
DELETE /projects/{id}/repositories/{id}/instances/{id}
|
||||
POST /projects/{id}/repositories/{id}/instances/{id}/start
|
||||
POST /projects/{id}/repositories/{id}/instances/{id}/stop
|
||||
POST /projects/{id}/repositories/{id}/instances/{id}/restart
|
||||
GET /projects/{id}/repositories/{id}/instances/{id}/status
|
||||
GET /projects/{id}/repositories/{id}/instances/{id}/logs
|
||||
GET /users/me/sessions # Active sessions for nav
|
||||
```
|
||||
|
||||
## Docker Integration
|
||||
|
||||
### Compose Template Rendering
|
||||
```yaml
|
||||
# Template variables:
|
||||
# {{REPO_PATH}} - Absolute path to repo
|
||||
# {{INSTANCE_NAME}} - Unique instance name
|
||||
# {{TOOL_PORT}} - Exposed port
|
||||
|
||||
services:
|
||||
{{INSTANCE_NAME}}:
|
||||
image: codercom/code-server:latest
|
||||
volumes:
|
||||
- {{REPO_PATH}}:/workspace
|
||||
ports:
|
||||
- "{{TOOL_PORT}}:8080"
|
||||
environment:
|
||||
- PASSWORD={{INSTANCE_NAME}}
|
||||
```
|
||||
|
||||
### Execution Flow
|
||||
1. Create instance directory: `data/instances/{instance_id}/`
|
||||
2. Render compose file to `docker-compose.yml`
|
||||
3. Run `docker compose -f {path} up -d`
|
||||
4. Capture container ID from output
|
||||
5. Poll status until running or error
|
||||
|
||||
## Frontend Integration
|
||||
|
||||
### Session Store
|
||||
```typescript
|
||||
interface Session {
|
||||
id: string;
|
||||
name: string;
|
||||
displayName: string;
|
||||
toolType: string;
|
||||
toolIcon: string;
|
||||
repositoryId: string;
|
||||
projectId: string;
|
||||
status: "pending" | "running" | "stopped" | "error";
|
||||
url: string | null;
|
||||
}
|
||||
|
||||
const useSessions = () => {
|
||||
const sessions = useAtom(sessionsAtom);
|
||||
const addSession = (session: Session) => { ... };
|
||||
const removeSession = (id: string) => { ... };
|
||||
const updateStatus = (id: string, status: string) => { ... };
|
||||
return { sessions, addSession, removeSession, updateStatus };
|
||||
};
|
||||
```
|
||||
|
||||
### AppShell Navigation
|
||||
- Add "Sessions" section in nav
|
||||
- Show active sessions with tool icons
|
||||
- Session status indicator (green dot for running)
|
||||
- Click opens tool in new tab
|
||||
- Dropdown for managing sessions
|
||||
|
||||
### Repository Page
|
||||
- "Launch Tool" button
|
||||
- Dialog to select tool type
|
||||
- Instance list with status/actions
|
||||
- Quick actions: start/stop/delete
|
||||
|
||||
## State Machine
|
||||
|
||||
```
|
||||
[create] -> pending -> [docker up] -> building -> [container running] -> running
|
||||
|
|
||||
v
|
||||
[docker error] -> error
|
||||
|
||||
[running] -> [stop] -> stopped
|
||||
[stopped] -> [start] -> pending -> building -> running
|
||||
[any] -> [delete] -> [docker down] -> deleted
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
- Only repository owner can create instances
|
||||
- Instances run in isolated Docker networks
|
||||
- No privileged containers
|
||||
- Resource limits (CPU, memory) on containers
|
||||
@@ -0,0 +1,50 @@
|
||||
# Tool Instances with Sessions
|
||||
|
||||
## Problem
|
||||
|
||||
Users currently have no way to launch development tools (code-server, Jupyter, etc.) directly from their repositories. The tool-types system exists but cannot create running container instances. Additionally, there's no concept of a "session" - a running tool linked to a specific repo that appears in navigation for quick access.
|
||||
|
||||
## Solution
|
||||
|
||||
Implement a complete tool instance management system with sessions:
|
||||
|
||||
1. **ToolInstance Model** - Links a ToolType to a GitRepository with status tracking
|
||||
2. **Session Concept** - A running ToolInstance that gets a top-level navigation entry
|
||||
3. **Docker Integration** - Render compose templates and execute docker compose commands
|
||||
4. **Lifecycle Management** - Start, stop, restart, and delete instances
|
||||
5. **Navigation Integration** - Active sessions appear in the app shell for quick access
|
||||
|
||||
## Key Features
|
||||
|
||||
### Tool Instance Creation
|
||||
- Select a tool type and repository
|
||||
- Generate unique instance name
|
||||
- Render Docker Compose template with variables
|
||||
- Execute `docker compose up -d`
|
||||
- Store container metadata
|
||||
|
||||
### Session Management
|
||||
- Sessions are active/running instances
|
||||
- Each session gets a top-level nav entry with the tool icon
|
||||
- Session dropdown in app shell shows active sessions
|
||||
- Clicking a session opens the tool in a new tab/window
|
||||
|
||||
### Lifecycle Operations
|
||||
- Start: `docker compose start`
|
||||
- Stop: `docker compose stop`
|
||||
- Restart: `docker compose restart`
|
||||
- Delete: `docker compose down -v` + remove DB record
|
||||
|
||||
### Status Monitoring
|
||||
- pending, building, running, stopped, error
|
||||
- Real-time status via Docker API
|
||||
- Last accessed timestamp
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [ ] Create tool instances from repository page
|
||||
- [ ] Sessions appear in top-level navigation
|
||||
- [ ] Start/stop/restart/delete instances
|
||||
- [ ] Status monitoring works
|
||||
- [ ] Docker Compose templates render correctly
|
||||
- [ ] Navigation updates when sessions change
|
||||
@@ -0,0 +1,147 @@
|
||||
# Tool Instances Specification
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
1. **ToolInstance Model**: Store instance metadata with status tracking
|
||||
2. **Session API**: CRUD operations + lifecycle (start/stop/restart/delete)
|
||||
3. **Docker Integration**: Render compose templates and execute commands
|
||||
4. **Status Monitoring**: Real-time container status polling
|
||||
5. **Log Access**: View container logs (last 100 lines)
|
||||
6. **Session Navigation**: Active sessions appear in app shell
|
||||
7. **URL Generation**: Unique access URLs for each running instance
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
1. **Security**: Isolated containers, no privileged mode
|
||||
2. **Resource Limits**: CPU and memory constraints
|
||||
3. **Error Handling**: Graceful failure with cleanup
|
||||
4. **Performance**: Start time < 30 seconds
|
||||
|
||||
## API Specification
|
||||
|
||||
### Create Instance
|
||||
```
|
||||
POST /projects/{project_id}/repositories/{repo_id}/instances
|
||||
Body: {
|
||||
tool_type_id: string,
|
||||
display_name: string (optional)
|
||||
}
|
||||
Response: {
|
||||
id: string,
|
||||
name: string,
|
||||
display_name: string,
|
||||
tool_type_id: string,
|
||||
status: "pending",
|
||||
created_at: string
|
||||
}
|
||||
```
|
||||
|
||||
### List Instances
|
||||
```
|
||||
GET /projects/{project_id}/repositories/{repo_id}/instances
|
||||
Response: {
|
||||
instances: [...]
|
||||
}
|
||||
```
|
||||
|
||||
### Get Instance
|
||||
```
|
||||
GET /projects/{project_id}/repositories/{repo_id}/instances/{instance_id}
|
||||
Response: {
|
||||
id: string,
|
||||
name: string,
|
||||
display_name: string,
|
||||
status: string,
|
||||
container_id: string | null,
|
||||
url: string | null,
|
||||
port: number | null,
|
||||
last_started_at: string | null,
|
||||
last_stopped_at: string | null,
|
||||
created_at: string
|
||||
}
|
||||
```
|
||||
|
||||
### Lifecycle Operations
|
||||
```
|
||||
POST /projects/{project_id}/repositories/{repo_id}/instances/{instance_id}/start
|
||||
POST /projects/{project_id}/repositories/{repo_id}/instances/{instance_id}/stop
|
||||
POST /projects/{project_id}/repositories/{repo_id}/instances/{instance_id}/restart
|
||||
Response: { status: string }
|
||||
```
|
||||
|
||||
### Delete Instance
|
||||
```
|
||||
DELETE /projects/{project_id}/repositories/{repo_id}/instances/{instance_id}
|
||||
Response: 204 No Content
|
||||
```
|
||||
|
||||
### Get User Sessions
|
||||
```
|
||||
GET /users/me/sessions
|
||||
Response: {
|
||||
sessions: [
|
||||
{
|
||||
id: string,
|
||||
display_name: string,
|
||||
tool_type_name: string,
|
||||
tool_icon: string,
|
||||
repository_name: string,
|
||||
project_name: string,
|
||||
status: string,
|
||||
url: string | null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Docker Compose Template Variables
|
||||
|
||||
- `{{REPO_PATH}}`: Absolute path to git repository on host
|
||||
- `{{INSTANCE_NAME}}`: Unique instance identifier
|
||||
- `{{INSTANCE_ID}}`: UUID of the instance
|
||||
- `{{TOOL_PORT}}`: Assigned port for the tool
|
||||
- `{{USER_ID}}`: Owner user ID
|
||||
- `{{PROJECT_ID}}`: Project ID
|
||||
|
||||
## Status Values
|
||||
|
||||
- `pending`: Instance created, waiting to start
|
||||
- `building`: Docker compose up in progress
|
||||
- `running`: Container is running
|
||||
- `stopped`: Container stopped
|
||||
- `error`: Container failed to start or crashed
|
||||
|
||||
## Database Schema
|
||||
|
||||
```sql
|
||||
CREATE TABLE tool_instances (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
display_name VARCHAR(255) NOT NULL,
|
||||
tool_type_id UUID NOT NULL REFERENCES tool_types(id),
|
||||
repository_id UUID NOT NULL REFERENCES git_repositories(id),
|
||||
project_id UUID NOT NULL REFERENCES projects(id),
|
||||
owner_id UUID NOT NULL REFERENCES users(id),
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
||||
container_id VARCHAR(255),
|
||||
compose_path VARCHAR(1024),
|
||||
url VARCHAR(1024),
|
||||
port INTEGER,
|
||||
last_started_at TIMESTAMP WITH TIME ZONE,
|
||||
last_stopped_at TIMESTAMP WITH TIME ZONE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_tool_instances_owner ON tool_instances(owner_id);
|
||||
CREATE INDEX idx_tool_instances_repo ON tool_instances(repository_id);
|
||||
CREATE INDEX idx_tool_instances_status ON tool_instances(status);
|
||||
```
|
||||
|
||||
## Testing Requirements
|
||||
|
||||
1. **Unit Tests**: Docker command generation, template rendering
|
||||
2. **Integration Tests**: API endpoints, database operations
|
||||
3. **Manual Testing**: Container lifecycle, navigation updates
|
||||
@@ -0,0 +1,157 @@
|
||||
# Tool Instances - Tasks
|
||||
|
||||
## Phase 1: Backend Model & Database
|
||||
|
||||
- [ ] **Task 1.1**: Create ToolInstance model
|
||||
- Create `src/models/tool_instance.py`
|
||||
- Fields: id, name, display_name, tool_type_id, repository_id, project_id, owner_id, status, container_id, compose_path, url, port, timestamps
|
||||
- Add relationship to ToolType and GitRepository
|
||||
- Add to `models/__init__.py`
|
||||
|
||||
- [ ] **Task 1.2**: Create Alembic migration
|
||||
- Generate migration for tool_instances table
|
||||
- Add indexes for owner_id, repository_id, status
|
||||
|
||||
## Phase 2: Backend Docker Service
|
||||
|
||||
- [ ] **Task 2.1**: Create Docker service
|
||||
- Create `src/services/docker.py`
|
||||
- Function: `render_compose_template(template, variables)`
|
||||
- Function: `execute_compose_command(compose_path, action)`
|
||||
- Function: `get_container_status(container_id)`
|
||||
- Function: `get_container_logs(container_id, tail=100)`
|
||||
- Error handling and cleanup
|
||||
|
||||
- [ ] **Task 2.2**: Create instance directory structure
|
||||
- Base path: `data/instances/{instance_id}/`
|
||||
- Render compose file to `docker-compose.yml`
|
||||
- Ensure directory exists and is writable
|
||||
|
||||
## Phase 3: Backend API
|
||||
|
||||
- [ ] **Task 3.1**: Create instances API module
|
||||
- Create `src/api/tool_instances.py`
|
||||
- Import required dependencies
|
||||
|
||||
- [ ] **Task 3.2**: Implement create instance endpoint
|
||||
- POST /projects/{id}/repositories/{id}/instances
|
||||
- Validate tool_type_id exists
|
||||
- Generate unique instance name
|
||||
- Render compose template
|
||||
- Save to database (status: pending)
|
||||
- Return instance metadata
|
||||
|
||||
- [ ] **Task 3.3**: Implement list instances endpoint
|
||||
- GET /projects/{id}/repositories/{id}/instances
|
||||
- Filter by repository
|
||||
- Include tool type info
|
||||
|
||||
- [ ] **Task 3.4**: Implement get instance endpoint
|
||||
- GET /projects/{id}/repositories/{id}/instances/{id}
|
||||
- Include real-time status from Docker
|
||||
|
||||
- [ ] **Task 3.5**: Implement lifecycle endpoints
|
||||
- POST .../start - execute docker compose up
|
||||
- POST .../stop - execute docker compose stop
|
||||
- POST .../restart - execute docker compose restart
|
||||
- Update status in database
|
||||
|
||||
- [ ] **Task 3.6**: Implement delete instance endpoint
|
||||
- DELETE /projects/{id}/repositories/{id}/instances/{id}
|
||||
- Execute docker compose down -v
|
||||
- Remove instance directory
|
||||
- Delete database record
|
||||
|
||||
- [ ] **Task 3.7**: Implement user sessions endpoint
|
||||
- GET /users/me/sessions
|
||||
- Return all running instances for current user
|
||||
- Include tool type icon and names
|
||||
|
||||
- [ ] **Task 3.8**: Register router
|
||||
- Add tool_instances router to main.py
|
||||
|
||||
## Phase 4: Frontend State Management
|
||||
|
||||
- [ ] **Task 4.1**: Create session store
|
||||
- Create `src/state/sessions.ts`
|
||||
- Define Session interface
|
||||
- Create atom for sessions list
|
||||
- Add helper functions
|
||||
|
||||
- [ ] **Task 4.2**: Create sessions API client
|
||||
- Create `src/api/sessions.ts`
|
||||
- Functions: list, create, start, stop, restart, delete, getStatus
|
||||
- TypeScript interfaces
|
||||
|
||||
## Phase 5: Frontend Components
|
||||
|
||||
- [ ] **Task 5.1**: Update AppShell with sessions
|
||||
- Add "Sessions" section in navigation
|
||||
- Show active sessions with icons
|
||||
- Status indicators (green dot)
|
||||
- Click opens tool URL
|
||||
|
||||
- [ ] **Task 5.2**: Create LaunchToolDialog
|
||||
- Select tool type from dropdown
|
||||
- Enter display name (optional)
|
||||
- Create instance on submit
|
||||
- Show creation progress
|
||||
|
||||
- [ ] **Task 5.3**: Create InstanceList component
|
||||
- List instances for a repository
|
||||
- Show status, name, tool type
|
||||
- Action buttons: start/stop/restart/delete
|
||||
- Open URL button
|
||||
|
||||
- [ ] **Task 5.4**: Create InstanceCard component
|
||||
- Compact card showing instance info
|
||||
- Status badge
|
||||
- Quick actions
|
||||
|
||||
## Phase 6: Frontend Pages
|
||||
|
||||
- [ ] **Task 6.1**: Add instances to repository page
|
||||
- Add "Instances" tab or section
|
||||
- Show InstanceList
|
||||
- Add "Launch Tool" button
|
||||
|
||||
- [ ] **Task 6.2**: Create sessions dropdown
|
||||
- Add to header or nav
|
||||
- Quick access to active sessions
|
||||
- Show status indicators
|
||||
|
||||
## Phase 7: Integration & Polish
|
||||
|
||||
- [ ] **Task 7.1**: Add instance status polling
|
||||
- Poll status every 5 seconds
|
||||
- Update session store
|
||||
- Reflect in UI
|
||||
|
||||
- [ ] **Task 7.2**: Add error handling
|
||||
- Docker failures
|
||||
- Template rendering errors
|
||||
- Network errors
|
||||
|
||||
- [ ] **Task 7.3**: Add loading states
|
||||
- Creating instance
|
||||
- Starting/stopping
|
||||
- Deleting
|
||||
|
||||
## Phase 8: Quality Gates
|
||||
|
||||
- [ ] **Task 8.1**: Backend tests
|
||||
- ruff check
|
||||
- mypy check
|
||||
- pytest
|
||||
|
||||
- [ ] **Task 8.2**: Frontend tests
|
||||
- typecheck
|
||||
- lint
|
||||
- build
|
||||
|
||||
- [ ] **Task 8.3**: Manual testing
|
||||
- Create instance
|
||||
- Start/stop/restart
|
||||
- Delete
|
||||
- Navigation updates
|
||||
- Status polling
|
||||
Reference in New Issue
Block a user