Files

151 lines
3.9 KiB
Go

package automation
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"time"
"github.com/google/uuid"
)
type ScriptRunRecord struct {
ID string `json:"id"`
ScriptID string `json:"scriptId"`
ScriptName string `json:"scriptName"`
ScriptType string `json:"scriptType"`
Status string `json:"status"`
Summary string `json:"summary"`
Error string `json:"error"`
ResultText string `json:"resultText"`
LogText string `json:"logText"`
StartedAt string `json:"startedAt"`
FinishedAt string `json:"finishedAt"`
DurationMs int64 `json:"durationMs"`
}
type ScriptRunRequest struct {
ScriptID string `json:"scriptId"`
SelectorText string `json:"selectorText"`
TargetMode string `json:"targetMode,omitempty"`
TargetInput any `json:"targetInput,omitempty"`
ParamsText string `json:"paramsText"`
UseScriptSelector bool `json:"useScriptSelector"`
UseScriptParams bool `json:"useScriptParams"`
TimeoutMs int `json:"timeoutMs,omitempty"`
}
type ScriptRunStore struct {
rootDir string
}
func NewScriptRunStore(rootDir string) *ScriptRunStore {
return &ScriptRunStore{
rootDir: filepath.Clean(strings.TrimSpace(rootDir)),
}
}
func (s *ScriptRunStore) Save(input ScriptRunRecord) (ScriptRunRecord, error) {
record := normalizeScriptRunRecord(input)
if err := os.MkdirAll(s.rootDir, 0o755); err != nil {
return ScriptRunRecord{}, fmt.Errorf("create automation run dir failed: %w", err)
}
data, err := json.MarshalIndent(record, "", " ")
if err != nil {
return ScriptRunRecord{}, fmt.Errorf("marshal automation run record failed: %w", err)
}
if err := writeFileAtomic(filepath.Join(s.rootDir, record.ID+".json"), data, 0o644); err != nil {
return ScriptRunRecord{}, fmt.Errorf("write automation run record failed: %w", err)
}
return record, nil
}
func (s *ScriptRunStore) List(limit int) ([]ScriptRunRecord, error) {
if limit <= 0 {
limit = 20
}
if limit > 200 {
limit = 200
}
if err := os.MkdirAll(s.rootDir, 0o755); err != nil {
return nil, fmt.Errorf("create automation run dir failed: %w", err)
}
entries, err := os.ReadDir(s.rootDir)
if err != nil {
if os.IsNotExist(err) {
return []ScriptRunRecord{}, nil
}
return nil, fmt.Errorf("read automation run dir failed: %w", err)
}
items := make([]ScriptRunRecord, 0, len(entries))
for _, entry := range entries {
if entry.IsDir() || !strings.HasSuffix(strings.ToLower(entry.Name()), ".json") {
continue
}
data, err := os.ReadFile(filepath.Join(s.rootDir, entry.Name()))
if err != nil {
continue
}
var record ScriptRunRecord
if err := json.Unmarshal(data, &record); err != nil {
continue
}
items = append(items, normalizeScriptRunRecord(record))
}
sort.Slice(items, func(i, j int) bool {
return parseRFC3339OrZero(items[i].StartedAt).After(parseRFC3339OrZero(items[j].StartedAt))
})
if len(items) > limit {
items = items[:limit]
}
return items, nil
}
func normalizeScriptRunRecord(input ScriptRunRecord) ScriptRunRecord {
now := time.Now().Format(time.RFC3339)
id := strings.TrimSpace(input.ID)
if id == "" {
id = uuid.NewString()
}
status := strings.TrimSpace(input.Status)
switch status {
case "success", "failed", "running":
default:
status = "failed"
}
startedAt := firstNonEmpty(input.StartedAt, now)
finishedAt := firstNonEmpty(input.FinishedAt, startedAt)
durationMs := input.DurationMs
if durationMs < 0 {
durationMs = 0
}
return ScriptRunRecord{
ID: id,
ScriptID: strings.TrimSpace(input.ScriptID),
ScriptName: strings.TrimSpace(input.ScriptName),
ScriptType: strings.TrimSpace(input.ScriptType),
Status: status,
Summary: strings.TrimSpace(input.Summary),
Error: strings.TrimSpace(input.Error),
ResultText: strings.TrimSpace(input.ResultText),
LogText: strings.TrimSpace(input.LogText),
StartedAt: startedAt,
FinishedAt: finishedAt,
DurationMs: durationMs,
}
}