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

@@ -2403,6 +2403,97 @@
],
"type": "object"
},
"cabana.MarketplaceMeta": {
"properties": {
"categories": {
"items": {
"type": "string"
},
"type": "array"
},
"registry": {
"$ref": "#/components/schemas/cabana.RegistryState"
},
"total": {
"type": "integer"
}
},
"required": [
"categories",
"registry",
"total"
],
"type": "object"
},
"cabana.MarketplacePlugin": {
"properties": {
"category": {
"type": "string"
},
"compatible": {
"type": "boolean"
},
"description": {
"type": "string"
},
"featured": {
"type": "boolean"
},
"iconUrl": {
"type": "string"
},
"id": {
"type": "string"
},
"installCommand": {
"type": "string"
},
"installed": {
"type": "boolean"
},
"latestFramework": {
"type": "string"
},
"latestVersion": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"category",
"compatible",
"description",
"featured",
"iconUrl",
"id",
"installCommand",
"installed",
"latestFramework",
"latestVersion",
"name"
],
"type": "object"
},
"cabana.MarketplaceResponse": {
"properties": {
"data": {
"items": {
"$ref": "#/components/schemas/cabana.MarketplacePlugin"
},
"type": "array"
},
"meta": {
"$ref": "#/components/schemas/cabana.MarketplaceMeta"
}
},
"required": [
"data",
"meta"
],
"type": "object"
},
"cabana.MessageForms": {
"additionalProperties": {
"type": "string"
@@ -4283,7 +4374,7 @@
},
"/plugins": {
"get": {
"description": "The plugins the application was built with, in summer.yaml order, with the name, description, version and source summer build baked in (only the ID for an application built before plugin metadata existed), the framework version the application was built with and whether the plugin registry is configured (registry.enabled false and registry.status disabled when plugins.registry_url is empty). Requires backend.view_plugins (superusers always pass).",
"description": "The plugins the application was built with, in summer.yaml order, with the name, description, version and source summer build baked in (only the ID for an application built before plugin metadata existed), the framework version the application was built with and the state of the plugin registry. For a plugin the registry lists (listed), latest is the newest release the built framework version allows, updateAvailable says whether it is newer than the installed version (a git describe version compares by its base tag) and updateCommand is then the summer command that installs it; latestFramework names the framework a newer, incompatible release needs. Registry calls are made by the server with a 4 second timeout and cached (versions 15 minutes, failures 1 minute). A registry that is disabled (plugins.registry_url empty, registry.enabled false) or does not answer leaves the list intact and sets registry.status to disabled or unavailable. Requires backend.view_plugins (superusers always pass).",
"responses": {
"200": {
"content": {
@@ -4327,6 +4418,80 @@
]
}
},
"/plugins/marketplace": {
"get": {
"description": "The plugin registry's search for q (matched against ID, name and description, at most 100 characters) and category (^[a-z0-9-]{1,40}$), both optional and forwarded to the registry; other values are a 400 invalid_query. Each plugin carries installed (the application already has it), compatible (the built framework version satisfies the newest release's framework minimum) and installCommand, the summer command that installs it; the admin only shows that command. meta.registry is the registry state, meta.categories the registry's categories for the filter. Searches are cached for 5 minutes. A disabled registry (registry.status disabled) or one that does not answer (unavailable) gives an empty list, never an error. Requires backend.view_plugins (superusers always pass).",
"parameters": [
{
"description": "Search text",
"in": "query",
"name": "q",
"schema": {
"type": "string"
}
},
{
"description": "Category",
"in": "query",
"name": "category",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/cabana.MarketplaceResponse"
}
}
},
"description": "OK"
},
"400": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/cabana.ErrorEnvelope"
}
}
},
"description": "Bad Request"
},
"401": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/cabana.ErrorEnvelope"
}
}
},
"description": "Unauthorized"
},
"403": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/cabana.ErrorEnvelope"
}
}
},
"description": "Forbidden"
}
},
"security": [
{
"BackendBearer": []
}
],
"summary": "Plugin marketplace search",
"tags": [
"admin"
]
}
},
"/settings": {
"get": {
"responses": {