feat(cabana): plugin updates and marketplace search on the Plugins screen

- GET /plugins looks up each versioned plugin's releases through
  kiosk.Client and applies plugin:update's rule (NewestCompatible above
  the BaseVersion), with the update command and the framework a newer
  release needs
- GET /plugins/marketplace forwards q and category (validated like the
  registry), marks installed and incompatible plugins and returns the
  install command; ids outside the plugin ID shape are dropped
- 4 s per-call timeout, at most 4 lookups at once, in-process cache
  (searches 5 min, releases 15 min, failures 1 min); a failing registry
  gives status unavailable, an empty plugins.registry_url sends nothing
- SPA: update badges and Marketplace tab with copy buttons (clipboard
  fallback), hidden when the registry is disabled; no install button
- OpenAPI, SPA types, inventories, conformance case, en/pl strings, docs
This commit is contained in:
Jakub Zych
2026-10-29 19:20:14 +01:00
parent c4b1ac4076
commit 7e8a19399d
18 changed files with 1624 additions and 79 deletions

View File

@@ -54,6 +54,9 @@ type service struct {
// bodies, since surf applies no body limit to raw routes.
uploadBytes int64
defaultBytes int64
// catalog is the Plugins screen's registry view with its cache, built
// by Activate.
catalog *pluginCatalog
}
// adminPrefix returns the mount path; a zero service uses the default.
@@ -238,6 +241,7 @@ func Activate(app *backpack.App, plugins []party.Plugin) (*Routes, error) {
return nil, fmt.Errorf("cabana: admin SPA: %w", err)
}
svc.spa = spa
svc.catalog = newPluginCatalog(app.Config)
return &Routes{Middleware: mw, Mount: svc.mount, Prefix: prefix}, nil
}
@@ -339,6 +343,7 @@ func (s *service) mount(r pact.Router) {
g.Get("/settings", s.settingsList)
// The read-only Plugins screen (backend.view_plugins).
g.Get("/plugins", s.pluginsInstalled)
g.Get("/plugins/marketplace", s.pluginsMarketplace)
g.Get("/settings/{code}/schema", s.settingsSchema)
constrainSetting(g)
g.Get("/settings/{code}", s.settingsGet)