Commit Graph
609 Commits
Author SHA1 Message Date
mrhid6 a3c6b2a305 feat: public status page endpoint with per-address rate limit 2026-08-24 14:29:24 +00:00
mrhid6 21a2d077d8 feat: cached public status snapshot with licence gate 2026-08-24 14:24:07 +00:00
mrhid6 6263c7e16f fix: clear resolved_at when reopening a status incident via appended update 2026-08-24 14:21:26 +00:00
mrhid6 161835802d feat: authored status incidents and maintenance windows 2026-08-24 14:18:06 +00:00
mrhid6 6f998ff506 feat: status page CRUD and cache invalidation 2026-08-24 14:15:25 +00:00
mrhid6 d192589790 fix: maintenance repaint no longer zeroes no_data uptime; strengthen redaction test 2026-08-24 14:12:38 +00:00
mrhid6 21c2bb2646 feat: public status snapshot assembly and redaction boundary 2026-08-24 14:07:10 +00:00
mrhid6 9c0bbd13dd feat: status page id validation and cache key 2026-08-24 14:02:46 +00:00
mrhid6 1c15961309 feat: status page schema, licence feature and indexes 2026-08-24 14:00:00 +00:00
mrhid6 383b763a66 docs: attach approved status page mockup to the implementation plan 2026-08-24 13:52:07 +00:00
mrhid6 3e99a9df33 docs: public status pages implementation plan 2026-08-24 13:42:44 +00:00
mrhid6 f1b6f90345 docs: public status pages design spec 2026-08-24 13:32:24 +00:00
mrhid6 2a660697c5 docs: Updated troubleshotting doc 2026-08-24 12:50:44 +00:00
mrhid6 0c08dda635 feat: Useragent 2026-08-24 12:17:46 +00:00
mrhid6 22b99ff895 feat: Monitor grath zoom 2026-08-24 10:58:50 +00:00
mrhid6 2fab784ba7 feat: Monitor groups and chart information 2026-08-24 10:30:23 +00:00
mrhid6 83cdf92575 feat: Updated edit monitor page 2026-08-24 09:25:43 +00:00
mrhid6 aa1c8e4aa1 feat: Hide secrets on api and channels 2026-08-14 12:23:36 +00:00
mrhid6 ac61015cc0 fix: Fixed incorrect openapi doc 2026-08-13 13:04:44 +00:00
mrhid6 a0fbf5b9ba fix: Word and colour Windows workloads correctly across the UI 2026-08-13 12:57:37 +00:00
mrhid6 ddf0814803 docs: Fix Windows package-inventory and poll-loop claims in CLAUDE.md 2026-08-13 12:28:51 +00:00
mrhid6 2bc15648b7 docs: Describe the Windows agent's update and workload support 2026-08-13 12:23:34 +00:00
mrhid6 7f348b7b2b fix: Follow Windows Th/Td labels through the updates table 2026-08-13 12:19:47 +00:00
mrhid6 a71fd9a9f4 fix: Fixed docs entitlement 2026-08-13 12:18:21 +00:00
mrhid6 36848a519a feat: Word the workload and update panels for Windows servers 2026-08-13 12:16:10 +00:00
mrhid6 ea6d0b969c fix: Trim Windows event log after SCM filtering, not before 2026-08-13 12:12:11 +00:00
mrhid6 4ba9983cf8 feat: Control Windows services and read their event log as workloads 2026-08-13 12:04:49 +00:00
mrhid6 8caaa4540f fix: Respect .exe word boundary and unterminated quotes in servicePath 2026-08-13 12:00:59 +00:00
mrhid6 42f0cb67cb feat: Collect Windows services as workloads 2026-08-13 11:52:08 +00:00
mrhid6 1dfb3cc28c refactor: Split the agent workloads package by build tag 2026-08-13 10:49:24 +00:00
mrhid6 b51e87477e feat: Report whether a managed host is waiting on a reboot 2026-08-13 10:39:27 +00:00
mrhid6 05c0ad43d2 feat: Check and apply Windows updates through the Windows Update COM API 2026-08-13 10:36:22 +00:00
mrhid6 4a07af049c refactor: Split the agent updates package by build tag 2026-08-13 10:33:35 +00:00
mrhid6 057c193e26 feat: Add winexec helper for running PowerShell from the agent 2026-08-13 10:26:31 +00:00
mrhid6 38a731f269 docs: Add implementation plan for Windows agent parity 2026-08-13 10:21:04 +00:00
mrhid6 06d69590fc docs: Design for Windows agent parity on updates and workloads 2026-08-13 10:08:25 +00:00
mrhid6 6adee810dc feat: Cleanup old docs 2026-08-13 09:41:32 +00:00
mrhid6 00d4307346 fix: Fixed chart for ingress 2026-08-13 09:29:01 +00:00
mrhid6 7e767ecb4f fix: Move the API keys page off the /api prefix
/api-keys shares a raw string prefix with /api, and the proxies in front
of this app do not all match by path segment. Nginx Proxy Manager routes
/api straight to the Go server with a prefix location, so /api-keys never
reached Next at all — it reached a control plane with no such route and
came back as a JSON 404. Traefik's PathPrefix has the same shape of
matcher, which puts the Helm ingress at risk whenever ingress.api.enabled
is on.

