Files
ITWorx Pulse release export bd774932d5
Public source validation / validate (push) Failing after 3m8s
Publish ITWorx Pulse source
2026-09-03 02:09:19 +02:00

181 lines
5.8 KiB
Go

package widgetpreview
import (
"fmt"
"math"
"strings"
)
const (
MaxSeries = 20
MaxPoints = 2000
MaxRows = 1000
)
var validTypes = map[string]bool{
"stat": true, "timeseries": true, "gauge": true, "ranked-list": true,
"status-grid": true, "table": true, "heatmap": true, "event-timeline": true,
"storage-map": true, "topology": true, "service-matrix": true, "alert-summary": true,
"text": true, "query-inspector": true,
}
var validSources = map[string]bool{
"semantic-metric": true, "inventory": true, "events": true,
"alerts": true, "incidents": true, "text": true,
}
var validStates = map[string]string{
"loading": "Voorbeeld wordt geladen.",
"empty": "Geen gegevens binnen de ingestelde scope.",
"error": "De bron gaf een fout terug.",
"stale": "De gegevens zijn verouderd en moeten opnieuw worden gecontroleerd.",
}
var validRanges = map[string]bool{"live": true, "15m": true, "1h": true, "6h": true, "24h": true, "7d": true}
var validAggregations = map[string]bool{"avg": true, "min": true, "max": true, "sum": true, "last": true}
type InvalidConfig struct{ Fields map[string]string }
func (e InvalidConfig) Error() string { return "widget configuration is invalid" }
type Limits struct {
MaxSeries int `json:"maxSeries"`
MaxPoints int `json:"maxPoints"`
MaxRows int `json:"maxRows"`
RequestedRows int `json:"requestedRows"`
AppliedRows int `json:"appliedRows"`
}
type Response struct {
Type string `json:"type"`
Title string `json:"title"`
State string `json:"state"`
Message string `json:"message"`
Sample map[string]any `json:"sample"`
Limits Limits `json:"limits"`
}
func Preview(widget map[string]any, state string) (Response, error) {
fields := Validate(widget)
if len(fields) > 0 {
return Response{}, InvalidConfig{Fields: fields}
}
if state == "" {
state = "loading"
}
message, ok := validStates[state]
if !ok {
return Response{}, InvalidConfig{Fields: map[string]string{"state": "Kies loading, empty, error of stale."}}
}
data, _ := object(widget["data"])
requested := intValue(data["limit"], 100)
applied := requested
if applied > MaxRows {
applied = MaxRows
}
return Response{
Type: stringValue(widget["type"]),
Title: stringValue(widget["title"]),
State: state,
Message: message,
Sample: map[string]any{"status": state, "text": message},
Limits: Limits{MaxSeries: MaxSeries, MaxPoints: MaxPoints, MaxRows: MaxRows, RequestedRows: requested, AppliedRows: applied},
}, nil
}
func Validate(widget map[string]any) map[string]string {
fields := make(map[string]string)
if id := stringValue(widget["id"]); id == "" {
fields["id"] = "Een widget-id is verplicht."
}
typ := stringValue(widget["type"])
if !validTypes[typ] {
fields["type"] = "Dit widgettype wordt niet ondersteund."
}
title := stringValue(widget["title"])
if title == "" {
fields["title"] = "Een titel is verplicht."
} else if len([]rune(title)) > 120 {
fields["title"] = "De titel mag maximaal 120 tekens bevatten."
}
data, ok := object(widget["data"])
if !ok {
fields["data"] = "Dataconfiguratie is verplicht."
} else {
source := stringValue(data["sourceType"])
if !validSources[source] {
fields["data.sourceType"] = "Kies een ondersteund brontype."
}
if source == "semantic-metric" {
metric := stringValue(data["metric"])
if metric == "" {
fields["data.metric"] = "Een semantische metric is verplicht."
}
if strings.Contains(strings.ToLower(metric), "promql") || strings.Contains(strings.ToLower(metric), "query=") {
fields["data.metric"] = "Gebruik een semantische metric; vrije PromQL is niet toegestaan."
}
}
if value := stringValue(data["range"]); value != "" && !validRanges[value] && !strings.HasPrefix(value, "$") {
fields["data.range"] = "Kies een begrensde periode."
}
if value := stringValue(data["aggregation"]); value != "" && !validAggregations[value] {
fields["data.aggregation"] = "Kies een ondersteunde aggregatie."
}
if number, ok := numberValue(data["limit"]); ok && (number < 1 || number > MaxRows || math.Trunc(number) != number) {
fields["data.limit"] = fmt.Sprintf("De limiet moet een geheel getal tussen 1 en %d zijn.", MaxRows)
}
}
if visualization, ok := object(widget["visualization"]); ok {
if number, ok := numberValue(visualization["decimals"]); ok && (number < 0 || number > 6 || math.Trunc(number) != number) {
fields["visualization.decimals"] = "Decimalen moeten tussen 0 en 6 liggen."
}
minimum, hasMin := numberValue(visualization["min"])
maximum, hasMax := numberValue(visualization["max"])
if hasMin && hasMax && minimum > maximum {
fields["visualization.max"] = "Maximum moet groter of gelijk aan minimum zijn."
}
if thresholds, ok := visualization["thresholds"].([]any); ok && len(thresholds) > 10 {
fields["visualization.thresholds"] = "Er zijn maximaal 10 drempels toegestaan."
}
}
if behavior, ok := object(widget["behavior"]); ok {
if number, ok := numberValue(behavior["liveIntervalSeconds"]); ok && (number < 1 || number > 300 || math.Trunc(number) != number) {
fields["behavior.liveIntervalSeconds"] = "Verversing moet tussen 1 en 300 seconden liggen."
}
}
return fields
}
func object(value any) (map[string]any, bool) {
result, ok := value.(map[string]any)
return result, ok
}
func stringValue(value any) string {
result, _ := value.(string)
return strings.TrimSpace(result)
}
func numberValue(value any) (float64, bool) {
switch result := value.(type) {
case float64:
return result, true
case float32:
return float64(result), true
case int:
return float64(result), true
case int64:
return float64(result), true
default:
return 0, false
}
}
func intValue(value any, fallback int) int {
number, ok := numberValue(value)
if !ok || number < 1 || math.Trunc(number) != number {
return fallback
}
return int(number)
}