Show a host-specific Docker remote-API setup script in Settings

When adding/editing a Docker integration with a tcp:// or http:// remote
URL, display a copyable systemd override + curl verification script
scoped to the entered host:port, so enabling the daemon's API doesn't
require looking up the steps separately.
This commit is contained in:
Claude 2026-06-20 22:15:43 +00:00
parent 4a4a5a01b3
commit 628187befb
No known key found for this signature in database

View file

@ -784,6 +784,77 @@ function SshHostsSection() {
type NewIntegrationDraft = { id: number; type: string; values: Record<string, string> } type NewIntegrationDraft = { id: number; type: string; values: Record<string, string> }
function dockerHostInfo(baseUrl: string): { host: string; port: string } | null {
if (!baseUrl || baseUrl.startsWith('unix://')) return null
try {
const u = new URL(baseUrl.replace(/^tcp:\/\//, 'http://'))
if (u.protocol !== 'http:' && u.protocol !== 'https:') return null
if (!u.hostname) return null
return { host: u.hostname, port: u.port || '2375' }
} catch {
return null
}
}
function DockerSetupHint({ baseUrl }: { baseUrl: string }) {
const [copied, setCopied] = useState(false)
const info = dockerHostInfo(baseUrl)
if (!info) return null
const script = `sudo mkdir -p /etc/systemd/system/docker.service.d
sudo tee /etc/systemd/system/docker.service.d/override.conf > /dev/null <<EOF
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://${info.host}:${info.port}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
curl http://${info.host}:${info.port}/version`
return (
<div
style={{
marginTop: '12px',
padding: '12px 14px',
borderRadius: '8px',
border: '1px solid rgba(200,164,52,0.12)',
backgroundColor: 'rgba(255,255,255,0.02)',
}}
>
<div className="flex items-center justify-between" style={{ marginBottom: '8px' }}>
<span style={{ fontSize: '11px', color: '#7A7D85' }}>
Run this on <strong style={{ color: '#E8E6E0' }}>{info.host}</strong> to expose its Docker API on port {info.port}:
</span>
<button
onClick={() => {
navigator.clipboard.writeText(script)
setCopied(true)
setTimeout(() => setCopied(false), 1500)
}}
className="flex items-center gap-1 cursor-pointer border-none bg-transparent"
style={{ fontSize: '10.5px', fontWeight: 600, color: copied ? '#2ECC71' : '#C8A434', flexShrink: 0 }}
>
{copied ? <Check size={11} /> : null}
{copied ? 'Copied' : 'Copy script'}
</button>
</div>
<pre
style={{
fontSize: '10.5px',
color: '#9DA0A8',
fontFamily: 'monospace',
whiteSpace: 'pre-wrap',
wordBreak: 'break-all',
margin: 0,
lineHeight: 1.5,
}}
>
{script}
</pre>
</div>
)
}
function IntegrationsSection() { function IntegrationsSection() {
const { user } = useAuth() const { user } = useAuth()
const isAdmin = user?.role === 'admin' const isAdmin = user?.role === 'admin'
@ -1051,6 +1122,9 @@ function IntegrationsSection() {
'••••••••••••', '••••••••••••',
existing, existing,
)} )}
{def.type === 'docker' && (
<DockerSetupHint baseUrl={draft.baseUrl ?? existing.config.baseUrl ?? ''} />
)}
</div> </div>
) )
})} })}
@ -1100,6 +1174,7 @@ function IntegrationsSection() {
(f, value) => setNewDraftField(draft.id, f.key, value), (f, value) => setNewDraftField(draft.id, f.key, value),
'', '',
)} )}
{def.type === 'docker' && <DockerSetupHint baseUrl={draft.values.baseUrl ?? ''} />}
</div> </div>
) )
})} })}