feat: Removed email alert settings
Chart Release / chart (push) Successful in 10s
Server Deploy / deploy (push) Successful in 5m15s

This commit is contained in:
2026-08-03 14:40:15 +01:00
parent 6881d92d0a
commit 1f08e90009
9 changed files with 123 additions and 168 deletions
+59 -13
View File
@@ -130,16 +130,41 @@ export default function SettingsPage() {
enabled: isAdmin,
});
const { data: channels } = useQuery({
queryKey: ["channels"],
queryFn: api.listChannels,
enabled: isAdmin,
});
const [thresholdMinutes, setThresholdMinutes] = useState(5);
const [logRetentionDays, setLogRetentionDays] = useState(30);
const [offlineChannelIds, setOfflineChannelIds] = useState<string[]>([]);
const [saved, setSaved] = useState(false);
useEffect(() => {
if (!settings) return;
setThresholdMinutes(settings.alerts.offline_threshold_minutes || 5);
setLogRetentionDays(settings.workflow_log_retention_days ?? 30);
setOfflineChannelIds(settings.alerts.offline_channel_ids ?? []);
}, [settings]);
// The one place the in-progress form is turned into a payload. Both the
// Save button and the local-login toggle go through it, so flipping the
// toggle can never submit a stale copy of the other fields.
function currentPayload() {
return {
alerts: {
offline_threshold_minutes: thresholdMinutes,
offline_channel_ids: offlineChannelIds,
},
workflow_log_retention_days: logRetentionDays,
};
}
function toggleOfflineChannel(id: string) {
setOfflineChannelIds((prev) => (prev.includes(id) ? prev.filter((c) => c !== id) : [...prev, id]));
}
const { mutate: save, isPending } = useMutation({
mutationFn: (payload: Parameters<typeof api.saveSettings>[0]) => api.saveSettings(payload),
onSuccess: () => {
@@ -153,11 +178,7 @@ export default function SettingsPage() {
e.preventDefault();
if (!settings) return;
save({
alerts: { ...settings.alerts, offline_threshold_minutes: thresholdMinutes },
email: settings.email,
workflow_log_retention_days: logRetentionDays,
});
save(currentPayload());
}
if (!isAdmin) {
@@ -200,18 +221,13 @@ export default function SettingsPage() {
// object — otherwise an unsaved edit to the offline
// threshold or retention days is silently reverted the
// moment this toggle is flipped.
save({
alerts: { ...settings.alerts, offline_threshold_minutes: thresholdMinutes },
email: settings.email,
workflow_log_retention_days: logRetentionDays,
local_login_enabled: v,
});
save({ ...currentPayload(), local_login_enabled: v });
}}
/>
</Group>
<Group label="Monitoring">
<SectionCard title="Alerting" description="Alerts are delivered through notification channels, triggered by service monitors." icon={<BellIcon />}>
<SectionCard title="Alerting" description="Alerts are delivered through notification channels, triggered by service monitors and by servers going offline." icon={<BellIcon />}>
<div className="flex flex-wrap gap-3">
<Link href="/settings/notifications">
<Button variant="secondary">Manage notification channels</Button>
@@ -227,10 +243,40 @@ export default function SettingsPage() {
<form onSubmit={handleSubmit}>
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
<SectionCard title="Server health" description="When to consider an agent-backed server offline." icon={<ServerIcon />}>
<SectionCard title="Server health" description="When to consider an agent-backed server offline, and where to say so." icon={<ServerIcon />}>
<Field label="Offline threshold (minutes)" hint="How long a server must be silent before being marked offline. Agents poll every 30s, so 5 minutes is a safe minimum.">
<input type="number" min={1} max={60} value={thresholdMinutes} onChange={(e) => setThresholdMinutes(Number(e.target.value))} className={numberInputClass} />
</Field>
<div className="mt-6">
<Field label="Offline alert channels" hint="Notification channels an agent-offline alert is sent to. None selected means the event is still audited, but nobody is notified.">
{channels && channels.length > 0 ? (
<div className="space-y-2">
{channels.map((ch) => (
<label key={ch.channel_id} className="flex items-center gap-2 text-sm text-text-secondary">
<input
type="checkbox"
checked={offlineChannelIds.includes(ch.channel_id)}
onChange={() => toggleOfflineChannel(ch.channel_id)}
className="h-4 w-4 rounded border-border bg-surface-2 accent-accent"
/>
<span className="text-text-primary">{ch.name}</span>
<span className="text-xs text-text-tertiary">{ch.type}</span>
{!ch.enabled && <span className="text-xs text-warning">disabled</span>}
</label>
))}
</div>
) : (
<p className="text-sm text-text-tertiary">
No notification channels yet.{" "}
<Link href="/settings/notifications" className="text-accent hover:underline">
Create one
</Link>
.
</p>
)}
</Field>
</div>
</SectionCard>
<SectionCard title="Workflow logs" description="How long run logs are kept before automatic deletion." icon={<DocumentIcon />}>