/* 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 { zodResolver } from '@hookform/resolvers/zod' import { useMemo, useRef } from 'react' import { useForm } from 'react-hook-form' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import * as z from 'zod' import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form' import { Input } from '@/components/ui/input' import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { Separator } from '@/components/ui/separator' import { Switch } from '@/components/ui/switch' import { Textarea } from '@/components/ui/textarea' import { parseHttpStatusCodeRules } from '@/lib/http-status-code-rules' 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' import { safeNumberFieldProps } from '../utils/numeric-field' 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 channelTestModes = [ 'scheduled_all', 'auto_ban_only', 'passive_recovery', ] as const type ChannelTestMode = (typeof channelTestModes)[number] const routingReliabilitySchema = z .object({ RetryTimes: z.coerce.number().min(0).max(10), ChannelDisableThreshold: 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'), channel_test_mode: z.enum(channelTestModes), }), }) .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 RoutingReliabilityFormValues = z.output type RoutingReliabilityFormInput = z.input type RoutingReliabilitySectionProps = { defaultValues: { RetryTimes: number ChannelDisableThreshold: 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 'monitor_setting.channel_test_mode': ChannelTestMode } } function normalizeLineEndings(value: string) { return value.replaceAll('\r\n', '\n') } type NormalizedRoutingReliabilityValues = { RetryTimes: number ChannelDisableThreshold: 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 'monitor_setting.channel_test_mode': ChannelTestMode } function normalizeChannelTestMode(value?: string): ChannelTestMode { if (value === 'auto_ban_only' || value === 'passive_recovery') { return value } return 'scheduled_all' } const buildFormDefaults = ( defaults: RoutingReliabilitySectionProps['defaultValues'] ): RoutingReliabilityFormInput => ({ RetryTimes: defaults.RetryTimes ?? 0, ChannelDisableThreshold: defaults.ChannelDisableThreshold ?? '', 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'], channel_test_mode: normalizeChannelTestMode( defaults['monitor_setting.channel_test_mode'] ), }, }) const normalizeDefaults = ( defaults: RoutingReliabilitySectionProps['defaultValues'] ): NormalizedRoutingReliabilityValues => ({ RetryTimes: defaults.RetryTimes ?? 0, ChannelDisableThreshold: (defaults.ChannelDisableThreshold ?? '').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'], 'monitor_setting.channel_test_mode': normalizeChannelTestMode( defaults['monitor_setting.channel_test_mode'] ), }) const normalizeFormValues = ( values: RoutingReliabilityFormValues ): NormalizedRoutingReliabilityValues => ({ RetryTimes: values.RetryTimes, ChannelDisableThreshold: values.ChannelDisableThreshold.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, 'monitor_setting.channel_test_mode': values.monitor_setting.channel_test_mode, }) export function RoutingReliabilitySection({ defaultValues, }: RoutingReliabilitySectionProps) { const { t } = useTranslation() const updateOption = useUpdateOption() const baselineRef = useRef( normalizeDefaults(defaultValues) ) const formDefaults = useMemo( () => buildFormDefaults(defaultValues), [defaultValues] ) const form = useForm< RoutingReliabilityFormInput, unknown, RoutingReliabilityFormValues >({ resolver: zodResolver(routingReliabilitySchema), defaultValues: formDefaults, }) useResetForm(form, formDefaults) const autoDisableStatusCodes = form.watch('AutomaticDisableStatusCodes') const autoRetryStatusCodes = form.watch('AutomaticRetryStatusCodes') const channelTestMode = form.watch('monitor_setting.channel_test_mode') let channelTestModeDescription: string switch (channelTestMode) { case 'auto_ban_only': channelTestModeDescription = t( 'Periodically checks only channels with auto-disable enabled, excluding manually disabled channels.' ) break case 'passive_recovery': channelTestModeDescription = t( 'Does not check healthy channels. It only rechecks auto-disabled channels and restores them after they recover.' ) break default: channelTestModeDescription = t( 'Periodically checks all channels except manually disabled ones to detect failures and recover channels automatically.' ) } const autoDisableParsed = useMemo( () => parseHttpStatusCodeRules(autoDisableStatusCodes), [autoDisableStatusCodes] ) const autoRetryParsed = useMemo( () => parseHttpStatusCodeRules(autoRetryStatusCodes), [autoRetryStatusCodes] ) const onSubmit = async (values: RoutingReliabilityFormValues) => { 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('Request retry')}

( {t('Retry Times')} {t('Number of times to retry failed requests (0-10)')} )} /> ( {t('Auto-retry status codes')} field.onChange(event.target.value)} /> {t( 'Accepts comma-separated status codes and inclusive ranges.' )}{' '} {autoRetryParsed.ok && autoRetryParsed.normalized && autoRetryParsed.normalized !== field.value.trim() && ( {t('Normalized:')} {autoRetryParsed.normalized} )} )} />

{t('Channel health checks')}

( {t('Scheduled channel tests')} {t( 'Automatically probe all channels in the background' )} )} /> ( {t('Channel test mode')} {channelTestModeDescription} )} /> ( {t('Test interval (minutes)')} {channelTestMode === 'passive_recovery' ? t( 'How frequently the system checks auto-disabled channels for recovery' ) : t('How frequently the system tests all channels')} )} /> ( {t('Re-enable on success')} {t( 'Bring channels back online after successful checks' )} )} />

{t('Auto-disable rules')}

( {t('Disable on failure')} {t('Automatically disable channels when tests fail')} )} /> ( {t('Disable threshold (seconds)')} field.onChange(event.target.value)} /> {t( 'Automatically disable channels exceeding this response time' )} )} /> ( {t('Auto-disable status codes')} field.onChange(event.target.value)} /> {t( 'Accepts comma-separated status codes and inclusive ranges.' )}{' '} {autoDisableParsed.ok && autoDisableParsed.normalized && autoDisableParsed.normalized !== field.value.trim() && ( {t('Normalized:')} {autoDisableParsed.normalized} )} )} /> ( {t('Failure keywords')}