<#1016 Test alert button in alerts channel creatio...
# osctrl
g
#1016 Test alert button in alerts channel creation modal + bugfixes Pull request opened by javuto Test alert channels before saving, plus two premade error alerts Test a channel from the editor Channels could only be verified by saving one and waiting for a real alert to fire. The editor (New and Edit) now has a Send test button that delivers one synthetic notification using the form's current values — nothing is stored — and reports the outcome inline: the server's confirmation, or the relay's own error. •
alerts.TestSend(type, config)
builds a sender from the existing
ChannelRegistry
and sends
TestHit()
(
rule_name: osctrl-channel-test
), bounded by a 15s backstop since the SMTP sender carries no timeout of its own. The goroutine is buffered-by-one so an abandoned send exits instead of leaking. •
POST /api/v1/alerts/channels/test
(
types.AlertChannelTestRequest
) — global admin, audit-logged, same gate as the rest of the alerts API. A config the registry refuses is *400*; a relay that refuses us is 502, so an operator mistake and a broken endpoint don't look alike. No new capability: an admin could already save a channel and wait for it to fire. • Passing
id
(edit mode) merges stored secrets through the existing
MergeChannelSecrets
, so testing an untouched secret sends the real one instead of the
***
placeholder. "Errors reported" premade alert The node detail page's alert modal offered "node inactive" and "log match". It now offers a third, and so does the Alerts page's rule editor: • Node modal: "Errors reported by this node" → a
status_log
rule scoped to the node with
status_severity: error
and no pattern, named
<hostname>-errors
. The three kinds now live in one
NODE_ALERT_KINDS
table (suffix, title, help, rule fields), which replaced two copy-pasted
<label>
blocks and the ternary chain at the submit site. • Rule editor: a "Start from a premade rule" row (create mode) whose Errors reported button fills in name, source and severity — same rule without the node scope, so it covers every node in the environment. No new matcher machinery: a severity floor with an empty substring already means "every error line". Bugs found on the way •
invalid value
told the operator nothing.
respondAlertsErr
collapsed every rejected channel config, channel type and rule source into that string, so a channel test failed with no reason. It now echoes the actual error (admin-only endpoints), e.g.
webhook target "127.0.0.1" is a private/loopback address; set allowPrivateTargets to override (dev only)
. • That private-target guard had no UI.
allowPrivateTargets
was reachable only via API/YAML, so a local relay could never be configured — let alone tested — from the browser. Exposed as an off-by-default checkbox with help text saying what it is for. • Pattern-less rules alerted with no context. An empty substring returned itself as the hit detail, so any rule without a pattern (the new preset included) would have paged with an empty
Detail
.
substringDetail
now falls back to the value that matched. Affects every pattern-less rule. • The rule editor could not express "every error line".
submit()
demanded a match value for any pattern source. A status-log rule with a severity floor is now complete without one (
patternOptional
), and the label says so. Result and query rules still require a pattern — an empty one there matches every row. •
Unexpected end of JSON input
on every DELETE.
apiFetch
ended in
res.json()
, which throws on the 204 that all five delete endpoints return — alert rules, alert channels, log sinks, auth providers, env packages. Each deleted the row server-side, then reported a failure and skipped its cache invalidation, so the row lingered until reload. It now reads the body as text and parses only when there are bytes. Audited the rest of the API: those five (plus
GET /health
when the DB is degraded, which the SPA never calls) are the only empty-body responses; every other route returns JSON. •
osctrl-api
exited without draining on restart.
The apply handler wrote its 202 and signalled `RestartCh`; main called
os.Exit(1)
immediately, racing the response flush the handler's own docs promised to wait for. It now calls
srv.Shutdown
with a 5s bound first. Tests Go — channel test send (200 / 502 / 400, and that a 400 names what it rejected), stored-secret merge (asserts
X-Osctrl-Signature
arrives), node-scoped error rule matching (fires on the node's error line only, and carries the message), and the API restart branch of the apply handler, which had no coverage (explicit
service: "api"
, bodyless POST, and 503 without a wired channel). Frontend — the Send test success and failure paths, the preset button and the body it saves, the node modal's error kind, and
apiFetch
body handling (204 →
undefined
, JSON still parsed, error body still raises
ApiError
).
go build ./... && go test ./pkg/alerts/ ./cmd/api/...
pass; 314 frontend tests pass;
tsc --noEmit
clean. Deploy note Requires an
osctrl-api
restart for the new endpoint and the registry field. jmpsec/osctrl