import { useCircuitBreakerConfig, useUpdateCircuitBreakerConfig, } from "@/lib/query/failover"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; import { useState, useEffect } from "react"; import { toast } from "sonner"; import { useTranslation } from "react-i18next"; /** * 熔断器配置面板 * 允许用户调整熔断器参数 */ export function CircuitBreakerConfigPanel() { const { t } = useTranslation(); const { data: config, isLoading } = useCircuitBreakerConfig(); const updateConfig = useUpdateCircuitBreakerConfig(); // 使用字符串状态以支持完全清空输入框 const [formData, setFormData] = useState({ failureThreshold: "5", successThreshold: "2", timeoutSeconds: "60", errorRateThreshold: "50", // 存储百分比值 minRequests: "10", }); // 当配置加载完成时更新表单数据 useEffect(() => { if (config) { setFormData({ failureThreshold: String(config.failureThreshold), successThreshold: String(config.successThreshold), timeoutSeconds: String(config.timeoutSeconds), errorRateThreshold: String(Math.round(config.errorRateThreshold * 100)), minRequests: String(config.minRequests), }); } }, [config]); const handleSave = async () => { // 解析数字,返回 NaN 表示无效输入 const parseNum = (val: string) => { const trimmed = val.trim(); // 必须是纯数字 if (!/^-?\d+$/.test(trimmed)) return NaN; return parseInt(trimmed); }; // 定义各字段的有效范围 const ranges = { failureThreshold: { min: 1, max: 20 }, successThreshold: { min: 1, max: 10 }, timeoutSeconds: { min: 0, max: 300 }, errorRateThreshold: { min: 0, max: 100 }, minRequests: { min: 5, max: 100 }, }; // 解析原始值 const raw = { failureThreshold: parseNum(formData.failureThreshold), successThreshold: parseNum(formData.successThreshold), timeoutSeconds: parseNum(formData.timeoutSeconds), errorRateThreshold: parseNum(formData.errorRateThreshold), minRequests: parseNum(formData.minRequests), }; // 校验是否超出范围(NaN 也视为无效) const errors: string[] = []; const checkRange = ( value: number, range: { min: number; max: number }, label: string, ) => { if (isNaN(value) || value < range.min || value > range.max) { errors.push(`${label}: ${range.min}-${range.max}`); } }; checkRange( raw.failureThreshold, ranges.failureThreshold, t("circuitBreaker.failureThreshold", "失败阈值"), ); checkRange( raw.successThreshold, ranges.successThreshold, t("circuitBreaker.successThreshold", "成功阈值"), ); checkRange( raw.timeoutSeconds, ranges.timeoutSeconds, t("circuitBreaker.timeoutSeconds", "超时时间"), ); checkRange( raw.errorRateThreshold, ranges.errorRateThreshold, t("circuitBreaker.errorRateThreshold", "错误率阈值"), ); checkRange( raw.minRequests, ranges.minRequests, t("circuitBreaker.minRequests", "最小请求数"), ); if (errors.length > 0) { toast.error( t("circuitBreaker.validationFailed", { fields: errors.join("; "), defaultValue: `以下字段超出有效范围: ${errors.join("; ")}`, }), ); return; } try { await updateConfig.mutateAsync({ failureThreshold: raw.failureThreshold, successThreshold: raw.successThreshold, timeoutSeconds: raw.timeoutSeconds, errorRateThreshold: raw.errorRateThreshold / 100, minRequests: raw.minRequests, }); toast.success(t("circuitBreaker.configSaved", "熔断器配置已保存"), { closeButton: true, }); } catch (error) { toast.error( t("circuitBreaker.saveFailed", "保存失败") + ": " + String(error), ); } }; const handleReset = () => { if (config) { setFormData({ failureThreshold: String(config.failureThreshold), successThreshold: String(config.successThreshold), timeoutSeconds: String(config.timeoutSeconds), errorRateThreshold: String(Math.round(config.errorRateThreshold * 100)), minRequests: String(config.minRequests), }); } }; if (isLoading) { return (
{t("circuitBreaker.loading", "加载中...")}
); } return (

{t("circuitBreaker.title", "熔断器配置")}

{t( "circuitBreaker.description", "调整熔断器参数以控制故障检测和恢复行为", )}

{/* 失败阈值 */}
setFormData({ ...formData, failureThreshold: e.target.value }) } />

{t( "circuitBreaker.failureThresholdHint", "连续失败多少次后打开熔断器", )}

{/* 超时时间 */}
setFormData({ ...formData, timeoutSeconds: e.target.value }) } />

{t( "circuitBreaker.timeoutSecondsHint", "熔断器打开后多久尝试恢复(半开状态)", )}

{/* 成功阈值 */}
setFormData({ ...formData, successThreshold: e.target.value }) } />

{t( "circuitBreaker.successThresholdHint", "半开状态下成功多少次后关闭熔断器", )}

{/* 错误率阈值 */}
setFormData({ ...formData, errorRateThreshold: e.target.value }) } />

{t( "circuitBreaker.errorRateThresholdHint", "错误率超过此值时打开熔断器", )}

{/* 最小请求数 */}
setFormData({ ...formData, minRequests: e.target.value }) } />

{t("circuitBreaker.minRequestsHint", "计算错误率前的最小请求数")}

{/* 说明信息 */}

{t("circuitBreaker.instructionsTitle", "配置说明")}

  • •{" "} {t("circuitBreaker.failureThreshold", "失败阈值")} : {t( "circuitBreaker.instructions.failureThreshold", "连续失败达到此次数时,熔断器打开", )}
  • {t("circuitBreaker.timeoutSeconds", "超时时间")} : {t( "circuitBreaker.instructions.timeout", "熔断器打开后,等待此时间后尝试半开", )}
  • •{" "} {t("circuitBreaker.successThreshold", "成功阈值")} : {t( "circuitBreaker.instructions.successThreshold", "半开状态下,成功达到此次数时关闭熔断器", )}
  • •{" "} {t("circuitBreaker.errorRateThreshold", "错误率阈值")} : {t( "circuitBreaker.instructions.errorRate", "错误率超过此值时,熔断器打开", )}
  • {t("circuitBreaker.minRequests", "最小请求数")}: {t( "circuitBreaker.instructions.minRequests", "只有请求数达到此值后才计算错误率", )}
); }