6 Commits

Author SHA1 Message Date
2e176f3048 Merge pull request #10 from johndoe6345789/claude/fix-terminal-modal-typescript-X6MVx
Fix terminal input text color styling in TerminalModal
2026-01-30 20:54:42 +00:00
Claude
938cb5a0ba Fix duplicate '& input' property in TerminalModal TextField sx prop
Merged two duplicate '& input' style properties into a single object to resolve TypeScript error during build. The color property is now combined with fontFamily, fontSize, and padding in one declaration.

https://claude.ai/code/session_X6MVx
2026-01-30 20:52:32 +00:00
85819a2f84 Merge pull request #9 from johndoe6345789/claude/cleanup-filesystem-xwToR
Add persistent working directory tracking to terminal sessions
2026-01-30 20:34:22 +00:00
Claude
613c2dc55c Enhance terminal with Ubuntu-style UI and fix shell command issues
Backend improvements:
- Fix execline shadowing standard commands by setting proper PATH
- Add session-based working directory tracking for persistent cd
- Wrap all commands in bash/sh to avoid execline interpreter
- Handle cd commands specially to update session state
- Add robust error handling and fallback to sh

Frontend enhancements:
- Implement Ubuntu aubergine color scheme (#300A24 background)
- Add syntax highlighting for commands, arguments, and errors
- Display working directory in prompt (root@container:path#)
- Auto-scroll terminal output to bottom
- Improve terminal UX with Ubuntu Mono font
- Show current directory in command input prompt

https://claude.ai/code/session_01EvWjmaqmfnKwUTdBUj5JFY
2026-01-30 20:32:13 +00:00
1398b3a82f Merge pull request #8 from johndoe6345789/claude/fix-caprover-docker-U6NbO
Add manual Docker socket mount fix and comprehensive troubleshooting
2026-01-30 19:53:28 +00:00
Claude
f8d2320236 Add manual Docker socket mount fix and comprehensive troubleshooting
Problem: CapRover's serviceUpdateOverride in captain-definition doesn't
always apply automatically, causing "Docker socket NOT found" errors.

Solution: Manual docker service update command to apply the mount.

Changes:
- Added CAPROVER_TROUBLESHOOTING.md with complete step-by-step fix
  - Manual docker service update command
  - Verification steps
  - Common issues and solutions
  - SELinux/AppArmor troubleshooting

- Created fix-caprover-docker-mount.sh automated script
  - Finds service automatically
  - Applies mount
  - Verifies configuration
  - Shows service status

- Enhanced backend/app.py diagnostics
  - Lists /var/run directory contents
  - Shows Docker-related files
  - Better error messages explaining the issue
  - Explicit note when mount is missing

- Updated backend/requirements.txt
  - Docker SDK 7.0.0 -> 7.1.0 (fixes URL scheme error)

- Updated CAPROVER_DEPLOYMENT.md
  - Prominent warning about serviceUpdateOverride limitation
  - New Step 4: Verify and Apply Docker Socket Mount
  - Quick fix command prominently displayed
  - Links to troubleshooting guide
  - Updated troubleshooting section with manual fix

- Updated QUICKSTART.md
  - Warning after backend deployment instructions
  - Quick fix command for both deployment options
  - Links to troubleshooting guide

This provides users with immediate solutions when encountering the
"Cannot connect to Docker" error, which is now properly diagnosed
and can be fixed with a single command.

https://claude.ai/code/session_01NfGGGQ9Zn6ue7PRZpAoB2N
2026-01-30 19:48:53 +00:00
7 changed files with 757 additions and 67 deletions

View File

@@ -71,6 +71,25 @@ The `backend/captain-definition` file contains critical configuration:
5. **Replicas**: Set to 1 (multiple replicas can't share the same socket)
### ⚠️ IMPORTANT: serviceUpdateOverride Limitation
**The `serviceUpdateOverride` in `captain-definition` may not be applied automatically by CapRover.** This is a known limitation with some CapRover versions.
**If you see "Docker socket NOT found" in your logs**, you MUST manually apply the Docker socket mount after deployment.
**Quick Fix** (run on your CapRover server):
```bash
# SSH into your CapRover server
ssh root@your-server.com
# Apply the mount (replace with your service name)
docker service update \
--mount-add type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
srv-captain--terminalbackend
```
**See [CAPROVER_TROUBLESHOOTING.md](CAPROVER_TROUBLESHOOTING.md) for detailed instructions.**
### Security Considerations
**IMPORTANT**: Granting Docker socket access to a container is a security-sensitive operation. The container effectively has root access to the host system.
@@ -116,27 +135,79 @@ caprover deploy
Or manually:
1. Create a tarball: `tar -czf backend.tar.gz .`
1. Create a tarball: `tar -cf backend.tar .`
2. Upload via CapRover dashboard
3. Wait for deployment to complete
#### 4. Verify Deployment
#### 4. **CRITICAL: Verify and Apply Docker Socket Mount**
Check the application logs in CapRover dashboard. You should see:
After deployment, check if the Docker socket is mounted:
**a) Check Application Logs** (in CapRover dashboard):
Look for:
```
=== Docker Environment Diagnosis ===
DOCKER_HOST: unix:///var/run/docker.sock
✓ Docker socket exists at /var/run/docker.sock
Socket permissions: 0o140777
Readable: True
Writable: True
Current user: root (UID: 0, GID: 0)
✓ Successfully connected to Docker using Unix socket
✓ Docker connection verified on startup
```
If you see errors, check the "Troubleshooting" section below.
If you see:
```
✗ Docker socket NOT found at /var/run/docker.sock
```
Then the `serviceUpdateOverride` wasn't applied. **You must manually apply it.**
**b) Manually Apply the Mount** (run on your CapRover server):
```bash
# SSH into your CapRover server
ssh root@your-server.com
# Find your service name
docker service ls | grep terminalbackend
# Should show something like: srv-captain--terminalbackend
# Apply the Docker socket mount
docker service update \
--mount-add type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
srv-captain--terminalbackend
```
**c) Verify the Mount Was Applied**:
```bash
docker service inspect srv-captain--terminalbackend \
--format '{{json .Spec.TaskTemplate.ContainerSpec.Mounts}}' | python3 -m json.tool
```
Should show:
```json
[
{
"Type": "bind",
"Source": "/var/run/docker.sock",
"Target": "/var/run/docker.sock"
}
]
```
**d) Wait for Service Restart**:
The service will automatically restart with the new configuration. Monitor:
```bash
docker service ps srv-captain--terminalbackend
```
**e) Check Logs Again**:
In CapRover dashboard, refresh the logs. You should now see:
```
✓ Docker socket exists at /var/run/docker.sock
✓ Docker connection verified on startup
```
**See [CAPROVER_TROUBLESHOOTING.md](CAPROVER_TROUBLESHOOTING.md) for detailed troubleshooting.**
#### 5. Test the API
@@ -184,33 +255,72 @@ caprover deploy
### "Cannot connect to Docker" Error
If you see this error, check the following:
**This is the most common issue!** CapRover's `serviceUpdateOverride` often doesn't apply automatically.
1. **Verify captain-definition**: Ensure `serviceUpdateOverride` is present and correct
#### Quick Fix (Run on CapRover Server)
2. **Check logs for diagnostics**:
```bash
# SSH into your CapRover server
ssh root@your-server.com
# Apply the Docker socket mount manually
docker service update \
--mount-add type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
srv-captain--terminalbackend
# Verify it worked
docker service inspect srv-captain--terminalbackend \
--format '{{json .Spec.TaskTemplate.ContainerSpec.Mounts}}' | python3 -m json.tool
```
**📖 See [CAPROVER_TROUBLESHOOTING.md](CAPROVER_TROUBLESHOOTING.md) for complete step-by-step instructions.**
#### Diagnostic Checklist
If the quick fix doesn't work, check:
1. **Check logs in CapRover dashboard** for:
```
=== Docker Environment Diagnosis ===
Docker socket NOT found at /var/run/docker.sock
```
Look for:
- Socket existence
- Permissions (should be readable and writable)
- User info (should be root)
3. **Common issues**:
2. **Verify socket exists on host**:
```bash
ls -la /var/run/docker.sock
```
**Socket not found**:
- The mount configuration isn't being applied
- Redeploy the app after updating `captain-definition`
3. **Check service is running as root**:
```bash
docker service inspect srv-captain--terminalbackend \
--format '{{.Spec.TaskTemplate.ContainerSpec.User}}'
```
Should return: `root`
**Permission denied**:
- User isn't root
- Socket permissions are wrong
- Check that `"User": "root"` is in captain-definition
4. **Check Docker version compatibility**:
```bash
docker version
```
**Connection refused**:
- Docker daemon isn't running on the host
- Check CapRover host: `docker info`
5. **Review SELinux/AppArmor** if on RHEL/Ubuntu:
```bash
getenforce # Should be Permissive or Disabled for testing
```
#### Common Issues
**Socket not found**:
- ✅ **Solution**: Manually apply mount (see Quick Fix above)
- The `serviceUpdateOverride` wasn't applied by CapRover
**Permission denied**:
- ✅ **Solution**: Ensure service runs as root:
```bash
docker service update --user root srv-captain--terminalbackend
```
**Connection refused / "Not supported URL scheme http+docker"**:
- ✅ **Solution**: Update docker library version in `requirements.txt` to `docker==7.1.0`
- Redeploy the application
### Viewing Logs

