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>
|
<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}
|
: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}
|
*{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}
|
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}
|
a{color:var(--accent);text-decoration:none}
|
||||||
/* Sidebar */
|
/* 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);});};
|
btn.onclick=function(){navigator.clipboard.writeText(t.textContent).then(function(){btn.textContent='Copied!';setTimeout(function(){btn.textContent='Copy';},1500);});};
|
||||||
w.appendChild(btn);
|
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>` +
|
</script>` +
|
||||||
`</body></html>`
|
`</body></html>`
|
||||||
}
|
}
|
||||||
@@ -107,6 +118,14 @@ func renderDashboard(opts HandlerOptions) string {
|
|||||||
b.WriteString(renderHardwareSummaryCard(opts))
|
b.WriteString(renderHardwareSummaryCard(opts))
|
||||||
b.WriteString(renderHealthCard(opts))
|
b.WriteString(renderHealthCard(opts))
|
||||||
b.WriteString(renderMetrics())
|
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()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,14 +204,14 @@ func renderAudit() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func renderHardwareSummaryCard(opts HandlerOptions) 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)
|
data, err := loadSnapshot(opts.AuditPath)
|
||||||
if err != nil {
|
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
|
var ingest schema.HardwareIngestRequest
|
||||||
if err := json.Unmarshal(data, &ingest); err != nil {
|
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
|
hw := ingest.Hardware
|
||||||
|
|
||||||
@@ -202,7 +221,7 @@ func renderHardwareSummaryCard(opts HandlerOptions) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var b strings.Builder
|
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.
|
// Server identity block above the component table.
|
||||||
{
|
{
|
||||||
@@ -237,7 +256,7 @@ func renderHardwareSummaryCard(opts HandlerOptions) string {
|
|||||||
var labelHTML string
|
var labelHTML string
|
||||||
if compType != "" {
|
if compType != "" {
|
||||||
labelHTML = fmt.Sprintf(
|
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))
|
compType, html.EscapeString(label))
|
||||||
} else {
|
} else {
|
||||||
labelHTML = html.EscapeString(label)
|
labelHTML = html.EscapeString(label)
|
||||||
|
|||||||
Reference in New Issue
Block a user