Documentation
¶
Overview ¶
Package plugin is the hope plugin SDK: build a small JSON-RPC server that hope discovers across a fleet and renders in its UI. A plugin is your container, your language, your endpoint — hope dials in. Extend hope without joining it.
Minimal plugin:
p := plugin.New("badge-directory", "1.0.0").Icon("database")
p.View("counts", "Counts", plugin.KV, func(ctx context.Context) (any, error) {
return map[string]any{"users": 1402301, "badges": 88123}, nil
})
log.Fatal(p.ListenAndServe(":8080")) // serves JSON-RPC 2.0 at /__hope
The container then declares the labels hope scans for:
hope.plugin=true hope.plugin.port=8080 hope.plugin.path=/__hope
Index ¶
- Constants
- Variables
- func Input(ctx context.Context) string
- func NewError(code int, msg string) error
- func Params(ctx context.Context, v any) error
- func SearchQuery(ctx context.Context) string
- type ActionDesc
- type ActionFunc
- type ActionOpt
- type Capabilities
- type Card
- type CardField
- type CardsData
- type Cell
- func Badge(value, tone string) Cell
- func Code(value string) Cell
- func DetailLink(value, pageID, arg string) Cell
- func ExternalLink(value, href string) Cell
- func Image(src, alt string, opts ...ImageOpt) Cell
- func Link(value, to string) Cell
- func Number(n any, unit string) Cell
- func Progress(frac float64) Cell
- func Time(unix int64) Cell
- type ChartData
- type ChartSeries
- type Comp
- func Box(children ...*Comp) *Comp
- func CCell(cell Cell) *Comp
- func CGrid(children ...*Comp) *Comp
- func CIcon(name string) *Comp
- func CRow(children ...*Comp) *Comp
- func CTable(data *TableData) *Comp
- func CText(s string) *Comp
- func Divider() *Comp
- func Heading(text string, level int) *Comp
- func KeyVal(label string, value any) *Comp
- func Spacer(px int) *Comp
- func Sparkline(vals ...float64) *Comp
- func Stack(children ...*Comp) *Comp
- type CompKind
- type Contribution
- type Crumb
- type EmitFunc
- type EmptyOpt
- type EmptyState
- type Event
- type EventFunc
- type Facet
- type Field
- type Hope
- type ImageOpt
- type InitContext
- type KVData
- type Layout
- type Match
- type Node
- type NodeKind
- type Option
- type PageItem
- type Permission
- type Plugin
- func (p *Plugin) Action(method, label string, fields []Field, fn ActionFunc, opts ...ActionOpt) *Plugin
- func (p *Plugin) Alert(ctx context.Context, severity, title, detail, dedupeKey string) error
- func (p *Plugin) Breadcrumbs(crumbs ...Crumb) *Plugin
- func (p *Plugin) CardsView(method, label string, fn ViewFunc, opts ...ViewOpt) *Plugin
- func (p *Plugin) ChartView(method, label string, fn ViewFunc, opts ...ViewOpt) *Plugin
- func (p *Plugin) ComponentView(method, label string, fn ViewFunc, opts ...ViewOpt) *Plugin
- func (p *Plugin) ContainerPanel(title string, match *Match, node *Node) *Plugin
- func (p *Plugin) Contribute(c Contribution) *Plugin
- func (p *Plugin) DangerAction(method, label string, fields []Field, fn ActionFunc, opts ...ActionOpt) *Plugin
- func (p *Plugin) DashboardWidget(title string, node *Node) *Plugin
- func (p *Plugin) Description(s string) *Plugin
- func (p *Plugin) DetailPage(id, title, paramKey string, node *Node) *Plugin
- func (p *Plugin) DynamicPage(title string, node *Node, items []PageItem) *Plugin
- func (p *Plugin) DynamicPageFunc(title string, node *Node, fn func(ctx context.Context) []PageItem) *Plugin
- func (p *Plugin) Handler() http.Handler
- func (p *Plugin) HeaderActions(refs ...string) *Plugin
- func (p *Plugin) Hope() *Hope
- func (p *Plugin) Icon(name string) *Plugin
- func (p *Plugin) Icons(m map[string]string) *Plugin
- func (p *Plugin) ListenAndServe(addr string) error
- func (p *Plugin) MaxBodyBytes(n int64) *Plugin
- func (p *Plugin) OnEvent(fn EventFunc) *Plugin
- func (p *Plugin) OnInit(fn func(ctx context.Context, in InitContext) error) *Plugin
- func (p *Plugin) Page(title string, node *Node) *Plugin
- func (p *Plugin) PageID(id string) *Plugin
- func (p *Plugin) Path(path string) *Plugin
- func (p *Plugin) Publish(ctx context.Context, e Event) error
- func (p *Plugin) QueryView(method, label, lang, def string, fn ViewFunc, opts ...ViewOpt) *Plugin
- func (p *Plugin) RequestPermission(ctx context.Context, scope, reason string) error
- func (p *Plugin) RequirePermission(scope, reason string) *Plugin
- func (p *Plugin) ResolveAlert(ctx context.Context, title, dedupeKey string) error
- func (p *Plugin) SearchView(method, label string, fn ViewFunc) *Plugin
- func (p *Plugin) Setting(s Setting) *Plugin
- func (p *Plugin) SettingValue(key string) string
- func (p *Plugin) StackWidget(title string, match *Match, node *Node) *Plugin
- func (p *Plugin) StatView(method, label string, fn ViewFunc, opts ...ViewOpt) *Plugin
- func (p *Plugin) Storage() *Storage
- func (p *Plugin) Stream(method, label string, kind StreamKind, fn StreamFunc) *Plugin
- func (p *Plugin) Subtitle(s string) *Plugin
- func (p *Plugin) TableView(method, label string, fn ViewFunc, opts ...TableOpt) *Plugin
- func (p *Plugin) TextView(method, label string, fn ViewFunc, opts ...ViewOpt) *Plugin
- func (p *Plugin) Token(token string) *Plugin
- func (p *Plugin) View(method, label string, kind ViewKind, fn ViewFunc, opts ...ViewOpt) *Plugin
- type RowAction
- type Schema
- type SearchData
- type SearchItem
- type Setting
- type SettingKind
- type SortSpec
- type StatBlock
- type StatData
- type Storage
- type StreamDesc
- type StreamFunc
- type StreamKind
- type Surface
- type TableData
- type TableOpt
- func DefaultSort(column, dir string) TableOpt
- func Editable(method string, columns ...string) TableOpt
- func Facets(facets ...Facet) TableOpt
- func NoFilter() TableOpt
- func NoSort() TableOpt
- func PageSize(n int) TableOpt
- func RowActions(actions ...RowAction) TableOpt
- func RowDetail(method string) TableOpt
- func RowDetailButton(method string) TableOpt
- func RowFlyout(method string) TableOpt
- func ServerSide() TableOpt
- type TableQuery
- type TableSort
- type TextData
- type TipPos
- type Tooltip
- type TreeData
- type TreeNode
- type ViewDesc
- type ViewFunc
- type ViewKind
- type ViewOpt
Constants ¶
const ( ToneOK = "ok" ToneWarn = "warn" ToneBad = "bad" ToneInfo = "info" )
Tone names a semantic color for a Badge (matches hope's ok/warn/bad/info).
const ( ScopeEventsSubscribe = "events:subscribe" // receive hope events via OnEvent ScopeEventsPublish = "events:publish" // publish events onto hope's bus ScopeStorage = "storage" // durable per-install KV (p.Storage) ScopeSpecLabel = "spec:label" // add/update a service label in the plugin's own stack (p.Hope().AddServiceLabel) )
Reverse-capability scopes a plugin may request. Plain strings so older/newer peers interoperate — an unknown scope is simply never granted. Additive: publish/storage/ action scopes land with their phases.
const ProtocolVersion = 1
ProtocolVersion is the plugin-protocol version this SDK speaks. hope sends its own; a mismatch degrades gracefully (unknown surfaces/kinds are skipped) rather than breaking, so a new plugin still works against an older hope and vice-versa.
Variables ¶
var ErrNoReverseChannel = errors.New("hope reverse channel unavailable (no callback URL from hope.init)")
ErrNoReverseChannel is returned by Publish/Storage when hope hasn't provided a callback URL (an older hope, or one without [plugins] callback_url configured). The plugin still runs; it just can't call back into hope.
Functions ¶
func Input ¶
Input returns the "input" string from a Query view's params (the text the user typed into the query box), or "" if absent.
func NewError ¶
NewError builds an *rpcError a handler can return to control the JSON-RPC error code sent to hope (e.g. invalid query -> codeInvalidArgs).
func Params ¶
Params unmarshals the current call's JSON-RPC params into v. Use inside an action/view/stream handler to read structured input.
func SearchQuery ¶
SearchQuery reads the text a Search (autocomplete) view was called with (the "q" param). Empty when there's no query yet — return no items for that.
Types ¶
type ActionDesc ¶
type ActionDesc struct {
Method string `json:"method"`
Label string `json:"label"`
Icon string `json:"icon,omitempty"`
Danger bool `json:"danger,omitempty"`
Fields []Field `json:"fields,omitempty"`
Tip *Tooltip `json:"tip,omitempty"` // hover tooltip on the action button (set with ActionTip)
}
ActionDesc describes an invocable action (a mutation). Danger flags a destructive action so hope confirms before running it.
type ActionFunc ¶
ActionFunc runs an action with the UI-collected field values.
type ActionOpt ¶ added in v0.0.3
type ActionOpt func(*ActionDesc)
ActionOpt configures a registered action beyond its label (its icon, …). Pass these to Action/DangerAction; the danger tone is set by DangerAction itself.
func ActionIcon ¶ added in v0.0.3
ActionIcon sets the action button's icon — a hope built-in icon name or one of this plugin's Icons keys. e.g. plugin.ActionIcon("rotate").
type Capabilities ¶ added in v0.0.6
type Capabilities struct {
ViewKinds []string `json:"view_kinds"`
Features []string `json:"features"`
}
Capabilities is what the connected hope advertised it can render: the supported view kinds (kv/table/…/component) and feature flags (static, empty). Read it with Caps and query it with Supports.
func Caps ¶ added in v0.0.6
func Caps(ctx context.Context) Capabilities
Caps returns the capabilities the connected hope advertised for the current call. Use it to adapt output across hope versions (see the package example above). Outside a hope-driven call (no headers) it returns an empty Capabilities.
func (Capabilities) Supports ¶ added in v0.0.6
func (c Capabilities) Supports(name string) bool
Supports reports whether hope advertised the named capability — a view kind ("component") or a feature ("static", "empty"). False when hope sent no capabilities (an older build), so guard optional output and keep a baseline fallback.
type Card ¶
type Card struct {
Title string `json:"title"`
Subtitle string `json:"subtitle,omitempty"`
Icon string `json:"icon,omitempty"`
Tone string `json:"tone,omitempty"` // ok|warn|bad|info accent
To string `json:"to,omitempty"`
Image string `json:"image,omitempty"` // absolute http(s) URL -> a hero image at the card top
Fields []CardField `json:"fields,omitempty"`
}
Card is one tile in a Cards view. Fields render as a small label/value list (the values may be rich cells — Badge/Number/…). A non-empty To makes the card navigate on click (plugin-relative, like a Link cell — see DetailLink).
type CardsData ¶
type CardsData struct {
Items []Card `json:"items"`
}
CardsData is what a Cards view returns: a grid of cards.
type Cell ¶ added in v0.0.6
Cell is a rich table/field cell — a typed shape hope renders specially (a pill, a link, a progress bar, …). It's a type *alias* for map[string]any, so it stays a plain JSON object on the wire and every builder below (and any hand-built map) satisfies it with no conversion; the name just documents intent where a cell is expected (e.g. a Comp's Cell field, or CCell). Build one with Badge/Link/Number/Time/Progress/Code/Image.
func Badge ¶
Badge renders value as a colored pill. tone is one of the Tone* constants ("" = neutral).
func DetailLink ¶
DetailLink renders value as a link to one of this plugin's DetailPage ids, passing arg as the page's ParamKey — a master-detail link that needs no knowledge of the plugin's hope key. e.g. DetailLink("alice", "user", "42") -> the "user" detail page with param {<paramKey>: "42"}.
func ExternalLink ¶
ExternalLink renders value as a link that opens href in a new tab.
func Image ¶
Image renders src as an image (click opens the full image in a new tab). alt is the hover/accessible label. Unlike RPC calls, hope does NOT proxy image bytes — the browser loads src directly, so src MUST be an absolute http(s) URL reachable from the browser (e.g. a public on-demand webp/avif image proxy). A non-http(s) src renders as its alt text. Usable as a table cell or a stat/card/cards field value.
With no opts it's a small inline thumbnail. Control the render box with opts:
Image(u, alt) // default thumbnail
Image(u, alt, ImgW(240)) // 240px wide, height auto (keeps aspect)
Image(u, alt, ImgBox(110, 110)) // fixed 110×110 box, image centered, contained
Image(u, alt, ImgBox(110,110), ImgFit("cover")) // fill the box, cropping overflow
func Link ¶
Link renders value as an in-app link that navigates to a hope route `to` (e.g. a master-detail page). Use ExternalLink for an off-site URL.
func Number ¶
Number renders n right-formatted with thousands separators; unit ("" = none) is appended (e.g. "MB", "reqs").
type ChartData ¶
type ChartData struct {
Type string `json:"type,omitempty"`
Labels []string `json:"labels"`
Series []ChartSeries `json:"series"`
}
ChartData is what a Chart view returns: categorical labels on the x-axis and one or more named series of values. Type is "bar" (default) or "line". A line chart with one series and many points is a good time-series-at-rest view (use a stream for live). hope draws the axes, gridlines, legend, and scaling.
type ChartSeries ¶
ChartSeries is one named line/bar series; Values aligns with ChartData.Labels.
type Comp ¶ added in v0.0.6
type Comp struct {
Kind CompKind `json:"kind"`
Children []*Comp `json:"children,omitempty"` // containers
Text string `json:"text,omitempty"` // text/heading content
Label string `json:"label,omitempty"` // keyval label
Cell Cell `json:"cell,omitempty"` // cell primitive: a rich Cell
Value any `json:"value,omitempty"` // keyval value (a scalar or a rich Cell)
Level int `json:"level,omitempty"` // heading level 1..4
Tone string `json:"tone,omitempty"` // ok|warn|bad|info accent (text/heading/keyval/box)
Icon string `json:"icon,omitempty"` // icon primitive
Values []float64 `json:"values,omitempty"` // sparkline points
Gap int `json:"gap,omitempty"` // container child gap, px
Size int `json:"size,omitempty"` // row/grid child weight | spacer height px
Table *TableData `json:"table,omitempty"` // embedded table (CompTable)
}
Comp is one node in a Component tree. Build nodes with the constructors below (Box/Stack/CRow/CGrid for containers; Heading/CText/KeyVal/CIcon/Sparkline/CCell for terminals) rather than filling this struct by hand. Unknown kinds are skipped by hope, never fatal, so a newer primitive degrades gracefully on an older hope.
func CCell ¶ added in v0.0.6
CCell wraps any rich Cell (Badge/Link/Number/Time/Progress/Code/Image) as a standalone primitive, so the whole cell vocabulary works inside a Component tree.
func CIcon ¶ added in v0.0.6
CIcon builds a single icon (a hope built-in name or one of this plugin's Icons keys). C-prefixed to leave room for future top-level icon helpers.
func CRow ¶ added in v0.0.6
CRow builds a horizontal container (children laid out left-to-right). Named CRow (not Row) because Row already builds a layout Node; a Comp tree uses CRow.
func CTable ¶ added in v0.0.10
CTable embeds a full table inside a Component tree — column headers, aligned cells, ellipsis, DetailLink/Image cells, all rendered by hope's table renderer. Build the data with plugin.Table(...). Ideal for a compact list-with-structure inside a flyout/panel (e.g. the badges on a canvas) instead of hand-stacking rows.
func CText ¶ added in v0.0.6
CText builds a line/paragraph of text (auto-escaped by hope). C-prefixed because Text is already a ViewKind constant.
func Divider ¶ added in v0.0.6
func Divider() *Comp
Divider builds a horizontal rule between sections of a tile.
func Heading ¶ added in v0.0.6
Heading builds a heading; level is clamped to 1..4 by hope (1 = largest).
func KeyVal ¶ added in v0.0.6
KeyVal builds a label + value line. value may be a plain scalar or a rich Cell (Badge/Number/…); the `any` mirrors Number's signature — hope renders a Cell specially and stringifies a scalar.
func Stack ¶ added in v0.0.6
Stack builds a tight vertical container — good for a run of KeyVal lines.
type CompKind ¶ added in v0.0.6
type CompKind string
CompKind names one primitive in a Component tree. Containers hold Children; terminals carry their own inline payload (text, a cell, values, an icon).
const ( CompBox CompKind = "box" // vertical container (a card/tile body) CompStack CompKind = "stack" // vertical container, tighter — labeled rows CompRow CompKind = "row" // horizontal container (children side by side) CompGrid CompKind = "grid" // responsive grid of children CompText CompKind = "text" // a line/paragraph of text CompHeading CompKind = "heading" // a heading (Level 1..4) CompDivider CompKind = "divider" // a horizontal rule CompSpacer CompKind = "spacer" // vertical gap of Size px CompKeyval CompKind = "keyval" // a Label + Value line (value may be a rich Cell) CompIcon CompKind = "icon" // a single icon (built-in name or an Icons key) CompSparkline CompKind = "sparkline" // a tiny inline line chart from Values CompCell CompKind = "cell" // any rich Cell (Badge/Link/Number/Time/Progress/Code/Image) CompTable CompKind = "table" // an embedded table (rich cells, alignment, ellipsis) )
type Contribution ¶
type Contribution struct {
Surface Surface `json:"surface"`
Title string `json:"title,omitempty"` // tab/page title
Icon string `json:"icon,omitempty"`
Match *Match `json:"match,omitempty"`
Pages []PageItem `json:"pages,omitempty"`
Node *Node `json:"node"`
// Actions are method names of registered actions shown as a toolbar at the top of
// this surface (page/panel/dashboard header) — page-level actions distinct from
// leaf actions inside the layout. hope collects fields, confirms danger, audits.
Actions []string `json:"actions,omitempty"`
// ID is a stable address for a page contribution, so links can target it by name
// (a plugin does NOT know its hope key or a page's positional path). A DetailPage
// sets ID + ParamKey and is Hidden from the rail; a Link/DetailLink navigates to
// it plugin-relative, and hope passes the URL arg as param[ParamKey].
ID string `json:"id,omitempty"`
Hidden bool `json:"hidden,omitempty"` // not listed in the rail (a link/detail target)
ParamKey string `json:"param_key,omitempty"` // detail pages: the URL arg becomes param[ParamKey]
// Subtitle sets the page header's sub/meta line (page surfaces) — e.g. a record
// count or a connection string. {param} placeholders are filled from the page
// param. Empty => hope shows the plugin name.
Subtitle string `json:"subtitle,omitempty"`
// Breadcrumbs render above the page heading (page surfaces). Each Crumb's Label
// and To may contain {param} placeholders hope fills from the page param — e.g.
// on a user detail page, [{Users, users}, {"user {id}"}].
Breadcrumbs []Crumb `json:"breadcrumbs,omitempty"`
}
Contribution mounts one layout tree onto a Surface. For container/stack surfaces, Match decides which containers it applies to. For page surfaces, Pages (optional) turns one node into MANY rail entries that share the layout but each pass a distinct Param.
type Crumb ¶
Crumb is one breadcrumb. To (optional) is a plugin-relative navigation target (like a Link cell / DetailLink); the last crumb is usually the current page and has no To. {param} placeholders in Label/To are filled from the page param.
type EmptyOpt ¶ added in v0.0.6
type EmptyOpt func(*EmptyState)
EmptyOpt configures a view's EmptyState (its icon, secondary text, or a custom Comp).
func EmptyComp ¶ added in v0.0.6
EmptyComp sets a fully custom empty state (a Comp tree — see component.go), overriding the icon/title/text — e.g. an icon plus a "create one" Link.
type EmptyState ¶ added in v0.0.6
type EmptyState struct {
Icon string `json:"icon,omitempty"` // leading icon (a built-in name or an Icons key)
Title string `json:"title,omitempty"` // the headline, e.g. "No slow queries 🎉"
Text string `json:"text,omitempty"` // a dim secondary line
Comp *Comp `json:"comp,omitempty"` // optional custom empty state (overrides Icon/Title/Text)
}
EmptyState is what hope shows when a view resolves to no data (an empty table, a tree with no nodes, a stat with no blocks) instead of the generic "empty" text. The plain Icon/Title/Text cover the common case; set Comp for a fully custom empty state (an icon + heading + a "create one" Link). Build it with plugin.EmptyView.
type Event ¶ added in v0.1.0
type Event struct {
Seq uint64 `json:"seq,omitempty"`
Kind string `json:"kind"`
Host string `json:"host,omitempty"`
Project string `json:"project,omitempty"`
IDs []string `json:"ids,omitempty"`
Source string `json:"source,omitempty"`
Ts int64 `json:"ts,omitempty"`
Data json.RawMessage `json:"data,omitempty"`
}
Event is one hope event delivered to an OnEvent handler. Mirrors hope's wire event; Data is kind-specific JSON (may be empty). Delivered only when the plugin holds the events:subscribe grant.
type EventFunc ¶ added in v0.1.0
EventFunc handles a hope event pushed to the plugin (one unary hope.event call per event). It should return quickly; hope treats a slow/erroring handler as a missed delivery and moves on. Requires the events:subscribe permission.
type Facet ¶
type Facet struct {
Key string `json:"key"`
Label string `json:"label"`
Options []Option `json:"options"`
}
Facet is one dropdown filter on a server table: a key, a label, and the choices.
type Field ¶
type Field struct {
Key string `json:"key"`
Label string `json:"label"`
Type string `json:"type,omitempty"` // text|textarea|select|toggle|kv (default text)
Placeholder string `json:"placeholder,omitempty"`
Hint string `json:"hint,omitempty"`
Value string `json:"value,omitempty"`
Optional bool `json:"optional,omitempty"`
Options []Option `json:"options,omitempty"`
}
Field mirrors hope's PromptField: the UI collects these before invoking an action, then passes the values map to the action handler.
type Hope ¶ added in v0.1.0
type Hope struct {
// contains filtered or unexported fields
}
Hope is a handle to the operator ACTIONS a plugin may perform on hope — the operator pattern (watch events via OnEvent, then reconcile by mutating hope). Each action requires its own operator-granted scope and hope's reverse channel; without the grant hope rejects the call, and every action is audited. Actions target the plugin's OWN stack only.
func (*Hope) AddServiceLabel ¶ added in v0.1.0
AddServiceLabel adds (or updates) a label on a service in the plugin's own stack and hope re-applies the stack, so the label PERSISTS across future redeploys. The classic use is a plugin that auto-adds Prometheus scrape labels on deploy. service is a service name in this plugin's stack; the plugin can't target another stack. Requires the spec:label permission.
type ImageOpt ¶
ImageOpt configures an Image cell's render box (size + fit).
func ImgBox ¶
ImgBox fixes both dimensions: the image is centered in a w×h box and, by default, contained (scaled to fit, no crop). Add ImgFit("cover") to fill and crop instead.
func ImgCache ¶ added in v0.0.9
func ImgCache() ImageOpt
ImgCache opts THIS image into hope's durable local byte cache. hope caches the flagged image client-side via a service worker (the CORS-free way — it caches the <img>'s own request/response, so the image host needs no CORS/cooperation) and serves it from cache on later views, surviving reloads and sessions. Opt-in per image: only images you flag are cached — nothing else. Prefer it on immutable, content-addressed art (a badge/canvas keyed by hash) that's expensive to re-fetch from a remote host.
func ImgFallback ¶
ImgFallback sets an image shown when src fails to load (broken/404/blocked). It must also be an absolute http(s) URL. Tried once; if it too fails, the cell renders blank (no browser broken-image icon).
func ImgFit ¶
ImgFit sets object-fit: "contain" (default — whole image, letterboxed) or "cover" (fill the box, cropping overflow).
func ImgLightbox ¶
func ImgLightbox() ImageOpt
ImgLightbox makes clicking the image open it in an in-app lightbox (a dimmed full-screen overlay, closed by backdrop click / Esc / the close button) instead of a new browser tab — nicer for viewing badge/avatar art in place.
type InitContext ¶ added in v0.0.7
type InitContext struct {
Settings map[string]string
Protocol int
Caps Capabilities
}
InitContext is what hope hands a plugin at initialization (hope.init): the settings the operator configured (so the plugin can set up WITH them), plus the hope build's protocol version and capabilities.
type KVData ¶
KVData is what a KV view returns: a flat map of label -> value. A value may be a plain scalar or a rich cell (Badge/Link/Image/…). It's a named alias so a KV handler reads as returning KVData, while a map literal still satisfies it.
type Layout ¶
type Layout struct {
ProtocolVersion int `json:"protocolVersion"`
Contributions []Contribution `json:"contributions"`
}
Layout is the result of the fixed hope.layout method: the plugin's UI contributions, versioned for graceful cross-version degradation.
type Match ¶
type Match struct {
Always bool `json:"always,omitempty"` // every container
Images []string `json:"images,omitempty"` // image-ref globs, e.g. "postgres*"
Labels map[string]string `json:"labels,omitempty"` // label == value
Services []string `json:"services,omitempty"` // compose service names
}
Match decides which containers a container/stack contribution applies to. The plugin declares this (hope does not map containers to plugins). A nil/empty Match means "the plugin's own container" — the trivial self-describing case. Semantics: set clauses are AND-ed; values within a clause are OR-ed.
type Node ¶
type Node struct {
Kind NodeKind `json:"kind"`
Title string `json:"title,omitempty"`
Ref string `json:"ref,omitempty"` // leaf: method name of a view/action/stream
Size int `json:"size,omitempty"` // optional row/grid weight
Fill bool `json:"fill,omitempty"` // grow to fill the remaining height (e.g. a table)
// Collapsible makes a titled section fold on a title click; Collapsed starts it
// closed. For dense pages where not everything needs to be open at once.
Collapsible bool `json:"collapsible,omitempty"`
Collapsed bool `json:"collapsed,omitempty"`
Children []*Node `json:"children,omitempty"`
// Comp carries an inline Component tree when Kind is NodeComponent (see Component).
Comp *Comp `json:"comp,omitempty"`
}
Node is one node in the surface-agnostic layout tree. The same tree drives a container panel now and a full page later; hope's renderer walks it without caring which surface hosts it.
func Buttons ¶ added in v0.0.3
Buttons builds a horizontal group of action buttons from action method refs. The buttons size to content and sit together (a toolbar), unlike Row's stretched columns — e.g. Buttons("analyze", "vacuum") for a maintenance section.
func Component ¶ added in v0.0.6
Component builds an inline component node: a Comp tree (see component.go) rendered straight from the layout, with no per-view round-trip. Use it for a small static tile — e.g. plugin.Component(plugin.Box(plugin.Heading("Fleet", 3), …)).
func Row ¶
Row builds a horizontal row from children — equal-width columns (each child gets an equal flex share). For a group of action buttons use Buttons instead, so they size to content and don't spread across the row.
func (*Node) Collapse ¶
Collapse makes a titled section fold on a title click. Pass collapsed=true to start it closed.
func (*Node) Filled ¶
Filled marks a node to grow and fill the remaining height (and propagates up its ancestors when rendered) — e.g. a table that should fill the page.
func (*Node) Weight ¶
Weight sets a child's flex weight inside a Row (or Grid) — the WIDTH proportion it takes relative to its siblings. Default (0) means equal share. e.g. in a two-column Row, Weight(1) beside Weight(2) makes the second column twice as wide:
plugin.Row(
plugin.Section("Overview", plugin.Leaf("head")).Weight(1),
plugin.Section("Fields", plugin.Leaf("fields")).Weight(2),
)
type NodeKind ¶
type NodeKind string
NodeKind enumerates layout-tree node types. Containers hold Children; a leaf references a registered view/action/stream by method name.
const ( NodeSection NodeKind = "section" // titled group NodeTabs NodeKind = "tabs" // tabbed children NodeRow NodeKind = "row" // horizontal arrangement (equal-width columns) NodeButtons NodeKind = "buttons" // horizontal group of action buttons, sized to content NodeGrid NodeKind = "grid" // grid arrangement NodeLeaf NodeKind = "leaf" // a single view/action/stream // NodeComponent carries an inline Comp tree (see component.go) rendered directly // from the layout — no ref, no per-view round-trip. Build it with Component(). NodeComponent NodeKind = "component" )
type PageItem ¶
type PageItem struct {
Title string `json:"title"`
Icon string `json:"icon,omitempty"`
Param map[string]any `json:"param,omitempty"`
Children []PageItem `json:"children,omitempty"`
}
PageItem is one dynamic subpage: it shares the contribution's Node but carries its own Param, which hope merges into every call the page makes — so a plugin can list e.g. every DB table as a rail entry that renders the same view with a different argument. Read it in a handler with plugin.Params(ctx, &v).
Children nests items one level (a group node), so e.g. three databases each listing their tables become nested rail entries. A node with Children is a group (not itself a page); a leaf node (no Children) is the navigable page.
type Permission ¶ added in v0.1.0
Permission is a reverse capability the plugin REQUESTS from hope. Least privilege: a plugin gets NOTHING on the plugin->hope direction unless it declares the scope here (via Plugin.RequirePermission) AND the operator consents when enabling it. hope records the granted subset and gates every reverse call on it; the plugin's token authenticates identity, the grant set authorizes. Reason is shown on the operator's consent prompt.
type Plugin ¶
type Plugin struct {
// contains filtered or unexported fields
}
Plugin is a hope plugin server. Construct with New, register capabilities, then ListenAndServe. Registration is not safe for concurrent use; serving is.
func New ¶
New creates a plugin with the given name and semantic version. The endpoint path defaults to /__hope and the request body cap to 4 MiB. If the environment sets HOPE_PLUGIN_TOKEN, calls must present it as a bearer token.
func (*Plugin) Action ¶
func (p *Plugin) Action(method, label string, fields []Field, fn ActionFunc, opts ...ActionOpt) *Plugin
Action registers an invocable action. The UI collects fields, then calls fn with the values. Mark destructive actions with DangerAction.
func (*Plugin) Alert ¶ added in v0.1.0
Alert is a convenience over Publish: it publishes an event whose Data is {severity, title, detail, dedupeKey}. hope namespaces the kind to plugin.<identity>.alert. Requires events:publish.
func (*Plugin) Breadcrumbs ¶
Breadcrumbs attaches a breadcrumb trail to the most recently added page contribution (call right after Page/DetailPage). {param} placeholders in a crumb's label/to are filled from the page param.
func (*Plugin) CardsView ¶
CardsView registers a Cards view; the handler returns CardsData (a grid of cards / a gallery — e.g. badges, users). Cards with a To navigate on click. opts (Static/EmptyView/…) are optional.
func (*Plugin) ChartView ¶
ChartView registers a Chart view; the handler returns ChartData (bar or line, one or more named series over categorical labels). hope draws axes + legend. opts (Static/EmptyView/…) are optional.
func (*Plugin) ComponentView ¶ added in v0.0.6
ComponentView registers a Component view — the escape hatch. The handler returns a *Comp: a tree of safe primitives (box/row/heading/keyval/sparkline/cell/…) hope composes into a custom widget the built-in kinds don't cover (see component.go). Add plugin.Static() to cache it or plugin.EmptyView(...) to customize its empty state. For a small STATIC tile, prefer an inline plugin.Component node in the layout instead — it needs no per-view round-trip.
func (*Plugin) ContainerPanel ¶
ContainerPanel is a shorthand for a container-surface contribution with the given title, match, and layout node.
func (*Plugin) Contribute ¶
func (p *Plugin) Contribute(c Contribution) *Plugin
Contribute adds an explicit UI contribution. Without any, the plugin auto-generates a single container contribution (see layout) listing every registered capability — so a minimal plugin needs no layout code.
func (*Plugin) DangerAction ¶
func (p *Plugin) DangerAction(method, label string, fields []Field, fn ActionFunc, opts ...ActionOpt) *Plugin
DangerAction is like Action but flags the action destructive, so hope confirms before running it and audit-logs the invocation.
func (*Plugin) DashboardWidget ¶
DashboardWidget contributes a widget to hope's fleet/host dashboard (the `dashboard` surface): the node renders as a compact panel alongside hope's own dashboard cards. Keep it small — a couple of kv/counter/series leaves.
func (*Plugin) Description ¶
Description sets a one-line description shown in hope.
func (*Plugin) DetailPage ¶
DetailPage contributes a hidden master-detail page addressed by a stable id (not shown in the rail). A Link/DetailLink navigates to it plugin-relative, and hope passes the URL arg as param[paramKey] — read it in a handler with plugin.Params. e.g. DetailPage("user", "User", "id", node) rendered at .../user/42 => {id:"42"}.
func (*Plugin) DynamicPage ¶
DynamicPage contributes MANY rail pages that share one layout node but each pass a distinct Param (merged into every call the page makes). items may nest one level (groups of pages) — e.g. databases -> tables. Read the param in a handler with plugin.Params. Regenerate the items in getLayout to reflect live state.
func (*Plugin) DynamicPageFunc ¶ added in v0.0.2
func (p *Plugin) DynamicPageFunc(title string, node *Node, fn func(ctx context.Context) []PageItem) *Plugin
DynamicPageFunc is DynamicPage with LIVE items: fn runs on every hope.layout to produce the rail entries, so the set reflects current state (a database's tables, a broker's topics) instead of a snapshot frozen at startup — the answer to "my pages depend on data I don't have until runtime." Keep fn fast: hope fetches the layout per surface, so cache if it hits a slow backend.
func (*Plugin) Handler ¶
Handler returns the http.Handler that serves the JSON-RPC endpoint at the plugin's path. Mount it yourself, or use ListenAndServe.
func (*Plugin) HeaderActions ¶
HeaderActions attaches action buttons to the most recently added contribution — a page/panel/dashboard toolbar (distinct from leaf actions inside the layout). Each ref names a registered Action; hope collects its fields, confirms danger, and audits. Call it right after Page/ContainerPanel/DashboardWidget.
func (*Plugin) Icon ¶
Icon sets the plugin's default icon — a hope built-in name or one of the Icons keys registered with Icons.
func (*Plugin) Icons ¶
Icons registers plugin-scoped icons: name -> inner SVG markup (path/circle/rect elements only, NOT a full <svg>), 24x24 stroke to match hope's icon set. hope resolves and sanitizes these in a per-plugin namespace, so they can't collide with other plugins or shadow hope's built-ins.
func (*Plugin) ListenAndServe ¶
ListenAndServe serves the plugin on addr (e.g. ":8080"). Blocks. A ReadHeaderTimeout guards against a slow-header (Slowloris) client holding a goroutine; no WriteTimeout is set because stream handlers write indefinitely.
func (*Plugin) MaxBodyBytes ¶
MaxBodyBytes overrides the request body cap (default 4 MiB).
func (*Plugin) OnEvent ¶ added in v0.1.0
OnEvent registers a handler for hope events (stack/container/image/plugin/agent changes, and plugin-published events). hope pushes each relevant event as a unary hope.event call. Registering a handler auto-declares the events:subscribe permission, so the operator is asked to consent on enable; without the grant hope never calls it. Call RequirePermission(ScopeEventsSubscribe, "<reason>") first to set a custom consent reason.
func (*Plugin) OnInit ¶ added in v0.0.7
OnInit registers a handler hope calls once the plugin is reachable and being initialized (hope.init) — and again if the plugin restarts. It receives the current settings, so a plugin that needs its config at startup (a pool sized by a setting, a mode flag) can initialize with them instead of booting on defaults and waiting for a later hope.settings push. Optional: without it, settings are still applied so SettingValue works immediately. Errors are returned to hope (the install surfaces them). Register before ListenAndServe.
func (*Plugin) Page ¶
Page contributes a full custom nav page (the `page` surface). hope lists it in the rail under the plugin and renders the node tree as a full page.
func (*Plugin) PageID ¶
PageID gives the most recently added page contribution a stable id so links and breadcrumbs can target it by name (a plugin doesn't know positional page paths). Call right after Page.
func (*Plugin) Path ¶
Path overrides the endpoint path (default /__hope). Must match the hope.plugin.path label.
func (*Plugin) Publish ¶ added in v0.1.0
Publish emits an event onto hope's bus. Requires (a) the events:publish permission granted by the operator and (b) hope's reverse channel configured. hope stamps the Source and namespaces the Kind (plugin.<identity>.<kind>), so only Kind + Data here are meaningful — a plugin can never spoof another's events or a core hope kind.
func (*Plugin) QueryView ¶
QueryView registers a Query view whose input editor syntax-highlights lang and prepopulates with def (a template where {param} placeholders are filled from the page's param, e.g. "select * from {table}"). Read the input with plugin.Input.
func (*Plugin) RequestPermission ¶ added in v0.1.0
RequestPermission asks the operator, at runtime, to grant a scope this plugin doesn't yet hold (the Android runtime-permission model). It does not grant anything — hope raises a consent prompt; the plugin gains the capability only if the operator allows it. Safe to call repeatedly: an already-granted/denied/pending scope is a no-op on hope's side. Declaring the scope up front via RequirePermission (so it's asked at enable) is usually better; use this when a capability is only needed conditionally at runtime.
func (*Plugin) RequirePermission ¶ added in v0.1.0
RequirePermission declares that the plugin wants a reverse capability (an events:*/storage/... scope). It appears in hope.schema; the operator consents when enabling the plugin, and hope gates the capability on the grant. Idempotent per scope — a later call only updates the reason if one is given.
func (*Plugin) ResolveAlert ¶ added in v0.1.0
ResolveAlert clears a previously-raised alert (matched by dedupeKey): hope surfaces it as a "resolved" confirmation and drops the alert from any active view. Use it when a monitored condition recovers. Requires events:publish.
func (*Plugin) SearchView ¶
SearchView registers a Search (autocomplete) view: hope calls fn with {q: <text>} as the user types and renders the returned SearchData as a live dropdown; selecting a SearchItem navigates to its To. Read the query with plugin.SearchQuery(ctx). Great for a "go to <entity>" jump box.
func (*Plugin) Setting ¶
Setting declares an operator-managed configuration field. hope renders these in the plugin inspector, persists the values (encrypted), and pushes them to the plugin; read a value in any handler with SettingValue. Settings are config the operator SETS — distinct from the panel the plugin SHOWS.
func (*Plugin) SettingValue ¶
SettingValue returns the current value of an operator-managed setting (the value hope last pushed), falling back to the declared default. Safe for concurrent use.
func (*Plugin) StackWidget ¶ added in v0.0.2
StackWidget contributes a widget to the stack view (the `stack` surface): the node renders as a panel on a stack's page, for stacks whose containers the Match selects. It's the container panel's whole-stack analog — same Match semantics (images/labels/services/always), but evaluated against the stack's set of containers rather than one. Keep it focused: a stack-scoped overview or action.
func (*Plugin) StatView ¶
StatView registers a Stat view: the handler returns StatData (one or more big-number blocks — counts, totals, sizes). Add plugin.Refreshable() for a manual refresh button (e.g. "count rows in my table" on demand).
func (*Plugin) Stream ¶
func (p *Plugin) Stream(method, label string, kind StreamKind, fn StreamFunc) *Plugin
Stream registers a live stream emitted as NDJSON frames.
func (*Plugin) Subtitle ¶
Subtitle sets the page header's sub/meta line for the most recently added page contribution ({param} placeholders filled from the page param). Call after Page.
func (*Plugin) TableView ¶
TableView registers a Table view. Add RowDetail/RowActions opts to make rows interactive — e.g. TableView("rows","Rows",fn, plugin.RowDetail("inspect"), plugin.RowActions(plugin.RowAction{Method:"del",Label:"Delete",Danger:true})).
func (*Plugin) TextView ¶
TextView registers a Text view: the handler returns {text: "…"} (or a raw string) rendered as a monospace scrollable block — logs, config, command output. opts (Static/EmptyView/…) are optional.
type RowAction ¶
type RowAction struct {
Method string `json:"method"`
Label string `json:"label"`
Icon string `json:"icon,omitempty"`
Danger bool `json:"danger,omitempty"` // hope confirms before running and audit-logs it
Fields []Field `json:"fields,omitempty"` // optional input collected before the call
Tip *Tooltip `json:"tip,omitempty"` // hover tooltip on the row action button (build with Tip)
}
RowAction is one author-declared action bound to a table row. hope calls Method with {row: {column: value}} (plus the page param); use for row-scoped mutations. If Fields is set, hope collects them first and merges the values into the call params alongside row — e.g. a "Rename" action with a new-name field.
type Schema ¶
type Schema struct {
ProtocolVersion int `json:"protocolVersion"`
Name string `json:"name"`
Version string `json:"version"`
Description string `json:"description,omitempty"`
Icon string `json:"icon,omitempty"` // default icon: a hope built-in OR an Icons key
Icons map[string]string `json:"icons,omitempty"` // plugin-scoped {name: inner-svg-markup}
Actions []ActionDesc `json:"actions"`
Views []ViewDesc `json:"views"`
Streams []StreamDesc `json:"streams"`
Settings []Setting `json:"settings,omitempty"` // operator-managed config (see Setting)
Permissions []Permission `json:"permissions,omitempty"` // reverse-capability requests (see Permission)
}
Schema is the plugin's identity + capability manifest — the result of the fixed hope.schema method. It is the only method hope calls before the operator enables the plugin (discovery), so it must be safe to expose unauthenticated.
type SearchData ¶
type SearchData struct {
Items []SearchItem `json:"items"`
}
SearchData is what a Search view returns for a query: the current suggestion list.
type SearchItem ¶
type SearchItem struct {
Label string `json:"label"`
Sub string `json:"sub,omitempty"`
Image string `json:"image,omitempty"`
To string `json:"to"`
}
SearchItem is one autocomplete suggestion. Label is the primary text; Sub is a dim secondary line (e.g. an id); Image is an optional thumbnail (absolute http(s) URL); To is where selecting it navigates — plugin-relative like a Link/DetailLink cell (e.g. "creator/42").
type Setting ¶
type Setting struct {
Key string `json:"key"`
Label string `json:"label"`
Kind SettingKind `json:"kind,omitempty"` // default text
Default string `json:"default,omitempty"`
Hint string `json:"hint,omitempty"`
Options []Option `json:"options,omitempty"` // for kind=select
}
Setting is one operator-managed configuration field the plugin exposes. Unlike an action's Fields (per-invocation input), settings are configured once in the plugin inspector, persisted by hope (encrypted at rest), and pushed to the plugin via the reserved hope.settings method — read them in a handler with plugin.SettingValue(key). This is distinct from the plugin's rendered panel & metrics, which live on the container inspector: settings are what the operator CONFIGURES, the panel is what the plugin SHOWS.
type SettingKind ¶
type SettingKind string
SettingKind enumerates the input type for an operator-managed plugin setting.
const ( SettingText SettingKind = "text" SettingTextarea SettingKind = "textarea" SettingSelect SettingKind = "select" SettingToggle SettingKind = "toggle" SettingNumber SettingKind = "number" SettingSecret SettingKind = "secret" // masked input; hope stores it encrypted, never renders it back )
type StatBlock ¶
type StatBlock struct {
Label string `json:"label"`
Value any `json:"value"`
Unit string `json:"unit,omitempty"`
Sub string `json:"sub,omitempty"`
Tone string `json:"tone,omitempty"` // ok|warn|bad|info
Icon string `json:"icon,omitempty"`
Tip *Tooltip `json:"tip,omitempty"` // hover tooltip explaining the metric (build with Tip)
}
StatBlock is one stat: a big Value with a Label, optional Unit, a Sub line (e.g. a delta or context), a semantic Tone, and an optional Icon.
type StatData ¶
type StatData struct {
Stats []StatBlock `json:"stats"`
}
StatData is what a Stat view returns: one or more big-number stat blocks (counts, totals, sizes). Return {stats: [...]} for a row, or a single Stat.
type Storage ¶ added in v0.1.0
type Storage struct {
// contains filtered or unexported fields
}
Storage is a handle to this plugin install's durable key/value store, persisted by hope. Values are opaque JSON hope never interprets, namespaced to this install's stable identity (two installs of the same image, or the same install on different hosts, are isolated; replicas of one install share it — last write wins). Requires the storage permission and hope's reverse channel; every op returns ErrNoReverseChannel until hope.init delivers a callback URL.
Use it for config a stateless plugin has nowhere else to keep — e.g. alert rules the operator defined. It's for small config, not bulk data (hope caps value size).
func (*Storage) Get ¶ added in v0.1.0
Get decodes the value at key into out. Returns (false, nil) when the key is absent.
type StreamDesc ¶
type StreamDesc struct {
Method string `json:"method"`
Label string `json:"label"`
Kind StreamKind `json:"kind"`
Icon string `json:"icon,omitempty"`
}
StreamDesc describes a live stream and how to render it.
type StreamFunc ¶
StreamFunc emits frames until it returns or ctx is cancelled (which hope does the moment the UI disconnects — so a dropped viewer never leaves you emitting forever). Always select on ctx.Done() in long loops.
type StreamKind ¶
type StreamKind string
StreamKind tells hope how to render a live NDJSON stream.
const ( Counter StreamKind = "counter" // number(s) ticking -> stat Log StreamKind = "log" // append-only lines Series StreamKind = "series" // time series -> sparkline )
type Surface ¶
type Surface string
Surface names a mount point in the hope UI. The schema defines them all so the wire is forward-compatible; hope renders the surfaces it knows and silently ignores any it doesn't, so a newer plugin degrades gracefully on older hope.
const ( SurfaceContainer Surface = "container" // panel/tab in the container inspector SurfacePage Surface = "page" // full custom nav page (+ dynamic nested pages) SurfaceRail Surface = "rail" // rail/nav entry + actions SurfaceDashboard Surface = "dashboard" // fleet/host dashboard widget SurfaceStack Surface = "stack" // stack-view widget (matched to the stack's containers) SurfaceCommand Surface = "command" // command-palette entry )
type TableData ¶
type TableData struct {
Columns []string `json:"columns"`
Rows [][]any `json:"rows"`
Total int `json:"total"`
Hidden []string `json:"hidden,omitempty"`
// ColumnTips maps a column name to a hover tooltip on its header — for clarifying
// a terse or computed column (e.g. "health": Tip("seq-scan / bloat state")).
ColumnTips map[string]*Tooltip `json:"column_tips,omitempty"`
// RowMethod makes rows clickable, opening the row-detail modal via that method —
// the data-side equivalent of the RowDetail table option (for views without opts,
// like a QueryView).
RowMethod string `json:"row_method,omitempty"`
// RowFlyout makes rows clickable into a right-side DRAWER instead of a modal. The
// method receives the clicked {row} and returns a component tree (plugin.Box(...)) hope
// renders in the flyout. Data-side equivalent of the RowFlyout table option. When both
// RowFlyout and RowMethod are set, the flyout wins.
RowFlyout string `json:"row_flyout,omitempty"`
// Flush renders the table full-height with no toolbar (filter/pager) and no inner scroll
// box — it flows and the containing panel/flyout scrolls instead. Meant for an embedded
// CTable showing a bounded list (e.g. the badges on a canvas), not a big paged view.
Flush bool `json:"flush,omitempty"`
}
TableData is what a Table view returns: the column names, the rows (each a slice of cells — plain scalars or rich cells like Badge/Link/Image), the total row count (for the pager; for a server table this is the full count, not len(Rows)), and optional hidden column names (kept in each row for row-detail/actions but not rendered). Build it with plugin.Table(...).
type TableOpt ¶
type TableOpt func(*ViewDesc)
TableOpt configures an interactive Table view (row-click detail, row actions).
func DefaultSort ¶
DefaultSort sets the sort a server table applies on first load (before the user touches a column header) — e.g. DefaultSort("indexed", "desc") for newest-first. dir is "asc" or "desc"; column must be one your handler's sort map accepts.
func Editable ¶
Editable makes cells editable: editing one calls method with {row, column, value}. Pass column names to limit which are editable (none => every column).
func Facets ¶
Facets adds dropdown filters to a server table; the selected values arrive in the query as filters[key] (read with ReadTableQuery). Apply them in your store.
func NoFilter ¶
func NoFilter() TableOpt
NoFilter hides a table's search box — for a plain paged list with no user search.
func NoSort ¶
func NoSort() TableOpt
NoSort makes a table's column headers non-interactive — the order is fixed by your handler (e.g. always newest-first), not user-sortable. Pair with NoFilter for a pure paged list.
func RowActions ¶
RowActions adds per-row action buttons; each click calls the action's Method with {row: {column: value}}. Use for row-scoped mutations like "delete row".
func RowDetail ¶
RowDetail makes table rows clickable: a click calls method with {row: {column: value}} and shows the returned kv/table in a modal (an author-controlled RPC).
func RowDetailButton ¶
RowDetailButton is like RowDetail but triggers from a per-row button instead of a whole-row click — use it when the row is also inline-editable so the click to edit a cell and the click to open the detail don't collide.
func RowFlyout ¶ added in v0.0.8
RowFlyout makes table rows clickable into a right-side DRAWER (not a modal): a click calls method with {row: {column: value}} and hope renders the returned component tree (plugin.Box(...) — image, key/values, Buttons bound to actions) in the flyout. Richer than RowDetail's modal; good for a compact record shown beside the table. If both RowFlyout and RowDetail are set, the flyout wins.
func ServerSide ¶
func ServerSide() TableOpt
ServerSide marks a table server-driven: hope sends the query state each call and expects one page + a total back, so a table too large to ship whole still works. Read the query with plugin.ReadTableQuery and return {columns, rows, total}.
type TableQuery ¶
type TableQuery struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
Sort TableSort `json:"sort"`
Filter string `json:"filter"`
Filters map[string]string `json:"filters"`
}
TableQuery is hope's per-call query state for a ServerSide table: which page (0- based) of what size, an optional sort, the free-text filter, and any Facet selections (filters[key] = chosen value). Use it to run just that slice of your data and return {columns, rows, total}.
func ReadTableQuery ¶
func ReadTableQuery(ctx context.Context) (q TableQuery, ok bool)
ReadTableQuery reads the server-table query state hope sends (under "_q"). ok is false when the call carried none (e.g. a non-server table) — treat that as page 0.
type TableSort ¶
TableSort is the sort request for a server-driven table: which column, and the direction (1 ascending, -1 descending; 0 = unsorted).
type TextData ¶
type TextData struct {
Text string `json:"text"`
}
TextData is what a Text view returns: a block of monospace text (logs, config, command output). A Text handler may also return a raw string.
type TipPos ¶ added in v0.0.4
type TipPos string
TipPos is where a tooltip points relative to its target.
type Tooltip ¶ added in v0.0.4
Tooltip is hover help with an optional placement. Build one with Tip("text") or Tip("text", plugin.TipBottom). Empty Pos renders at the top.
type TreeData ¶ added in v0.0.5
type TreeData struct {
Nodes []TreeNode `json:"nodes"`
}
TreeData is what a Tree view returns: a hierarchy of nodes. Return it (or a bare map literal) from a Tree handler.
type TreeNode ¶ added in v0.0.5
type TreeNode struct {
Label string `json:"label"`
Icon string `json:"icon,omitempty"` // built-in icon name or an Icons key
Tone string `json:"tone,omitempty"` // ok|warn|bad|info dot
To string `json:"to,omitempty"` // plugin-relative nav target (like a Link cell)
Collapsed bool `json:"collapsed,omitempty"`
Tip *Tooltip `json:"tip,omitempty"`
Children []TreeNode `json:"children,omitempty"`
}
TreeNode is one node in a Tree view. Children make it a collapsible group (Collapsed starts it closed). A non-empty To makes the label a plugin-relative link (like a Link/DetailLink cell), so a tree can navigate — e.g. To: "table/public.users". Icon and Tone add a leading icon and a status dot; Tip adds a hover tooltip. A node can be both a group and a link: the caret toggles children, the label navigates.
type ViewDesc ¶
type ViewDesc struct {
Method string `json:"method"`
Label string `json:"label"`
Kind ViewKind `json:"kind"`
Icon string `json:"icon,omitempty"`
// Empty customizes the "no data" state (see EmptyState); unset => hope's generic text.
Empty *EmptyState `json:"empty,omitempty"`
Lang string `json:"lang,omitempty"` // query views: syntax-highlight language (sql, json, …)
Default string `json:"default,omitempty"` // query views: initial text; {param} placeholders are filled from the page param
// RowMethod (table/query views): a method hope calls to open a row-detail modal,
// with params {row: {column: value}}. The result (kv or table) is shown in a
// modal — a fully author-controlled row-detail RPC.
RowMethod string `json:"row_method,omitempty"`
// RowFlyout (table/query views): like RowMethod but opens a right-side DRAWER instead
// of a modal. The method receives {row} and returns a component tree hope renders in
// the flyout. When both are set, the flyout wins.
RowFlyout string `json:"row_flyout,omitempty"`
// RowDetailButton triggers RowMethod from a dedicated per-row button instead of a
// whole-row click. Use when the row body is otherwise interactive (e.g. inline-
// editable cells) so the two don't fight.
RowDetailButton bool `json:"row_detail_button,omitempty"`
// RowActions (table/query views): per-row action buttons. hope renders each in a
// trailing column (and in the row-detail modal); clicking one calls its Method
// with {row: {column: value}} — an author-controlled mutation like "delete row".
// A Danger action is confirmed first; on success hope refetches the table.
RowActions []RowAction `json:"row_actions,omitempty"`
// PageSize (table/query views) sets how many rows hope shows per page — the
// author knows the shape of their data, so paging is plugin-level. 0 => hope's
// default.
PageSize int `json:"page_size,omitempty"`
// EditMethod (table/query views): editing a cell calls this method with
// {row: {column: value}, column, value}. Return ok (and optionally a message).
// EditColumns limits which columns are editable (empty => all). An
// author-controlled inline-edit RPC — hope refetches the table on success.
EditMethod string `json:"edit_method,omitempty"`
EditColumns []string `json:"edit_columns,omitempty"`
// Server marks a table server-driven: hope does NOT ship every row and page in
// the browser. Instead it sends the query state ({_q: {page, page_size, sort,
// filter}}) on each call and expects one page back plus a total row count
// ({columns, rows, total}). Required for tables too large to send whole — read
// the query with plugin.ReadTableQuery.
Server bool `json:"server,omitempty"`
// Refresh adds a manual refresh button to the view header that re-fetches it —
// e.g. a stat/counter you want to recompute on demand.
Refresh bool `json:"refresh,omitempty"`
// RefreshInterval auto-refetches the view every N seconds (0 = off) — a live-ish
// view without a stream. hope stops the timer when the view leaves the DOM.
RefreshInterval int `json:"refresh_interval,omitempty"`
// Static marks a view's data as fixed for the life of the surface: hope fetches it
// once and serves the cached result on tab re-entry / re-navigation instead of
// re-calling the plugin — cutting round-trips and easing the per-plugin rate limit.
// A manual Refresh() button still forces a re-fetch, so Static()+Refresh() = "load
// once, refresh on demand". Don't combine with RefreshInterval.
Static bool `json:"static,omitempty"`
// Facets (server tables) are dropdown filters hope renders in the toolbar; the
// selected values arrive in the query as filters[key] (see TableQuery.Filters),
// which you apply in your store. Distinct from the free-text search box.
Facets []Facet `json:"facets,omitempty"`
// DefaultSort (server tables) is the sort hope applies on FIRST load, before the
// user clicks any column header — e.g. newest-indexed first. hope seeds the query
// state with it (so it arrives in TableQuery.Sort) and shows the arrow on that
// column. The user can still re-sort. Column must be one your handler accepts.
DefaultSort *SortSpec `json:"default_sort,omitempty"`
// NoFilter hides the search box and NoSort makes column headers non-interactive —
// for a plain paged list (a fixed order your handler controls, no user search/sort).
NoFilter bool `json:"no_filter,omitempty"`
NoSort bool `json:"no_sort,omitempty"`
}
ViewDesc describes a read-only data view and how to render it.
type ViewFunc ¶
ViewFunc returns the data for a view; hope renders it per the view's ViewKind. For a Query view, the user's input arrives in the request params — read it with plugin.Input(ctx) (or plugin.Params for structured input).
type ViewKind ¶
type ViewKind string
ViewKind tells hope how to render a view's returned data.
const ( KV ViewKind = "kv" // flat key/value map -> hope-kvlist Table ViewKind = "table" // {columns, rows} -> paginated grid Query ViewKind = "query" // user-edited input -> tabular result grid (e.g. a SQL box) Tree ViewKind = "tree" // hierarchy -> tree browser (e.g. a schema) Chart ViewKind = "chart" // {type, labels, series} -> bar/line chart (see ChartData) Cards ViewKind = "cards" // {items:[Card]} -> a responsive grid of cards (a gallery) Stat ViewKind = "stat" // {stats:[Stat]} (or one Stat) -> big-number stat blocks (see StatData) Text ViewKind = "text" // {text:"…"} (or a raw string) -> a monospace scrollable block (logs, config, output) // Search is an autocomplete box: hope calls the method with {q: <typed text>} as the // user types (debounced) and renders the returned SearchItems as a live dropdown. // Selecting one navigates to its To (a DetailPage/link target) — a "go to X" jump. Search ViewKind = "search" // CompView is the escape hatch: the handler returns a *Comp — a tree of safe // primitives (box/row/heading/keyval/sparkline/cell/…) hope composes into a custom // widget the built-in kinds don't cover. See component.go. Also usable inline in a // layout via plugin.Component (no per-view round-trip). CompView ViewKind = "component" )
type ViewOpt ¶
type ViewOpt = TableOpt
ViewOpt configures a view (Refreshable, PageSize on tables, …). TableOpt is the same underlying type, so table options compose here too.
func EmptyView ¶ added in v0.0.6
EmptyView customizes the "no data" state shown when a view resolves empty (an empty table, a stat with no blocks) instead of hope's generic text — a title plus optional icon/text (or a custom Comp). Attach it wherever view opts are accepted (TableView, StatView, ComponentView), e.g.
p.TableView("slow", "Slow queries", fn,
plugin.EmptyView("No slow queries 🎉", plugin.EmptyIcon("check")))
func RefreshEvery ¶
RefreshEvery auto-refetches the view every n seconds (a live-ish view without a stream). hope stops the timer when the view leaves the DOM.
func Refreshable ¶
func Refreshable() ViewOpt
Refreshable adds a manual refresh button to the view header (re-fetches on click).
func Static ¶ added in v0.0.6
func Static() ViewOpt
Static marks a view's data fixed for the life of the surface: hope fetches it once and reuses the cached result on tab re-entry / re-navigation instead of re-calling the plugin — fewer round-trips, less rate-limit pressure. Pair with Refreshable() for a "load once, refresh on demand" view. Don't combine with RefreshEvery.