mirror of
https://github.com/Dvorinka/Dash.git
synced 2026-10-28 15:51:27 +00:00
feat(api): phase 5 — alerts, agent depth, subdomains, auth, boards
P5b: notifier transport registry (webhook/slack/discord/telegram/ gotify/ntfy/smtp via net/smtp), per-monitor+domain alert rules (failsBeforeDown, latencyWarn, certDays, mute). P5c: per-container cpu/mem, optional SMART/ZFS/GPU exec-or-skip collectors, crt.sh subdomain discovery with daily sweep. P5d: opt-in auth (bcrypt, sqlite sessions, 7d rotating cookie, login rate limit, public-route bypass) and multiple boards (boards table, sections.board_id, /api/boards CRUD, slug routing). Authored By: TDvorak <[email protected]> Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
8973be3b41
commit
f3143af351
33 files changed
+2772
-126
No files matched your search
@@ -0,0 +1,89 @@
|
||||
package widget
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// JSON status: GET a URL, extract one value by dot/bracket path
|
||||
// ("data.status", "items[0].name"), render it in a tile. Covers the
|
||||
// "is API X alive / what does it report" case without per-service widgets.
|
||||
type jsonpath struct{}
|
||||
|
||||
func init() { Register(jsonpath{}) }
|
||||
|
||||
func (jsonpath) Meta() Type {
|
||||
return Type{
|
||||
Type: "jsonpath",
|
||||
Name: "JSON value",
|
||||
Description: "Extract one value from a JSON endpoint",
|
||||
Fields: []Field{
|
||||
{Key: "url", Label: "URL", Placeholder: "https://api…/status", Required: true},
|
||||
{Key: "path", Label: "Path", Placeholder: "data.status", Required: true},
|
||||
{Key: "header", Label: "Header", Placeholder: "Authorization: Bearer …"},
|
||||
{Key: "label", Label: "Label", Placeholder: "Status"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (jsonpath) Fetch(ctx context.Context, cfg json.RawMessage) (any, error) {
|
||||
url := cfgStr(cfg, "url")
|
||||
path := cfgStr(cfg, "path")
|
||||
if url == "" || path == "" {
|
||||
return nil, fmt.Errorf("url and path required")
|
||||
}
|
||||
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
|
||||
return nil, fmt.Errorf("url must be http(s)")
|
||||
}
|
||||
var headers map[string]string
|
||||
if h := cfgStr(cfg, "header"); h != "" {
|
||||
if k, v, ok := strings.Cut(h, ":"); ok {
|
||||
headers = map[string]string{strings.TrimSpace(k): strings.TrimSpace(v)}
|
||||
}
|
||||
}
|
||||
var doc any
|
||||
if err := getJSON(ctx, url, headers, &doc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v, ok := jsonPath(doc, path)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("path %q not found", path)
|
||||
}
|
||||
return map[string]any{"value": v, "label": cfgStr(cfg, "label")}, nil
|
||||
}
|
||||
|
||||
// jsonPath walks doc along a path like "a.b[0].c". Segments split on dots;
|
||||
// a trailing [n] indexes into arrays. Returns false on any miss.
|
||||
func jsonPath(doc any, path string) (any, bool) {
|
||||
cur := doc
|
||||
for _, seg := range strings.Split(path, ".") {
|
||||
name, rest, _ := strings.Cut(seg, "[")
|
||||
if name != "" {
|
||||
m, ok := cur.(map[string]any)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
cur, ok = m[name]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
for rest != "" {
|
||||
idxStr, tail, _ := strings.Cut(rest, "]")
|
||||
i, err := strconv.Atoi(idxStr)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
arr, ok := cur.([]any)
|
||||
if !ok || i < 0 || i >= len(arr) {
|
||||
return nil, false
|
||||
}
|
||||
cur = arr[i]
|
||||
rest = strings.TrimPrefix(tail, "[")
|
||||
}
|
||||
}
|
||||
return cur, true
|
||||
}
|
||||
Reference in new issue
Block a user