fix: polish service runtime UX and harden reconcile edge cases

Backend:
- tolerate concurrent project-network creation (re-resolve on conflict)
- always publish an ephemeral host port when a port is configured, even
  with a domain set — Traefik routes on top when the edge network exists
- public_url now prefers the configured domain, falls back to the first
  published port
- service logs resolve replicas by containr.service label and merge all
  replica streams — previously queried a nonexistent containr-<id> name

Canvas/detail:
- degraded status renders amber end-to-end (node card, detail badge,
  status dot) instead of falling through to "Stopped"
- edges pick handle sides by relative node position — no more backward
  loops when the source sits right of the target
- context menu gains Start for stopped services, Visit uses the
  published URL when no domain is set, menu clamps to canvas bounds
- node cards show the image tag instead of a redundant hint
- scaling tab falls back to live runtime replica counts when no
  autoscaler state exists; manual scale no longer requires it

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-21 20:29:31 +02:00
1 parent 04c3b99531
commit b7e2e93261
9 files changed
+193 -69

No files matched your search

+12 -4
View File
@@ -97,6 +97,15 @@ func (de *DeploymentEngine) EnsureProjectNetwork(ctx context.Context, projectID
},
})
if err != nil {
// Concurrent reconciles race on create; re-resolve before failing.
networks, lerr := de.dockerClient.ListNetworks(ctx)
if lerr == nil {
for _, n := range networks {
if n.Name == name {
return name, nil
}
}
}
return "", fmt.Errorf("create project network: %w", err)
}
return name, nil
@@ -231,10 +240,9 @@ func (de *DeploymentEngine) createReplica(ctx context.Context, spec RuntimeSpec,
if spec.Port > 0 {
port := nat.Port(fmt.Sprintf("%d/tcp", spec.Port))
cfg.ExposedPorts = nat.PortSet{port: struct{}{}}
if spec.Domain == "" {
// No edge router: publish an ephemeral host port for direct access.
cfg.PortBindings = nat.PortMap{port: []nat.PortBinding{{HostIP: "0.0.0.0", HostPort: ""}}}
}
// Always publish an ephemeral host port for direct access; a configured
// domain is additionally routed via Traefik when the edge network exists.
cfg.PortBindings = nat.PortMap{port: []nat.PortBinding{{HostIP: "0.0.0.0", HostPort: ""}}}
if spec.HealthPath != "" {
cfg.Healthcheck = &container.HealthConfig{
Test: []string{"CMD-SHELL", fmt.Sprintf("wget -q -O /dev/null http://127.0.0.1:%d%s || exit 1", spec.Port, spec.HealthPath)},