Add Show Widget for library actions with Overview charts from last run output.

This commit is contained in:
2026-07-20 20:48:06 +02:00
parent e047499fed
commit d188ca41b8
20 changed files with 1918 additions and 128 deletions
+125
View File
@@ -0,0 +1,125 @@
package settings
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"time"
)
// LoadActionWidgets reads and parses node-action-widgets.json from dir.
func LoadActionWidgets(dir string) (ActionWidgetStore, error) {
path := filepath.Join(dir, NodeActionWidgetsFileName)
payload, err := os.ReadFile(path)
if err != nil {
return ActionWidgetStore{}, err
}
var store ActionWidgetStore
if err := json.Unmarshal(payload, &store); err != nil {
return ActionWidgetStore{}, fmt.Errorf("parse action widgets: %w", err)
}
if store.Widgets == nil {
store.Widgets = []ActionWidgetSnapshot{}
}
return store, nil
}
// LoadActionWidgetsOrEmpty returns an empty store when the file is missing.
func LoadActionWidgetsOrEmpty(dir string) (ActionWidgetStore, error) {
store, err := LoadActionWidgets(dir)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return ActionWidgetStore{Widgets: []ActionWidgetSnapshot{}}, nil
}
return ActionWidgetStore{}, err
}
return store, nil
}
// SaveActionWidgets writes node-action-widgets.json to dir with mode 0640.
func SaveActionWidgets(dir string, store ActionWidgetStore) error {
if err := ensureConfigDir(dir); err != nil {
return err
}
if store.Widgets == nil {
store.Widgets = []ActionWidgetSnapshot{}
}
payload, err := json.MarshalIndent(store, "", " ")
if err != nil {
return fmt.Errorf("encode action widgets: %w", err)
}
payload = append(payload, '\n')
path := filepath.Join(dir, NodeActionWidgetsFileName)
return os.WriteFile(path, payload, 0o640)
}
// WidgetsForNode returns snapshots belonging to nodeID.
func WidgetsForNode(store ActionWidgetStore, nodeID string) []ActionWidgetSnapshot {
out := make([]ActionWidgetSnapshot, 0)
for _, widget := range store.Widgets {
if widget.NodeID == nodeID {
out = append(out, widget)
}
}
return out
}
// UpsertActionWidgetSnapshot replaces or appends a snapshot keyed by node_id + item_id.
func UpsertActionWidgetSnapshot(dir string, snapshot ActionWidgetSnapshot) error {
if strings.TrimSpace(snapshot.NodeID) == "" {
return fmt.Errorf("node id is required")
}
if strings.TrimSpace(snapshot.ItemID) == "" {
return fmt.Errorf("item id is required")
}
if snapshot.UpdatedAt.IsZero() {
snapshot.UpdatedAt = time.Now().UTC()
}
store, err := LoadActionWidgetsOrEmpty(dir)
if err != nil {
return err
}
found := false
for index := range store.Widgets {
if store.Widgets[index].NodeID == snapshot.NodeID &&
store.Widgets[index].ItemID == snapshot.ItemID {
store.Widgets[index] = snapshot
found = true
break
}
}
if !found {
store.Widgets = append(store.Widgets, snapshot)
}
return SaveActionWidgets(dir, store)
}
// DeleteActionWidgetsForItem removes the snapshot for a node action item, if any.
func DeleteActionWidgetsForItem(dir string, nodeID string, itemID string) error {
store, err := LoadActionWidgetsOrEmpty(dir)
if err != nil {
return err
}
filtered := make([]ActionWidgetSnapshot, 0, len(store.Widgets))
changed := false
for _, widget := range store.Widgets {
if widget.NodeID == nodeID && widget.ItemID == itemID {
changed = true
continue
}
filtered = append(filtered, widget)
}
if !changed {
return nil
}
store.Widgets = filtered
return SaveActionWidgets(dir, store)
}