feat(ui): integrate failover controls into provider cards

- Add failover toggle button to provider card actions
- Show priority badge (P1, P2, ...) for queued providers
- Highlight active provider with green border in failover mode
- Sync drag-drop order with failover queue
- Move per-app failover toggle to FailoverQueueManager
- Simplify SettingsPage failover section
This commit is contained in:
YoVinchen
2025-12-22 00:39:15 +08:00
parent c9b851c5e6
commit 79adfc7fe8
9 changed files with 364 additions and 102 deletions
+13 -1
View File
@@ -68,9 +68,20 @@ function App() {
"bg-orange-500 hover:bg-orange-600 dark:bg-orange-500 dark:hover:bg-orange-600 text-white shadow-lg shadow-orange-500/30 dark:shadow-orange-500/40 rounded-full w-8 h-8";
// 获取代理服务状态
const { isRunning: isProxyRunning, takeoverStatus } = useProxyStatus();
const {
isRunning: isProxyRunning,
takeoverStatus,
status: proxyStatus,
} = useProxyStatus();
// 当前应用的代理是否开启
const isCurrentAppTakeoverActive = takeoverStatus?.[activeApp] || false;
// 当前应用代理实际使用的供应商 ID(从 active_targets 中获取)
const activeProviderId = useMemo(() => {
const target = proxyStatus?.active_targets?.find(
(t) => t.app_type === activeApp,
);
return target?.provider_id;
}, [proxyStatus?.active_targets, activeApp]);
// 获取供应商列表,当代理服务运行时自动刷新
const { data, isLoading, refetch } = useProvidersQuery(activeApp, {
@@ -353,6 +364,7 @@ function App() {
isProxyTakeover={
isProxyRunning && isCurrentAppTakeoverActive
}
activeProviderId={activeProviderId}
onSwitch={switchProvider}
onEdit={setEditingProvider}
onDelete={setConfirmDelete}
@@ -0,0 +1,34 @@
import { cn } from "@/lib/utils";
import { useTranslation } from "react-i18next";
interface FailoverPriorityBadgeProps {
priority: number; // 1, 2, 3, ...
className?: string;
}
/**
* 故障转移优先级徽章
* 显示供应商在故障转移队列中的优先级顺序
*/
export function FailoverPriorityBadge({
priority,
className,
}: FailoverPriorityBadgeProps) {
const { t } = useTranslation();
return (
<div
className={cn(
"inline-flex items-center px-1.5 py-0.5 rounded text-xs font-semibold",
"bg-emerald-500/10 text-emerald-600 dark:text-emerald-400",
className,
)}
title={t("failover.priority.tooltip", {
priority,
defaultValue: `故障转移优先级 ${priority}`,
})}
>
P{priority}
</div>
);
}
+78 -23
View File
@@ -5,6 +5,7 @@ import {
Edit,
Loader2,
Play,
Plus,
TestTube2,
Trash2,
} from "lucide-react";
@@ -22,6 +23,10 @@ interface ProviderActionsProps {
onTest?: () => void;
onConfigureUsage: () => void;
onDelete: () => void;
// 故障转移相关
isAutoFailoverEnabled?: boolean;
isInFailoverQueue?: boolean;
onToggleFailover?: (enabled: boolean) => void;
}
export function ProviderActions({
@@ -34,38 +39,88 @@ export function ProviderActions({
onTest,
onConfigureUsage,
onDelete,
// 故障转移相关
isAutoFailoverEnabled = false,
isInFailoverQueue = false,
onToggleFailover,
}: ProviderActionsProps) {
const { t } = useTranslation();
const iconButtonClass = "h-8 w-8 p-1";
// 故障转移模式下的按钮逻辑
const isFailoverMode = isAutoFailoverEnabled && onToggleFailover;
// 处理主按钮点击
const handleMainButtonClick = () => {
if (isFailoverMode) {
// 故障转移模式:切换队列状态
onToggleFailover(!isInFailoverQueue);
} else {
// 普通模式:切换供应商
onSwitch();
}
};
// 主按钮的状态和样式
const getMainButtonState = () => {
if (isFailoverMode) {
// 故障转移模式
if (isInFailoverQueue) {
return {
disabled: false,
variant: "secondary" as const,
className:
"bg-blue-100 text-blue-600 hover:bg-blue-200 dark:bg-blue-900/50 dark:text-blue-400 dark:hover:bg-blue-900/70",
icon: <Check className="h-4 w-4" />,
text: t("failover.inQueue", { defaultValue: "已加入" }),
};
}
return {
disabled: false,
variant: "default" as const,
className:
"bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700",
icon: <Plus className="h-4 w-4" />,
text: t("failover.addQueue", { defaultValue: "加入" }),
};
}
// 普通模式
if (isCurrent) {
return {
disabled: true,
variant: "secondary" as const,
className:
"bg-gray-200 text-muted-foreground hover:bg-gray-200 hover:text-muted-foreground dark:bg-gray-700 dark:hover:bg-gray-700",
icon: <Check className="h-4 w-4" />,
text: t("provider.inUse"),
};
}
return {
disabled: false,
variant: "default" as const,
className: isProxyTakeover
? "bg-emerald-500 hover:bg-emerald-600 dark:bg-emerald-600 dark:hover:bg-emerald-700"
: "",
icon: <Play className="h-4 w-4" />,
text: t("provider.enable"),
};
};
const buttonState = getMainButtonState();
return (
<div className="flex items-center gap-1.5">
<Button
size="sm"
variant={isCurrent ? "secondary" : "default"}
onClick={onSwitch}
disabled={isCurrent}
className={cn(
"w-[4.5rem] px-2.5",
isCurrent &&
"bg-gray-200 text-muted-foreground hover:bg-gray-200 hover:text-muted-foreground dark:bg-gray-700 dark:hover:bg-gray-700",
// 代理接管模式下启用按钮使用绿色
!isCurrent &&
isProxyTakeover &&
"bg-emerald-500 hover:bg-emerald-600 dark:bg-emerald-600 dark:hover:bg-emerald-700",
)}
variant={buttonState.variant}
onClick={handleMainButtonClick}
disabled={buttonState.disabled}
className={cn("w-[4.5rem] px-2.5", buttonState.className)}
>
{isCurrent ? (
<>
<Check className="h-4 w-4" />
{t("provider.inUse")}
</>
) : (
<>
<Play className="h-4 w-4" />
{t("provider.enable")}
</>
)}
{buttonState.icon}
{buttonState.text}
</Button>
<div className="flex items-center gap-1">
+41 -13
View File
@@ -12,6 +12,7 @@ import { ProviderActions } from "@/components/providers/ProviderActions";
import { ProviderIcon } from "@/components/ProviderIcon";
import UsageFooter from "@/components/UsageFooter";
import { ProviderHealthBadge } from "@/components/providers/ProviderHealthBadge";
import { FailoverPriorityBadge } from "@/components/providers/FailoverPriorityBadge";
import { useProviderHealth } from "@/lib/query/failover";
import { useUsageQuery } from "@/lib/query/queries";
@@ -36,6 +37,12 @@ interface ProviderCardProps {
isProxyRunning: boolean;
isProxyTakeover?: boolean; // 代理接管模式(Live配置已被接管,切换为热切换)
dragHandleProps?: DragHandleProps;
// 故障转移相关
isAutoFailoverEnabled?: boolean; // 是否开启自动故障转移
failoverPriority?: number; // 故障转移优先级(1 = P1, 2 = P2, ...
isInFailoverQueue?: boolean; // 是否在故障转移队列中
onToggleFailover?: (enabled: boolean) => void; // 切换故障转移队列
activeProviderId?: string; // 代理当前实际使用的供应商 ID(用于故障转移模式下标注绿色边框)
}
const extractApiUrl = (provider: Provider, fallbackText: string) => {
@@ -88,6 +95,12 @@ export function ProviderCard({
isProxyRunning,
isProxyTakeover = false,
dragHandleProps,
// 故障转移相关
isAutoFailoverEnabled = false,
failoverPriority,
isInFailoverQueue = false,
onToggleFailover,
activeProviderId,
}: ProviderCardProps) {
const { t } = useTranslation();
@@ -148,21 +161,27 @@ export function ProviderCard({
onOpenWebsite(displayUrl);
};
// 判断是否是"当前使用中"的供应商
// - 故障转移模式:代理实际使用的供应商(activeProviderId
// - 代理接管模式(非故障转移):isCurrent
// - 普通模式:isCurrent
const isActiveProvider = isAutoFailoverEnabled
? activeProviderId === provider.id
: isCurrent;
return (
<div
className={cn(
"relative overflow-hidden rounded-xl border border-border p-4 transition-all duration-300",
"bg-card text-card-foreground group",
// 代理接管模式下 hover 使用绿色边框,否则使用蓝色
isProxyTakeover
// hover 时的边框效果
isAutoFailoverEnabled || isProxyTakeover
? "hover:border-emerald-500/50"
: "hover:border-border-active",
// 代理接管模式下当前供应商使用绿色边框
isProxyTakeover && isCurrent
// 当前激活的供应商边框样式
isActiveProvider
? "border-emerald-500/60 shadow-sm shadow-emerald-500/10"
: isCurrent
? "border-primary/50 shadow-sm"
: "hover:shadow-sm",
: "hover:shadow-sm",
dragHandleProps?.isDragging &&
"cursor-grabbing border-primary shadow-lg scale-105 z-10",
)}
@@ -170,11 +189,9 @@ export function ProviderCard({
<div
className={cn(
"absolute inset-0 bg-gradient-to-r to-transparent transition-opacity duration-500 pointer-events-none",
// 代理接管模式下使用绿色渐变,否则使用蓝色主色调
isProxyTakeover && isCurrent
? "from-emerald-500/10"
: "from-primary/10",
isCurrent ? "opacity-100" : "opacity-0",
// 当前激活的供应商使用绿色渐变
isActiveProvider ? "from-emerald-500/10" : "from-primary/10",
isActiveProvider ? "opacity-100" : "opacity-0",
)}
/>
<div className="relative flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
@@ -209,13 +226,20 @@ export function ProviderCard({
{provider.name}
</h3>
{/* 健康状态徽章和优先级 */}
{/* 健康状态徽章 */}
{isProxyRunning && health && (
<ProviderHealthBadge
consecutiveFailures={health.consecutive_failures}
/>
)}
{/* 故障转移优先级徽章 */}
{isAutoFailoverEnabled &&
isInFailoverQueue &&
failoverPriority && (
<FailoverPriorityBadge priority={failoverPriority} />
)}
{provider.category === "third_party" &&
provider.meta?.isPartner && (
<span
@@ -308,6 +332,10 @@ export function ProviderCard({
onTest={onTest ? () => onTest(provider) : undefined}
onConfigureUsage={() => onConfigureUsage(provider)}
onDelete={() => onDelete(provider)}
// 故障转移相关
isAutoFailoverEnabled={isAutoFailoverEnabled}
isInFailoverQueue={isInFailoverQueue}
onToggleFailover={onToggleFailover}
/>
</div>
</div>
+124 -2
View File
@@ -12,6 +12,14 @@ import { useDragSort } from "@/hooks/useDragSort";
import { useStreamCheck } from "@/hooks/useStreamCheck";
import { ProviderCard } from "@/components/providers/ProviderCard";
import { ProviderEmptyState } from "@/components/providers/ProviderEmptyState";
import {
useAutoFailoverEnabled,
useFailoverQueue,
useAddToFailoverQueue,
useRemoveFromFailoverQueue,
useReorderFailoverQueue,
} from "@/lib/query/failover";
import { useCallback, useEffect, useRef } from "react";
interface ProviderListProps {
providers: Record<string, Provider>;
@@ -27,6 +35,7 @@ interface ProviderListProps {
isLoading?: boolean;
isProxyRunning?: boolean; // 代理服务运行状态
isProxyTakeover?: boolean; // 代理接管模式(Live配置已被接管)
activeProviderId?: string; // 代理当前实际使用的供应商 ID(用于故障转移模式下标注绿色边框)
}
export function ProviderList({
@@ -41,8 +50,9 @@ export function ProviderList({
onOpenWebsite,
onCreate,
isLoading = false,
isProxyRunning = false, // 默认值为 false
isProxyTakeover = false, // 默认值为 false
isProxyRunning = false,
isProxyTakeover = false,
activeProviderId,
}: ProviderListProps) {
const { sortedProviders, sensors, handleDragEnd } = useDragSort(
providers,
@@ -52,6 +62,93 @@ export function ProviderList({
// 流式健康检查
const { checkProvider, isChecking } = useStreamCheck(appId);
// 故障转移相关
const { data: isAutoFailoverEnabled } = useAutoFailoverEnabled(appId);
const { data: failoverQueue } = useFailoverQueue(appId);
const addToQueue = useAddToFailoverQueue();
const removeFromQueue = useRemoveFromFailoverQueue();
const reorderQueue = useReorderFailoverQueue();
// 联动状态:只有当前应用开启代理接管且故障转移开启时才启用故障转移模式
const isFailoverModeActive =
isProxyTakeover === true && isAutoFailoverEnabled === true;
// 防止重复调用的 ref
const lastReorderRef = useRef<string>("");
// 计算供应商在故障转移队列中的优先级
const getFailoverPriority = useCallback(
(providerId: string): number | undefined => {
if (!isFailoverModeActive || !failoverQueue) return undefined;
// 只计算已启用的供应商的优先级
const enabledQueue = failoverQueue.filter((item) => item.enabled);
const index = enabledQueue.findIndex(
(item) => item.providerId === providerId,
);
return index >= 0 ? index + 1 : undefined;
},
[isFailoverModeActive, failoverQueue],
);
// 判断供应商是否在故障转移队列中
const isInFailoverQueue = useCallback(
(providerId: string): boolean => {
if (!isFailoverModeActive || !failoverQueue) return false;
return failoverQueue.some(
(item) => item.providerId === providerId && item.enabled,
);
},
[isFailoverModeActive, failoverQueue],
);
// 切换供应商的故障转移队列状态
const handleToggleFailover = useCallback(
(providerId: string, enabled: boolean) => {
if (enabled) {
addToQueue.mutate({ appType: appId, providerId });
} else {
removeFromQueue.mutate({ appType: appId, providerId });
}
},
[appId, addToQueue, removeFromQueue],
);
// 当拖拽排序后,同步故障转移队列顺序
useEffect(() => {
if (!isFailoverModeActive || !failoverQueue || failoverQueue.length === 0)
return;
// 获取当前在队列中且已启用的供应商 ID 列表(按显示顺序)
const enabledProviderIds = sortedProviders
.filter((p) => isInFailoverQueue(p.id))
.map((p) => p.id);
if (enabledProviderIds.length === 0) return;
// 生成唯一标识防止重复调用
const orderKey = enabledProviderIds.join(",");
if (orderKey === lastReorderRef.current) return;
// 检查顺序是否需要更新
const currentOrder = failoverQueue
.filter((item) => item.enabled)
.sort((a, b) => a.queueOrder - b.queueOrder)
.map((item) => item.providerId)
.join(",");
if (orderKey !== currentOrder) {
lastReorderRef.current = orderKey;
reorderQueue.mutate({ appType: appId, providerIds: enabledProviderIds });
}
}, [
sortedProviders,
isFailoverModeActive,
failoverQueue,
isInFailoverQueue,
appId,
reorderQueue,
]);
const handleTest = (provider: Provider) => {
checkProvider(provider.id, provider.name);
};
@@ -100,6 +197,14 @@ export function ProviderList({
isTesting={isChecking(provider.id)}
isProxyRunning={isProxyRunning}
isProxyTakeover={isProxyTakeover}
// 故障转移相关:联动状态
isAutoFailoverEnabled={isFailoverModeActive}
failoverPriority={getFailoverPriority(provider.id)}
isInFailoverQueue={isInFailoverQueue(provider.id)}
onToggleFailover={(enabled) =>
handleToggleFailover(provider.id, enabled)
}
activeProviderId={activeProviderId}
/>
))}
</div>
@@ -122,6 +227,12 @@ interface SortableProviderCardProps {
isTesting: boolean;
isProxyRunning: boolean;
isProxyTakeover: boolean;
// 故障转移相关
isAutoFailoverEnabled: boolean;
failoverPriority?: number;
isInFailoverQueue: boolean;
onToggleFailover: (enabled: boolean) => void;
activeProviderId?: string;
}
function SortableProviderCard({
@@ -138,6 +249,11 @@ function SortableProviderCard({
isTesting,
isProxyRunning,
isProxyTakeover,
isAutoFailoverEnabled,
failoverPriority,
isInFailoverQueue,
onToggleFailover,
activeProviderId,
}: SortableProviderCardProps) {
const {
setNodeRef,
@@ -176,6 +292,12 @@ function SortableProviderCard({
listeners,
isDragging,
}}
// 故障转移相关
isAutoFailoverEnabled={isAutoFailoverEnabled}
failoverPriority={failoverPriority}
isInFailoverQueue={isInFailoverQueue}
onToggleFailover={onToggleFailover}
activeProviderId={activeProviderId}
/>
</div>
);
@@ -12,14 +12,14 @@ import {
} from "@/lib/query/failover";
export interface AutoFailoverConfigPanelProps {
enabled: boolean;
onEnabledChange: (enabled: boolean) => void;
enabled?: boolean;
onEnabledChange?: (enabled: boolean) => void;
}
export function AutoFailoverConfigPanel({
enabled,
enabled = true,
onEnabledChange: _onEnabledChange,
}: AutoFailoverConfigPanelProps) {
}: AutoFailoverConfigPanelProps = {}) {
// Note: onEnabledChange is currently unused but kept in the interface
// for potential future use by parent components
void _onEnabledChange;
@@ -53,6 +53,8 @@ import {
useRemoveFromFailoverQueue,
useReorderFailoverQueue,
useSetFailoverItemEnabled,
useAutoFailoverEnabled,
useSetAutoFailoverEnabled,
} from "@/lib/query/failover";
interface FailoverQueueManagerProps {
@@ -67,6 +69,10 @@ export function FailoverQueueManager({
const { t } = useTranslation();
const [selectedProviderId, setSelectedProviderId] = useState<string>("");
// 故障转移开关状态(每个应用独立)
const { data: isFailoverEnabled = false } = useAutoFailoverEnabled(appType);
const setFailoverEnabled = useSetAutoFailoverEnabled();
// 查询数据
const {
data: queue,
@@ -82,6 +88,11 @@ export function FailoverQueueManager({
const reorderQueue = useReorderFailoverQueue();
const setItemEnabled = useSetFailoverItemEnabled();
// 切换故障转移开关
const handleToggleFailover = (enabled: boolean) => {
setFailoverEnabled.mutate({ appType, enabled });
};
// 拖拽配置
const sensors = useSensors(
useSensor(PointerSensor, {
@@ -203,6 +214,34 @@ export function FailoverQueueManager({
return (
<div className="space-y-4">
{/* 自动故障转移开关 */}
<div className="flex items-center justify-between p-4 rounded-lg bg-muted/50 border border-border/50">
<div className="space-y-0.5">
<div className="flex items-center gap-2">
<span className="text-sm font-medium">
{t("proxy.failover.autoSwitch", {
defaultValue: "自动故障转移",
})}
</span>
{isFailoverEnabled && (
<span className="px-2 py-0.5 text-xs rounded-full bg-emerald-500/20 text-emerald-600 dark:text-emerald-400">
{t("common.enabled", { defaultValue: "已开启" })}
</span>
)}
</div>
<p className="text-xs text-muted-foreground">
{t("proxy.failover.autoSwitchDescription", {
defaultValue: "开启后,请求失败时自动切换到队列中的下一个供应商",
})}
</p>
</div>
<Switch
checked={isFailoverEnabled}
onCheckedChange={handleToggleFailover}
disabled={disabled || setFailoverEnabled.isPending}
/>
</div>
{/* 说明信息 */}
<Alert className="border-blue-500/40 bg-blue-500/10">
<Info className="h-4 w-4" />
+30 -59
View File
@@ -47,10 +47,6 @@ import type { SettingsFormState } from "@/hooks/useSettings";
import { Switch } from "@/components/ui/switch";
import { Badge } from "@/components/ui/badge";
import { useProxyStatus } from "@/hooks/useProxyStatus";
import {
useAutoFailoverEnabled,
useSetAutoFailoverEnabled,
} from "@/lib/query/failover";
interface SettingsDialogProps {
open: boolean;
@@ -187,17 +183,9 @@ export function SettingsPage({
isPending: isProxyPending,
} = useProxyStatus();
// 使用持久化的自动故障转移开关状态
const { data: failoverEnabled = false } = useAutoFailoverEnabled();
const setAutoFailoverEnabled = useSetAutoFailoverEnabled();
const handleToggleProxy = async (checked: boolean) => {
try {
if (!checked) {
// 关闭代理时,同时关闭故障转移
if (failoverEnabled) {
setAutoFailoverEnabled.mutate(false);
}
await stopWithRestore();
} else {
await startProxyServer();
@@ -207,19 +195,6 @@ export function SettingsPage({
}
};
// 处理故障转移开关:开启时自动启动代理
const handleToggleFailover = async (checked: boolean) => {
try {
if (checked && !isRunning) {
// 开启故障转移时,先启动代理
await startProxyServer();
}
setAutoFailoverEnabled.mutate(checked);
} catch (error) {
console.error("Toggle failover failed:", error);
}
};
return (
<div className="mx-auto max-w-[56rem] flex flex-col h-[calc(100vh-8rem)] overflow-hidden px-6">
{isBusy ? (
@@ -380,37 +355,36 @@ export function SettingsPage({
<AccordionItem
value="failover"
className="rounded-xl glass-card overflow-hidden [&[data-state=open]>.accordion-header]:bg-muted/50"
className="rounded-xl glass-card overflow-hidden"
>
<AccordionPrimitive.Header className="accordion-header flex items-center justify-between px-6 py-4 hover:bg-muted/50">
<AccordionPrimitive.Trigger className="flex flex-1 items-center justify-between hover:no-underline [&[data-state=open]>svg]:rotate-180">
<div className="flex items-center gap-3">
<Activity className="h-5 w-5 text-orange-500" />
<div className="text-left">
<h3 className="text-base font-semibold">
{t("settings.advanced.failover.title")}
</h3>
<p className="text-sm text-muted-foreground font-normal">
{t("settings.advanced.failover.description")}
</p>
</div>
<AccordionTrigger className="px-6 py-4 hover:no-underline hover:bg-muted/50 data-[state=open]:bg-muted/50">
<div className="flex items-center gap-3">
<Activity className="h-5 w-5 text-orange-500" />
<div className="text-left">
<h3 className="text-base font-semibold">
{t("settings.advanced.failover.title")}
</h3>
<p className="text-sm text-muted-foreground font-normal">
{t("settings.advanced.failover.description")}
</p>
</div>
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
<div className="flex items-center gap-2 pl-4">
<Switch
checked={failoverEnabled && isRunning}
onCheckedChange={handleToggleFailover}
disabled={
setAutoFailoverEnabled.isPending || isProxyPending
}
/>
</div>
</AccordionPrimitive.Header>
</AccordionTrigger>
<AccordionContent className="px-6 pb-6 pt-4 border-t border-border/50">
<div className="space-y-6">
{/* 故障转移队列管理 */}
{/* 代理未运行时的提示 */}
{!isRunning && (
<div className="p-4 rounded-lg bg-yellow-500/10 border border-yellow-500/20">
<p className="text-sm text-yellow-600 dark:text-yellow-400">
{t("proxy.failover.proxyRequired", {
defaultValue:
"需要先启动代理服务才能配置故障转移",
})}
</p>
</div>
)}
{/* 故障转移队列管理 - 每个应用独立 */}
<div className="space-y-4">
<div>
<h4 className="text-sm font-semibold">
@@ -429,30 +403,27 @@ export function SettingsPage({
<TabsContent value="claude" className="mt-4">
<FailoverQueueManager
appType="claude"
disabled={!failoverEnabled || !isRunning}
disabled={!isRunning}
/>
</TabsContent>
<TabsContent value="codex" className="mt-4">
<FailoverQueueManager
appType="codex"
disabled={!failoverEnabled || !isRunning}
disabled={!isRunning}
/>
</TabsContent>
<TabsContent value="gemini" className="mt-4">
<FailoverQueueManager
appType="gemini"
disabled={!failoverEnabled || !isRunning}
disabled={!isRunning}
/>
</TabsContent>
</Tabs>
</div>
{/* 熔断器配置 */}
{/* 熔断器配置 - 全局共享 */}
<div className="border-t border-border/50 pt-6">
<AutoFailoverConfigPanel
enabled={failoverEnabled && isRunning}
onEnabledChange={handleToggleFailover}
/>
<AutoFailoverConfigPanel />
</div>
</div>
</AccordionContent>
+1
View File
@@ -75,6 +75,7 @@ export function useProxyStatus() {
queryClient.invalidateQueries({ queryKey: ["proxyTakeoverStatus"] });
// 清除所有供应商健康状态缓存(后端已清空数据库记录)
queryClient.invalidateQueries({ queryKey: ["providerHealth"] });
// 注意:故障转移队列和开关状态会保留,不需要刷新
},
onError: (error: Error) => {
const detail =