Documentation
¶
Index ¶
- Constants
- func Exec[R any](container IPMAASContainer, f func() R) (R, error)
- func ExecValueFunctionOnPluginGoRoutine[R any](container IPMAASContainer, f func() R, defaultValueFn func() R, ...) (R, error)
- type CompiledTemplate
- type EntityInvocationHandlerFunc
- type EntityRenderFunc
- type EntityRenderer
- type EntityRendererFactory
- type EntityStubFactoryFunc
- type EntityView
- type EntityViewFunc
- type EntityViewResult
- type HttpHandlerOptions
- type IPMAASContainer
- type IPMAASPlugin
- type IPMAASRenderPlugin
- type IPMAASTemplateEnginePlugin
- type ITemplate
- type JsonHandlerFunc
- type LoadAverage
- type MemoryStats
- type MenuEntry
- type PluginVersion
- type RenderListOptions
- type RequestObjectFactoryFunc
- type RootStatusHandlerFunc
- type ServerStatus
- type StreamingEntityRenderFunc
- type TemplateInfo
Constants ¶
const PluginAssetPrefix = "/plugins-assets/"
PluginAssetPrefix is the fixed prefix under which a plugin's static content and template Scripts/Styles are served. It's deliberately disjoint from PluginRoutePrefix: static content is mounted as a whole subtree at a plugin's asset root, so if it shared PluginRoutePrefix, that mount would collide with the plugin's own list route (both would be the exact pattern "/plugins/<name>/", which net/http.ServeMux refuses to register twice) regardless of what relative paths the plugin chooses for AddRoute.
const PluginRoutePrefix = "/plugins/"
PluginRoutePrefix is the fixed prefix under which every plugin's routes are namespaced. The server always constructs a plugin's full route pattern as PluginRoutePrefix + ShortName + "/" + the relative path given to AddRoute/AddRouteWithOptions/AddJsonRoute.
Variables ¶
This section is empty.
Functions ¶
func Exec ¶ added in v0.0.9
func Exec[R any](container IPMAASContainer, f func() R) (R, error)
Exec enqueues f for execution on the plugin's own goroutine and blocks until it runs, returning its result. It's the "ask" half of the tell/ask split already used elsewhere in this codebase (see pmaas-common/mailbox.Mailbox.Exec) - EnqueueOnPluginGoRoutine alone is fire-and-forget (tell); Go doesn't support generic interface methods, so this has to be a package-level function taking the container as a parameter rather than a method on IPMAASContainer itself.
The same reentrancy warning as EnqueueOnPluginGoRoutine applies: calling Exec from a function already executing on the plugin's own goroutine deadlocks, since nothing will ever run f.
func ExecValueFunctionOnPluginGoRoutine ¶
func ExecValueFunctionOnPluginGoRoutine[R any]( container IPMAASContainer, f func() R, defaultValueFn func() R, errorMessage string) (R, error)
ExecValueFunctionOnPluginGoRoutine is Exec with a caller-supplied fallback value for when enqueueing fails, and a message to wrap the resulting error with. Callers should probably handle the error, but can be useful if you really don't care.
Types ¶
type CompiledTemplate ¶
type EntityRenderFunc ¶
type EntityRenderer ¶
type EntityRenderer struct {
RenderFunc EntityRenderFunc
StreamingRenderFunc StreamingEntityRenderFunc
Styles []string
Scripts []string
}
func TemplateBasedRendererFactory ¶
func TemplateBasedRendererFactory( container IPMAASContainer, templateInfo *TemplateInfo, typeCheckFn func(any) bool, typeName string) (EntityRenderer, error)
type EntityRendererFactory ¶
type EntityRendererFactory func() (EntityRenderer, error)
type EntityStubFactoryFunc ¶
type EntityView ¶ added in v0.0.21
type EntityView struct {
// Key says which card this is. Entities whose views have the same key are one card: a device's card holds its children, which are
// entities of their own, and a page that lists them all draws the card once. Empty means the view is the entity's alone.
Key string
// Category is what kind of thing the card is, for a page that sorts entities by kind: one of the group package's Category
// constants, or a name of the plugin's own. A card is one kind even if it holds several (a device with a fan and a light is a
// fan). Empty leaves it to the page, which goes by the entity's registered type.
Category string
// Covers are the entities whose own cards this one makes redundant: a plugin that shows another plugin's entity in a fuller card
// (the environment plugin's thermometers) names the entity it shows, so a page that lists them all draws the fuller card and not
// both.
Covers []string
// Value is what the renderer is given.
Value any
}
EntityView is what an entity looks like on a page.
type EntityViewFunc ¶ added in v0.0.21
type EntityViewFunc func() (EntityView, error)
EntityViewFunc makes the view of an entity: the value a page draws a card for it from. The plugin that registered the entity gives one for it (see IPMAASContainer.RegisterEntityView), and the server runs it on that plugin's goroutine, so it may read the plugin's state. The value's type is what finds its renderer (see RegisterEntityRenderer), the same one the plugin's own page uses.
type EntityViewResult ¶ added in v0.0.21
type EntityViewResult struct {
EntityId string
Name string
EntityType reflect.Type
// Plugin is the short name of the plugin that owns the entity, and PluginPath where its page is.
Plugin string
PluginPath string
// View is the entity's view, or nil if its plugin gave none: all there is to show of it is the above.
View *EntityView
// Err is why the view could not be made, if it could not. The rest is filled in nevertheless.
Err error
}
EntityViewResult is the answer to a request for the view of an entity (see IPMAASContainer.GetEntityViews).
type HttpHandlerOptions ¶ added in v0.0.8
type HttpHandlerOptions struct {
SupportsXsrfValidation bool
RequiresXsrfValidation bool
// IncludeInMenu controls whether this route gets a navigation menu entry. If nil, the
// server applies its default: a plugin's list route (registered with relativePath "")
// defaults to included, every other route defaults to excluded. Set explicitly to
// override either default.
IncludeInMenu *bool
// MenuLabel is the display text for this route's menu entry. If empty, the server falls
// back to the plugin's ShortName() for the list route, or the route's relative path for
// any other route.
MenuLabel string
// MenuIcon is an optional icon identifier for this route's menu entry.
MenuIcon string
// MenuPriority is where the plugin's menu entry goes, on its list route: the higher the number, the earlier, and entries with the
// same priority are in alphabetical order. The default, 0, is for everything else. Entries above 0 come first, and a rule is drawn
// between them and the rest. The assembly can replace it for a plugin (config.PluginConfig.MenuPriority).
MenuPriority int
}
type IPMAASContainer ¶
type IPMAASContainer interface {
// AddRoute registers a handler for a path relative to this plugin's namespace. The
// server serves it at PluginFullPath(pluginShortName, path) - e.g. relativePath "" is
// the plugin's list route ("/plugins/<name>/"), and relativePath "callback" is served at
// "/plugins/<name>/callback". Plugins never include the "/plugins/<name>/" prefix
// themselves.
AddRoute(path string, handlerFunc http.HandlerFunc)
// AddRouteWithOptions is AddRoute with explicit HttpHandlerOptions, e.g. to control this
// route's menu visibility/label/icon.
AddRouteWithOptions(path string, handlerFunc http.HandlerFunc, options *HttpHandlerOptions)
// AddJsonRoute is AddRoute for a JSON request/response handler; path is relative exactly
// as in AddRoute.
AddJsonRoute(path string, requestFactoryFnc RequestObjectFactoryFunc, handlerFunc JsonHandlerFunc)
// GetMenu returns the current navigation menu, built from every plugin's menu-visible
// routes, in plugin-registration order.
GetMenu() []MenuEntry
// RouteFullPath returns the full, server-rooted path for one of this plugin's own routes at
// the given path relative to its own namespace - equivalent to
// PluginFullPath(ShortName(), relativePath), but without the plugin needing to know or pass
// its own ShortName(). Useful for building a fully-qualified URL to one of the plugin's own
// routes for use outside the server itself (e.g. an OAuth redirect URI).
RouteFullPath(relativePath string) string
// AssetFullPath returns the full, server-rooted path for one of this plugin's own static
// content/template assets at the given path relative to its own asset root - equivalent to
// PluginAssetFullPath(ShortName(), relativePath), but without the plugin needing to know or
// pass its own ShortName().
AssetFullPath(relativePath string) string
BroadcastEvent(entityEventId string, event any) error
RenderList(w http.ResponseWriter, r *http.Request, options RenderListOptions, items []interface{})
GetTemplate(templateInfo *TemplateInfo) (CompiledTemplate, error)
GetEntityRenderer(entityType reflect.Type) (EntityRenderer, error)
RegisterEntityRenderer(entityType reflect.Type, renderFactory EntityRendererFactory)
EnableStaticContent(staticContentDir string)
// GetBaseUrl returns the base URL (scheme://host[:port]) that should be used to build absolute
// URLs (e.g. OAuth redirect URIs) back to this server, chosen from the server's configured list
// of base URLs by matching the given request's Host header. This lets a single deployment serve
// requests under multiple externally-reachable names (e.g. "localhost" during development and a
// real hostname in production) without trusting the request itself for scheme/host: the returned
// value always comes from server configuration, never from request headers directly. Returns an
// error if no configured base URL matches the request's Host header.
GetBaseUrl(r *http.Request) (string, error)
// ProvideContentFS Registers an io/fs.FS instance that the server can use to read plugin resources such as
// templates or static file content for serving over HTTP.
ProvideContentFS(fs fs.FS, prefix string)
// RegisterEntity Registers an entity with server. This gives it a unique name, which can be used for
// further interaction with the entity.
//
// The server guarantees that the provided stubFactoryFn will only ever be called on the
// plugin's main goroutine.
RegisterEntity(
uniqueData string,
entityType reflect.Type,
name string,
stubFactoryFn EntityStubFactoryFunc) (string, error)
// DeregisterEntity Removes an entity previously registered with the server. Pass the ID returned from the
// previous call to RegisterEntity.
DeregisterEntity(id string) error
// RegisterEntityView gives the entity a view, which is how it looks on a page that lists entities of different plugins (see
// GetEntityViews). The entity must be one this plugin registered. Replaces an earlier view for it. It goes when the entity is
// deregistered.
RegisterEntityView(entityId string, view EntityViewFunc) error
// GetEntityViews makes the views of the entities, in the order asked, with what is known about each. An id that isn't an entity
// gives a result with an error. The views are made on the goroutines of the plugins that own the entities, one after another, so
// this must not be called from the goroutine of such a plugin; a page handler is fine.
GetEntityViews(entityIds []string) []EntityViewResult
// AssertEntityType Verifies that an entity with the given ID exists and is of the passed type
AssertEntityType(pmaasEntityId string, entityType reflect.Type) error
// GetEntities Returns a list of entities that match the supplied predicate.
GetEntities(predicate func(info *entity.RegisteredEntityInfo) bool) ([]entity.RegisteredEntityInfo, error)
// InvokeOnEntity Invokes the supplied function on the plugin-runner goroutine of the plugin that owns the specified
//entity, supplying the entity.
InvokeOnEntity(id string, function func(entity any)) error
// RegisterEventReceiver Registers a receiver for events. If successful, returns an integer handle that can be used
// to deregister the handler in the future.
RegisterEventReceiver(predicate events.EventPredicate, receiver events.EventReceiver) (int, error)
// DeregisterEventReceiver Removes a previously registered event receiver
DeregisterEventReceiver(receiverHandle int) error
// EnqueueOnPluginGoRoutine Enqueues the passed function for execution on the plugin's main GoRoutine.
// Returns an error if the function cannot be enqueued. This method returns as soon as the function
// is enqueued, it does not wait for execution. If you need the results of the execution, you'll need to
// orchestrate that in the function. For example, the function can send the result back via a channel.
// Warning: Calling this function when already executing on the plugin's main GoRoutine will result in deadlock.
EnqueueOnPluginGoRoutine(f func()) error
// EnqueueOnServerGoRoutine Enqueues the passed functions for execution on the server's main GoRoutine.
// The server's main GoRoutine is the one used to call PMAAS.Run().
// Use this to execute callbacks registered during the server configuration phase.
EnqueueOnServerGoRoutine(invocations []func()) error
// ClosedCallbackChannel returns an already closed callback channel. You can use this when you know
// there will not be a need for any callbacks.
ClosedCallbackChannel() chan func()
SaveConfig(config any) error
LoadConfig(f func(typeName string) any) (any, error)
// ProvideTLSCertificate registers this plugin as the server's TLS certificate provider. The
// getCertificateFunc is stored as-is and handed to Go's net/http.Server as
// tls.Config.GetCertificate, so it's invoked fresh on every incoming TLS handshake - a plugin
// that renews/rotates its certificate in the background (e.g. an ACME client) doesn't need to
// tell the server anything when that happens: the next handshake after a renewal automatically
// picks up whatever certificate the function now returns. Because it's called concurrently, once
// per handshake, getCertificateFunc must be safe for concurrent use.
//
// At most one plugin may call this - it returns an error if a certificate provider has already
// been registered by another plugin. If no plugin ever calls it, the server continues to serve
// plain HTTP exactly as it did before this method existed.
//
// Must be called during Init or Start, before this plugin's Start returns - the server reads the
// registered provider (if any) only once, when it starts listening, which happens strictly after
// every plugin has finished starting.
ProvideTLSCertificate(getCertificateFunc func(*tls.ClientHelloInfo) (*tls.Certificate, error)) error
// ProvideRootStatusHandler registers this plugin as the server's root ("/") status page
// handler. handlerFunc is called for every request to "/", along with a freshly computed
// ServerStatus - the plugin owns rendering it (e.g. via its own templates), keeping that
// presentation logic out of pmaas-core.
//
// At most one plugin may call this - it returns an error if a handler has already been
// registered by another plugin. If no plugin ever calls it, the server serves a minimal
// placeholder page at "/" instead.
//
// Must be called during Init or Start, before this plugin's Start returns - the same
// timing requirement as ProvideTLSCertificate, and for the same reason: the server only
// reads the registered handler (if any) once, when it starts listening.
ProvideRootStatusHandler(handlerFunc RootStatusHandlerFunc) error
}
IPMAASContainer is an interface for plugins to interact with the PMAAS server.
type IPMAASPlugin ¶
type IPMAASPlugin interface {
// ShortName returns the plugin's stable, URL-safe identifier (e.g. "nestthermostat").
// The server uses it to namespace every route the plugin registers via AddRoute/
// AddRouteWithOptions/AddJsonRoute under "/plugins/<ShortName>/", and to derive the
// plugin's default menu entry. It must be constant for the lifetime of the plugin
// and must not change between releases, since it becomes part of the plugin's public
// URL space.
ShortName() string
Init(container IPMAASContainer)
Start()
Stop() chan func()
}
IPMAASPlugin is the interface all plugins must implement. The core will call Init, Start, and Stop in sequence, all from the same go routine.
All of these functions will be called on the plugin's main goroutine.
The Stop function must not block. If there is blocking work to be done, do the work in a new or separate goroutine, and inform the server that it's complete by closing the returned channel. The Stop function can also request additional callbacks by sending functions to the returned channel.
type IPMAASRenderPlugin ¶
type IPMAASRenderPlugin interface {
IPMAASPlugin
RenderList(w http.ResponseWriter, r *http.Request, options RenderListOptions, items []interface{})
}
type IPMAASTemplateEnginePlugin ¶
type IPMAASTemplateEnginePlugin interface {
IPMAASPlugin
GetTemplate(templateInfo *TemplateInfo) (CompiledTemplate, error)
}
type JsonHandlerFunc ¶ added in v0.0.8
type LoadAverage ¶ added in v0.0.8
LoadAverage is the host system's 1/5/15-minute load average. See ServerStatus.LoadAverage for when it's available.
type MemoryStats ¶ added in v0.0.8
MemoryStats is this server process's own memory usage (from runtime.MemStats) - not the host system's total memory, which would need platform-specific code to obtain portably.
type MenuEntry ¶ added in v0.0.8
type MenuEntry struct {
Label string
Icon string
Path string
Children []MenuEntry
// Priority is what the entry is ordered by (see HttpHandlerOptions.MenuPriority). The menu is already in order.
Priority int
// Separator is that this is not an entry but a rule between the entries above and below it. It has no label or path.
Separator bool
}
MenuEntry describes one navigation menu item, built from the routes plugins have registered as menu-visible. Only a plugin's list route (relativePath "") can have Children - the server supports a single level of nesting, so a non-list-route entry never has children of its own.
type PluginVersion ¶ added in v0.0.8
PluginVersion identifies one running plugin's build version, for status-page display. Version comes from the running binary's own embedded module build info (see runtime/debug.ReadBuildInfo): a semantic version tag if the plugin's module was fetched at one, a pseudo-version encoding its git commit hash and commit time if not, or "(devel)" when built from a local replace/workspace directory rather than a fixed module version - as this project's own development builds normally are, via its go.work file. Empty if no build info is available at all.
type RenderListOptions ¶
type RenderListOptions struct {
Title string
// TitleSuffix is optional additional text shown after Title, rendered smaller/muted (see
// layout.htmlt's "title-suffix" class) since it can be an arbitrarily long string that would
// otherwise threaten to overflow the page header - e.g. the root status page's assembly
// name/version (see pmaas-plugin-basicwebui's statusTitle). Ignored (no visual effect) when
// empty.
TitleSuffix string
Header any
// Masonry asks for the items to be laid out so that each is as tall as its content, rather than every item in a row being as
// tall as the tallest, which wastes space on a page whose items vary a lot in height (a host with many interfaces next to one
// with none). It fills left to right and then drops each item into the shortest column. It is a request: a renderer that cannot
// do it, or a browser that cannot, lays the items out as it would otherwise, so it is safe to ask for.
Masonry bool
}
type RequestObjectFactoryFunc ¶ added in v0.0.8
type RequestObjectFactoryFunc func() any
type RootStatusHandlerFunc ¶ added in v0.0.8
type RootStatusHandlerFunc func(w http.ResponseWriter, r *http.Request, status ServerStatus)
RootStatusHandlerFunc handles a request to the server's root ("/") path, given a freshly computed ServerStatus - see IPMAASContainer.ProvideRootStatusHandler.
type ServerStatus ¶ added in v0.0.8
type ServerStatus struct {
Uptime time.Duration
// AssemblyName/AssemblyVersion identify the running assembly (the main module that wired
// this particular PMAAS binary together, e.g. pmaas-assembly-demo) - AssemblyName is the
// last path segment of its module path, AssemblyVersion its build version (see
// PluginVersion.Version for what that value can look like). Both empty if no build info is
// available at all.
AssemblyName string
AssemblyVersion string
// CommitTime is the timestamp of the git commit the running assembly was built from,
// sourced from the build's embedded VCS info (Go's toolchain stamps this in automatically
// for a build done inside a git checkout - see runtime/debug.BuildInfo.Settings' "vcs.time"
// key). It's the commit's time, not necessarily when `go build` was actually run. Useful
// as a freshness signal even when AssemblyVersion is "(devel)" (no fixed tag - this
// project's own go.work-based development builds) rather than a real semantic version.
// Zero if no VCS info was embedded at all (e.g. built outside a git checkout).
CommitTime time.Time
Plugins []PluginVersion
// LoadAverage is nil where a load average isn't available - currently anywhere other than
// Linux (this project's actual deployment target), or if it couldn't be read for any
// other reason.
LoadAverage *LoadAverage
Memory MemoryStats
}
ServerStatus is the data behind the server's root ("/") status page - see IPMAASContainer.ProvideRootStatusHandler.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package alert is how plugins and the server tell the user about something that needs their attention.
|
Package alert is how plugins and the server tell the user about something that needs their attention. |
|
Package automation is the contract between whatever runs automations (the automations plugin) and the automations themselves, which are plain code in an assembly or in a library of re-usable ones: turn the holiday lights on 15 minutes before sunset, and off at half past midnight.
|
Package automation is the contract between whatever runs automations (the automations plugin) and the automations themselves, which are plain code in an assembly or in a library of re-usable ones: turn the holiday lights on 15 minutes before sunset, and off at half past midnight. |
|
Package bluetooth is how Bluetooth LE advertisements get from where they are heard to where they are understood.
|
Package bluetooth is how Bluetooth LE advertisements get from where they are heard to where they are understood. |
|
bttest
Package bttest is for testing a device model with advertisements captured as hex, as a proxy sends them.
|
Package bttest is for testing a device model with advertisements captured as hex, as a proxy sends them. |
|
Package clock describes the server's clock: once the server is running it broadcasts an event each time the wall-clock minute, hour, day, month or year changes, so that something can be done at a time of day without every plugin running a timer of its own.
|
Package clock describes the server's clock: once the server is running it broadcasts an event each time the wall-clock minute, hour, day, month or year changes, so that something can be done at a time of day without every plugin running a timer of its own. |
|
Package device holds the producer-facing representations of home devices that aren't environmental sensors (see the environment package for those): a plugin that fits one of these shapes implements the matching interface and registers itself under its reflect.Type, the same way environment.IThermostat and environment.IWirelessThermometer work.
|
Package device holds the producer-facing representations of home devices that aren't environmental sensors (see the environment package for those): a plugin that fits one of these shapes implements the matching interface and registers itself under its reflect.Type, the same way environment.IThermostat and environment.IWirelessThermometer work. |
|
Package format says how long ago, or how long until, a time is, for pages: the plugins' templates all want it, and said it in several ways of their own.
|
Package format says how long ago, or how long until, a time is, for pages: the plugins' templates all want it, and said it in several ways of their own. |
|
Package group is a way to organise entities: a tree of named groups, each with the entities that are in it directly.
|
Package group is a way to organise entities: a tree of named groups, each with the entities that are in it directly. |
|
Package logging is how a plugin gets a logger, and how the levels of the server's loggers are looked at and changed.
|
Package logging is how a plugin gets a logger, and how the levels of the server's loggers are looked at and changed. |