Users no longer need to know the dot-delimited token format; the provider
form now collects the public prefix and secret separately and joins them
when constructing the libdns provider.
Deletes from the retention janitor and tidy passes leave tombstones that
LevelDB only compacts opportunistically, slowing prefix scans for hours. A
background worker now runs a full-keyspace CompactRange on a configurable
interval (-leveldb-compaction-interval, default 24h, 0 disables). Its
lifecycle is bound to the store: Close stops the worker before closing the
DB so no compaction races the close.
Open LevelDB with a larger block cache, write buffer, open-files cache,
compaction table size and a Bloom filter instead of the small goleveldb
defaults, since the workload is dominated by point lookups. All values
are exposed as flags so operators can tune them per deployment.
Also fix the recovery path discarding the recovered DB handle, which
left the storage with a nil database after a successful RecoverFile.
A checker ID is embedded verbatim in several "|"-delimited storage keys
(chckrcfg|, chckpln-chkr|, chckexec-chkr|, ...). An ID containing "|"
would misalign key parsing and let records collide. Validate at the
registration boundary and refuse such IDs.
Route every backend write through a shared storage.Marshal helper that uses
a json.Encoder with SetEscapeHTML(false), so stored payloads no longer waste
encoder work escaping <, > and & that never reach a browser as raw HTML.
The fallback path in DeleteExecutionsByChecker and DeleteEvaluationsByChecker
scanned whole secondary-index prefixes per orphan, turning a delete loop into
O(orphans x index size). Rebuild the user and domain execution index keys
directly from the checker key's shared trailing segments instead, and leave the
planId-first plan index, unknowable from a missing primary, to the Tidy jobs.
Also remove the now-dead deleteCheckPlanSecondaryIndexesByPlanID.
Align the notifrec-user index with the notifch/notifpref pattern: the
index now holds an empty value and records are resolved through the
primary key, dropping the previous double-write of the full record.
The checker, user and domain execution indexes and both evaluation
indexes now embed a reverse-chronological time segment, so a forward
prefix scan returns the newest entries first and stops at the requested
limit instead of loading every match and sorting it in memory.
GetLatestEvaluation becomes a single-row read. migrateFrom11 (still
unpublished) rebuilds the affected indexes from the primary records.
Two concurrent writers targeting the same observation-ref primary key could
both observe the same old snapshot id, each stage a delete for it in their
batch, and each commit its own snap-index. The loser's snap-index would
outlive its primary write and the next cascade delete of THAT snapshot
would erase a primary that no longer belongs to it.
Serialize the Get and the batch commit per primary key through a 64-shard
mutex table on KVStorage, picked by FNV-1a hash of the primary key.
Migrate Update/Delete/Restore variants for executions, plans, evaluations,
channels, preferences, records, plus ReplaceDiscoveryEntries and the
cascading deletes to use the Batch primitive, so primary records and their
secondary indexes are committed atomically instead of leaving the tidy job
load-bearing.
Replace the non-standard HAPPYDOMAIN_SOCKET variable with
HAPPYDOMAIN_ADMIN_BIND, matching the config env var. Handle TCP bind
addresses (:8080, 0.0.0.0:8080, [::]:8080) in addition to Unix sockets.
Move the by-name sort into listScopedCheckers so every consumer gets a
stable ordering, and drop the redundant sort wrappers in CheckerListPage
and CheckResultsDashboard.
Add with_availables query param to checker status endpoint; restrict
default listing to checkers that auto-schedule or have an active plan.
Introduce IsAutoScheduled helper unifying scheduler eligibility logic.
The route at /domains/[dn]/checks listed checker configurations, not check
runs. Rename it to /checkers and use /checks for a new aggregated dashboard
that surfaces the latest result for every enabled checker across the domain
and its services in one place. Same split applied at service scope.
Expose GET /api/users/stats returning provider, domain and zone counts
per user. Zone count is derived from the ZoneHistory length of each
domain. Wire the new endpoint in the admin UI as a sortable table at
/users/stats.
Refactors the admin backup usecase to share per-user collection logic
via backupOneUser(), then adds BackupUser() which filters system-wide
collections (checker configs, plans, evaluations, executions, discovery)
down to the requesting user by UserId.
Wires the backup usecase into the public API dependency graph and adds
a download button in the /me settings page near the delete-account
section.
Replace the per-route editableGroups/readOnlyGroups callback pair in
CheckerConfigPage with a single groups prop that returns both lists, and
switch all call-sites to buildOptionGroupLayout. Add showCheckerInfo and
showExecutions flags so the admin checker page can reuse CheckerConfigPage
without schedule controls or execution links.
CheckerOptionsPanel now shows an "Overriding inherited" badge and a
"Reset to inherited" link for fields where a local value shadows an
inherited one. CheckerOptionsGroups shows the effective inherited value
next to the type hint in read-only groups.
Extend collectAllOptionDocs, splitPositionalOptions, and
collectAutoFillKeys to include serviceOpts alongside the existing
adminOpts/userOpts/domainOpts buckets.
Add buildOptionGroupLayout which derives editable/read-only group lists
from the checker's option documentation and a CheckerPageScope ("admin",
"domain", or "service"). This consolidates the per-route hand-curated
arrays into one place.
Improve splitPositionalOptions with an optional isCurrentScope predicate
so the correct positional is selected for the page's scope instead of
always taking the last stored entry.
Make CheckerScope.domainId optional so callers can target the global
admin scope (no domain). getScopedCheckStatus, getScopedCheckOptions,
and updateScopedCheckOptions now short-circuit to the admin /checkers/*
routes when domainId is absent. All narrower-scope callsites gain the
non-null assertion required by the type change.
Callers like MergeCheckerOptions and SetCheckerOption mutated the map
returned by getScopedOptions before persisting; the store may hand back
a shared reference so mutating it caused silent state corruption. Return
a copy instead.
Add filterOptionsForScope which drops auto-fill keys (system-provided at
runtime) and NoOverride keys declared at a broader scope, so saving at a
narrow scope cannot accidentally persist fields that belong elsewhere.
Add a `disableMetrics` store that gets set when metrics fail to load,
preventing users from switching to a broken metrics view. Also disable
the JSON view button when no observations data is present.
WHOIS data may report statuses as "client transfer prohibited" (spaced,
lowercase) while the configured required value is camelCase
"clientTransferProhibited". Strip spaces, dashes and underscores when
comparing so all common formats match.
Remove the invalid bind:rr chain between RecordLine and RecordText (RecordText only reads rr, never writes back), and use refreshDomains + invalidateAll instead of directly setting thisZone when the returned zone has a new ID, matching the pattern already used in the domain layout.
The resetPassword struct lacked a json tag, causing Go to serialize the
field as "Password" (capital P) while the frontend read "password"
(lowercase), always getting undefined. Also display the generated
password in a copyable input field as a fallback when clipboard access
fails.
Treat io.EOF from ShouldBindJSON as "no body provided" so callers can
POST without a request body to trigger random password generation. Also
fix double-pointer bug (&urp where urp is already *resetPassword) and
bump checker dependencies.
When POST /auth returned 401 with an errmsg (e.g. "Invalid username or
password"), customFetch was ignoring it and attempting a session refresh
via GET /auth. The refresh also failed, ultimately throwing
NotAuthorizedError("Not authenticated") instead of the actual credential
error. This also triggered an extra toast in some cases.
Fix by reading errmsg from any 401 response body first: if present, throw
it directly as a plain Error, bypassing the session-refresh logic entirely.
The previous click-to-copy behavior wasn't obvious to users. Show the
URL in a modal with an explicit copy button and a brief scrape_configs
example so users understand what the endpoint is for.
When switching between checkers via the sidebar, plan.enabled retained
the previous checker's values because the guard condition prevented
re-initialization. Reset all derived state eagerly on checkerId change
and always repopulate plan.enabled from fresh status.
- Move enable/disable toggle to card header (inverted to "Enable scheduling")
- Replace Save button with debounced autosave (700ms) with inline status indicator
- Show interval controls only when enabled; paused hint when disabled
- Remove obsolete no-schedule-yet alert and save button translation keys
The HTML placeholder attribute is rendered in plaintext even on
type="password" inputs, so an inherited secret value injected as a
placeholder was visible without toggling the eye button. Replace the
placeholder with bullets of the same length while the secret is hidden, and
reveal the real value only when the user explicitly toggles visibility.