fix: replace access list ID field with EntityPicker, add deploy toggle, improve UX

- Replace raw NPM access list ID input with EntityPicker on project edit form
- Resolve access list name from NPM API when editing project
- Add "Deploy immediately" toggle to Quick Deploy (off by default)
- Fix stage form layout: all fields on same row with toggles
- Fix empty port default on project creation (placeholder instead of pre-filled)
- Improve inspect error message when Docker is unavailable
- Trigger proxy resync when NPM access list changes
- Resolve access list name on NPM settings page load
This commit is contained in:
2026-04-05 13:07:09 +03:00
parent feec97fe9e
commit a830378c5b
8 changed files with 134 additions and 37 deletions
+28 -9
View File
@@ -81,7 +81,13 @@ func (s *Server) inspectImage(w http.ResponseWriter, r *http.Request) {
info, err := s.docker.InspectImage(ctx, req.Image)
if err != nil {
slog.Error("failed to inspect image", "image", req.Image, "error", err)
respondError(w, http.StatusInternalServerError, "internal server error")
errMsg := "Failed to inspect image. "
if strings.Contains(err.Error(), "docker_engine") || strings.Contains(err.Error(), "docker.sock") {
errMsg += "Docker is not available on this machine. Enter port and project name manually."
} else {
errMsg += "Image may not exist or registry requires authentication."
}
respondError(w, http.StatusBadGateway, errMsg)
return
}
@@ -103,6 +109,7 @@ type quickDeployRequest struct {
Port int `json:"port"`
Force bool `json:"force"` // skip duplicate check
EnableProxy *bool `json:"enable_proxy"` // nil defaults to true
AutoDeploy *bool `json:"auto_deploy"` // nil defaults to true (deploy immediately)
}
// quickDeploy handles POST /api/deploy/quick.
@@ -172,11 +179,15 @@ func (s *Server) quickDeploy(w http.ResponseWriter, r *http.Request) {
if req.EnableProxy != nil {
enableProxy = *req.EnableProxy
}
shouldDeploy := true
if req.AutoDeploy != nil {
shouldDeploy = *req.AutoDeploy
}
stage, err := s.store.CreateStage(store.Stage{
ProjectID: project.ID,
Name: "dev",
TagPattern: "*",
AutoDeploy: true,
AutoDeploy: shouldDeploy,
MaxInstances: 1,
EnableProxy: enableProxy,
})
@@ -186,12 +197,20 @@ func (s *Server) quickDeploy(w http.ResponseWriter, r *http.Request) {
return
}
// Trigger deploy asynchronously.
deployID, err := s.deployer.AsyncTriggerDeploy(r.Context(), project.ID, stage.ID, req.Tag)
if err != nil {
slog.Error("failed to trigger deploy", "error", err)
respondError(w, http.StatusInternalServerError, "internal server error")
return
// Only trigger deploy if auto_deploy is enabled.
var deployID string
if shouldDeploy {
deployID, err = s.deployer.AsyncTriggerDeploy(r.Context(), project.ID, stage.ID, req.Tag)
if err != nil {
slog.Error("failed to trigger deploy", "error", err)
respondError(w, http.StatusInternalServerError, "internal server error")
return
}
}
status := "created"
if shouldDeploy {
status = "deploying"
}
respondJSON(w, http.StatusAccepted, map[string]any{
@@ -199,7 +218,7 @@ func (s *Server) quickDeploy(w http.ResponseWriter, r *http.Request) {
"stage": stage,
"tag": req.Tag,
"deploy_id": deployID,
"status": "deploying",
"status": status,
})
}
+1
View File
@@ -239,6 +239,7 @@ func (s *Server) updateSettings(w http.ResponseWriter, r *http.Request) {
proxyChanged := existing.Domain != updated.Domain ||
existing.ProxyProvider != updated.ProxyProvider ||
existing.NpmRemote != updated.NpmRemote ||
existing.NpmAccessListID != updated.NpmAccessListID ||
sslChanged
if proxyChanged {
go s.resyncAllProxies(existing, updated)