feat(webui): add POST /api/sat/abort + update bible-local runtime-flows
- jobState now has optional cancel func; abort() calls it if job is running - handleAPISATRun passes cancellable context to RunNvidiaAcceptancePackWithOptions - POST /api/sat/abort?job_id=... cancels the running SAT job - bible-local/runtime-flows.md: replace TUI SAT flow with Web UI flow Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -155,8 +155,11 @@ func (h *handler) handleAPISATRun(target string) http.HandlerFunc {
|
||||
}
|
||||
id := newJobID("sat-" + target)
|
||||
j := globalJobs.create(id)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
j.cancel = cancel
|
||||
|
||||
go func() {
|
||||
defer cancel()
|
||||
j.append(fmt.Sprintf("Starting %s acceptance test...", target))
|
||||
var (
|
||||
archive string
|
||||
@@ -178,7 +181,7 @@ func (h *handler) handleAPISATRun(target string) http.HandlerFunc {
|
||||
case "nvidia":
|
||||
if len(body.GPUIndices) > 0 || body.DiagLevel > 0 {
|
||||
result, e := h.opts.App.RunNvidiaAcceptancePackWithOptions(
|
||||
context.Background(), "", body.DiagLevel, body.GPUIndices,
|
||||
ctx, "", body.DiagLevel, body.GPUIndices,
|
||||
)
|
||||
if e != nil {
|
||||
err = e
|
||||
@@ -201,8 +204,13 @@ func (h *handler) handleAPISATRun(target string) http.HandlerFunc {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
j.append("ERROR: " + err.Error())
|
||||
j.finish(err.Error())
|
||||
if ctx.Err() != nil {
|
||||
j.append("Aborted.")
|
||||
j.finish("aborted")
|
||||
} else {
|
||||
j.append("ERROR: " + err.Error())
|
||||
j.finish(err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
j.append(fmt.Sprintf("Archive written: %s", archive))
|
||||
@@ -223,6 +231,20 @@ func (h *handler) handleAPISATStream(w http.ResponseWriter, r *http.Request) {
|
||||
streamJob(w, r, j)
|
||||
}
|
||||
|
||||
func (h *handler) handleAPISATAbort(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.URL.Query().Get("job_id")
|
||||
j, ok := globalJobs.get(id)
|
||||
if !ok {
|
||||
http.Error(w, "job not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if j.abort() {
|
||||
writeJSON(w, map[string]string{"status": "aborted"})
|
||||
} else {
|
||||
writeJSON(w, map[string]string{"status": "not_running"})
|
||||
}
|
||||
}
|
||||
|
||||
// ── Services ──────────────────────────────────────────────────────────────────
|
||||
|
||||
func (h *handler) handleAPIServicesList(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -7,12 +7,23 @@ import (
|
||||
|
||||
// jobState holds the output lines and completion status of an async job.
|
||||
type jobState struct {
|
||||
lines []string
|
||||
done bool
|
||||
err string
|
||||
mu sync.Mutex
|
||||
// subs is a list of channels that receive new lines as they arrive.
|
||||
subs []chan string
|
||||
lines []string
|
||||
done bool
|
||||
err string
|
||||
mu sync.Mutex
|
||||
subs []chan string
|
||||
cancel func() // optional cancel function; nil if job is not cancellable
|
||||
}
|
||||
|
||||
// abort cancels the job if it has a cancel function and is not yet done.
|
||||
func (j *jobState) abort() bool {
|
||||
j.mu.Lock()
|
||||
defer j.mu.Unlock()
|
||||
if j.done || j.cancel == nil {
|
||||
return false
|
||||
}
|
||||
j.cancel()
|
||||
return true
|
||||
}
|
||||
|
||||
func (j *jobState) append(line string) {
|
||||
|
||||
@@ -132,6 +132,7 @@ func NewHandler(opts HandlerOptions) http.Handler {
|
||||
mux.HandleFunc("POST /api/sat/storage/run", h.handleAPISATRun("storage"))
|
||||
mux.HandleFunc("POST /api/sat/cpu/run", h.handleAPISATRun("cpu"))
|
||||
mux.HandleFunc("GET /api/sat/stream", h.handleAPISATStream)
|
||||
mux.HandleFunc("POST /api/sat/abort", h.handleAPISATAbort)
|
||||
|
||||
// Services
|
||||
mux.HandleFunc("GET /api/services", h.handleAPIServicesList)
|
||||
|
||||
Reference in New Issue
Block a user