Add SSH source support with dialstdio and UI components

- Implement SSH dial via stdio for remote connections
- Add sources API and storage layer for managing connection sources
- Add SourcePanel and SshFields web components for SSH configuration
- Update app structure to support source-based connections
- Update handlers and server for new sources endpoint

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-08-13 10:19:47 +02:00
co-authored by Claude Haiku 4.5
parent 05db8bfeb9
commit 9b354636bb
19 changed files with 1857 additions and 174 deletions
+40
View File
@@ -6,6 +6,9 @@ package dkr
import (
"context"
"fmt"
"net"
"net/http"
"time"
"github.com/docker/docker/api/types/system"
"github.com/docker/docker/client"
@@ -33,6 +36,43 @@ func New(host string) (*Client, error) {
return &Client{api: api, Endpoint: api.DaemonHost()}, nil
}
// Dialer opens one connection to a daemon's API socket.
type Dialer func(ctx context.Context, network, addr string) (net.Conn, error)
// NewTunnel connects to a daemon that is only reachable through dial, such as a
// remote daemon behind an SSH connection. The HTTP host is a placeholder: every
// connection comes from dial, so the address is never resolved.
//
// endpoint is what the UI displays, e.g. ssh://root@10.0.0.5.
func NewTunnel(endpoint string, dial Dialer) (*Client, error) {
// The transport is ours so that WithHost cannot leave a TCP dialer or the
// environment's HTTP proxy in place; either would send API calls somewhere
// other than through the tunnel.
tr := &http.Transport{
DisableCompression: true,
// Every connection through the tunnel costs one SSH channel, and sshd
// allows ten per connection by default (MaxSessions). Capping the pool
// keeps a parallel migration from exhausting them; extra calls wait.
MaxConnsPerHost: 8,
MaxIdleConnsPerHost: 4,
IdleConnTimeout: 5 * time.Minute,
}
api, err := client.NewClientWithOpts(
client.WithHTTPClient(&http.Client{Transport: tr}),
client.WithHost("http://docker.tunnel.invalid"),
client.WithAPIVersionNegotiation(),
)
if err != nil {
return nil, fmt.Errorf("create docker client: %w", err)
}
tr.Proxy = nil
tr.DialContext = dial
if endpoint == "" {
endpoint = "tunnel"
}
return &Client{api: api, Endpoint: endpoint}, nil
}
// API exposes the underlying SDK client for callers that need an operation
// this package does not wrap.
func (c *Client) API() *client.Client { return c.api }