Public source validation / validate (push) Failing after 3m8s
181 lines
5.8 KiB
Go
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)
|
|
}
|