Bring the wilm branch into main: endpoints plugin, agent, policies, CSV import
Ports the corporate-GitHub wilm branch onto main, keeping the two branches
separate. Thirteen upstream commits arrive with their authorship intact; the
review fixes and the portability work below are on top.
What arrives:
endpoints plugin - a ShopDB agent reporting endpoint state, telemetry,
services and interfaces, with a command channel the agent
long-polls. Ingest stays on the generic collector route
(ADR-006); the agent key is a UI-managed single-scope
token whose revocation is an instant kill switch.
computers policies - Configuration and Business Policies (RMM-style), scope
trees validated at write time, actions checked against
the endpoints command allowlist. Feature-gated: every
route 404s while the setting is off, so enabling it can
never half-expose the surface.
asset CSV import - templates, a collector script carrying the site path, and
a validate-then-commit import. A plugin can own a virtual
column plus a post-write row hook, which is how a PC's
machine number becomes a controls link without a
hand-built relationships sheet.
agent/ - the Windows agent and its installer.
Contract 0.21.0 -> 0.24.0, additive throughout: register_import_hook (0.22),
mint/list/revoke_service_token (0.23), collector_authorized (0.24). The stability
table had no rows for any of them and now does.
MySQL 5.6 portability. The branch stored three policy columns as sa.JSON, the
only JSON columns in the product, and 5.6 has no JSON type: the migration failed
with a bare syntax error near the word JSON. They are TEXT now, through a
JSONText type decorator, so all seventeen call sites still read and write dicts.
The convention elsewhere is already TEXT (guides.blocksjson,
geenforce.manifestjson).
Review fixes carried on top:
- six policy GETs were jwt_required(optional=True), which authenticates a
caller without checking anything; they require computers.view now, and six
redundant jwt_required decorators are gone
- the agent installer took a default BaseUrl and CollectorKey, so a mistyped
run silently pointed an endpoint at the wrong instance; both are mandatory
and a non-https BaseUrl throws unless -AllowInsecureHttp is passed
- a committing CSV import wrote no audit row
Documentation: the 34 new routes are described in docs/api-inventory.json, which
is hand-maintained on purpose, and the generated spec now covers 331 paths and
477 operations across 22 surfaces. DEVELOPMENT-SETUP's 1071 troubleshooting row
said "use MySQL 8 for dev" and now gives the actual fix, including the trap that
5.6 downgrades ROW_FORMAT=DYNAMIC to Compact with a warning instead of an error.
Full suite 2123 passed, vitest 260 passed, all seventeen plugin chains migrate
clean on MySQL 5.6 and the naming, docs, parity and read-surface gates are green.
This commit is contained in:
@@ -41,6 +41,9 @@ Recorded in the comment block in `shopdb/__init__.py`:
|
||||
| 0.19.0 | **BREAKING.** `get_dashboard_widgets` returns DATA AND SHAPE, not a component name. The old shape (`name` + `component` + `size`) named a Vue component per widget, which cannot survive a lean build - a plugin's component may never be staged into the frontend bundle (ADR-013) - and five plugins were declaring widgets that pointed at components nobody had written. A card now declares `id` / `title` / `endpoint` / `render` / `severity` / `permission` / `empty` / `position`; see PLUGIN-HOOKS.md. Also added `send_upload` so a plugin serving user-supplied bytes gets the headers that keep an SVG from running as script | **contract change (minor, pre-1.0)** |
|
||||
| 0.20.0 | The `Asset` model gained `levelid` (which floor plan its `mapx`/`mapy` are pixels of) and `mapverifiedat`; `Location` gained `levelid` too, and `resolve_asset_position` now returns the `levelid` belonging to whichever source supplied the coordinates. A plugin that writes a map position MUST write the level with it - a position without one renders as "level unknown" rather than being drawn on the default drawing, because a marker on the wrong floor plan looks correct (ADR-017) | additive surface (minor) |
|
||||
| 0.21.0 | Added the `get_guest_grantable_permissions` hook: a plugin names the WRITE permissions a site may grant to the seeded `guest` role, which is who anyone without a token now holds. Reads (`.view`/`.read`) are grantable without a plugin saying anything; anything else is refused. Also exposed `require_any_permission` on `shopdb.api` for a read two different rights should reach | additive optional hook + surface (minor) |
|
||||
| 0.22.0 | Added the `register_import_hook` surface: a plugin owns a VIRTUAL COLUMN on an importable CSV table - a header the bulk importer accepts but never writes - plus a post-write row hook that turns the value into a plugin concept. The computers plugin uses it for `machineassetnumber` on `computers.csv`, which builds the PC->machine controls link, so a site writes the machine number in the PC's own row instead of hand-building a separate relationships sheet. Core never learns what the column means. `RowWarning` is exported with it so a hook can report a soft failure without aborting the import | additive surface (minor) |
|
||||
| 0.23.0 | Added `mint_service_token`, `list_service_tokens` and `revoke_service_tokens` so a plugin can provision, list and revoke a single-scope managed service token without importing core token internals. The endpoints plugin uses them for a UI-managed, regenerable agent key scoped to `collector.ingest.endpoints` alone - narrow on purpose, so rotating or revoking it cannot disturb the computers or GE-Enforce collectors, and revocation is an instant kill switch on agent ingest | additive surface (minor) |
|
||||
| 0.24.0 | Added `collector_authorized(pluginname)` so a plugin can gate an agent-facing route that lives OUTSIDE the generic collector blueprint on the same per-plugin key and kill switch the ingest route uses. The endpoints command channel needs it: the agent long-polls `/api/endpoints/commands/next` and posts results back, and those are agent callbacks rather than JWT routes. Without it a plugin would reach into core collector internals to authorize them | additive surface (minor) |
|
||||
|
||||
The source comment block documents 0.3.0, 0.4.0, 0.6.0, 0.7.0, 0.9.0, 0.10.0,
|
||||
0.11.0, 0.16.0, 0.18.0 and 0.19.0; 0.12.0 through 0.15.0 and 0.17.0 are recorded
|
||||
|
||||
@@ -12,7 +12,7 @@ never by editing this file.
|
||||
| series | value | governed by |
|
||||
|---|---|---|
|
||||
| product `__version__` | `0.12.0` | ADR-007 |
|
||||
| plugin contract `__contract_version__` | `0.21.0` | ADR-002 |
|
||||
| plugin contract `__contract_version__` | `0.24.0` | ADR-002 |
|
||||
|
||||
They move independently. A contract bump is not a release.
|
||||
|
||||
@@ -25,8 +25,9 @@ with `flask plugin upgrade-all`. Both are needed on a deploy.
|
||||
|---|---|
|
||||
| core | `7d34_singular_relationship_types` |
|
||||
| backups | `backups0003clearlastseen` |
|
||||
| computers | `computers0001anchor` |
|
||||
| computers | `computers0004policies` |
|
||||
| employees | `employees0002photo` |
|
||||
| endpoints | `endpoints0003cmdtarget` |
|
||||
| geenforce | `geenforce0004minlib` |
|
||||
| guides | `guides0001baseline` |
|
||||
| knowledgebase | `knowledgebase0001anchor` |
|
||||
@@ -41,13 +42,14 @@ with `flask plugin upgrade-all`. Both are needed on a deploy.
|
||||
| usb | `usb0002dropmachineid` |
|
||||
| warranty | `warranty0002proof` |
|
||||
|
||||
## Bundled plugins (16)
|
||||
## Bundled plugins (17)
|
||||
|
||||
| plugin | version | core_version | owns migrations |
|
||||
|---|---|---|---|
|
||||
| backups | 1.0.0 | >=0.16.0,<1.0.0 | yes |
|
||||
| computers | 1.0.0 | >=0.20.0,<1.0.0 | yes |
|
||||
| employees | 1.0.0 | >=0.1.0,<1.0.0 | yes |
|
||||
| endpoints | 0.1.0 | >=0.20.0,<1.0.0 | yes |
|
||||
| geenforce | 0.1.0 | >=0.18.0,<1.0.0 | yes |
|
||||
| guides | 1.0.0 | >=0.1.0,<1.0.0 | yes |
|
||||
| knowledgebase | 1.0.0 | >=0.1.0,<1.0.0 | yes |
|
||||
@@ -88,6 +90,6 @@ Manifest-less directories under `plugins/` are core frontend surface and always
|
||||
|
||||
## Size
|
||||
|
||||
- test functions defined: **1418** (parametrised cases collect higher)
|
||||
- documented API paths: **305** (`docs/openapi.json`, regenerate with `scripts/gen_openapi.py`)
|
||||
- test functions defined: **1448** (parametrised cases collect higher)
|
||||
- documented API paths: **331** (`docs/openapi.json`, regenerate with `scripts/gen_openapi.py`)
|
||||
|
||||
|
||||
@@ -273,6 +273,38 @@
|
||||
"params": "path: filename",
|
||||
"purpose": "Serve an uploaded branding logo (public - kiosks and print pages read it)",
|
||||
"example": "curl -O http://localhost:5001/api/settings/branding/logo-site.svg"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/settings/asset-import/script",
|
||||
"auth": "admin role",
|
||||
"params": "none",
|
||||
"purpose": "Download Collect-ShopDB.ps1 with this site CSV path already embedded, so the collector script needs no hand editing before it runs",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/settings/asset-import/script -o Collect-ShopDB.ps1"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/settings/asset-import/templates",
|
||||
"auth": "admin role",
|
||||
"params": "none",
|
||||
"purpose": "List the importable CSV tables in the order they must be uploaded - foreign keys mean the order is not optional",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/settings/asset-import/templates"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/settings/asset-import/templates/<tablename>",
|
||||
"auth": "admin role",
|
||||
"params": "path: tablename",
|
||||
"purpose": "Download a generated CSV template for one importable table, with its real column names as the header row",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/settings/asset-import/templates/assets -o assets.csv"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/settings/asset-import/import",
|
||||
"auth": "admin role",
|
||||
"params": "multipart: one file per table; form: commit (false = validate only)",
|
||||
"purpose": "Validate uploaded CSVs and, only when commit=true, load them. The default is a dry run that reports what WOULD change; a committing run writes an AuditLog row with the reconciled count",
|
||||
"example": "curl -X POST -H \"Authorization: Bearer $TOKEN\" -F \"assets=@assets.csv\" -F \"commit=true\" http://localhost:5001/api/settings/asset-import/import"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1729,6 +1761,102 @@
|
||||
"params": "none",
|
||||
"purpose": "The collector reporter (Report-AssetToShopDB.ps1) stamped with THIS site's values: the site base URL becomes the -ApiUrl default and computers_routableranges becomes -AllowedRanges, so it downloads ready to deploy. Only the parameter DEFAULTS are substituted - the copy in plugins/computers/client/ stays runnable, so there is no second version to drift - and everything stamped stays overridable by argument or registry. The collector key is deliberately NOT included: this file lands on every shop-floor PC, and a token spread across hundreds of bays cannot be rotated quietly. Serves text/plain as an attachment, with the SHA-256 in X-Script-Sha256 so a deployment can verify what it fetched",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/computers/client-script"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/computers/policies/configuration",
|
||||
"auth": "computers.view",
|
||||
"params": "none",
|
||||
"purpose": "List Configuration Policies (scope + the actions applied to matching endpoints); 404 while the computers_configpolicies_enabled setting is off",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/computers/policies/configuration"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/computers/policies/configuration/<policy_id>",
|
||||
"auth": "computers.view",
|
||||
"params": "path: policy_id",
|
||||
"purpose": "One Configuration Policy with its actions expanded; 404 while the computers_configpolicies_enabled setting is off",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/computers/policies/configuration/1policy_id"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/computers/policies/configuration",
|
||||
"auth": "computers.create",
|
||||
"params": "body: name (required), description, scopejson (scope AST), actions[] of {action, mode: desired|gate, params}",
|
||||
"purpose": "Create a Configuration Policy. Scope trees go through validate_scope and every action name is checked against the endpoints command allowlist, so a bad tree or unknown action is rejected before it reaches the database; 404 while the computers_configpolicies_enabled setting is off",
|
||||
"example": "curl -X POST -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"name\":\"Shopfloor MTConnect\",\"actions\":[{\"action\":\"mtconnect_start\",\"mode\":\"desired\"}]}' http://localhost:5001/api/computers/policies/configuration"
|
||||
},
|
||||
{
|
||||
"method": "PUT",
|
||||
"path": "/api/computers/policies/configuration/<policy_id>",
|
||||
"auth": "computers.edit",
|
||||
"params": "path: policy_id; body: any of name, description, scopejson, actions[] (actions replace wholesale)",
|
||||
"purpose": "Update a Configuration Policy; same scope and action validation as create; 404 while the computers_configpolicies_enabled setting is off",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/computers/policies/configuration/1policy_id"
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/api/computers/policies/configuration/<policy_id>",
|
||||
"auth": "computers.delete",
|
||||
"params": "path: policy_id",
|
||||
"purpose": "Delete a Configuration Policy and its actions; 404 while the computers_configpolicies_enabled setting is off",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/computers/policies/configuration/1policy_id"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/computers/policies/business",
|
||||
"auth": "computers.view",
|
||||
"params": "none",
|
||||
"purpose": "List Business Policies (ordered bundles of Configuration Policies); 404 while the computers_configpolicies_enabled setting is off",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/computers/policies/business"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/computers/policies/business/<policy_id>",
|
||||
"auth": "computers.view",
|
||||
"params": "path: policy_id",
|
||||
"purpose": "One Business Policy with its member Configuration Policies in sort order; 404 while the computers_configpolicies_enabled setting is off",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/computers/policies/business/1policy_id"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/computers/policies/business",
|
||||
"auth": "computers.create",
|
||||
"params": "body: name (required), description, priority, items[] of {configurationpolicyid, sortorder}",
|
||||
"purpose": "Create a Business Policy. Every referenced Configuration Policy must exist; priority decides which policy wins when two match the same endpoint; 404 while the computers_configpolicies_enabled setting is off",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/computers/policies/business"
|
||||
},
|
||||
{
|
||||
"method": "PUT",
|
||||
"path": "/api/computers/policies/business/<policy_id>",
|
||||
"auth": "computers.edit",
|
||||
"params": "path: policy_id; body: any of name, description, priority, items[] (items replace wholesale)",
|
||||
"purpose": "Update a Business Policy or reorder its members; 404 while the computers_configpolicies_enabled setting is off",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/computers/policies/business/1policy_id"
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/api/computers/policies/business/<policy_id>",
|
||||
"auth": "computers.delete",
|
||||
"params": "path: policy_id",
|
||||
"purpose": "Delete a Business Policy and its item rows; 404 while the computers_configpolicies_enabled setting is off",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/computers/policies/business/1policy_id"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/computers/policies/actions",
|
||||
"auth": "computers.view",
|
||||
"params": "none",
|
||||
"purpose": "The allowlisted action names and display labels a policy may reference, read from the endpoints command allowlist. The UI builds its action picker from this rather than hardcoding a list that would drift; 404 while the computers_configpolicies_enabled setting is off",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/computers/policies/actions"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/computers/policies/effective/<asset_id>",
|
||||
"auth": "computers.view",
|
||||
"params": "path: asset_id",
|
||||
"purpose": "Preview the resolved effective policy for one endpoint: which Business Policy won, which Configuration Policies matched, and the resulting desired/gate actions. Read-only, for debugging a scope that is not matching what its author expected; 404 while the computers_configpolicies_enabled setting is off",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/computers/policies/effective/1asset_id"
|
||||
}
|
||||
],
|
||||
"surface": "plugin-computers"
|
||||
@@ -3623,5 +3751,154 @@
|
||||
"example": "curl http://localhost:5001/api/guides/images/5 -H \"Authorization: Bearer $TOK\" --output shot.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"surface": "plugin-endpoints",
|
||||
"endpoints": [
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/endpoints/",
|
||||
"auth": "endpoints.view",
|
||||
"params": "query: search (hostname substring)",
|
||||
"purpose": "List endpoints reporting through the ShopDB agent, with last-seen and online state",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/endpoints/"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/endpoints/<asset_id>",
|
||||
"auth": "endpoints.view",
|
||||
"params": "path: asset_id",
|
||||
"purpose": "One endpoint: its agent state, version, and last check-in",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/endpoints/1asset_id"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/endpoints/<asset_id>/events",
|
||||
"auth": "endpoints.view",
|
||||
"params": "path: asset_id; query: eventid (page from this id)",
|
||||
"purpose": "Agent event history for one endpoint, newest first",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/endpoints/1asset_id/events"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/endpoints/asset/<asset_id>/status",
|
||||
"auth": "endpoints.view",
|
||||
"params": "path: asset_id",
|
||||
"purpose": "Asset-detail panel: online state, uptime, and agent version for one endpoint",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/endpoints/asset/1asset_id/status"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/endpoints/asset/<asset_id>/telemetrymeta",
|
||||
"auth": "endpoints.view",
|
||||
"params": "path: asset_id",
|
||||
"purpose": "Asset-detail panel: what telemetry this endpoint reports and when each series was last updated",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/endpoints/asset/1asset_id/telemetrymeta"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/endpoints/asset/<asset_id>/services",
|
||||
"auth": "endpoints.view",
|
||||
"params": "path: asset_id",
|
||||
"purpose": "Asset-detail panel: Windows services the agent watches on this endpoint, with running state",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/endpoints/asset/1asset_id/services"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/endpoints/asset/<asset_id>/recentevents",
|
||||
"auth": "endpoints.view",
|
||||
"params": "path: asset_id",
|
||||
"purpose": "Asset-detail panel: the most recent agent events for one endpoint",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/endpoints/asset/1asset_id/recentevents"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/endpoints/asset/<asset_id>/interfaces",
|
||||
"auth": "endpoints.view",
|
||||
"params": "path: asset_id",
|
||||
"purpose": "Asset-detail panel: network interfaces as the collector reports them ({alias, ipv4, mac, gateway, subnet, dhcp})",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/endpoints/asset/1asset_id/interfaces"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/endpoints/dashboard/summary",
|
||||
"auth": "endpoints.view",
|
||||
"params": "none",
|
||||
"purpose": "Dashboard card: endpoint counts by online/offline/never-seen",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/endpoints/dashboard/summary"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/endpoints/dashboard/quiet",
|
||||
"auth": "endpoints.view",
|
||||
"params": "none",
|
||||
"purpose": "Dashboard card: endpoints that have gone quiet - no check-in inside the configured quiet-hours window",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/endpoints/dashboard/quiet"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/endpoints/dashboard/stability",
|
||||
"auth": "endpoints.view",
|
||||
"params": "none",
|
||||
"purpose": "Dashboard card: endpoints ranked by reboot/agent-restart churn, to surface a machine that is flapping",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/endpoints/dashboard/stability"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/endpoints/agentkey",
|
||||
"auth": "admin role",
|
||||
"params": "none",
|
||||
"purpose": "Status of the endpoints agent key: whether one exists, when it was issued, and its last use. Never returns the key itself",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/endpoints/agentkey"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/endpoints/agentkey/regenerate",
|
||||
"auth": "admin role",
|
||||
"params": "none",
|
||||
"purpose": "Issue a new endpoints agent key and return it ONCE. The key is scoped to collector.ingest.endpoints only, so rotating it cannot disturb the computers or GE-Enforce collectors. Every agent must be reconfigured with the new value",
|
||||
"example": "curl -X POST -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/endpoints/agentkey/regenerate"
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/api/endpoints/agentkey",
|
||||
"auth": "admin role",
|
||||
"params": "none",
|
||||
"purpose": "Revoke the endpoints agent key. The kill switch for agent ingest: reports and command polls stop being authorized immediately",
|
||||
"example": "curl -X DELETE -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/endpoints/agentkey"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/endpoints/<asset_id>/commands",
|
||||
"auth": "endpoints.view",
|
||||
"params": "path: asset_id",
|
||||
"purpose": "Command history for one endpoint: what was queued, claimed, and what it returned",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/endpoints/1asset_id/commands"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/endpoints/<asset_id>/commands",
|
||||
"auth": "endpoints.command",
|
||||
"params": "path: asset_id; body: action (required, allowlisted), target, targetkind",
|
||||
"purpose": "Queue one allowlisted command for an endpoint. Unknown actions are rejected, destructive ones (system_restart) demand a stricter permission, the effective policy must permit the action for this endpoint, and a second command will not stack while one is pending or running",
|
||||
"example": "curl -X POST -H \"Authorization: Bearer $TOKEN\" -H 'Content-Type: application/json' -d '{\"action\":\"service_restart\",\"target\":\"MTConnectAgent\",\"targetkind\":\"service\"}' http://localhost:5001/api/endpoints/1/commands"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/endpoints/commands/next",
|
||||
"auth": "collector token (collector.ingest.endpoints)",
|
||||
"params": "query: hostname (required)",
|
||||
"purpose": "Agent long-poll. Claims the oldest pending command for the calling host and atomically marks it running so no second poll claims it; 204 when nothing is queued inside the hold window, and the agent re-polls. Collector-token auth, not JWT",
|
||||
"example": "curl -H \"X-Collector-Key: $KEY\" \"http://localhost:5001/api/endpoints/commands/next?hostname=WJSF-PC-01\""
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/endpoints/commands/<command_id>/result",
|
||||
"auth": "collector token (collector.ingest.endpoints)",
|
||||
"params": "path: command_id; body: the command outcome (status, output)",
|
||||
"purpose": "Agent reports a claimed command result. Finalizes the command row and, for MTConnect actions that observed run state, caches that on the EndpointState so the UI toggle reflects reality between polls - both in one commit. Collector-token auth, not JWT",
|
||||
"example": "curl -X POST -H \"X-Collector-Key: $KEY\" -H 'Content-Type: application/json' -d '{\"status\":\"succeeded\",\"output\":\"ok\"}' http://localhost:5001/api/endpoints/commands/1/result"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
1609
docs/openapi.json
1609
docs/openapi.json
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user