GitHub
05/13/2026, 2:14 PMosctrl-admin templates do. Every new endpoint is additive — no existing routes are removed or repurposed. The legacy admin keeps working unchanged.
End-to-end tested against a Kali docker deployment.
⚠️ Stacked on #813 (security hardening). Merge #813 first, then this. If #813 is merged before review starts here, this branch will be cleanly rebased on the new main HEAD with no conflicts.
New endpoints
Stats / dashboard
| Method | Path | Purpose |
| ------ | ---------------------------------------- | ------------------------------------- |
| GET | /api/v1/stats | Cross-env summary KPIs |
| GET | /api/v1/stats/osquery-versions | Fleet agent versions |
| GET | /api/v1/stats/activity/{env} | Env-scoped audit-log activity heatmap |
| GET | /api/v1/stats/activity/node/{env}/{uuid} | Per-node activity heatmap |
| GET | /api/v1/stats/activity/node-batch/{env} | Per-node heatmap, up to 100 uuids |
Logs (live log viewer)
| Method | Path | Purpose |
| ------ | -------------------------------- | ------------------------------------------- |
| GET | /api/v1/logs/{type}/{env}/{uuid} | Paginated, since-aware, optional ?q= search |
Saved queries (full CRUD)
| Method | Path |
| ------ | ---------------------------------- |
| GET | /api/v1/saved-queries/{env} |
| POST | /api/v1/saved-queries/{env} |
| PATCH | /api/v1/saved-queries/{env}/{name} |
| DELETE | /api/v1/saved-queries/{env}/{name} |
User profile + permissions + token
| Method | Path |
| ------ | -------------------------------------- |
| GET | /api/v1/users/me |
| PATCH | /api/v1/users/me |
| POST | /api/v1/users/me/password |
| POST | /api/v1/users/{username}/permissions |
| POST | /api/v1/users/{username}/token/refresh |
| DELETE | /api/v1/users/{username}/token |
Environment CRUD + config PATCHes
| Method | Path |
| ------ | ------------------------------------- |
| POST | /api/v1/environments |
| PATCH | /api/v1/environments/{env} |
| DELETE | /api/v1/environments/{env} |
| GET | /api/v1/environments/{env}/config |
| PATCH | /api/v1/environments/{env}/config |
| PATCH | /api/v1/environments/{env}/intervals |
| PATCH | /api/v1/environments/{env}/expiration |
Settings PATCH
| Method | Path |
| ------ | --------------------------------- |
| PATCH | /api/v1/settings/{service}/{name} |
Audit log filters + pagination
| Method | Path |
| ------ | ---------------------------------------------------------------------------------- |
| GET | /api/v1/audit-logs?service=&username=&type=&envUuid=&since=&until=&page=&pageSize= |
Login envs (pre-auth env list for the SPA's login page env picker)
| Method | Path |
| ------ | -------------------------------------------------------------------------------- |
| GET | /api/v1/login/environments (UUID + name only; everything else stays behind auth) |
Sample libraries (operator starter packs)
| Method | Path |
| ------ | ----------------------- |
| GET | /api/v1/queries/samples |
| GET | /api/v1/carves/samples |
| GET | /api/v1/osquery/tables |
Pagination, sort, search conventions
Every list endpoint accepts ?page=&page_size= (default 50, max 500) and returns the envelope:
{ "items": [...], "page": N, "page_size": N, "total_items": N, "total_pages": N }
Sortable fields use a per-resource SortableColumns allowlist enforced at the package layer (pkg/nodes, pkg/queries, pkg/carves). Unknown sort keys fall back to the resource's default order without 400ing.
Search is ?q= free-text against a per-resource field set (case-insensitive LIKE). Wildcards are escaped server-side.
New package: pkg/dbutil
Dialect-aware SQL bucket-expression helper (postgres / mysql / sqlite) used by the activity heatmap endpoints. Each category (status logs / result logs / distributed queries / carves) issues a single SQL GROUP BY rather than plucking every timestamp — at 50k+ nodes the table-page heatmap query is bounded by the index instead of the chatty-row count.
Package-layer additions
• pkg/nodes — GetByEnvPaged, NodeView projection, SortableColumns, platform-bucket helpers, GetOsqueryVersionCounts.
• pkg/queries — GetByEnvTargetPaged, GetSaved* CRUD, SortableColumns, sample-template loader, GetNodeQueryBucketed.
• pkg/carves — GetByEnvPaged, sample-template loader, GetNodeCarveBucketed.
• pkg/environments — Create / Update / Delete, UpdateConfig / UpdateIntervals / UpdateExpiration helpers.
• pkg/auditlog — GetPaged with `PageFilter`; GetEnvActivityBucketed for the heatmap.
• pkg/logging — GetNodeLogs with ?q= search filter, GetNode{Status,Result}Bucketed for the heatmap.
• pkg/osquery — LoadTables (osquery schema for the SPA query editor).
• pkg/types — NodeView, paginated response envelopes, EnvCreate / EnvUpdate / EnvConfig* request types, SettingPatchRequest, SavedQueryView, AdminUserView.
Test plan
• go build ./... — clean
• go vet ./... — clean
• go test ./... — all packages pass
• gofmt -l ./... — empty
• End-to-end smoke against a Kali docker deployment (paginated lists, activity heatmap, env CRUD, saved-queries CRUD, profile flow)
• New tests added: cmd/api/handlers/stats_test.go, pkg/nodes/nodes_test.go, pkg/queries/queries_test.go, pkg/queries/saved_test.go
What this enables
Round 3 will land the React admin SPA under a new frontend/ directory at the repo root. The SPA consumes only the endpoints in this PR — no admin-template surface is touched.
jmpsec/osctrlGitHub
05/16/2026, 7:58 AM