The page is /tokens now, which cannot collide with anything, and which
matches the /api/tokens the REST API already publishes. The sidebar still
says API Keys — the label is for the reader, the path is for the router.

A permanent redirect covers anyone who bookmarked the old path today.
Fixing the proxy config instead would have left the trap set for the next
deployment, and for whatever sits in front of it.
2026-08-13 09:26:34 +00:00
mrhid6 18495dba68 feat: Restyle the API keys page onto the shared list patterns
The page was hand-rolling its loading spinner, error line and empty
paragraph while the rest of the console routes these through
AsyncBoundary with a TableSkeleton and an EmptyState — the same drift
Async.tsx was written to end. It also passed className="p-0" where Card
takes padding={false}.

The admin-only scope switch becomes the vulnerabilities page's pill
filter rather than a loose checkbox: seeing everyone's keys is a filter
over the list, not a preference, and someone who has learned one control
has now learned both.

Scopes collapse to one chip per resource with an r/rw qualifier. Sixteen
badges made the row taller than everything around it and still had to be
read one at a time.

In the create dialog Copy is now the primary action and Done the quiet
one, because the value is unrecoverable once the dialog closes, and the
result panel repeats the role, scopes and expiry that were just granted.
2026-08-13 08:59:06 +00:00
mrhid6 689d0e1d5b feat: Give API keys their own page and group the sidebar
The token management card sat on /settings, which is owner|admin
throughout, so it hid a capability every member already had: the API has
never required a role to mint or revoke your own key. It is now the
/api-keys page, reachable at every role, with the instance-wide lifetime
cap left behind on /settings because that is policy rather than one
person's credentials — and that split is what lets the page be ungated.

The sidebar gains groups: Fleet, Access, Automation, Instance, each with a
small-caps heading and a rule above it. Grouping is by what the operator
is doing rather than by which service answers, so SSH keys, secrets and
API keys sit together as credentials. A group whose every item is
admin-only disappears whole for a member; a labelled section with nothing
under it reads as a failure rather than a restriction.

The UI says keys while the collection, prefix and routes still say tokens.
Renaming a published endpoint to match a nav label would break every
script already written against it.
2026-08-13 08:54:42 +00:00
mrhid6 95527b3956 fix: Exclude /install and /update scripts from the OpenAPI document
handleInstallScript and handleUpdateScript are registered on the bare
gin engine at /install and /update, outside the /api group the
generated document's BasePath assumes. Their @Router annotations
therefore published /api/install and /api/update, paths that 404 —
the reference page told a reader to curl a URL that does not exist.

