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:
co-authored by
Claude Sonnet 5
parent
5a780e96b4
commit
d108df7fe9
@@ -1,6 +1,7 @@
|
||||
package platform
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -207,8 +208,28 @@ func (s *System) ReadScenarioFromRemovableMedia(name string) ([]byte, error) {
|
||||
// ScenarioFileOnRemovableMedia is one scenarios/*.json file found on a
|
||||
// mounted removable target, as returned by ListScenarioFilesOnRemovableMedia.
|
||||
type ScenarioFileOnRemovableMedia struct {
|
||||
Name string // filename without ".json" — what ReadScenarioFromRemovableMedia/bee run expects
|
||||
Device string // which removable target it was found on
|
||||
Name string // filename without ".json" — what ReadScenarioFromRemovableMedia/bee run expects
|
||||
Description string // from the scenario's own "description" field, if it parses; "" otherwise
|
||||
Device string // which removable target it was found on
|
||||
}
|
||||
|
||||
// scenarioDescription reads a scenario file's "description" field without
|
||||
// requiring the whole file to pass ParseScenarioJSON's stricter validation
|
||||
// — a listing shouldn't hide (or crash on) a scenario just because it, say,
|
||||
// hasn't gotten a "type" filled in on every job yet. Returns "" if the file
|
||||
// can't be read or parsed at all.
|
||||
func scenarioDescription(path string) string {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
var partial struct {
|
||||
Description string `json:"description"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &partial); err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(partial.Description)
|
||||
}
|
||||
|
||||
// ListScenarioFilesOnRemovableMedia mounts each removable target in turn
|
||||
@@ -227,15 +248,17 @@ func (s *System) ListScenarioFilesOnRemovableMedia() ([]ScenarioFileOnRemovableM
|
||||
if mountErr != nil {
|
||||
continue
|
||||
}
|
||||
entries, readErr := os.ReadDir(filepath.Join(mountpoint, "scenarios"))
|
||||
scenariosDir := filepath.Join(mountpoint, "scenarios")
|
||||
entries, readErr := os.ReadDir(scenariosDir)
|
||||
if readErr == nil {
|
||||
for _, e := range entries {
|
||||
if e.IsDir() || !strings.HasSuffix(e.Name(), ".json") {
|
||||
continue
|
||||
}
|
||||
out = append(out, ScenarioFileOnRemovableMedia{
|
||||
Name: strings.TrimSuffix(e.Name(), ".json"),
|
||||
Device: target.Device,
|
||||
Name: strings.TrimSuffix(e.Name(), ".json"),
|
||||
Description: scenarioDescription(filepath.Join(scenariosDir, e.Name())),
|
||||
Device: target.Device,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -280,8 +303,9 @@ func (s *System) ListLocalScenarioFiles() ([]ScenarioFileOnRemovableMedia, error
|
||||
continue
|
||||
}
|
||||
out = append(out, ScenarioFileOnRemovableMedia{
|
||||
Name: strings.TrimSuffix(e.Name(), ".json"),
|
||||
Device: "local (shipped with image)",
|
||||
Name: strings.TrimSuffix(e.Name(), ".json"),
|
||||
Description: scenarioDescription(filepath.Join(LocalScenariosDir, e.Name())),
|
||||
Device: "local (shipped with image)",
|
||||
})
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name })
|
||||
|
||||
Reference in New Issue
Block a user