feat: add Docker container management functionality
- Implement DockerHandler to handle API requests for listing Docker containers. - Create DockerService to interact with Docker API and retrieve container information. - Add routes for Docker container management in the API. - Introduce frontend API integration for Docker container listing. - Enhance ProxyHostForm to allow quick selection of Docker containers. - Update Docker-related tests to ensure functionality and error handling. - Modify Docker Compose files to enable Docker socket access for local and remote environments. - Add TypeScript configurations for improved build processes.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useState } from 'react'
|
||||
import type { ProxyHost } from '../api/proxyHosts'
|
||||
import { useRemoteServers } from '../hooks/useRemoteServers'
|
||||
import { useDocker } from '../hooks/useDocker'
|
||||
|
||||
interface ProxyHostFormProps {
|
||||
host?: ProxyHost
|
||||
@@ -25,6 +26,9 @@ export default function ProxyHostForm({ host, onSubmit, onCancel }: ProxyHostFor
|
||||
})
|
||||
|
||||
const { servers: remoteServers } = useRemoteServers()
|
||||
const [dockerHost, setDockerHost] = useState('')
|
||||
const [showDockerHost, setShowDockerHost] = useState(false)
|
||||
const { containers: dockerContainers, isLoading: dockerLoading, error: dockerError } = useDocker(dockerHost)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
@@ -54,6 +58,23 @@ export default function ProxyHostForm({ host, onSubmit, onCancel }: ProxyHostFor
|
||||
}
|
||||
}
|
||||
|
||||
const handleContainerSelect = (containerId: string) => {
|
||||
const container = dockerContainers.find(c => c.id === containerId)
|
||||
if (container) {
|
||||
// Prefer internal IP if available, otherwise use container name
|
||||
const host = container.ip || container.names[0]
|
||||
// Use the first exposed port if available, otherwise default to 80
|
||||
const port = container.ports && container.ports.length > 0 ? container.ports[0].private_port : 80
|
||||
|
||||
setFormData({
|
||||
...formData,
|
||||
forward_host: host,
|
||||
forward_port: port,
|
||||
forward_scheme: 'http',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
|
||||
<div className="bg-dark-card rounded-lg border border-gray-800 max-w-2xl w-full max-h-[90vh] overflow-y-auto">
|
||||
@@ -85,26 +106,75 @@ export default function ProxyHostForm({ host, onSubmit, onCancel }: ProxyHostFor
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Remote Server Quick Select */}
|
||||
{remoteServers.length > 0 && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{/* Remote Server Quick Select */}
|
||||
{remoteServers.length > 0 && (
|
||||
<div>
|
||||
<label htmlFor="quick-select-server" className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Quick Select: Remote Server
|
||||
</label>
|
||||
<select
|
||||
id="quick-select-server"
|
||||
onChange={e => handleServerSelect(e.target.value)}
|
||||
className="w-full bg-gray-900 border border-gray-700 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="">-- Select a server --</option>
|
||||
{remoteServers.map(server => (
|
||||
<option key={server.uuid} value={server.uuid}>
|
||||
{server.name} ({server.host}:{server.port})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Docker Container Quick Select */}
|
||||
<div>
|
||||
<label htmlFor="quick-select" className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Quick Select from Remote Servers
|
||||
</label>
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<label htmlFor="quick-select-docker" className="block text-sm font-medium text-gray-300">
|
||||
Quick Select: Container
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowDockerHost(!showDockerHost)}
|
||||
className="text-xs text-blue-400 hover:text-blue-300"
|
||||
>
|
||||
{showDockerHost ? 'Hide Remote' : 'Remote Docker?'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{showDockerHost && (
|
||||
<input
|
||||
type="text"
|
||||
placeholder="tcp://100.x.y.z:2375"
|
||||
value={dockerHost}
|
||||
onChange={(e) => setDockerHost(e.target.value)}
|
||||
className="w-full bg-gray-900 border border-gray-700 rounded-lg px-4 py-2 text-white text-sm mb-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
)}
|
||||
|
||||
<select
|
||||
id="quick-select"
|
||||
onChange={e => handleServerSelect(e.target.value)}
|
||||
className="w-full bg-gray-900 border border-gray-700 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
id="quick-select-docker"
|
||||
onChange={e => handleContainerSelect(e.target.value)}
|
||||
disabled={dockerLoading}
|
||||
className="w-full bg-gray-900 border border-gray-700 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50"
|
||||
>
|
||||
<option value="">-- Select a server --</option>
|
||||
{remoteServers.map(server => (
|
||||
<option key={server.uuid} value={server.uuid}>
|
||||
{server.name} ({server.host}:{server.port})
|
||||
<option value="">
|
||||
{dockerLoading ? 'Loading containers...' : '-- Select a container --'}
|
||||
</option>
|
||||
{dockerContainers.map(container => (
|
||||
<option key={container.id} value={container.id}>
|
||||
{container.names[0]} ({container.image})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{dockerError && (
|
||||
<p className="text-xs text-red-400 mt-1">
|
||||
Failed to connect: {(dockerError as Error).message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Forward Details */}
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
|
||||
Reference in New Issue
Block a user