Build the Settings module: notification channels, event toggles, DNS badge colors
The Settings page was a "coming soon" placeholder. Port Sloth Manager's settings feature set: Gotify/ntfy/SMTP/webhook notification channels (each with its own test-send button), per-event toggles (DNS record added/updated/deleted, a daily secret-expiry digest with configurable time/timezone), and per-provider DNS badge color customization. Settings persist in the existing `settings` key/value table via a new settingsStore service; a notify service fans a message out to every enabled channel. DNS record add/update/delete now fire notifications, and a node-schedule job re-arms itself whenever the notification settings change. Removed the now-superseded GOTIFY_URL/GOTIFY_TOKEN env vars in favor of in-app configuration. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3a53a86ce0
commit
3255314402
16 files changed
+1272
-23
No files matched your search
@@ -0,0 +1,53 @@
|
||||
import schedule from "node-schedule";
|
||||
import { db } from "../db/client.js";
|
||||
import { secrets } from "../db/schema.js";
|
||||
import { computeSecretStatus } from "./secretStatus.js";
|
||||
import { notifySecretExpiry } from "./notify.js";
|
||||
import { getSettings, getInternalFlag, setInternalFlag } from "./settingsStore.js";
|
||||
|
||||
const LAST_RUN_FLAG = "secretCheckLastRunDate";
|
||||
|
||||
async function checkSecretExpiry(): Promise<void> {
|
||||
const rows = await db.select().from(secrets);
|
||||
const expiring = rows
|
||||
.map((s) => ({ name: s.name, ...computeSecretStatus(s.expiryDate, s.warnDays) }))
|
||||
.filter((s): s is typeof s & { status: "expired" | "expiring" } => s.status === "expired" || s.status === "expiring");
|
||||
await notifySecretExpiry(expiring);
|
||||
}
|
||||
|
||||
async function checkSecretExpiryOnce(): Promise<void> {
|
||||
const today = new Date().toDateString();
|
||||
const lastRun = await getInternalFlag(LAST_RUN_FLAG);
|
||||
if (lastRun === today) return;
|
||||
await setInternalFlag(LAST_RUN_FLAG, today);
|
||||
await checkSecretExpiry();
|
||||
}
|
||||
|
||||
function cronFromTime(time: string): string {
|
||||
const [h, m] = time.split(":").map(Number);
|
||||
return `${Number.isFinite(m) ? m : 0} ${Number.isFinite(h) ? h : 8} * * *`;
|
||||
}
|
||||
|
||||
let currentJob: schedule.Job | null = null;
|
||||
|
||||
/** (Re)schedules the daily secret-expiry check per the current notification settings. Call again after settings change. */
|
||||
export async function scheduleSecretExpiryCheck(): Promise<void> {
|
||||
if (currentJob) {
|
||||
currentJob.cancel();
|
||||
currentJob = null;
|
||||
}
|
||||
const { notifications } = await getSettings();
|
||||
currentJob = schedule.scheduleJob({ rule: cronFromTime(notifications.secretCheckTime), tz: notifications.timezone }, () => {
|
||||
setInternalFlag(LAST_RUN_FLAG, "").catch(() => {});
|
||||
getSettings().then(({ notifications: n }) => {
|
||||
if (n.secretCheck) checkSecretExpiry().catch((err) => console.error("[secretExpiry] check failed:", err));
|
||||
});
|
||||
});
|
||||
console.log(`Secret expiry check scheduled at ${notifications.secretCheckTime} (${notifications.timezone})`);
|
||||
}
|
||||
|
||||
/** Runs once at startup (skipped if already run today), then arms the daily schedule. */
|
||||
export async function initSecretExpiryScheduler(): Promise<void> {
|
||||
await checkSecretExpiryOnce();
|
||||
await scheduleSecretExpiryCheck();
|
||||
}
|
||||
Reference in new issue
Block a user