platform/webui: add scenario description, show it in the Scenario page's list

ScenarioSpec gains an optional "description" field. Listing (both
ListLocalScenarioFiles and ListScenarioFilesOnRemovableMedia, via the new
scenarioDescription helper) reads it out of each file without requiring
full ParseScenarioJSON validation to succeed, so a listing never hides a
scenario over an unrelated validation issue. The webui Scenario page now
renders Name/Description/Found-on/Run instead of just Name/Found-on — a
bare filename rarely tells anyone but the author what a scenario actually
does.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-07-28 19:28:07 +03:00
co-authored by Claude Sonnet 5
parent 5a780e96b4
commit d108df7fe9
7 changed files with 61 additions and 17 deletions
+4 -3
View File
@@ -588,12 +588,13 @@ func (h *handler) handleAPIScenarioList(w http.ResponseWriter, _ *http.Request)
return
}
type scenarioFile struct {
Name string `json:"name"`
Device string `json:"device"`
Name string `json:"name"`
Description string `json:"description"`
Device string `json:"device"`
}
out := make([]scenarioFile, 0, len(files))
for _, f := range files {
out = append(out, scenarioFile{Name: f.Name, Device: f.Device})
out = append(out, scenarioFile{Name: f.Name, Description: f.Description, Device: f.Device})
}
writeJSON(w, out)
}
+6 -3
View File
@@ -44,10 +44,13 @@ function scenarioRefresh() {
list.innerHTML = '<p style="color:var(--muted);font-size:13px">No scenarios found — none shipped with this image and none under scenarios/ on mounted removable media.</p>';
return;
}
let html = '<table class="table"><thead><tr><th>Name</th><th>Found on</th><th></th></tr></thead><tbody>';
let html = '<table class="table"><thead><tr><th>Name</th><th>Description</th><th>Found on</th><th></th></tr></thead><tbody>';
for (const f of files) {
html += '<tr><td>' + escapeHTML(f.name) + '</td><td style="color:var(--muted);font-size:12px">' + escapeHTML(f.device) + '</td>'
+ '<td><button class="btn btn-sm btn-primary" onclick="scenarioRun(' + JSON.stringify(f.name) + ', this)">&#9654; Run</button></td></tr>';
const desc = f.description ? escapeHTML(f.description) : '<span style="color:var(--muted)">—</span>';
html += '<tr><td style="white-space:nowrap">' + escapeHTML(f.name) + '</td>'
+ '<td style="font-size:12px;max-width:360px;color:var(--muted)">' + desc + '</td>'
+ '<td style="color:var(--muted);font-size:12px;white-space:nowrap">' + escapeHTML(f.device) + '</td>'
+ '<td style="white-space:nowrap"><button class="btn btn-primary" onclick="scenarioRun(' + JSON.stringify(f.name) + ', this)">&#9654; Run</button></td></tr>';
}
html += '</tbody></table>';
list.innerHTML = html;