Publish ITWorx Pulse source
Public source validation / validate (push) Failing after 3m8s

This commit is contained in:
ITWorx Pulse release export
2026-09-03 02:09:19 +02:00
commit bd774932d5
614 changed files with 77116 additions and 0 deletions
+331
View File
@@ -0,0 +1,331 @@
{
"schemaVersion": 1,
"id": "11111111-1111-4111-8111-111111111111",
"slug": "overview",
"name": "Overzicht",
"description": "Standaard operationeel overzicht.",
"scope": "system",
"variables": [
{
"name": "server",
"type": "server",
"label": "Server",
"default": "primary"
},
{
"name": "timeRange",
"type": "time-range",
"label": "Periode",
"default": "1h"
}
],
"widgets": [
{
"id": "21111111-1111-4111-8111-111111111111",
"type": "stat",
"title": "Serverstatus",
"description": "Samengevoegde status met verklaringen.",
"data": {
"sourceType": "inventory",
"scope": {
"entityType": "server",
"variable": "server"
},
"transformations": []
},
"visualization": {
"legend": false,
"showSparkline": false,
"decimals": 0,
"thresholds": []
},
"behavior": {
"locked": false,
"hidden": false,
"hideWhenEmpty": false,
"showOnlyOnProblem": false,
"link": "/infrastructure/host",
"liveIntervalSeconds": 5,
"independentTimeRange": null
},
"layouts": {
"desktop": {
"x": 0,
"y": 0,
"w": 6,
"h": 4,
"visible": true
},
"tablet": {
"x": 0,
"y": 0,
"w": 4,
"h": 4,
"visible": true
},
"mobile": {
"x": 0,
"y": 0,
"w": 1,
"h": 4,
"visible": true
},
"wallboard": {
"x": 0,
"y": 0,
"w": 6,
"h": 4,
"visible": true
}
}
},
{
"id": "31111111-1111-4111-8111-111111111111",
"type": "timeseries",
"title": "CPU en belasting",
"data": {
"sourceType": "semantic-metric",
"metric": "host.cpu.utilization",
"scope": {
"serverId": "$server"
},
"range": "$timeRange",
"aggregation": "avg",
"groupBy": [
"instance"
],
"transformations": []
},
"visualization": {
"unit": "percent",
"decimals": 1,
"legend": true,
"showSparkline": false,
"min": 0,
"max": 100,
"thresholds": []
},
"behavior": {
"locked": false,
"hidden": false,
"hideWhenEmpty": false,
"showOnlyOnProblem": false,
"link": "/infrastructure/host",
"liveIntervalSeconds": 2,
"independentTimeRange": null
},
"layouts": {
"desktop": {
"x": 6,
"y": 0,
"w": 12,
"h": 8,
"visible": true
},
"tablet": {
"x": 4,
"y": 0,
"w": 4,
"h": 5,
"visible": true
},
"mobile": {
"x": 0,
"y": 4,
"w": 1,
"h": 5,
"visible": true
},
"wallboard": {
"x": 6,
"y": 0,
"w": 10,
"h": 6,
"visible": true
}
}
},
{
"id": "41111111-1111-4111-8111-111111111111",
"type": "storage-map",
"title": "Array en pools",
"data": {
"sourceType": "inventory",
"scope": {
"entityTypes": [
"array",
"pool",
"disk"
]
},
"transformations": []
},
"visualization": {
"legend": true,
"showSparkline": false,
"decimals": 1,
"thresholds": []
},
"behavior": {
"locked": false,
"hidden": false,
"hideWhenEmpty": false,
"showOnlyOnProblem": false,
"link": "/infrastructure/storage",
"liveIntervalSeconds": 15,
"independentTimeRange": null
},
"layouts": {
"desktop": {
"x": 0,
"y": 8,
"w": 9,
"h": 8,
"visible": true
},
"tablet": {
"x": 0,
"y": 5,
"w": 8,
"h": 7,
"visible": true
},
"mobile": {
"x": 0,
"y": 9,
"w": 1,
"h": 7,
"visible": true
},
"wallboard": {
"x": 0,
"y": 6,
"w": 12,
"h": 7,
"visible": true
}
}
},
{
"id": "51111111-1111-4111-8111-111111111111",
"type": "status-grid",
"title": "Applicaties",
"data": {
"sourceType": "inventory",
"scope": {
"entityType": "application"
},
"transformations": [],
"limit": 50
},
"visualization": {
"legend": true,
"showSparkline": false,
"decimals": 0,
"thresholds": []
},
"behavior": {
"locked": false,
"hidden": false,
"hideWhenEmpty": false,
"showOnlyOnProblem": false,
"link": "/applications",
"liveIntervalSeconds": 5,
"independentTimeRange": null
},
"layouts": {
"desktop": {
"x": 9,
"y": 8,
"w": 9,
"h": 8,
"visible": true
},
"tablet": {
"x": 0,
"y": 12,
"w": 8,
"h": 7,
"visible": true
},
"mobile": {
"x": 0,
"y": 16,
"w": 1,
"h": 7,
"visible": true
},
"wallboard": {
"x": 12,
"y": 6,
"w": 12,
"h": 7,
"visible": true
}
}
},
{
"id": "61111111-1111-4111-8111-111111111111",
"type": "event-timeline",
"title": "Recente gebeurtenissen",
"data": {
"sourceType": "events",
"scope": {},
"range": "$timeRange",
"transformations": [],
"limit": 100
},
"visualization": {
"legend": false,
"showSparkline": false,
"decimals": 0,
"thresholds": []
},
"behavior": {
"locked": false,
"hidden": false,
"hideWhenEmpty": false,
"showOnlyOnProblem": false,
"link": "/events",
"liveIntervalSeconds": 5,
"independentTimeRange": null
},
"layouts": {
"desktop": {
"x": 0,
"y": 16,
"w": 18,
"h": 7,
"visible": true
},
"tablet": {
"x": 0,
"y": 19,
"w": 8,
"h": 7,
"visible": true
},
"mobile": {
"x": 0,
"y": 23,
"w": 1,
"h": 7,
"visible": true
},
"wallboard": {
"x": 0,
"y": 13,
"w": 24,
"h": 6,
"visible": true
}
}
}
],
"settings": {
"defaultTimeRange": "1h",
"live": true,
"refreshSeconds": 5,
"rotationSeconds": null
}
}
@@ -0,0 +1,56 @@
package onboarding
import (
"context"
"os"
"testing"
"github.com/itworx/pulse/internal/alert"
"github.com/itworx/pulse/internal/alertdefaults"
"github.com/itworx/pulse/internal/dashboard"
"github.com/itworx/pulse/internal/database"
"github.com/itworx/pulse/internal/metriccatalog"
)
func TestPostgreSQLOnboardingIsResumableAndIdempotent(t *testing.T) {
dsn := os.Getenv("PULSE_TEST_DATABASE_URL")
if dsn == "" {
t.Skip("PULSE_TEST_DATABASE_URL is not set")
}
ctx := context.Background()
pool, err := database.NewPool(ctx, database.Config{URL: dsn})
if err != nil {
t.Fatal(err)
}
defer pool.Close()
if err := database.Migrate(ctx, pool); err != nil {
t.Fatal(err)
}
registry, err := metriccatalog.DefaultRegistry()
if err != nil {
t.Fatal(err)
}
alerts := alert.Repository{Pool: pool, Registry: registry}
if _, err := alertdefaults.Seed(ctx, alerts, registry, "system-defaults"); err != nil {
t.Fatal(err)
}
service := Service{State: StateStore{Pool: pool}, Pool: pool, Dashboards: dashboard.Repository{Pool: pool}, Alerts: alerts, AuthMode: "mock"}
first, err := service.Complete(ctx, Choice{Dashboard: "default", Rules: "default"})
if err != nil {
t.Fatal(err)
}
second, err := service.Complete(ctx, Choice{Dashboard: "default", Rules: "default"})
if err != nil {
t.Fatal(err)
}
if !first.State.Completed || !second.State.Completed || first.State.DashboardID == "" || first.State.DashboardID != second.State.DashboardID {
t.Fatalf("non-idempotent completion: first=%+v second=%+v", first.State, second.State)
}
var dashboards int
if err := pool.QueryRow(ctx, `SELECT count(*) FROM dashboards WHERE slug=$1 AND archived_at IS NULL`, DefaultSlug).Scan(&dashboards); err != nil {
t.Fatal(err)
}
if dashboards != 1 {
t.Fatalf("default dashboard count=%d, want 1", dashboards)
}
}
+222
View File
@@ -0,0 +1,222 @@
package onboarding
import (
"context"
"embed"
"encoding/json"
"errors"
"fmt"
"sort"
"strings"
"github.com/itworx/pulse/internal/alert"
"github.com/itworx/pulse/internal/dashboard"
"github.com/jackc/pgx/v5/pgxpool"
)
//go:embed default-dashboard.json
var defaultDashboardFS embed.FS
type Service struct {
State StateStore
Pool *pgxpool.Pool
Dashboards dashboard.Repository
Alerts alert.Store
AuthMode string
OIDCIssuer string
OIDCClient string
OIDCRedirect string
Prometheus bool
Unraid bool
// RuntimeCapabilities supplies observed source readiness. It overrides the
// configuration-only Prometheus and Unraid placeholders when present.
RuntimeCapabilities func(context.Context) ([]Capability, error)
}
func (s Service) Status(ctx context.Context) (Status, error) {
state, err := s.State.Load(ctx)
if err != nil {
return Status{}, err
}
caps := make([]Capability, 0, 7)
if s.Pool == nil {
caps = append(caps, Capability{ID: "database", State: "unavailable", Detail: "De database is niet geconfigureerd."})
} else if err := s.Pool.Ping(ctx); err != nil {
caps = append(caps, Capability{ID: "database", State: "unknown", Detail: "De database is niet bereikbaar."})
} else {
caps = append(caps, Capability{ID: "database", State: "ready", Detail: "De database is beschikbaar."})
}
if s.Prometheus {
caps = append(caps, Capability{ID: "prometheus", State: "configured", Detail: "Prometheus is geconfigureerd; de endpoint blijft verborgen."})
} else {
caps = append(caps, Capability{ID: "prometheus", State: "unknown", Detail: "Prometheus is nog niet geconfigureerd."})
}
if s.Unraid {
caps = append(caps, Capability{ID: "unraid", State: "configured", Detail: "De read-only Unraid-bron is geconfigureerd."})
} else {
caps = append(caps, Capability{ID: "unraid", State: "unknown", Detail: "De read-only Unraid-bron is nog niet geconfigureerd."})
}
if s.Pool == nil {
caps = append(caps, Capability{ID: "services", State: "unavailable", Detail: "Servicebewaking is niet beschikbaar zonder database."})
} else {
var enabled int
if err := s.Pool.QueryRow(ctx, `SELECT count(*) FROM probes WHERE enabled=true AND archived_at IS NULL`).Scan(&enabled); err != nil {
caps = append(caps, Capability{ID: "services", State: "unknown", Detail: "De actieve servicechecks konden niet worden gelezen."})
} else if enabled == 0 {
caps = append(caps, Capability{ID: "services", State: "not-ready", Detail: "Er zijn nog geen veilige servicechecks geconfigureerd."})
} else {
caps = append(caps, Capability{ID: "services", State: "ready", Detail: fmt.Sprintf("%d veilige read-only servicechecks zijn actief.", enabled)})
}
}
switch {
case s.AuthMode == "mock":
caps = append(caps, Capability{ID: "auth", State: "development", Detail: "Mock-authenticatie is actief voor development."})
case strings.TrimSpace(s.OIDCIssuer) != "" && strings.TrimSpace(s.OIDCClient) != "" && strings.TrimSpace(s.OIDCRedirect) != "":
caps = append(caps, Capability{ID: "auth", State: "ready", Detail: "OIDC-configuratie is compleet; secrets worden niet getoond."})
default:
caps = append(caps, Capability{ID: "auth", State: "incomplete", Detail: "OIDC-configuratie is nog niet compleet."})
}
if s.Alerts == nil {
caps = append(caps, Capability{ID: "default-rules", State: "unavailable", Detail: "De alertregels zijn niet beschikbaar."})
} else if rules, listErr := s.Alerts.List(ctx, 100); listErr != nil {
caps = append(caps, Capability{ID: "default-rules", State: "unknown", Detail: "De alertregels konden niet worden gelezen."})
} else if len(rules) == 0 {
caps = append(caps, Capability{ID: "default-rules", State: "not-ready", Detail: "Er zijn nog geen alertregels beschikbaar."})
} else {
caps = append(caps, Capability{ID: "default-rules", State: "ready", Detail: fmt.Sprintf("%d alertregels zijn beschikbaar.", len(rules))})
}
if state.DashboardID != "" {
caps = append(caps, Capability{ID: "default-dashboard", State: "ready", Detail: "Het standaarddashboard is geselecteerd."})
} else {
caps = append(caps, Capability{ID: "default-dashboard", State: "not-ready", Detail: "Het standaarddashboard is nog niet geselecteerd."})
}
if s.RuntimeCapabilities != nil {
observed, observedErr := s.RuntimeCapabilities(ctx)
if observedErr != nil {
return Status{}, fmt.Errorf("read runtime capabilities: %w", observedErr)
}
caps = mergeCapabilities(caps, observed)
}
sort.Slice(caps, func(i, j int) bool { return caps[i].ID < caps[j].ID })
return Status{State: state, Capabilities: caps, Resume: !state.Completed && state.Step != "" && state.Step != "welcome"}, nil
}
func mergeCapabilities(configured, observed []Capability) []Capability {
byID := make(map[string]Capability, len(configured)+len(observed))
for _, capability := range configured {
byID[capability.ID] = capability
}
for _, capability := range observed {
if capability.ID == "prometheus" || capability.ID == "unraid" {
byID[capability.ID] = capability
}
}
result := make([]Capability, 0, len(byID))
for _, capability := range byID {
result = append(result, capability)
}
return result
}
func (s Service) Complete(ctx context.Context, choice Choice) (Status, error) {
if choice.Dashboard == "" {
choice.Dashboard = "default"
}
if choice.Rules == "" {
choice.Rules = "default"
}
if !validChoice(choice.Dashboard) || !validChoice(choice.Rules) {
return Status{}, errors.New("onboarding choices must be default or skip")
}
state, err := s.State.Load(ctx)
if err != nil {
return Status{}, err
}
state.DashboardChoice = choice.Dashboard
state.RulesChoice = choice.Rules
state.Step = "dashboard"
if err := s.State.Save(ctx, state); err != nil {
return Status{}, err
}
if choice.Dashboard == "default" {
if state.DashboardID == "" {
id, ensureErr := s.ensureDefaultDashboard(ctx)
if ensureErr != nil {
return Status{}, ensureErr
}
state.DashboardID = id
}
}
state.Step = "rules"
if err := s.State.Save(ctx, state); err != nil {
return Status{}, err
}
if s.Alerts != nil {
rules, listErr := s.Alerts.List(ctx, 100)
if listErr != nil {
return Status{}, fmt.Errorf("read default alert rules: %w", listErr)
}
state.RulesReady = len(rules) > 0
}
if choice.Rules == "default" && !state.RulesReady {
return Status{}, errors.New("default alert rules are not ready")
}
state.Completed = true
state.Step = "complete"
if err := s.State.Save(ctx, state); err != nil {
return Status{}, err
}
return s.Status(ctx)
}
func validChoice(value string) bool { return value == "default" || value == "skip" }
func (s Service) ensureDefaultDashboard(ctx context.Context) (string, error) {
if s.Dashboards.Pool == nil {
return "", errors.New("dashboard repository is not configured")
}
items, err := s.Dashboards.List(ctx, "onboarding", 100)
if err != nil {
return "", fmt.Errorf("find default dashboard: %w", err)
}
for _, item := range items {
if item.Slug == DefaultSlug && item.ArchivedAt == nil {
return item.ID, nil
}
}
document, err := defaultDashboard()
if err != nil {
return "", err
}
created, _, err := s.Dashboards.Create(ctx, "system-defaults", document, "first-run default dashboard")
if err != nil {
if errors.Is(err, dashboard.ErrConflict) {
items, listErr := s.Dashboards.List(ctx, "onboarding", 100)
if listErr == nil {
for _, item := range items {
if item.Slug == DefaultSlug && item.ArchivedAt == nil {
return item.ID, nil
}
}
}
}
return "", fmt.Errorf("install default dashboard: %w", err)
}
return created.ID, nil
}
func defaultDashboard() (dashboard.Document, error) {
raw, err := defaultDashboardFS.ReadFile("default-dashboard.json")
if err != nil {
return nil, fmt.Errorf("read embedded default dashboard: %w", err)
}
var document dashboard.Document
if err := json.Unmarshal(raw, &document); err != nil {
return nil, fmt.Errorf("decode embedded default dashboard: %w", err)
}
migrated, err := dashboard.Migrate(document)
if err != nil {
return nil, fmt.Errorf("validate embedded default dashboard: %w", err)
}
return migrated, nil
}
+73
View File
@@ -0,0 +1,73 @@
package onboarding
import (
"context"
"encoding/json"
"strings"
"testing"
)
func TestDefaultDashboardIsEmbeddedAndMigrated(t *testing.T) {
document, err := defaultDashboard()
if err != nil {
t.Fatal(err)
}
if document["id"] == nil || document["slug"] != DefaultSlug {
t.Fatalf("unexpected default dashboard: %#v", document)
}
if document["schemaVersion"] != 2 {
t.Fatalf("schema version=%v, want 2", document["schemaVersion"])
}
encoded, err := json.Marshal(document)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(encoded), `"serverId":"$server"`) || strings.Contains(string(encoded), `"server":"$server"`) {
t.Fatalf("default metric scope does not use the query planner alias: %s", encoded)
}
}
func TestCompleteRejectsUnknownChoicesBeforePersistence(t *testing.T) {
service := Service{}
if _, err := service.Complete(context.Background(), Choice{Dashboard: "unsafe", Rules: "default"}); err == nil {
t.Fatal("expected invalid onboarding choice")
}
}
func TestValidChoicesAreBounded(t *testing.T) {
for _, value := range []string{"default", "skip"} {
if !validChoice(value) {
t.Fatalf("choice %q should be accepted", value)
}
}
if validChoice("install-everything") {
t.Fatal("unexpected unbounded onboarding choice")
}
}
func TestRuntimeCapabilitiesOverrideConfigurationPlaceholders(t *testing.T) {
configured := []Capability{
{ID: "database", State: "ready", Detail: "database"},
{ID: "prometheus", State: "unknown", Detail: "not configured"},
{ID: "unraid", State: "unknown", Detail: "not configured"},
}
observed := []Capability{
{ID: "prometheus", State: "ready", Detail: "runtime sampled"},
{ID: "unraid", State: "ready", Detail: "runtime sampled"},
{ID: "unexpected", State: "ready", Detail: "must stay closed"},
}
merged := mergeCapabilities(configured, observed)
if len(merged) != 3 {
t.Fatalf("merged capabilities = %#v", merged)
}
for _, capability := range merged {
if capability.ID == "prometheus" || capability.ID == "unraid" {
if capability.State != "ready" || capability.Detail != "runtime sampled" {
t.Fatalf("runtime capability not authoritative: %#v", capability)
}
}
if capability.ID == "unexpected" {
t.Fatal("runtime callback expanded the closed capability set")
}
}
}
+53
View File
@@ -0,0 +1,53 @@
package onboarding
import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
)
type StateStore struct {
Pool *pgxpool.Pool
}
func (s StateStore) Load(ctx context.Context) (State, error) {
if s.Pool == nil {
return State{}, errors.New("onboarding state store is not configured")
}
var raw []byte
if err := s.Pool.QueryRow(ctx, `SELECT value FROM system_settings WHERE key=$1`, StateKey).Scan(&raw); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return State{SchemaVersion: SchemaVersion, Step: "welcome"}, nil
}
return State{}, fmt.Errorf("load onboarding state: %w", err)
}
var state State
if err := json.Unmarshal(raw, &state); err != nil {
return State{}, fmt.Errorf("decode onboarding state: %w", err)
}
if state.SchemaVersion != SchemaVersion {
return State{}, fmt.Errorf("unsupported onboarding state schema version %d", state.SchemaVersion)
}
return state, nil
}
func (s StateStore) Save(ctx context.Context, state State) error {
if s.Pool == nil {
return errors.New("onboarding state store is not configured")
}
state.SchemaVersion = SchemaVersion
state.UpdatedAt = time.Now().UTC()
raw, err := json.Marshal(state)
if err != nil {
return fmt.Errorf("encode onboarding state: %w", err)
}
if _, err := s.Pool.Exec(ctx, `INSERT INTO system_settings (key,value,version) VALUES ($1,$2::jsonb,1) ON CONFLICT (key) DO UPDATE SET value=EXCLUDED.value,version=system_settings.version+1,updated_at=now()`, StateKey, raw); err != nil {
return fmt.Errorf("save onboarding state: %w", err)
}
return nil
}
+37
View File
@@ -0,0 +1,37 @@
package onboarding
import "time"
const (
SchemaVersion = 1
StateKey = "onboarding.v1"
DefaultSlug = "overview"
)
type State struct {
SchemaVersion int `json:"schemaVersion"`
Completed bool `json:"completed"`
Step string `json:"step"`
DashboardChoice string `json:"dashboardChoice"`
RulesChoice string `json:"rulesChoice"`
DashboardID string `json:"dashboardId,omitempty"`
RulesReady bool `json:"rulesReady"`
UpdatedAt time.Time `json:"updatedAt"`
}
type Choice struct {
Dashboard string `json:"dashboard"`
Rules string `json:"rules"`
}
type Capability struct {
ID string `json:"id"`
State string `json:"state"`
Detail string `json:"detail"`
}
type Status struct {
State State `json:"state"`
Capabilities []Capability `json:"capabilities"`
Resume bool `json:"resume"`
}