Removed the swag annotations from both handlers (replaced with a plain
comment explaining why) rather than adding a corrected @Router, since
swag has no per-route BasePath override and there is nothing lost by
leaving two shell-script endpoints out of a JSON API reference — their
.ps1 counterparts were already undocumented for the same reason.
Regenerated internal/api/docs/openapi.json accordingly.
2026-08-13 08:31:20 +00:00
mrhid6 225b53bfa7 fix: Throttle audit logging for expired API token use
Every request presenting an expired token wrote a token.expired_use
audit row, and RateLimitTokens only applies once a session exists, so a
rejected token was never rate-limited. A looping job with one expired
token could write an unbounded number of audit rows, drowning the real
audit trail.

services.ShouldLogExpiredTokenUse now dedupes to at most one
token.expired_use record per token per minute, mirroring the throttle
TouchAPIToken already uses for last-used. It lives in services rather
than auth because the storage concern belongs beside the token's other
storage-backed state. The first use per window is still recorded, which
is what makes a forgotten job visible.
2026-08-13 08:31:12 +00:00
mrhid6 965419b2b8 fix: Confine created API token scopes to the calling token's own
CreateAPIToken capped a new token's role at the creator's role but never
capped its scopes against the calling credential's scopes, and POST
/api/tokens required only settings:write. A token holding settings:write
alone could therefore mint a token holding keys:write or secrets:write,
reaching every SSH private key and vault secret in the instance.

createToken now refuses (403 scope_confinement) when the calling
credential is itself a token and any requested scope is not satisfied by
that token's own scopes, via services.ScopeSatisfied so servers:write
still permits granting servers:read. Cookie sessions are unaffected,
since their authority is the user's role. Also correct the createToken
doc comment, which claimed the scope cap already existed.

Also document why Hint stores 5 hex characters of the token secret.
2026-08-13 08:31:07 +00:00
mrhid6 f6988b0f1e docs: Correct API token access-control claim in CLAUDE.md 2026-08-13 08:12:48 +00:00
mrhid6 0784ef3719 docs: Document API tokens and the OpenAPI reference 2026-08-13 08:04:09 +00:00
mrhid6 9df4a29210 fix: Separate stacked securityDefinitions into distinct comment groups
swag v2.0.0-rc5's parseSecAttributesV3 resolves a security scheme's map key
via getSecurityDefinitionKey(lines), which scans from the start of whatever
comment-line slice it was handed and returns the first @securitydefinitions
match — ignoring the current parse position entirely. Three
@securityDefinitions.apikey blocks stacked in one Go comment group (the
three were separated only by bare '//' lines, which do not split an
ast.CommentGroup) therefore all resolved to the first block's name
(cookieAuth), with the last block's in/name/description winning: the
generated document had exactly one securityScheme, keyed cookieAuth, body
esoAuth.

Separating the three blocks with real blank source lines splits them into
three distinct ast.CommentGroups, so swag's file-level comment scan (which
requires no other tokens between them, same rule Go uses for doc comments)
hands each block its own line slice and each resolves its own key.
Regenerated openapi.json now carries all three schemes with correct
bodies, referenced with no dangling security requirements.
2026-08-13 07:54:56 +00:00
mrhid6 4c88d6e768 feat: Publish an OpenAPI 3.1 document and a Scalar reference
Generated from swaggo v2 annotations, committed rather than built into the
image: the runtime stage is scratch and adding codegen puts the toolchain
in the build. CI regenerates and diffs, so an annotation edited without
regenerating fails the build — without that the annotations would drift
while still looking authoritative.

Scalar is vendored rather than loaded from a CDN, because air-gapped
self-hosted installs are supported and a reference page that fails closed
offline is a support ticket.
2026-08-12 15:23:02 +00:00
mrhid6 a85a354e57 feat: Annotate workflow, step, run and workload routes
Same treatment: named types replace gin.H literals, and every handler gets
a swaggo doc block. This is the last of the handler files under
server/internal/api/.
2026-08-12 15:22:54 +00:00
mrhid6 9b18d09d9b feat: Annotate monitor, secrets and vulnerability routes
Same treatment: named types replace gin.H literals, and every handler gets
a swaggo doc block. VulnSummaryResponse uses pointer fields so the
db-freshness block stays entirely absent when no vulndb_meta document
exists yet, matching the handler's original conditional gin.H exactly.
2026-08-12 15:22:51 +00:00