feat(proxy): implement independent failover queue management

Add a new failover queue system that operates independently from provider
sortIndex, allowing users to configure failover order per app type.

Backend changes:
- Add failover_queue table to schema.rs for persistent storage
- Create dao/failover.rs with CRUD operations for queue management
- Add Tauri commands for queue operations (get, add, remove, reorder, toggle)
- Refactor provider_router.rs select_providers() to use failover queue:
  - Current provider always takes first priority
  - Queue providers ordered by queue_order as fallback
  - Only providers with open circuit breakers are included

Frontend changes:
- Add FailoverQueueItem type to proxy.ts
- Extend failover.ts API with queue management methods
- Add React Query hooks for queue data fetching and mutations
- Create FailoverQueueManager component with drag-and-drop reordering
- Integrate queue management into SettingsPage under "Auto Failover"
- Add i18n translations for zh and en locales
This commit is contained in:
Jason
2025-12-12 16:13:07 +08:00
parent c42a0dccaf
commit 5d424b1383
15 changed files with 1198 additions and 47 deletions
+137 -13
View File
@@ -1,6 +1,8 @@
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { failoverApi } from "@/lib/api/failover";
// ========== 旧版代理目标 Hooks(保留向后兼容)==========
/**
* 获取代理目标列表
*/
@@ -12,19 +14,6 @@ export function useProxyTargets(appType: string) {
});
}
/**
* 获取供应商健康状态
*/
export function useProviderHealth(providerId: string, appType: string) {
return useQuery({
queryKey: ["providerHealth", providerId, appType],
queryFn: () => failoverApi.getProviderHealth(providerId, appType),
enabled: !!providerId && !!appType,
refetchInterval: 5000, // 每 5 秒刷新一次
retry: false,
});
}
/**
* 设置代理目标
*/
@@ -56,6 +45,21 @@ export function useSetProxyTarget() {
});
}
// ========== 熔断器 Hooks ==========
/**
* 获取供应商健康状态
*/
export function useProviderHealth(providerId: string, appType: string) {
return useQuery({
queryKey: ["providerHealth", providerId, appType],
queryFn: () => failoverApi.getProviderHealth(providerId, appType),
enabled: !!providerId && !!appType,
refetchInterval: 5000, // 每 5 秒刷新一次
retry: false,
});
}
/**
* 重置熔断器
*/
@@ -114,3 +118,123 @@ export function useCircuitBreakerStats(providerId: string, appType: string) {
refetchInterval: 5000, // 每 5 秒刷新一次
});
}
// ========== 故障转移队列 Hooks(新) ==========
/**
* 获取故障转移队列
*/
export function useFailoverQueue(appType: string) {
return useQuery({
queryKey: ["failoverQueue", appType],
queryFn: () => failoverApi.getFailoverQueue(appType),
enabled: !!appType,
});
}
/**
* 获取可添加到队列的供应商
*/
export function useAvailableProvidersForFailover(appType: string) {
return useQuery({
queryKey: ["availableProvidersForFailover", appType],
queryFn: () => failoverApi.getAvailableProvidersForFailover(appType),
enabled: !!appType,
});
}
/**
* 添加供应商到故障转移队列
*/
export function useAddToFailoverQueue() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
appType,
providerId,
}: {
appType: string;
providerId: string;
}) => failoverApi.addToFailoverQueue(appType, providerId),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({
queryKey: ["failoverQueue", variables.appType],
});
queryClient.invalidateQueries({
queryKey: ["availableProvidersForFailover", variables.appType],
});
},
});
}
/**
* 从故障转移队列移除供应商
*/
export function useRemoveFromFailoverQueue() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
appType,
providerId,
}: {
appType: string;
providerId: string;
}) => failoverApi.removeFromFailoverQueue(appType, providerId),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({
queryKey: ["failoverQueue", variables.appType],
});
queryClient.invalidateQueries({
queryKey: ["availableProvidersForFailover", variables.appType],
});
},
});
}
/**
* 重新排序故障转移队列
*/
export function useReorderFailoverQueue() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
appType,
providerIds,
}: {
appType: string;
providerIds: string[];
}) => failoverApi.reorderFailoverQueue(appType, providerIds),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({
queryKey: ["failoverQueue", variables.appType],
});
},
});
}
/**
* 设置故障转移队列项的启用状态
*/
export function useSetFailoverItemEnabled() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
appType,
providerId,
enabled,
}: {
appType: string;
providerId: string;
enabled: boolean;
}) => failoverApi.setFailoverItemEnabled(appType, providerId, enabled),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({
queryKey: ["failoverQueue", variables.appType],
});
},
});
}