Fix component detail modal: replace dead hx-* with fetch-based JS
HTMX was never loaded on the page, so hx-get on the component label
spans was dead code — the dialog opened empty. Replace with a plain
openComponentDetail() fetch call. Also fix dialog positioning broken
by the CSS reset (*{margin:0} overrode the UA margin:auto that centers
<dialog>). Replace card hx-trigger polling with a setInterval.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ func layoutHead(title string) string {
|
||||
<style>
|
||||
:root{--bg:#fff;--surface:#fff;--surface-2:#f9fafb;--border:rgba(34,36,38,.15);--border-lite:rgba(34,36,38,.1);--ink:rgba(0,0,0,.87);--muted:rgba(0,0,0,.6);--accent:#2185d0;--accent-dark:#1678c2;--crit-bg:#fff6f6;--crit-fg:#9f3a38;--crit-border:#e0b4b4;--ok-bg:#fcfff5;--ok-fg:#2c662d;--warn-bg:#fffaf3;--warn-fg:#573a08}
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
dialog{margin:auto}
|
||||
body{font:14px/1.5 Lato,"Helvetica Neue",Arial,Helvetica,sans-serif;background:var(--bg);color:var(--ink);display:flex;min-height:100vh}
|
||||
a{color:var(--accent);text-decoration:none}
|
||||
/* Sidebar */
|
||||
|
||||
@@ -95,6 +95,17 @@ document.querySelectorAll('.terminal').forEach(function(t){
|
||||
btn.onclick=function(){navigator.clipboard.writeText(t.textContent).then(function(){btn.textContent='Copied!';setTimeout(function(){btn.textContent='Copy';},1500);});};
|
||||
w.appendChild(btn);
|
||||
});
|
||||
function openComponentDetail(type) {
|
||||
var dlg = document.getElementById('component-detail-dialog');
|
||||
var body = document.getElementById('component-detail-body');
|
||||
body.innerHTML = '<div style="padding:20px;color:var(--muted)">Loading…</div>';
|
||||
dlg.showModal();
|
||||
fetch('/api/components/' + type).then(function(r){ return r.text(); }).then(function(html){
|
||||
body.innerHTML = html;
|
||||
}).catch(function(){
|
||||
body.innerHTML = '<div style="padding:20px;color:var(--crit-fg)">Error loading details.</div>';
|
||||
});
|
||||
}
|
||||
</script>` +
|
||||
`</body></html>`
|
||||
}
|
||||
@@ -107,6 +118,14 @@ func renderDashboard(opts HandlerOptions) string {
|
||||
b.WriteString(renderHardwareSummaryCard(opts))
|
||||
b.WriteString(renderHealthCard(opts))
|
||||
b.WriteString(renderMetrics())
|
||||
b.WriteString(`<script>
|
||||
setInterval(function(){
|
||||
fetch('/api/hardware-summary').then(function(r){return r.text();}).then(function(html){
|
||||
var el=document.getElementById('hw-summary-card');
|
||||
if(el){el.outerHTML=html;}
|
||||
}).catch(function(){});
|
||||
},30000);
|
||||
</script>`)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
@@ -185,14 +204,14 @@ func renderAudit() string {
|
||||
}
|
||||
|
||||
func renderHardwareSummaryCard(opts HandlerOptions) string {
|
||||
const cardAttrs = ` hx-get="/api/hardware-summary" hx-trigger="every 30s" hx-swap="outerHTML"`
|
||||
const cardID = ` id="hw-summary-card"`
|
||||
data, err := loadSnapshot(opts.AuditPath)
|
||||
if err != nil {
|
||||
return `<div class="card"` + cardAttrs + `><div class="card-head card-head-actions"><span>Hardware Summary</span><div class="card-head-buttons"><button class="btn btn-primary btn-sm" onclick="auditModalRun()">Run audit</button></div></div><div class="card-body"></div></div>`
|
||||
return `<div class="card"` + cardID + `><div class="card-head card-head-actions"><span>Hardware Summary</span><div class="card-head-buttons"><button class="btn btn-primary btn-sm" onclick="auditModalRun()">Run audit</button></div></div><div class="card-body"></div></div>`
|
||||
}
|
||||
var ingest schema.HardwareIngestRequest
|
||||
if err := json.Unmarshal(data, &ingest); err != nil {
|
||||
return `<div class="card"` + cardAttrs + `><div class="card-head">Hardware Summary</div><div class="card-body"><span class="badge badge-err">Parse error</span></div></div>`
|
||||
return `<div class="card"` + cardID + `><div class="card-head">Hardware Summary</div><div class="card-body"><span class="badge badge-err">Parse error</span></div></div>`
|
||||
}
|
||||
hw := ingest.Hardware
|
||||
|
||||
@@ -202,7 +221,7 @@ func renderHardwareSummaryCard(opts HandlerOptions) string {
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString(`<div class="card"` + cardAttrs + `><div class="card-head">Hardware Summary</div><div class="card-body">`)
|
||||
b.WriteString(`<div class="card"` + cardID + `><div class="card-head">Hardware Summary</div><div class="card-body">`)
|
||||
|
||||
// Server identity block above the component table.
|
||||
{
|
||||
@@ -237,7 +256,7 @@ func renderHardwareSummaryCard(opts HandlerOptions) string {
|
||||
var labelHTML string
|
||||
if compType != "" {
|
||||
labelHTML = fmt.Sprintf(
|
||||
`<span style="cursor:pointer;text-decoration:underline dotted;text-underline-offset:3px" hx-get="/api/components/%s" hx-target="#component-detail-body" hx-swap="innerHTML" onclick="document.getElementById('component-detail-dialog').showModal()">%s</span>`,
|
||||
`<span style="cursor:pointer;text-decoration:underline dotted;text-underline-offset:3px" onclick="openComponentDetail('%s')">%s</span>`,
|
||||
compType, html.EscapeString(label))
|
||||
} else {
|
||||
labelHTML = html.EscapeString(label)
|
||||
|
||||
Reference in New Issue
Block a user