dashboards

package
v0.18.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 26, 2026 License: Apache-2.0 Imports: 24 Imported by: 0

Documentation

Overview

Package dashboards owns the interpretation of dashboard rows: the config document (HTML + named datasources) and its validation rules, name→row resolution, and datasource execution into the {columns, rows, truncated, warning?} document the render iframe draws from (GDK-781).

Two surfaces speak dashboards — `gadak dashboards` (cmd/gadak) and the /api/v1/dashboards/ handlers (internal/server) — and both import this package for the same reason internal/views exists (GDK-612): the rule and the row shape must have one owner, not a CLI copy and an API copy that drift.

SQL datasources run only on a read-only connection handed in by the caller (store.DB.ReadOnly). That is not a convenience choice: arbitrary SQL is allowed precisely because it can never take a write path — the mirror is a cache of the origin, and this package is where that stays true.

Index

Constants

View Source
const (
	MaxRows     = 10000
	MaxRowBytes = 2 << 20 // 2 MiB of marshaled row payload
)

Row ceilings for datasource execution (server halves of the same contract). Generous on purpose — this is a performance guard for a local single-user tool, not a quota: a triage wall wants thousands of rows long before anyone writes a query that returns them.

View Source
const (
	// 50 MiB (GDK-808, user call 2026-08-24): the cap is a mistake guard on a
	// user-invoked download, not a security boundary — sized so a whole built
	// app bundle can ride as one lib.
	MaxLibBytes     = 50 << 20
	MaxLibRedirects = 3
	MaxLibs         = 8
)

Cache limits. MaxLibBytes bounds both a lying Content-Length and an infinite stream (the reader is capped one byte above, so a body at the limit is read to that point and refused, never buffered unbounded). Redirects are bounded because a redirect chain is attacker-controlled routing, not content. The timeout reuses the origin clients' budget (httppolicy.DefaultTimeout): one user-invoked fetch, no retries — a 5xx is an answer, and retrying would re-download bytes the user pays for. The rest of httppolicy (retryable statuses, the 64 MiB MaxBody) belongs to API clients; this is not one.

View Source
const LibIDPattern = `[a-f0-9]{16}-[A-Za-z0-9](?:[A-Za-z0-9._-]{0,62}[A-Za-z0-9_-])?`

LibIDPattern is the lib id rule: 16 hex of the content's sha256, a dash, then the sanitized original basename. The id is also the cache filename, so the pattern doubles as the path-safety contract — no separators, no "..", nothing that can escape libs/. Quoted in violation messages like NamePattern so an author can fix the config without a second round-trip.

View Source
const NamePattern = `[a-z0-9][a-z0-9_-]{0,63}`

NamePattern is the datasource-name rule, quoted verbatim in every violation message so an agent can fix the name without a second round-trip.

Variables

View Source
var ErrLibCorrupt = errors.New("lib cache corrupt")

ErrLibCorrupt means bytes exist but do not match their pinned hash (or the manifest itself does not parse) — an operator problem, surfaced as a 500 by the serve route, never as a silently-served file.

View Source
var ErrLibNotFound = errors.New("lib not found")

ErrLibNotFound is returned by LibLookup/LibRemove for an id the manifest does not carry. The serve route answers 404 on it, like the vendor route.

Functions

func FindDashboard

func FindDashboard(db *store.DB, name string) (store.Dashboard, error)

FindDashboard resolves one name to one row: exact id or exact name (case-insensitive) first, then a single substring hit. Same resolution policy as views.FindView so `gadak dashboards show triage` and a future MCP tool behave alike; only the miss policy is dashboard-specific (the message names the command that lists what exists).

func LibRemove

func LibRemove(dir, id string) error

LibRemove drops one entry and its bytes. Unknown id is ErrLibNotFound.

func LibsDir

func LibsDir(profileDir string) string

LibsDir is the cache directory under a profile directory. Single owner of the path shape: the CLI passes config.Dir(), the server config.DirFor of its profile — both get "dashboards/libs" from here, never spelled twice.

func LibsExist

func LibsExist(dir string, ids []string) (missing []string, err error)

