diff --git a/audit/internal/platform/install_to_ram.go b/audit/internal/platform/install_to_ram.go
index c6a7bd0..4f6b399 100644
--- a/audit/internal/platform/install_to_ram.go
+++ b/audit/internal/platform/install_to_ram.go
@@ -14,9 +14,17 @@ import (
func (s *System) IsLiveMediaInRAM() bool {
fsType := mountFSType("/run/live/medium")
if fsType == "" {
+ // No medium mount at all — fall back to toram kernel parameter.
return toramActive()
}
- return strings.EqualFold(fsType, "tmpfs")
+ if strings.EqualFold(fsType, "tmpfs") {
+ return true
+ }
+ // When RunInstallToRAM copies squashfs to /dev/shm/bee-live but the bind
+ // mount of /run/live/medium fails (common for CD-ROM boots), the medium
+ // fstype still shows the CD-ROM type. Check whether the RAM copy exists.
+ files, _ := filepath.Glob("/dev/shm/bee-live/*.squashfs")
+ return len(files) > 0
}
func (s *System) LiveBootSource() LiveBootSource {
diff --git a/audit/internal/platform/runtime.go b/audit/internal/platform/runtime.go
index 546f064..538869f 100644
--- a/audit/internal/platform/runtime.go
+++ b/audit/internal/platform/runtime.go
@@ -244,11 +244,17 @@ func findUSBExportMount() string {
if readOnly {
continue
}
- // Check USB transport via lsblk on the device
+ // Check USB transport via lsblk on the device (or its parent disk for partitions).
if !strings.HasPrefix(device, "/dev/") {
continue
}
- if blockDeviceTransport(device) == "usb" {
+ checkDev := device
+ // lsblk only reports TRAN for the whole disk, not for partitions (e.g. /dev/sdc1).
+ // Strip trailing partition digits to get the parent disk name.
+ if trimmed := strings.TrimRight(device, "0123456789"); trimmed != device && len(trimmed) > len("/dev/") {
+ checkDev = trimmed
+ }
+ if blockDeviceTransport(checkDev) == "usb" {
return mountPoint
}
}
diff --git a/audit/internal/webui/pages.go b/audit/internal/webui/pages.go
index b2f7d08..ddd437b 100644
--- a/audit/internal/webui/pages.go
+++ b/audit/internal/webui/pages.go
@@ -330,6 +330,33 @@ func renderHardwareSummaryCard(opts HandlerOptions) string {
var b strings.Builder
b.WriteString(`
Hardware Summary
`)
+
+ // Server identity block above the component table.
+ {
+ var model, serial string
+ parts := []string{}
+ if hw.Board.Manufacturer != nil && strings.TrimSpace(*hw.Board.Manufacturer) != "" {
+ parts = append(parts, strings.TrimSpace(*hw.Board.Manufacturer))
+ }
+ if hw.Board.ProductName != nil && strings.TrimSpace(*hw.Board.ProductName) != "" {
+ parts = append(parts, strings.TrimSpace(*hw.Board.ProductName))
+ }
+ if len(parts) > 0 {
+ model = strings.Join(parts, " ")
+ }
+ serial = strings.TrimSpace(hw.Board.SerialNumber)
+ if model != "" || serial != "" {
+ b.WriteString(`
`)
+ if model != "" {
+ fmt.Fprintf(&b, `
%s
`, html.EscapeString(model))
+ }
+ if serial != "" {
+ fmt.Fprintf(&b, `
Select at least one NVIDIA GPU to enable NVIDIA validate tasks.
-
-
-
@@ -1455,10 +1473,6 @@ function satSelectedGPUIndices() {
.filter(function(v) { return !Number.isNaN(v); })
.sort(function(a, b) { return a - b; });
}
-function satMultiGPUAll() {
- const cb = document.getElementById('sat-multi-gpu-all');
- return cb ? cb.checked : true;
-}
function satUpdateGPUSelectionNote() {
const note = document.getElementById('sat-gpu-selection-note');
if (!note) return;
@@ -1467,8 +1481,7 @@ function satUpdateGPUSelectionNote() {
note.textContent = 'Select at least one NVIDIA GPU to enable NVIDIA validate tasks.';
return;
}
- const multiAll = satMultiGPUAll();
- note.textContent = 'Selected GPUs: ' + selected.join(', ') + '. Multi-GPU tests: ' + (multiAll ? 'all GPUs in system' : 'selected GPUs only') + '.';
+ note.textContent = 'Selected GPUs: ' + selected.join(', ') + '. Multi-GPU tests will use all selected GPUs.';
}
function satRenderGPUList(gpus) {
const root = document.getElementById('sat-gpu-list');
@@ -1582,15 +1595,8 @@ const nvidiaPerGPUTargets = ['nvidia', 'nvidia-targeted-stress', 'nvidia-targete
// pulse_test and fabric tests run on all selected GPUs simultaneously
const nvidiaAllGPUTargets = ['nvidia-pulse', 'nvidia-interconnect', 'nvidia-bandwidth'];
function satAllGPUIndicesForMulti() {
- // If "Multi-GPU tests — all GPUs" is checked, return all detected GPUs.
- // Otherwise fall back to the per-GPU selection.
- if (satMultiGPUAll()) {
- return loadSatNvidiaGPUs().then(function(gpus) {
- return gpus.map(function(g) { return Number(g.index); });
- });
- }
- const sel = satSelectedGPUIndices();
- return Promise.resolve(sel);
+ // Multi-GPU tests always use the current GPU selection.
+ return Promise.resolve(satSelectedGPUIndices());
}
function expandSATTarget(target) {
if (nvidiaAllGPUTargets.indexOf(target) >= 0) {
@@ -1680,7 +1686,7 @@ function runAMDValidateSet() {
return runNext(0);
}
function runAllSAT() {
- const cycles = Math.max(1, parseInt(document.getElementById('sat-cycles').value)||1);
+ const cycles = 1;
const status = document.getElementById('sat-all-status');
status.textContent = 'Enqueuing...';
const stressOnlyTargets = ['nvidia-targeted-stress', 'nvidia-targeted-power', 'nvidia-pulse', 'nvidia-interconnect', 'nvidia-bandwidth'];
@@ -1967,12 +1973,16 @@ func renderBenchmark(opts HandlerOptions) string {
- ';
}).join('');
+ benchmarkApplyMultiGPUState(gpus.length);
+ benchmarkUpdateSelectionNote();
+}
+
+// Disable radio options that require multiple GPUs when only one is present.
+function benchmarkApplyMultiGPUState(gpuCount) {
+ var multiValues = ['parallel', 'ramp-up'];
+ var radios = document.querySelectorAll('input[name="benchmark-mode"]');
+ radios.forEach(function(el) {
+ var isMulti = multiValues.indexOf(el.value) >= 0;
+ if (gpuCount < 2 && isMulti) {
+ el.disabled = true;
+ if (el.checked) {
+ // fall back to sequential
+ var seq = document.querySelector('input[name="benchmark-mode"][value="sequential"]');
+ if (seq) seq.checked = true;
+ }
+ var label = el.closest('label');
+ if (label) label.style.opacity = '0.4';
+ } else {
+ el.disabled = false;
+ // restore default: ramp-up checked when ≥2 GPUs
+ if (gpuCount >= 2 && el.value === 'ramp-up') el.checked = true;
+ var label = el.closest('label');
+ if (label) label.style.opacity = '';
+ }
+ });
benchmarkUpdateSelectionNote();
}
@@ -2094,8 +2138,9 @@ function runNvidiaBenchmark() {
return;
}
if (benchmarkES) { benchmarkES.close(); benchmarkES = null; }
- const rampUp = selected.length > 1 && !!document.getElementById('benchmark-ramp-up').checked;
- const parallelGPUs = !rampUp && !!document.getElementById('benchmark-parallel-gpus').checked;
+ const mode = benchmarkMode();
+ const rampUp = mode === 'ramp-up' && selected.length > 1;
+ const parallelGPUs = mode === 'parallel';
const body = {
profile: document.getElementById('benchmark-profile').value || 'standard',
gpu_indices: selected,
@@ -2371,10 +2416,20 @@ func renderBurn() string {
Loading NVIDIA GPUs...
Select at least one NVIDIA GPU to enable NVIDIA burn recipes.
-
-
- Ramp selected NVIDIA GPUs one by one before the full-load hold. Smoke: +2 min per GPU, then 5 min with all selected GPUs under load. Acceptance: +10 min per GPU, then at least 1 hour with all selected GPUs under load. Overnight: +1 hour per GPU, then at least 1 hour with all selected GPUs under load, capped at 10 hours total.
-
+
+
+
+ Sequential — selected GPUs one at a time
+
+
+
+ Parallel — all selected GPUs simultaneously
+
+
+
+ Ramp-up — add one GPU at a time
+
+
@@ -2450,9 +2505,30 @@ function burnSelectedGPUIndices() {
.sort(function(a, b) { return a - b; });
}
-function burnUseNvidiaRampUp() {
- const el = document.getElementById('burn-stagger-nvidia');
- return !!(el && el.checked);
+function burnNvidiaMode() {
+ const el = document.querySelector('input[name="burn-nvidia-mode"]:checked');
+ return el ? el.value : 'sequential';
+}
+
+function burnApplyMultiGPUState(gpuCount) {
+ var multiValues = ['parallel', 'ramp-up'];
+ var radios = document.querySelectorAll('input[name="burn-nvidia-mode"]');
+ radios.forEach(function(el) {
+ var isMulti = multiValues.indexOf(el.value) >= 0;
+ if (gpuCount < 2 && isMulti) {
+ el.disabled = true;
+ if (el.checked) {
+ var seq = document.querySelector('input[name="burn-nvidia-mode"][value="sequential"]');
+ if (seq) seq.checked = true;
+ }
+ var label = el.closest('label');
+ if (label) label.style.opacity = '0.4';
+ } else {
+ el.disabled = false;
+ var label = el.closest('label');
+ if (label) label.style.opacity = '';
+ }
+ });
}
function burnUpdateSelectionNote() {
@@ -2479,6 +2555,7 @@ function burnRenderGPUList(gpus) {
+ 'GPU ' + gpu.index + ' — ' + gpu.name + mem + ''
+ '';
}).join('');
+ burnApplyMultiGPUState(gpus.length);
burnUpdateSelectionNote();
}
@@ -2514,8 +2591,11 @@ function enqueueBurnTask(target, label, extra, useSelectedNvidia) {
return Promise.reject(new Error('Select at least one NVIDIA GPU.'));
}
body.gpu_indices = selected;
- if (burnUseNvidiaRampUp() && selected.length > 1) {
+ const bMode = burnNvidiaMode();
+ if (bMode === 'ramp-up' && selected.length > 1) {
body.stagger_gpu_start = true;
+ } else if (bMode === 'parallel' && selected.length > 1) {
+ body.parallel_gpus = true;
}
}
return fetch('/api/sat/' + target + '/run', {