/* Copyright (C) 2023-2026 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import { useMemo, useRef } from 'react' import * as z from 'zod' import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import { parseHttpStatusCodeRules } from '@/lib/http-status-code-rules' import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form' import { Input } from '@/components/ui/input' import { Switch } from '@/components/ui/switch' import { Textarea } from '@/components/ui/textarea' import { SettingsForm, SettingsSwitchContent, SettingsSwitchItem, } from '../components/settings-form-layout' import { SettingsPageFormActions } from '../components/settings-page-context' import { SettingsSection } from '../components/settings-section' import { useResetForm } from '../hooks/use-reset-form' import { useUpdateOption } from '../hooks/use-update-option' const numericString = z.string().refine((value) => { const trimmed = value.trim() if (!trimmed) return true return !Number.isNaN(Number(trimmed)) && Number(trimmed) >= 0 }, 'Enter a non-negative number or leave empty') const monitoringSchema = z .object({ ChannelDisableThreshold: numericString, QuotaRemindThreshold: numericString, AutomaticDisableChannelEnabled: z.boolean(), AutomaticEnableChannelEnabled: z.boolean(), AutomaticDisableKeywords: z.string(), AutomaticDisableStatusCodes: z.string(), AutomaticRetryStatusCodes: z.string(), monitor_setting: z.object({ auto_test_channel_enabled: z.boolean(), auto_test_channel_minutes: z.coerce .number() .int() .min(1, 'Interval must be at least 1 minute'), }), }) .superRefine((values, ctx) => { const disableParsed = parseHttpStatusCodeRules( values.AutomaticDisableStatusCodes ) if (!disableParsed.ok) { ctx.addIssue({ code: 'custom', path: ['AutomaticDisableStatusCodes'], message: `Invalid status code rules: ${disableParsed.invalidTokens.join( ', ' )}`, }) } const retryParsed = parseHttpStatusCodeRules( values.AutomaticRetryStatusCodes ) if (!retryParsed.ok) { ctx.addIssue({ code: 'custom', path: ['AutomaticRetryStatusCodes'], message: `Invalid status code rules: ${retryParsed.invalidTokens.join( ', ' )}`, }) } }) type MonitoringFormValues = z.output type MonitoringFormInput = z.input type MonitoringSettingsSectionProps = { defaultValues: { ChannelDisableThreshold: string QuotaRemindThreshold: string AutomaticDisableChannelEnabled: boolean AutomaticEnableChannelEnabled: boolean AutomaticDisableKeywords: string AutomaticDisableStatusCodes: string AutomaticRetryStatusCodes: string 'monitor_setting.auto_test_channel_enabled': boolean 'monitor_setting.auto_test_channel_minutes': number } } function normalizeLineEndings(value: string) { return value.replace(/\r\n/g, '\n') } type NormalizedMonitoringValues = { ChannelDisableThreshold: string QuotaRemindThreshold: string AutomaticDisableChannelEnabled: boolean AutomaticEnableChannelEnabled: boolean AutomaticDisableKeywords: string AutomaticDisableStatusCodes: string AutomaticRetryStatusCodes: string 'monitor_setting.auto_test_channel_enabled': boolean 'monitor_setting.auto_test_channel_minutes': number } const buildFormDefaults = ( defaults: MonitoringSettingsSectionProps['defaultValues'] ): MonitoringFormInput => ({ ChannelDisableThreshold: defaults.ChannelDisableThreshold ?? '', QuotaRemindThreshold: defaults.QuotaRemindThreshold ?? '', AutomaticDisableChannelEnabled: defaults.AutomaticDisableChannelEnabled, AutomaticEnableChannelEnabled: defaults.AutomaticEnableChannelEnabled, AutomaticDisableKeywords: normalizeLineEndings( defaults.AutomaticDisableKeywords ?? '' ), AutomaticDisableStatusCodes: defaults.AutomaticDisableStatusCodes ?? '', AutomaticRetryStatusCodes: defaults.AutomaticRetryStatusCodes ?? '', monitor_setting: { auto_test_channel_enabled: defaults['monitor_setting.auto_test_channel_enabled'], auto_test_channel_minutes: defaults['monitor_setting.auto_test_channel_minutes'], }, }) const normalizeDefaults = ( defaults: MonitoringSettingsSectionProps['defaultValues'] ): NormalizedMonitoringValues => ({ ChannelDisableThreshold: (defaults.ChannelDisableThreshold ?? '').trim(), QuotaRemindThreshold: (defaults.QuotaRemindThreshold ?? '').trim(), AutomaticDisableChannelEnabled: defaults.AutomaticDisableChannelEnabled, AutomaticEnableChannelEnabled: defaults.AutomaticEnableChannelEnabled, AutomaticDisableKeywords: normalizeLineEndings( defaults.AutomaticDisableKeywords ?? '' ), AutomaticDisableStatusCodes: parseHttpStatusCodeRules( defaults.AutomaticDisableStatusCodes ?? '' ).normalized, AutomaticRetryStatusCodes: parseHttpStatusCodeRules( defaults.AutomaticRetryStatusCodes ?? '' ).normalized, 'monitor_setting.auto_test_channel_enabled': defaults['monitor_setting.auto_test_channel_enabled'], 'monitor_setting.auto_test_channel_minutes': defaults['monitor_setting.auto_test_channel_minutes'], }) const normalizeFormValues = ( values: MonitoringFormValues ): NormalizedMonitoringValues => ({ ChannelDisableThreshold: values.ChannelDisableThreshold.trim(), QuotaRemindThreshold: values.QuotaRemindThreshold.trim(), AutomaticDisableChannelEnabled: values.AutomaticDisableChannelEnabled, AutomaticEnableChannelEnabled: values.AutomaticEnableChannelEnabled, AutomaticDisableKeywords: normalizeLineEndings( values.AutomaticDisableKeywords ), AutomaticDisableStatusCodes: parseHttpStatusCodeRules( values.AutomaticDisableStatusCodes ).normalized, AutomaticRetryStatusCodes: parseHttpStatusCodeRules( values.AutomaticRetryStatusCodes ).normalized, 'monitor_setting.auto_test_channel_enabled': values.monitor_setting.auto_test_channel_enabled, 'monitor_setting.auto_test_channel_minutes': values.monitor_setting.auto_test_channel_minutes, }) export function MonitoringSettingsSection({ defaultValues, }: MonitoringSettingsSectionProps) { const { t } = useTranslation() const updateOption = useUpdateOption() const baselineRef = useRef( normalizeDefaults(defaultValues) ) const formDefaults = useMemo( () => buildFormDefaults(defaultValues), [defaultValues] ) const form = useForm({ resolver: zodResolver(monitoringSchema), defaultValues: formDefaults, }) useResetForm(form, formDefaults) const autoDisableStatusCodes = form.watch('AutomaticDisableStatusCodes') const autoRetryStatusCodes = form.watch('AutomaticRetryStatusCodes') const autoDisableParsed = useMemo( () => parseHttpStatusCodeRules(autoDisableStatusCodes), [autoDisableStatusCodes] ) const autoRetryParsed = useMemo( () => parseHttpStatusCodeRules(autoRetryStatusCodes), [autoRetryStatusCodes] ) const onSubmit = async (values: MonitoringFormValues) => { const normalized = normalizeFormValues(values) const updates = ( Object.keys(normalized) as Array ).filter((key) => normalized[key] !== baselineRef.current[key]) if (updates.length === 0) { toast.info(t('No changes to save')) return } for (const key of updates) { const value = normalized[key] await updateOption.mutateAsync({ key, value, }) } baselineRef.current = normalized } return (
( {t('Scheduled channel tests')} {t('Automatically probe all channels in the background')} )} /> ( {t('Test interval (minutes)')} field.onChange(event.target.valueAsNumber) } name={field.name} onBlur={field.onBlur} ref={field.ref} /> {t('How frequently the system tests all channels')} )} />
( {t('Disable threshold (seconds)')} field.onChange(event.target.value)} /> {t( 'Automatically disable channels exceeding this response time' )} )} /> ( {t('Quota reminder (tokens)')} field.onChange(event.target.value)} /> {t('Send email alerts when a user falls below this quota')} )} />
( {t('Disable on failure')} {t('Automatically disable channels when tests fail')} )} /> ( {t('Re-enable on success')} {t('Bring channels back online after successful checks')} )} />
( {t('Failure keywords')}