feat(webui): show GPU serial in NVIDIA selection pickers
Every GPU-selection picker (Load/SAT, Burn, Benchmark) now renders the
card serial: "GPU N — <model> · <mem> MiB · sn: <serial>". The serial is
monospace and the digits that differ across the listed GPUs (common
prefix/suffix stripped) are emphasised so operators can tell cards apart.
Row markup was duplicated across three pages (and twice within
page_validate.go). Consolidated into a single module,
internal/webui/gpu_picker.go: beeGpuPicker.render({...}) builds every row;
gpuPickerCSS/gpuPickerJS are injected once by layoutHead/renderPage. Pages
keep their own selection-note text and multi-GPU toggles but no longer
hand-build <label> markup.
ListNvidiaGPUs() adds the serial via nvidia-smi --query-gpu=...,serial;
N/A is normalised to empty. Serial flows to the client unchanged through
the existing /api/gpu/nvidia JSON response.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AudE3Q2nd9kxxVuxPKcTho
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
f31971f440
commit
e4f7519ef3
@@ -14,6 +14,7 @@ type NvidiaGPU struct {
|
|||||||
Index int `json:"index"`
|
Index int `json:"index"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
MemoryMB int `json:"memory_mb"`
|
MemoryMB int `json:"memory_mb"`
|
||||||
|
Serial string `json:"serial,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NvidiaGPUStatus struct {
|
type NvidiaGPUStatus struct {
|
||||||
@@ -226,7 +227,7 @@ func amdStressJobs(seconds int, cfgFile string) []satJob {
|
|||||||
// ListNvidiaGPUs returns GPUs visible to nvidia-smi.
|
// ListNvidiaGPUs returns GPUs visible to nvidia-smi.
|
||||||
func (s *System) ListNvidiaGPUs() ([]NvidiaGPU, error) {
|
func (s *System) ListNvidiaGPUs() ([]NvidiaGPU, error) {
|
||||||
out, err := exec.Command("nvidia-smi",
|
out, err := exec.Command("nvidia-smi",
|
||||||
"--query-gpu=index,name,memory.total",
|
"--query-gpu=index,name,memory.total,serial",
|
||||||
"--format=csv,noheader,nounits").Output()
|
"--format=csv,noheader,nounits").Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("nvidia-smi: %w", err)
|
return nil, fmt.Errorf("nvidia-smi: %w", err)
|
||||||
@@ -237,8 +238,8 @@ func (s *System) ListNvidiaGPUs() ([]NvidiaGPU, error) {
|
|||||||
if line == "" {
|
if line == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
parts := strings.SplitN(line, ", ", 3)
|
parts := strings.SplitN(line, ", ", 4)
|
||||||
if len(parts) != 3 {
|
if len(parts) < 3 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
idx, err := strconv.Atoi(strings.TrimSpace(parts[0]))
|
idx, err := strconv.Atoi(strings.TrimSpace(parts[0]))
|
||||||
@@ -246,10 +247,18 @@ func (s *System) ListNvidiaGPUs() ([]NvidiaGPU, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
memMB, _ := strconv.Atoi(strings.TrimSpace(parts[2]))
|
memMB, _ := strconv.Atoi(strings.TrimSpace(parts[2]))
|
||||||
|
serial := ""
|
||||||
|
if len(parts) == 4 {
|
||||||
|
serial = strings.TrimSpace(parts[3])
|
||||||
|
if strings.EqualFold(serial, "N/A") || strings.EqualFold(serial, "[N/A]") {
|
||||||
|
serial = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
gpus = append(gpus, NvidiaGPU{
|
gpus = append(gpus, NvidiaGPU{
|
||||||
Index: idx,
|
Index: idx,
|
||||||
Name: strings.TrimSpace(parts[1]),
|
Name: strings.TrimSpace(parts[1]),
|
||||||
MemoryMB: memMB,
|
MemoryMB: memMB,
|
||||||
|
Serial: serial,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
sort.Slice(gpus, func(i, j int) bool {
|
sort.Slice(gpus, func(i, j int) bool {
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package webui
|
||||||
|
|
||||||
|
// Shared NVIDIA GPU selection picker.
|
||||||
|
//
|
||||||
|
// Every page that lets the operator pick GPUs (Load, Burn, Benchmark) renders
|
||||||
|
// the same row markup: "GPU N — <model> · <memory> MiB · sn: <serial>". That
|
||||||
|
// markup lives here once. Pages keep their own selection-note text, multi-GPU
|
||||||
|
// mode toggles and CSS-class prefixes, but call beeGpuPicker.render({...}) from
|
||||||
|
// their *RenderGPUList wrapper instead of hand-building each <label>.
|
||||||
|
//
|
||||||
|
// The serial number is rendered monospace, with the digits that differ across
|
||||||
|
// the listed GPUs (usually a run at the end, sometimes the middle or start)
|
||||||
|
// emphasised so operators can tell cards apart at a glance.
|
||||||
|
//
|
||||||
|
// gpuPickerCSS is injected once by layoutHead; gpuPickerJS once by renderPage.
|
||||||
|
|
||||||
|
const gpuPickerCSS = `.bee-gpu-row{display:flex;align-items:flex-start;gap:8px;padding:6px 0;cursor:pointer;font-size:13px}
|
||||||
|
.bee-gpu-row input[type=checkbox]{width:16px;height:16px;margin-top:2px;flex-shrink:0}
|
||||||
|
.bee-gpu-sn{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px}
|
||||||
|
.bee-gpu-sn b{font-weight:700;color:var(--accent)}`
|
||||||
|
|
||||||
|
const gpuPickerJS = `window.beeGpuPicker={
|
||||||
|
_esc:function(s){return String(s).replace(/[&<>"]/g,function(c){return {'&':'&','<':'<','>':'>','"':'"'}[c];});},
|
||||||
|
_commonPrefix:function(arr){
|
||||||
|
arr=arr.filter(Boolean);
|
||||||
|
if(arr.length<2)return 0;
|
||||||
|
var n=0,a=arr[0];
|
||||||
|
while(n<a.length&&arr.every(function(s){return s.charAt(n)===a.charAt(n);}))n++;
|
||||||
|
return n;
|
||||||
|
},
|
||||||
|
_commonSuffix:function(arr,capLeft){
|
||||||
|
arr=arr.filter(Boolean);
|
||||||
|
if(arr.length<2)return 0;
|
||||||
|
var minLen=Math.min.apply(null,arr.map(function(s){return s.length;}));
|
||||||
|
var n=0,a=arr[0];
|
||||||
|
while(n<a.length&&capLeft+n<minLen&&arr.every(function(s){return s.charAt(s.length-1-n)===a.charAt(a.length-1-n);}))n++;
|
||||||
|
return n;
|
||||||
|
},
|
||||||
|
_fmtSerial:function(s,p,q){
|
||||||
|
s=String(s);
|
||||||
|
if(p+q>=s.length){p=0;q=0;}
|
||||||
|
if(p===0&&q===0)return this._esc(s); // nothing distinguishing to highlight
|
||||||
|
return this._esc(s.slice(0,p))+'<b>'+this._esc(s.slice(p,s.length-q))+'</b>'+this._esc(s.slice(s.length-q));
|
||||||
|
},
|
||||||
|
row:function(gpu,checkboxClass,onToggle,prefixLen,suffixLen){
|
||||||
|
var mem=gpu.memory_mb>0?' · '+gpu.memory_mb+' MiB':'';
|
||||||
|
var name=this._esc(gpu.name||('GPU '+gpu.index));
|
||||||
|
var sn=gpu.serial?' · <span class="bee-gpu-sn">sn: '+this._fmtSerial(gpu.serial,prefixLen||0,suffixLen||0)+'</span>':'';
|
||||||
|
return '<label class="bee-gpu-row">'
|
||||||
|
+'<input class="'+checkboxClass+'" type="checkbox" value="'+gpu.index+'" checked'
|
||||||
|
+(onToggle?' onchange="'+onToggle+'"':'')+'>'
|
||||||
|
+'<span><strong>GPU '+gpu.index+'</strong> — '+name+mem+sn+'</span>'
|
||||||
|
+'</label>';
|
||||||
|
},
|
||||||
|
render:function(opts){
|
||||||
|
var root=document.getElementById(opts.rootId);
|
||||||
|
if(!root)return;
|
||||||
|
var gpus=opts.gpus||[];
|
||||||
|
if(!gpus.length){
|
||||||
|
root.innerHTML=opts.emptyHTML||'<p style="color:var(--muted);font-size:13px">No NVIDIA GPUs detected.</p>';
|
||||||
|
if(opts.after)opts.after(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var serials=gpus.map(function(g){return g.serial||'';});
|
||||||
|
var p=this._commonPrefix(serials);
|
||||||
|
var q=this._commonSuffix(serials,p);
|
||||||
|
var self=this;
|
||||||
|
root.innerHTML=gpus.map(function(g){return self.row(g,opts.checkboxClass,opts.onToggle,p,q);}).join('');
|
||||||
|
if(opts.after)opts.after(gpus.length);
|
||||||
|
}
|
||||||
|
};`
|
||||||
@@ -91,6 +91,7 @@ tbody tr:hover td{background:rgba(0,0,0,.03)}
|
|||||||
.alert-info{background:#dff0ff;border:1px solid #a9d4f5;color:#1e3a5f}
|
.alert-info{background:#dff0ff;border:1px solid #a9d4f5;color:#1e3a5f}
|
||||||
.alert-warn{background:var(--warn-bg);border:1px solid #c9ba9b;color:var(--warn-fg)}
|
.alert-warn{background:var(--warn-bg);border:1px solid #c9ba9b;color:var(--warn-fg)}
|
||||||
.alert-crit{background:var(--crit-bg);border:1px solid var(--crit-border);color:var(--crit-fg);font-weight:700}
|
.alert-crit{background:var(--crit-bg);border:1px solid var(--crit-border);color:var(--crit-fg);font-weight:700}
|
||||||
|
` + gpuPickerCSS + `
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -97,8 +97,6 @@ func renderBenchmark(opts HandlerOptions) string {
|
|||||||
<style>
|
<style>
|
||||||
.benchmark-cb-row { display:flex; align-items:flex-start; gap:8px; cursor:pointer; font-size:13px; }
|
.benchmark-cb-row { display:flex; align-items:flex-start; gap:8px; cursor:pointer; font-size:13px; }
|
||||||
.benchmark-cb-row input[type=checkbox] { width:16px; height:16px; margin-top:2px; flex-shrink:0; }
|
.benchmark-cb-row input[type=checkbox] { width:16px; height:16px; margin-top:2px; flex-shrink:0; }
|
||||||
.benchmark-gpu-row { display:flex; align-items:flex-start; gap:8px; padding:6px 0; cursor:pointer; font-size:13px; }
|
|
||||||
.benchmark-gpu-row input[type=checkbox] { width:16px; height:16px; margin-top:2px; flex-shrink:0; }
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -142,21 +140,11 @@ function benchmarkUpdateSelectionNote() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function benchmarkRenderGPUList(gpus) {
|
function benchmarkRenderGPUList(gpus) {
|
||||||
const root = document.getElementById('benchmark-gpu-list');
|
beeGpuPicker.render({
|
||||||
if (!gpus || !gpus.length) {
|
rootId: 'benchmark-gpu-list', gpus: gpus,
|
||||||
root.innerHTML = '<p style="color:var(--muted);font-size:13px">No NVIDIA GPUs detected.</p>';
|
checkboxClass: 'benchmark-gpu-checkbox', onToggle: 'benchmarkUpdateSelectionNote()',
|
||||||
benchmarkUpdateSelectionNote();
|
after: function(n) { benchmarkApplyMultiGPUState(n); benchmarkUpdateSelectionNote(); },
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
root.innerHTML = gpus.map(function(gpu) {
|
|
||||||
const mem = gpu.memory_mb > 0 ? ' · ' + gpu.memory_mb + ' MiB' : '';
|
|
||||||
return '<label class="benchmark-gpu-row">'
|
|
||||||
+ '<input class="benchmark-gpu-checkbox" type="checkbox" value="' + gpu.index + '" checked onchange="benchmarkUpdateSelectionNote()">'
|
|
||||||
+ '<span><strong>GPU ' + gpu.index + '</strong> — ' + gpu.name + mem + '</span>'
|
|
||||||
+ '</label>';
|
|
||||||
}).join('');
|
|
||||||
benchmarkApplyMultiGPUState(gpus.length);
|
|
||||||
benchmarkUpdateSelectionNote();
|
|
||||||
}
|
}
|
||||||
function benchmarkApplyMultiGPUState(gpuCount) {
|
function benchmarkApplyMultiGPUState(gpuCount) {
|
||||||
var multiValues = ['parallel', 'ramp-up'];
|
var multiValues = ['parallel', 'ramp-up'];
|
||||||
|
|||||||
@@ -92,8 +92,6 @@ func renderBurn() string {
|
|||||||
.cb-row input[type=checkbox]:disabled { opacity:0.4; cursor:not-allowed; }
|
.cb-row input[type=checkbox]:disabled { opacity:0.4; cursor:not-allowed; }
|
||||||
.cb-row input[type=checkbox]:disabled ~ span { opacity:0.45; cursor:not-allowed; }
|
.cb-row input[type=checkbox]:disabled ~ span { opacity:0.45; cursor:not-allowed; }
|
||||||
.cb-note { font-size:11px; color:var(--muted); font-style:italic; }
|
.cb-note { font-size:11px; color:var(--muted); font-style:italic; }
|
||||||
.burn-gpu-row { display:flex; align-items:flex-start; gap:8px; padding:6px 0; cursor:pointer; font-size:13px; }
|
|
||||||
.burn-gpu-row input[type=checkbox] { width:16px; height:16px; margin-top:2px; flex-shrink:0; }
|
|
||||||
.burn-profile-body { display:grid; grid-template-columns:1fr 1fr 1fr; gap:24px; align-items:stretch; }
|
.burn-profile-body { display:grid; grid-template-columns:1fr 1fr 1fr; gap:24px; align-items:stretch; }
|
||||||
.burn-profile-col { min-width:0; }
|
.burn-profile-col { min-width:0; }
|
||||||
.burn-profile-action { display:flex; flex-direction:column; align-items:center; justify-content:flex-start; gap:8px; }
|
.burn-profile-action { display:flex; flex-direction:column; align-items:center; justify-content:flex-start; gap:8px; }
|
||||||
@@ -159,21 +157,11 @@ function burnUpdateSelectionNote() {
|
|||||||
note.textContent = 'Selected NVIDIA GPUs: ' + selected.join(', ') + '. Official and custom NVIDIA tasks will use only these GPUs.';
|
note.textContent = 'Selected NVIDIA GPUs: ' + selected.join(', ') + '. Official and custom NVIDIA tasks will use only these GPUs.';
|
||||||
}
|
}
|
||||||
function burnRenderGPUList(gpus) {
|
function burnRenderGPUList(gpus) {
|
||||||
const root = document.getElementById('burn-gpu-list');
|
beeGpuPicker.render({
|
||||||
if (!gpus || !gpus.length) {
|
rootId: 'burn-gpu-list', gpus: gpus,
|
||||||
root.innerHTML = '<p style="color:var(--muted);font-size:13px">No NVIDIA GPUs detected.</p>';
|
checkboxClass: 'burn-gpu-checkbox', onToggle: 'burnUpdateSelectionNote()',
|
||||||
burnUpdateSelectionNote();
|
after: function(n) { burnApplyMultiGPUState(n); burnUpdateSelectionNote(); },
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
root.innerHTML = gpus.map(function(gpu) {
|
|
||||||
const mem = gpu.memory_mb > 0 ? ' · ' + gpu.memory_mb + ' MiB' : '';
|
|
||||||
return '<label class="burn-gpu-row">'
|
|
||||||
+ '<input class="burn-gpu-checkbox" type="checkbox" value="' + gpu.index + '" checked onchange="burnUpdateSelectionNote()">'
|
|
||||||
+ '<span><strong>GPU ' + gpu.index + '</strong> — ' + gpu.name + mem + '</span>'
|
|
||||||
+ '</label>';
|
|
||||||
}).join('');
|
|
||||||
burnApplyMultiGPUState(gpus.length);
|
|
||||||
burnUpdateSelectionNote();
|
|
||||||
}
|
}
|
||||||
function burnSelectAll() {
|
function burnSelectAll() {
|
||||||
document.querySelectorAll('.burn-gpu-checkbox').forEach(function(el) { el.checked = true; });
|
document.querySelectorAll('.burn-gpu-checkbox').forEach(function(el) { el.checked = true; });
|
||||||
|
|||||||
@@ -212,8 +212,6 @@ func renderValidateMode(opts HandlerOptions, stressDefault bool) string {
|
|||||||
.validate-card-body { padding:0; }
|
.validate-card-body { padding:0; }
|
||||||
.validate-card-section { padding:12px 16px 0; }
|
.validate-card-section { padding:12px 16px 0; }
|
||||||
.validate-card-section:last-child { padding-bottom:16px; }
|
.validate-card-section:last-child { padding-bottom:16px; }
|
||||||
.sat-gpu-row { display:flex; align-items:flex-start; gap:8px; padding:6px 0; cursor:pointer; font-size:13px; }
|
|
||||||
.sat-gpu-row input[type=checkbox] { width:16px; height:16px; margin-top:2px; flex-shrink:0; }
|
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
let satES = null;
|
let satES = null;
|
||||||
@@ -251,21 +249,11 @@ function satUpdateGPUSelectionNote() {
|
|||||||
note.textContent = 'Selected GPUs: ' + selected.join(', ') + '. Multi-GPU tests will use all selected GPUs.';
|
note.textContent = 'Selected GPUs: ' + selected.join(', ') + '. Multi-GPU tests will use all selected GPUs.';
|
||||||
}
|
}
|
||||||
function satRenderGPUList(gpus) {
|
function satRenderGPUList(gpus) {
|
||||||
const root = document.getElementById('sat-gpu-list');
|
beeGpuPicker.render({
|
||||||
if (!root) return;
|
rootId: 'sat-gpu-list', gpus: gpus,
|
||||||
if (!gpus || !gpus.length) {
|
checkboxClass: 'sat-nvidia-checkbox', onToggle: 'satUpdateGPUSelectionNote()',
|
||||||
root.innerHTML = '<p style="color:var(--muted);font-size:13px">No NVIDIA GPUs detected.</p>';
|
after: function() { satUpdateGPUSelectionNote(); },
|
||||||
satUpdateGPUSelectionNote();
|
});
|
||||||
return;
|
|
||||||
}
|
|
||||||
root.innerHTML = gpus.map(function(gpu) {
|
|
||||||
const mem = gpu.memory_mb > 0 ? ' · ' + gpu.memory_mb + ' MiB' : '';
|
|
||||||
return '<label class="sat-gpu-row">'
|
|
||||||
+ '<input class="sat-nvidia-checkbox" type="checkbox" value="' + gpu.index + '" checked onchange="satUpdateGPUSelectionNote()">'
|
|
||||||
+ '<span><strong>GPU ' + gpu.index + '</strong> — ' + gpu.name + mem + '</span>'
|
|
||||||
+ '</label>';
|
|
||||||
}).join('');
|
|
||||||
satUpdateGPUSelectionNote();
|
|
||||||
}
|
}
|
||||||
function satSelectAllGPUs() {
|
function satSelectAllGPUs() {
|
||||||
document.querySelectorAll('.sat-nvidia-checkbox').forEach(function(el) { el.checked = true; });
|
document.querySelectorAll('.sat-nvidia-checkbox').forEach(function(el) { el.checked = true; });
|
||||||
@@ -729,8 +717,6 @@ func renderCheck(opts HandlerOptions) string {
|
|||||||
.validate-card-body { padding:0; }
|
.validate-card-body { padding:0; }
|
||||||
.validate-card-section { padding:12px 16px 0; }
|
.validate-card-section { padding:12px 16px 0; }
|
||||||
.validate-card-section:last-child { padding-bottom:16px; }
|
.validate-card-section:last-child { padding-bottom:16px; }
|
||||||
.sat-gpu-row { display:flex; align-items:flex-start; gap:8px; padding:6px 0; cursor:pointer; font-size:13px; }
|
|
||||||
.sat-gpu-row input[type=checkbox] { width:16px; height:16px; margin-top:2px; flex-shrink:0; }
|
|
||||||
.cb-row { display:flex; align-items:flex-start; gap:8px; padding:4px 0; cursor:pointer; font-size:13px; }
|
.cb-row { display:flex; align-items:flex-start; gap:8px; padding:4px 0; cursor:pointer; font-size:13px; }
|
||||||
.cb-row input[type=checkbox] { width:16px; height:16px; margin-top:2px; flex-shrink:0; }
|
.cb-row input[type=checkbox] { width:16px; height:16px; margin-top:2px; flex-shrink:0; }
|
||||||
</style>
|
</style>
|
||||||
@@ -765,17 +751,11 @@ function satUpdateGPUSelectionNote() {
|
|||||||
: 'Select at least one NVIDIA GPU to enable NVIDIA check tasks.';
|
: 'Select at least one NVIDIA GPU to enable NVIDIA check tasks.';
|
||||||
}
|
}
|
||||||
function satRenderGPUList(gpus) {
|
function satRenderGPUList(gpus) {
|
||||||
const root = document.getElementById('sat-gpu-list');
|
beeGpuPicker.render({
|
||||||
if (!root) return;
|
rootId: 'sat-gpu-list', gpus: gpus,
|
||||||
if (!gpus || !gpus.length) {
|
checkboxClass: 'sat-nvidia-checkbox', onToggle: 'satUpdateGPUSelectionNote()',
|
||||||
root.innerHTML = '<p style="color:var(--muted);font-size:13px">No NVIDIA GPUs detected.</p>';
|
after: function() { satUpdateGPUSelectionNote(); },
|
||||||
satUpdateGPUSelectionNote(); return;
|
});
|
||||||
}
|
|
||||||
root.innerHTML = gpus.map(gpu => {
|
|
||||||
const mem = gpu.memory_mb > 0 ? ' · ' + gpu.memory_mb + ' MiB' : '';
|
|
||||||
return '<label class="sat-gpu-row"><input class="sat-nvidia-checkbox" type="checkbox" value="' + gpu.index + '" checked onchange="satUpdateGPUSelectionNote()"><span><strong>GPU ' + gpu.index + '</strong> — ' + gpu.name + mem + '</span></label>';
|
|
||||||
}).join('');
|
|
||||||
satUpdateGPUSelectionNote();
|
|
||||||
}
|
}
|
||||||
function satSelectAllGPUs() { document.querySelectorAll('.sat-nvidia-checkbox').forEach(el => { el.checked = true; }); satUpdateGPUSelectionNote(); }
|
function satSelectAllGPUs() { document.querySelectorAll('.sat-nvidia-checkbox').forEach(el => { el.checked = true; }); satUpdateGPUSelectionNote(); }
|
||||||
function satSelectNoGPUs() { document.querySelectorAll('.sat-nvidia-checkbox').forEach(el => { el.checked = false; }); satUpdateGPUSelectionNote(); }
|
function satSelectNoGPUs() { document.querySelectorAll('.sat-nvidia-checkbox').forEach(el => { el.checked = false; }); satUpdateGPUSelectionNote(); }
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ function openComponentDetail(type) {
|
|||||||
body.innerHTML = '<div style="padding:20px;color:var(--crit-fg)">Error loading details.</div>';
|
body.innerHTML = '<div style="padding:20px;color:var(--crit-fg)">Error loading details.</div>';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
` + gpuPickerJS + `
|
||||||
</script>` +
|
</script>` +
|
||||||
`</body></html>`
|
`</body></html>`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,13 @@ This happens for:
|
|||||||
**File:** `audit/internal/webui/pages.go`
|
**File:** `audit/internal/webui/pages.go`
|
||||||
|
|
||||||
- Source: `GET /api/gpus` → `api.go` → `ListNvidiaGPUs()` → live nvidia-smi
|
- Source: `GET /api/gpus` → `api.go` → `ListNvidiaGPUs()` → live nvidia-smi
|
||||||
- Render: `'GPU ' + gpu.index + ' — ' + gpu.name + ' · ' + mem`
|
- Render: `'GPU N — <model> · <mem> MiB · sn: <serial>'`
|
||||||
|
(serial from `nvidia-smi --query-gpu=...,serial`; omitted when N/A)
|
||||||
|
- **Single source:** `audit/internal/webui/gpu_picker.go` — `beeGpuPicker.render({...})` builds every
|
||||||
|
row. All three pages (Load/SAT, Burn, Benchmark) call it from their `*RenderGPUList` wrapper;
|
||||||
|
`gpuPickerCSS`/`gpuPickerJS` are injected once by `layoutHead`/`renderPage`.
|
||||||
|
- Serial is rendered monospace; the digits that differ across the listed GPUs (common
|
||||||
|
prefix/suffix stripped) are bolded in accent colour.
|
||||||
- Fallback: `gpu.name || 'GPU ' + idx` (JS, line ~1432)
|
- Fallback: `gpu.name || 'GPU ' + idx` (JS, line ~1432)
|
||||||
|
|
||||||
This always shows the correct model because it queries nvidia-smi live. It is **not** connected to benchmark result data.
|
This always shows the correct model because it queries nvidia-smi live. It is **not** connected to benchmark result data.
|
||||||
|
|||||||
Reference in New Issue
Block a user