diff --git a/README.md b/README.md index e06cf52..e8162d6 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,16 @@ Two ways to move things: The target needs **nothing installed**: no agent, no Python, no Go — just `sshd`, `docker`, `bash` and `gzip`. +The **source** is picked in the UI: the daemon DockMV runs next to, another daemon by address, or a +remote host over SSH — which needs nothing installed either. One DockMV can therefore move containers +between any two of your hosts. + --- ## Install -Run DockMV on the **source** host (the one holding the containers to move). +Run DockMV on the **source** host (the one holding the containers to move), or anywhere that can +reach it — see [sources](#sources). ### Docker Compose — recommended @@ -66,7 +71,7 @@ make build # rebuilds the UI, then the binary ## Dependencies -### Source host — where DockMV runs +### Source host — where the containers are | Requirement | Notes | | --- | --- | @@ -75,6 +80,9 @@ make build # rebuilds the UI, then the binary Nothing else. The binary is static: no libc, no runtime, no Python. +A **remote** source needs the same as a target — `sshd` and a `docker` CLI of 18.09 or newer, since +the Engine API is tunnelled through `docker system dial-stdio`. Nothing is installed there either. + ### Target host — where containers land | Requirement | Why | @@ -104,17 +112,37 @@ on the Go side; React 19, Vite 7 and TypeScript 5.9 on the UI side. That is the ## Using it -1. **Containers tab** — everything on the source host, grouped by compose project. Tick what to move. -2. **Expand a row** (`▸`) for per-container details: target name, image pulled or transferred, +1. **Source host** — top of the right panel. Defaults to the daemon DockMV runs next to; pick another + one to read a different host. See [sources](#sources). +2. **Containers tab** — everything on the source host, grouped by compose project. Tick what to move. +3. **Expand a row** (`▸`) for per-container details: target name, image pulled or transferred, networks and ports, and — per mount — **copy the data**, **create it empty**, or **do not mount it**. Bind mounts can be relocated; named volumes renamed. -3. **Apply to selected** does the same thing to every selected container at once. -4. **Right panel** — add the target host, *connect*, then **migrate over SSH** or **build a package**. -5. **Jobs tab** — live progress per container and per mount, with the full command log. +4. **Apply to selected** does the same thing to every selected container at once. +5. **Right panel** — add the target host, *connect*, then **migrate over SSH** or **build a package**. +6. **Jobs tab** — live progress per container and per mount, with the full command log. Start with **dry run** ticked: it runs every check and prints every command without touching the target. **Preview the commands** shows the exact `docker` invocations that will run. Nothing is hidden. +### Sources + +Three kinds, all interchangeable once selected — the container list, the preview, the migration and +the package build all read from whichever source is active: + +| Kind | How it is reached | Notes | +| --- | --- | --- | +| **this host** | the socket in `DOCKER_HOST`, or `--docker-host` | always present; cannot be edited or removed | +| **docker address** | `tcp://host:2375`, or another `unix://` socket | TLS uses the certificates from `DOCKER_CERT_PATH` in DockMV's own environment. A plain `tcp://` daemon is unauthenticated — anyone who reaches that port is root on that host | +| **ssh** | the remote host's own docker CLI, through `docker system dial-stdio` | host keys are verified and credentials handled exactly like a target's | + +The selected source is remembered in `/sources.json` and reselected on the next start; an +explicit `--docker-host` on the command line overrides it for that run. Saved sources whose +credentials you chose not to remember ask for them again after a restart. + +With a remote source the data relays through DockMV — source → this host → target — so it crosses the +network twice. Running DockMV on the source host keeps it to one hop. +
How the data is actually moved @@ -163,10 +191,11 @@ The tool can stop containers and read every volume on the host, so it is treated admin tool: - Binds to **`127.0.0.1` by default**. Binding elsewhere auto-generates an access token and prints it. -- **SSH host keys are verified** like OpenSSH. An unknown key is refused until you approve the - fingerprint in the UI; a *changed* key is refused outright. Trusted keys go to `/known_hosts`. +- **SSH host keys are verified** like OpenSSH, for sources as well as targets. An unknown key is + refused until you approve the fingerprint in the UI; a *changed* key is refused outright. Trusted + keys go to `/known_hosts`. - **Credentials are not persisted unless you ask.** *Remember* writes them to - `/connections.json`, mode `0600`. + `/connections.json` for targets and `/sources.json` for sources, mode `0600`. - **Nothing on the target is overwritten by default.** An existing container name fails the item; you pick *skip*, *rename* or *replace*. An existing volume is reused and merged into, never silently deleted, unless you pick *replace*. @@ -227,9 +256,10 @@ dockmv version ``` --addr string address to listen on (default "127.0.0.1:8080") --token string require this token on every request; "auto" generates one ---data-dir string connections and trusted host keys (default: OS config dir) +--data-dir string sources, connections and trusted host keys (default: OS config dir) --package-dir string where migration packages are written (default /packages) ---docker-host string source docker daemon (default: the DOCKER_HOST environment) +--docker-host string local source docker daemon (default: the DOCKER_HOST environment); + given explicitly, it overrides the remembered source -v verbose logging ``` @@ -245,6 +275,12 @@ Everything the UI does is available over HTTP. Pass the token as `X-Auth-Token` GET /api/health GET /api/source inventory + default selections GET /api/source/sizes volume sizes (slow) +GET /api/sources known sources + which one is selected +POST /api/sources +DELETE /api/sources/{id} +POST /api/sources/{id}/select switch the source everything reads from +POST /api/sources/{id}/probe read the SSH host key fingerprint +POST /api/sources/{id}/trust approve that fingerprint GET /api/connections POST /api/connections DELETE /api/connections/{id} @@ -270,7 +306,9 @@ GET /api/packages, /api/packages/{name}/download web/ React + TypeScript UI (vite) internal/spec/ the transport model: a container, and how to render it back into docker flags internal/dkr/ source Docker daemon: inventory, archive streams, image save -internal/sshx/ SSH transport, host key trust, driving the target's docker CLI +internal/sshx/ SSH transport, host key trust, driving the target's docker CLI, and + tunnelling a remote source's API through `docker system dial-stdio` +internal/store/ saved sources and target connections, and which source is selected internal/migrate/ the two engines: SSH streaming, and package + installer generation internal/job/ progress tracking for long-running work internal/api/ HTTP handlers and SSE diff --git a/internal/api/handlers.go b/internal/api/handlers.go index 474aa0e..cd273a2 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -21,23 +21,39 @@ import ( ) func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) { - ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) + ctx, cancel := context.WithTimeout(r.Context(), 45*time.Second) defer cancel() body := map[string]any{ "ok": true, - "dockerHost": s.docker.Endpoint, "packageDir": s.cfg.PackageDir, "dataDir": s.cfg.DataDir, "knownHosts": s.hosts.Path(), "authRequired": s.cfg.Token != "", } - if v, err := s.docker.Ping(ctx); err != nil { + + // Health is also what connects to the selected source on a fresh start, so + // the UI learns straight away when the remembered source is unreachable. + conn, release, err := s.source(ctx) + if err != nil { + selected, _ := s.sources.Get(s.sources.Selected()) body["ok"] = false body["dockerError"] = err.Error() - } else { - body["dockerVersion"] = v + endpoint := sourceEndpoint(selected) + body["dockerHost"] = endpoint + body["source"] = sourceStatus{ + ID: selected.ID, Name: selected.Name, Kind: selected.Kind, + Endpoint: endpoint, Error: err.Error(), + } + writeJSON(w, http.StatusOK, body) + return } + defer release() + + st := conn.status() + body["dockerHost"] = st.Endpoint + body["dockerVersion"] = st.DockerVersion + body["source"] = st writeJSON(w, http.StatusOK, body) } @@ -47,7 +63,14 @@ func (s *Server) handleSource(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(r.Context(), 2*time.Minute) defer cancel() - inv, err := s.docker.Inventory(ctx) + conn, release, err := s.source(ctx) + if err != nil { + s.writeDialError(w, err) + return + } + defer release() + + inv, err := conn.docker.Inventory(ctx) if err != nil { writeError(w, http.StatusBadGateway, "%v", err) return @@ -69,7 +92,14 @@ func (s *Server) handleSourceSizes(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(r.Context(), 5*time.Minute) defer cancel() - sizes, err := s.docker.VolumeSizes(ctx) + conn, release, err := s.source(ctx) + if err != nil { + s.writeDialError(w, err) + return + } + defer release() + + sizes, err := conn.docker.VolumeSizes(ctx) if err != nil { writeError(w, http.StatusBadGateway, "%v", err) return @@ -209,7 +239,14 @@ func (s *Server) handlePreview(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(r.Context(), 2*time.Minute) defer cancel() - inv, err := s.docker.Inventory(ctx) + conn, release, err := s.source(ctx) + if err != nil { + s.writeDialError(w, err) + return + } + defer release() + + inv, err := conn.docker.Inventory(ctx) if err != nil { writeError(w, http.StatusBadGateway, "%v", err) return @@ -299,12 +336,23 @@ func (s *Server) handleMigrateSSH(w http.ResponseWriter, r *http.Request) { return } + // The source is held for the whole job: picking another source in the UI + // while this runs must not close the socket it is reading from. + srcCtx, srcCancel := context.WithTimeout(r.Context(), 60*time.Second) + src, release, err := s.source(srcCtx) + srcCancel() + if err != nil { + s.writeDialError(w, err) + return + } + // The inventory is re-read now so the plan is applied to current state // rather than to whatever the browser last loaded. invCtx, cancel := context.WithTimeout(r.Context(), 2*time.Minute) - inv, err := s.docker.Inventory(invCtx) + inv, err := src.docker.Inventory(invCtx) cancel() if err != nil { + release() writeError(w, http.StatusBadGateway, "%v", err) return } @@ -315,13 +363,14 @@ func (s *Server) handleMigrateSSH(w http.ResponseWriter, r *http.Request) { client, err := sshx.Dial(dialCtx, cfg, s.hosts) dialCancel() if err != nil { + release() s.writeDialError(w, err) return } - title := fmt.Sprintf("%d container(s) to %s", countIncluded(req.Plan), cfg.Name) + title := fmt.Sprintf("%d container(s) from %s to %s", countIncluded(req.Plan), src.src.Name, cfg.Name) runner := &migrate.SSHRunner{ - Src: s.docker, + Src: src.docker, Dst: sshx.NewRemoteDocker(client), Containers: inv.Containers, Volumes: inv.Volumes, @@ -331,8 +380,10 @@ func (s *Server) handleMigrateSSH(w http.ResponseWriter, r *http.Request) { j := s.jobs.Run(context.Background(), job.KindSSH, title, req.Plan.Options.DryRun, func(ctx context.Context, j *job.Job) error { + defer release() defer client.Close() - j.Logf(job.LevelInfo, "", "migrating to %s@%s over ssh", cfg.User, cfg.Host) + j.Logf(job.LevelInfo, "", "migrating from %s to %s@%s over ssh", + src.docker.Endpoint, cfg.User, cfg.Host) return runner.Run(ctx, j) }) @@ -362,16 +413,25 @@ func (s *Server) handleBuildPackage(w http.ResponseWriter, r *http.Request) { return } + srcCtx, srcCancel := context.WithTimeout(r.Context(), 60*time.Second) + src, release, err := s.source(srcCtx) + srcCancel() + if err != nil { + s.writeDialError(w, err) + return + } + invCtx, cancel := context.WithTimeout(r.Context(), 2*time.Minute) - inv, err := s.docker.Inventory(invCtx) + inv, err := src.docker.Inventory(invCtx) cancel() if err != nil { + release() writeError(w, http.StatusBadGateway, "%v", err) return } packager := &migrate.Packager{ - Src: s.docker, + Src: src.docker, Containers: inv.Containers, Volumes: inv.Volumes, Networks: inv.Networks, @@ -384,6 +444,8 @@ func (s *Server) handleBuildPackage(w http.ResponseWriter, r *http.Request) { title := fmt.Sprintf("package of %d container(s)", countIncluded(req.Plan)) j := s.jobs.Run(context.Background(), job.KindPackage, title, req.Plan.Options.DryRun, func(ctx context.Context, j *job.Job) error { + defer release() + j.Logf(job.LevelInfo, "", "reading from %s", src.docker.Endpoint) res, err := packager.Run(ctx, j) if err != nil { return err diff --git a/internal/api/server.go b/internal/api/server.go index 15d6dfb..6e620a8 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -11,9 +11,9 @@ import ( "os" "path/filepath" "strings" + "sync" "time" - "github.com/arescom/dockmv/internal/dkr" "github.com/arescom/dockmv/internal/job" "github.com/arescom/dockmv/internal/sshx" "github.com/arescom/dockmv/internal/store" @@ -29,23 +29,31 @@ type Config struct { DataDir string // PackageDir is where migration packages are written. PackageDir string - // DockerHost overrides the source daemon address. + // DockerHost overrides the local source daemon address. DockerHost string + // DockerHostSet reports that DockerHost was given explicitly on the command + // line. It then wins over the source remembered from the last run. + DockerHostSet bool // UI is the embedded web app; nil disables the UI. UI fs.FS // Logger receives request and error logs. Logger *slog.Logger } -// Server ties the Docker client, connection store and job manager to HTTP. +// Server ties the source daemon, connection store and job manager to HTTP. type Server struct { - cfg Config - log *slog.Logger - docker *dkr.Client - conns *store.Connections - hosts *sshx.KnownHosts - jobs *job.Manager - mux *http.ServeMux + cfg Config + log *slog.Logger + sources *store.Sources + conns *store.Connections + hosts *sshx.KnownHosts + jobs *job.Manager + mux *http.ServeMux + + // srcMu guards cur, the connection to the selected source. It is opened on + // first use and replaced when the operator picks another source. + srcMu sync.Mutex + cur *sourceConn } // New builds the server and everything it owns. @@ -60,10 +68,6 @@ func New(cfg Config) (*Server, error) { return nil, fmt.Errorf("create package directory: %w", err) } - docker, err := dkr.New(cfg.DockerHost) - if err != nil { - return nil, err - } conns, err := store.NewConnections(filepath.Join(cfg.DataDir, "connections.json")) if err != nil { return nil, err @@ -72,17 +76,35 @@ func New(cfg Config) (*Server, error) { if err != nil { return nil, err } + local := store.Source{Name: "this host", DockerHost: cfg.DockerHost} + if local.DockerHost == "" { + local.DockerHost = os.Getenv("DOCKER_HOST") + } + sources, err := store.NewSources(filepath.Join(cfg.DataDir, "sources.json"), local) + if err != nil { + return nil, err + } + // An explicit --docker-host is an instruction for this run, so it overrides + // the source remembered from the last one. + if cfg.DockerHostSet { + if err := sources.Select(store.LocalSourceID); err != nil { + return nil, err + } + } s := &Server{ - cfg: cfg, log: cfg.Logger, docker: docker, + cfg: cfg, log: cfg.Logger, sources: sources, conns: conns, hosts: hosts, jobs: job.NewManager(), mux: http.NewServeMux(), } s.routes() return s, nil } -// Close releases the Docker connection. -func (s *Server) Close() error { return s.docker.Close() } +// Close releases the connection to the current source. +func (s *Server) Close() error { + s.invalidateSource("") + return nil +} // Handler returns the root HTTP handler. func (s *Server) Handler() http.Handler { @@ -96,6 +118,13 @@ func (s *Server) routes() { m.HandleFunc("GET /api/source", s.handleSource) m.HandleFunc("GET /api/source/sizes", s.handleSourceSizes) + m.HandleFunc("GET /api/sources", s.handleListSources) + m.HandleFunc("POST /api/sources", s.handleSaveSource) + m.HandleFunc("DELETE /api/sources/{id}", s.handleDeleteSource) + m.HandleFunc("POST /api/sources/{id}/select", s.handleSelectSource) + m.HandleFunc("POST /api/sources/{id}/probe", s.handleSourceProbe) + m.HandleFunc("POST /api/sources/{id}/trust", s.handleSourceTrust) + m.HandleFunc("GET /api/connections", s.handleListConnections) m.HandleFunc("POST /api/connections", s.handleSaveConnection) m.HandleFunc("DELETE /api/connections/{id}", s.handleDeleteConnection) diff --git a/internal/api/sources.go b/internal/api/sources.go new file mode 100644 index 0000000..2e3116c --- /dev/null +++ b/internal/api/sources.go @@ -0,0 +1,328 @@ +package api + +import ( + "context" + "errors" + "fmt" + "net" + "net/http" + "strconv" + "time" + + "github.com/arescom/dockmv/internal/dkr" + "github.com/arescom/dockmv/internal/sshx" + "github.com/arescom/dockmv/internal/store" +) + +// sourceConn is a live connection to one source daemon. +// +// It is reference counted rather than closed eagerly: a migration holds its +// source for as long as it runs, so switching source in the UI half way through +// a transfer must not pull the socket out from under it. The connection is +// closed once it is both replaced and unused. +type sourceConn struct { + src store.Source + docker *dkr.Client + ssh *sshx.Client + version string + + refs int + stale bool +} + +func (c *sourceConn) close() { + if c.docker != nil { + _ = c.docker.Close() + } + if c.ssh != nil { + _ = c.ssh.Close() + } +} + +// sourceStatus is what the UI shows about the source it is looking at. +type sourceStatus struct { + ID string `json:"id"` + Name string `json:"name"` + Kind store.SourceKind `json:"kind"` + Endpoint string `json:"endpoint"` + DockerVersion string `json:"dockerVersion,omitempty"` + Connected bool `json:"connected"` + Error string `json:"error,omitempty"` +} + +func (c *sourceConn) status() sourceStatus { + return sourceStatus{ + ID: c.src.ID, Name: c.src.Name, Kind: c.src.Kind, + Endpoint: c.docker.Endpoint, DockerVersion: c.version, Connected: true, + } +} + +// source returns the current source, connecting to it on first use. The +// returned release function must be called when the caller is done with it — +// for a job, when the job finishes. +func (s *Server) source(ctx context.Context) (*sourceConn, func(), error) { + s.srcMu.Lock() + if cur := s.cur; cur != nil { + cur.refs++ + s.srcMu.Unlock() + return cur, func() { s.releaseSource(cur) }, nil + } + id := s.sources.Selected() + s.srcMu.Unlock() + + conn, err := s.dialSource(ctx, id) + if err != nil { + return nil, nil, err + } + + s.srcMu.Lock() + defer s.srcMu.Unlock() + // Another request may have connected while this one was dialling; one + // connection is enough, so the loser is dropped. + if s.cur != nil { + conn.close() + conn = s.cur + } else { + s.cur = conn + } + conn.refs++ + return conn, func() { s.releaseSource(conn) }, nil +} + +func (s *Server) releaseSource(c *sourceConn) { + s.srcMu.Lock() + defer s.srcMu.Unlock() + c.refs-- + if c.refs <= 0 && c.stale { + c.close() + } +} + +// selectSource connects to a source and, once that worked, makes it the current +// one and records the choice for the next run. +func (s *Server) selectSource(ctx context.Context, id string) (sourceStatus, error) { + conn, err := s.dialSource(ctx, id) + if err != nil { + return sourceStatus{}, err + } + st := conn.status() + + s.srcMu.Lock() + old := s.cur + s.cur = conn + if old != nil { + old.stale = true + if old.refs <= 0 { + old.close() + } + } + s.srcMu.Unlock() + + if err := s.sources.Select(conn.src.ID); err != nil { + return st, fmt.Errorf("remember the selected source: %w", err) + } + s.log.Info("source selected", "id", conn.src.ID, "endpoint", conn.docker.Endpoint) + return st, nil +} + +// invalidateSource drops the cached connection when the source behind it has +// been edited or removed. An empty id invalidates whatever is current. +func (s *Server) invalidateSource(id string) { + s.srcMu.Lock() + defer s.srcMu.Unlock() + if s.cur == nil { + return + } + if id != "" && s.cur.src.ID != id { + return + } + s.cur.stale = true + if s.cur.refs <= 0 { + s.cur.close() + } + s.cur = nil +} + +// dialSource opens a connection to one source and verifies the daemon answers. +func (s *Server) dialSource(ctx context.Context, id string) (*sourceConn, error) { + src, err := s.sources.Get(id) + if err != nil { + return nil, err + } + + conn := &sourceConn{src: src} + switch src.Kind { + case store.SourceLocal, store.SourceDocker: + c, err := dkr.New(src.DockerHost) + if err != nil { + return nil, err + } + conn.docker = c + case store.SourceSSH: + if src.SSH == nil { + return nil, errors.New("source has no ssh configuration") + } + client, err := sshx.Dial(ctx, *src.SSH, s.hosts) + if err != nil { + return nil, err + } + rd := sshx.NewRemoteDocker(client) + // The CLI is checked first: a missing binary or a user outside the + // docker group is a readable error here, and an unexplained broken + // socket if it is left to the tunnel. + if _, err := rd.ProbeCLI(ctx); err != nil { + client.Close() + return nil, err + } + c, err := dkr.NewTunnel(sourceEndpoint(src), func(ctx context.Context, _, _ string) (net.Conn, error) { + return rd.DialAPI(ctx) + }) + if err != nil { + client.Close() + return nil, err + } + conn.ssh, conn.docker = client, c + default: + return nil, fmt.Errorf("unknown source kind %q", src.Kind) + } + + pingCtx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() + version, err := conn.docker.Ping(pingCtx) + if err != nil { + conn.close() + return nil, fmt.Errorf("connect to docker at %s: %w", conn.docker.Endpoint, err) + } + conn.version = version + return conn, nil +} + +// sourceEndpoint describes where a source lives, before and after it is +// connected to. +func sourceEndpoint(src store.Source) string { + if src.Kind == store.SourceSSH && src.SSH != nil { + port := src.SSH.Port + if port == 0 { + port = 22 + } + return fmt.Sprintf("ssh://%s@%s", src.SSH.User, net.JoinHostPort(src.SSH.Host, strconv.Itoa(port))) + } + return src.DockerHost +} + +// handleListSources lists the sources without connecting to any of them; the +// state of the current one comes from /api/health. +func (s *Server) handleListSources(w http.ResponseWriter, r *http.Request) { + s.srcMu.Lock() + var current *sourceStatus + if s.cur != nil { + st := s.cur.status() + current = &st + } + s.srcMu.Unlock() + + writeJSON(w, http.StatusOK, map[string]any{ + "sources": s.sources.List(), + "selected": s.sources.Selected(), + "current": current, + }) +} + +func (s *Server) handleSaveSource(w http.ResponseWriter, r *http.Request) { + var src store.Source + if err := decode(r, &src); err != nil { + writeError(w, http.StatusBadRequest, "%v", err) + return + } + saved, err := s.sources.Save(src) + if err != nil { + writeError(w, http.StatusBadRequest, "%v", err) + return + } + // Editing the source in use means the live connection describes the old + // settings; drop it so the next call reconnects. + s.invalidateSource(saved.ID) + writeJSON(w, http.StatusOK, saved) +} + +func (s *Server) handleDeleteSource(w http.ResponseWriter, r *http.Request) { + id := r.PathValue("id") + if err := s.sources.Delete(id); err != nil { + if errors.Is(err, store.ErrNotFound) { + writeError(w, http.StatusNotFound, "%v", err) + return + } + writeError(w, http.StatusBadRequest, "%v", err) + return + } + s.invalidateSource(id) + w.WriteHeader(http.StatusNoContent) +} + +// handleSelectSource switches the source the whole UI works against. +func (s *Server) handleSelectSource(w http.ResponseWriter, r *http.Request) { + ctx, cancel := context.WithTimeout(r.Context(), 60*time.Second) + defer cancel() + + st, err := s.selectSource(ctx, r.PathValue("id")) + if err != nil { + s.writeDialError(w, err) + return + } + writeJSON(w, http.StatusOK, st) +} + +// handleSourceProbe reads the SSH host key of a source host, so its fingerprint +// can be approved the same way a target's is. +func (s *Server) handleSourceProbe(w http.ResponseWriter, r *http.Request) { + cfg, err := s.sourceSSH(r.PathValue("id")) + if err != nil { + writeError(w, http.StatusNotFound, "%v", err) + return + } + ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second) + defer cancel() + + info, err := sshx.Probe(ctx, cfg, s.hosts) + if err != nil { + writeError(w, http.StatusBadGateway, "%v", err) + return + } + writeJSON(w, http.StatusOK, info) +} + +func (s *Server) handleSourceTrust(w http.ResponseWriter, r *http.Request) { + cfg, err := s.sourceSSH(r.PathValue("id")) + if err != nil { + writeError(w, http.StatusNotFound, "%v", err) + return + } + var body struct { + Fingerprint string `json:"fingerprint"` + } + if err := decode(r, &body); err != nil { + writeError(w, http.StatusBadRequest, "%v", err) + return + } + ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second) + defer cancel() + + if err := sshx.TrustFromProbe(ctx, cfg, s.hosts, body.Fingerprint); err != nil { + writeError(w, http.StatusBadRequest, "%v", err) + return + } + writeJSON(w, http.StatusOK, map[string]any{"trusted": true}) +} + +// sourceSSH returns the SSH configuration of a source, refusing the ones that +// are not reached over SSH. +func (s *Server) sourceSSH(id string) (sshx.Config, error) { + src, err := s.sources.Get(id) + if err != nil { + return sshx.Config{}, err + } + if src.Kind != store.SourceSSH || src.SSH == nil { + return sshx.Config{}, fmt.Errorf("source %s is not reached over ssh", src.Name) + } + return *src.SSH, nil +} diff --git a/internal/dkr/client.go b/internal/dkr/client.go index 32978d1..0d6b635 100644 --- a/internal/dkr/client.go +++ b/internal/dkr/client.go @@ -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 } diff --git a/internal/sshx/dialstdio.go b/internal/sshx/dialstdio.go new file mode 100644 index 0000000..da30a50 --- /dev/null +++ b/internal/sshx/dialstdio.go @@ -0,0 +1,170 @@ +package sshx + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "net" + "strings" + "sync" + "time" + + "golang.org/x/crypto/ssh" +) + +// DialAPI opens one connection to the remote daemon's API by running +// `docker system dial-stdio` over SSH and treating that session's stdin and +// stdout as a socket. It is the same mechanism `docker -H ssh://…` uses, so the +// remote host still needs nothing but sshd and the docker CLI. +// +// The returned connection is what dkr.NewTunnel dials through: from there on the +// whole Docker Engine API — inventory, archive streams, image save — is +// available on a remote source host exactly as it is on a local one. +func (r *RemoteDocker) DialAPI(ctx context.Context) (net.Conn, error) { + sess, err := r.c.conn.NewSession() + if err != nil { + return nil, fmt.Errorf("open ssh session: %w", err) + } + stdin, err := sess.StdinPipe() + if err != nil { + sess.Close() + return nil, fmt.Errorf("attach to remote stdin: %w", err) + } + stdout, err := sess.StdoutPipe() + if err != nil { + sess.Close() + return nil, fmt.Errorf("attach to remote stdout: %w", err) + } + errBuf := &syncBuffer{} + sess.Stderr = errBuf + + cmd := r.Cmd("system", "dial-stdio") + if err := sess.Start(cmd); err != nil { + sess.Close() + return nil, fmt.Errorf("start %q: %w", cmd, err) + } + + cfg := r.c.Config() + // The connection deliberately outlives ctx: the HTTP transport keeps it in + // its idle pool between API calls, and closes it itself when a request is + // cancelled or the client is closed. + return &apiConn{ + sess: sess, stdin: stdin, stdout: stdout, stderr: errBuf, + remote: apiAddr(fmt.Sprintf("%s@%s", cfg.User, cfg.addr())), + }, nil +} + +// ProbeCLI checks that the remote docker CLI is usable before the API is +// tunnelled through it, so a missing binary or a permission problem is reported +// as itself rather than as a broken socket. It returns the daemon version. +func (r *RemoteDocker) ProbeCLI(ctx context.Context) (string, error) { + out, ok, err := r.Try(ctx, "version", "--format", "{{.Server.Version}}") + if err != nil { + return "", err + } + if ok { + if v := strings.TrimSpace(out); v != "" { + return v, nil + } + } + res, err := r.c.Run(ctx, r.Cmd("version")) + if err != nil { + return "", err + } + msg := strings.TrimSpace(res.Stderr) + switch { + case strings.Contains(msg, "permission denied"): + return "", errors.New("the login user cannot talk to the docker daemon; " + + "add it to the docker group, or enable sudo -n for this source") + case msg != "": + return "", errors.New("docker is not usable on that host: " + firstLine(msg)) + default: + return "", errors.New("docker is not installed or not on PATH on that host") + } +} + +// apiConn adapts an SSH session to net.Conn. +type apiConn struct { + sess *ssh.Session + stdin io.WriteCloser + stdout io.Reader + stderr *syncBuffer + remote apiAddr + + once sync.Once + err error +} + +func (c *apiConn) Read(p []byte) (int, error) { + n, err := c.stdout.Read(p) + if err != nil { + return n, c.wrap(err) + } + return n, nil +} + +func (c *apiConn) Write(p []byte) (int, error) { + n, err := c.stdin.Write(p) + if err != nil { + return n, c.wrap(err) + } + return n, nil +} + +// wrap replaces the bare EOF a failed remote command produces with whatever it +// printed on stderr, which is the only place the reason appears. +func (c *apiConn) wrap(err error) error { + if msg := strings.TrimSpace(c.stderr.String()); msg != "" { + return fmt.Errorf("docker system dial-stdio on the remote host failed: %s", firstLine(msg)) + } + return err +} + +func (c *apiConn) Close() error { + c.once.Do(func() { + // Closing stdin lets the remote docker exit cleanly; the session is torn + // down straight after either way. + _ = c.stdin.Close() + c.err = c.sess.Close() + if errors.Is(c.err, io.EOF) { + c.err = nil + } + }) + return c.err +} + +func (c *apiConn) LocalAddr() net.Addr { return apiAddr("dockmv") } +func (c *apiConn) RemoteAddr() net.Addr { return c.remote } + +// The deadline calls are no-ops: an SSH channel has no deadline of its own, and +// the Docker client relies on context cancellation rather than on these. This +// mirrors what the docker CLI's own ssh:// transport does. +func (c *apiConn) SetDeadline(time.Time) error { return nil } +func (c *apiConn) SetReadDeadline(time.Time) error { return nil } +func (c *apiConn) SetWriteDeadline(time.Time) error { return nil } + +type apiAddr string + +func (a apiAddr) Network() string { return "ssh" } +func (a apiAddr) String() string { return string(a) } + +// syncBuffer collects remote stderr, which the ssh session writes from its own +// goroutine while the connection is being read. +type syncBuffer struct { + mu sync.Mutex + buf bytes.Buffer +} + +func (b *syncBuffer) Write(p []byte) (int, error) { + b.mu.Lock() + defer b.mu.Unlock() + return b.buf.Write(p) +} + +func (b *syncBuffer) String() string { + b.mu.Lock() + defer b.mu.Unlock() + return b.buf.String() +} diff --git a/internal/sshx/dialstdio_test.go b/internal/sshx/dialstdio_test.go new file mode 100644 index 0000000..319d301 --- /dev/null +++ b/internal/sshx/dialstdio_test.go @@ -0,0 +1,79 @@ +package sshx + +import ( + "errors" + "io" + "net" + "strings" + "testing" + "time" +) + +// TestDialAPICommand pins the command the tunnel runs on the source host: it is +// the same one `docker -H ssh://…` uses, and the sudo / custom binary settings +// have to reach it. +func TestDialAPICommand(t *testing.T) { + for _, tc := range []struct { + name string + rd *RemoteDocker + want string + }{ + {"plain", &RemoteDocker{binary: "docker"}, "docker system dial-stdio"}, + {"sudo", &RemoteDocker{binary: "docker", sudo: true}, "sudo -n docker system dial-stdio"}, + {"podman", &RemoteDocker{binary: "podman"}, "podman system dial-stdio"}, + {"path with a space", &RemoteDocker{binary: "/opt/my docker/bin/docker"}, "'/opt/my docker/bin/docker' system dial-stdio"}, + } { + t.Run(tc.name, func(t *testing.T) { + if got := tc.rd.Cmd("system", "dial-stdio"); got != tc.want { + t.Fatalf("command = %q, want %q", got, tc.want) + } + }) + } +} + +// TestAPIConnSurfacesRemoteStderr covers the failure that would otherwise reach +// the Docker client as a bare EOF: the remote docker printing a reason and +// exiting. +func TestAPIConnSurfacesRemoteStderr(t *testing.T) { + errBuf := &syncBuffer{} + errBuf.Write([]byte("docker: 'system dial-stdio' is not a docker command\n")) + c := &apiConn{ + stdout: strings.NewReader(""), + stdin: nopWriteCloser{io.Discard}, + stderr: errBuf, + } + _, err := c.Read(make([]byte, 8)) + if err == nil { + t.Fatal("a closed stream with remote stderr should be an error") + } + if !strings.Contains(err.Error(), "is not a docker command") { + t.Fatalf("error = %v, want the remote stderr in it", err) + } + // Without stderr the plain EOF must survive, or the HTTP transport cannot + // tell a finished response from a broken one. + quiet := &apiConn{stdout: strings.NewReader(""), stdin: nopWriteCloser{io.Discard}, stderr: &syncBuffer{}} + if _, err := quiet.Read(make([]byte, 8)); !errors.Is(err, io.EOF) { + t.Fatalf("error = %v, want io.EOF", err) + } +} + +func TestAPIConnDeadlinesAreNoops(t *testing.T) { + var c net.Conn = &apiConn{stdout: strings.NewReader(""), stdin: nopWriteCloser{io.Discard}, stderr: &syncBuffer{}} + now := time.Now() + if err := c.SetDeadline(now); err != nil { + t.Fatalf("SetDeadline: %v", err) + } + if err := c.SetReadDeadline(now); err != nil { + t.Fatalf("SetReadDeadline: %v", err) + } + if err := c.SetWriteDeadline(now); err != nil { + t.Fatalf("SetWriteDeadline: %v", err) + } + if c.RemoteAddr().Network() != "ssh" { + t.Fatalf("network = %q, want ssh", c.RemoteAddr().Network()) + } +} + +type nopWriteCloser struct{ io.Writer } + +func (nopWriteCloser) Close() error { return nil } diff --git a/internal/store/sources.go b/internal/store/sources.go new file mode 100644 index 0000000..5682765 --- /dev/null +++ b/internal/store/sources.go @@ -0,0 +1,315 @@ +package store + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "sort" + "strings" + "sync" + + "github.com/arescom/dockmv/internal/sshx" +) + +// SourceKind says how a source daemon is reached. +type SourceKind string + +const ( + // SourceLocal is the daemon this process talks to by default: the socket in + // DOCKER_HOST, or whatever --docker-host was given. + SourceLocal SourceKind = "local" + // SourceDocker is an explicit daemon address, e.g. tcp://10.0.0.5:2375. + SourceDocker SourceKind = "docker" + // SourceSSH is a remote daemon reached over SSH, driven through the remote + // host's own docker CLI. + SourceSSH SourceKind = "ssh" +) + +// LocalSourceID identifies the built-in local source. It is always listed, and +// cannot be edited or deleted. +const LocalSourceID = "local" + +// Source is one place containers can be read from. +type Source struct { + ID string `json:"id"` + Name string `json:"name"` + Kind SourceKind `json:"kind"` + // DockerHost is the daemon address for SourceDocker, and the address the + // local source resolved to for SourceLocal (read-only in that case). + DockerHost string `json:"dockerHost,omitempty"` + // SSH describes the remote host for SourceSSH. + SSH *sshx.Config `json:"ssh,omitempty"` +} + +// Sources is a JSON-backed list of source daemons plus the one currently +// selected, so a restart comes back to the host the operator was working on. +// +// Like Connections, SSH credentials only reach the file when the operator ticks +// "remember"; otherwise they live in memory for this process only. +type Sources struct { + path string + local Source + + mu sync.RWMutex + items map[string]Source + secrets map[string]secret + selected string +} + +// sourcesFile is the on-disk shape. +type sourcesFile struct { + Selected string `json:"selected,omitempty"` + Sources []Source `json:"sources"` +} + +// NewSources loads (or creates) the source file at path. local describes the +// built-in local source, which is not persisted. +func NewSources(path string, local Source) (*Sources, error) { + local.ID = LocalSourceID + local.Kind = SourceLocal + if local.Name == "" { + local.Name = "this host" + } + s := &Sources{ + path: path, local: local, + items: map[string]Source{}, secrets: map[string]secret{}, + selected: LocalSourceID, + } + if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { + return nil, fmt.Errorf("create data directory: %w", err) + } + b, err := os.ReadFile(path) + if errors.Is(err, os.ErrNotExist) { + return s, nil + } + if err != nil { + return nil, fmt.Errorf("read sources: %w", err) + } + var f sourcesFile + if err := json.Unmarshal(b, &f); err != nil { + return nil, fmt.Errorf("parse sources file %s: %w", path, err) + } + for _, src := range f.Sources { + if src.ID == "" || src.ID == LocalSourceID { + continue + } + s.items[src.ID] = src + } + if f.Selected != "" { + if _, ok := s.items[f.Selected]; ok || f.Selected == LocalSourceID { + s.selected = f.Selected + } + } + return s, nil +} + +// Local returns the built-in local source. +func (s *Sources) Local() Source { return s.local } + +// List returns the local source followed by the saved ones, credentials +// stripped, in name order. +func (s *Sources) List() []Source { + s.mu.RLock() + defer s.mu.RUnlock() + out := make([]Source, 0, len(s.items)+1) + for _, src := range s.items { + out = append(out, redactSource(src)) + } + sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name }) + return append([]Source{s.local}, out...) +} + +// Get returns a source ready to connect to, with credentials filled back in. +func (s *Sources) Get(id string) (Source, error) { + if id == "" || id == LocalSourceID { + return s.local, nil + } + s.mu.RLock() + defer s.mu.RUnlock() + src, ok := s.items[id] + if !ok { + return Source{}, ErrNotFound + } + if src.SSH != nil { + cfg := *src.SSH + if sec, ok := s.secrets[id]; ok { + if cfg.Password == "" { + cfg.Password = sec.Password + } + if cfg.PrivateKey == "" { + cfg.PrivateKey = sec.PrivateKey + } + if cfg.Passphrase == "" { + cfg.Passphrase = sec.Passphrase + } + } + src.SSH = &cfg + } + return src, nil +} + +// Selected returns the id of the current source, falling back to the local one. +func (s *Sources) Selected() string { + s.mu.RLock() + defer s.mu.RUnlock() + return s.selected +} + +// Select records which source is in use. It does not connect: that is the +// caller's job, so a selection is only stored once it has been shown to work. +func (s *Sources) Select(id string) error { + if id == "" { + id = LocalSourceID + } + s.mu.Lock() + defer s.mu.Unlock() + if id != LocalSourceID { + if _, ok := s.items[id]; !ok { + return ErrNotFound + } + } + if s.selected == id { + return nil + } + s.selected = id + return s.flush() +} + +// Save inserts or updates a source and returns the stored, redacted form. +// +// As with connections, an update that omits credentials keeps the ones already +// held, so a source can be edited without re-entering a password. +func (s *Sources) Save(src Source) (Source, error) { + if src.ID == LocalSourceID { + return Source{}, errors.New("the local source cannot be edited") + } + switch src.Kind { + case SourceDocker: + src.DockerHost = strings.TrimSpace(src.DockerHost) + if src.DockerHost == "" { + return Source{}, errors.New("a docker address is required, e.g. tcp://10.0.0.5:2375") + } + if !strings.Contains(src.DockerHost, "://") { + return Source{}, fmt.Errorf("%q is not a docker address; it needs a scheme, e.g. tcp://%s", + src.DockerHost, src.DockerHost) + } + src.SSH = nil + if src.Name == "" { + src.Name = src.DockerHost + } + case SourceSSH: + if src.SSH == nil || src.SSH.Host == "" { + return Source{}, errors.New("host is required") + } + if src.SSH.User == "" { + return Source{}, errors.New("user is required") + } + if src.SSH.Port == 0 { + src.SSH.Port = 22 + } + src.DockerHost = "" + if src.Name == "" { + src.Name = src.SSH.Host + } + case SourceLocal: + return Source{}, errors.New("there is only one local source") + default: + return Source{}, fmt.Errorf("unknown source kind %q", src.Kind) + } + + s.mu.Lock() + defer s.mu.Unlock() + + if src.ID == "" { + src.ID = newID() + } + if src.SSH != nil { + prev := s.items[src.ID] + prevSecret := s.secrets[src.ID] + var prevSSH sshx.Config + if prev.SSH != nil { + prevSSH = *prev.SSH + } + if src.SSH.Password == "" { + src.SSH.Password = firstNonEmpty(prevSSH.Password, prevSecret.Password) + } + if src.SSH.PrivateKey == "" { + src.SSH.PrivateKey = firstNonEmpty(prevSSH.PrivateKey, prevSecret.PrivateKey) + } + if src.SSH.Passphrase == "" { + src.SSH.Passphrase = firstNonEmpty(prevSSH.Passphrase, prevSecret.Passphrase) + } + // The SSH id is only meaningful inside the source that owns it. + src.SSH.ID = src.ID + src.SSH.Name = src.Name + + if src.SSH.SaveSecrets { + delete(s.secrets, src.ID) + s.items[src.ID] = src + } else { + s.secrets[src.ID] = secret{ + Password: src.SSH.Password, + PrivateKey: src.SSH.PrivateKey, + Passphrase: src.SSH.Passphrase, + } + s.items[src.ID] = redactSource(src) + } + } else { + s.items[src.ID] = src + } + + if err := s.flush(); err != nil { + return Source{}, err + } + return redactSource(s.items[src.ID]), nil +} + +// Delete removes a source, falling back to the local one when the source being +// removed is the selected one. +func (s *Sources) Delete(id string) error { + if id == LocalSourceID { + return errors.New("the local source cannot be deleted") + } + s.mu.Lock() + defer s.mu.Unlock() + if _, ok := s.items[id]; !ok { + return ErrNotFound + } + delete(s.items, id) + delete(s.secrets, id) + if s.selected == id { + s.selected = LocalSourceID + } + return s.flush() +} + +// flush writes the file. The caller must hold the write lock. +func (s *Sources) flush() error { + f := sourcesFile{Selected: s.selected, Sources: make([]Source, 0, len(s.items))} + for _, src := range s.items { + f.Sources = append(f.Sources, src) + } + sort.Slice(f.Sources, func(i, j int) bool { return f.Sources[i].ID < f.Sources[j].ID }) + b, err := json.MarshalIndent(f, "", " ") + if err != nil { + return err + } + tmp := s.path + ".tmp" + if err := os.WriteFile(tmp, b, 0o600); err != nil { + return fmt.Errorf("write sources: %w", err) + } + if err := os.Rename(tmp, s.path); err != nil { + return fmt.Errorf("replace sources file: %w", err) + } + return nil +} + +func redactSource(src Source) Source { + if src.SSH != nil { + cfg := redact(*src.SSH) + src.SSH = &cfg + } + return src +} diff --git a/internal/store/sources_test.go b/internal/store/sources_test.go new file mode 100644 index 0000000..9a8a429 --- /dev/null +++ b/internal/store/sources_test.go @@ -0,0 +1,202 @@ +package store + +import ( + "errors" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/arescom/dockmv/internal/sshx" +) + +func newTestSources(t *testing.T) (*Sources, string) { + t.Helper() + path := filepath.Join(t.TempDir(), "sources.json") + s, err := NewSources(path, Source{Name: "this host", DockerHost: "unix:///var/run/docker.sock"}) + if err != nil { + t.Fatalf("NewSources: %v", err) + } + return s, path +} + +func TestSourcesLocalIsAlwaysPresent(t *testing.T) { + s, _ := newTestSources(t) + + list := s.List() + if len(list) != 1 || list[0].ID != LocalSourceID || list[0].Kind != SourceLocal { + t.Fatalf("expected only the local source, got %+v", list) + } + if got := s.Selected(); got != LocalSourceID { + t.Fatalf("selected = %q, want %q", got, LocalSourceID) + } + if _, err := s.Save(Source{ID: LocalSourceID, Kind: SourceDocker, DockerHost: "tcp://x:2375"}); err == nil { + t.Fatal("editing the local source should be refused") + } + if err := s.Delete(LocalSourceID); err == nil { + t.Fatal("deleting the local source should be refused") + } +} + +func TestSourcesValidation(t *testing.T) { + s, _ := newTestSources(t) + + for _, tc := range []struct { + name string + src Source + want string + }{ + {"no docker address", Source{Kind: SourceDocker}, "docker address is required"}, + {"address without scheme", Source{Kind: SourceDocker, DockerHost: "10.0.0.5:2375"}, "needs a scheme"}, + {"ssh without host", Source{Kind: SourceSSH, SSH: &sshx.Config{User: "root"}}, "host is required"}, + {"ssh without user", Source{Kind: SourceSSH, SSH: &sshx.Config{Host: "h"}}, "user is required"}, + {"unknown kind", Source{Kind: "carrier-pigeon"}, "unknown source kind"}, + } { + t.Run(tc.name, func(t *testing.T) { + if _, err := s.Save(tc.src); err == nil || !strings.Contains(err.Error(), tc.want) { + t.Fatalf("error = %v, want it to mention %q", err, tc.want) + } + }) + } +} + +func TestSourcesSaveDefaults(t *testing.T) { + s, _ := newTestSources(t) + + saved, err := s.Save(Source{Kind: SourceSSH, SSH: &sshx.Config{Host: "10.0.0.9", User: "root"}}) + if err != nil { + t.Fatalf("Save: %v", err) + } + if saved.ID == "" { + t.Fatal("an id should have been generated") + } + if saved.Name != "10.0.0.9" { + t.Fatalf("name = %q, want the host as a fallback", saved.Name) + } + if saved.SSH.Port != 22 { + t.Fatalf("port = %d, want 22", saved.SSH.Port) + } + + // A docker source drops any ssh configuration, and the other way round. + dock, err := s.Save(Source{Kind: SourceDocker, DockerHost: "tcp://10.0.0.5:2375", SSH: &sshx.Config{Host: "x", User: "y"}}) + if err != nil { + t.Fatalf("Save: %v", err) + } + if dock.SSH != nil { + t.Fatalf("ssh configuration should be dropped for a docker source: %+v", dock.SSH) + } + if dock.Name != "tcp://10.0.0.5:2375" { + t.Fatalf("name = %q, want the address as a fallback", dock.Name) + } +} + +func TestSourcesSecretsStayOffDiskUnlessAsked(t *testing.T) { + s, path := newTestSources(t) + + kept, err := s.Save(Source{ + Kind: SourceSSH, + SSH: &sshx.Config{Host: "h1", User: "root", Auth: sshx.AuthPassword, Password: "in-memory"}, + }) + if err != nil { + t.Fatalf("Save: %v", err) + } + if kept.SSH.Password != "" { + t.Fatal("the returned form must be redacted") + } + if lst := s.List(); lst[1].SSH.Password != "" { + t.Fatal("List must not hand out credentials") + } + // Get is the dialling path, so it does see the password. + got, err := s.Get(kept.ID) + if err != nil || got.SSH.Password != "in-memory" { + t.Fatalf("Get password = %q (err %v), want the in-memory secret", got.SSH.Password, err) + } + + remembered, err := s.Save(Source{ + Kind: SourceSSH, + SSH: &sshx.Config{Host: "h2", User: "root", Auth: sshx.AuthPassword, Password: "on-disk", SaveSecrets: true}, + }) + if err != nil { + t.Fatalf("Save: %v", err) + } + + b, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read file: %v", err) + } + if strings.Contains(string(b), "in-memory") { + t.Fatal("a secret the operator did not want persisted reached the disk") + } + if !strings.Contains(string(b), "on-disk") { + t.Fatal("a remembered secret should have been written") + } + + // An update that omits the password keeps the one already held. + again, err := s.Save(Source{ID: remembered.ID, Kind: SourceSSH, SSH: &sshx.Config{Host: "h2", User: "admin", SaveSecrets: true}}) + if err != nil { + t.Fatalf("Save: %v", err) + } + reloaded, err := s.Get(again.ID) + if err != nil { + t.Fatalf("Get: %v", err) + } + if reloaded.SSH.Password != "on-disk" { + t.Fatalf("password = %q, want it carried forward", reloaded.SSH.Password) + } + if reloaded.SSH.User != "admin" { + t.Fatalf("user = %q, want the update to apply", reloaded.SSH.User) + } +} + +func TestSourcesSelectionSurvivesRestart(t *testing.T) { + s, path := newTestSources(t) + + saved, err := s.Save(Source{Name: "prod", Kind: SourceSSH, SSH: &sshx.Config{Host: "10.0.0.9", User: "root", SaveSecrets: true}}) + if err != nil { + t.Fatalf("Save: %v", err) + } + if err := s.Select("nope"); !errors.Is(err, ErrNotFound) { + t.Fatalf("Select of an unknown id = %v, want ErrNotFound", err) + } + if err := s.Select(saved.ID); err != nil { + t.Fatalf("Select: %v", err) + } + + reopened, err := NewSources(path, Source{Name: "this host"}) + if err != nil { + t.Fatalf("NewSources: %v", err) + } + if got := reopened.Selected(); got != saved.ID { + t.Fatalf("selected after restart = %q, want %q", got, saved.ID) + } + if len(reopened.List()) != 2 { + t.Fatalf("sources after restart = %+v", reopened.List()) + } + + // Deleting the selected source falls back to the local one. + if err := reopened.Delete(saved.ID); err != nil { + t.Fatalf("Delete: %v", err) + } + if got := reopened.Selected(); got != LocalSourceID { + t.Fatalf("selected after delete = %q, want %q", got, LocalSourceID) + } + if _, err := reopened.Get(saved.ID); !errors.Is(err, ErrNotFound) { + t.Fatalf("Get after delete = %v, want ErrNotFound", err) + } +} + +func TestSourcesUnknownSelectionIgnoredOnLoad(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "sources.json") + body := `{"selected":"gone","sources":[{"id":"gone-too","name":"x","kind":"docker","dockerHost":"tcp://h:2375"}]}` + if err := os.WriteFile(path, []byte(body), 0o600); err != nil { + t.Fatalf("write: %v", err) + } + s, err := NewSources(path, Source{Name: "this host"}) + if err != nil { + t.Fatalf("NewSources: %v", err) + } + if got := s.Selected(); got != LocalSourceID { + t.Fatalf("selected = %q, want the local fallback", got) + } +} diff --git a/internal/webui/dist/assets/index-2w4y0Lpg.js b/internal/webui/dist/assets/index-2w4y0Lpg.js deleted file mode 100644 index ec111a4..0000000 --- a/internal/webui/dist/assets/index-2w4y0Lpg.js +++ /dev/null @@ -1,11 +0,0 @@ -(function(){const N=document.createElement("link").relList;if(N&&N.supports&&N.supports("modulepreload"))return;for(const _ of document.querySelectorAll('link[rel="modulepreload"]'))s(_);new MutationObserver(_=>{for(const H of _)if(H.type==="childList")for(const E of H.addedNodes)E.tagName==="LINK"&&E.rel==="modulepreload"&&s(E)}).observe(document,{childList:!0,subtree:!0});function O(_){const H={};return _.integrity&&(H.integrity=_.integrity),_.referrerPolicy&&(H.referrerPolicy=_.referrerPolicy),_.crossOrigin==="use-credentials"?H.credentials="include":_.crossOrigin==="anonymous"?H.credentials="omit":H.credentials="same-origin",H}function s(_){if(_.ep)return;_.ep=!0;const H=O(_);fetch(_.href,H)}})();var mf={exports:{}},_n={};var Ar;function cy(){if(Ar)return _n;Ar=1;var h=Symbol.for("react.transitional.element"),N=Symbol.for("react.fragment");function O(s,_,H){var E=null;if(H!==void 0&&(E=""+H),_.key!==void 0&&(E=""+_.key),"key"in _){H={};for(var k in _)k!=="key"&&(H[k]=_[k])}else H=_;return _=H.ref,{$$typeof:h,type:s,key:E,ref:_!==void 0?_:null,props:H}}return _n.Fragment=N,_n.jsx=O,_n.jsxs=O,_n}var Nr;function fy(){return Nr||(Nr=1,mf.exports=cy()),mf.exports}var c=fy(),yf={exports:{}},w={};var _r;function sy(){if(_r)return w;_r=1;var h=Symbol.for("react.transitional.element"),N=Symbol.for("react.portal"),O=Symbol.for("react.fragment"),s=Symbol.for("react.strict_mode"),_=Symbol.for("react.profiler"),H=Symbol.for("react.consumer"),E=Symbol.for("react.context"),k=Symbol.for("react.forward_ref"),D=Symbol.for("react.suspense"),g=Symbol.for("react.memo"),J=Symbol.for("react.lazy"),A=Symbol.for("react.activity"),B=Symbol.iterator;function dl(d){return d===null||typeof d!="object"?null:(d=B&&d[B]||d["@@iterator"],typeof d=="function"?d:null)}var yl={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},il=Object.assign,Xl={};function El(d,x,R){this.props=d,this.context=x,this.refs=Xl,this.updater=R||yl}El.prototype.isReactComponent={},El.prototype.setState=function(d,x){if(typeof d!="object"&&typeof d!="function"&&d!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,d,x,"setState")},El.prototype.forceUpdate=function(d){this.updater.enqueueForceUpdate(this,d,"forceUpdate")};function Al(){}Al.prototype=El.prototype;function rl(d,x,R){this.props=d,this.context=x,this.refs=Xl,this.updater=R||yl}var xl=rl.prototype=new Al;xl.constructor=rl,il(xl,El.prototype),xl.isPureReactComponent=!0;var Dl=Array.isArray;function _l(){}var U={H:null,A:null,T:null,S:null},Q=Object.prototype.hasOwnProperty;function q(d,x,R){var Y=R.ref;return{$$typeof:h,type:d,key:x,ref:Y!==void 0?Y:null,props:R}}function $(d,x){return q(d.type,x,d.props)}function cl(d){return typeof d=="object"&&d!==null&&d.$$typeof===h}function hl(d){var x={"=":"=0",":":"=2"};return"$"+d.replace(/[=:]/g,function(R){return x[R]})}var Pl=/\/+/g;function Zl(d,x){return typeof d=="object"&&d!==null&&d.key!=null?hl(""+d.key):x.toString(36)}function Ll(d){switch(d.status){case"fulfilled":return d.value;case"rejected":throw d.reason;default:switch(typeof d.status=="string"?d.then(_l,_l):(d.status="pending",d.then(function(x){d.status==="pending"&&(d.status="fulfilled",d.value=x)},function(x){d.status==="pending"&&(d.status="rejected",d.reason=x)})),d.status){case"fulfilled":return d.value;case"rejected":throw d.reason}}throw d}function p(d,x,R,Y,W){var P=typeof d;(P==="undefined"||P==="boolean")&&(d=null);var ml=!1;if(d===null)ml=!0;else switch(P){case"bigint":case"string":case"number":ml=!0;break;case"object":switch(d.$$typeof){case h:case N:ml=!0;break;case J:return ml=d._init,p(ml(d._payload),x,R,Y,W)}}if(ml)return W=W(d),ml=Y===""?"."+Zl(d,0):Y,Dl(W)?(R="",ml!=null&&(R=ml.replace(Pl,"$&/")+"/"),p(W,x,R,"",function(Ha){return Ha})):W!=null&&(cl(W)&&(W=$(W,R+(W.key==null||d&&d.key===W.key?"":(""+W.key).replace(Pl,"$&/")+"/")+ml)),x.push(W)),1;ml=0;var lt=Y===""?".":Y+":";if(Dl(d))for(var Ul=0;Ul>>1,nl=p[V];if(0<_(nl,C))p[V]=C,p[M]=nl,M=V;else break l}}function O(p){return p.length===0?null:p[0]}function s(p){if(p.length===0)return null;var C=p[0],M=p.pop();if(M!==C){p[0]=M;l:for(var V=0,nl=p.length,d=nl>>>1;V_(R,M))Y_(W,R)?(p[V]=W,p[Y]=M,V=Y):(p[V]=R,p[x]=M,V=x);else if(Y_(W,M))p[V]=W,p[Y]=M,V=Y;else break l}}return C}function _(p,C){var M=p.sortIndex-C.sortIndex;return M!==0?M:p.id-C.id}if(h.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var H=performance;h.unstable_now=function(){return H.now()}}else{var E=Date,k=E.now();h.unstable_now=function(){return E.now()-k}}var D=[],g=[],J=1,A=null,B=3,dl=!1,yl=!1,il=!1,Xl=!1,El=typeof setTimeout=="function"?setTimeout:null,Al=typeof clearTimeout=="function"?clearTimeout:null,rl=typeof setImmediate<"u"?setImmediate:null;function xl(p){for(var C=O(g);C!==null;){if(C.callback===null)s(g);else if(C.startTime<=p)s(g),C.sortIndex=C.expirationTime,N(D,C);else break;C=O(g)}}function Dl(p){if(il=!1,xl(p),!yl)if(O(D)!==null)yl=!0,_l||(_l=!0,hl());else{var C=O(g);C!==null&&Ll(Dl,C.startTime-p)}}var _l=!1,U=-1,Q=5,q=-1;function $(){return Xl?!0:!(h.unstable_now()-qp&&$());){var V=A.callback;if(typeof V=="function"){A.callback=null,B=A.priorityLevel;var nl=V(A.expirationTime<=p);if(p=h.unstable_now(),typeof nl=="function"){A.callback=nl,xl(p),C=!0;break t}A===O(D)&&s(D),xl(p)}else s(D);A=O(D)}if(A!==null)C=!0;else{var d=O(g);d!==null&&Ll(Dl,d.startTime-p),C=!1}}break l}finally{A=null,B=M,dl=!1}C=void 0}}finally{C?hl():_l=!1}}}var hl;if(typeof rl=="function")hl=function(){rl(cl)};else if(typeof MessageChannel<"u"){var Pl=new MessageChannel,Zl=Pl.port2;Pl.port1.onmessage=cl,hl=function(){Zl.postMessage(null)}}else hl=function(){El(cl,0)};function Ll(p,C){U=El(function(){p(h.unstable_now())},C)}h.unstable_IdlePriority=5,h.unstable_ImmediatePriority=1,h.unstable_LowPriority=4,h.unstable_NormalPriority=3,h.unstable_Profiling=null,h.unstable_UserBlockingPriority=2,h.unstable_cancelCallback=function(p){p.callback=null},h.unstable_forceFrameRate=function(p){0>p||125V?(p.sortIndex=M,N(g,p),O(D)===null&&p===O(g)&&(il?(Al(U),U=-1):il=!0,Ll(Dl,M-V))):(p.sortIndex=nl,N(D,p),yl||dl||(yl=!0,_l||(_l=!0,hl()))),p},h.unstable_shouldYield=$,h.unstable_wrapCallback=function(p){var C=B;return function(){var M=B;B=C;try{return p.apply(this,arguments)}finally{B=M}}}})(bf)),bf}var Dr;function dy(){return Dr||(Dr=1,gf.exports=oy()),gf.exports}var Sf={exports:{}},Il={};var Ur;function ry(){if(Ur)return Il;Ur=1;var h=xf();function N(D){var g="https://react.dev/errors/"+D;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(h)}catch(N){console.error(N)}}return h(),Sf.exports=ry(),Sf.exports}var Hr;function my(){if(Hr)return On;Hr=1;var h=dy(),N=xf(),O=hy();function s(l){var t="https://react.dev/errors/"+l;if(1nl||(l.current=V[nl],V[nl]=null,nl--)}function R(l,t){nl++,V[nl]=l.current,l.current=t}var Y=d(null),W=d(null),P=d(null),ml=d(null);function lt(l,t){switch(R(P,t),R(W,l),R(Y,null),t.nodeType){case 9:case 11:l=(l=t.documentElement)&&(l=l.namespaceURI)?$d(l):0;break;default:if(l=t.tagName,t=t.namespaceURI)t=$d(t),l=Wd(t,l);else switch(l){case"svg":l=1;break;case"math":l=2;break;default:l=0}}x(Y),R(Y,l)}function Ul(){x(Y),x(W),x(P)}function Ha(l){l.memoizedState!==null&&R(ml,l);var t=Y.current,e=Wd(t,l.type);t!==e&&(R(W,l),R(Y,e))}function Dn(l){W.current===l&&(x(Y),x(W)),ml.current===l&&(x(ml),Tn._currentValue=M)}var $u,Tf;function Me(l){if($u===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);$u=t&&t[1]||"",Tf=-1)":-1n||o[a]!==v[n]){var j=` -`+o[a].replace(" at new "," at ");return l.displayName&&j.includes("")&&(j=j.replace("",l.displayName)),j}while(1<=a&&0<=n);break}}}finally{Wu=!1,Error.prepareStackTrace=e}return(e=l?l.displayName||l.name:"")?Me(e):""}function Yr(l,t){switch(l.tag){case 26:case 27:case 5:return Me(l.type);case 16:return Me("Lazy");case 13:return l.child!==t&&t!==null?Me("Suspense Fallback"):Me("Suspense");case 19:return Me("SuspenseList");case 0:case 15:return Fu(l.type,!1);case 11:return Fu(l.type.render,!1);case 1:return Fu(l.type,!0);case 31:return Me("Activity");default:return""}}function Ef(l){try{var t="",e=null;do t+=Yr(l,e),e=l,l=l.return;while(l);return t}catch(a){return` -Error generating stack: `+a.message+` -`+a.stack}}var Iu=Object.prototype.hasOwnProperty,Pu=h.unstable_scheduleCallback,li=h.unstable_cancelCallback,Gr=h.unstable_shouldYield,Xr=h.unstable_requestPaint,ot=h.unstable_now,Qr=h.unstable_getCurrentPriorityLevel,Af=h.unstable_ImmediatePriority,Nf=h.unstable_UserBlockingPriority,Un=h.unstable_NormalPriority,Zr=h.unstable_LowPriority,_f=h.unstable_IdlePriority,Lr=h.log,Vr=h.unstable_setDisableYieldValue,Ra=null,dt=null;function ne(l){if(typeof Lr=="function"&&Vr(l),dt&&typeof dt.setStrictMode=="function")try{dt.setStrictMode(Ra,l)}catch{}}var rt=Math.clz32?Math.clz32:wr,Kr=Math.log,Jr=Math.LN2;function wr(l){return l>>>=0,l===0?32:31-(Kr(l)/Jr|0)|0}var Cn=256,Hn=262144,Rn=4194304;function De(l){var t=l&42;if(t!==0)return t;switch(l&-l){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return l&261888;case 262144:case 524288:case 1048576:case 2097152:return l&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return l&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return l}}function qn(l,t,e){var a=l.pendingLanes;if(a===0)return 0;var n=0,u=l.suspendedLanes,i=l.pingedLanes;l=l.warmLanes;var f=a&134217727;return f!==0?(a=f&~u,a!==0?n=De(a):(i&=f,i!==0?n=De(i):e||(e=f&~l,e!==0&&(n=De(e))))):(f=a&~u,f!==0?n=De(f):i!==0?n=De(i):e||(e=a&~l,e!==0&&(n=De(e)))),n===0?0:t!==0&&t!==n&&(t&u)===0&&(u=n&-n,e=t&-t,u>=e||u===32&&(e&4194048)!==0)?t:n}function qa(l,t){return(l.pendingLanes&~(l.suspendedLanes&~l.pingedLanes)&t)===0}function kr(l,t){switch(l){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Of(){var l=Rn;return Rn<<=1,(Rn&62914560)===0&&(Rn=4194304),l}function ti(l){for(var t=[],e=0;31>e;e++)t.push(l);return t}function Ba(l,t){l.pendingLanes|=t,t!==268435456&&(l.suspendedLanes=0,l.pingedLanes=0,l.warmLanes=0)}function $r(l,t,e,a,n,u){var i=l.pendingLanes;l.pendingLanes=e,l.suspendedLanes=0,l.pingedLanes=0,l.warmLanes=0,l.expiredLanes&=e,l.entangledLanes&=e,l.errorRecoveryDisabledLanes&=e,l.shellSuspendCounter=0;var f=l.entanglements,o=l.expirationTimes,v=l.hiddenUpdates;for(e=i&~e;0"u")return null;try{return l.activeElement||l.body}catch{return l.body}}var th=/[\n"\\]/g;function jt(l){return l.replace(th,function(t){return"\\"+t.charCodeAt(0).toString(16)+" "})}function ci(l,t,e,a,n,u,i,f){l.name="",i!=null&&typeof i!="function"&&typeof i!="symbol"&&typeof i!="boolean"?l.type=i:l.removeAttribute("type"),t!=null?i==="number"?(t===0&&l.value===""||l.value!=t)&&(l.value=""+pt(t)):l.value!==""+pt(t)&&(l.value=""+pt(t)):i!=="submit"&&i!=="reset"||l.removeAttribute("value"),t!=null?fi(l,i,pt(t)):e!=null?fi(l,i,pt(e)):a!=null&&l.removeAttribute("value"),n==null&&u!=null&&(l.defaultChecked=!!u),n!=null&&(l.checked=n&&typeof n!="function"&&typeof n!="symbol"),f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"?l.name=""+pt(f):l.removeAttribute("name")}function Zf(l,t,e,a,n,u,i,f){if(u!=null&&typeof u!="function"&&typeof u!="symbol"&&typeof u!="boolean"&&(l.type=u),t!=null||e!=null){if(!(u!=="submit"&&u!=="reset"||t!=null)){ii(l);return}e=e!=null?""+pt(e):"",t=t!=null?""+pt(t):e,f||t===l.value||(l.value=t),l.defaultValue=t}a=a??n,a=typeof a!="function"&&typeof a!="symbol"&&!!a,l.checked=f?l.checked:!!a,l.defaultChecked=!!a,i!=null&&typeof i!="function"&&typeof i!="symbol"&&typeof i!="boolean"&&(l.name=i),ii(l)}function fi(l,t,e){t==="number"&&Gn(l.ownerDocument)===l||l.defaultValue===""+e||(l.defaultValue=""+e)}function la(l,t,e,a){if(l=l.options,t){t={};for(var n=0;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),hi=!1;if(Qt)try{var Qa={};Object.defineProperty(Qa,"passive",{get:function(){hi=!0}}),window.addEventListener("test",Qa,Qa),window.removeEventListener("test",Qa,Qa)}catch{hi=!1}var ie=null,mi=null,Qn=null;function $f(){if(Qn)return Qn;var l,t=mi,e=t.length,a,n="value"in ie?ie.value:ie.textContent,u=n.length;for(l=0;l=Va),ts=" ",es=!1;function as(l,t){switch(l){case"keyup":return Oh.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function ns(l){return l=l.detail,typeof l=="object"&&"data"in l?l.data:null}var na=!1;function Dh(l,t){switch(l){case"compositionend":return ns(t);case"keypress":return t.which!==32?null:(es=!0,ts);case"textInput":return l=t.data,l===ts&&es?null:l;default:return null}}function Uh(l,t){if(na)return l==="compositionend"||!Si&&as(l,t)?(l=$f(),Qn=mi=ie=null,na=!1,l):null;switch(l){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:e,offset:t-l};l=a}l:{for(;e;){if(e.nextSibling){e=e.nextSibling;break l}e=e.parentNode}e=void 0}e=rs(e)}}function ms(l,t){return l&&t?l===t?!0:l&&l.nodeType===3?!1:t&&t.nodeType===3?ms(l,t.parentNode):"contains"in l?l.contains(t):l.compareDocumentPosition?!!(l.compareDocumentPosition(t)&16):!1:!1}function ys(l){l=l!=null&&l.ownerDocument!=null&&l.ownerDocument.defaultView!=null?l.ownerDocument.defaultView:window;for(var t=Gn(l.document);t instanceof l.HTMLIFrameElement;){try{var e=typeof t.contentWindow.location.href=="string"}catch{e=!1}if(e)l=t.contentWindow;else break;t=Gn(l.document)}return t}function xi(l){var t=l&&l.nodeName&&l.nodeName.toLowerCase();return t&&(t==="input"&&(l.type==="text"||l.type==="search"||l.type==="tel"||l.type==="url"||l.type==="password")||t==="textarea"||l.contentEditable==="true")}var Xh=Qt&&"documentMode"in document&&11>=document.documentMode,ua=null,zi=null,ka=null,Ti=!1;function vs(l,t,e){var a=e.window===e?e.document:e.nodeType===9?e:e.ownerDocument;Ti||ua==null||ua!==Gn(a)||(a=ua,"selectionStart"in a&&xi(a)?a={start:a.selectionStart,end:a.selectionEnd}:(a=(a.ownerDocument&&a.ownerDocument.defaultView||window).getSelection(),a={anchorNode:a.anchorNode,anchorOffset:a.anchorOffset,focusNode:a.focusNode,focusOffset:a.focusOffset}),ka&&wa(ka,a)||(ka=a,a=Hu(zi,"onSelect"),0>=i,n-=i,Rt=1<<32-rt(t)+n|e<I?(al=X,X=null):al=X.sibling;var sl=b(m,X,y[I],z);if(sl===null){X===null&&(X=al);break}l&&X&&sl.alternate===null&&t(m,X),r=u(sl,r,I),fl===null?Z=sl:fl.sibling=sl,fl=sl,X=al}if(I===y.length)return e(m,X),ul&&Lt(m,I),Z;if(X===null){for(;II?(al=X,X=null):al=X.sibling;var _e=b(m,X,sl.value,z);if(_e===null){X===null&&(X=al);break}l&&X&&_e.alternate===null&&t(m,X),r=u(_e,r,I),fl===null?Z=_e:fl.sibling=_e,fl=_e,X=al}if(sl.done)return e(m,X),ul&&Lt(m,I),Z;if(X===null){for(;!sl.done;I++,sl=y.next())sl=T(m,sl.value,z),sl!==null&&(r=u(sl,r,I),fl===null?Z=sl:fl.sibling=sl,fl=sl);return ul&&Lt(m,I),Z}for(X=a(X);!sl.done;I++,sl=y.next())sl=S(X,m,I,sl.value,z),sl!==null&&(l&&sl.alternate!==null&&X.delete(sl.key===null?I:sl.key),r=u(sl,r,I),fl===null?Z=sl:fl.sibling=sl,fl=sl);return l&&X.forEach(function(iy){return t(m,iy)}),ul&&Lt(m,I),Z}function pl(m,r,y,z){if(typeof y=="object"&&y!==null&&y.type===il&&y.key===null&&(y=y.props.children),typeof y=="object"&&y!==null){switch(y.$$typeof){case dl:l:{for(var Z=y.key;r!==null;){if(r.key===Z){if(Z=y.type,Z===il){if(r.tag===7){e(m,r.sibling),z=n(r,y.props.children),z.return=m,m=z;break l}}else if(r.elementType===Z||typeof Z=="object"&&Z!==null&&Z.$$typeof===Q&&Ze(Z)===r.type){e(m,r.sibling),z=n(r,y.props),ln(z,y),z.return=m,m=z;break l}e(m,r);break}else t(m,r);r=r.sibling}y.type===il?(z=Be(y.props.children,m.mode,z,y.key),z.return=m,m=z):(z=Fn(y.type,y.key,y.props,null,m.mode,z),ln(z,y),z.return=m,m=z)}return i(m);case yl:l:{for(Z=y.key;r!==null;){if(r.key===Z)if(r.tag===4&&r.stateNode.containerInfo===y.containerInfo&&r.stateNode.implementation===y.implementation){e(m,r.sibling),z=n(r,y.children||[]),z.return=m,m=z;break l}else{e(m,r);break}else t(m,r);r=r.sibling}z=Di(y,m.mode,z),z.return=m,m=z}return i(m);case Q:return y=Ze(y),pl(m,r,y,z)}if(Ll(y))return G(m,r,y,z);if(hl(y)){if(Z=hl(y),typeof Z!="function")throw Error(s(150));return y=Z.call(y),L(m,r,y,z)}if(typeof y.then=="function")return pl(m,r,nu(y),z);if(y.$$typeof===rl)return pl(m,r,lu(m,y),z);uu(m,y)}return typeof y=="string"&&y!==""||typeof y=="number"||typeof y=="bigint"?(y=""+y,r!==null&&r.tag===6?(e(m,r.sibling),z=n(r,y),z.return=m,m=z):(e(m,r),z=Mi(y,m.mode,z),z.return=m,m=z),i(m)):e(m,r)}return function(m,r,y,z){try{Pa=0;var Z=pl(m,r,y,z);return va=null,Z}catch(X){if(X===ya||X===eu)throw X;var fl=mt(29,X,null,m.mode);return fl.lanes=z,fl.return=m,fl}}}var Ve=Gs(!0),Xs=Gs(!1),de=!1;function Li(l){l.updateQueue={baseState:l.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Vi(l,t){l=l.updateQueue,t.updateQueue===l&&(t.updateQueue={baseState:l.baseState,firstBaseUpdate:l.firstBaseUpdate,lastBaseUpdate:l.lastBaseUpdate,shared:l.shared,callbacks:null})}function re(l){return{lane:l,tag:0,payload:null,callback:null,next:null}}function he(l,t,e){var a=l.updateQueue;if(a===null)return null;if(a=a.shared,(ol&2)!==0){var n=a.pending;return n===null?t.next=t:(t.next=n.next,n.next=t),a.pending=t,t=Wn(l),zs(l,null,e),t}return $n(l,a,t,e),Wn(l)}function tn(l,t,e){if(t=t.updateQueue,t!==null&&(t=t.shared,(e&4194048)!==0)){var a=t.lanes;a&=l.pendingLanes,e|=a,t.lanes=e,Df(l,e)}}function Ki(l,t){var e=l.updateQueue,a=l.alternate;if(a!==null&&(a=a.updateQueue,e===a)){var n=null,u=null;if(e=e.firstBaseUpdate,e!==null){do{var i={lane:e.lane,tag:e.tag,payload:e.payload,callback:null,next:null};u===null?n=u=i:u=u.next=i,e=e.next}while(e!==null);u===null?n=u=t:u=u.next=t}else n=u=t;e={baseState:a.baseState,firstBaseUpdate:n,lastBaseUpdate:u,shared:a.shared,callbacks:a.callbacks},l.updateQueue=e;return}l=e.lastBaseUpdate,l===null?e.firstBaseUpdate=t:l.next=t,e.lastBaseUpdate=t}var Ji=!1;function en(){if(Ji){var l=ma;if(l!==null)throw l}}function an(l,t,e,a){Ji=!1;var n=l.updateQueue;de=!1;var u=n.firstBaseUpdate,i=n.lastBaseUpdate,f=n.shared.pending;if(f!==null){n.shared.pending=null;var o=f,v=o.next;o.next=null,i===null?u=v:i.next=v,i=o;var j=l.alternate;j!==null&&(j=j.updateQueue,f=j.lastBaseUpdate,f!==i&&(f===null?j.firstBaseUpdate=v:f.next=v,j.lastBaseUpdate=o))}if(u!==null){var T=n.baseState;i=0,j=v=o=null,f=u;do{var b=f.lane&-536870913,S=b!==f.lane;if(S?(el&b)===b:(a&b)===b){b!==0&&b===ha&&(Ji=!0),j!==null&&(j=j.next={lane:0,tag:f.tag,payload:f.payload,callback:null,next:null});l:{var G=l,L=f;b=t;var pl=e;switch(L.tag){case 1:if(G=L.payload,typeof G=="function"){T=G.call(pl,T,b);break l}T=G;break l;case 3:G.flags=G.flags&-65537|128;case 0:if(G=L.payload,b=typeof G=="function"?G.call(pl,T,b):G,b==null)break l;T=A({},T,b);break l;case 2:de=!0}}b=f.callback,b!==null&&(l.flags|=64,S&&(l.flags|=8192),S=n.callbacks,S===null?n.callbacks=[b]:S.push(b))}else S={lane:b,tag:f.tag,payload:f.payload,callback:f.callback,next:null},j===null?(v=j=S,o=T):j=j.next=S,i|=b;if(f=f.next,f===null){if(f=n.shared.pending,f===null)break;S=f,f=S.next,S.next=null,n.lastBaseUpdate=S,n.shared.pending=null}}while(!0);j===null&&(o=T),n.baseState=o,n.firstBaseUpdate=v,n.lastBaseUpdate=j,u===null&&(n.shared.lanes=0),be|=i,l.lanes=i,l.memoizedState=T}}function Qs(l,t){if(typeof l!="function")throw Error(s(191,l));l.call(t)}function Zs(l,t){var e=l.callbacks;if(e!==null)for(l.callbacks=null,l=0;lu?u:8;var i=p.T,f={};p.T=f,dc(l,!1,t,e);try{var o=n(),v=p.S;if(v!==null&&v(f,o),o!==null&&typeof o=="object"&&typeof o.then=="function"){var j=$h(o,a);cn(l,t,j,St(l))}else cn(l,t,a,St(l))}catch(T){cn(l,t,{then:function(){},status:"rejected",reason:T},St())}finally{C.p=u,i!==null&&f.types!==null&&(i.types=f.types),p.T=i}}function tm(){}function sc(l,t,e,a){if(l.tag!==5)throw Error(s(476));var n=jo(l).queue;po(l,n,t,M,e===null?tm:function(){return xo(l),e(a)})}function jo(l){var t=l.memoizedState;if(t!==null)return t;t={memoizedState:M,baseState:M,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:wt,lastRenderedState:M},next:null};var e={};return t.next={memoizedState:e,baseState:e,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:wt,lastRenderedState:e},next:null},l.memoizedState=t,l=l.alternate,l!==null&&(l.memoizedState=t),t}function xo(l){var t=jo(l);t.next===null&&(t=l.alternate.memoizedState),cn(l,t.next.queue,{},St())}function oc(){return kl(Tn)}function zo(){return Hl().memoizedState}function To(){return Hl().memoizedState}function em(l){for(var t=l.return;t!==null;){switch(t.tag){case 24:case 3:var e=St();l=re(e);var a=he(t,l,e);a!==null&&(ft(a,t,e),tn(a,t,e)),t={cache:Gi()},l.payload=t;return}t=t.return}}function am(l,t,e){var a=St();e={lane:a,revertLane:0,gesture:null,action:e,hasEagerState:!1,eagerState:null,next:null},yu(l)?Ao(t,e):(e=_i(l,t,e,a),e!==null&&(ft(e,l,a),No(e,t,a)))}function Eo(l,t,e){var a=St();cn(l,t,e,a)}function cn(l,t,e,a){var n={lane:a,revertLane:0,gesture:null,action:e,hasEagerState:!1,eagerState:null,next:null};if(yu(l))Ao(t,n);else{var u=l.alternate;if(l.lanes===0&&(u===null||u.lanes===0)&&(u=t.lastRenderedReducer,u!==null))try{var i=t.lastRenderedState,f=u(i,e);if(n.hasEagerState=!0,n.eagerState=f,ht(f,i))return $n(l,t,n,0),jl===null&&kn(),!1}catch{}if(e=_i(l,t,n,a),e!==null)return ft(e,l,a),No(e,t,a),!0}return!1}function dc(l,t,e,a){if(a={lane:2,revertLane:Lc(),gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},yu(l)){if(t)throw Error(s(479))}else t=_i(l,e,a,2),t!==null&&ft(t,l,2)}function yu(l){var t=l.alternate;return l===F||t!==null&&t===F}function Ao(l,t){ba=fu=!0;var e=l.pending;e===null?t.next=t:(t.next=e.next,e.next=t),l.pending=t}function No(l,t,e){if((e&4194048)!==0){var a=t.lanes;a&=l.pendingLanes,e|=a,t.lanes=e,Df(l,e)}}var fn={readContext:kl,use:du,useCallback:Ol,useContext:Ol,useEffect:Ol,useImperativeHandle:Ol,useLayoutEffect:Ol,useInsertionEffect:Ol,useMemo:Ol,useReducer:Ol,useRef:Ol,useState:Ol,useDebugValue:Ol,useDeferredValue:Ol,useTransition:Ol,useSyncExternalStore:Ol,useId:Ol,useHostTransitionStatus:Ol,useFormState:Ol,useActionState:Ol,useOptimistic:Ol,useMemoCache:Ol,useCacheRefresh:Ol};fn.useEffectEvent=Ol;var _o={readContext:kl,use:du,useCallback:function(l,t){return tt().memoizedState=[l,t===void 0?null:t],l},useContext:kl,useEffect:oo,useImperativeHandle:function(l,t,e){e=e!=null?e.concat([l]):null,hu(4194308,4,yo.bind(null,t,l),e)},useLayoutEffect:function(l,t){return hu(4194308,4,l,t)},useInsertionEffect:function(l,t){hu(4,2,l,t)},useMemo:function(l,t){var e=tt();t=t===void 0?null:t;var a=l();if(Ke){ne(!0);try{l()}finally{ne(!1)}}return e.memoizedState=[a,t],a},useReducer:function(l,t,e){var a=tt();if(e!==void 0){var n=e(t);if(Ke){ne(!0);try{e(t)}finally{ne(!1)}}}else n=t;return a.memoizedState=a.baseState=n,l={pending:null,lanes:0,dispatch:null,lastRenderedReducer:l,lastRenderedState:n},a.queue=l,l=l.dispatch=am.bind(null,F,l),[a.memoizedState,l]},useRef:function(l){var t=tt();return l={current:l},t.memoizedState=l},useState:function(l){l=nc(l);var t=l.queue,e=Eo.bind(null,F,t);return t.dispatch=e,[l.memoizedState,e]},useDebugValue:cc,useDeferredValue:function(l,t){var e=tt();return fc(e,l,t)},useTransition:function(){var l=nc(!1);return l=po.bind(null,F,l.queue,!0,!1),tt().memoizedState=l,[!1,l]},useSyncExternalStore:function(l,t,e){var a=F,n=tt();if(ul){if(e===void 0)throw Error(s(407));e=e()}else{if(e=t(),jl===null)throw Error(s(349));(el&127)!==0||ks(a,t,e)}n.memoizedState=e;var u={value:e,getSnapshot:t};return n.queue=u,oo(Ws.bind(null,a,u,l),[l]),a.flags|=2048,pa(9,{destroy:void 0},$s.bind(null,a,u,e,t),null),e},useId:function(){var l=tt(),t=jl.identifierPrefix;if(ul){var e=qt,a=Rt;e=(a&~(1<<32-rt(a)-1)).toString(32)+e,t="_"+t+"R_"+e,e=su++,0<\/script>",u=u.removeChild(u.firstChild);break;case"select":u=typeof a.is=="string"?i.createElement("select",{is:a.is}):i.createElement("select"),a.multiple?u.multiple=!0:a.size&&(u.size=a.size);break;default:u=typeof a.is=="string"?i.createElement(n,{is:a.is}):i.createElement(n)}}u[Jl]=t,u[et]=a;l:for(i=t.child;i!==null;){if(i.tag===5||i.tag===6)u.appendChild(i.stateNode);else if(i.tag!==4&&i.tag!==27&&i.child!==null){i.child.return=i,i=i.child;continue}if(i===t)break l;for(;i.sibling===null;){if(i.return===null||i.return===t)break l;i=i.return}i.sibling.return=i.return,i=i.sibling}t.stateNode=u;l:switch(Wl(u,n,a),n){case"button":case"input":case"select":case"textarea":a=!!a.autoFocus;break l;case"img":a=!0;break l;default:a=!1}a&&$t(t)}}return Tl(t),Ec(t,t.type,l===null?null:l.memoizedProps,t.pendingProps,e),null;case 6:if(l&&t.stateNode!=null)l.memoizedProps!==a&&$t(t);else{if(typeof a!="string"&&t.stateNode===null)throw Error(s(166));if(l=P.current,da(t)){if(l=t.stateNode,e=t.memoizedProps,a=null,n=wl,n!==null)switch(n.tag){case 27:case 5:a=n.memoizedProps}l[Jl]=t,l=!!(l.nodeValue===e||a!==null&&a.suppressHydrationWarning===!0||wd(l.nodeValue,e)),l||se(t,!0)}else l=Ru(l).createTextNode(a),l[Jl]=t,t.stateNode=l}return Tl(t),null;case 31:if(e=t.memoizedState,l===null||l.memoizedState!==null){if(a=da(t),e!==null){if(l===null){if(!a)throw Error(s(318));if(l=t.memoizedState,l=l!==null?l.dehydrated:null,!l)throw Error(s(557));l[Jl]=t}else Ye(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;Tl(t),l=!1}else e=Ri(),l!==null&&l.memoizedState!==null&&(l.memoizedState.hydrationErrors=e),l=!0;if(!l)return t.flags&256?(vt(t),t):(vt(t),null);if((t.flags&128)!==0)throw Error(s(558))}return Tl(t),null;case 13:if(a=t.memoizedState,l===null||l.memoizedState!==null&&l.memoizedState.dehydrated!==null){if(n=da(t),a!==null&&a.dehydrated!==null){if(l===null){if(!n)throw Error(s(318));if(n=t.memoizedState,n=n!==null?n.dehydrated:null,!n)throw Error(s(317));n[Jl]=t}else Ye(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;Tl(t),n=!1}else n=Ri(),l!==null&&l.memoizedState!==null&&(l.memoizedState.hydrationErrors=n),n=!0;if(!n)return t.flags&256?(vt(t),t):(vt(t),null)}return vt(t),(t.flags&128)!==0?(t.lanes=e,t):(e=a!==null,l=l!==null&&l.memoizedState!==null,e&&(a=t.child,n=null,a.alternate!==null&&a.alternate.memoizedState!==null&&a.alternate.memoizedState.cachePool!==null&&(n=a.alternate.memoizedState.cachePool.pool),u=null,a.memoizedState!==null&&a.memoizedState.cachePool!==null&&(u=a.memoizedState.cachePool.pool),u!==n&&(a.flags|=2048)),e!==l&&e&&(t.child.flags|=8192),pu(t,t.updateQueue),Tl(t),null);case 4:return Ul(),l===null&&wc(t.stateNode.containerInfo),Tl(t),null;case 10:return Kt(t.type),Tl(t),null;case 19:if(x(Cl),a=t.memoizedState,a===null)return Tl(t),null;if(n=(t.flags&128)!==0,u=a.rendering,u===null)if(n)on(a,!1);else{if(Ml!==0||l!==null&&(l.flags&128)!==0)for(l=t.child;l!==null;){if(u=cu(l),u!==null){for(t.flags|=128,on(a,!1),l=u.updateQueue,t.updateQueue=l,pu(t,l),t.subtreeFlags=0,l=e,e=t.child;e!==null;)Ts(e,l),e=e.sibling;return R(Cl,Cl.current&1|2),ul&&Lt(t,a.treeForkCount),t.child}l=l.sibling}a.tail!==null&&ot()>Eu&&(t.flags|=128,n=!0,on(a,!1),t.lanes=4194304)}else{if(!n)if(l=cu(u),l!==null){if(t.flags|=128,n=!0,l=l.updateQueue,t.updateQueue=l,pu(t,l),on(a,!0),a.tail===null&&a.tailMode==="hidden"&&!u.alternate&&!ul)return Tl(t),null}else 2*ot()-a.renderingStartTime>Eu&&e!==536870912&&(t.flags|=128,n=!0,on(a,!1),t.lanes=4194304);a.isBackwards?(u.sibling=t.child,t.child=u):(l=a.last,l!==null?l.sibling=u:t.child=u,a.last=u)}return a.tail!==null?(l=a.tail,a.rendering=l,a.tail=l.sibling,a.renderingStartTime=ot(),l.sibling=null,e=Cl.current,R(Cl,n?e&1|2:e&1),ul&&Lt(t,a.treeForkCount),l):(Tl(t),null);case 22:case 23:return vt(t),ki(),a=t.memoizedState!==null,l!==null?l.memoizedState!==null!==a&&(t.flags|=8192):a&&(t.flags|=8192),a?(e&536870912)!==0&&(t.flags&128)===0&&(Tl(t),t.subtreeFlags&6&&(t.flags|=8192)):Tl(t),e=t.updateQueue,e!==null&&pu(t,e.retryQueue),e=null,l!==null&&l.memoizedState!==null&&l.memoizedState.cachePool!==null&&(e=l.memoizedState.cachePool.pool),a=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(a=t.memoizedState.cachePool.pool),a!==e&&(t.flags|=2048),l!==null&&x(Qe),null;case 24:return e=null,l!==null&&(e=l.memoizedState.cache),t.memoizedState.cache!==e&&(t.flags|=2048),Kt(Rl),Tl(t),null;case 25:return null;case 30:return null}throw Error(s(156,t.tag))}function fm(l,t){switch(Ci(t),t.tag){case 1:return l=t.flags,l&65536?(t.flags=l&-65537|128,t):null;case 3:return Kt(Rl),Ul(),l=t.flags,(l&65536)!==0&&(l&128)===0?(t.flags=l&-65537|128,t):null;case 26:case 27:case 5:return Dn(t),null;case 31:if(t.memoizedState!==null){if(vt(t),t.alternate===null)throw Error(s(340));Ye()}return l=t.flags,l&65536?(t.flags=l&-65537|128,t):null;case 13:if(vt(t),l=t.memoizedState,l!==null&&l.dehydrated!==null){if(t.alternate===null)throw Error(s(340));Ye()}return l=t.flags,l&65536?(t.flags=l&-65537|128,t):null;case 19:return x(Cl),null;case 4:return Ul(),null;case 10:return Kt(t.type),null;case 22:case 23:return vt(t),ki(),l!==null&&x(Qe),l=t.flags,l&65536?(t.flags=l&-65537|128,t):null;case 24:return Kt(Rl),null;case 25:return null;default:return null}}function Io(l,t){switch(Ci(t),t.tag){case 3:Kt(Rl),Ul();break;case 26:case 27:case 5:Dn(t);break;case 4:Ul();break;case 31:t.memoizedState!==null&&vt(t);break;case 13:vt(t);break;case 19:x(Cl);break;case 10:Kt(t.type);break;case 22:case 23:vt(t),ki(),l!==null&&x(Qe);break;case 24:Kt(Rl)}}function dn(l,t){try{var e=t.updateQueue,a=e!==null?e.lastEffect:null;if(a!==null){var n=a.next;e=n;do{if((e.tag&l)===l){a=void 0;var u=e.create,i=e.inst;a=u(),i.destroy=a}e=e.next}while(e!==n)}}catch(f){gl(t,t.return,f)}}function ve(l,t,e){try{var a=t.updateQueue,n=a!==null?a.lastEffect:null;if(n!==null){var u=n.next;a=u;do{if((a.tag&l)===l){var i=a.inst,f=i.destroy;if(f!==void 0){i.destroy=void 0,n=t;var o=e,v=f;try{v()}catch(j){gl(n,o,j)}}}a=a.next}while(a!==u)}}catch(j){gl(t,t.return,j)}}function Po(l){var t=l.updateQueue;if(t!==null){var e=l.stateNode;try{Zs(t,e)}catch(a){gl(l,l.return,a)}}}function ld(l,t,e){e.props=Je(l.type,l.memoizedProps),e.state=l.memoizedState;try{e.componentWillUnmount()}catch(a){gl(l,t,a)}}function rn(l,t){try{var e=l.ref;if(e!==null){switch(l.tag){case 26:case 27:case 5:var a=l.stateNode;break;case 30:a=l.stateNode;break;default:a=l.stateNode}typeof e=="function"?l.refCleanup=e(a):e.current=a}}catch(n){gl(l,t,n)}}function Bt(l,t){var e=l.ref,a=l.refCleanup;if(e!==null)if(typeof a=="function")try{a()}catch(n){gl(l,t,n)}finally{l.refCleanup=null,l=l.alternate,l!=null&&(l.refCleanup=null)}else if(typeof e=="function")try{e(null)}catch(n){gl(l,t,n)}else e.current=null}function td(l){var t=l.type,e=l.memoizedProps,a=l.stateNode;try{l:switch(t){case"button":case"input":case"select":case"textarea":e.autoFocus&&a.focus();break l;case"img":e.src?a.src=e.src:e.srcSet&&(a.srcset=e.srcSet)}}catch(n){gl(l,l.return,n)}}function Ac(l,t,e){try{var a=l.stateNode;Mm(a,l.type,e,t),a[et]=t}catch(n){gl(l,l.return,n)}}function ed(l){return l.tag===5||l.tag===3||l.tag===26||l.tag===27&&ze(l.type)||l.tag===4}function Nc(l){l:for(;;){for(;l.sibling===null;){if(l.return===null||ed(l.return))return null;l=l.return}for(l.sibling.return=l.return,l=l.sibling;l.tag!==5&&l.tag!==6&&l.tag!==18;){if(l.tag===27&&ze(l.type)||l.flags&2||l.child===null||l.tag===4)continue l;l.child.return=l,l=l.child}if(!(l.flags&2))return l.stateNode}}function _c(l,t,e){var a=l.tag;if(a===5||a===6)l=l.stateNode,t?(e.nodeType===9?e.body:e.nodeName==="HTML"?e.ownerDocument.body:e).insertBefore(l,t):(t=e.nodeType===9?e.body:e.nodeName==="HTML"?e.ownerDocument.body:e,t.appendChild(l),e=e._reactRootContainer,e!=null||t.onclick!==null||(t.onclick=Xt));else if(a!==4&&(a===27&&ze(l.type)&&(e=l.stateNode,t=null),l=l.child,l!==null))for(_c(l,t,e),l=l.sibling;l!==null;)_c(l,t,e),l=l.sibling}function ju(l,t,e){var a=l.tag;if(a===5||a===6)l=l.stateNode,t?e.insertBefore(l,t):e.appendChild(l);else if(a!==4&&(a===27&&ze(l.type)&&(e=l.stateNode),l=l.child,l!==null))for(ju(l,t,e),l=l.sibling;l!==null;)ju(l,t,e),l=l.sibling}function ad(l){var t=l.stateNode,e=l.memoizedProps;try{for(var a=l.type,n=t.attributes;n.length;)t.removeAttributeNode(n[0]);Wl(t,a,e),t[Jl]=l,t[et]=e}catch(u){gl(l,l.return,u)}}var Wt=!1,Yl=!1,Oc=!1,nd=typeof WeakSet=="function"?WeakSet:Set,Kl=null;function sm(l,t){if(l=l.containerInfo,Wc=Zu,l=ys(l),xi(l)){if("selectionStart"in l)var e={start:l.selectionStart,end:l.selectionEnd};else l:{e=(e=l.ownerDocument)&&e.defaultView||window;var a=e.getSelection&&e.getSelection();if(a&&a.rangeCount!==0){e=a.anchorNode;var n=a.anchorOffset,u=a.focusNode;a=a.focusOffset;try{e.nodeType,u.nodeType}catch{e=null;break l}var i=0,f=-1,o=-1,v=0,j=0,T=l,b=null;t:for(;;){for(var S;T!==e||n!==0&&T.nodeType!==3||(f=i+n),T!==u||a!==0&&T.nodeType!==3||(o=i+a),T.nodeType===3&&(i+=T.nodeValue.length),(S=T.firstChild)!==null;)b=T,T=S;for(;;){if(T===l)break t;if(b===e&&++v===n&&(f=i),b===u&&++j===a&&(o=i),(S=T.nextSibling)!==null)break;T=b,b=T.parentNode}T=S}e=f===-1||o===-1?null:{start:f,end:o}}else e=null}e=e||{start:0,end:0}}else e=null;for(Fc={focusedElem:l,selectionRange:e},Zu=!1,Kl=t;Kl!==null;)if(t=Kl,l=t.child,(t.subtreeFlags&1028)!==0&&l!==null)l.return=t,Kl=l;else for(;Kl!==null;){switch(t=Kl,u=t.alternate,l=t.flags,t.tag){case 0:if((l&4)!==0&&(l=t.updateQueue,l=l!==null?l.events:null,l!==null))for(e=0;e title"))),Wl(u,a,e),u[Jl]=l,Vl(u),a=u;break l;case"link":var i=or("link","href",n).get(a+(e.href||""));if(i){for(var f=0;fpl&&(i=pl,pl=L,L=i);var m=hs(f,L),r=hs(f,pl);if(m&&r&&(S.rangeCount!==1||S.anchorNode!==m.node||S.anchorOffset!==m.offset||S.focusNode!==r.node||S.focusOffset!==r.offset)){var y=T.createRange();y.setStart(m.node,m.offset),S.removeAllRanges(),L>pl?(S.addRange(y),S.extend(r.node,r.offset)):(y.setEnd(r.node,r.offset),S.addRange(y))}}}}for(T=[],S=f;S=S.parentNode;)S.nodeType===1&&T.push({element:S,left:S.scrollLeft,top:S.scrollTop});for(typeof f.focus=="function"&&f.focus(),f=0;fe?32:e,p.T=null,e=qc,qc=null;var u=pe,i=te;if(Ql=0,Ea=pe=null,te=0,(ol&6)!==0)throw Error(s(331));var f=ol;if(ol|=4,yd(u.current),rd(u,u.current,i,e),ol=f,bn(0,!1),dt&&typeof dt.onPostCommitFiberRoot=="function")try{dt.onPostCommitFiberRoot(Ra,u)}catch{}return!0}finally{C.p=n,p.T=a,Cd(l,t)}}function Rd(l,t,e){t=zt(e,t),t=yc(l.stateNode,t,2),l=he(l,t,2),l!==null&&(Ba(l,2),Yt(l))}function gl(l,t,e){if(l.tag===3)Rd(l,l,e);else for(;t!==null;){if(t.tag===3){Rd(t,l,e);break}else if(t.tag===1){var a=t.stateNode;if(typeof t.type.getDerivedStateFromError=="function"||typeof a.componentDidCatch=="function"&&(Se===null||!Se.has(a))){l=zt(e,l),e=qo(2),a=he(t,e,2),a!==null&&(Bo(e,a,t,l),Ba(a,2),Yt(a));break}}t=t.return}}function Xc(l,t,e){var a=l.pingCache;if(a===null){a=l.pingCache=new rm;var n=new Set;a.set(t,n)}else n=a.get(t),n===void 0&&(n=new Set,a.set(t,n));n.has(e)||(Uc=!0,n.add(e),l=gm.bind(null,l,t,e),t.then(l,l))}function gm(l,t,e){var a=l.pingCache;a!==null&&a.delete(t),l.pingedLanes|=l.suspendedLanes&e,l.warmLanes&=~e,jl===l&&(el&e)===e&&(Ml===4||Ml===3&&(el&62914560)===el&&300>ot()-Tu?(ol&2)===0&&Aa(l,0):Cc|=e,Ta===el&&(Ta=0)),Yt(l)}function qd(l,t){t===0&&(t=Of()),l=qe(l,t),l!==null&&(Ba(l,t),Yt(l))}function bm(l){var t=l.memoizedState,e=0;t!==null&&(e=t.retryLane),qd(l,e)}function Sm(l,t){var e=0;switch(l.tag){case 31:case 13:var a=l.stateNode,n=l.memoizedState;n!==null&&(e=n.retryLane);break;case 19:a=l.stateNode;break;case 22:a=l.stateNode._retryCache;break;default:throw Error(s(314))}a!==null&&a.delete(t),qd(l,e)}function pm(l,t){return Pu(l,t)}var Du=null,_a=null,Qc=!1,Uu=!1,Zc=!1,xe=0;function Yt(l){l!==_a&&l.next===null&&(_a===null?Du=_a=l:_a=_a.next=l),Uu=!0,Qc||(Qc=!0,xm())}function bn(l,t){if(!Zc&&Uu){Zc=!0;do for(var e=!1,a=Du;a!==null;){if(l!==0){var n=a.pendingLanes;if(n===0)var u=0;else{var i=a.suspendedLanes,f=a.pingedLanes;u=(1<<31-rt(42|l)+1)-1,u&=n&~(i&~f),u=u&201326741?u&201326741|1:u?u|2:0}u!==0&&(e=!0,Xd(a,u))}else u=el,u=qn(a,a===jl?u:0,a.cancelPendingCommit!==null||a.timeoutHandle!==-1),(u&3)===0||qa(a,u)||(e=!0,Xd(a,u));a=a.next}while(e);Zc=!1}}function jm(){Bd()}function Bd(){Uu=Qc=!1;var l=0;xe!==0&&Um()&&(l=xe);for(var t=ot(),e=null,a=Du;a!==null;){var n=a.next,u=Yd(a,t);u===0?(a.next=null,e===null?Du=n:e.next=n,n===null&&(_a=e)):(e=a,(l!==0||(u&3)!==0)&&(Uu=!0)),a=n}Ql!==0&&Ql!==5||bn(l),xe!==0&&(xe=0)}function Yd(l,t){for(var e=l.suspendedLanes,a=l.pingedLanes,n=l.expirationTimes,u=l.pendingLanes&-62914561;0f)break;var j=o.transferSize,T=o.initiatorType;j&&kd(T)&&(o=o.responseEnd,i+=j*(o"u"?null:document;function ir(l,t,e){var a=Oa;if(a&&typeof t=="string"&&t){var n=jt(t);n='link[rel="'+l+'"][href="'+n+'"]',typeof e=="string"&&(n+='[crossorigin="'+e+'"]'),ur.has(n)||(ur.add(n),l={rel:l,crossOrigin:e,href:t},a.querySelector(n)===null&&(t=a.createElement("link"),Wl(t,"link",l),Vl(t),a.head.appendChild(t)))}}function Qm(l){ee.D(l),ir("dns-prefetch",l,null)}function Zm(l,t){ee.C(l,t),ir("preconnect",l,t)}function Lm(l,t,e){ee.L(l,t,e);var a=Oa;if(a&&l&&t){var n='link[rel="preload"][as="'+jt(t)+'"]';t==="image"&&e&&e.imageSrcSet?(n+='[imagesrcset="'+jt(e.imageSrcSet)+'"]',typeof e.imageSizes=="string"&&(n+='[imagesizes="'+jt(e.imageSizes)+'"]')):n+='[href="'+jt(l)+'"]';var u=n;switch(t){case"style":u=Ma(l);break;case"script":u=Da(l)}Ot.has(u)||(l=A({rel:"preload",href:t==="image"&&e&&e.imageSrcSet?void 0:l,as:t},e),Ot.set(u,l),a.querySelector(n)!==null||t==="style"&&a.querySelector(xn(u))||t==="script"&&a.querySelector(zn(u))||(t=a.createElement("link"),Wl(t,"link",l),Vl(t),a.head.appendChild(t)))}}function Vm(l,t){ee.m(l,t);var e=Oa;if(e&&l){var a=t&&typeof t.as=="string"?t.as:"script",n='link[rel="modulepreload"][as="'+jt(a)+'"][href="'+jt(l)+'"]',u=n;switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":u=Da(l)}if(!Ot.has(u)&&(l=A({rel:"modulepreload",href:l},t),Ot.set(u,l),e.querySelector(n)===null)){switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(e.querySelector(zn(u)))return}a=e.createElement("link"),Wl(a,"link",l),Vl(a),e.head.appendChild(a)}}}function Km(l,t,e){ee.S(l,t,e);var a=Oa;if(a&&l){var n=Ie(a).hoistableStyles,u=Ma(l);t=t||"default";var i=n.get(u);if(!i){var f={loading:0,preload:null};if(i=a.querySelector(xn(u)))f.loading=5;else{l=A({rel:"stylesheet",href:l,"data-precedence":t},e),(e=Ot.get(u))&&nf(l,e);var o=i=a.createElement("link");Vl(o),Wl(o,"link",l),o._p=new Promise(function(v,j){o.onload=v,o.onerror=j}),o.addEventListener("load",function(){f.loading|=1}),o.addEventListener("error",function(){f.loading|=2}),f.loading|=4,Bu(i,t,a)}i={type:"stylesheet",instance:i,count:1,state:f},n.set(u,i)}}}function Jm(l,t){ee.X(l,t);var e=Oa;if(e&&l){var a=Ie(e).hoistableScripts,n=Da(l),u=a.get(n);u||(u=e.querySelector(zn(n)),u||(l=A({src:l,async:!0},t),(t=Ot.get(n))&&uf(l,t),u=e.createElement("script"),Vl(u),Wl(u,"link",l),e.head.appendChild(u)),u={type:"script",instance:u,count:1,state:null},a.set(n,u))}}function wm(l,t){ee.M(l,t);var e=Oa;if(e&&l){var a=Ie(e).hoistableScripts,n=Da(l),u=a.get(n);u||(u=e.querySelector(zn(n)),u||(l=A({src:l,async:!0,type:"module"},t),(t=Ot.get(n))&&uf(l,t),u=e.createElement("script"),Vl(u),Wl(u,"link",l),e.head.appendChild(u)),u={type:"script",instance:u,count:1,state:null},a.set(n,u))}}function cr(l,t,e,a){var n=(n=P.current)?qu(n):null;if(!n)throw Error(s(446));switch(l){case"meta":case"title":return null;case"style":return typeof e.precedence=="string"&&typeof e.href=="string"?(t=Ma(e.href),e=Ie(n).hoistableStyles,a=e.get(t),a||(a={type:"style",instance:null,count:0,state:null},e.set(t,a)),a):{type:"void",instance:null,count:0,state:null};case"link":if(e.rel==="stylesheet"&&typeof e.href=="string"&&typeof e.precedence=="string"){l=Ma(e.href);var u=Ie(n).hoistableStyles,i=u.get(l);if(i||(n=n.ownerDocument||n,i={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},u.set(l,i),(u=n.querySelector(xn(l)))&&!u._p&&(i.instance=u,i.state.loading=5),Ot.has(l)||(e={rel:"preload",as:"style",href:e.href,crossOrigin:e.crossOrigin,integrity:e.integrity,media:e.media,hrefLang:e.hrefLang,referrerPolicy:e.referrerPolicy},Ot.set(l,e),u||km(n,l,e,i.state))),t&&a===null)throw Error(s(528,""));return i}if(t&&a!==null)throw Error(s(529,""));return null;case"script":return t=e.async,e=e.src,typeof e=="string"&&t&&typeof t!="function"&&typeof t!="symbol"?(t=Da(e),e=Ie(n).hoistableScripts,a=e.get(t),a||(a={type:"script",instance:null,count:0,state:null},e.set(t,a)),a):{type:"void",instance:null,count:0,state:null};default:throw Error(s(444,l))}}function Ma(l){return'href="'+jt(l)+'"'}function xn(l){return'link[rel="stylesheet"]['+l+"]"}function fr(l){return A({},l,{"data-precedence":l.precedence,precedence:null})}function km(l,t,e,a){l.querySelector('link[rel="preload"][as="style"]['+t+"]")?a.loading=1:(t=l.createElement("link"),a.preload=t,t.addEventListener("load",function(){return a.loading|=1}),t.addEventListener("error",function(){return a.loading|=2}),Wl(t,"link",e),Vl(t),l.head.appendChild(t))}function Da(l){return'[src="'+jt(l)+'"]'}function zn(l){return"script[async]"+l}function sr(l,t,e){if(t.count++,t.instance===null)switch(t.type){case"style":var a=l.querySelector('style[data-href~="'+jt(e.href)+'"]');if(a)return t.instance=a,Vl(a),a;var n=A({},e,{"data-href":e.href,"data-precedence":e.precedence,href:null,precedence:null});return a=(l.ownerDocument||l).createElement("style"),Vl(a),Wl(a,"style",n),Bu(a,e.precedence,l),t.instance=a;case"stylesheet":n=Ma(e.href);var u=l.querySelector(xn(n));if(u)return t.state.loading|=4,t.instance=u,Vl(u),u;a=fr(e),(n=Ot.get(n))&&nf(a,n),u=(l.ownerDocument||l).createElement("link"),Vl(u);var i=u;return i._p=new Promise(function(f,o){i.onload=f,i.onerror=o}),Wl(u,"link",a),t.state.loading|=4,Bu(u,e.precedence,l),t.instance=u;case"script":return u=Da(e.src),(n=l.querySelector(zn(u)))?(t.instance=n,Vl(n),n):(a=e,(n=Ot.get(u))&&(a=A({},e),uf(a,n)),l=l.ownerDocument||l,n=l.createElement("script"),Vl(n),Wl(n,"link",a),l.head.appendChild(n),t.instance=n);case"void":return null;default:throw Error(s(443,t.type))}else t.type==="stylesheet"&&(t.state.loading&4)===0&&(a=t.instance,t.state.loading|=4,Bu(a,e.precedence,l));return t.instance}function Bu(l,t,e){for(var a=e.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),n=a.length?a[a.length-1]:null,u=n,i=0;i title"):null)}function $m(l,t,e){if(e===1||t.itemProp!=null)return!1;switch(l){case"meta":case"title":return!0;case"style":if(typeof t.precedence!="string"||typeof t.href!="string"||t.href==="")break;return!0;case"link":if(typeof t.rel!="string"||typeof t.href!="string"||t.href===""||t.onLoad||t.onError)break;return t.rel==="stylesheet"?(l=t.disabled,typeof t.precedence=="string"&&l==null):!0;case"script":if(t.async&&typeof t.async!="function"&&typeof t.async!="symbol"&&!t.onLoad&&!t.onError&&t.src&&typeof t.src=="string")return!0}return!1}function rr(l){return!(l.type==="stylesheet"&&(l.state.loading&3)===0)}function Wm(l,t,e,a){if(e.type==="stylesheet"&&(typeof a.media!="string"||matchMedia(a.media).matches!==!1)&&(e.state.loading&4)===0){if(e.instance===null){var n=Ma(a.href),u=t.querySelector(xn(n));if(u){t=u._p,t!==null&&typeof t=="object"&&typeof t.then=="function"&&(l.count++,l=Gu.bind(l),t.then(l,l)),e.state.loading|=4,e.instance=u,Vl(u);return}u=t.ownerDocument||t,a=fr(a),(n=Ot.get(n))&&nf(a,n),u=u.createElement("link"),Vl(u);var i=u;i._p=new Promise(function(f,o){i.onload=f,i.onerror=o}),Wl(u,"link",a),e.instance=u}l.stylesheets===null&&(l.stylesheets=new Map),l.stylesheets.set(e,t),(t=e.state.preload)&&(e.state.loading&3)===0&&(l.count++,e=Gu.bind(l),t.addEventListener("load",e),t.addEventListener("error",e))}}var cf=0;function Fm(l,t){return l.stylesheets&&l.count===0&&Qu(l,l.stylesheets),0cf?50:800)+t);return l.unsuspend=e,function(){l.unsuspend=null,clearTimeout(a),clearTimeout(n)}}:null}function Gu(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Qu(this,this.stylesheets);else if(this.unsuspend){var l=this.unsuspend;this.unsuspend=null,l()}}}var Xu=null;function Qu(l,t){l.stylesheets=null,l.unsuspend!==null&&(l.count++,Xu=new Map,t.forEach(Im,l),Xu=null,Gu.call(l))}function Im(l,t){if(!(t.state.loading&4)){var e=Xu.get(l);if(e)var a=e.get(null);else{e=new Map,Xu.set(l,e);for(var n=l.querySelectorAll("link[data-precedence],style[data-precedence]"),u=0;u"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(h)}catch(N){console.error(N)}}return h(),vf.exports=my(),vf.exports}var vy=yy();function gy(){const h=new URLSearchParams(location.search).get("token");if(h){sessionStorage.setItem("dm.token",h);const N=location.pathname+location.hash;return history.replaceState(null,"",N),h}return sessionStorage.getItem("dm.token")??""}const Ca=gy();class pf extends Error{status;body;constructor(N,O,s={}){super(O),this.status=N,this.body=s}get needsTrust(){return this.body.needsTrust===!0}}async function Mt(h,N){const O={...N?.headers};Ca&&(O["X-Auth-Token"]=Ca),N?.body&&(O["Content-Type"]="application/json");const s=await fetch(h,{...N,headers:O});if(s.status===204)return;const _=await s.text();let H={};if(_)try{H=JSON.parse(_)}catch{if(!s.ok)throw new pf(s.status,_.slice(0,400))}if(!s.ok){const E=typeof H.error=="string"?H.error:`request failed (${s.status})`;throw new pf(s.status,E,H)}return H}const Oe=(h,N)=>Mt(h,{method:"POST",body:N===void 0?void 0:JSON.stringify(N)}),Gl={health:()=>Mt("/api/health"),source:()=>Mt("/api/source"),volumeSizes:()=>Mt("/api/source/sizes"),connections:()=>Mt("/api/connections"),saveConnection:h=>Oe("/api/connections",h),deleteConnection:h=>Mt(`/api/connections/${h}`,{method:"DELETE"}),probe:h=>Oe(`/api/connections/${h}/probe`),trust:(h,N)=>Oe(`/api/connections/${h}/trust`,{fingerprint:N}),testConnection:h=>Oe(`/api/connections/${h}/test`),targetInventory:h=>Mt(`/api/connections/${h}/inventory`),preview:h=>Oe("/api/plan/preview",h),migrateSSH:(h,N)=>Oe("/api/migrate/ssh",{connectionId:h,plan:N}),buildPackage:(h,N)=>Oe("/api/migrate/package",{plan:h,format:N}),jobs:()=>Mt("/api/jobs"),job:h=>Mt(`/api/jobs/${h}`),cancelJob:h=>Oe(`/api/jobs/${h}/cancel`),deleteJob:h=>Mt(`/api/jobs/${h}`,{method:"DELETE"}),packages:()=>Mt("/api/packages"),deletePackage:h=>Mt(`/api/packages/${encodeURIComponent(h)}`,{method:"DELETE"}),downloadUrl:h=>`/api/packages/${encodeURIComponent(h)}/download`+(Ca?`?token=${encodeURIComponent(Ca)}`:""),jobEvents:h=>new EventSource(`/api/jobs/${h}/events`+(Ca?`?token=${encodeURIComponent(Ca)}`:""))};function ae(h){if(h==null||h<0)return"–";if(h===0)return"0 B";const N=["B","KiB","MiB","GiB","TiB","PiB"];let O=h,s=0;for(;O>=1024&&sN(H.target.checked)}),c.jsx("span",{children:O})]})}function Fl({label:h,children:N}){return c.jsxs("label",{className:"field",children:[c.jsx("span",{children:h}),N]})}function zf({title:h,onClose:N,children:O,footer:s,wide:_}){return K.useEffect(()=>{const H=E=>{E.key==="Escape"&&N()};return window.addEventListener("keydown",H),()=>window.removeEventListener("keydown",H)},[N]),c.jsx("div",{className:"modal-backdrop",onMouseDown:H=>H.target===H.currentTarget&&N(),children:c.jsxs("div",{className:"modal",style:_?{width:"min(1000px, 100%)"}:void 0,children:[c.jsxs("header",{children:[h,c.jsx("span",{className:"spacer"}),c.jsx("button",{className:"btn ghost tiny",onClick:N,children:"close"})]}),c.jsx("div",{className:"content",children:O}),s&&c.jsx("footer",{children:s})]})})}function Dt({kind:h,children:N}){return c.jsx("div",{className:`notice ${h==="info"?"":h}`,children:N})}function jf({done:h,total:N,state:O}){const s=N>0?Math.min(100,h/N*100):O==="succeeded"?100:0,_=O==="succeeded"?"done":O==="failed"?"failed":"";return c.jsx("div",{className:`progress ${_}`,children:c.jsx("div",{style:{width:`${s}%`}})})}function Br(h,N){return h.kind==="tmpfs"?0:h.sizeBytes>=0?h.sizeBytes:h.name&&N[h.name]!==void 0?N[h.name]:-1}function by({source:h,sel:N,setSel:O,targetInv:s,loading:_,sizes:H}){const[E,k]=K.useState(""),[D,g]=K.useState(new Set),[J,A]=K.useState(!1),B=h?.inventory.containers??[],dl=K.useMemo(()=>new Set((s?.containers??[]).map(U=>U.name)),[s]),yl=K.useMemo(()=>{const U=E.trim().toLowerCase();return B.filter(Q=>J&&Q.state!=="running"?!1:U?Q.name.toLowerCase().includes(U)||Q.image.toLowerCase().includes(U)||(Q.composeProject??"").toLowerCase().includes(U)||(Q.mounts??[]).some(q=>q.destination.toLowerCase().includes(U)||(q.name??"").toLowerCase().includes(U)):!0)},[B,E,J]),il=K.useMemo(()=>{const U=new Map;for(const Q of yl){const q=Q.composeProject||"",$=U.get(q);$?$.push(Q):U.set(q,[Q])}return[...U.entries()].sort((Q,q)=>Q[0]===""?1:q[0]===""?-1:Q[0].localeCompare(q[0]))},[yl]);function Xl(U,Q){O(q=>({...q,[U]:{...q[U],...Q}}))}function El(U,Q){O(q=>{const $={...q};for(const cl of U)$[cl]&&($[cl]={...$[cl],include:Q});return $})}function Al(U){O(Q=>{const q={...Q};for(const $ of B){const cl=q[$.id];cl?.include&&(q[$.id]=U(cl,$))}return q})}function rl(U,Q){Al((q,$)=>{const cl={...q.mounts};for(const hl of $.mounts??[])hl.kind!=="tmpfs"&&Q.includes(hl.kind)&&(cl[hl.destination]={...cl[hl.destination],action:U});return{...q,mounts:cl}})}const xl=yl.map(U=>U.id),Dl=yl.filter(U=>N[U.id]?.include).length,_l=Object.values(N).some(U=>U.include);return c.jsxs(c.Fragment,{children:[c.jsxs("div",{className:"toolbar",children:[c.jsx("input",{className:"search",type:"text",placeholder:"filter by name, image, mount…",value:E,onChange:U=>k(U.target.value)}),c.jsx("button",{className:"btn tiny",onClick:()=>El(xl,!0),children:"select all"}),c.jsx("button",{className:"btn tiny",onClick:()=>El(xl,!1),children:"clear"}),c.jsx("button",{className:"btn tiny",onClick:()=>El(yl.filter(U=>U.state==="running").map(U=>U.id),!0),children:"select running"}),c.jsx(st,{checked:J,onChange:A,label:c.jsx("span",{className:"small muted",children:"running only"})}),c.jsx("span",{className:"spacer"}),c.jsxs("span",{className:"small faint nowrap",children:["apply to ",Dl?`${Dl} selected`:"selection",":"]}),c.jsx("button",{className:"btn tiny",disabled:!_l,onClick:()=>rl("copy",["volume","anonymous","bind"]),children:"copy all data"}),c.jsx("button",{className:"btn tiny",disabled:!_l,onClick:()=>rl("skip",["bind"]),children:"skip binds"}),c.jsx("button",{className:"btn tiny",disabled:!_l,onClick:()=>rl("structure",["volume","anonymous","bind"]),children:"structure only"}),c.jsxs("select",{className:"btn tiny",style:{width:"auto"},disabled:!_l,value:"",onChange:U=>{const Q=U.target.value;if(Q){if(Q==="start"&&Al(q=>({...q,startAfter:!0})),Q==="nostart"&&Al(q=>({...q,startAfter:!1})),Q==="live"&&Al(q=>({...q,stopSourceDuringCopy:!1})),Q==="quiesce"&&Al(q=>({...q,stopSourceDuringCopy:!0})),Q==="keepsource"&&Al(q=>({...q,stopSourceAfter:!1})),Q==="stopsource"&&Al(q=>({...q,stopSourceAfter:!0})),Q.startsWith("img:")){const q=Q.slice(4);Al($=>({...$,migrateImage:q!=="skip",imageMode:q}))}U.target.value=""}},children:[c.jsx("option",{value:"",children:"more…"}),c.jsx("option",{value:"start",children:"start after migration"}),c.jsx("option",{value:"nostart",children:"leave stopped on target"}),c.jsx("option",{value:"quiesce",children:"stop source while copying"}),c.jsx("option",{value:"live",children:"copy while running (hot)"}),c.jsx("option",{value:"stopsource",children:"stop source after migration"}),c.jsx("option",{value:"keepsource",children:"leave source running"}),c.jsx("option",{value:"img:auto",children:"image: auto"}),c.jsx("option",{value:"img:pull",children:"image: pull on target"}),c.jsx("option",{value:"img:stream",children:"image: transfer layers"}),c.jsx("option",{value:"img:skip",children:"image: already on target"})]})]}),_&&B.length===0&&c.jsx("div",{className:"empty",children:"reading the source daemon…"}),!_&&B.length===0&&c.jsx("div",{className:"empty",children:"no containers on this host"}),!_&&B.length>0&&yl.length===0&&c.jsx("div",{className:"empty",children:"nothing matches the filter"}),c.jsx("div",{className:"clist",children:il.map(([U,Q])=>c.jsxs("div",{children:[il.length>1&&c.jsxs("div",{className:"group-head",children:[c.jsx(st,{checked:Q.every(q=>N[q.id]?.include),onChange:q=>El(Q.map($=>$.id),q),label:U?`compose: ${U}`:"standalone"}),c.jsx("span",{className:"line"}),c.jsx("span",{children:Q.length})]}),Q.map(q=>c.jsx(Sy,{c:q,s:N[q.id],onChange:$=>Xl(q.id,$),expanded:D.has(q.id),toggleExpanded:()=>g($=>{const cl=new Set($);return cl.has(q.id)?cl.delete(q.id):cl.add(q.id),cl}),conflicts:dl.has(N[q.id]?.nameOverride||q.name),sizes:H},q.id))]},U||"__none"))})]})}function Sy({c:h,s:N,onChange:O,expanded:s,toggleExpanded:_,conflicts:H,sizes:E}){if(!N)return null;const D=(h.mounts??[]).filter(A=>A.kind!=="tmpfs"),g=D.filter(A=>(N.mounts[A.destination]?.action??"copy")==="copy"),J=g.reduce((A,B)=>{const dl=Br(B,E);return A+(dl>0?dl:0)},0);return c.jsxs(c.Fragment,{children:[c.jsxs("div",{className:`crow${N.include?" selected":""}`,children:[c.jsx(st,{checked:N.include,onChange:A=>O({include:A}),label:""}),c.jsx("button",{className:"expander",onClick:_,title:"per-item options",children:s?"▾":"▸"}),c.jsxs("div",{style:{minWidth:0},children:[c.jsx("div",{className:"name truncate",title:h.name,children:h.name}),c.jsxs("div",{className:"sub row",style:{gap:6},children:[c.jsx(Mn,{state:h.state}),h.composeService&&c.jsxs("span",{className:"faint",children:["· ",h.composeService]}),H&&c.jsx("span",{className:"badge",style:{borderColor:"#5c4520",color:"#e0b556"},children:"on target"})]})]}),c.jsx("div",{className:"image truncate",title:h.image,children:h.image}),c.jsxs("div",{className:"tags",children:[D.map(A=>c.jsx("span",{className:`badge ${A.kind==="bind"?"bind":A.kind==="anonymous"?"anon":"vol"}`,title:`${A.kind} → ${A.destination}${A.readOnly?" (read-only)":""}`,style:{opacity:(N.mounts[A.destination]?.action??"copy")==="skip"?.35:1},children:A.kind==="bind"?(A.source??"").split("/").pop()||"/":A.kind==="anonymous"?"anon":A.name},A.destination)),(h.endpoints??[]).filter(A=>!["bridge","host","none"].includes(A.network)).map(A=>c.jsx("span",{className:"badge net",title:`network ${A.network}`,children:A.network},A.network)),(h.ports??[]).slice(0,3).map((A,B)=>c.jsxs("span",{className:"badge port",children:[A.hostPort,":",A.containerPort.split("/")[0]]},B)),(h.ports??[]).length>3&&c.jsxs("span",{className:"badge port",children:["+",(h.ports??[]).length-3]})]}),c.jsxs("div",{className:"small faint nowrap",style:{textAlign:"right"},children:[g.length>0?`${g.length} to copy`:"no data",J>0&&c.jsxs(c.Fragment,{children:[" · ",ae(J)]})]})]}),s&&c.jsx(py,{c:h,s:N,onChange:O,sizes:E})]})}function py({c:h,s:N,onChange:O,sizes:s}){const _=h.mounts??[];function H(E,k){O({mounts:{...N.mounts,[E]:{...N.mounts[E],...k}}})}return c.jsxs("div",{className:"detail",children:[(h.warnings??[]).map((E,k)=>c.jsx("div",{className:"notice warn",children:E},k)),c.jsxs("div",{className:"grid2",children:[c.jsx(Fl,{label:"name on target",children:c.jsx("input",{type:"text",placeholder:h.name,value:N.nameOverride??"",onChange:E=>O({nameOverride:E.target.value})})}),c.jsx(Fl,{label:"image",children:c.jsxs("select",{value:N.migrateImage?N.imageMode:"skip",onChange:E=>{const k=E.target.value;O({migrateImage:k!=="skip",imageMode:k})},children:[c.jsx("option",{value:"auto",children:"auto — reuse, pull, or transfer"}),c.jsx("option",{value:"pull",children:"pull on the target"}),c.jsx("option",{value:"stream",children:"transfer the layers"}),c.jsx("option",{value:"skip",children:"already on the target"})]})}),c.jsxs("div",{className:"stack",style:{gap:6},children:[c.jsx(st,{checked:N.migrateNetworks,onChange:E=>O({migrateNetworks:E}),label:"recreate networks and reattach"}),c.jsx(st,{checked:N.keepStaticIps,onChange:E=>O({keepStaticIps:E}),disabled:!N.migrateNetworks,label:"keep static IP addresses",title:"Only works when the target networks use the same subnets"}),c.jsx(st,{checked:N.migratePorts,onChange:E=>O({migratePorts:E}),label:"publish the same host ports"})]}),c.jsxs("div",{className:"stack",style:{gap:6},children:[c.jsx(st,{checked:N.startAfter,onChange:E=>O({startAfter:E}),label:"start on the target"}),c.jsx(st,{checked:N.stopSourceDuringCopy,onChange:E=>O({stopSourceDuringCopy:E}),label:"stop the source while copying",title:"Recommended: databases and other writers produce inconsistent copies while running"}),c.jsx(st,{checked:N.stopSourceAfter,onChange:E=>O({stopSourceAfter:E}),label:"leave the source stopped afterwards"})]})]}),_.length===0?c.jsx("div",{className:"small faint",children:"this container has no mounts"}):c.jsxs("table",{className:"mount-table",children:[c.jsx("thead",{children:c.jsxs("tr",{children:[c.jsx("th",{style:{width:74},children:"kind"}),c.jsx("th",{children:"in the container"}),c.jsx("th",{children:"on the source"}),c.jsx("th",{style:{width:130},children:"action"}),c.jsx("th",{children:"on the target"}),c.jsx("th",{style:{width:70,textAlign:"right"},children:"size"})]})}),c.jsx("tbody",{children:_.map(E=>{const k=N.mounts[E.destination]??{action:"copy"},D=E.kind==="tmpfs";return c.jsxs("tr",{children:[c.jsx("td",{children:c.jsx("span",{className:`badge ${E.kind==="bind"?"bind":E.kind==="anonymous"?"anon":E.kind==="tmpfs"?"tmpfs":"vol"}`,children:E.kind})}),c.jsxs("td",{className:"mono truncate",title:E.destination,children:[E.destination,E.readOnly&&c.jsx("span",{className:"faint",children:" :ro"})]}),c.jsx("td",{className:"mono truncate faint",title:E.source||E.name,children:E.kind==="bind"?E.source:E.kind==="anonymous"?"(generated)":E.name}),c.jsx("td",{children:c.jsxs("select",{value:k.action,disabled:D,onChange:g=>H(E.destination,{action:g.target.value}),children:[c.jsx("option",{value:"copy",children:"copy data"}),c.jsx("option",{value:"structure",children:"create empty"}),c.jsx("option",{value:"skip",children:"do not mount"})]})}),c.jsxs("td",{children:[E.kind==="bind"&&k.action!=="skip"&&c.jsx("input",{type:"text",placeholder:E.source,value:k.targetSource??"",onChange:g=>H(E.destination,{targetSource:g.target.value})}),E.kind==="volume"&&k.action!=="skip"&&c.jsx("input",{type:"text",placeholder:E.name,value:k.targetName??"",onChange:g=>H(E.destination,{targetName:g.target.value})}),E.kind==="anonymous"&&c.jsx("span",{className:"small faint",children:"a fresh volume is created"}),D&&c.jsx("span",{className:"small faint",children:"in memory, nothing to copy"})]}),c.jsx("td",{className:"small faint nowrap",style:{textAlign:"right"},children:D?"–":ae(Br(E,s))})]},E.destination)})})]})]})}function jy({source:h,plan:N,includedCount:O,options:s,setOptions:_,connections:H,activeConn:E,setActiveConn:k,reloadConnections:D,targetInv:g,connectTarget:J,onJobStarted:A,onError:B}){const[dl,yl]=K.useState(null),[il,Xl]=K.useState(null),[El,Al]=K.useState(null),[rl,xl]=K.useState(""),[Dl,_l]=K.useState(""),[U,Q]=K.useState("tar"),q=H.find(M=>M.id===E),$=g?.preflight,cl=!!$?.serverVersion;async function hl(M,V){xl(M);try{await V()}catch(nl){nl instanceof pf&&nl.needsTrust?await Zl():B(nl instanceof Error?nl.message:String(nl))}finally{xl("")}}const Pl=K.useCallback(()=>{E&&hl("test",()=>J(E))},[E]);K.useEffect(()=>{Pl()},[Pl]);async function Zl(){if(E)try{Xl(await Gl.probe(E))}catch(M){B(M instanceof Error?M.message:String(M))}}async function Ll(){!E||!il||await hl("trust",async()=>{await Gl.trust(E,il.fingerprint),Xl(null),await J(E)})}const p=O>0&&cl&&!rl,C=O>0&&!rl;return c.jsxs(c.Fragment,{children:[c.jsxs("div",{className:"section",children:[c.jsx("h3",{children:"target host"}),c.jsxs("div",{className:"stack",children:[c.jsxs("div",{className:"row",children:[c.jsxs("select",{value:E,onChange:M=>k(M.target.value),children:[c.jsx("option",{value:"",children:"— no target selected —"}),H.map(M=>c.jsxs("option",{value:M.id,children:[M.name," (",M.user,"@",M.host,")"]},M.id))]}),c.jsx("button",{className:"btn tiny",onClick:()=>yl({port:22,auth:"password",saveSecrets:!1,sudo:!1}),children:"new"})]}),q&&c.jsxs("div",{className:"row wrap",style:{gap:6},children:[c.jsx("button",{className:"btn tiny",disabled:!!rl,onClick:Pl,children:rl==="test"?"connecting…":"connect"}),c.jsx("button",{className:"btn tiny",onClick:()=>yl(q),children:"edit"}),c.jsx("button",{className:"btn tiny",onClick:Zl,children:"host key"}),c.jsx("button",{className:"btn tiny danger",onClick:()=>{confirm(`Delete connection "${q.name}"?`)&&hl("del",async()=>{await Gl.deleteConnection(q.id),D()})},children:"delete"})]}),q&&!g&&c.jsx("div",{className:"small faint",children:"not connected yet"}),$&&c.jsxs(c.Fragment,{children:[($.problems??[]).map((M,V)=>c.jsx(Dt,{kind:"warn",children:M},V)),cl&&c.jsxs("dl",{className:"kv",children:[c.jsx("dt",{children:"host"}),c.jsx("dd",{children:g?.host||q?.host}),c.jsx("dt",{children:"docker"}),c.jsxs("dd",{children:[$.serverVersion," · ",$.os,"/",$.arch]}),c.jsx("dt",{children:"free space"}),c.jsxs("dd",{children:[ae($.diskFreeBytes)," on ",$.dockerRoot]}),c.jsx("dt",{children:"existing"}),c.jsxs("dd",{children:[(g?.containers??[]).length," containers ·"," ",(g?.volumes??[]).length," volumes"]}),c.jsx("dt",{children:"gzip"}),c.jsx("dd",{children:$.hasGzip?"yes":"missing"})]})]})]})]}),c.jsxs("div",{className:"section",children:[c.jsx("h3",{children:"options"}),c.jsxs("div",{className:"stack",children:[c.jsx(Fl,{label:"if the name already exists on the target",children:c.jsxs("select",{value:s.conflict,onChange:M=>_(V=>({...V,conflict:M.target.value})),children:[c.jsx("option",{value:"fail",children:"stop with an error"}),c.jsx("option",{value:"skip",children:"skip that container"}),c.jsx("option",{value:"rename",children:"create it under a new name"}),c.jsx("option",{value:"replace",children:"remove the target's container first"})]})}),s.conflict==="rename"&&c.jsx(Fl,{label:"suffix",children:c.jsx("input",{type:"text",value:s.renameSuffix??"",onChange:M=>_(V=>({...V,renameSuffix:M.target.value}))})}),s.conflict==="replace"&&c.jsx(Dt,{kind:"warn",children:"Existing containers and volumes with the same name are deleted on the target before the copy."}),c.jsx(st,{checked:s.compress,onChange:M=>_(V=>({...V,compress:M})),label:"compress transfers (gzip)"}),c.jsx(st,{checked:s.verifyAfter,onChange:M=>_(V=>({...V,verifyAfter:M})),label:"verify each container after migrating"}),c.jsx(st,{checked:s.dryRun,onChange:M=>_(V=>({...V,dryRun:M})),label:"dry run — show every command, change nothing"}),c.jsx(Fl,{label:`containers at a time: ${s.parallelism}`,children:c.jsx("input",{type:"range",min:1,max:6,value:s.parallelism,onChange:M=>_(V=>({...V,parallelism:Number(M.target.value)})),style:{width:"100%"}})})]})]}),c.jsxs("div",{className:"section",children:[c.jsx("h3",{children:"migrate over ssh"}),c.jsxs("div",{className:"stack",children:[c.jsx("button",{className:"btn primary",disabled:!p,onClick:()=>hl("ssh",async()=>{const M=await Gl.migrateSSH(E,N);A(M)}),children:rl==="ssh"?"starting…":`migrate ${O} container${O===1?"":"s"} to target`}),c.jsx("button",{className:"btn",disabled:O===0||!!rl,onClick:()=>hl("preview",async()=>{Al(await Gl.preview(N))}),children:"preview the commands"}),O===0&&c.jsx("div",{className:"small faint",children:"select at least one container"}),O>0&&!cl&&c.jsx("div",{className:"small faint",children:"connect to a target first"})]})]}),c.jsxs("div",{className:"section",children:[c.jsx("h3",{children:"migration package"}),c.jsxs("div",{className:"stack",children:[c.jsxs("div",{className:"small muted",children:["Builds a self-contained folder with the data, the images and an ",c.jsx("span",{className:"mono",children:"install.sh"})," to run on the target. No network between the hosts required."]}),c.jsx(Fl,{label:"package name",children:c.jsx("input",{type:"text",placeholder:"auto (timestamped)",value:Dl,onChange:M=>_l(M.target.value)})}),c.jsx(Fl,{label:"format",children:c.jsxs("select",{value:U,onChange:M=>Q(M.target.value),children:[c.jsx("option",{value:"tar",children:"single .tar file (downloadable)"}),c.jsx("option",{value:"dir",children:"directory on this host"})]})}),c.jsx("button",{className:"btn",disabled:!C,onClick:()=>hl("pkg",async()=>{const M=await Gl.buildPackage({...N,packageName:Dl},U);A(M)}),children:rl==="pkg"?"starting…":"build package"})]})]}),h?.inventory.warnings?.length?c.jsxs("div",{className:"section",children:[c.jsx("h3",{children:"source warnings"}),c.jsx("div",{className:"stack",children:h.inventory.warnings.map((M,V)=>c.jsx(Dt,{kind:"warn",children:M},V))})]}):null,dl&&c.jsx(xy,{initial:dl,onClose:()=>yl(null),onSaved:M=>{yl(null),D(),k(M.id)},onError:B}),il&&c.jsx(zf,{title:"SSH host key",onClose:()=>Xl(null),footer:c.jsxs(c.Fragment,{children:[c.jsx("button",{className:"btn",onClick:()=>Xl(null),children:"cancel"}),c.jsx("button",{className:"btn primary",onClick:Ll,children:il.changed?"replace the stored key and trust":"trust this host"})]}),children:c.jsxs("div",{className:"stack",children:[il.changed&&c.jsxs(Dt,{kind:"err",children:["The key presented by this host is ",c.jsx("b",{children:"different"})," from the one recorded earlier. This happens after a reinstall — but it is also what a machine-in-the-middle looks like. Only continue if you know why it changed."]}),il.trusted&&!il.changed&&c.jsx(Dt,{kind:"ok",children:"This host key is already trusted."}),c.jsxs("div",{className:"small muted",children:["Compare this with the output of ",c.jsxs("span",{className:"mono",children:["ssh-keyscan -t ",il.keyType," ",il.host]})," ","run on the target itself, or with ",c.jsx("span",{className:"mono",children:"ssh-keygen -lf /etc/ssh/ssh_host_*_key.pub"}),"."]}),c.jsxs("div",{className:"fingerprint",children:[il.keyType,c.jsx("br",{}),il.fingerprint]})]})}),El&&c.jsx(zy,{data:El,onClose:()=>Al(null)})]})}function xy({initial:h,onClose:N,onSaved:O,onError:s}){const[_,H]=K.useState(h),[E,k]=K.useState(!1);function D(g,J){H(A=>({...A,[g]:J}))}return c.jsx(zf,{title:h.id?`Edit ${h.name}`:"New target host",onClose:N,footer:c.jsxs(c.Fragment,{children:[c.jsx("button",{className:"btn",onClick:N,children:"cancel"}),c.jsx("button",{className:"btn primary",disabled:E||!_.host||!_.user,onClick:async()=>{k(!0);try{O(await Gl.saveConnection(_))}catch(g){s(g instanceof Error?g.message:String(g))}finally{k(!1)}},children:E?"saving…":"save"})]}),children:c.jsxs("div",{className:"stack",style:{gap:12},children:[c.jsxs("div",{className:"row",style:{gap:12},children:[c.jsx(Fl,{label:"label",children:c.jsx("input",{type:"text",value:_.name??"",onChange:g=>D("name",g.target.value)})}),c.jsx(Fl,{label:"host",children:c.jsx("input",{type:"text",value:_.host??"",onChange:g=>D("host",g.target.value)})}),c.jsx("div",{style:{width:90},children:c.jsx(Fl,{label:"port",children:c.jsx("input",{type:"number",value:_.port??22,onChange:g=>D("port",Number(g.target.value))})})})]}),c.jsxs("div",{className:"row",style:{gap:12},children:[c.jsx(Fl,{label:"user",children:c.jsx("input",{type:"text",value:_.user??"",onChange:g=>D("user",g.target.value)})}),c.jsx(Fl,{label:"authentication",children:c.jsxs("select",{value:_.auth??"password",onChange:g=>D("auth",g.target.value),children:[c.jsx("option",{value:"password",children:"password"}),c.jsx("option",{value:"key",children:"private key"}),c.jsx("option",{value:"agent",children:"ssh agent"})]})})]}),_.auth==="password"&&c.jsx(Fl,{label:"password",children:c.jsx("input",{type:"password",value:_.password??"",onChange:g=>D("password",g.target.value)})}),_.auth==="key"&&c.jsxs(c.Fragment,{children:[c.jsx(Fl,{label:"private key path on this machine (leave empty to paste the key below)",children:c.jsx("input",{type:"text",placeholder:"/root/.ssh/id_ed25519",value:_.privateKeyPath??"",onChange:g=>D("privateKeyPath",g.target.value)})}),c.jsx(Fl,{label:"or paste the private key",children:c.jsx("textarea",{rows:5,value:_.privateKey??"",onChange:g=>D("privateKey",g.target.value),placeholder:"-----BEGIN OPENSSH PRIVATE KEY-----"})}),c.jsx(Fl,{label:"passphrase (if the key is encrypted)",children:c.jsx("input",{type:"password",value:_.passphrase??"",onChange:g=>D("passphrase",g.target.value)})})]}),_.auth==="agent"&&c.jsxs("div",{className:"small muted",children:["Uses the agent at ",c.jsx("span",{className:"mono",children:"$SSH_AUTH_SOCK"})," of the process running dockmv."]}),c.jsx(st,{checked:_.sudo??!1,onChange:g=>D("sudo",g),label:"run docker through sudo -n on the target",title:"Needed when the login user is not in the docker group. sudo must not ask for a password."}),c.jsx(Fl,{label:"docker command on the target (optional)",children:c.jsx("input",{type:"text",placeholder:"docker",value:_.dockerCmd??"",onChange:g=>D("dockerCmd",g.target.value)})}),c.jsx(st,{checked:_.saveSecrets??!1,onChange:g=>D("saveSecrets",g),label:"remember the password / key on disk"}),_.saveSecrets?c.jsx(Dt,{kind:"warn",children:"Credentials are stored in plain text in the connections file, readable only by this user. Leave this off to keep them in memory for this session only."}):c.jsx("div",{className:"small faint",children:"Credentials stay in memory and are lost when dockmv restarts."})]})})}function zy({data:h,onClose:N}){const O=h.items.reduce((s,_)=>s+_.totalBytes,0);return c.jsx(zf,{title:"What this migration will run",wide:!0,onClose:N,footer:c.jsx("button",{className:"btn",onClick:N,children:"close"}),children:c.jsxs("div",{className:"stack",style:{gap:16},children:[c.jsxs("div",{className:"small muted",children:[h.items.length," container(s)",O>0&&c.jsxs(c.Fragment,{children:[" · about ",ae(O)," of known volume data"]}),". These are the commands that run on the target; data is streamed into ",c.jsx("span",{className:"mono",children:"docker cp"})," rather than written to a file."]}),(h.networkCommands??[]).length>0&&c.jsxs("div",{children:[c.jsx("h3",{style:{margin:"0 0 6px",fontSize:12},children:"shared networks"}),c.jsx("pre",{className:"cmdblock",children:(h.networkCommands??[]).join(` -`)})]}),h.items.map(s=>c.jsxs("div",{children:[c.jsxs("h3",{style:{margin:"0 0 6px",fontSize:12},children:[s.name,s.targetName!==s.name&&c.jsxs("span",{className:"faint",children:[" → ",s.targetName]})]}),(s.warnings??[]).map((_,H)=>c.jsx(Dt,{kind:"warn",children:_},`w${H}`)),(s.notes??[]).map((_,H)=>c.jsxs("div",{className:"small faint",children:["· ",_]},`n${H}`)),c.jsx("pre",{className:"cmdblock",children:(s.commands??[]).join(` -`)})]},s.containerId))]})})}function Ty({jobs:h,activeJob:N,setActiveJob:O,reload:s,reloadPackages:_}){const H=N||h[0]?.id||"";return h.length===0?c.jsx("div",{className:"empty",children:"no migrations yet — select containers and start one"}):c.jsxs("div",{style:{display:"flex",minHeight:0,height:"100%"},children:[c.jsx("div",{className:"joblist",style:{width:320,flex:"0 0 320px",overflow:"auto"},children:h.map(E=>c.jsxs("div",{className:`jobcard${E.id===H?" active":""}`,onClick:()=>O(E.id),children:[c.jsxs("div",{className:"row",children:[c.jsx(Mn,{state:E.state}),c.jsx("span",{className:"spacer"}),c.jsx("span",{className:"small faint",children:E.kind==="ssh"?"ssh":"package"})]}),c.jsx("div",{className:"truncate",style:{marginTop:2},children:E.title}),c.jsxs("div",{className:"small faint",children:[new Date(E.createdAt).toLocaleTimeString()," · ",qr(E.startedAt,E.endedAt),E.dryRun&&" · dry run"]}),c.jsx("div",{style:{marginTop:6},children:c.jsx(jf,{done:E.bytesDone,total:E.bytesTotal,state:E.state})})]},E.id))}),c.jsx("div",{style:{flex:1,minWidth:0,overflow:"auto",borderLeft:"1px solid var(--border)"},children:H&&c.jsx(Ey,{id:H,reload:s,reloadPackages:_})})]})}function Ey({id:h,reload:N,reloadPackages:O}){const[s,_]=K.useState(null),[H,E]=K.useState(!0),k=K.useRef(null),D=K.useRef(!0);K.useEffect(()=>{_(null);let A=!1;Gl.job(h).then(dl=>!A&&_(dl)).catch(()=>{});const B=Gl.jobEvents(h);return B.onmessage=dl=>{try{_(JSON.parse(dl.data))}catch{}},B.addEventListener("done",()=>{B.close(),N(),O()}),B.onerror=()=>B.close(),()=>{A=!0,B.close()}},[h,N,O]);const g=K.useMemo(()=>(s?.log??[]).filter(A=>H||A.level!=="cmd"),[s,H]);if(K.useEffect(()=>{const A=k.current;A&&D.current&&(A.scrollTop=A.scrollHeight)},[g]),!s)return c.jsx("div",{className:"empty",children:"loading…"});const J=s.state==="running"||s.state==="pending";return c.jsxs("div",{style:{padding:16,display:"flex",flexDirection:"column",gap:14},children:[c.jsxs("div",{className:"row",children:[c.jsx(Mn,{state:s.state}),c.jsx("b",{children:s.title}),s.dryRun&&c.jsx("span",{className:"badge",children:"dry run"}),c.jsx("span",{className:"spacer"}),c.jsxs("span",{className:"small faint",children:[ae(s.bytesDone),s.bytesTotal>0&&c.jsxs(c.Fragment,{children:[" of ",ae(s.bytesTotal)]})," · ",qr(s.startedAt,s.endedAt)]}),J?c.jsx("button",{className:"btn tiny danger",onClick:()=>Gl.cancelJob(s.id).then(N),children:"cancel"}):c.jsx("button",{className:"btn tiny ghost",onClick:()=>Gl.deleteJob(s.id).then(N),children:"remove"})]}),c.jsx(jf,{done:s.bytesDone,total:s.bytesTotal,state:s.state}),s.error&&c.jsx(Dt,{kind:"err",children:s.error}),s.state==="succeeded"&&s.artifact&&c.jsxs(Dt,{kind:"ok",children:["Package ready at ",c.jsx("span",{className:"mono",children:s.artifact})," (",ae(s.artifactBytes??0),")."," ","Open the Packages tab to download it."]}),s.items.map(A=>c.jsxs("div",{style:{border:"1px solid var(--border)",borderRadius:6,padding:"8px 10px"},children:[c.jsxs("div",{className:"row",children:[c.jsx(Mn,{state:A.state}),c.jsx("b",{children:A.name}),c.jsx("span",{className:"spacer"}),c.jsxs("span",{className:"small faint",children:[A.steps.filter(B=>B.state==="succeeded").length,"/",A.steps.length," steps"]})]}),A.error&&c.jsx("div",{className:"small",style:{color:"var(--err)"},children:A.error}),(A.warnings??[]).map((B,dl)=>c.jsxs("div",{className:"small",style:{color:"var(--warn)"},children:["! ",B]},dl)),c.jsx("div",{className:"steps",children:A.steps.map(B=>c.jsxs("div",{className:`step ${B.state}`,children:[c.jsx(Mn,{state:B.state,label:""}),c.jsx("span",{className:"label truncate",title:B.error||B.label,children:B.label}),c.jsx("span",{children:B.bytesTotal>0||B.bytesDone>0?c.jsx(jf,{done:B.bytesDone,total:B.bytesTotal,state:B.state}):null}),c.jsx("span",{className:"faint nowrap",style:{textAlign:"right"},children:B.bytesDone>0?ae(B.bytesDone):B.state==="skipped"?"skipped":""})]},B.id))})]},A.id)),c.jsxs("div",{className:"row",children:[c.jsx("h3",{style:{margin:0,fontSize:12},children:"log"}),c.jsx("span",{className:"spacer"}),c.jsxs("label",{className:"check small",children:[c.jsx("input",{type:"checkbox",checked:H,onChange:A=>E(A.target.checked)}),c.jsx("span",{children:"show commands"})]})]}),c.jsxs("div",{className:"log",ref:k,onScroll:A=>{const B=A.currentTarget;D.current=B.scrollHeight-B.scrollTop-B.clientHeight<24},children:[g.map(A=>c.jsxs("div",{className:`l-${A.level}`,children:[c.jsxs("span",{className:"ts",children:[new Date(A.at).toLocaleTimeString()," "]}),A.message]},A.seq)),g.length===0&&c.jsx("span",{className:"faint",children:"nothing logged yet"})]})]})}function Ay({packages:h,reload:N}){return h.length===0?c.jsx("div",{className:"empty",children:"no packages built yet"}):c.jsxs("div",{style:{padding:16,display:"flex",flexDirection:"column",gap:12},children:[c.jsxs(Dt,{kind:"info",children:["Copy a package to the target host, then run ",c.jsx("span",{className:"mono",children:"./install.sh --dry-run"})," to review it and"," ",c.jsx("span",{className:"mono",children:"./install.sh"})," to restore. The target needs only bash, gzip and docker."]}),c.jsxs("table",{className:"mount-table",children:[c.jsx("thead",{children:c.jsxs("tr",{children:[c.jsx("th",{children:"name"}),c.jsx("th",{style:{width:110},children:"kind"}),c.jsx("th",{style:{width:110,textAlign:"right"},children:"size"}),c.jsx("th",{style:{width:170},children:"built"}),c.jsx("th",{style:{width:190}})]})}),c.jsx("tbody",{children:h.map(O=>c.jsxs("tr",{children:[c.jsx("td",{className:"mono truncate",title:O.path,children:O.name}),c.jsx("td",{children:c.jsx("span",{className:"badge",children:O.isDir?"directory":"tar"})}),c.jsx("td",{className:"nowrap",style:{textAlign:"right"},children:ae(O.bytes)}),c.jsx("td",{className:"small faint",children:new Date(O.createdAt).toLocaleString()}),c.jsx("td",{children:c.jsxs("div",{className:"row",style:{justifyContent:"flex-end",gap:6},children:[O.isDir?c.jsx("span",{className:"small faint",title:O.path,children:"copy it from disk"}):c.jsx("a",{className:"btn tiny",href:Gl.downloadUrl(O.name),download:!0,children:"download"}),c.jsx("button",{className:"btn tiny danger",onClick:()=>{confirm(`Delete package "${O.name}"? This cannot be undone.`)&&Gl.deletePackage(O.name).then(N)},children:"delete"})]})})]},O.name))})]})]})}function Ny(){const[h,N]=K.useState(null),[O,s]=K.useState(null),[_,H]=K.useState({}),[E,k]=K.useState({conflict:"fail",renameSuffix:"-migrated",compress:!0,compressLevel:1,dryRun:!1,parallelism:1,verifyAfter:!0}),[D,g]=K.useState({}),[J,A]=K.useState([]),[B,dl]=K.useState(""),[yl,il]=K.useState(null),[Xl,El]=K.useState([]),[Al,rl]=K.useState([]),[xl,Dl]=K.useState("containers"),[_l,U]=K.useState(""),[Q,q]=K.useState(""),[$,cl]=K.useState(!0),hl=K.useCallback(async()=>{cl(!0);try{const d=await Gl.source();s(d),H(x=>{const R={};for(const Y of d.inventory.containers)R[Y.id]=x[Y.id]??d.defaults[Y.id];return R}),q(""),Gl.volumeSizes().then(x=>g(x.volumes??{})).catch(()=>{})}catch(d){q(d instanceof Error?d.message:String(d))}finally{cl(!1)}},[]),Pl=K.useCallback(async()=>{try{const d=await Gl.connections();A(d),dl(x=>x&&d.some(R=>R.id===x)?x:d[0]?.id??"")}catch(d){q(d instanceof Error?d.message:String(d))}},[]),Zl=K.useCallback(async()=>{try{El(await Gl.jobs())}catch{}},[]),Ll=K.useCallback(async()=>{try{rl(await Gl.packages())}catch{}},[]);K.useEffect(()=>{Gl.health().then(N).catch(()=>{}),hl(),Pl(),Zl(),Ll()},[hl,Pl,Zl,Ll]),K.useEffect(()=>{const d=setInterval(Zl,4e3);return()=>clearInterval(d)},[Zl]);const p=K.useCallback(async d=>{if(il(null),!d)return;const x=await Gl.targetInventory(d);il(x),q("")},[]),C=K.useMemo(()=>Object.values(_).filter(d=>d.include),[_]),M=K.useMemo(()=>({items:Object.values(_),options:E}),[_,E]),V=Xl.filter(d=>d.state==="running"||d.state==="pending").length,nl=K.useCallback(d=>{El(x=>[d,...x]),U(d.id),Dl("jobs")},[]);return c.jsxs("div",{className:"app",children:[c.jsxs("header",{className:"topbar",children:[c.jsxs("div",{className:"brand",children:[c.jsx("img",{src:"/logo-icon.png",alt:"",className:"brand-logo"}),"DockMV"]}),c.jsxs("nav",{className:"tabs",children:[c.jsxs("button",{className:`tab${xl==="containers"?" active":""}`,onClick:()=>Dl("containers"),children:["Containers",c.jsxs("span",{className:"count",children:[C.length,"/",O?.inventory.containers.length??0]})]}),c.jsxs("button",{className:`tab${xl==="jobs"?" active":""}`,onClick:()=>Dl("jobs"),children:["Jobs",V>0&&c.jsxs("span",{className:"count",children:[V," running"]})]}),c.jsxs("button",{className:`tab${xl==="packages"?" active":""}`,onClick:()=>Dl("packages"),children:["Packages",Al.length>0&&c.jsx("span",{className:"count",children:Al.length})]})]}),c.jsxs("div",{className:"topbar-right",children:[h&&c.jsxs("span",{className:"hostinfo",children:["source ",c.jsx("b",{children:O?.inventory.host||h.dockerHost}),h.dockerVersion&&c.jsxs(c.Fragment,{children:[" · docker ",h.dockerVersion]})]}),c.jsx("button",{className:"btn tiny",onClick:hl,disabled:$,children:$?"loading…":"refresh"})]})]}),Q&&c.jsx("div",{style:{padding:"10px 16px"},children:c.jsxs(Dt,{kind:"err",children:[Q,c.jsx("button",{className:"btn tiny ghost",style:{marginLeft:8},onClick:()=>q(""),children:"dismiss"})]})}),h&&!h.ok&&c.jsx("div",{style:{padding:"10px 16px"},children:c.jsxs(Dt,{kind:"err",children:["Cannot reach the source Docker daemon at ",c.jsx("span",{className:"mono",children:h.dockerHost}),h.dockerError&&c.jsxs(c.Fragment,{children:[" — ",h.dockerError]})]})}),c.jsxs("div",{className:"body",children:[c.jsxs("main",{className:"main",children:[xl==="containers"&&c.jsx(by,{source:O,sel:_,setSel:H,targetInv:yl,loading:$,sizes:D}),xl==="jobs"&&c.jsx(Ty,{jobs:Xl,activeJob:_l,setActiveJob:U,reload:Zl,reloadPackages:Ll}),xl==="packages"&&c.jsx(Ay,{packages:Al,reload:Ll})]}),xl==="containers"&&c.jsx("aside",{className:"sidebar",children:c.jsx(jy,{source:O,plan:M,includedCount:C.length,options:E,setOptions:k,connections:J,activeConn:B,setActiveConn:dl,reloadConnections:Pl,targetInv:yl,connectTarget:p,onJobStarted:nl,onError:q})})]})]})}vy.createRoot(document.getElementById("root")).render(c.jsx(K.StrictMode,{children:c.jsx(Ny,{})})); diff --git a/internal/webui/dist/assets/index-qcSVszEj.js b/internal/webui/dist/assets/index-qcSVszEj.js new file mode 100644 index 0000000..13dfbc0 --- /dev/null +++ b/internal/webui/dist/assets/index-qcSVszEj.js @@ -0,0 +1,11 @@ +(function(){const S=document.createElement("link").relList;if(S&&S.supports&&S.supports("modulepreload"))return;for(const O of document.querySelectorAll('link[rel="modulepreload"]'))s(O);new MutationObserver(O=>{for(const D of O)if(D.type==="childList")for(const T of D.addedNodes)T.tagName==="LINK"&&T.rel==="modulepreload"&&s(T)}).observe(document,{childList:!0,subtree:!0});function z(O){const D={};return O.integrity&&(D.integrity=O.integrity),O.referrerPolicy&&(D.referrerPolicy=O.referrerPolicy),O.crossOrigin==="use-credentials"?D.credentials="include":O.crossOrigin==="anonymous"?D.credentials="omit":D.credentials="same-origin",D}function s(O){if(O.ep)return;O.ep=!0;const D=z(O);fetch(O.href,D)}})();var gf={exports:{}},Dn={};var Nr;function sy(){if(Nr)return Dn;Nr=1;var o=Symbol.for("react.transitional.element"),S=Symbol.for("react.fragment");function z(s,O,D){var T=null;if(D!==void 0&&(T=""+D),O.key!==void 0&&(T=""+O.key),"key"in O){D={};for(var w in O)w!=="key"&&(D[w]=O[w])}else D=O;return O=D.ref,{$$typeof:o,type:s,key:T,ref:O!==void 0?O:null,props:D}}return Dn.Fragment=S,Dn.jsx=z,Dn.jsxs=z,Dn}var Ar;function oy(){return Ar||(Ar=1,gf.exports=sy()),gf.exports}var c=oy(),Sf={exports:{}},I={};var _r;function dy(){if(_r)return I;_r=1;var o=Symbol.for("react.transitional.element"),S=Symbol.for("react.portal"),z=Symbol.for("react.fragment"),s=Symbol.for("react.strict_mode"),O=Symbol.for("react.profiler"),D=Symbol.for("react.consumer"),T=Symbol.for("react.context"),w=Symbol.for("react.forward_ref"),H=Symbol.for("react.suspense"),b=Symbol.for("react.memo"),V=Symbol.for("react.lazy"),E=Symbol.for("react.activity"),U=Symbol.iterator;function ll(h){return h===null||typeof h!="object"?null:(h=U&&h[U]||h["@@iterator"],typeof h=="function"?h:null)}var W={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},vl=Object.assign,Yl={};function gl(h,A,q){this.props=h,this.context=A,this.refs=Yl,this.updater=q||W}gl.prototype.isReactComponent={},gl.prototype.setState=function(h,A){if(typeof h!="object"&&typeof h!="function"&&h!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,h,A,"setState")},gl.prototype.forceUpdate=function(h){this.updater.enqueueForceUpdate(this,h,"forceUpdate")};function _l(){}_l.prototype=gl.prototype;function dl(h,A,q){this.props=h,this.context=A,this.refs=Yl,this.updater=q||W}var el=dl.prototype=new _l;el.constructor=dl,vl(el,gl.prototype),el.isPureReactComponent=!0;var Sl=Array.isArray;function Nl(){}var C={H:null,A:null,T:null,S:null},Z=Object.prototype.hasOwnProperty;function B(h,A,q){var Y=q.ref;return{$$typeof:o,type:h,key:A,ref:Y!==void 0?Y:null,props:q}}function $(h,A){return B(h.type,A,h.props)}function sl(h){return typeof h=="object"&&h!==null&&h.$$typeof===o}function jl(h){var A={"=":"=0",":":"=2"};return"$"+h.replace(/[=:]/g,function(q){return A[q]})}var at=/\/+/g;function tt(h,A){return typeof h=="object"&&h!==null&&h.key!=null?jl(""+h.key):A.toString(36)}function Cl(h){switch(h.status){case"fulfilled":return h.value;case"rejected":throw h.reason;default:switch(typeof h.status=="string"?h.then(Nl,Nl):(h.status="pending",h.then(function(A){h.status==="pending"&&(h.status="fulfilled",h.value=A)},function(A){h.status==="pending"&&(h.status="rejected",h.reason=A)})),h.status){case"fulfilled":return h.value;case"rejected":throw h.reason}}throw h}function j(h,A,q,Y,F){var al=typeof h;(al==="undefined"||al==="boolean")&&(h=null);var rl=!1;if(h===null)rl=!0;else switch(al){case"bigint":case"string":case"number":rl=!0;break;case"object":switch(h.$$typeof){case o:case S:rl=!0;break;case V:return rl=h._init,j(rl(h._payload),A,q,Y,F)}}if(rl)return F=F(h),rl=Y===""?"."+tt(h,0):Y,Sl(F)?(q="",rl!=null&&(q=rl.replace(at,"$&/")+"/"),j(F,A,q,"",function(k){return k})):F!=null&&(sl(F)&&(F=$(F,q+(F.key==null||h&&h.key===F.key?"":(""+F.key).replace(at,"$&/")+"/")+rl)),A.push(F)),1;rl=0;var Kl=Y===""?".":Y+":";if(Sl(h))for(var Ul=0;Ul>>1,nl=j[K];if(0>>1;KO(q,M))YO(F,q)?(j[K]=F,j[Y]=M,K=Y):(j[K]=q,j[A]=M,K=A);else if(YO(F,M))j[K]=F,j[Y]=M,K=Y;else break l}}return R}function O(j,R){var M=j.sortIndex-R.sortIndex;return M!==0?M:j.id-R.id}if(o.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var D=performance;o.unstable_now=function(){return D.now()}}else{var T=Date,w=T.now();o.unstable_now=function(){return T.now()-w}}var H=[],b=[],V=1,E=null,U=3,ll=!1,W=!1,vl=!1,Yl=!1,gl=typeof setTimeout=="function"?setTimeout:null,_l=typeof clearTimeout=="function"?clearTimeout:null,dl=typeof setImmediate<"u"?setImmediate:null;function el(j){for(var R=z(b);R!==null;){if(R.callback===null)s(b);else if(R.startTime<=j)s(b),R.sortIndex=R.expirationTime,S(H,R);else break;R=z(b)}}function Sl(j){if(vl=!1,el(j),!W)if(z(H)!==null)W=!0,Nl||(Nl=!0,jl());else{var R=z(b);R!==null&&Cl(Sl,R.startTime-j)}}var Nl=!1,C=-1,Z=5,B=-1;function $(){return Yl?!0:!(o.unstable_now()-Bj&&$());){var K=E.callback;if(typeof K=="function"){E.callback=null,U=E.priorityLevel;var nl=K(E.expirationTime<=j);if(j=o.unstable_now(),typeof nl=="function"){E.callback=nl,el(j),R=!0;break t}E===z(H)&&s(H),el(j)}else s(H);E=z(H)}if(E!==null)R=!0;else{var h=z(b);h!==null&&Cl(Sl,h.startTime-j),R=!1}}break l}finally{E=null,U=M,ll=!1}R=void 0}}finally{R?jl():Nl=!1}}}var jl;if(typeof dl=="function")jl=function(){dl(sl)};else if(typeof MessageChannel<"u"){var at=new MessageChannel,tt=at.port2;at.port1.onmessage=sl,jl=function(){tt.postMessage(null)}}else jl=function(){gl(sl,0)};function Cl(j,R){C=gl(function(){j(o.unstable_now())},R)}o.unstable_IdlePriority=5,o.unstable_ImmediatePriority=1,o.unstable_LowPriority=4,o.unstable_NormalPriority=3,o.unstable_Profiling=null,o.unstable_UserBlockingPriority=2,o.unstable_cancelCallback=function(j){j.callback=null},o.unstable_forceFrameRate=function(j){0>j||125K?(j.sortIndex=M,S(b,j),z(H)===null&&j===z(b)&&(vl?(_l(C),C=-1):vl=!0,Cl(Sl,M-K))):(j.sortIndex=nl,S(H,j),W||ll||(W=!0,Nl||(Nl=!0,jl()))),j},o.unstable_shouldYield=$,o.unstable_wrapCallback=function(j){var R=U;return function(){var M=U;U=R;try{return j.apply(this,arguments)}finally{U=M}}}})(jf)),jf}var Dr;function hy(){return Dr||(Dr=1,pf.exports=ry()),pf.exports}var xf={exports:{}},lt={};var Cr;function my(){if(Cr)return lt;Cr=1;var o=Tf();function S(H){var b="https://react.dev/errors/"+H;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(o)}catch(S){console.error(S)}}return o(),xf.exports=my(),xf.exports}var Hr;function vy(){if(Hr)return Cn;Hr=1;var o=hy(),S=Tf(),z=yy();function s(l){var t="https://react.dev/errors/"+l;if(1nl||(l.current=K[nl],K[nl]=null,nl--)}function q(l,t){nl++,K[nl]=l.current,l.current=t}var Y=h(null),F=h(null),al=h(null),rl=h(null);function Kl(l,t){switch(q(al,t),q(F,l),q(Y,null),t.nodeType){case 9:case 11:l=(l=t.documentElement)&&(l=l.namespaceURI)?$d(l):0;break;default:if(l=t.tagName,t=t.namespaceURI)t=$d(t),l=Wd(t,l);else switch(l){case"svg":l=1;break;case"math":l=2;break;default:l=0}}A(Y),q(Y,l)}function Ul(){A(Y),A(F),A(al)}function k(l){l.memoizedState!==null&&q(rl,l);var t=Y.current,e=Wd(t,l.type);t!==e&&(q(F,l),q(Y,e))}function Jl(l){F.current===l&&(A(Y),A(F)),rl.current===l&&(A(rl),An._currentValue=M)}var ce,Ie;function Ue(l){if(ce===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);ce=t&&t[1]||"",Ie=-1)":-1n||d[a]!==v[n]){var x=` +`+d[a].replace(" at new "," at ");return l.displayName&&x.includes("")&&(x=x.replace("",l.displayName)),x}while(1<=a&&0<=n);break}}}finally{Pu=!1,Error.prepareStackTrace=e}return(e=l?l.displayName||l.name:"")?Ue(e):""}function Xr(l,t){switch(l.tag){case 26:case 27:case 5:return Ue(l.type);case 16:return Ue("Lazy");case 13:return l.child!==t&&t!==null?Ue("Suspense Fallback"):Ue("Suspense");case 19:return Ue("SuspenseList");case 0:case 15:return li(l.type,!1);case 11:return li(l.type.render,!1);case 1:return li(l.type,!0);case 31:return Ue("Activity");default:return""}}function Ef(l){try{var t="",e=null;do t+=Xr(l,e),e=l,l=l.return;while(l);return t}catch(a){return` +Error generating stack: `+a.message+` +`+a.stack}}var ti=Object.prototype.hasOwnProperty,ei=o.unstable_scheduleCallback,ai=o.unstable_cancelCallback,Qr=o.unstable_shouldYield,Zr=o.unstable_requestPaint,mt=o.unstable_now,Lr=o.unstable_getCurrentPriorityLevel,Nf=o.unstable_ImmediatePriority,Af=o.unstable_UserBlockingPriority,Hn=o.unstable_NormalPriority,Vr=o.unstable_LowPriority,_f=o.unstable_IdlePriority,Kr=o.log,Jr=o.unstable_setDisableYieldValue,Ya=null,yt=null;function fe(l){if(typeof Kr=="function"&&Jr(l),yt&&typeof yt.setStrictMode=="function")try{yt.setStrictMode(Ya,l)}catch{}}var vt=Math.clz32?Math.clz32:$r,wr=Math.log,kr=Math.LN2;function $r(l){return l>>>=0,l===0?32:31-(wr(l)/kr|0)|0}var Rn=256,qn=262144,Bn=4194304;function He(l){var t=l&42;if(t!==0)return t;switch(l&-l){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return l&261888;case 262144:case 524288:case 1048576:case 2097152:return l&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return l&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return l}}function Yn(l,t,e){var a=l.pendingLanes;if(a===0)return 0;var n=0,u=l.suspendedLanes,i=l.pingedLanes;l=l.warmLanes;var f=a&134217727;return f!==0?(a=f&~u,a!==0?n=He(a):(i&=f,i!==0?n=He(i):e||(e=f&~l,e!==0&&(n=He(e))))):(f=a&~u,f!==0?n=He(f):i!==0?n=He(i):e||(e=a&~l,e!==0&&(n=He(e)))),n===0?0:t!==0&&t!==n&&(t&u)===0&&(u=n&-n,e=t&-t,u>=e||u===32&&(e&4194048)!==0)?t:n}function Ga(l,t){return(l.pendingLanes&~(l.suspendedLanes&~l.pingedLanes)&t)===0}function Wr(l,t){switch(l){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Of(){var l=Bn;return Bn<<=1,(Bn&62914560)===0&&(Bn=4194304),l}function ni(l){for(var t=[],e=0;31>e;e++)t.push(l);return t}function Xa(l,t){l.pendingLanes|=t,t!==268435456&&(l.suspendedLanes=0,l.pingedLanes=0,l.warmLanes=0)}function Fr(l,t,e,a,n,u){var i=l.pendingLanes;l.pendingLanes=e,l.suspendedLanes=0,l.pingedLanes=0,l.warmLanes=0,l.expiredLanes&=e,l.entangledLanes&=e,l.errorRecoveryDisabledLanes&=e,l.shellSuspendCounter=0;var f=l.entanglements,d=l.expirationTimes,v=l.hiddenUpdates;for(e=i&~e;0"u")return null;try{return l.activeElement||l.body}catch{return l.body}}var ah=/[\n"\\]/g;function Et(l){return l.replace(ah,function(t){return"\\"+t.charCodeAt(0).toString(16)+" "})}function oi(l,t,e,a,n,u,i,f){l.name="",i!=null&&typeof i!="function"&&typeof i!="symbol"&&typeof i!="boolean"?l.type=i:l.removeAttribute("type"),t!=null?i==="number"?(t===0&&l.value===""||l.value!=t)&&(l.value=""+Tt(t)):l.value!==""+Tt(t)&&(l.value=""+Tt(t)):i!=="submit"&&i!=="reset"||l.removeAttribute("value"),t!=null?di(l,i,Tt(t)):e!=null?di(l,i,Tt(e)):a!=null&&l.removeAttribute("value"),n==null&&u!=null&&(l.defaultChecked=!!u),n!=null&&(l.checked=n&&typeof n!="function"&&typeof n!="symbol"),f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"?l.name=""+Tt(f):l.removeAttribute("name")}function Zf(l,t,e,a,n,u,i,f){if(u!=null&&typeof u!="function"&&typeof u!="symbol"&&typeof u!="boolean"&&(l.type=u),t!=null||e!=null){if(!(u!=="submit"&&u!=="reset"||t!=null)){si(l);return}e=e!=null?""+Tt(e):"",t=t!=null?""+Tt(t):e,f||t===l.value||(l.value=t),l.defaultValue=t}a=a??n,a=typeof a!="function"&&typeof a!="symbol"&&!!a,l.checked=f?l.checked:!!a,l.defaultChecked=!!a,i!=null&&typeof i!="function"&&typeof i!="symbol"&&typeof i!="boolean"&&(l.name=i),si(l)}function di(l,t,e){t==="number"&&Qn(l.ownerDocument)===l||l.defaultValue===""+e||(l.defaultValue=""+e)}function na(l,t,e,a){if(l=l.options,t){t={};for(var n=0;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),vi=!1;if(Vt)try{var Va={};Object.defineProperty(Va,"passive",{get:function(){vi=!0}}),window.addEventListener("test",Va,Va),window.removeEventListener("test",Va,Va)}catch{vi=!1}var oe=null,gi=null,Ln=null;function $f(){if(Ln)return Ln;var l,t=gi,e=t.length,a,n="value"in oe?oe.value:oe.textContent,u=n.length;for(l=0;l=wa),ts=" ",es=!1;function as(l,t){switch(l){case"keyup":return Dh.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function ns(l){return l=l.detail,typeof l=="object"&&"data"in l?l.data:null}var fa=!1;function Uh(l,t){switch(l){case"compositionend":return ns(t);case"keypress":return t.which!==32?null:(es=!0,ts);case"textInput":return l=t.data,l===ts&&es?null:l;default:return null}}function Hh(l,t){if(fa)return l==="compositionend"||!xi&&as(l,t)?(l=$f(),Ln=gi=oe=null,fa=!1,l):null;switch(l){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:e,offset:t-l};l=a}l:{for(;e;){if(e.nextSibling){e=e.nextSibling;break l}e=e.parentNode}e=void 0}e=rs(e)}}function ms(l,t){return l&&t?l===t?!0:l&&l.nodeType===3?!1:t&&t.nodeType===3?ms(l,t.parentNode):"contains"in l?l.contains(t):l.compareDocumentPosition?!!(l.compareDocumentPosition(t)&16):!1:!1}function ys(l){l=l!=null&&l.ownerDocument!=null&&l.ownerDocument.defaultView!=null?l.ownerDocument.defaultView:window;for(var t=Qn(l.document);t instanceof l.HTMLIFrameElement;){try{var e=typeof t.contentWindow.location.href=="string"}catch{e=!1}if(e)l=t.contentWindow;else break;t=Qn(l.document)}return t}function Ei(l){var t=l&&l.nodeName&&l.nodeName.toLowerCase();return t&&(t==="input"&&(l.type==="text"||l.type==="search"||l.type==="tel"||l.type==="url"||l.type==="password")||t==="textarea"||l.contentEditable==="true")}var Zh=Vt&&"documentMode"in document&&11>=document.documentMode,sa=null,Ni=null,Fa=null,Ai=!1;function vs(l,t,e){var a=e.window===e?e.document:e.nodeType===9?e:e.ownerDocument;Ai||sa==null||sa!==Qn(a)||(a=sa,"selectionStart"in a&&Ei(a)?a={start:a.selectionStart,end:a.selectionEnd}:(a=(a.ownerDocument&&a.ownerDocument.defaultView||window).getSelection(),a={anchorNode:a.anchorNode,anchorOffset:a.anchorOffset,focusNode:a.focusNode,focusOffset:a.focusOffset}),Fa&&Wa(Fa,a)||(Fa=a,a=qu(Ni,"onSelect"),0>=i,n-=i,Yt=1<<32-vt(t)+n|e<tl?(fl=X,X=null):fl=X.sibling;var ml=g(m,X,y[tl],N);if(ml===null){X===null&&(X=fl);break}l&&X&&ml.alternate===null&&t(m,X),r=u(ml,r,tl),hl===null?L=ml:hl.sibling=ml,hl=ml,X=fl}if(tl===y.length)return e(m,X),ol&&Jt(m,tl),L;if(X===null){for(;tltl?(fl=X,X=null):fl=X.sibling;var Ce=g(m,X,ml.value,N);if(Ce===null){X===null&&(X=fl);break}l&&X&&Ce.alternate===null&&t(m,X),r=u(Ce,r,tl),hl===null?L=Ce:hl.sibling=Ce,hl=Ce,X=fl}if(ml.done)return e(m,X),ol&&Jt(m,tl),L;if(X===null){for(;!ml.done;tl++,ml=y.next())ml=_(m,ml.value,N),ml!==null&&(r=u(ml,r,tl),hl===null?L=ml:hl.sibling=ml,hl=ml);return ol&&Jt(m,tl),L}for(X=a(X);!ml.done;tl++,ml=y.next())ml=p(X,m,tl,ml.value,N),ml!==null&&(l&&ml.alternate!==null&&X.delete(ml.key===null?tl:ml.key),r=u(ml,r,tl),hl===null?L=ml:hl.sibling=ml,hl=ml);return l&&X.forEach(function(fy){return t(m,fy)}),ol&&Jt(m,tl),L}function Tl(m,r,y,N){if(typeof y=="object"&&y!==null&&y.type===vl&&y.key===null&&(y=y.props.children),typeof y=="object"&&y!==null){switch(y.$$typeof){case ll:l:{for(var L=y.key;r!==null;){if(r.key===L){if(L=y.type,L===vl){if(r.tag===7){e(m,r.sibling),N=n(r,y.props.children),N.return=m,m=N;break l}}else if(r.elementType===L||typeof L=="object"&&L!==null&&L.$$typeof===Z&&Ke(L)===r.type){e(m,r.sibling),N=n(r,y.props),an(N,y),N.return=m,m=N;break l}e(m,r);break}else t(m,r);r=r.sibling}y.type===vl?(N=Xe(y.props.children,m.mode,N,y.key),N.return=m,m=N):(N=Pn(y.type,y.key,y.props,null,m.mode,N),an(N,y),N.return=m,m=N)}return i(m);case W:l:{for(L=y.key;r!==null;){if(r.key===L)if(r.tag===4&&r.stateNode.containerInfo===y.containerInfo&&r.stateNode.implementation===y.implementation){e(m,r.sibling),N=n(r,y.children||[]),N.return=m,m=N;break l}else{e(m,r);break}else t(m,r);r=r.sibling}N=Hi(y,m.mode,N),N.return=m,m=N}return i(m);case Z:return y=Ke(y),Tl(m,r,y,N)}if(Cl(y))return G(m,r,y,N);if(jl(y)){if(L=jl(y),typeof L!="function")throw Error(s(150));return y=L.call(y),J(m,r,y,N)}if(typeof y.then=="function")return Tl(m,r,iu(y),N);if(y.$$typeof===dl)return Tl(m,r,eu(m,y),N);cu(m,y)}return typeof y=="string"&&y!==""||typeof y=="number"||typeof y=="bigint"?(y=""+y,r!==null&&r.tag===6?(e(m,r.sibling),N=n(r,y),N.return=m,m=N):(e(m,r),N=Ui(y,m.mode,N),N.return=m,m=N),i(m)):e(m,r)}return function(m,r,y,N){try{en=0;var L=Tl(m,r,y,N);return pa=null,L}catch(X){if(X===ba||X===nu)throw X;var hl=St(29,X,null,m.mode);return hl.lanes=N,hl.return=m,hl}}}var we=Gs(!0),Xs=Gs(!1),ye=!1;function Ji(l){l.updateQueue={baseState:l.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function wi(l,t){l=l.updateQueue,t.updateQueue===l&&(t.updateQueue={baseState:l.baseState,firstBaseUpdate:l.firstBaseUpdate,lastBaseUpdate:l.lastBaseUpdate,shared:l.shared,callbacks:null})}function ve(l){return{lane:l,tag:0,payload:null,callback:null,next:null}}function ge(l,t,e){var a=l.updateQueue;if(a===null)return null;if(a=a.shared,(yl&2)!==0){var n=a.pending;return n===null?t.next=t:(t.next=n.next,n.next=t),a.pending=t,t=In(l),zs(l,null,e),t}return Fn(l,a,t,e),In(l)}function nn(l,t,e){if(t=t.updateQueue,t!==null&&(t=t.shared,(e&4194048)!==0)){var a=t.lanes;a&=l.pendingLanes,e|=a,t.lanes=e,Df(l,e)}}function ki(l,t){var e=l.updateQueue,a=l.alternate;if(a!==null&&(a=a.updateQueue,e===a)){var n=null,u=null;if(e=e.firstBaseUpdate,e!==null){do{var i={lane:e.lane,tag:e.tag,payload:e.payload,callback:null,next:null};u===null?n=u=i:u=u.next=i,e=e.next}while(e!==null);u===null?n=u=t:u=u.next=t}else n=u=t;e={baseState:a.baseState,firstBaseUpdate:n,lastBaseUpdate:u,shared:a.shared,callbacks:a.callbacks},l.updateQueue=e;return}l=e.lastBaseUpdate,l===null?e.firstBaseUpdate=t:l.next=t,e.lastBaseUpdate=t}var $i=!1;function un(){if($i){var l=Sa;if(l!==null)throw l}}function cn(l,t,e,a){$i=!1;var n=l.updateQueue;ye=!1;var u=n.firstBaseUpdate,i=n.lastBaseUpdate,f=n.shared.pending;if(f!==null){n.shared.pending=null;var d=f,v=d.next;d.next=null,i===null?u=v:i.next=v,i=d;var x=l.alternate;x!==null&&(x=x.updateQueue,f=x.lastBaseUpdate,f!==i&&(f===null?x.firstBaseUpdate=v:f.next=v,x.lastBaseUpdate=d))}if(u!==null){var _=n.baseState;i=0,x=v=d=null,f=u;do{var g=f.lane&-536870913,p=g!==f.lane;if(p?(cl&g)===g:(a&g)===g){g!==0&&g===ga&&($i=!0),x!==null&&(x=x.next={lane:0,tag:f.tag,payload:f.payload,callback:null,next:null});l:{var G=l,J=f;g=t;var Tl=e;switch(J.tag){case 1:if(G=J.payload,typeof G=="function"){_=G.call(Tl,_,g);break l}_=G;break l;case 3:G.flags=G.flags&-65537|128;case 0:if(G=J.payload,g=typeof G=="function"?G.call(Tl,_,g):G,g==null)break l;_=E({},_,g);break l;case 2:ye=!0}}g=f.callback,g!==null&&(l.flags|=64,p&&(l.flags|=8192),p=n.callbacks,p===null?n.callbacks=[g]:p.push(g))}else p={lane:g,tag:f.tag,payload:f.payload,callback:f.callback,next:null},x===null?(v=x=p,d=_):x=x.next=p,i|=g;if(f=f.next,f===null){if(f=n.shared.pending,f===null)break;p=f,f=p.next,p.next=null,n.lastBaseUpdate=p,n.shared.pending=null}}while(!0);x===null&&(d=_),n.baseState=d,n.firstBaseUpdate=v,n.lastBaseUpdate=x,u===null&&(n.shared.lanes=0),xe|=i,l.lanes=i,l.memoizedState=_}}function Qs(l,t){if(typeof l!="function")throw Error(s(191,l));l.call(t)}function Zs(l,t){var e=l.callbacks;if(e!==null)for(l.callbacks=null,l=0;lu?u:8;var i=j.T,f={};j.T=f,mc(l,!1,t,e);try{var d=n(),v=j.S;if(v!==null&&v(f,d),d!==null&&typeof d=="object"&&typeof d.then=="function"){var x=Fh(d,a);on(l,t,x,zt(l))}else on(l,t,a,zt(l))}catch(_){on(l,t,{then:function(){},status:"rejected",reason:_},zt())}finally{R.p=u,i!==null&&f.types!==null&&(i.types=f.types),j.T=i}}function am(){}function rc(l,t,e,a){if(l.tag!==5)throw Error(s(476));var n=jo(l).queue;po(l,n,t,M,e===null?am:function(){return xo(l),e(a)})}function jo(l){var t=l.memoizedState;if(t!==null)return t;t={memoizedState:M,baseState:M,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Wt,lastRenderedState:M},next:null};var e={};return t.next={memoizedState:e,baseState:e,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Wt,lastRenderedState:e},next:null},l.memoizedState=t,l=l.alternate,l!==null&&(l.memoizedState=t),t}function xo(l){var t=jo(l);t.next===null&&(t=l.alternate.memoizedState),on(l,t.next.queue,{},zt())}function hc(){return Fl(An)}function zo(){return Bl().memoizedState}function To(){return Bl().memoizedState}function nm(l){for(var t=l.return;t!==null;){switch(t.tag){case 24:case 3:var e=zt();l=ve(e);var a=ge(t,l,e);a!==null&&(ot(a,t,e),nn(a,t,e)),t={cache:Zi()},l.payload=t;return}t=t.return}}function um(l,t,e){var a=zt();e={lane:a,revertLane:0,gesture:null,action:e,hasEagerState:!1,eagerState:null,next:null},gu(l)?No(t,e):(e=Di(l,t,e,a),e!==null&&(ot(e,l,a),Ao(e,t,a)))}function Eo(l,t,e){var a=zt();on(l,t,e,a)}function on(l,t,e,a){var n={lane:a,revertLane:0,gesture:null,action:e,hasEagerState:!1,eagerState:null,next:null};if(gu(l))No(t,n);else{var u=l.alternate;if(l.lanes===0&&(u===null||u.lanes===0)&&(u=t.lastRenderedReducer,u!==null))try{var i=t.lastRenderedState,f=u(i,e);if(n.hasEagerState=!0,n.eagerState=f,gt(f,i))return Fn(l,t,n,0),El===null&&Wn(),!1}catch{}if(e=Di(l,t,n,a),e!==null)return ot(e,l,a),Ao(e,t,a),!0}return!1}function mc(l,t,e,a){if(a={lane:2,revertLane:Jc(),gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},gu(l)){if(t)throw Error(s(479))}else t=Di(l,e,a,2),t!==null&&ot(t,l,2)}function gu(l){var t=l.alternate;return l===P||t!==null&&t===P}function No(l,t){xa=ou=!0;var e=l.pending;e===null?t.next=t:(t.next=e.next,e.next=t),l.pending=t}function Ao(l,t,e){if((e&4194048)!==0){var a=t.lanes;a&=l.pendingLanes,e|=a,t.lanes=e,Df(l,e)}}var dn={readContext:Fl,use:hu,useCallback:Hl,useContext:Hl,useEffect:Hl,useImperativeHandle:Hl,useLayoutEffect:Hl,useInsertionEffect:Hl,useMemo:Hl,useReducer:Hl,useRef:Hl,useState:Hl,useDebugValue:Hl,useDeferredValue:Hl,useTransition:Hl,useSyncExternalStore:Hl,useId:Hl,useHostTransitionStatus:Hl,useFormState:Hl,useActionState:Hl,useOptimistic:Hl,useMemoCache:Hl,useCacheRefresh:Hl};dn.useEffectEvent=Hl;var _o={readContext:Fl,use:hu,useCallback:function(l,t){return et().memoizedState=[l,t===void 0?null:t],l},useContext:Fl,useEffect:oo,useImperativeHandle:function(l,t,e){e=e!=null?e.concat([l]):null,yu(4194308,4,yo.bind(null,t,l),e)},useLayoutEffect:function(l,t){return yu(4194308,4,l,t)},useInsertionEffect:function(l,t){yu(4,2,l,t)},useMemo:function(l,t){var e=et();t=t===void 0?null:t;var a=l();if(ke){fe(!0);try{l()}finally{fe(!1)}}return e.memoizedState=[a,t],a},useReducer:function(l,t,e){var a=et();if(e!==void 0){var n=e(t);if(ke){fe(!0);try{e(t)}finally{fe(!1)}}}else n=t;return a.memoizedState=a.baseState=n,l={pending:null,lanes:0,dispatch:null,lastRenderedReducer:l,lastRenderedState:n},a.queue=l,l=l.dispatch=um.bind(null,P,l),[a.memoizedState,l]},useRef:function(l){var t=et();return l={current:l},t.memoizedState=l},useState:function(l){l=cc(l);var t=l.queue,e=Eo.bind(null,P,t);return t.dispatch=e,[l.memoizedState,e]},useDebugValue:oc,useDeferredValue:function(l,t){var e=et();return dc(e,l,t)},useTransition:function(){var l=cc(!1);return l=po.bind(null,P,l.queue,!0,!1),et().memoizedState=l,[!1,l]},useSyncExternalStore:function(l,t,e){var a=P,n=et();if(ol){if(e===void 0)throw Error(s(407));e=e()}else{if(e=t(),El===null)throw Error(s(349));(cl&127)!==0||ks(a,t,e)}n.memoizedState=e;var u={value:e,getSnapshot:t};return n.queue=u,oo(Ws.bind(null,a,u,l),[l]),a.flags|=2048,Ta(9,{destroy:void 0},$s.bind(null,a,u,e,t),null),e},useId:function(){var l=et(),t=El.identifierPrefix;if(ol){var e=Gt,a=Yt;e=(a&~(1<<32-vt(a)-1)).toString(32)+e,t="_"+t+"R_"+e,e=du++,0<\/script>",u=u.removeChild(u.firstChild);break;case"select":u=typeof a.is=="string"?i.createElement("select",{is:a.is}):i.createElement("select"),a.multiple?u.multiple=!0:a.size&&(u.size=a.size);break;default:u=typeof a.is=="string"?i.createElement(n,{is:a.is}):i.createElement(n)}}u[$l]=t,u[nt]=a;l:for(i=t.child;i!==null;){if(i.tag===5||i.tag===6)u.appendChild(i.stateNode);else if(i.tag!==4&&i.tag!==27&&i.child!==null){i.child.return=i,i=i.child;continue}if(i===t)break l;for(;i.sibling===null;){if(i.return===null||i.return===t)break l;i=i.return}i.sibling.return=i.return,i=i.sibling}t.stateNode=u;l:switch(Pl(u,n,a),n){case"button":case"input":case"select":case"textarea":a=!!a.autoFocus;break l;case"img":a=!0;break l;default:a=!1}a&&It(t)}}return Ml(t),_c(t,t.type,l===null?null:l.memoizedProps,t.pendingProps,e),null;case 6:if(l&&t.stateNode!=null)l.memoizedProps!==a&&It(t);else{if(typeof a!="string"&&t.stateNode===null)throw Error(s(166));if(l=al.current,ya(t)){if(l=t.stateNode,e=t.memoizedProps,a=null,n=Wl,n!==null)switch(n.tag){case 27:case 5:a=n.memoizedProps}l[$l]=t,l=!!(l.nodeValue===e||a!==null&&a.suppressHydrationWarning===!0||wd(l.nodeValue,e)),l||he(t,!0)}else l=Bu(l).createTextNode(a),l[$l]=t,t.stateNode=l}return Ml(t),null;case 31:if(e=t.memoizedState,l===null||l.memoizedState!==null){if(a=ya(t),e!==null){if(l===null){if(!a)throw Error(s(318));if(l=t.memoizedState,l=l!==null?l.dehydrated:null,!l)throw Error(s(557));l[$l]=t}else Qe(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;Ml(t),l=!1}else e=Yi(),l!==null&&l.memoizedState!==null&&(l.memoizedState.hydrationErrors=e),l=!0;if(!l)return t.flags&256?(pt(t),t):(pt(t),null);if((t.flags&128)!==0)throw Error(s(558))}return Ml(t),null;case 13:if(a=t.memoizedState,l===null||l.memoizedState!==null&&l.memoizedState.dehydrated!==null){if(n=ya(t),a!==null&&a.dehydrated!==null){if(l===null){if(!n)throw Error(s(318));if(n=t.memoizedState,n=n!==null?n.dehydrated:null,!n)throw Error(s(317));n[$l]=t}else Qe(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;Ml(t),n=!1}else n=Yi(),l!==null&&l.memoizedState!==null&&(l.memoizedState.hydrationErrors=n),n=!0;if(!n)return t.flags&256?(pt(t),t):(pt(t),null)}return pt(t),(t.flags&128)!==0?(t.lanes=e,t):(e=a!==null,l=l!==null&&l.memoizedState!==null,e&&(a=t.child,n=null,a.alternate!==null&&a.alternate.memoizedState!==null&&a.alternate.memoizedState.cachePool!==null&&(n=a.alternate.memoizedState.cachePool.pool),u=null,a.memoizedState!==null&&a.memoizedState.cachePool!==null&&(u=a.memoizedState.cachePool.pool),u!==n&&(a.flags|=2048)),e!==l&&e&&(t.child.flags|=8192),xu(t,t.updateQueue),Ml(t),null);case 4:return Ul(),l===null&&Wc(t.stateNode.containerInfo),Ml(t),null;case 10:return kt(t.type),Ml(t),null;case 19:if(A(ql),a=t.memoizedState,a===null)return Ml(t),null;if(n=(t.flags&128)!==0,u=a.rendering,u===null)if(n)hn(a,!1);else{if(Rl!==0||l!==null&&(l.flags&128)!==0)for(l=t.child;l!==null;){if(u=su(l),u!==null){for(t.flags|=128,hn(a,!1),l=u.updateQueue,t.updateQueue=l,xu(t,l),t.subtreeFlags=0,l=e,e=t.child;e!==null;)Ts(e,l),e=e.sibling;return q(ql,ql.current&1|2),ol&&Jt(t,a.treeForkCount),t.child}l=l.sibling}a.tail!==null&&mt()>Au&&(t.flags|=128,n=!0,hn(a,!1),t.lanes=4194304)}else{if(!n)if(l=su(u),l!==null){if(t.flags|=128,n=!0,l=l.updateQueue,t.updateQueue=l,xu(t,l),hn(a,!0),a.tail===null&&a.tailMode==="hidden"&&!u.alternate&&!ol)return Ml(t),null}else 2*mt()-a.renderingStartTime>Au&&e!==536870912&&(t.flags|=128,n=!0,hn(a,!1),t.lanes=4194304);a.isBackwards?(u.sibling=t.child,t.child=u):(l=a.last,l!==null?l.sibling=u:t.child=u,a.last=u)}return a.tail!==null?(l=a.tail,a.rendering=l,a.tail=l.sibling,a.renderingStartTime=mt(),l.sibling=null,e=ql.current,q(ql,n?e&1|2:e&1),ol&&Jt(t,a.treeForkCount),l):(Ml(t),null);case 22:case 23:return pt(t),Fi(),a=t.memoizedState!==null,l!==null?l.memoizedState!==null!==a&&(t.flags|=8192):a&&(t.flags|=8192),a?(e&536870912)!==0&&(t.flags&128)===0&&(Ml(t),t.subtreeFlags&6&&(t.flags|=8192)):Ml(t),e=t.updateQueue,e!==null&&xu(t,e.retryQueue),e=null,l!==null&&l.memoizedState!==null&&l.memoizedState.cachePool!==null&&(e=l.memoizedState.cachePool.pool),a=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(a=t.memoizedState.cachePool.pool),a!==e&&(t.flags|=2048),l!==null&&A(Ve),null;case 24:return e=null,l!==null&&(e=l.memoizedState.cache),t.memoizedState.cache!==e&&(t.flags|=2048),kt(Gl),Ml(t),null;case 25:return null;case 30:return null}throw Error(s(156,t.tag))}function om(l,t){switch(qi(t),t.tag){case 1:return l=t.flags,l&65536?(t.flags=l&-65537|128,t):null;case 3:return kt(Gl),Ul(),l=t.flags,(l&65536)!==0&&(l&128)===0?(t.flags=l&-65537|128,t):null;case 26:case 27:case 5:return Jl(t),null;case 31:if(t.memoizedState!==null){if(pt(t),t.alternate===null)throw Error(s(340));Qe()}return l=t.flags,l&65536?(t.flags=l&-65537|128,t):null;case 13:if(pt(t),l=t.memoizedState,l!==null&&l.dehydrated!==null){if(t.alternate===null)throw Error(s(340));Qe()}return l=t.flags,l&65536?(t.flags=l&-65537|128,t):null;case 19:return A(ql),null;case 4:return Ul(),null;case 10:return kt(t.type),null;case 22:case 23:return pt(t),Fi(),l!==null&&A(Ve),l=t.flags,l&65536?(t.flags=l&-65537|128,t):null;case 24:return kt(Gl),null;case 25:return null;default:return null}}function Io(l,t){switch(qi(t),t.tag){case 3:kt(Gl),Ul();break;case 26:case 27:case 5:Jl(t);break;case 4:Ul();break;case 31:t.memoizedState!==null&&pt(t);break;case 13:pt(t);break;case 19:A(ql);break;case 10:kt(t.type);break;case 22:case 23:pt(t),Fi(),l!==null&&A(Ve);break;case 24:kt(Gl)}}function mn(l,t){try{var e=t.updateQueue,a=e!==null?e.lastEffect:null;if(a!==null){var n=a.next;e=n;do{if((e.tag&l)===l){a=void 0;var u=e.create,i=e.inst;a=u(),i.destroy=a}e=e.next}while(e!==n)}}catch(f){pl(t,t.return,f)}}function pe(l,t,e){try{var a=t.updateQueue,n=a!==null?a.lastEffect:null;if(n!==null){var u=n.next;a=u;do{if((a.tag&l)===l){var i=a.inst,f=i.destroy;if(f!==void 0){i.destroy=void 0,n=t;var d=e,v=f;try{v()}catch(x){pl(n,d,x)}}}a=a.next}while(a!==u)}}catch(x){pl(t,t.return,x)}}function Po(l){var t=l.updateQueue;if(t!==null){var e=l.stateNode;try{Zs(t,e)}catch(a){pl(l,l.return,a)}}}function ld(l,t,e){e.props=$e(l.type,l.memoizedProps),e.state=l.memoizedState;try{e.componentWillUnmount()}catch(a){pl(l,t,a)}}function yn(l,t){try{var e=l.ref;if(e!==null){switch(l.tag){case 26:case 27:case 5:var a=l.stateNode;break;case 30:a=l.stateNode;break;default:a=l.stateNode}typeof e=="function"?l.refCleanup=e(a):e.current=a}}catch(n){pl(l,t,n)}}function Xt(l,t){var e=l.ref,a=l.refCleanup;if(e!==null)if(typeof a=="function")try{a()}catch(n){pl(l,t,n)}finally{l.refCleanup=null,l=l.alternate,l!=null&&(l.refCleanup=null)}else if(typeof e=="function")try{e(null)}catch(n){pl(l,t,n)}else e.current=null}function td(l){var t=l.type,e=l.memoizedProps,a=l.stateNode;try{l:switch(t){case"button":case"input":case"select":case"textarea":e.autoFocus&&a.focus();break l;case"img":e.src?a.src=e.src:e.srcSet&&(a.srcset=e.srcSet)}}catch(n){pl(l,l.return,n)}}function Oc(l,t,e){try{var a=l.stateNode;Cm(a,l.type,e,t),a[nt]=t}catch(n){pl(l,l.return,n)}}function ed(l){return l.tag===5||l.tag===3||l.tag===26||l.tag===27&&Ae(l.type)||l.tag===4}function Mc(l){l:for(;;){for(;l.sibling===null;){if(l.return===null||ed(l.return))return null;l=l.return}for(l.sibling.return=l.return,l=l.sibling;l.tag!==5&&l.tag!==6&&l.tag!==18;){if(l.tag===27&&Ae(l.type)||l.flags&2||l.child===null||l.tag===4)continue l;l.child.return=l,l=l.child}if(!(l.flags&2))return l.stateNode}}function Dc(l,t,e){var a=l.tag;if(a===5||a===6)l=l.stateNode,t?(e.nodeType===9?e.body:e.nodeName==="HTML"?e.ownerDocument.body:e).insertBefore(l,t):(t=e.nodeType===9?e.body:e.nodeName==="HTML"?e.ownerDocument.body:e,t.appendChild(l),e=e._reactRootContainer,e!=null||t.onclick!==null||(t.onclick=Lt));else if(a!==4&&(a===27&&Ae(l.type)&&(e=l.stateNode,t=null),l=l.child,l!==null))for(Dc(l,t,e),l=l.sibling;l!==null;)Dc(l,t,e),l=l.sibling}function zu(l,t,e){var a=l.tag;if(a===5||a===6)l=l.stateNode,t?e.insertBefore(l,t):e.appendChild(l);else if(a!==4&&(a===27&&Ae(l.type)&&(e=l.stateNode),l=l.child,l!==null))for(zu(l,t,e),l=l.sibling;l!==null;)zu(l,t,e),l=l.sibling}function ad(l){var t=l.stateNode,e=l.memoizedProps;try{for(var a=l.type,n=t.attributes;n.length;)t.removeAttributeNode(n[0]);Pl(t,a,e),t[$l]=l,t[nt]=e}catch(u){pl(l,l.return,u)}}var Pt=!1,Zl=!1,Cc=!1,nd=typeof WeakSet=="function"?WeakSet:Set,kl=null;function dm(l,t){if(l=l.containerInfo,Pc=Vu,l=ys(l),Ei(l)){if("selectionStart"in l)var e={start:l.selectionStart,end:l.selectionEnd};else l:{e=(e=l.ownerDocument)&&e.defaultView||window;var a=e.getSelection&&e.getSelection();if(a&&a.rangeCount!==0){e=a.anchorNode;var n=a.anchorOffset,u=a.focusNode;a=a.focusOffset;try{e.nodeType,u.nodeType}catch{e=null;break l}var i=0,f=-1,d=-1,v=0,x=0,_=l,g=null;t:for(;;){for(var p;_!==e||n!==0&&_.nodeType!==3||(f=i+n),_!==u||a!==0&&_.nodeType!==3||(d=i+a),_.nodeType===3&&(i+=_.nodeValue.length),(p=_.firstChild)!==null;)g=_,_=p;for(;;){if(_===l)break t;if(g===e&&++v===n&&(f=i),g===u&&++x===a&&(d=i),(p=_.nextSibling)!==null)break;_=g,g=_.parentNode}_=p}e=f===-1||d===-1?null:{start:f,end:d}}else e=null}e=e||{start:0,end:0}}else e=null;for(lf={focusedElem:l,selectionRange:e},Vu=!1,kl=t;kl!==null;)if(t=kl,l=t.child,(t.subtreeFlags&1028)!==0&&l!==null)l.return=t,kl=l;else for(;kl!==null;){switch(t=kl,u=t.alternate,l=t.flags,t.tag){case 0:if((l&4)!==0&&(l=t.updateQueue,l=l!==null?l.events:null,l!==null))for(e=0;e title"))),Pl(u,a,e),u[$l]=l,wl(u),a=u;break l;case"link":var i=or("link","href",n).get(a+(e.href||""));if(i){for(var f=0;fTl&&(i=Tl,Tl=J,J=i);var m=hs(f,J),r=hs(f,Tl);if(m&&r&&(p.rangeCount!==1||p.anchorNode!==m.node||p.anchorOffset!==m.offset||p.focusNode!==r.node||p.focusOffset!==r.offset)){var y=_.createRange();y.setStart(m.node,m.offset),p.removeAllRanges(),J>Tl?(p.addRange(y),p.extend(r.node,r.offset)):(y.setEnd(r.node,r.offset),p.addRange(y))}}}}for(_=[],p=f;p=p.parentNode;)p.nodeType===1&&_.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof f.focus=="function"&&f.focus(),f=0;f<_.length;f++){var N=_[f];N.element.scrollLeft=N.left,N.element.scrollTop=N.top}}Vu=!!Pc,lf=Pc=null}finally{yl=n,R.p=a,j.T=e}}l.current=t,Vl=2}}function Dd(){if(Vl===2){Vl=0;var l=Te,t=Oa,e=(t.flags&8772)!==0;if((t.subtreeFlags&8772)!==0||e){e=j.T,j.T=null;var a=R.p;R.p=2;var n=yl;yl|=4;try{ud(l,t.alternate,t)}finally{yl=n,R.p=a,j.T=e}}Vl=3}}function Cd(){if(Vl===4||Vl===3){Vl=0,Zr();var l=Te,t=Oa,e=ne,a=Sd;(t.subtreeFlags&10256)!==0||(t.flags&10256)!==0?Vl=5:(Vl=0,Oa=Te=null,Ud(l,l.pendingLanes));var n=l.pendingLanes;if(n===0&&(ze=null),ii(e),t=t.stateNode,yt&&typeof yt.onCommitFiberRoot=="function")try{yt.onCommitFiberRoot(Ya,t,void 0,(t.current.flags&128)===128)}catch{}if(a!==null){t=j.T,n=R.p,R.p=2,j.T=null;try{for(var u=l.onRecoverableError,i=0;ie?32:e,j.T=null,e=Gc,Gc=null;var u=Te,i=ne;if(Vl=0,Oa=Te=null,ne=0,(yl&6)!==0)throw Error(s(331));var f=yl;if(yl|=4,yd(u.current),rd(u,u.current,i,e),yl=f,jn(0,!1),yt&&typeof yt.onPostCommitFiberRoot=="function")try{yt.onPostCommitFiberRoot(Ya,u)}catch{}return!0}finally{R.p=n,j.T=a,Ud(l,t)}}function Rd(l,t,e){t=At(e,t),t=Sc(l.stateNode,t,2),l=ge(l,t,2),l!==null&&(Xa(l,2),Qt(l))}function pl(l,t,e){if(l.tag===3)Rd(l,l,e);else for(;t!==null;){if(t.tag===3){Rd(t,l,e);break}else if(t.tag===1){var a=t.stateNode;if(typeof t.type.getDerivedStateFromError=="function"||typeof a.componentDidCatch=="function"&&(ze===null||!ze.has(a))){l=At(e,l),e=qo(2),a=ge(t,e,2),a!==null&&(Bo(e,a,t,l),Xa(a,2),Qt(a));break}}t=t.return}}function Lc(l,t,e){var a=l.pingCache;if(a===null){a=l.pingCache=new mm;var n=new Set;a.set(t,n)}else n=a.get(t),n===void 0&&(n=new Set,a.set(t,n));n.has(e)||(Rc=!0,n.add(e),l=bm.bind(null,l,t,e),t.then(l,l))}function bm(l,t,e){var a=l.pingCache;a!==null&&a.delete(t),l.pingedLanes|=l.suspendedLanes&e,l.warmLanes&=~e,El===l&&(cl&e)===e&&(Rl===4||Rl===3&&(cl&62914560)===cl&&300>mt()-Nu?(yl&2)===0&&Ma(l,0):qc|=e,_a===cl&&(_a=0)),Qt(l)}function qd(l,t){t===0&&(t=Of()),l=Ge(l,t),l!==null&&(Xa(l,t),Qt(l))}function pm(l){var t=l.memoizedState,e=0;t!==null&&(e=t.retryLane),qd(l,e)}function jm(l,t){var e=0;switch(l.tag){case 31:case 13:var a=l.stateNode,n=l.memoizedState;n!==null&&(e=n.retryLane);break;case 19:a=l.stateNode;break;case 22:a=l.stateNode._retryCache;break;default:throw Error(s(314))}a!==null&&a.delete(t),qd(l,e)}function xm(l,t){return ei(l,t)}var Uu=null,Ca=null,Vc=!1,Hu=!1,Kc=!1,Ne=0;function Qt(l){l!==Ca&&l.next===null&&(Ca===null?Uu=Ca=l:Ca=Ca.next=l),Hu=!0,Vc||(Vc=!0,Tm())}function jn(l,t){if(!Kc&&Hu){Kc=!0;do for(var e=!1,a=Uu;a!==null;){if(l!==0){var n=a.pendingLanes;if(n===0)var u=0;else{var i=a.suspendedLanes,f=a.pingedLanes;u=(1<<31-vt(42|l)+1)-1,u&=n&~(i&~f),u=u&201326741?u&201326741|1:u?u|2:0}u!==0&&(e=!0,Xd(a,u))}else u=cl,u=Yn(a,a===El?u:0,a.cancelPendingCommit!==null||a.timeoutHandle!==-1),(u&3)===0||Ga(a,u)||(e=!0,Xd(a,u));a=a.next}while(e);Kc=!1}}function zm(){Bd()}function Bd(){Hu=Vc=!1;var l=0;Ne!==0&&Hm()&&(l=Ne);for(var t=mt(),e=null,a=Uu;a!==null;){var n=a.next,u=Yd(a,t);u===0?(a.next=null,e===null?Uu=n:e.next=n,n===null&&(Ca=e)):(e=a,(l!==0||(u&3)!==0)&&(Hu=!0)),a=n}Vl!==0&&Vl!==5||jn(l),Ne!==0&&(Ne=0)}function Yd(l,t){for(var e=l.suspendedLanes,a=l.pingedLanes,n=l.expirationTimes,u=l.pendingLanes&-62914561;0f)break;var x=d.transferSize,_=d.initiatorType;x&&kd(_)&&(d=d.responseEnd,i+=x*(d"u"?null:document;function ir(l,t,e){var a=Ua;if(a&&typeof t=="string"&&t){var n=Et(t);n='link[rel="'+l+'"][href="'+n+'"]',typeof e=="string"&&(n+='[crossorigin="'+e+'"]'),ur.has(n)||(ur.add(n),l={rel:l,crossOrigin:e,href:t},a.querySelector(n)===null&&(t=a.createElement("link"),Pl(t,"link",l),wl(t),a.head.appendChild(t)))}}function Lm(l){ue.D(l),ir("dns-prefetch",l,null)}function Vm(l,t){ue.C(l,t),ir("preconnect",l,t)}function Km(l,t,e){ue.L(l,t,e);var a=Ua;if(a&&l&&t){var n='link[rel="preload"][as="'+Et(t)+'"]';t==="image"&&e&&e.imageSrcSet?(n+='[imagesrcset="'+Et(e.imageSrcSet)+'"]',typeof e.imageSizes=="string"&&(n+='[imagesizes="'+Et(e.imageSizes)+'"]')):n+='[href="'+Et(l)+'"]';var u=n;switch(t){case"style":u=Ha(l);break;case"script":u=Ra(l)}Ut.has(u)||(l=E({rel:"preload",href:t==="image"&&e&&e.imageSrcSet?void 0:l,as:t},e),Ut.set(u,l),a.querySelector(n)!==null||t==="style"&&a.querySelector(En(u))||t==="script"&&a.querySelector(Nn(u))||(t=a.createElement("link"),Pl(t,"link",l),wl(t),a.head.appendChild(t)))}}function Jm(l,t){ue.m(l,t);var e=Ua;if(e&&l){var a=t&&typeof t.as=="string"?t.as:"script",n='link[rel="modulepreload"][as="'+Et(a)+'"][href="'+Et(l)+'"]',u=n;switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":u=Ra(l)}if(!Ut.has(u)&&(l=E({rel:"modulepreload",href:l},t),Ut.set(u,l),e.querySelector(n)===null)){switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(e.querySelector(Nn(u)))return}a=e.createElement("link"),Pl(a,"link",l),wl(a),e.head.appendChild(a)}}}function wm(l,t,e){ue.S(l,t,e);var a=Ua;if(a&&l){var n=ea(a).hoistableStyles,u=Ha(l);t=t||"default";var i=n.get(u);if(!i){var f={loading:0,preload:null};if(i=a.querySelector(En(u)))f.loading=5;else{l=E({rel:"stylesheet",href:l,"data-precedence":t},e),(e=Ut.get(u))&&ff(l,e);var d=i=a.createElement("link");wl(d),Pl(d,"link",l),d._p=new Promise(function(v,x){d.onload=v,d.onerror=x}),d.addEventListener("load",function(){f.loading|=1}),d.addEventListener("error",function(){f.loading|=2}),f.loading|=4,Gu(i,t,a)}i={type:"stylesheet",instance:i,count:1,state:f},n.set(u,i)}}}function km(l,t){ue.X(l,t);var e=Ua;if(e&&l){var a=ea(e).hoistableScripts,n=Ra(l),u=a.get(n);u||(u=e.querySelector(Nn(n)),u||(l=E({src:l,async:!0},t),(t=Ut.get(n))&&sf(l,t),u=e.createElement("script"),wl(u),Pl(u,"link",l),e.head.appendChild(u)),u={type:"script",instance:u,count:1,state:null},a.set(n,u))}}function $m(l,t){ue.M(l,t);var e=Ua;if(e&&l){var a=ea(e).hoistableScripts,n=Ra(l),u=a.get(n);u||(u=e.querySelector(Nn(n)),u||(l=E({src:l,async:!0,type:"module"},t),(t=Ut.get(n))&&sf(l,t),u=e.createElement("script"),wl(u),Pl(u,"link",l),e.head.appendChild(u)),u={type:"script",instance:u,count:1,state:null},a.set(n,u))}}function cr(l,t,e,a){var n=(n=al.current)?Yu(n):null;if(!n)throw Error(s(446));switch(l){case"meta":case"title":return null;case"style":return typeof e.precedence=="string"&&typeof e.href=="string"?(t=Ha(e.href),e=ea(n).hoistableStyles,a=e.get(t),a||(a={type:"style",instance:null,count:0,state:null},e.set(t,a)),a):{type:"void",instance:null,count:0,state:null};case"link":if(e.rel==="stylesheet"&&typeof e.href=="string"&&typeof e.precedence=="string"){l=Ha(e.href);var u=ea(n).hoistableStyles,i=u.get(l);if(i||(n=n.ownerDocument||n,i={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},u.set(l,i),(u=n.querySelector(En(l)))&&!u._p&&(i.instance=u,i.state.loading=5),Ut.has(l)||(e={rel:"preload",as:"style",href:e.href,crossOrigin:e.crossOrigin,integrity:e.integrity,media:e.media,hrefLang:e.hrefLang,referrerPolicy:e.referrerPolicy},Ut.set(l,e),u||Wm(n,l,e,i.state))),t&&a===null)throw Error(s(528,""));return i}if(t&&a!==null)throw Error(s(529,""));return null;case"script":return t=e.async,e=e.src,typeof e=="string"&&t&&typeof t!="function"&&typeof t!="symbol"?(t=Ra(e),e=ea(n).hoistableScripts,a=e.get(t),a||(a={type:"script",instance:null,count:0,state:null},e.set(t,a)),a):{type:"void",instance:null,count:0,state:null};default:throw Error(s(444,l))}}function Ha(l){return'href="'+Et(l)+'"'}function En(l){return'link[rel="stylesheet"]['+l+"]"}function fr(l){return E({},l,{"data-precedence":l.precedence,precedence:null})}function Wm(l,t,e,a){l.querySelector('link[rel="preload"][as="style"]['+t+"]")?a.loading=1:(t=l.createElement("link"),a.preload=t,t.addEventListener("load",function(){return a.loading|=1}),t.addEventListener("error",function(){return a.loading|=2}),Pl(t,"link",e),wl(t),l.head.appendChild(t))}function Ra(l){return'[src="'+Et(l)+'"]'}function Nn(l){return"script[async]"+l}function sr(l,t,e){if(t.count++,t.instance===null)switch(t.type){case"style":var a=l.querySelector('style[data-href~="'+Et(e.href)+'"]');if(a)return t.instance=a,wl(a),a;var n=E({},e,{"data-href":e.href,"data-precedence":e.precedence,href:null,precedence:null});return a=(l.ownerDocument||l).createElement("style"),wl(a),Pl(a,"style",n),Gu(a,e.precedence,l),t.instance=a;case"stylesheet":n=Ha(e.href);var u=l.querySelector(En(n));if(u)return t.state.loading|=4,t.instance=u,wl(u),u;a=fr(e),(n=Ut.get(n))&&ff(a,n),u=(l.ownerDocument||l).createElement("link"),wl(u);var i=u;return i._p=new Promise(function(f,d){i.onload=f,i.onerror=d}),Pl(u,"link",a),t.state.loading|=4,Gu(u,e.precedence,l),t.instance=u;case"script":return u=Ra(e.src),(n=l.querySelector(Nn(u)))?(t.instance=n,wl(n),n):(a=e,(n=Ut.get(u))&&(a=E({},e),sf(a,n)),l=l.ownerDocument||l,n=l.createElement("script"),wl(n),Pl(n,"link",a),l.head.appendChild(n),t.instance=n);case"void":return null;default:throw Error(s(443,t.type))}else t.type==="stylesheet"&&(t.state.loading&4)===0&&(a=t.instance,t.state.loading|=4,Gu(a,e.precedence,l));return t.instance}function Gu(l,t,e){for(var a=e.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),n=a.length?a[a.length-1]:null,u=n,i=0;i title"):null)}function Fm(l,t,e){if(e===1||t.itemProp!=null)return!1;switch(l){case"meta":case"title":return!0;case"style":if(typeof t.precedence!="string"||typeof t.href!="string"||t.href==="")break;return!0;case"link":if(typeof t.rel!="string"||typeof t.href!="string"||t.href===""||t.onLoad||t.onError)break;return t.rel==="stylesheet"?(l=t.disabled,typeof t.precedence=="string"&&l==null):!0;case"script":if(t.async&&typeof t.async!="function"&&typeof t.async!="symbol"&&!t.onLoad&&!t.onError&&t.src&&typeof t.src=="string")return!0}return!1}function rr(l){return!(l.type==="stylesheet"&&(l.state.loading&3)===0)}function Im(l,t,e,a){if(e.type==="stylesheet"&&(typeof a.media!="string"||matchMedia(a.media).matches!==!1)&&(e.state.loading&4)===0){if(e.instance===null){var n=Ha(a.href),u=t.querySelector(En(n));if(u){t=u._p,t!==null&&typeof t=="object"&&typeof t.then=="function"&&(l.count++,l=Qu.bind(l),t.then(l,l)),e.state.loading|=4,e.instance=u,wl(u);return}u=t.ownerDocument||t,a=fr(a),(n=Ut.get(n))&&ff(a,n),u=u.createElement("link"),wl(u);var i=u;i._p=new Promise(function(f,d){i.onload=f,i.onerror=d}),Pl(u,"link",a),e.instance=u}l.stylesheets===null&&(l.stylesheets=new Map),l.stylesheets.set(e,t),(t=e.state.preload)&&(e.state.loading&3)===0&&(l.count++,e=Qu.bind(l),t.addEventListener("load",e),t.addEventListener("error",e))}}var of=0;function Pm(l,t){return l.stylesheets&&l.count===0&&Lu(l,l.stylesheets),0of?50:800)+t);return l.unsuspend=e,function(){l.unsuspend=null,clearTimeout(a),clearTimeout(n)}}:null}function Qu(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Lu(this,this.stylesheets);else if(this.unsuspend){var l=this.unsuspend;this.unsuspend=null,l()}}}var Zu=null;function Lu(l,t){l.stylesheets=null,l.unsuspend!==null&&(l.count++,Zu=new Map,t.forEach(ly,l),Zu=null,Qu.call(l))}function ly(l,t){if(!(t.state.loading&4)){var e=Zu.get(l);if(e)var a=e.get(null);else{e=new Map,Zu.set(l,e);for(var n=l.querySelectorAll("link[data-precedence],style[data-precedence]"),u=0;u"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(o)}catch(S){console.error(S)}}return o(),bf.exports=vy(),bf.exports}var Sy=gy();function by(){const o=new URLSearchParams(location.search).get("token");if(o){sessionStorage.setItem("dm.token",o);const S=location.pathname+location.hash;return history.replaceState(null,"",S),o}return sessionStorage.getItem("dm.token")??""}const Ba=by();class Fu extends Error{status;body;constructor(S,z,s={}){super(z),this.status=S,this.body=s}get needsTrust(){return this.body.needsTrust===!0}}async function dt(o,S){const z={...S?.headers};Ba&&(z["X-Auth-Token"]=Ba),S?.body&&(z["Content-Type"]="application/json");const s=await fetch(o,{...S,headers:z});if(s.status===204)return;const O=await s.text();let D={};if(O)try{D=JSON.parse(O)}catch{if(!s.ok)throw new Fu(s.status,O.slice(0,400))}if(!s.ok){const T=typeof D.error=="string"?D.error:`request failed (${s.status})`;throw new Fu(s.status,T,D)}return D}const Ht=(o,S)=>dt(o,{method:"POST",body:S===void 0?void 0:JSON.stringify(S)}),Al={health:()=>dt("/api/health"),source:()=>dt("/api/source"),volumeSizes:()=>dt("/api/source/sizes"),sources:()=>dt("/api/sources"),saveSource:o=>Ht("/api/sources",o),deleteSource:o=>dt(`/api/sources/${o}`,{method:"DELETE"}),selectSource:o=>Ht(`/api/sources/${o}/select`),probeSource:o=>Ht(`/api/sources/${o}/probe`),trustSource:(o,S)=>Ht(`/api/sources/${o}/trust`,{fingerprint:S}),connections:()=>dt("/api/connections"),saveConnection:o=>Ht("/api/connections",o),deleteConnection:o=>dt(`/api/connections/${o}`,{method:"DELETE"}),probe:o=>Ht(`/api/connections/${o}/probe`),trust:(o,S)=>Ht(`/api/connections/${o}/trust`,{fingerprint:S}),testConnection:o=>Ht(`/api/connections/${o}/test`),targetInventory:o=>dt(`/api/connections/${o}/inventory`),preview:o=>Ht("/api/plan/preview",o),migrateSSH:(o,S)=>Ht("/api/migrate/ssh",{connectionId:o,plan:S}),buildPackage:(o,S)=>Ht("/api/migrate/package",{plan:o,format:S}),jobs:()=>dt("/api/jobs"),job:o=>dt(`/api/jobs/${o}`),cancelJob:o=>Ht(`/api/jobs/${o}/cancel`),deleteJob:o=>dt(`/api/jobs/${o}`,{method:"DELETE"}),packages:()=>dt("/api/packages"),deletePackage:o=>dt(`/api/packages/${encodeURIComponent(o)}`,{method:"DELETE"}),downloadUrl:o=>`/api/packages/${encodeURIComponent(o)}/download`+(Ba?`?token=${encodeURIComponent(Ba)}`:""),jobEvents:o=>new EventSource(`/api/jobs/${o}/events`+(Ba?`?token=${encodeURIComponent(Ba)}`:""))};function ie(o){if(o==null||o<0)return"–";if(o===0)return"0 B";const S=["B","KiB","MiB","GiB","TiB","PiB"];let z=o,s=0;for(;z>=1024&&sS(D.target.checked)}),c.jsx("span",{children:z})]})}function Ll({label:o,children:S}){return c.jsxs("label",{className:"field",children:[c.jsx("span",{children:o}),S]})}function Iu({title:o,onClose:S,children:z,footer:s,wide:O}){return Q.useEffect(()=>{const D=T=>{T.key==="Escape"&&S()};return window.addEventListener("keydown",D),()=>window.removeEventListener("keydown",D)},[S]),c.jsx("div",{className:"modal-backdrop",onMouseDown:D=>D.target===D.currentTarget&&S(),children:c.jsxs("div",{className:"modal",style:O?{width:"min(1000px, 100%)"}:void 0,children:[c.jsxs("header",{children:[o,c.jsx("span",{className:"spacer"}),c.jsx("button",{className:"btn ghost tiny",onClick:S,children:"close"})]}),c.jsx("div",{className:"content",children:z}),s&&c.jsx("footer",{children:s})]})})}function ht({kind:o,children:S}){return c.jsx("div",{className:`notice ${o==="info"?"":o}`,children:S})}function zf({done:o,total:S,state:z}){const s=S>0?Math.min(100,o/S*100):z==="succeeded"?100:0,O=z==="succeeded"?"done":z==="failed"?"failed":"";return c.jsx("div",{className:`progress ${O}`,children:c.jsx("div",{style:{width:`${s}%`}})})}function Br(o,S){return o.kind==="tmpfs"?0:o.sizeBytes>=0?o.sizeBytes:o.name&&S[o.name]!==void 0?S[o.name]:-1}function py({source:o,sel:S,setSel:z,targetInv:s,loading:O,sizes:D}){const[T,w]=Q.useState(""),[H,b]=Q.useState(new Set),[V,E]=Q.useState(!1),U=o?.inventory.containers??[],ll=Q.useMemo(()=>new Set((s?.containers??[]).map(C=>C.name)),[s]),W=Q.useMemo(()=>{const C=T.trim().toLowerCase();return U.filter(Z=>V&&Z.state!=="running"?!1:C?Z.name.toLowerCase().includes(C)||Z.image.toLowerCase().includes(C)||(Z.composeProject??"").toLowerCase().includes(C)||(Z.mounts??[]).some(B=>B.destination.toLowerCase().includes(C)||(B.name??"").toLowerCase().includes(C)):!0)},[U,T,V]),vl=Q.useMemo(()=>{const C=new Map;for(const Z of W){const B=Z.composeProject||"",$=C.get(B);$?$.push(Z):C.set(B,[Z])}return[...C.entries()].sort((Z,B)=>Z[0]===""?1:B[0]===""?-1:Z[0].localeCompare(B[0]))},[W]);function Yl(C,Z){z(B=>({...B,[C]:{...B[C],...Z}}))}function gl(C,Z){z(B=>{const $={...B};for(const sl of C)$[sl]&&($[sl]={...$[sl],include:Z});return $})}function _l(C){z(Z=>{const B={...Z};for(const $ of U){const sl=B[$.id];sl?.include&&(B[$.id]=C(sl,$))}return B})}function dl(C,Z){_l((B,$)=>{const sl={...B.mounts};for(const jl of $.mounts??[])jl.kind!=="tmpfs"&&Z.includes(jl.kind)&&(sl[jl.destination]={...sl[jl.destination],action:C});return{...B,mounts:sl}})}const el=W.map(C=>C.id),Sl=W.filter(C=>S[C.id]?.include).length,Nl=Object.values(S).some(C=>C.include);return c.jsxs(c.Fragment,{children:[c.jsxs("div",{className:"toolbar",children:[c.jsx("input",{className:"search",type:"text",placeholder:"filter by name, image, mount…",value:T,onChange:C=>w(C.target.value)}),c.jsx("button",{className:"btn tiny",onClick:()=>gl(el,!0),children:"select all"}),c.jsx("button",{className:"btn tiny",onClick:()=>gl(el,!1),children:"clear"}),c.jsx("button",{className:"btn tiny",onClick:()=>gl(W.filter(C=>C.state==="running").map(C=>C.id),!0),children:"select running"}),c.jsx(rt,{checked:V,onChange:E,label:c.jsx("span",{className:"small muted",children:"running only"})}),c.jsx("span",{className:"spacer"}),c.jsxs("span",{className:"small faint nowrap",children:["apply to ",Sl?`${Sl} selected`:"selection",":"]}),c.jsx("button",{className:"btn tiny",disabled:!Nl,onClick:()=>dl("copy",["volume","anonymous","bind"]),children:"copy all data"}),c.jsx("button",{className:"btn tiny",disabled:!Nl,onClick:()=>dl("skip",["bind"]),children:"skip binds"}),c.jsx("button",{className:"btn tiny",disabled:!Nl,onClick:()=>dl("structure",["volume","anonymous","bind"]),children:"structure only"}),c.jsxs("select",{className:"btn tiny",style:{width:"auto"},disabled:!Nl,value:"",onChange:C=>{const Z=C.target.value;if(Z){if(Z==="start"&&_l(B=>({...B,startAfter:!0})),Z==="nostart"&&_l(B=>({...B,startAfter:!1})),Z==="live"&&_l(B=>({...B,stopSourceDuringCopy:!1})),Z==="quiesce"&&_l(B=>({...B,stopSourceDuringCopy:!0})),Z==="keepsource"&&_l(B=>({...B,stopSourceAfter:!1})),Z==="stopsource"&&_l(B=>({...B,stopSourceAfter:!0})),Z.startsWith("img:")){const B=Z.slice(4);_l($=>({...$,migrateImage:B!=="skip",imageMode:B}))}C.target.value=""}},children:[c.jsx("option",{value:"",children:"more…"}),c.jsx("option",{value:"start",children:"start after migration"}),c.jsx("option",{value:"nostart",children:"leave stopped on target"}),c.jsx("option",{value:"quiesce",children:"stop source while copying"}),c.jsx("option",{value:"live",children:"copy while running (hot)"}),c.jsx("option",{value:"stopsource",children:"stop source after migration"}),c.jsx("option",{value:"keepsource",children:"leave source running"}),c.jsx("option",{value:"img:auto",children:"image: auto"}),c.jsx("option",{value:"img:pull",children:"image: pull on target"}),c.jsx("option",{value:"img:stream",children:"image: transfer layers"}),c.jsx("option",{value:"img:skip",children:"image: already on target"})]})]}),O&&U.length===0&&c.jsx("div",{className:"empty",children:"reading the source daemon…"}),!O&&U.length===0&&c.jsx("div",{className:"empty",children:"no containers on this host"}),!O&&U.length>0&&W.length===0&&c.jsx("div",{className:"empty",children:"nothing matches the filter"}),c.jsx("div",{className:"clist",children:vl.map(([C,Z])=>c.jsxs("div",{children:[vl.length>1&&c.jsxs("div",{className:"group-head",children:[c.jsx(rt,{checked:Z.every(B=>S[B.id]?.include),onChange:B=>gl(Z.map($=>$.id),B),label:C?`compose: ${C}`:"standalone"}),c.jsx("span",{className:"line"}),c.jsx("span",{children:Z.length})]}),Z.map(B=>c.jsx(jy,{c:B,s:S[B.id],onChange:$=>Yl(B.id,$),expanded:H.has(B.id),toggleExpanded:()=>b($=>{const sl=new Set($);return sl.has(B.id)?sl.delete(B.id):sl.add(B.id),sl}),conflicts:ll.has(S[B.id]?.nameOverride||B.name),sizes:D},B.id))]},C||"__none"))})]})}function jy({c:o,s:S,onChange:z,expanded:s,toggleExpanded:O,conflicts:D,sizes:T}){if(!S)return null;const H=(o.mounts??[]).filter(E=>E.kind!=="tmpfs"),b=H.filter(E=>(S.mounts[E.destination]?.action??"copy")==="copy"),V=b.reduce((E,U)=>{const ll=Br(U,T);return E+(ll>0?ll:0)},0);return c.jsxs(c.Fragment,{children:[c.jsxs("div",{className:`crow${S.include?" selected":""}`,children:[c.jsx(rt,{checked:S.include,onChange:E=>z({include:E}),label:""}),c.jsx("button",{className:"expander",onClick:O,title:"per-item options",children:s?"▾":"▸"}),c.jsxs("div",{style:{minWidth:0},children:[c.jsx("div",{className:"name truncate",title:o.name,children:o.name}),c.jsxs("div",{className:"sub row",style:{gap:6},children:[c.jsx(Un,{state:o.state}),o.composeService&&c.jsxs("span",{className:"faint",children:["· ",o.composeService]}),D&&c.jsx("span",{className:"badge",style:{borderColor:"#5c4520",color:"#e0b556"},children:"on target"})]})]}),c.jsx("div",{className:"image truncate",title:o.image,children:o.image}),c.jsxs("div",{className:"tags",children:[H.map(E=>c.jsx("span",{className:`badge ${E.kind==="bind"?"bind":E.kind==="anonymous"?"anon":"vol"}`,title:`${E.kind} → ${E.destination}${E.readOnly?" (read-only)":""}`,style:{opacity:(S.mounts[E.destination]?.action??"copy")==="skip"?.35:1},children:E.kind==="bind"?(E.source??"").split("/").pop()||"/":E.kind==="anonymous"?"anon":E.name},E.destination)),(o.endpoints??[]).filter(E=>!["bridge","host","none"].includes(E.network)).map(E=>c.jsx("span",{className:"badge net",title:`network ${E.network}`,children:E.network},E.network)),(o.ports??[]).slice(0,3).map((E,U)=>c.jsxs("span",{className:"badge port",children:[E.hostPort,":",E.containerPort.split("/")[0]]},U)),(o.ports??[]).length>3&&c.jsxs("span",{className:"badge port",children:["+",(o.ports??[]).length-3]})]}),c.jsxs("div",{className:"small faint nowrap",style:{textAlign:"right"},children:[b.length>0?`${b.length} to copy`:"no data",V>0&&c.jsxs(c.Fragment,{children:[" · ",ie(V)]})]})]}),s&&c.jsx(xy,{c:o,s:S,onChange:z,sizes:T})]})}function xy({c:o,s:S,onChange:z,sizes:s}){const O=o.mounts??[];function D(T,w){z({mounts:{...S.mounts,[T]:{...S.mounts[T],...w}}})}return c.jsxs("div",{className:"detail",children:[(o.warnings??[]).map((T,w)=>c.jsx("div",{className:"notice warn",children:T},w)),c.jsxs("div",{className:"grid2",children:[c.jsx(Ll,{label:"name on target",children:c.jsx("input",{type:"text",placeholder:o.name,value:S.nameOverride??"",onChange:T=>z({nameOverride:T.target.value})})}),c.jsx(Ll,{label:"image",children:c.jsxs("select",{value:S.migrateImage?S.imageMode:"skip",onChange:T=>{const w=T.target.value;z({migrateImage:w!=="skip",imageMode:w})},children:[c.jsx("option",{value:"auto",children:"auto — reuse, pull, or transfer"}),c.jsx("option",{value:"pull",children:"pull on the target"}),c.jsx("option",{value:"stream",children:"transfer the layers"}),c.jsx("option",{value:"skip",children:"already on the target"})]})}),c.jsxs("div",{className:"stack",style:{gap:6},children:[c.jsx(rt,{checked:S.migrateNetworks,onChange:T=>z({migrateNetworks:T}),label:"recreate networks and reattach"}),c.jsx(rt,{checked:S.keepStaticIps,onChange:T=>z({keepStaticIps:T}),disabled:!S.migrateNetworks,label:"keep static IP addresses",title:"Only works when the target networks use the same subnets"}),c.jsx(rt,{checked:S.migratePorts,onChange:T=>z({migratePorts:T}),label:"publish the same host ports"})]}),c.jsxs("div",{className:"stack",style:{gap:6},children:[c.jsx(rt,{checked:S.startAfter,onChange:T=>z({startAfter:T}),label:"start on the target"}),c.jsx(rt,{checked:S.stopSourceDuringCopy,onChange:T=>z({stopSourceDuringCopy:T}),label:"stop the source while copying",title:"Recommended: databases and other writers produce inconsistent copies while running"}),c.jsx(rt,{checked:S.stopSourceAfter,onChange:T=>z({stopSourceAfter:T}),label:"leave the source stopped afterwards"})]})]}),O.length===0?c.jsx("div",{className:"small faint",children:"this container has no mounts"}):c.jsxs("table",{className:"mount-table",children:[c.jsx("thead",{children:c.jsxs("tr",{children:[c.jsx("th",{style:{width:74},children:"kind"}),c.jsx("th",{children:"in the container"}),c.jsx("th",{children:"on the source"}),c.jsx("th",{style:{width:130},children:"action"}),c.jsx("th",{children:"on the target"}),c.jsx("th",{style:{width:70,textAlign:"right"},children:"size"})]})}),c.jsx("tbody",{children:O.map(T=>{const w=S.mounts[T.destination]??{action:"copy"},H=T.kind==="tmpfs";return c.jsxs("tr",{children:[c.jsx("td",{children:c.jsx("span",{className:`badge ${T.kind==="bind"?"bind":T.kind==="anonymous"?"anon":T.kind==="tmpfs"?"tmpfs":"vol"}`,children:T.kind})}),c.jsxs("td",{className:"mono truncate",title:T.destination,children:[T.destination,T.readOnly&&c.jsx("span",{className:"faint",children:" :ro"})]}),c.jsx("td",{className:"mono truncate faint",title:T.source||T.name,children:T.kind==="bind"?T.source:T.kind==="anonymous"?"(generated)":T.name}),c.jsx("td",{children:c.jsxs("select",{value:w.action,disabled:H,onChange:b=>D(T.destination,{action:b.target.value}),children:[c.jsx("option",{value:"copy",children:"copy data"}),c.jsx("option",{value:"structure",children:"create empty"}),c.jsx("option",{value:"skip",children:"do not mount"})]})}),c.jsxs("td",{children:[T.kind==="bind"&&w.action!=="skip"&&c.jsx("input",{type:"text",placeholder:T.source,value:w.targetSource??"",onChange:b=>D(T.destination,{targetSource:b.target.value})}),T.kind==="volume"&&w.action!=="skip"&&c.jsx("input",{type:"text",placeholder:T.name,value:w.targetName??"",onChange:b=>D(T.destination,{targetName:b.target.value})}),T.kind==="anonymous"&&c.jsx("span",{className:"small faint",children:"a fresh volume is created"}),H&&c.jsx("span",{className:"small faint",children:"in memory, nothing to copy"})]}),c.jsx("td",{className:"small faint nowrap",style:{textAlign:"right"},children:H?"–":ie(Br(T,s))})]},T.destination)})})]})]})}function Yr({value:o,set:S,where:z}){return c.jsxs(c.Fragment,{children:[c.jsxs("div",{className:"row",style:{gap:12},children:[c.jsx(Ll,{label:"host",children:c.jsx("input",{type:"text",value:o.host??"",onChange:s=>S("host",s.target.value)})}),c.jsx("div",{style:{width:90},children:c.jsx(Ll,{label:"port",children:c.jsx("input",{type:"number",value:o.port??22,onChange:s=>S("port",Number(s.target.value))})})})]}),c.jsxs("div",{className:"row",style:{gap:12},children:[c.jsx(Ll,{label:"user",children:c.jsx("input",{type:"text",value:o.user??"",onChange:s=>S("user",s.target.value)})}),c.jsx(Ll,{label:"authentication",children:c.jsxs("select",{value:o.auth??"password",onChange:s=>S("auth",s.target.value),children:[c.jsx("option",{value:"password",children:"password"}),c.jsx("option",{value:"key",children:"private key"}),c.jsx("option",{value:"agent",children:"ssh agent"})]})})]}),o.auth==="password"&&c.jsx(Ll,{label:"password",children:c.jsx("input",{type:"password",value:o.password??"",onChange:s=>S("password",s.target.value)})}),o.auth==="key"&&c.jsxs(c.Fragment,{children:[c.jsx(Ll,{label:"private key path on this machine (leave empty to paste the key below)",children:c.jsx("input",{type:"text",placeholder:"/root/.ssh/id_ed25519",value:o.privateKeyPath??"",onChange:s=>S("privateKeyPath",s.target.value)})}),c.jsx(Ll,{label:"or paste the private key",children:c.jsx("textarea",{rows:5,value:o.privateKey??"",onChange:s=>S("privateKey",s.target.value),placeholder:"-----BEGIN OPENSSH PRIVATE KEY-----"})}),c.jsx(Ll,{label:"passphrase (if the key is encrypted)",children:c.jsx("input",{type:"password",value:o.passphrase??"",onChange:s=>S("passphrase",s.target.value)})})]}),o.auth==="agent"&&c.jsxs("div",{className:"small muted",children:["Uses the agent at ",c.jsx("span",{className:"mono",children:"$SSH_AUTH_SOCK"})," of the process running dockmv."]}),c.jsx(rt,{checked:o.sudo??!1,onChange:s=>S("sudo",s),label:`run docker through sudo -n on the ${z}`,title:"Needed when the login user is not in the docker group. sudo must not ask for a password."}),c.jsx(Ll,{label:`docker command on the ${z} (optional)`,children:c.jsx("input",{type:"text",placeholder:"docker",value:o.dockerCmd??"",onChange:s=>S("dockerCmd",s.target.value)})}),c.jsx(rt,{checked:o.saveSecrets??!1,onChange:s=>S("saveSecrets",s),label:"remember the password / key on disk"}),o.saveSecrets?c.jsx(ht,{kind:"warn",children:"Credentials are stored in plain text in dockmv's data directory, readable only by this user. Leave this off to keep them in memory for this session only."}):c.jsx("div",{className:"small faint",children:"Credentials stay in memory and are lost when dockmv restarts."})]})}function Gr({info:o,onClose:S,onTrust:z}){return c.jsx(Iu,{title:"SSH host key",onClose:S,footer:c.jsxs(c.Fragment,{children:[c.jsx("button",{className:"btn",onClick:S,children:"cancel"}),c.jsx("button",{className:"btn primary",onClick:z,children:o.changed?"replace the stored key and trust":"trust this host"})]}),children:c.jsxs("div",{className:"stack",children:[o.changed&&c.jsxs(ht,{kind:"err",children:["The key presented by this host is ",c.jsx("b",{children:"different"})," from the one recorded earlier. This happens after a reinstall — but it is also what a machine-in-the-middle looks like. Only continue if you know why it changed."]}),o.trusted&&!o.changed&&c.jsx(ht,{kind:"ok",children:"This host key is already trusted."}),c.jsxs("div",{className:"small muted",children:["Compare this with the output of ",c.jsxs("span",{className:"mono",children:["ssh-keyscan -t ",o.keyType," ",o.host]})," ","run on the host itself, or with ",c.jsx("span",{className:"mono",children:"ssh-keygen -lf /etc/ssh/ssh_host_*_key.pub"}),"."]}),c.jsxs("div",{className:"fingerprint",children:[o.keyType,c.jsx("br",{}),o.fingerprint]})]})})}function zy({sources:o,selected:S,status:z,selectSource:s,reload:O,onError:D}){const[T,w]=Q.useState(null),[H,b]=Q.useState(null),[V,E]=Q.useState(""),[U,ll]=Q.useState(""),W=o.find(el=>el.id===S),vl=!W||W.kind==="local",Yl=W?.kind==="ssh";async function gl(el,Sl,Nl){E(el);try{await Nl()}catch(C){C instanceof Fu&&C.needsTrust?(ll(Sl),await _l(Sl)):D(C instanceof Error?C.message:String(C))}finally{E("")}}async function _l(el){try{b(await Al.probeSource(el))}catch(Sl){D(Sl instanceof Error?Sl.message:String(Sl))}}async function dl(){const el=U||S;H&&await gl("trust",el,async()=>{await Al.trustSource(el,H.fingerprint),b(null),await s(el)})}return c.jsxs("div",{className:"section",children:[c.jsx("h3",{children:"source host"}),c.jsxs("div",{className:"stack",children:[c.jsxs("div",{className:"row",children:[c.jsx("select",{value:S,disabled:!!V,onChange:el=>{const Sl=el.target.value;gl("select",Sl,()=>s(Sl))},children:o.map(el=>c.jsx("option",{value:el.id,children:Ty(el)},el.id))}),c.jsx("button",{className:"btn tiny",onClick:()=>w({kind:"ssh",ssh:{port:22,auth:"password",saveSecrets:!1,sudo:!1}}),children:"new"})]}),c.jsxs("div",{className:"row wrap",style:{gap:6},children:[c.jsx("button",{className:"btn tiny",disabled:!!V,onClick:()=>{gl("select",S,()=>s(S))},children:V==="select"?"connecting…":"reconnect"}),!vl&&c.jsx("button",{className:"btn tiny",onClick:()=>w(W),children:"edit"}),Yl&&c.jsx("button",{className:"btn tiny",onClick:()=>{ll(S),_l(S)},children:"host key"}),!vl&&c.jsx("button",{className:"btn tiny danger",onClick:()=>{!W||!confirm(`Delete source "${W.name}"?`)||gl("del",W.id,async()=>{await Al.deleteSource(W.id),await O(),S===W.id&&await s("local")})},children:"delete"})]}),z?.error&&c.jsx(ht,{kind:"err",children:z.error}),z&&!z.error&&c.jsxs("dl",{className:"kv",children:[c.jsx("dt",{children:"reached by"}),c.jsx("dd",{className:"mono",children:z.endpoint}),z.dockerVersion&&c.jsxs(c.Fragment,{children:[c.jsx("dt",{children:"docker"}),c.jsx("dd",{children:z.dockerVersion})]})]}),Yl&&c.jsx("div",{className:"small faint",children:"Data is streamed through dockmv: source → this host → target. A local source moves it in one hop."})]}),T&&c.jsx(Ey,{initial:T,onClose:()=>w(null),onSaved:async el=>{w(null),await O(),await gl("select",el.id,()=>s(el.id))},onError:D}),H&&c.jsx(Gr,{info:H,onClose:()=>b(null),onTrust:dl})]})}function Ty(o){switch(o.kind){case"local":return`${o.name} (local docker)`;case"docker":return`${o.name} (${o.dockerHost})`;default:return`${o.name} (ssh ${o.ssh?.user}@${o.ssh?.host})`}}function Ey({initial:o,onClose:S,onSaved:z,onError:s}){const[O,D]=Q.useState(o),[T,w]=Q.useState(!1),H=O.kind??"ssh",b=O.ssh??{};function V(U,ll){D(W=>({...W,ssh:{...W.ssh,[U]:ll}}))}const E=H==="ssh"?!!b.host&&!!b.user:!!O.dockerHost;return c.jsx(Iu,{title:o.id?`Edit ${o.name}`:"New source host",onClose:S,footer:c.jsxs(c.Fragment,{children:[c.jsx("button",{className:"btn",onClick:S,children:"cancel"}),c.jsx("button",{className:"btn primary",disabled:T||!E,onClick:async()=>{w(!0);try{await z(await Al.saveSource(Ny(O,H)))}catch(U){s(U instanceof Error?U.message:String(U))}finally{w(!1)}},children:T?"saving…":"save"})]}),children:c.jsxs("div",{className:"stack",style:{gap:12},children:[c.jsxs("div",{className:"row",style:{gap:12},children:[c.jsx(Ll,{label:"label",children:c.jsx("input",{type:"text",value:O.name??"",onChange:U=>D(ll=>({...ll,name:U.target.value}))})}),c.jsx(Ll,{label:"reached by",children:c.jsxs("select",{value:H,onChange:U=>D(ll=>({...ll,kind:U.target.value})),children:[c.jsx("option",{value:"ssh",children:"ssh — remote host, driven through its docker CLI"}),c.jsx("option",{value:"docker",children:"docker address — a daemon this host can reach"})]})})]}),H==="docker"?c.jsxs(c.Fragment,{children:[c.jsx(Ll,{label:"docker address",children:c.jsx("input",{type:"text",placeholder:"tcp://10.0.0.5:2375",value:O.dockerHost??"",onChange:U=>D(ll=>({...ll,dockerHost:U.target.value}))})}),c.jsxs("div",{className:"small muted",children:["Any address the docker CLI accepts: ",c.jsx("span",{className:"mono",children:"tcp://host:2375"}),", or another socket with"," ",c.jsx("span",{className:"mono",children:"unix:///path/docker.sock"}),". A TLS-protected daemon uses the certificates from"," ",c.jsx("span",{className:"mono",children:"DOCKER_CERT_PATH"})," in dockmv's own environment."]}),c.jsxs(ht,{kind:"warn",children:["A plain ",c.jsx("span",{className:"mono",children:"tcp://"})," daemon is unauthenticated: anyone who can reach that port is root on that host. Prefer an ssh source unless the port is already protected."]})]}):c.jsxs(c.Fragment,{children:[c.jsx(Yr,{value:b,set:V,where:"source host"}),c.jsxs("div",{className:"small muted",children:["Needs ",c.jsx("span",{className:"mono",children:"sshd"})," and a docker CLI of 18.09 or newer on that host — the API is tunnelled through ",c.jsx("span",{className:"mono",children:"docker system dial-stdio"}),". Nothing is installed."]})]})]})})}function Ny(o,S){const z={id:o.id,name:o.name,kind:S};if(S==="docker")return z.dockerHost=o.dockerHost,z;const s=o.ssh??{};return z.ssh={host:s.host??"",port:s.port??22,user:s.user??"",auth:s.auth??"password",password:s.password,privateKey:s.privateKey,privateKeyPath:s.privateKeyPath,passphrase:s.passphrase,sudo:s.sudo??!1,dockerCmd:s.dockerCmd,saveSecrets:s.saveSecrets??!1},z}function Ay({source:o,plan:S,includedCount:z,options:s,setOptions:O,connections:D,activeConn:T,setActiveConn:w,reloadConnections:H,targetInv:b,connectTarget:V,onJobStarted:E,onError:U}){const[ll,W]=Q.useState(null),[vl,Yl]=Q.useState(null),[gl,_l]=Q.useState(null),[dl,el]=Q.useState(""),[Sl,Nl]=Q.useState(""),[C,Z]=Q.useState("tar"),B=D.find(M=>M.id===T),$=b?.preflight,sl=!!$?.serverVersion;async function jl(M,K){el(M);try{await K()}catch(nl){nl instanceof Fu&&nl.needsTrust?await tt():U(nl instanceof Error?nl.message:String(nl))}finally{el("")}}const at=Q.useCallback(()=>{T&&jl("test",()=>V(T))},[T]);Q.useEffect(()=>{at()},[at]);async function tt(){if(T)try{Yl(await Al.probe(T))}catch(M){U(M instanceof Error?M.message:String(M))}}async function Cl(){!T||!vl||await jl("trust",async()=>{await Al.trust(T,vl.fingerprint),Yl(null),await V(T)})}const j=z>0&&sl&&!dl,R=z>0&&!dl;return c.jsxs(c.Fragment,{children:[c.jsxs("div",{className:"section",children:[c.jsx("h3",{children:"target host"}),c.jsxs("div",{className:"stack",children:[c.jsxs("div",{className:"row",children:[c.jsxs("select",{value:T,onChange:M=>w(M.target.value),children:[c.jsx("option",{value:"",children:"— no target selected —"}),D.map(M=>c.jsxs("option",{value:M.id,children:[M.name," (",M.user,"@",M.host,")"]},M.id))]}),c.jsx("button",{className:"btn tiny",onClick:()=>W({port:22,auth:"password",saveSecrets:!1,sudo:!1}),children:"new"})]}),B&&c.jsxs("div",{className:"row wrap",style:{gap:6},children:[c.jsx("button",{className:"btn tiny",disabled:!!dl,onClick:at,children:dl==="test"?"connecting…":"connect"}),c.jsx("button",{className:"btn tiny",onClick:()=>W(B),children:"edit"}),c.jsx("button",{className:"btn tiny",onClick:tt,children:"host key"}),c.jsx("button",{className:"btn tiny danger",onClick:()=>{confirm(`Delete connection "${B.name}"?`)&&jl("del",async()=>{await Al.deleteConnection(B.id),H()})},children:"delete"})]}),B&&!b&&c.jsx("div",{className:"small faint",children:"not connected yet"}),$&&c.jsxs(c.Fragment,{children:[($.problems??[]).map((M,K)=>c.jsx(ht,{kind:"warn",children:M},K)),sl&&c.jsxs("dl",{className:"kv",children:[c.jsx("dt",{children:"host"}),c.jsx("dd",{children:b?.host||B?.host}),c.jsx("dt",{children:"docker"}),c.jsxs("dd",{children:[$.serverVersion," · ",$.os,"/",$.arch]}),c.jsx("dt",{children:"free space"}),c.jsxs("dd",{children:[ie($.diskFreeBytes)," on ",$.dockerRoot]}),c.jsx("dt",{children:"existing"}),c.jsxs("dd",{children:[(b?.containers??[]).length," containers ·"," ",(b?.volumes??[]).length," volumes"]}),c.jsx("dt",{children:"gzip"}),c.jsx("dd",{children:$.hasGzip?"yes":"missing"})]})]})]})]}),c.jsxs("div",{className:"section",children:[c.jsx("h3",{children:"options"}),c.jsxs("div",{className:"stack",children:[c.jsx(Ll,{label:"if the name already exists on the target",children:c.jsxs("select",{value:s.conflict,onChange:M=>O(K=>({...K,conflict:M.target.value})),children:[c.jsx("option",{value:"fail",children:"stop with an error"}),c.jsx("option",{value:"skip",children:"skip that container"}),c.jsx("option",{value:"rename",children:"create it under a new name"}),c.jsx("option",{value:"replace",children:"remove the target's container first"})]})}),s.conflict==="rename"&&c.jsx(Ll,{label:"suffix",children:c.jsx("input",{type:"text",value:s.renameSuffix??"",onChange:M=>O(K=>({...K,renameSuffix:M.target.value}))})}),s.conflict==="replace"&&c.jsx(ht,{kind:"warn",children:"Existing containers and volumes with the same name are deleted on the target before the copy."}),c.jsx(rt,{checked:s.compress,onChange:M=>O(K=>({...K,compress:M})),label:"compress transfers (gzip)"}),c.jsx(rt,{checked:s.verifyAfter,onChange:M=>O(K=>({...K,verifyAfter:M})),label:"verify each container after migrating"}),c.jsx(rt,{checked:s.dryRun,onChange:M=>O(K=>({...K,dryRun:M})),label:"dry run — show every command, change nothing"}),c.jsx(Ll,{label:`containers at a time: ${s.parallelism}`,children:c.jsx("input",{type:"range",min:1,max:6,value:s.parallelism,onChange:M=>O(K=>({...K,parallelism:Number(M.target.value)})),style:{width:"100%"}})})]})]}),c.jsxs("div",{className:"section",children:[c.jsx("h3",{children:"migrate over ssh"}),c.jsxs("div",{className:"stack",children:[c.jsx("button",{className:"btn primary",disabled:!j,onClick:()=>jl("ssh",async()=>{const M=await Al.migrateSSH(T,S);E(M)}),children:dl==="ssh"?"starting…":`migrate ${z} container${z===1?"":"s"} to target`}),c.jsx("button",{className:"btn",disabled:z===0||!!dl,onClick:()=>jl("preview",async()=>{_l(await Al.preview(S))}),children:"preview the commands"}),z===0&&c.jsx("div",{className:"small faint",children:"select at least one container"}),z>0&&!sl&&c.jsx("div",{className:"small faint",children:"connect to a target first"})]})]}),c.jsxs("div",{className:"section",children:[c.jsx("h3",{children:"migration package"}),c.jsxs("div",{className:"stack",children:[c.jsxs("div",{className:"small muted",children:["Builds a self-contained folder with the data, the images and an ",c.jsx("span",{className:"mono",children:"install.sh"})," to run on the target. No network between the hosts required."]}),c.jsx(Ll,{label:"package name",children:c.jsx("input",{type:"text",placeholder:"auto (timestamped)",value:Sl,onChange:M=>Nl(M.target.value)})}),c.jsx(Ll,{label:"format",children:c.jsxs("select",{value:C,onChange:M=>Z(M.target.value),children:[c.jsx("option",{value:"tar",children:"single .tar file (downloadable)"}),c.jsx("option",{value:"dir",children:"directory on this host"})]})}),c.jsx("button",{className:"btn",disabled:!R,onClick:()=>jl("pkg",async()=>{const M=await Al.buildPackage({...S,packageName:Sl},C);E(M)}),children:dl==="pkg"?"starting…":"build package"})]})]}),o?.inventory.warnings?.length?c.jsxs("div",{className:"section",children:[c.jsx("h3",{children:"source warnings"}),c.jsx("div",{className:"stack",children:o.inventory.warnings.map((M,K)=>c.jsx(ht,{kind:"warn",children:M},K))})]}):null,ll&&c.jsx(_y,{initial:ll,onClose:()=>W(null),onSaved:M=>{W(null),H(),w(M.id)},onError:U}),vl&&c.jsx(Gr,{info:vl,onClose:()=>Yl(null),onTrust:Cl}),gl&&c.jsx(Oy,{data:gl,onClose:()=>_l(null)})]})}function _y({initial:o,onClose:S,onSaved:z,onError:s}){const[O,D]=Q.useState(o),[T,w]=Q.useState(!1);function H(b,V){D(E=>({...E,[b]:V}))}return c.jsx(Iu,{title:o.id?`Edit ${o.name}`:"New target host",onClose:S,footer:c.jsxs(c.Fragment,{children:[c.jsx("button",{className:"btn",onClick:S,children:"cancel"}),c.jsx("button",{className:"btn primary",disabled:T||!O.host||!O.user,onClick:async()=>{w(!0);try{z(await Al.saveConnection(O))}catch(b){s(b instanceof Error?b.message:String(b))}finally{w(!1)}},children:T?"saving…":"save"})]}),children:c.jsxs("div",{className:"stack",style:{gap:12},children:[c.jsx(Ll,{label:"label",children:c.jsx("input",{type:"text",value:O.name??"",onChange:b=>H("name",b.target.value)})}),c.jsx(Yr,{value:O,set:H,where:"target"})]})})}function Oy({data:o,onClose:S}){const z=o.items.reduce((s,O)=>s+O.totalBytes,0);return c.jsx(Iu,{title:"What this migration will run",wide:!0,onClose:S,footer:c.jsx("button",{className:"btn",onClick:S,children:"close"}),children:c.jsxs("div",{className:"stack",style:{gap:16},children:[c.jsxs("div",{className:"small muted",children:[o.items.length," container(s)",z>0&&c.jsxs(c.Fragment,{children:[" · about ",ie(z)," of known volume data"]}),". These are the commands that run on the target; data is streamed into ",c.jsx("span",{className:"mono",children:"docker cp"})," rather than written to a file."]}),(o.networkCommands??[]).length>0&&c.jsxs("div",{children:[c.jsx("h3",{style:{margin:"0 0 6px",fontSize:12},children:"shared networks"}),c.jsx("pre",{className:"cmdblock",children:(o.networkCommands??[]).join(` +`)})]}),o.items.map(s=>c.jsxs("div",{children:[c.jsxs("h3",{style:{margin:"0 0 6px",fontSize:12},children:[s.name,s.targetName!==s.name&&c.jsxs("span",{className:"faint",children:[" → ",s.targetName]})]}),(s.warnings??[]).map((O,D)=>c.jsx(ht,{kind:"warn",children:O},`w${D}`)),(s.notes??[]).map((O,D)=>c.jsxs("div",{className:"small faint",children:["· ",O]},`n${D}`)),c.jsx("pre",{className:"cmdblock",children:(s.commands??[]).join(` +`)})]},s.containerId))]})})}function My({jobs:o,activeJob:S,setActiveJob:z,reload:s,reloadPackages:O}){const D=S||o[0]?.id||"";return o.length===0?c.jsx("div",{className:"empty",children:"no migrations yet — select containers and start one"}):c.jsxs("div",{style:{display:"flex",minHeight:0,height:"100%"},children:[c.jsx("div",{className:"joblist",style:{width:320,flex:"0 0 320px",overflow:"auto"},children:o.map(T=>c.jsxs("div",{className:`jobcard${T.id===D?" active":""}`,onClick:()=>z(T.id),children:[c.jsxs("div",{className:"row",children:[c.jsx(Un,{state:T.state}),c.jsx("span",{className:"spacer"}),c.jsx("span",{className:"small faint",children:T.kind==="ssh"?"ssh":"package"})]}),c.jsx("div",{className:"truncate",style:{marginTop:2},children:T.title}),c.jsxs("div",{className:"small faint",children:[new Date(T.createdAt).toLocaleTimeString()," · ",qr(T.startedAt,T.endedAt),T.dryRun&&" · dry run"]}),c.jsx("div",{style:{marginTop:6},children:c.jsx(zf,{done:T.bytesDone,total:T.bytesTotal,state:T.state})})]},T.id))}),c.jsx("div",{style:{flex:1,minWidth:0,overflow:"auto",borderLeft:"1px solid var(--border)"},children:D&&c.jsx(Dy,{id:D,reload:s,reloadPackages:O})})]})}function Dy({id:o,reload:S,reloadPackages:z}){const[s,O]=Q.useState(null),[D,T]=Q.useState(!0),w=Q.useRef(null),H=Q.useRef(!0);Q.useEffect(()=>{O(null);let E=!1;Al.job(o).then(ll=>!E&&O(ll)).catch(()=>{});const U=Al.jobEvents(o);return U.onmessage=ll=>{try{O(JSON.parse(ll.data))}catch{}},U.addEventListener("done",()=>{U.close(),S(),z()}),U.onerror=()=>U.close(),()=>{E=!0,U.close()}},[o,S,z]);const b=Q.useMemo(()=>(s?.log??[]).filter(E=>D||E.level!=="cmd"),[s,D]);if(Q.useEffect(()=>{const E=w.current;E&&H.current&&(E.scrollTop=E.scrollHeight)},[b]),!s)return c.jsx("div",{className:"empty",children:"loading…"});const V=s.state==="running"||s.state==="pending";return c.jsxs("div",{style:{padding:16,display:"flex",flexDirection:"column",gap:14},children:[c.jsxs("div",{className:"row",children:[c.jsx(Un,{state:s.state}),c.jsx("b",{children:s.title}),s.dryRun&&c.jsx("span",{className:"badge",children:"dry run"}),c.jsx("span",{className:"spacer"}),c.jsxs("span",{className:"small faint",children:[ie(s.bytesDone),s.bytesTotal>0&&c.jsxs(c.Fragment,{children:[" of ",ie(s.bytesTotal)]})," · ",qr(s.startedAt,s.endedAt)]}),V?c.jsx("button",{className:"btn tiny danger",onClick:()=>Al.cancelJob(s.id).then(S),children:"cancel"}):c.jsx("button",{className:"btn tiny ghost",onClick:()=>Al.deleteJob(s.id).then(S),children:"remove"})]}),c.jsx(zf,{done:s.bytesDone,total:s.bytesTotal,state:s.state}),s.error&&c.jsx(ht,{kind:"err",children:s.error}),s.state==="succeeded"&&s.artifact&&c.jsxs(ht,{kind:"ok",children:["Package ready at ",c.jsx("span",{className:"mono",children:s.artifact})," (",ie(s.artifactBytes??0),")."," ","Open the Packages tab to download it."]}),s.items.map(E=>c.jsxs("div",{style:{border:"1px solid var(--border)",borderRadius:6,padding:"8px 10px"},children:[c.jsxs("div",{className:"row",children:[c.jsx(Un,{state:E.state}),c.jsx("b",{children:E.name}),c.jsx("span",{className:"spacer"}),c.jsxs("span",{className:"small faint",children:[E.steps.filter(U=>U.state==="succeeded").length,"/",E.steps.length," steps"]})]}),E.error&&c.jsx("div",{className:"small",style:{color:"var(--err)"},children:E.error}),(E.warnings??[]).map((U,ll)=>c.jsxs("div",{className:"small",style:{color:"var(--warn)"},children:["! ",U]},ll)),c.jsx("div",{className:"steps",children:E.steps.map(U=>c.jsxs("div",{className:`step ${U.state}`,children:[c.jsx(Un,{state:U.state,label:""}),c.jsx("span",{className:"label truncate",title:U.error||U.label,children:U.label}),c.jsx("span",{children:U.bytesTotal>0||U.bytesDone>0?c.jsx(zf,{done:U.bytesDone,total:U.bytesTotal,state:U.state}):null}),c.jsx("span",{className:"faint nowrap",style:{textAlign:"right"},children:U.bytesDone>0?ie(U.bytesDone):U.state==="skipped"?"skipped":""})]},U.id))})]},E.id)),c.jsxs("div",{className:"row",children:[c.jsx("h3",{style:{margin:0,fontSize:12},children:"log"}),c.jsx("span",{className:"spacer"}),c.jsxs("label",{className:"check small",children:[c.jsx("input",{type:"checkbox",checked:D,onChange:E=>T(E.target.checked)}),c.jsx("span",{children:"show commands"})]})]}),c.jsxs("div",{className:"log",ref:w,onScroll:E=>{const U=E.currentTarget;H.current=U.scrollHeight-U.scrollTop-U.clientHeight<24},children:[b.map(E=>c.jsxs("div",{className:`l-${E.level}`,children:[c.jsxs("span",{className:"ts",children:[new Date(E.at).toLocaleTimeString()," "]}),E.message]},E.seq)),b.length===0&&c.jsx("span",{className:"faint",children:"nothing logged yet"})]})]})}function Cy({packages:o,reload:S}){return o.length===0?c.jsx("div",{className:"empty",children:"no packages built yet"}):c.jsxs("div",{style:{padding:16,display:"flex",flexDirection:"column",gap:12},children:[c.jsxs(ht,{kind:"info",children:["Copy a package to the target host, then run ",c.jsx("span",{className:"mono",children:"./install.sh --dry-run"})," to review it and"," ",c.jsx("span",{className:"mono",children:"./install.sh"})," to restore. The target needs only bash, gzip and docker."]}),c.jsxs("table",{className:"mount-table",children:[c.jsx("thead",{children:c.jsxs("tr",{children:[c.jsx("th",{children:"name"}),c.jsx("th",{style:{width:110},children:"kind"}),c.jsx("th",{style:{width:110,textAlign:"right"},children:"size"}),c.jsx("th",{style:{width:170},children:"built"}),c.jsx("th",{style:{width:190}})]})}),c.jsx("tbody",{children:o.map(z=>c.jsxs("tr",{children:[c.jsx("td",{className:"mono truncate",title:z.path,children:z.name}),c.jsx("td",{children:c.jsx("span",{className:"badge",children:z.isDir?"directory":"tar"})}),c.jsx("td",{className:"nowrap",style:{textAlign:"right"},children:ie(z.bytes)}),c.jsx("td",{className:"small faint",children:new Date(z.createdAt).toLocaleString()}),c.jsx("td",{children:c.jsxs("div",{className:"row",style:{justifyContent:"flex-end",gap:6},children:[z.isDir?c.jsx("span",{className:"small faint",title:z.path,children:"copy it from disk"}):c.jsx("a",{className:"btn tiny",href:Al.downloadUrl(z.name),download:!0,children:"download"}),c.jsx("button",{className:"btn tiny danger",onClick:()=>{confirm(`Delete package "${z.name}"? This cannot be undone.`)&&Al.deletePackage(z.name).then(S)},children:"delete"})]})})]},z.name))})]})]})}function Uy(){const[o,S]=Q.useState(null),[z,s]=Q.useState(null),[O,D]=Q.useState({}),[T,w]=Q.useState({conflict:"fail",renameSuffix:"-migrated",compress:!0,compressLevel:1,dryRun:!1,parallelism:1,verifyAfter:!0}),[H,b]=Q.useState({}),[V,E]=Q.useState([]),[U,ll]=Q.useState("local"),[W,vl]=Q.useState(null),[Yl,gl]=Q.useState([]),[_l,dl]=Q.useState(""),[el,Sl]=Q.useState(null),[Nl,C]=Q.useState([]),[Z,B]=Q.useState([]),[$,sl]=Q.useState("containers"),[jl,at]=Q.useState(""),[tt,Cl]=Q.useState(""),[j,R]=Q.useState(!0),M=Q.useCallback(async()=>{R(!0);try{const k=await Al.source();s(k),D(Jl=>{const ce={};for(const Ie of k.inventory.containers)ce[Ie.id]=Jl[Ie.id]??k.defaults[Ie.id];return ce}),Cl(""),Al.volumeSizes().then(Jl=>b(Jl.volumes??{})).catch(()=>{})}catch(k){Cl(k instanceof Error?k.message:String(k))}finally{R(!1)}},[]),K=Q.useCallback(async()=>{try{const k=await Al.health();S(k),k.source&&vl(k.source)}catch{}},[]),nl=Q.useCallback(async()=>{try{const k=await Al.sources();E(k.sources),ll(k.selected),k.current&&vl(k.current)}catch(k){Cl(k instanceof Error?k.message:String(k))}},[]),h=Q.useCallback(async k=>{const Jl=await Al.selectSource(k);ll(k),vl(Jl),D({}),b({}),Cl(""),await M(),await K()},[M,K]),A=Q.useCallback(async()=>{try{const k=await Al.connections();gl(k),dl(Jl=>Jl&&k.some(ce=>ce.id===Jl)?Jl:k[0]?.id??"")}catch(k){Cl(k instanceof Error?k.message:String(k))}},[]),q=Q.useCallback(async()=>{try{C(await Al.jobs())}catch{}},[]),Y=Q.useCallback(async()=>{try{B(await Al.packages())}catch{}},[]);Q.useEffect(()=>{K(),nl(),M(),A(),q(),Y()},[K,nl,M,A,q,Y]),Q.useEffect(()=>{const k=setInterval(q,4e3);return()=>clearInterval(k)},[q]);const F=Q.useCallback(async k=>{if(Sl(null),!k)return;const Jl=await Al.targetInventory(k);Sl(Jl),Cl("")},[]),al=Q.useMemo(()=>Object.values(O).filter(k=>k.include),[O]),rl=Q.useMemo(()=>({items:Object.values(O),options:T}),[O,T]),Kl=Nl.filter(k=>k.state==="running"||k.state==="pending").length,Ul=Q.useCallback(k=>{C(Jl=>[k,...Jl]),at(k.id),sl("jobs")},[]);return c.jsxs("div",{className:"app",children:[c.jsxs("header",{className:"topbar",children:[c.jsxs("div",{className:"brand",children:[c.jsx("img",{src:"/logo-icon.png",alt:"",className:"brand-logo"}),"DockMV"]}),c.jsxs("nav",{className:"tabs",children:[c.jsxs("button",{className:`tab${$==="containers"?" active":""}`,onClick:()=>sl("containers"),children:["Containers",c.jsxs("span",{className:"count",children:[al.length,"/",z?.inventory.containers.length??0]})]}),c.jsxs("button",{className:`tab${$==="jobs"?" active":""}`,onClick:()=>sl("jobs"),children:["Jobs",Kl>0&&c.jsxs("span",{className:"count",children:[Kl," running"]})]}),c.jsxs("button",{className:`tab${$==="packages"?" active":""}`,onClick:()=>sl("packages"),children:["Packages",Z.length>0&&c.jsx("span",{className:"count",children:Z.length})]})]}),c.jsxs("div",{className:"topbar-right",children:[o&&c.jsxs("span",{className:"hostinfo",children:["source ",c.jsx("b",{children:z?.inventory.host||W?.name||o.dockerHost}),W?.kind==="ssh"&&c.jsx(c.Fragment,{children:" · over ssh"}),W?.kind==="docker"&&c.jsxs(c.Fragment,{children:[" · ",W.endpoint]}),o.dockerVersion&&c.jsxs(c.Fragment,{children:[" · docker ",o.dockerVersion]})]}),c.jsx("button",{className:"btn tiny",onClick:M,disabled:j,children:j?"loading…":"refresh"})]})]}),tt&&c.jsx("div",{style:{padding:"10px 16px"},children:c.jsxs(ht,{kind:"err",children:[tt,c.jsx("button",{className:"btn tiny ghost",style:{marginLeft:8},onClick:()=>Cl(""),children:"dismiss"})]})}),o&&!o.ok&&c.jsx("div",{style:{padding:"10px 16px"},children:c.jsxs(ht,{kind:"err",children:["Cannot reach the source ",c.jsx("b",{children:o.source?.name??"docker daemon"}),o.dockerHost&&c.jsxs(c.Fragment,{children:[" at ",c.jsx("span",{className:"mono",children:o.dockerHost})]}),o.dockerError&&c.jsxs(c.Fragment,{children:[" — ",o.dockerError]}),c.jsx("div",{className:"small",children:"Pick another source in the panel on the right."})]})}),c.jsxs("div",{className:"body",children:[c.jsxs("main",{className:"main",children:[$==="containers"&&c.jsx(py,{source:z,sel:O,setSel:D,targetInv:el,loading:j,sizes:H}),$==="jobs"&&c.jsx(My,{jobs:Nl,activeJob:jl,setActiveJob:at,reload:q,reloadPackages:Y}),$==="packages"&&c.jsx(Cy,{packages:Z,reload:Y})]}),$==="containers"&&c.jsxs("aside",{className:"sidebar",children:[c.jsx(zy,{sources:V,selected:U,status:W,selectSource:h,reload:nl,onError:Cl}),c.jsx(Ay,{source:z,plan:rl,includedCount:al.length,options:T,setOptions:w,connections:Yl,activeConn:_l,setActiveConn:dl,reloadConnections:A,targetInv:el,connectTarget:F,onJobStarted:Ul,onError:Cl})]})]})]})}Sy.createRoot(document.getElementById("root")).render(c.jsx(Q.StrictMode,{children:c.jsx(Uy,{})})); diff --git a/internal/webui/dist/index.html b/internal/webui/dist/index.html index bc24fd4..95445e5 100644 --- a/internal/webui/dist/index.html +++ b/internal/webui/dist/index.html @@ -6,7 +6,7 @@ DockMV - + diff --git a/main.go b/main.go index 439eba4..0b1289e 100644 --- a/main.go +++ b/main.go @@ -84,7 +84,7 @@ func serve(args []string) error { token := fs.String("token", os.Getenv("DOCKMV_TOKEN"), "require this token on every request; \"auto\" generates one (default: $DOCKMV_TOKEN)") dataDir := fs.String("data-dir", defaultDataDir(), "directory for connections and trusted host keys") pkgDir := fs.String("package-dir", "", "directory for migration packages (default /packages)") - dockerHost := fs.String("docker-host", "", "source docker daemon (default: the DOCKER_HOST environment)") + dockerHost := fs.String("docker-host", "", "local source docker daemon (default: the DOCKER_HOST environment)") verbose := fs.Bool("v", false, "verbose logging") fs.Usage = func() { fmt.Fprintln(os.Stderr, "Usage: dockmv serve [flags]\n\nFlags:") @@ -93,6 +93,14 @@ func serve(args []string) error { if err := fs.Parse(args); err != nil { return err } + // A --docker-host given on the command line is an instruction for this run, + // and takes precedence over the source remembered from the last one. + dockerHostSet := false + fs.Visit(func(f *flag.Flag) { + if f.Name == "docker-host" { + dockerHostSet = true + } + }) level := slog.LevelInfo if *verbose { @@ -113,13 +121,14 @@ func serve(args []string) error { } cfg := api.Config{ - Addr: *addr, - Token: authToken, - DataDir: *dataDir, - PackageDir: *pkgDir, - DockerHost: *dockerHost, - UI: webui.FS(), - Logger: logger, + Addr: *addr, + Token: authToken, + DataDir: *dataDir, + PackageDir: *pkgDir, + DockerHost: *dockerHost, + DockerHostSet: dockerHostSet, + UI: webui.FS(), + Logger: logger, } srv, err := api.New(cfg) if err != nil { diff --git a/web/src/App.tsx b/web/src/App.tsx index 00cbab1..bc7f573 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,10 +1,12 @@ import { useCallback, useEffect, useMemo, useState } from 'react' import { api } from './api' import type { - Connection, Health, ItemSelection, JobSnapshot, Options, PackageInfo, Plan, SourceResponse, TargetInventory, + Connection, Health, ItemSelection, JobSnapshot, Options, PackageInfo, Plan, Source, SourceResponse, + SourceStatus, TargetInventory, } from './types' import { Notice } from './ui' import { Containers } from './Containers' +import { SourcePanel } from './SourcePanel' import { Sidebar } from './Sidebar' import { Jobs } from './Jobs' import { Packages } from './Packages' @@ -22,6 +24,9 @@ export default function App() { // Volume sizes come from a separate, slower endpoint so the container list // can render immediately; they are merged into the rows when they arrive. const [sizes, setSizes] = useState>({}) + const [sources, setSources] = useState([]) + const [selectedSource, setSelectedSource] = useState('local') + const [sourceStatus, setSourceStatus] = useState(null) const [connections, setConnections] = useState([]) const [activeConn, setActiveConn] = useState('') const [targetInv, setTargetInv] = useState(null) @@ -57,6 +62,40 @@ export default function App() { } }, []) + const loadHealth = useCallback(async () => { + try { + const h = await api.health() + setHealth(h) + if (h.source) setSourceStatus(h.source) + } catch { /* the panel shows the source error on its own */ } + }, []) + + const loadSources = useCallback(async () => { + try { + const r = await api.sources() + setSources(r.sources) + setSelectedSource(r.selected) + if (r.current) setSourceStatus(r.current) + } catch (e) { + setError(e instanceof Error ? e.message : String(e)) + } + }, []) + + // selectSource lets its error escape so the panel can turn an untrusted host + // key into a fingerprint prompt, the same way the target does. + const selectSource = useCallback(async (id: string) => { + const st = await api.selectSource(id) + setSelectedSource(id) + setSourceStatus(st) + // The selections describe containers on the host that was selected before, + // so they are dropped rather than carried over to a different inventory. + setSel({}) + setSizes({}) + setError('') + await loadSource() + await loadHealth() + }, [loadSource, loadHealth]) + const loadConnections = useCallback(async () => { try { const list = await api.connections() @@ -80,12 +119,13 @@ export default function App() { }, []) useEffect(() => { - api.health().then(setHealth).catch(() => undefined) + loadHealth() + loadSources() loadSource() loadConnections() loadJobs() loadPackages() - }, [loadSource, loadConnections, loadJobs, loadPackages]) + }, [loadHealth, loadSources, loadSource, loadConnections, loadJobs, loadPackages]) // A slow poll keeps the job list current without holding a stream open for // every job; the detail view subscribes to its own live stream. @@ -138,7 +178,9 @@ export default function App() {
{health && ( - source {source?.inventory.host || health.dockerHost} + source {source?.inventory.host || sourceStatus?.name || health.dockerHost} + {sourceStatus?.kind === 'ssh' && <> · over ssh} + {sourceStatus?.kind === 'docker' && <> · {sourceStatus.endpoint}} {health.dockerVersion && <> · docker {health.dockerVersion}} )} @@ -160,8 +202,10 @@ export default function App() { {health && !health.ok && (
- Cannot reach the source Docker daemon at {health.dockerHost} + Cannot reach the source {health.source?.name ?? 'docker daemon'} + {health.dockerHost && <> at {health.dockerHost}} {health.dockerError && <> — {health.dockerError}} +
Pick another source in the panel on the right.
)} @@ -192,6 +236,14 @@ export default function App() { {view === 'containers' && (