package templates import ( "bytes" "context" "fmt" "vetting/internal/model" "vetting/internal/store" ) // HostDetailData is the full payload the detail handler hands to the // HostDetail template. Tile carries host + latest-run enrichment (same // shape the dashboard tile uses), Stages/SpecDiffs drive the pipeline // and diff list. LogReplay is the pre-rendered history fragment // produced by logs.Hub.Replay on the initial page render so the operator // sees prior output without waiting for a fresh SSE event. type HostDetailData struct { Tile TileData Stages []model.Stage SpecDiffs []model.SpecDiff LogReplay string } templ HostDetail(d HostDetailData) { @Layout(d.Tile.Host.Name) {
@DetailSummary(d) if d.Tile.Latest != nil {

Pipeline

@Pipeline(BuildPipeline(d.Tile.Latest, d.Stages))
} else {

Pipeline

@Pipeline(BuildPipeline(nil, nil))
} @DetailHold(d) @DetailActions(d) @DetailSpecDiffs(d) if d.Tile.Latest != nil { @LogTabs(d.Tile.Latest.ID, d.LogReplay) }

Host details

if d.Tile.Host.Notes != "" {

Notes

{ d.Tile.Host.Notes }

}

Expected spec

{ d.Tile.Host.ExpectedSpecYAML }
} } // DetailSummary is the status header at the top of the detail page: // name, last-seen badge, run status, MAC/WoL/failed-stage/spec-diffs // meta grid. Keyed on host ID so the SSE event name is stable across // run turnover. templ DetailSummary(d HostDetailData) {

{ d.Tile.Host.Name }

{ lastSeenLabel(d.Tile.LastSeenAt) } { tileStatus(d.Tile.Latest) }
MAC
{ d.Tile.Host.MAC }
WoL
{ fmt.Sprintf("%s:%d", d.Tile.Host.WoLBroadcastIP, d.Tile.Host.WoLPort) }
if d.Tile.Latest != nil && d.Tile.Latest.FailedStage != "" {
Failed at
{ d.Tile.Latest.FailedStage }
} if d.Tile.SpecDiffCritical > 0 {
Spec diffs
{ fmt.Sprintf("%d critical", d.Tile.SpecDiffCritical) }
}
} // DetailActions is the button row (Start / Cancel / Override / View // report / Delete). Enabled/disabled state depends on the latest run's // state and host heartbeat; both change live, so this section re-renders // on every state change. Keyed on host ID — the actions exist even // without a run. templ DetailActions(d HostDetailData) {

Actions

if canStart(d.Tile) {
} else if canStartIfOnline(d.Tile.Latest) { } else { } if canCancel(d.Tile.Latest) {
} if canOverrideWipe(d.Tile.Latest) {
} if hasReport(d.Tile.Latest) { View report }
} // DetailSpecDiffs renders the "Spec diffs (N)" collapsible when a run // exists; otherwise it emits a bare empty wrapper so a later SSE push // after SpecValidate writes has a target to swap into. The wrapper is // keyed on run ID because the diffs belong to a specific run — a new // run publishes to a new event name, and the detail page navigates to // the new target via outerHTML swap only when the whole DetailSpecDiffs // section is re-rendered by a page reload. templ DetailSpecDiffs(d HostDetailData) { if d.Tile.Latest != nil {
if len(d.SpecDiffs) > 0 {

Spec diffs ({ fmt.Sprintf("%d", len(d.SpecDiffs)) })

}
} } // DetailHold renders the "Host is holding — SSH available" block while // a run is in FailedHolding with an IP recorded. Otherwise it emits an // empty wrapper so the first push when the hold actually fires has a // target. Keyed on run ID for the same reason as DetailSpecDiffs. templ DetailHold(d HostDetailData) { if d.Tile.Latest != nil {
if d.Tile.Latest.State == model.StateFailedHolding && d.Tile.Latest.HoldIP != "" {

Host is holding — SSH available

{ sshInvocation(d.Tile.HoldKeyPath, d.Tile.Latest.HoldIP) } }
} } // RenderDetailSummaryString, RenderDetailActionsString, // RenderDetailSpecDiffsString, RenderDetailHoldString each render one // component to a string so the orchestrator can publish SSE fragments // without importing the HTTP layer. Matches the RenderTileString / // RenderPipelineString pattern. func RenderDetailSummaryString(d HostDetailData) string { var buf bytes.Buffer _ = DetailSummary(d).Render(context.Background(), &buf) return buf.String() } func RenderDetailActionsString(d HostDetailData) string { var buf bytes.Buffer _ = DetailActions(d).Render(context.Background(), &buf) return buf.String() } func RenderDetailSpecDiffsString(d HostDetailData) string { var buf bytes.Buffer _ = DetailSpecDiffs(d).Render(context.Background(), &buf) return buf.String() } func RenderDetailHoldString(d HostDetailData) string { var buf bytes.Buffer _ = DetailHold(d).Render(context.Background(), &buf) return buf.String() } // hasCriticalDiff opens the spec-diff
by default when any // diff is critical — operator shouldn't have to click to see the blocker. func hasCriticalDiff(diffs []model.SpecDiff) bool { for _, d := range diffs { if d.Severity == "critical" && !d.Ignored { return true } } return false } // LogTabs renders an "All" tab plus one tab per stage in DefaultStageOrder. // Switching is pure CSS: hidden radio inputs drive sibling-selector // visibility on the panes. Each pane carries its own sse-swap target so // live events append only to the relevant pane. The All pane is seeded // with replay HTML so reload on an in-flight run still shows history. templ LogTabs(runID int64, replay string) {

Log

for _, s := range store.DefaultStageOrder { }
@templ.Raw(replay)
for _, s := range store.DefaultStageOrder {
}
}