Host detail v2: full pipeline + per-stage logs + WoL diagnostics
CI / Lint + build + test (push) Has been cancelled
CI / Lint + build + test (push) Has been cancelled
Pipeline now always renders all 13 nodes (3 pre-stage + 9 stage +
Completed), synthesising ghosts from run state when stage rows
aren't seeded yet. Makes a WaitingWoL host show the full timeline
ahead of it instead of just 4 dots.
Agent tags each log line with its stage; logs.Hub fans out to both
log-{runID} and log-{runID}-{stage} SSE events so the detail page
can show per-stage tabs with a pure-CSS radio-sibling switch. Flat
run log prepends [stage] so grep still works.
Dispatcher writes picked/sent-WoL/heartbeat lines into the per-run
log — the operator opens the detail page, sees WaitingWoL stuck,
and reads exactly what the dispatcher did and why nothing's
progressing, instead of having to tail journalctl on the LXC.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -4,16 +4,20 @@ import (
|
||||
"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.
|
||||
// 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) {
|
||||
@@ -123,15 +127,7 @@ templ HostDetail(d HostDetailData) {
|
||||
}
|
||||
|
||||
if d.Tile.Latest != nil {
|
||||
<section class="detail-section">
|
||||
<h2>Log</h2>
|
||||
<div
|
||||
class="detail-log"
|
||||
id={ fmt.Sprintf("log-%d", d.Tile.Latest.ID) }
|
||||
sse-swap={ fmt.Sprintf("log-%d", d.Tile.Latest.ID) }
|
||||
hx-swap="beforeend"
|
||||
></div>
|
||||
</section>
|
||||
@LogTabs(d.Tile.Latest.ID, d.LogReplay)
|
||||
}
|
||||
|
||||
<section class="detail-section detail-host-meta">
|
||||
@@ -163,3 +159,38 @@ func hasCriticalDiff(diffs []model.SpecDiff) bool {
|
||||
}
|
||||
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) {
|
||||
<section class="detail-section log-section">
|
||||
<h2>Log</h2>
|
||||
<div class="log-tabs">
|
||||
<input type="radio" name={ fmt.Sprintf("log-tab-%d", runID) } id={ fmt.Sprintf("log-tab-%d-all", runID) } class="log-tab-input log-tab-all" checked/>
|
||||
<label for={ fmt.Sprintf("log-tab-%d-all", runID) } class="log-tab-label">All</label>
|
||||
for _, s := range store.DefaultStageOrder {
|
||||
<input type="radio" name={ fmt.Sprintf("log-tab-%d", runID) } id={ fmt.Sprintf("log-tab-%d-%s", runID, s) } class={ "log-tab-input", "log-tab-" + s }/>
|
||||
<label for={ fmt.Sprintf("log-tab-%d-%s", runID, s) } class="log-tab-label">{ s }</label>
|
||||
}
|
||||
<div
|
||||
class="log-pane log-pane-all"
|
||||
id={ fmt.Sprintf("log-%d", runID) }
|
||||
sse-swap={ fmt.Sprintf("log-%d", runID) }
|
||||
hx-swap="beforeend show:bottom"
|
||||
>
|
||||
@templ.Raw(replay)
|
||||
</div>
|
||||
for _, s := range store.DefaultStageOrder {
|
||||
<div
|
||||
class={ "log-pane", "log-pane-" + s }
|
||||
id={ fmt.Sprintf("log-%d-%s", runID, s) }
|
||||
sse-swap={ fmt.Sprintf("log-%d-%s", runID, s) }
|
||||
hx-swap="beforeend show:bottom"
|
||||
></div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user