feat: Removed email alert settings
This commit is contained in:
@@ -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 />}>
|
||||
|
||||
+1
-15
@@ -164,20 +164,8 @@ export interface AuditEvent {
|
||||
}
|
||||
|
||||
export interface AlertSettings {
|
||||
enabled: boolean;
|
||||
webhook_url: string;
|
||||
offline_threshold_minutes: number;
|
||||
}
|
||||
|
||||
export interface EmailSettings {
|
||||
enabled: boolean;
|
||||
smtp_host: string;
|
||||
smtp_port: number;
|
||||
username: string;
|
||||
password: string;
|
||||
from_addr: string;
|
||||
to_addrs: string[];
|
||||
use_tls: boolean;
|
||||
offline_channel_ids: string[] | null;
|
||||
}
|
||||
|
||||
export interface SecretsSettings {
|
||||
@@ -187,7 +175,6 @@ export interface SecretsSettings {
|
||||
|
||||
export interface Settings {
|
||||
alerts: AlertSettings;
|
||||
email: EmailSettings;
|
||||
secrets: SecretsSettings;
|
||||
workflow_log_retention_days?: number | null;
|
||||
local_login_enabled?: boolean;
|
||||
@@ -657,7 +644,6 @@ export const api = {
|
||||
|
||||
saveSettings(settings: {
|
||||
alerts: AlertSettings;
|
||||
email: EmailSettings;
|
||||
workflow_log_retention_days?: number | null;
|
||||
local_login_enabled?: boolean;
|
||||
}): Promise<{ saved: boolean }> {
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user