LibsExist reports which of ids the cache does not carry — the save paths' existence check. A manifest that fails to parse names no lib (every id is "missing"), which is fail-closed: nothing saves against an unreadable pin.

func ValidLibID

func ValidLibID(id string) bool

ValidLibID is the lib id rule used by ParseConfig and by the manifest loader (a hand-edited manifest must not smuggle a path through the id).

func ValidName

func ValidName(name string) bool

ValidName is the datasource-name rule: a name is a URL-path-safe token (it becomes a path segment on /data/{name}/) starting alphanumeric.

Types

type Config

type Config struct {
	HTML        string            `json:"html"`
	Datasources map[string]Source `json:"datasources,omitempty"`
	Libs        []string          `json:"libs,omitempty"`
}

Config is the document a dashboard row stores. Datasources may be empty — a static HTML dashboard is valid — but never nil after ParseConfig. Libs (GDK-808) names cache entries from `gadak dashboards lib add`; existence is checked by the save paths (CLI and API), not here — ParseConfig owns the shape, callers own the world.

func ParseConfig

func ParseConfig(raw []byte) (Config, error)

ParseConfig decodes and validates a stored config document. It is strict on purpose: every field it accepts is a field render/data must honor, so an unknown key (an older config shape, a typo'd hand edit) is a named error, not a silently ignored clause.

type Lib

type Lib struct {
	ID        string `json:"id"`
	URL       string `json:"url"`
	SHA384    string `json:"sha384"`
	Size      int64  `json:"size"`
	FetchedAt string `json:"fetched_at"`
}

Lib is one manifest entry. ID is also the filename inside the cache dir, so entry and bytes cannot drift apart.

func LibAdd

func LibAdd(ctx context.Context, dir, rawURL string, replace bool, now time.Time) (lib Lib, added bool, err error)

LibAdd downloads rawURL once and caches it. added is false when the exact bytes are already cached (same URL and sha384 — the idempotent re-run). Same URL with a different hash is an upstream change: refused unless replace, because a dashboard's config pins the old id and a silent swap would change what its next render executes.

func LibList

func LibList(dir string) ([]Lib, error)

LibList returns every cached lib, sorted by id.

func LibLookup

func LibLookup(dir, id string) (Lib, error)

LibLookup resolves one id to its manifest entry.

func LibReadVerified

func LibReadVerified(dir, id string) (Lib, []byte, error)

LibReadVerified is the serve path's whole trust decision: read the bytes, re-hash them, and hand them over only when they still match the pin. The manifest itself not parsing is ErrLibCorrupt here too — a cache whose pin cannot be read serves nothing. Size is checked first because it is free and catches truncation without hashing.

type Result

type Result struct {
	Columns   []string `json:"columns"`
	Rows      [][]any  `json:"rows"`
	Truncated bool     `json:"truncated"`
	Warning   string   `json:"warning,omitempty"`
}

Result is the datasource execution document. It is also the postMessage payload contract for the web round: {type:'data', name, columns, rows, truncated, warning?} carries these fields verbatim.

func ExecuteJQL

func ExecuteJQL(ctx context.Context, db *store.DB, me jql.Identity, query string) (Result, error)

ExecuteJQL runs one datasource JQL query against the mirror: parse, resolve currentUser() against the configured identity and the actor directory, match, apply the ORDER BY, and project to jqlColumns. It is the server jql execution path (internal/server jql.go's parse+identity) with the CLI's match step (cmd/gadak searchJQL) — the two halves this feature finally needs in one place.

func ExecuteSQL

func ExecuteSQL(ro *sql.DB, query string) (Result, error)

ExecuteSQL runs one datasource SQL statement on ro, which callers must open through store.DB.ReadOnly — the read-only connection is what makes arbitrary SQL safe to allow. Zero rows plus a display-name comparison yields the sqlhint warning verbatim (the locale trap, surfaced where the agent will see it instead of as a mystery empty card).

type Source

type Source struct {
	SQL string `json:"sql,omitempty"`
	JQL string `json:"jql,omitempty"`
}

Source is one named datasource: exactly one of SQL or JQL.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL