hub v0.46.0: observability pass — per-box log pulls, bundle custody, 72h TTL + secret gate
log_bundle_requests + log_bundles store (gzip, newest-3, 72h TTL purged on the 60s sweep); SaveLogBundle secret gate fail-closed (blocked flag row, no payload; REDACTED/checksums pass). Report ACK gains controller_log_requested + ingests controller_log_tail; heartbeat envelope gains log_tail_requested + ingests log_tail (consume-once on arrival; pre-0.83 agents stay visibly pending). Host detail Diagnostics section: request buttons (controller/agent), state rows with honest latency hints, View/Download endpoint. Red-proofs: gate disabled and clear-on-arrival removed both FAIL their tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -173,6 +173,66 @@
|
||||
{{end}}
|
||||
</section>
|
||||
|
||||
<!-- Diagnostics: component log bundles (v0.46.0) -->
|
||||
<section class="card">
|
||||
<h2>Diagnostics — Log Bundles</h2>
|
||||
<p class="hint" style="color: var(--text-muted); font-size: 0.85rem;">
|
||||
Pull-based: the box ships its debug ring on its own next cycle — controller ≤ one report interval (~15 min),
|
||||
agent ≈ one heartbeat. The pull is recorded in the box's own log (customer-visible). Bundles expire after 72 h.
|
||||
</p>
|
||||
<div style="display: flex; gap: 0.5rem; margin: 0.75rem 0;">
|
||||
<form method="POST" action="/hosts/{{.HostID}}/request-logs" style="display: inline;">
|
||||
<input type="hidden" name="_csrf" value="{{.CSRFToken}}">
|
||||
<input type="hidden" name="component" value="controller">
|
||||
<button type="submit" class="btn btn-sm">Request controller logs</button>
|
||||
</form>
|
||||
<form method="POST" action="/hosts/{{.HostID}}/request-logs" style="display: inline;">
|
||||
<input type="hidden" name="_csrf" value="{{.CSRFToken}}">
|
||||
<input type="hidden" name="component" value="agent">
|
||||
<button type="submit" class="btn btn-sm">Request agent logs</button>
|
||||
</form>
|
||||
</div>
|
||||
{{if .LogBundles}}
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Component</th>
|
||||
<th>State</th>
|
||||
<th>Collected</th>
|
||||
<th>Received</th>
|
||||
<th>Size</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .LogBundles}}
|
||||
<tr>
|
||||
<td>{{.Component}}</td>
|
||||
<td>
|
||||
{{if eq .State "pending"}}<span class="badge badge-neutral" title="Waiting for the box's next cycle (requested {{timeAgo .RequestedAt}})">pending</span>
|
||||
{{else if eq .State "blocked"}}<span class="badge badge-error" title="{{.BlockedNote}}">blocked: possible secret</span>
|
||||
{{else}}<span class="badge badge-ok">available</span>{{end}}
|
||||
</td>
|
||||
<td>{{if .CollectedAt.IsZero}}—{{else}}{{timeAgo .CollectedAt}}{{end}}</td>
|
||||
<td>{{if .ReceivedAt.IsZero}}—{{else}}{{timeAgo .ReceivedAt}}{{end}}</td>
|
||||
<td>{{if .SizeBytes}}{{.SizeBytes}} B{{else}}—{{end}}</td>
|
||||
<td>
|
||||
{{if eq .State "available"}}
|
||||
<a href="/hosts/{{$.HostID}}/log-bundles/{{.ID}}" class="btn btn-sm">View</a>
|
||||
<a href="/hosts/{{$.HostID}}/log-bundles/{{.ID}}?download=1" class="btn btn-sm">Download</a>
|
||||
{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{else}}
|
||||
<div class="empty-state" style="border: none;">
|
||||
<p>No log bundles. Use the request buttons above — the box delivers on its next cycle.</p>
|
||||
</div>
|
||||
{{end}}
|
||||
</section>
|
||||
|
||||
<!-- DR / Backup -->
|
||||
<section class="card">
|
||||
<h2>DR / Backup</h2>
|
||||
|
||||
Reference in New Issue
Block a user