Apply all settings and alerts live without Save buttons
Release rolling / release (push) Has been cancelled

Auto-persist client preferences on change with footer feedback, and
apply server alert engine settings/channels/rules immediately.
This commit is contained in:
Raven Scott
2026-07-17 19:48:28 -04:00
parent f6f059ab94
commit e2fa2ff85a
5 changed files with 533 additions and 172 deletions
+42 -4
View File
@@ -1154,6 +1154,7 @@ function armPoll() {
clearInterval(pollTimer)
pollTimer = null
}
if (!config.enabled) return
const ms = config.pollIntervalMs || DEFAULT_POLL_MS
pollTimer = setInterval(() => {
pollHealth().catch((err) => {
@@ -1163,14 +1164,36 @@ function armPoll() {
if (typeof pollTimer.unref === 'function') pollTimer.unref()
}
/**
* Apply in-memory config to runtime (poll timer, etc.) without restart.
* Called after every config mutation so enable/disable and intervals are live.
*/
function applyRuntimeConfig({ kickPoll = false } = {}) {
armPoll()
if (kickPoll && config.enabled) {
pollHealth().catch((err) => {
logger.debug('alerts: kick poll failed', { error: err.message })
})
}
logger.info('alerts: runtime config applied', {
enabled: config.enabled,
pollIntervalMs: config.pollIntervalMs,
minSeverity: config.minSeverity,
rateLimitPerMinute: config.rateLimitPerMinute,
quietHours: Boolean(config.quietHours?.enabled),
channels: config.channels.filter((c) => c.enabled).length,
rules: config.rules.filter((r) => r.enabled).length,
})
}
export function startAlerts() {
if (started) return
started = true
loadDisk()
armPoll()
// Initial poll shortly after boot
applyRuntimeConfig({ kickPoll: false })
// Initial poll shortly after boot (only if enabled)
setTimeout(() => {
pollHealth().catch(() => {})
if (config.enabled) pollHealth().catch(() => {})
}, 5_000)
logger.info('Alerts engine started', {
channels: config.channels.length,
@@ -1216,6 +1239,7 @@ export function getAlertsConfigRaw() {
* @param {{ replace?: boolean }} [opts]
*/
export function updateAlertsConfig(partial, opts = {}) {
const wasEnabled = config.enabled
if (opts.replace) {
config = normalizeConfig(partial)
} else {
@@ -1237,7 +1261,10 @@ export function updateAlertsConfig(partial, opts = {}) {
}
config.updatedAt = new Date().toISOString()
saveDisk()
armPoll()
// Live apply: poll interval, enable/disable — no process restart
applyRuntimeConfig({
kickPoll: config.enabled && (!wasEnabled || partial.pollIntervalMs != null),
})
return getAlertsConfig()
}
@@ -1258,6 +1285,12 @@ export function upsertAlertChannel(input) {
config.channels.push(ch)
}
scheduleSave()
// Channel list is read on each fire — already live; log for ops clarity
logger.debug('alerts: channel upserted live', {
id: ch.id,
enabled: ch.enabled,
type: ch.type,
})
return redactChannel(ch)
}
@@ -1280,6 +1313,11 @@ export function upsertAlertRule(input) {
if (idx >= 0) config.rules[idx] = rule
else config.rules.push(rule)
scheduleSave()
logger.debug('alerts: rule upserted live', {
id: rule.id,
enabled: rule.enabled,
kind: rule.kind,
})
return rule
}