/** * 故障转移队列管理组件 * * 允许用户管理代理模式下的故障转移队列,支持: * - 添加/移除供应商 * - 队列顺序基于首页供应商列表的 sort_index */ import { useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { Plus, Trash2, Loader2, Info, AlertTriangle } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { cn } from "@/lib/utils"; import type { FailoverQueueItem } from "@/types/proxy"; import type { AppId } from "@/lib/api"; import { useFailoverQueue, useAvailableProvidersForFailover, useAddToFailoverQueue, useRemoveFromFailoverQueue, useAutoFailoverEnabled, useSetAutoFailoverEnabled, } from "@/lib/query/failover"; interface FailoverQueueManagerProps { appType: AppId; disabled?: boolean; } export function FailoverQueueManager({ appType, disabled = false, }: FailoverQueueManagerProps) { const { t } = useTranslation(); const [selectedProviderId, setSelectedProviderId] = useState(""); // 故障转移开关状态(每个应用独立) const { data: isFailoverEnabled = false } = useAutoFailoverEnabled(appType); const setFailoverEnabled = useSetAutoFailoverEnabled(); // 查询数据 const { data: queue, isLoading: isQueueLoading, error: queueError, } = useFailoverQueue(appType); const { data: availableProviders, isLoading: isProvidersLoading } = useAvailableProvidersForFailover(appType); // Mutations const addToQueue = useAddToFailoverQueue(); const removeFromQueue = useRemoveFromFailoverQueue(); // 切换故障转移开关 const handleToggleFailover = (enabled: boolean) => { setFailoverEnabled.mutate({ appType, enabled }); }; // 添加供应商到队列 const handleAddProvider = async () => { if (!selectedProviderId) return; try { await addToQueue.mutateAsync({ appType, providerId: selectedProviderId, }); setSelectedProviderId(""); toast.success( t("proxy.failoverQueue.addSuccess", "已添加到故障转移队列"), { closeButton: true }, ); } catch (error) { toast.error( t("proxy.failoverQueue.addFailed", "添加失败") + ": " + String(error), ); } }; // 从队列移除供应商 const handleRemoveProvider = async (providerId: string) => { try { await removeFromQueue.mutateAsync({ appType, providerId }); toast.success( t("proxy.failoverQueue.removeSuccess", "已从故障转移队列移除"), { closeButton: true }, ); } catch (error) { toast.error( t("proxy.failoverQueue.removeFailed", "移除失败") + ": " + String(error), ); } }; if (isQueueLoading) { return (
); } if (queueError) { return ( {String(queueError)} ); } return (
{/* 自动故障转移开关 */}
{t("proxy.failover.autoSwitch", { defaultValue: "自动故障转移", })} {isFailoverEnabled && ( {t("common.enabled", { defaultValue: "已开启" })} )}

{t("proxy.failover.autoSwitchDescription", { defaultValue: "开启后将立即切换到队列 P1,并在请求失败时自动切换到队列中的下一个供应商", })}

{/* 说明信息 */} {t( "proxy.failoverQueue.info", "队列顺序与首页供应商列表顺序一致。当请求失败时,系统会按顺序依次尝试队列中的供应商。", )} {/* 添加供应商 */}
{/* 队列列表 */} {!queue || queue.length === 0 ? (

{t( "proxy.failoverQueue.empty", "故障转移队列为空。添加供应商以启用自动故障转移。", )}

) : (
{queue.map((item, index) => ( ))}
)} {/* 队列说明 */} {queue && queue.length > 0 && (

{t( "proxy.failoverQueue.orderHint", "队列顺序与首页供应商列表顺序一致,可在首页拖拽调整顺序。", )}

)}
); } interface QueueItemProps { item: FailoverQueueItem; index: number; disabled: boolean; onRemove: (providerId: string) => void; isRemoving: boolean; } function QueueItem({ item, index, disabled, onRemove, isRemoving, }: QueueItemProps) { const { t } = useTranslation(); return (
{/* 序号 */}
{index + 1}
{/* 供应商名称 */}
{item.providerName} {item.providerNotes && ( ({item.providerNotes}) )}
{/* 删除按钮 */}
); }