259
CAPROVER_TROUBLESHOOTING.md Normal file
View File

@@ -0,0 +1,259 @@
# CapRover Docker Socket Troubleshooting Guide
This guide helps resolve the "Cannot connect to Docker" error in CapRover deployments.
## Problem: Docker Socket Not Mounted
### Symptoms
In your CapRover application logs, you see:
```
✗ Docker socket NOT found at /var/run/docker.sock
This means the Docker socket mount is NOT configured in CapRover
The serviceUpdateOverride in captain-definition may not be applied
```
### Root Cause
CapRover's `serviceUpdateOverride` in `captain-definition` **may not always be applied automatically**. This is a known limitation with some CapRover versions or configurations.
## Solution: Manual Docker Service Update
You need to manually update the Docker Swarm service to mount the Docker socket.
### Step 1: SSH into Your CapRover Server
```bash
ssh root@your-caprover-server.com
```
### Step 2: Find Your Service Name
List all services to find your backend service:
```bash
docker service ls
```
Look for your service, typically named: `srv-captain--terminalbackend` (or whatever you named your app)
### Step 3: Check Current Mounts
```bash
docker service inspect srv-captain--terminalbackend \
--format '{{json .Spec.TaskTemplate.ContainerSpec.Mounts}}' | python3 -m json.tool
```
If this returns `null` or an empty array, the mount isn't configured.
### Step 4: Apply the Docker Socket Mount
Run this command to mount the Docker socket:
```bash
docker service update \
--mount-add type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
srv-captain--terminalbackend
```
**Important**: Replace `srv-captain--terminalbackend` with your actual service name.
### Step 5: Verify the Mount
```bash
docker service inspect srv-captain--terminalbackend \
--format '{{json .Spec.TaskTemplate.ContainerSpec.Mounts}}' | python3 -m json.tool
```
You should see:
```json
[
{
"Type": "bind",
"Source": "/var/run/docker.sock",
"Target": "/var/run/docker.sock"
}
]
```
### Step 6: Wait for Service Restart
Docker Swarm will automatically restart your service with the new configuration. Monitor the status:
```bash
docker service ps srv-captain--terminalbackend --no-trunc
```
### Step 7: Check Logs
In CapRover dashboard, go to your app and check logs. You should now see:
```
✓ Docker socket exists at /var/run/docker.sock
Socket permissions: 0o140777
Readable: True
Writable: True
✓ Successfully connected to Docker using Unix socket
✓ Docker connection verified on startup
```
### Step 8: Test the API
```bash
curl https://terminalbackend.wardcrew.com/api/health
```
Should return:
```json
{"status":"healthy"}
```
## Automated Script
We've provided a script to automate this process. Download it from the repository:
```bash
# On your CapRover server
wget https://raw.githubusercontent.com/johndoe6345789/docker-swarm-termina/main/fix-caprover-docker-mount.sh
chmod +x fix-caprover-docker-mount.sh
# Run it
./fix-caprover-docker-mount.sh srv-captain--terminalbackend
```
## Alternative Solution: Use CapRover's Service Update Feature
Some CapRover versions support manual service configuration through the UI:
1. Go to CapRover dashboard
2. Navigate to your app
3. Click on "⚙️ Edit Default Nginx Configurations" (or similar settings)
4. Look for advanced Docker/Swarm settings
5. Add the mount configuration
However, this feature availability varies by CapRover version.
## Why serviceUpdateOverride Doesn't Always Work
The `captain-definition` file's `serviceUpdateOverride` field is designed to apply custom Docker Swarm configurations. However:
1. **Timing Issue**: It may only apply on initial deployment, not on updates
2. **CapRover Version**: Older versions may not fully support this feature
3. **Validation**: CapRover may skip configurations it deems risky
4. **Security**: Some CapRover installations restrict privileged configurations
## Persistence
Once you've manually applied the mount using `docker service update`, it will **persist across app updates** as long as you don't:
- Delete and recreate the app in CapRover
- Manually remove the mount
- Use a CapRover feature that resets service configuration
## Additional Troubleshooting
### Issue: "Permission denied" errors
**Solution**: Ensure the service runs as root:
```bash
docker service update \
--user root \
srv-captain--terminalbackend
```
### Issue: Socket exists but connection still fails
**Diagnosis**: Check socket permissions on the host:
```bash
ls -la /var/run/docker.sock
```
Should be:
```
srw-rw---- 1 root docker /var/run/docker.sock
```
**Solution**: Fix permissions:
```bash
chmod 666 /var/run/docker.sock # Temporary - not recommended for production
# OR
chmod 660 /var/run/docker.sock
chown root:docker /var/run/docker.sock
```
### Issue: "Not supported URL scheme http+docker"
This error indicates a docker-py library issue.
**Solution**: Update the docker library version in `requirements.txt`:
```
docker==7.1.0
```
Then redeploy the app.
### Issue: Can't find service name
**Solution**: List all services with details:
```bash
docker service ls --format "table {{.Name}}\t{{.Mode}}\t{{.Replicas}}"
```
Look for services starting with `srv-captain--`
### Issue: Mount applied but service won't start
**Diagnosis**: Check service logs:
```bash
docker service logs srv-captain--terminalbackend --tail 100 --follow
```
**Common causes**:
- SELinux blocking socket access
- AppArmor policies
- Container runtime restrictions
**Solution**: Temporarily disable SELinux/AppArmor to test:
```bash
# SELinux
setenforce 0
# After testing, re-enable
setenforce 1
```
## Production Recommendations
For production deployments:
1. **Use Docker Socket Proxy**: Instead of mounting the raw socket, use a proxy like [tecnativa/docker-socket-proxy](https://github.com/Tecnativa/docker-socket-proxy)
2. **Limit API Access**: Configure proxy to only allow specific Docker API endpoints
3. **Network Isolation**: Deploy backend on a dedicated private network
4. **Audit Logging**: Enable Docker audit logging for socket access
5. **Regular Updates**: Keep Docker, CapRover, and your application updated
## Support
If you're still experiencing issues:
1. **Check CapRover version**: `caprover --version`
2. **Check Docker version**: `docker version`
3. **Review CapRover logs**: `docker service logs captain-captain --tail 100`
4. **Test Docker socket on host**: `docker ps` (should work without errors)
Open an issue with:
- CapRover version
- Docker version
- Complete application logs
- Output of `docker service inspect srv-captain--terminalbackend`

View File

@@ -19,7 +19,21 @@ Get up and running with Docker Swarm Terminal in minutes.
- Go to "Deployment" tab
- Upload the `.tar` file (uncompressed - required by CapRover)
3. Wait for deployment to complete
4. Check logs for: `✓ Docker connection verified on startup`
4. **Check logs for: `✓ Docker connection verified on startup`**
**⚠️ IMPORTANT**: If you see `✗ Docker socket NOT found`, you must manually apply the mount:
```bash
# SSH into your CapRover server
ssh root@your-server.com
# Apply the Docker socket mount
docker service update \
--mount-add type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
srv-captain--terminalbackend
```
See [CAPROVER_TROUBLESHOOTING.md](CAPROVER_TROUBLESHOOTING.md) for details.
### Frontend
@@ -50,6 +64,14 @@ caprover deploy
caprover logs terminalbackend --follow
```
**⚠️ IMPORTANT**: If logs show `✗ Docker socket NOT found`, manually apply the mount on your CapRover server:
```bash
docker service update \
--mount-add type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
srv-captain--terminalbackend
```
### Frontend
```bash

View File

@@ -21,6 +21,8 @@ CORS(app)
# Simple in-memory session storage (in production, use proper session management)
sessions = {}
# Track working directory per session
session_workdirs = {}
# Default credentials (should be environment variables in production)
ADMIN_USERNAME = os.getenv('ADMIN_USERNAME', 'admin')
@@ -39,6 +41,22 @@ def diagnose_docker_environment():
logger.info(f"DOCKER_CERT_PATH: {docker_cert_path}")
logger.info(f"DOCKER_TLS_VERIFY: {docker_tls_verify}")
# Check what's in /var/run
logger.info("Checking /var/run directory contents:")
try:
if os.path.exists('/var/run'):
var_run_contents = os.listdir('/var/run')
logger.info(f" /var/run contains: {var_run_contents}")
# Check for any Docker-related files
docker_related = [f for f in var_run_contents if 'docker' in f.lower()]
if docker_related:
logger.info(f" Docker-related files/dirs found: {docker_related}")
else:
logger.warning(" /var/run directory doesn't exist")
except Exception as e:
logger.error(f" Error reading /var/run: {e}")
# Check Docker socket
socket_path = '/var/run/docker.sock'
logger.info(f"Checking Docker socket at {socket_path}")
@@ -63,6 +81,8 @@ def diagnose_docker_environment():
logger.warning(f"⚠ Socket exists but lacks proper permissions!")
else:
logger.error(f"✗ Docker socket NOT found at {socket_path}")
logger.error(f" This means the Docker socket mount is NOT configured in CapRover")
logger.error(f" The serviceUpdateOverride in captain-definition may not be applied")
# Check current user
import pwd
@@ -213,27 +233,111 @@ def exec_container(container_id):
auth_header = request.headers.get('Authorization')
if not auth_header or not auth_header.startswith('Bearer '):
return jsonify({'error': 'Unauthorized'}), 401
token = auth_header.split(' ')[1]
if token not in sessions:
return jsonify({'error': 'Invalid session'}), 401
data = request.get_json()
command = data.get('command', '/bin/sh')
user_command = data.get('command', 'echo "No command provided"')
client = get_docker_client()
if not client:
return jsonify({'error': 'Cannot connect to Docker'}), 500
try:
container = client.containers.get(container_id)
exec_instance = container.exec_run(command, stdout=True, stderr=True, stdin=True, tty=True)
# Get or initialize session working directory
session_key = f"{token}_{container_id}"
if session_key not in session_workdirs:
# Get container's default working directory or use root
session_workdirs[session_key] = '/'
current_workdir = session_workdirs[session_key]
# Check if this is a cd command
cd_match = user_command.strip()
is_cd_command = cd_match.startswith('cd ')
# If it's a cd command, handle it specially
if is_cd_command:
target_dir = cd_match[3:].strip() or '~'
# Resolve the new directory and update session
resolve_command = f'cd "{current_workdir}" && cd {target_dir} && pwd'
bash_command = [
'/bin/bash',
'-c',
f'export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; {resolve_command}'
]
else:
# Regular command - execute in current working directory
bash_command = [
'/bin/bash',
'-c',
f'export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; cd "{current_workdir}" && {user_command}; echo "::WORKDIR::$(pwd)"'
]
# Try bash first, fallback to sh if bash doesn't exist
try:
exec_instance = container.exec_run(
bash_command,
stdout=True,
stderr=True,
stdin=False,
tty=True,
environment={'TERM': 'xterm-256color', 'LANG': 'C.UTF-8'}
)
except Exception as bash_error:
logger.warning(f"Bash execution failed, trying sh: {bash_error}")
# Fallback to sh
if is_cd_command:
target_dir = cd_match[3:].strip() or '~'
resolve_command = f'cd "{current_workdir}" && cd {target_dir} && pwd'
sh_command = ['/bin/sh', '-c', f'export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; {resolve_command}']
else:
sh_command = ['/bin/sh', '-c', f'export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; cd "{current_workdir}" && {user_command}; echo "::WORKDIR::$(pwd)"']
exec_instance = container.exec_run(
sh_command,
stdout=True,
stderr=True,
stdin=False,
tty=True,
environment={'TERM': 'xterm-256color', 'LANG': 'C.UTF-8'}
)
# Decode output with error handling
output = ''
if exec_instance.output:
try:
output = exec_instance.output.decode('utf-8')
except UnicodeDecodeError:
# Try latin-1 as fallback
output = exec_instance.output.decode('latin-1', errors='replace')
# Extract and update working directory from output
new_workdir = current_workdir
if is_cd_command:
# For cd commands, the output is the new pwd
new_workdir = output.strip()
session_workdirs[session_key] = new_workdir
output = '' # Don't show the pwd output for cd
else:
# Extract workdir marker from output
if '::WORKDIR::' in output:
parts = output.rsplit('::WORKDIR::', 1)
output = parts[0]
new_workdir = parts[1].strip()
session_workdirs[session_key] = new_workdir
return jsonify({
'output': exec_instance.output.decode('utf-8') if exec_instance.output else '',
'exit_code': exec_instance.exit_code
'output': output,
'exit_code': exec_instance.exit_code,
'workdir': new_workdir
})
except Exception as e:
logger.error(f"Error executing command: {e}", exc_info=True)
return jsonify({'error': str(e)}), 500
@app.route('/api/health', methods=['GET'])

View File

@@ -1,4 +1,4 @@
Flask==3.0.0
Flask-CORS==6.0.0
python-dotenv==1.0.0
docker==7.0.0
docker==7.1.0

View File

@@ -0,0 +1,62 @@
#!/bin/bash
# Script to manually apply Docker socket mount to CapRover service
# Run this on your CapRover server if serviceUpdateOverride doesn't work
set -e
# Service name - update this to match your CapRover app name
SERVICE_NAME="${1:-srv-captain--terminalbackend}"
echo "=== CapRover Docker Socket Mount Fix ==="
echo "Service name: $SERVICE_NAME"
echo ""
# Check if service exists
if ! docker service ls | grep -q "$SERVICE_NAME"; then
echo "❌ Error: Service '$SERVICE_NAME' not found"
echo ""
echo "Available services:"
docker service ls --format "{{.Name}}"
echo ""
echo "Usage: $0 <service-name>"
echo "Example: $0 srv-captain--terminalbackend"
exit 1
fi
echo "✓ Service found"
echo ""
# Show current service configuration
echo "Current service mounts:"
docker service inspect "$SERVICE_NAME" --format '{{json .Spec.TaskTemplate.ContainerSpec.Mounts}}' | python3 -m json.tool || echo "No mounts configured"
echo ""
# Update service with Docker socket mount
echo "Applying Docker socket mount..."
docker service update \
--mount-add type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \
"$SERVICE_NAME"
echo ""
echo "✓ Mount applied successfully"
echo ""
# Verify the update
echo "Verifying updated configuration:"
docker service inspect "$SERVICE_NAME" --format '{{json .Spec.TaskTemplate.ContainerSpec.Mounts}}' | python3 -m json.tool
echo ""
# Check service status
echo "Service status:"
docker service ps "$SERVICE_NAME" --no-trunc
echo ""
echo "=== Next Steps ==="
echo "1. Wait for the service to restart (check logs in CapRover dashboard)"
echo "2. Look for this in logs: '✓ Docker socket exists at /var/run/docker.sock'"
echo "3. Test the API: curl https://your-backend-domain.com/api/health"
echo ""
echo "If you still see errors, check:"
echo " - The service is running as root (not restricted by CapRover)"
echo " - SELinux/AppArmor isn't blocking socket access"
echo " - Docker socket exists on host: ls -la /var/run/docker.sock"

View File

@@ -1,6 +1,6 @@
'use client';
import React, { useState } from 'react';
import React, { useState, useRef, useEffect } from 'react';
import {
Dialog,
DialogTitle,
@@ -23,6 +23,12 @@ interface TerminalModalProps {
containerId: string;
}
interface OutputLine {
type: 'command' | 'output' | 'error';
content: string;
workdir?: string;
}
export default function TerminalModal({
open,
onClose,
@@ -30,20 +36,50 @@ export default function TerminalModal({
containerId,
}: TerminalModalProps) {
const [command, setCommand] = useState('');
const [output, setOutput] = useState<string[]>([]);
const [output, setOutput] = useState<OutputLine[]>([]);
const [isExecuting, setIsExecuting] = useState(false);
const [workdir, setWorkdir] = useState('/');
const outputRef = useRef<HTMLDivElement>(null);
// Auto-scroll to bottom when output changes
useEffect(() => {
if (outputRef.current) {
outputRef.current.scrollTop = outputRef.current.scrollHeight;
}
}, [output]);
const handleExecute = async () => {
if (!command.trim()) return;
setIsExecuting(true);
setOutput((prev) => [...prev, `$ ${command}`]);
// Add command to output with current working directory
setOutput((prev) => [...prev, {
type: 'command',
content: command,
workdir: workdir
}]);
try {
const result = await apiClient.executeCommand(containerId, command);
setOutput((prev) => [...prev, result.output || '(no output)']);
// Update working directory if provided
if (result.workdir) {
setWorkdir(result.workdir);
}
// Add command output
if (result.output && result.output.trim()) {
setOutput((prev) => [...prev, {
type: result.exit_code === 0 ? 'output' : 'error',
content: result.output
}]);
}
} catch (error) {
setOutput((prev) => [...prev, `Error: ${error instanceof Error ? error.message : 'Unknown error'}`]);
setOutput((prev) => [...prev, {
type: 'error',
content: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`
}]);
} finally {
setIsExecuting(false);
setCommand('');
@@ -60,9 +96,50 @@ export default function TerminalModal({
const handleClose = () => {
setOutput([]);
setCommand('');
setWorkdir('/');
onClose();
};
const formatPrompt = (workdir: string) => {
// Shorten workdir if it's too long (show ~ for home, or just basename)
let displayDir = workdir;
if (workdir.length > 30) {
const parts = workdir.split('/');
displayDir = '.../' + parts[parts.length - 1];
}
return `root@${containerName}:${displayDir}#`;
};
const highlightCommand = (line: OutputLine) => {
if (line.type === 'command') {
const prompt = formatPrompt(line.workdir || '/');
const parts = line.content.split(' ');
const cmd = parts[0];
const args = parts.slice(1).join(' ');
return (
<div style={{ marginBottom: '4px' }}>
<span style={{ color: '#8BE9FD', fontWeight: 'bold' }}>{prompt}</span>
{' '}
<span style={{ color: '#50FA7B', fontWeight: 'bold' }}>{cmd}</span>
{args && <span style={{ color: '#F8F8F2' }}> {args}</span>}
</div>
);
} else if (line.type === 'error') {
return (
<div style={{ color: '#FF5555', marginBottom: '2px' }}>
{line.content}
</div>
);
} else {
return (
<div style={{ color: '#F8F8F2', marginBottom: '2px', whiteSpace: 'pre-wrap' }}>
{line.content}
</div>
);
}
};
return (
<Dialog
open={open}
@@ -94,65 +171,121 @@ export default function TerminalModal({
<DialogContent dividers>
<Paper
ref={outputRef}
elevation={0}
sx={{
backgroundColor: '#0d1117',
color: '#c9d1d9',
fontFamily: '"JetBrains Mono", monospace',
fontSize: '0.875rem',
backgroundColor: '#300A24',
color: '#F8F8F2',
fontFamily: '"Ubuntu Mono", "Courier New", monospace',
fontSize: '14px',
padding: 2,
minHeight: '300px',
maxHeight: '400px',
minHeight: '400px',
maxHeight: '500px',
overflowY: 'auto',
mb: 2,
border: '1px solid #5E2750',
borderRadius: '4px',
'&::-webkit-scrollbar': {
width: '8px',
width: '10px',
},
'&::-webkit-scrollbar-track': {
background: '#161b22',
background: '#2C0922',
},
'&::-webkit-scrollbar-thumb': {
background: '#30363d',
borderRadius: '4px',
background: '#5E2750',
borderRadius: '5px',
'&:hover': {
background: '#772953',
}
},
}}
>
{output.length === 0 ? (
<Typography color="text.secondary" sx={{ fontFamily: 'inherit' }}>
Connected to {containerName}. Enter a command to start...
</Typography>
<Box>
<Typography sx={{
color: '#8BE9FD',
fontFamily: 'inherit',
fontSize: '13px',
mb: 1
}}>
Ubuntu-style Terminal - Connected to <span style={{ color: '#50FA7B', fontWeight: 'bold' }}>{containerName}</span>
</Typography>
<Typography sx={{
color: '#6272A4',
fontFamily: 'inherit',
fontSize: '12px'
}}>
Type a command and press Enter or click Execute...
</Typography>
</Box>
) : (
<Box component="pre" sx={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
{output.join('\n')}
<Box>
{output.map((line, index) => (
<React.Fragment key={index}>
{highlightCommand(line)}
</React.Fragment>
))}
</Box>
)}
</Paper>
<Box sx={{ display: 'flex', gap: 1 }}>
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>
<Typography sx={{
fontFamily: '"Ubuntu Mono", monospace',
fontSize: '14px',
color: '#8BE9FD',
fontWeight: 'bold',
whiteSpace: 'nowrap'
}}>
{formatPrompt(workdir)}
</Typography>
<TextField
fullWidth
value={command}
onChange={(e) => setCommand(e.target.value)}
onKeyPress={handleKeyPress}
placeholder="Enter command (e.g., ls, pwd, echo 'hello')"
placeholder="ls -la"
disabled={isExecuting}
variant="outlined"
size="small"
autoFocus
sx={{
fontFamily: '"JetBrains Mono", monospace',
fontFamily: '"Ubuntu Mono", monospace',
'& input': {
fontFamily: '"JetBrains Mono", monospace',
fontFamily: '"Ubuntu Mono", monospace',
fontSize: '14px',
padding: '8px 12px',
color: '#F8F8F2',
},
'& .MuiOutlinedInput-root': {
backgroundColor: '#1E1E1E',
'& fieldset': {
borderColor: '#5E2750',
},
'&:hover fieldset': {
borderColor: '#772953',
},
'&.Mui-focused fieldset': {
borderColor: '#8BE9FD',
},
},
}}
/>
<Button
variant="contained"
color="secondary"
onClick={handleExecute}
disabled={isExecuting || !command.trim()}
startIcon={<Send />}
sx={{
backgroundColor: '#5E2750',
'&:hover': {
backgroundColor: '#772953',
},
textTransform: 'none',
fontWeight: 'bold',
}}
>
Execute
Run
</Button>
</Box>
</DialogContent>