Add upload progress bar with SSE extraction status
build-and-push / test (push) Successful in 40s
build-and-push / build-and-push (push) Successful in 1m8s

ISO uploads now show a progress bar during file transfer (via XHR
upload.onprogress) and real-time extraction status (via SSE events
through the existing Hub). Falls back to plain form POST if JS is
disabled.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-10 19:16:19 -04:00
parent 4774600040
commit 443a3db9e1
6 changed files with 200 additions and 22 deletions
+38 -8
View File
@@ -167,34 +167,64 @@ func (u *UI) NewImageForm(w http.ResponseWriter, r *http.Request) {
}
func (u *UI) UploadImage(w http.ResponseWriter, r *http.Request) {
isXHR := r.Header.Get("X-Requested-With") == "XMLHttpRequest"
if err := r.ParseMultipartForm(0); err != nil {
renderHTML(w, imageUploadForm("Invalid form submission"))
if isXHR {
writeJSON(w, http.StatusBadRequest, map[string]any{"ok": false, "error": "Invalid form submission"})
} else {
renderHTML(w, imageUploadForm("Invalid form submission"))
}
return
}
name := strings.TrimSpace(r.FormValue("name"))
version := strings.TrimSpace(r.FormValue("version"))
kind := strings.TrimSpace(r.FormValue("kind"))
uploadID := strings.TrimSpace(r.FormValue("upload_id"))
file, _, err := r.FormFile("iso")
if err != nil {
renderHTML(w, imageUploadForm("ISO file is required"))
if isXHR {
writeJSON(w, http.StatusBadRequest, map[string]any{"ok": false, "error": "ISO file is required"})
} else {
renderHTML(w, imageUploadForm("ISO file is required"))
}
return
}
defer file.Close()
var progressFn image.ProgressFunc
if uploadID != "" {
progressFn = func(stage, detail string) {
u.Hub.Publish(events.Event{
Name: "image.upload_progress",
Payload: fmt.Sprintf(`{"upload_id":%q,"stage":%q,"detail":%q}`, uploadID, stage, detail),
})
}
}
_, err = u.ImageSvc.Upload(r.Context(), image.UploadParams{
Name: name,
Kind: kind,
Version: version,
ISO: file,
Name: name,
Kind: kind,
Version: version,
ISO: file,
OnProgress: progressFn,
})
if err != nil {
renderHTML(w, imageUploadForm(err.Error()))
if isXHR {
writeJSON(w, http.StatusInternalServerError, map[string]any{"ok": false, "error": err.Error()})
} else {
renderHTML(w, imageUploadForm(err.Error()))
}
return
}
http.Redirect(w, r, "/images", http.StatusSeeOther)
if isXHR {
writeJSON(w, http.StatusOK, map[string]any{"ok": true})
} else {
http.Redirect(w, r, "/images", http.StatusSeeOther)
}
}
func (u *UI) SetDefaultImage(w http.ResponseWriter, r *http.Request) {