Merge events and commands into one Activity feed, and lead with live state
Two changes to the PC page, both about what a responder meets first. LIVE STATE FIRST. The contributed panels moved to the head of the card flow. The grid is a two-column FLOW (column-count, with display:contents on the column wrappers), so a card cannot be pinned to a side and only document order decides where it lands - putting the panels first fills the left column with endpoint status, services, processes, interfaces and activity, and leaves identity, hardware, network and location to the right. Panels still self-order among themselves by declared position and still self-hide when no plugin answers for the asset type. ONE ACTIVITY FEED replaces the Recent events card. Events and commands answer the same question - what happened to this box - and reading them as two lists meant interleaving by eye. GET /api/endpoints/asset/<id>/activity merges them newest-first, capped at 15. Both are keyed by assetid, so it is two indexed reads and a merge sort rather than a join, and it renders through the existing ADR-010 list renderer with no bespoke frontend work. A failed command is marked critical, because that is the row somebody needs to notice. Not collapsed by default, unlike the card it replaces. The core AuditLog is deliberately NOT a third source: its rows carry the action as the entity name and the asset only inside a details string, so including it would mean parsing prose to find out which machine a row belongs to. Requester names are withheld from callers who cannot themselves issue commands, via caller_holds. A read-only viewer - a guest, on a site that grants this panel - gets the history without a roster of who restarted what, and the lookup is skipped entirely rather than fetched and discarded. requestedby holds a user ID, so names resolve through one batched query, not one per row. Verified on dev telemetry: a queued command shows as 'Collect now / Pending by admin' above the Windows events, ordered by time. Full suite 2124 passed, vitest 260 passed, naming/contract/parity gates green, spec regenerated at 332 paths.
This commit is contained in:
@@ -91,5 +91,5 @@ Manifest-less directories under `plugins/` are core frontend surface and always
|
||||
## Size
|
||||
|
||||
- test functions defined: **1449** (parametrised cases collect higher)
|
||||
- documented API paths: **331** (`docs/openapi.json`, regenerate with `scripts/gen_openapi.py`)
|
||||
- documented API paths: **332** (`docs/openapi.json`, regenerate with `scripts/gen_openapi.py`)
|
||||
|
||||
|
||||
@@ -3811,6 +3811,14 @@
|
||||
"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>/activity",
|
||||
"auth": "endpoints.view",
|
||||
"params": "path: asset_id",
|
||||
"purpose": "Asset-detail panel: one chronological feed merging curated Windows events with operator commands, newest first, capped at 15. Both are keyed by assetid, so this is two indexed reads and a merge rather than a join. A failed command is marked critical. The requester name is withheld from callers who do not themselves hold endpoints.command, so a read-only viewer gets the history without the roster of who restarted what",
|
||||
"example": "curl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/endpoints/asset/1/activity"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/api/endpoints/asset/<asset_id>/interfaces",
|
||||
|
||||
@@ -22077,6 +22077,57 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/endpoints/asset/{asset_id}/activity": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"plugin-endpoints"
|
||||
],
|
||||
"summary": "Asset-detail panel: one chronological feed merging curated Windows events with operator commands, newest first, capped...",
|
||||
"description": "Asset-detail panel: one chronological feed merging curated Windows events with operator commands, newest first, capped at 15. Both are keyed by assetid, so this is two indexed reads and a merge rather than a join. A failed command is marked critical. The requester name is withheld from callers who do not themselves hold endpoints.command, so a read-only viewer gets the history without the roster of who restarted what\n\n**Auth:** endpoints.view\n\n**Params:** path: asset_id\n\n**Example:**\n```\ncurl -H \"Authorization: Bearer $TOKEN\" http://localhost:5001/api/endpoints/asset/1/activity\n```",
|
||||
"security": [
|
||||
{
|
||||
"bearerAuth": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success. Body is the success_response envelope: {status, data, meta}.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"$ref": "#/components/schemas/SuccessEnvelope"
|
||||
}
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"description": "Error. Body is the error envelope; the code and message are nested under data.error.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"$ref": "#/components/schemas/ErrorEnvelope"
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Missing or invalid credentials."
|
||||
},
|
||||
"403": {
|
||||
"description": "Authenticated, but not permitted."
|
||||
},
|
||||
"404": {
|
||||
"description": "No such record."
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "asset_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/endpoints/asset/{asset_id}/interfaces": {
|
||||
"get": {
|
||||
"tags": [
|
||||
|
||||
Reference in New Issue
Block a user