Restore USB support bundle export on tools page
This commit is contained in:
@@ -1089,72 +1089,7 @@ func renderExport(exportDir string) string {
|
|||||||
</div></div>
|
</div></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card" style="margin-top:16px">
|
` + renderUSBExportCard()
|
||||||
<div class="card-head">Export to USB
|
|
||||||
<button class="btn btn-sm btn-secondary" onclick="usbRefresh()" style="margin-left:auto">↻ Refresh</button>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<p style="font-size:13px;color:var(--muted);margin-bottom:12px">Write audit JSON or support bundle directly to a removable USB drive.</p>
|
|
||||||
<div id="usb-status" style="font-size:13px;color:var(--muted)">Scanning for USB devices...</div>
|
|
||||||
<div id="usb-targets" style="margin-top:12px"></div>
|
|
||||||
<div id="usb-msg" style="margin-top:10px;font-size:13px"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
(function(){
|
|
||||||
function usbRefresh() {
|
|
||||||
document.getElementById('usb-status').textContent = 'Scanning...';
|
|
||||||
document.getElementById('usb-targets').innerHTML = '';
|
|
||||||
document.getElementById('usb-msg').textContent = '';
|
|
||||||
fetch('/api/export/usb').then(r=>r.json()).then(targets => {
|
|
||||||
const st = document.getElementById('usb-status');
|
|
||||||
const ct = document.getElementById('usb-targets');
|
|
||||||
if (!targets || targets.length === 0) {
|
|
||||||
st.textContent = 'No removable USB devices found.';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
st.textContent = targets.length + ' device(s) found:';
|
|
||||||
ct.innerHTML = '<table><tr><th>Device</th><th>FS</th><th>Size</th><th>Label</th><th>Model</th><th>Actions</th></tr>' +
|
|
||||||
targets.map(t => {
|
|
||||||
const dev = t.device || '';
|
|
||||||
const label = t.label || '';
|
|
||||||
const model = t.model || '';
|
|
||||||
return '<tr>' +
|
|
||||||
'<td style="font-family:monospace">'+dev+'</td>' +
|
|
||||||
'<td>'+t.fs_type+'</td>' +
|
|
||||||
'<td>'+t.size+'</td>' +
|
|
||||||
'<td>'+label+'</td>' +
|
|
||||||
'<td style="font-size:12px;color:var(--muted)">'+model+'</td>' +
|
|
||||||
'<td style="white-space:nowrap">' +
|
|
||||||
'<button class="btn btn-sm btn-primary" onclick="usbExport(\'audit\','+JSON.stringify(t)+')">Audit JSON</button> ' +
|
|
||||||
'<button class="btn btn-sm btn-secondary" onclick="usbExport(\'bundle\','+JSON.stringify(t)+')">Support Bundle</button>' +
|
|
||||||
'</td></tr>';
|
|
||||||
}).join('') + '</table>';
|
|
||||||
}).catch(e => {
|
|
||||||
document.getElementById('usb-status').textContent = 'Error: ' + e;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
window.usbExport = function(type, target) {
|
|
||||||
const msg = document.getElementById('usb-msg');
|
|
||||||
msg.style.color = 'var(--muted)';
|
|
||||||
msg.textContent = 'Exporting to ' + (target.device||'') + '...';
|
|
||||||
fetch('/api/export/usb/'+type, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {'Content-Type':'application/json'},
|
|
||||||
body: JSON.stringify(target)
|
|
||||||
}).then(r=>r.json()).then(d => {
|
|
||||||
if (d.error) { msg.style.color='var(--err,red)'; msg.textContent = 'Error: '+d.error; return; }
|
|
||||||
msg.style.color = 'var(--ok,green)';
|
|
||||||
msg.textContent = d.message || 'Done.';
|
|
||||||
}).catch(e => {
|
|
||||||
msg.style.color = 'var(--err,red)';
|
|
||||||
msg.textContent = 'Error: '+e;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
window.usbRefresh = usbRefresh;
|
|
||||||
usbRefresh();
|
|
||||||
})();
|
|
||||||
</script>`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func listExportFiles(exportDir string) ([]string, error) {
|
func listExportFiles(exportDir string) ([]string, error) {
|
||||||
@@ -1224,6 +1159,77 @@ window.supportBundleDownload = function() {
|
|||||||
</script>`
|
</script>`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func renderUSBExportCard() string {
|
||||||
|
return `<div class="card" style="margin-top:16px">
|
||||||
|
<div class="card-head">Export to USB
|
||||||
|
<button class="btn btn-sm btn-secondary" onclick="usbRefresh()" style="margin-left:auto">↻ Refresh</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">` + renderUSBExportInline() + `</div>
|
||||||
|
</div>`
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderUSBExportInline() string {
|
||||||
|
return `<p style="font-size:13px;color:var(--muted);margin-bottom:12px">Write audit JSON or support bundle directly to a removable USB drive.</p>
|
||||||
|
<div id="usb-status" style="font-size:13px;color:var(--muted)">Scanning for USB devices...</div>
|
||||||
|
<div id="usb-targets" style="margin-top:12px"></div>
|
||||||
|
<div id="usb-msg" style="margin-top:10px;font-size:13px"></div>
|
||||||
|
<script>
|
||||||
|
(function(){
|
||||||
|
function usbRefresh() {
|
||||||
|
document.getElementById('usb-status').textContent = 'Scanning...';
|
||||||
|
document.getElementById('usb-targets').innerHTML = '';
|
||||||
|
document.getElementById('usb-msg').textContent = '';
|
||||||
|
fetch('/api/export/usb').then(r=>r.json()).then(targets => {
|
||||||
|
const st = document.getElementById('usb-status');
|
||||||
|
const ct = document.getElementById('usb-targets');
|
||||||
|
if (!targets || targets.length === 0) {
|
||||||
|
st.textContent = 'No removable USB devices found.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
st.textContent = targets.length + ' device(s) found:';
|
||||||
|
ct.innerHTML = '<table><tr><th>Device</th><th>FS</th><th>Size</th><th>Label</th><th>Model</th><th>Actions</th></tr>' +
|
||||||
|
targets.map(t => {
|
||||||
|
const dev = t.device || '';
|
||||||
|
const label = t.label || '';
|
||||||
|
const model = t.model || '';
|
||||||
|
return '<tr>' +
|
||||||
|
'<td style="font-family:monospace">'+dev+'</td>' +
|
||||||
|
'<td>'+t.fs_type+'</td>' +
|
||||||
|
'<td>'+t.size+'</td>' +
|
||||||
|
'<td>'+label+'</td>' +
|
||||||
|
'<td style="font-size:12px;color:var(--muted)">'+model+'</td>' +
|
||||||
|
'<td style="white-space:nowrap">' +
|
||||||
|
'<button class="btn btn-sm btn-primary" onclick="usbExport(\'audit\','+JSON.stringify(t)+')">Audit JSON</button> ' +
|
||||||
|
'<button class="btn btn-sm btn-secondary" onclick="usbExport(\'bundle\','+JSON.stringify(t)+')">Support Bundle</button>' +
|
||||||
|
'</td></tr>';
|
||||||
|
}).join('') + '</table>';
|
||||||
|
}).catch(e => {
|
||||||
|
document.getElementById('usb-status').textContent = 'Error: ' + e;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
window.usbExport = function(type, target) {
|
||||||
|
const msg = document.getElementById('usb-msg');
|
||||||
|
msg.style.color = 'var(--muted)';
|
||||||
|
msg.textContent = 'Exporting to ' + (target.device||'') + '...';
|
||||||
|
fetch('/api/export/usb/'+type, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type':'application/json'},
|
||||||
|
body: JSON.stringify(target)
|
||||||
|
}).then(r=>r.json()).then(d => {
|
||||||
|
if (d.error) { msg.style.color='var(--err,red)'; msg.textContent = 'Error: '+d.error; return; }
|
||||||
|
msg.style.color = 'var(--ok,green)';
|
||||||
|
msg.textContent = d.message || 'Done.';
|
||||||
|
}).catch(e => {
|
||||||
|
msg.style.color = 'var(--err,red)';
|
||||||
|
msg.textContent = 'Error: '+e;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
window.usbRefresh = usbRefresh;
|
||||||
|
usbRefresh();
|
||||||
|
})();
|
||||||
|
</script>`
|
||||||
|
}
|
||||||
|
|
||||||
// ── Display Resolution ────────────────────────────────────────────────────────
|
// ── Display Resolution ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
func renderDisplayInline() string {
|
func renderDisplayInline() string {
|
||||||
@@ -1325,6 +1331,10 @@ function installToRAM() {
|
|||||||
<div class="card"><div class="card-head">Support Bundle</div><div class="card-body">
|
<div class="card"><div class="card-head">Support Bundle</div><div class="card-body">
|
||||||
<p style="font-size:13px;color:var(--muted);margin-bottom:12px">Downloads a tar.gz archive of all audit files, SAT results, and logs.</p>
|
<p style="font-size:13px;color:var(--muted);margin-bottom:12px">Downloads a tar.gz archive of all audit files, SAT results, and logs.</p>
|
||||||
` + renderSupportBundleInline() + `
|
` + renderSupportBundleInline() + `
|
||||||
|
<div style="border-top:1px solid var(--border);margin-top:16px;padding-top:16px">
|
||||||
|
<div style="font-weight:600;margin-bottom:8px">Export to USB</div>
|
||||||
|
` + renderUSBExportInline() + `
|
||||||
|
</div>
|
||||||
</div></div>
|
</div></div>
|
||||||
|
|
||||||
<div class="card"><div class="card-head">Tool Check <button class="btn btn-sm btn-secondary" onclick="checkTools()" style="margin-left:auto">↻ Check</button></div>
|
<div class="card"><div class="card-head">Tool Check <button class="btn btn-sm btn-secondary" onclick="checkTools()" style="margin-left:auto">↻ Check</button></div>
|
||||||
|
|||||||
@@ -395,6 +395,12 @@ func TestToolsPageRendersRestartGPUDriversButton(t *testing.T) {
|
|||||||
if !strings.Contains(body, `id="boot-source-text"`) {
|
if !strings.Contains(body, `id="boot-source-text"`) {
|
||||||
t.Fatalf("tools page missing boot source field: %s", body)
|
t.Fatalf("tools page missing boot source field: %s", body)
|
||||||
}
|
}
|
||||||
|
if !strings.Contains(body, `Export to USB`) {
|
||||||
|
t.Fatalf("tools page missing export to usb section: %s", body)
|
||||||
|
}
|
||||||
|
if !strings.Contains(body, `Support Bundle</button>`) {
|
||||||
|
t.Fatalf("tools page missing support bundle usb button: %s", body)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestViewerRendersLatestSnapshot(t *testing.T) {
|
func TestViewerRendersLatestSnapshot(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user