Monitor update
Deploy / deploy (push) Successful in 8s

This commit is contained in:
2026-08-31 22:42:40 +05:00
parent 47ea18d020
commit 28702bd029
13 changed files with 434 additions and 217 deletions
+40 -11
View File
@@ -8,6 +8,7 @@ import (
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
@@ -24,11 +25,16 @@ type Item struct {
Name string `yaml:"name"`
Script string `yaml:"script"`
Refresh int `yaml:"refresh"` // monitor: seconds
Data string `yaml:"data"` // monitor: text | percentage
Args int `yaml:"args"` // exec: arg count
ArgTypes []string `yaml:"arg_types"` // exec: text | number per arg
}
type Monitor struct {
Name string `yaml:"name"`
Script string `yaml:"script"`
Refresh int `yaml:"refresh"` // seconds
}
type Link struct {
Name string `yaml:"name"`
URL string `yaml:"url"`
@@ -39,7 +45,7 @@ type Link struct {
type server struct {
cfg *Config
exec map[string]*Item // by item name, read-only after start
monVals sync.Map // monitor item name -> string (last output)
monVals sync.Map // monitor item name -> *monData (last parsed output)
docsDir string
srvDir string // dir with *.service units (~/srv on the box)
probeErr sync.Map // probe key -> last logged error; poll errors log once per change, not every 3s tick
@@ -48,10 +54,34 @@ type server struct {
const execTimeout = 10 * time.Second
func runScript(s *Item, args ...string) (string, error) {
// monData — распарсенный вывод скрипта-монитора: первая строка — процент,
// остальные — строки key=value для подписей в карточке.
type monData struct {
percentage int32 // -1 = скрипт не дал процент
stats []*homelabv1.Stat
}
func parseMonitor(out string) *monData {
d := &monData{percentage: -1}
lines := strings.Split(strings.TrimSpace(out), "\n")
if lines[0] == "" {
return d
}
if n, err := strconv.Atoi(strings.TrimSpace(lines[0])); err == nil {
d.percentage = int32(n)
}
for _, l := range lines[1:] {
if k, v, ok := strings.Cut(strings.TrimSpace(l), "="); ok {
d.stats = append(d.stats, &homelabv1.Stat{Name: strings.TrimSpace(k), Value: strings.TrimSpace(v)})
}
}
return d
}
func runScript(script string, args ...string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), execTimeout)
defer cancel()
out, err := exec.CommandContext(ctx, s.Script, args...).CombinedOutput()
out, err := exec.CommandContext(ctx, script, args...).CombinedOutput()
if err != nil {
return "", fmt.Errorf("%v: %s", err, out)
}
@@ -62,17 +92,16 @@ func (s *server) startMonitors() {
for i := range s.cfg.Monitor {
sc := &s.cfg.Monitor[i]
log.Printf("monitor started: %s (%s) every %ds", sc.Name, sc.Script, sc.Refresh)
go func(sc *Item) {
go func(sc *Monitor) {
for {
v, err := runScript(sc)
out, err := runScript(sc.Script)
if err != nil {
log.Printf("monitor %s: %v", sc.Name, err)
v = "error: " + err.Error()
} else {
v = strings.TrimSpace(v)
log.Printf("monitor %s -> %s", sc.Name, v)
out = "-\nerror=" + err.Error()
}
s.monVals.Store(sc.Name, v)
d := parseMonitor(out)
log.Printf("monitor %s -> %d%% (%d stats)", sc.Name, d.percentage, len(d.stats))
s.monVals.Store(sc.Name, d)
time.Sleep(time.Duration(sc.Refresh) * time.Second)
}
}(sc)