package templates import ( "bytes" "context" "fmt" "time" "vetting/internal/model" "vetting/internal/store" ) // RunPageData is the full payload for /runs/{runID}. Host is resolved // from Run.HostID so the breadcrumb + run actions (which post to // /hosts/{hostID}/...) have the host context without a separate call. // Stages/SubSteps/SpecDiffs drive the pipeline + active-step panels + // diff list. DefaultStepStage is the stage name whose
opens // on first render — running → failed → Reporting. HoldKeyPath is the // on-disk path of the hold_key artifact, needed to print the ssh // invocation in the hold banner. SpecDiffCritical is the count of // unignored critical diffs shown in the header. type RunPageData struct { Host model.Host Run model.Run Stages []model.Stage SubSteps []model.SubStep SpecDiffs []model.SpecDiff DefaultStepStage string LogReplayByStage map[string]string HoldKeyPath string SpecDiffCritical int } // RunPage is the run-focused URL: pipeline + per-stage active-step panels // + spec diffs + hold banner. Host metadata stays on /hosts/{id}; this // page carries only run-scoped content so the operator can read one run // without surrounding noise. templ RunPage(d RunPageData) { @Layout(fmt.Sprintf("%s — run #%d", d.Host.Name, d.Run.ID)) {
@RunHeader(d) @HoldBanner(d) @PipelineSection(&d.Run, BuildPipeline(&d.Run, d.Stages))
for _, stageName := range store.DefaultStageOrder { @ActiveStep(ActiveStepData{ RunID: d.Run.ID, Stage: stageForName(d.Stages, stageName), SubSteps: SubStepsForStage(d.SubSteps, stageName), LogReplay: d.LogReplayByStage[stageName], Open: stageName == d.DefaultStepStage, }) }
@RunSpecDiffs(d)
} } // RunHeader is the run-page header: run id, state badge, elapsed, and // the primary action on the right (Cancel during a non-terminal run; // Start-new-run + View-report after). Keyed on run ID so SSE updates // don't collide with a newer run's header. Rendered as a section rather // than a bare header so it composes with the breadcrumb strip above. templ RunHeader(d RunPageData) {

{ fmt.Sprintf("Run #%d", d.Run.ID) }

{ tileStatus(&d.Run) } { profileChipValue(d.Run.Profile) } { runDuration(&d.Run) } if d.Run.FailedStage != "" { failed at { d.Run.FailedStage } } if d.SpecDiffCritical > 0 { { fmt.Sprintf("%d critical diff", d.SpecDiffCritical) } }
if canCancel(&d.Run) { if d.Run.State == model.StateFailedHolding {
} else {
} } if canOverrideWipe(&d.Run) {
} if hasReport(&d.Run) { View report } if d.Run.State.IsTerminal() {
}
} // HoldBanner is the "Host is holding — SSH available" strip when a run // is FailedHolding with an IP recorded. Emits an empty placeholder // otherwise so the first SSE push when a hold actually fires has a // target to swap into. templ HoldBanner(d RunPageData) { if d.Run.State == model.StateFailedHolding && d.Run.HoldIP != "" {
Host is holding — SSH available:
{ sshInvocation(d.HoldKeyPath, d.Run.HoldIP) }
} else {
} } // RunSpecDiffs renders the "Spec diffs (N)" collapsible. The wrapper is // always emitted (even when empty) so SpecValidate-time SSE pushes have // a target; the
body only renders when diffs exist. templ RunSpecDiffs(d RunPageData) {
if len(d.SpecDiffs) > 0 {

Spec diffs

{ fmt.Sprintf("%d", len(d.SpecDiffs)) }
    for _, diff := range d.SpecDiffs {
  • { diff.Field }
    expected: { diff.Expected }
    actual: { diff.Actual }
  • }
}
} // RenderRunHeaderString, RenderHoldBannerString, and // RenderRunSpecDiffsString render each region to a string for the // orchestrator's SSE publish path. Matches the RenderTileString pattern. func RenderRunHeaderString(d RunPageData) string { var buf bytes.Buffer _ = RunHeader(d).Render(context.Background(), &buf) return buf.String() } func RenderHoldBannerString(d RunPageData) string { var buf bytes.Buffer _ = HoldBanner(d).Render(context.Background(), &buf) return buf.String() } func RenderRunSpecDiffsString(d RunPageData) string { var buf bytes.Buffer _ = RunSpecDiffs(d).Render(context.Background(), &buf) return buf.String() }