runs: add non-destructive flag + operator Cancel button
Non-destructive pre-declares "don't touch the disks" on Start: the Storage stage skips wipe-probe, badblocks -w, and write-mode fio, and reports a read-only summary. Runs a new non_destructive column; threaded through Claim → agent tests.Deps → Storage stage. Cancel halts an in-flight run. The orchestrator transitions to a new StateCancelled via TriggerOperatorCancelled (valid from any active state); the agent's next heartbeat returns cmd=cancel_stage, which fires a stored CancelFunc on the per-stage context. Stage subprocesses spawned with exec.CommandContext die with the context, the agent posts a cancelled outcome, then powers the host off. Destructive stages mid-run may leave the host in an intermediate state — the UI confirm dialog warns the operator; recovery is manual for now. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -29,11 +29,19 @@ templ HostTile(t TileData) {
|
||||
</header>
|
||||
<div class="tile-primary-action">
|
||||
if canStart(t) {
|
||||
<form method="post" action={ templ.SafeURL(fmt.Sprintf("/hosts/%d/start", t.Host.ID)) } class="inline">
|
||||
<form method="post" action={ templ.SafeURL(fmt.Sprintf("/hosts/%d/start", t.Host.ID)) } class="inline tile-start-form">
|
||||
<label class="tile-nd-toggle">
|
||||
<input type="checkbox" name="non_destructive" value="1"/>
|
||||
Non-destructive
|
||||
</label>
|
||||
<button type="submit">Start vetting</button>
|
||||
</form>
|
||||
} else if canStartIfOnline(t.Latest) {
|
||||
<button type="button" disabled title="host is not heartbeating — install the reporter via /register/quick.sh on the target host">Start vetting</button>
|
||||
} else if canCancel(t.Latest) {
|
||||
<form method="post" action={ templ.SafeURL(fmt.Sprintf("/hosts/%d/cancel", t.Host.ID)) } class="inline tile-cancel-form" onsubmit="return confirm('Cancel run? Destructive stages may leave the host in an intermediate state requiring manual cleanup.');">
|
||||
<button type="submit" class="danger">Cancel run</button>
|
||||
</form>
|
||||
} else if hasReport(t.Latest) {
|
||||
<a class="button-like" href={ templ.SafeURL(fmt.Sprintf("/reports/%d", t.Latest.ID)) } target="_blank" rel="noopener">View report</a>
|
||||
}
|
||||
@@ -76,11 +84,15 @@ func canStartIfOnline(r *model.Run) bool {
|
||||
if r == nil {
|
||||
return true
|
||||
}
|
||||
switch r.State {
|
||||
case model.StateCompleted, model.StateReleased, model.StateFailed, model.StateFailedHolding:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return r.State.IsTerminal()
|
||||
}
|
||||
|
||||
// canCancel is true for any non-terminal run — the Cancel button shows
|
||||
// whenever the pipeline is live (Queued through the stage states). The
|
||||
// handler refuses the action once the run enters a terminal state, so
|
||||
// the render decision just has to mirror that.
|
||||
func canCancel(r *model.Run) bool {
|
||||
return r != nil && !r.State.IsTerminal()
|
||||
}
|
||||
|
||||
func tileStatus(r *model.Run) string {
|
||||
@@ -103,7 +115,7 @@ func tileMood(r *model.Run) string {
|
||||
return "pass"
|
||||
case model.StateFailed, model.StateFailedHolding:
|
||||
return "fail"
|
||||
case model.StateReleased:
|
||||
case model.StateReleased, model.StateCancelled:
|
||||
return "idle"
|
||||
}
|
||||
return "active"
|
||||
|
||||
Reference in New Issue
Block a user