mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-28 00:35:32 +08:00
feat(frontend): add failover API layer and TanStack Query hooks
Add frontend data layer for failover management: - Add failover.ts API: Tauri invoke wrappers for all failover commands - Add failover.ts query hooks: TanStack Query mutations and queries - useProxyTargets, useProviderHealth queries - useSetProxyTarget, useResetCircuitBreaker mutations - useCircuitBreakerConfig query and mutation - Update queries.ts with provider health query key - Update mutations.ts to invalidate health on provider changes - Add CircuitBreakerConfig and ProviderHealth types
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import type {
|
||||
ProviderHealth,
|
||||
CircuitBreakerConfig,
|
||||
CircuitBreakerStats,
|
||||
} from "@/types/proxy";
|
||||
|
||||
export interface Provider {
|
||||
id: string;
|
||||
name: string;
|
||||
settingsConfig: unknown;
|
||||
websiteUrl?: string;
|
||||
category?: string;
|
||||
createdAt?: number;
|
||||
sortIndex?: number;
|
||||
notes?: string;
|
||||
meta?: unknown;
|
||||
icon?: string;
|
||||
iconColor?: string;
|
||||
isProxyTarget?: boolean;
|
||||
}
|
||||
|
||||
export const failoverApi = {
|
||||
// 获取代理目标列表
|
||||
async getProxyTargets(appType: string): Promise<Provider[]> {
|
||||
return invoke("get_proxy_targets", { appType });
|
||||
},
|
||||
|
||||
// 设置代理目标
|
||||
async setProxyTarget(
|
||||
providerId: string,
|
||||
appType: string,
|
||||
enabled: boolean,
|
||||
): Promise<void> {
|
||||
return invoke("set_proxy_target", { providerId, appType, enabled });
|
||||
},
|
||||
|
||||
// 获取供应商健康状态
|
||||
async getProviderHealth(
|
||||
providerId: string,
|
||||
appType: string,
|
||||
): Promise<ProviderHealth> {
|
||||
return invoke("get_provider_health", { providerId, appType });
|
||||
},
|
||||
|
||||
// 重置熔断器
|
||||
async resetCircuitBreaker(
|
||||
providerId: string,
|
||||
appType: string,
|
||||
): Promise<void> {
|
||||
return invoke("reset_circuit_breaker", { providerId, appType });
|
||||
},
|
||||
|
||||
// 获取熔断器配置
|
||||
async getCircuitBreakerConfig(): Promise<CircuitBreakerConfig> {
|
||||
return invoke("get_circuit_breaker_config");
|
||||
},
|
||||
|
||||
// 更新熔断器配置
|
||||
async updateCircuitBreakerConfig(
|
||||
config: CircuitBreakerConfig,
|
||||
): Promise<void> {
|
||||
return invoke("update_circuit_breaker_config", { config });
|
||||
},
|
||||
|
||||
// 获取熔断器统计信息
|
||||
async getCircuitBreakerStats(
|
||||
providerId: string,
|
||||
appType: string,
|
||||
): Promise<CircuitBreakerStats | null> {
|
||||
return invoke("get_circuit_breaker_stats", { providerId, appType });
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,116 @@
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { failoverApi } from "@/lib/api/failover";
|
||||
|
||||
/**
|
||||
* 获取代理目标列表
|
||||
*/
|
||||
export function useProxyTargets(appType: string) {
|
||||
return useQuery({
|
||||
queryKey: ["proxyTargets", appType],
|
||||
queryFn: () => failoverApi.getProxyTargets(appType),
|
||||
enabled: !!appType,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取供应商健康状态
|
||||
*/
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置代理目标
|
||||
*/
|
||||
export function useSetProxyTarget() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
providerId,
|
||||
appType,
|
||||
enabled,
|
||||
}: {
|
||||
providerId: string;
|
||||
appType: string;
|
||||
enabled: boolean;
|
||||
}) => failoverApi.setProxyTarget(providerId, appType, enabled),
|
||||
onSuccess: (_, variables) => {
|
||||
// 刷新代理目标列表
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["proxyTargets", variables.appType],
|
||||
});
|
||||
// 刷新供应商列表
|
||||
queryClient.invalidateQueries({ queryKey: ["providers"] });
|
||||
// 刷新健康状态
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["providerHealth", variables.providerId, variables.appType],
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置熔断器
|
||||
*/
|
||||
export function useResetCircuitBreaker() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
providerId,
|
||||
appType,
|
||||
}: {
|
||||
providerId: string;
|
||||
appType: string;
|
||||
}) => failoverApi.resetCircuitBreaker(providerId, appType),
|
||||
onSuccess: (_, variables) => {
|
||||
// 刷新健康状态
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["providerHealth", variables.providerId, variables.appType],
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取熔断器配置
|
||||
*/
|
||||
export function useCircuitBreakerConfig() {
|
||||
return useQuery({
|
||||
queryKey: ["circuitBreakerConfig"],
|
||||
queryFn: () => failoverApi.getCircuitBreakerConfig(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新熔断器配置
|
||||
*/
|
||||
export function useUpdateCircuitBreakerConfig() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: failoverApi.updateCircuitBreakerConfig,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["circuitBreakerConfig"] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取熔断器统计信息
|
||||
*/
|
||||
export function useCircuitBreakerStats(providerId: string, appType: string) {
|
||||
return useQuery({
|
||||
queryKey: ["circuitBreakerStats", providerId, appType],
|
||||
queryFn: () => failoverApi.getCircuitBreakerStats(providerId, appType),
|
||||
enabled: !!providerId && !!appType,
|
||||
refetchInterval: 5000, // 每 5 秒刷新一次
|
||||
});
|
||||
}
|
||||
+16
-12
@@ -36,9 +36,10 @@ export const useAddProviderMutation = (appId: AppId) => {
|
||||
toast.success(
|
||||
t("notifications.providerAdded", {
|
||||
defaultValue: "供应商已添加",
|
||||
}), {
|
||||
closeButton: true
|
||||
}
|
||||
}),
|
||||
{
|
||||
closeButton: true,
|
||||
},
|
||||
);
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
@@ -66,9 +67,10 @@ export const useUpdateProviderMutation = (appId: AppId) => {
|
||||
toast.success(
|
||||
t("notifications.updateSuccess", {
|
||||
defaultValue: "供应商更新成功",
|
||||
}), {
|
||||
closeButton: true
|
||||
}
|
||||
}),
|
||||
{
|
||||
closeButton: true,
|
||||
},
|
||||
);
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
@@ -106,9 +108,10 @@ export const useDeleteProviderMutation = (appId: AppId) => {
|
||||
toast.success(
|
||||
t("notifications.deleteSuccess", {
|
||||
defaultValue: "供应商已删除",
|
||||
}), {
|
||||
closeButton: true
|
||||
}
|
||||
}),
|
||||
{
|
||||
closeButton: true,
|
||||
},
|
||||
);
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
@@ -147,9 +150,10 @@ export const useSwitchProviderMutation = (appId: AppId) => {
|
||||
t("notifications.switchSuccess", {
|
||||
defaultValue: "切换供应商成功",
|
||||
appName: t(`apps.${appId}`, { defaultValue: appId }),
|
||||
}), {
|
||||
closeButton: true
|
||||
}
|
||||
}),
|
||||
{
|
||||
closeButton: true,
|
||||
},
|
||||
);
|
||||
},
|
||||
onError: (error: Error) => {
|
||||
|
||||
@@ -34,12 +34,22 @@ export interface ProvidersQueryData {
|
||||
currentProviderId: string;
|
||||
}
|
||||
|
||||
export interface UseProvidersQueryOptions {
|
||||
isProxyRunning?: boolean; // 代理服务是否运行中
|
||||
}
|
||||
|
||||
export const useProvidersQuery = (
|
||||
appId: AppId,
|
||||
options?: UseProvidersQueryOptions,
|
||||
): UseQueryResult<ProvidersQueryData> => {
|
||||
const { isProxyRunning = false } = options || {};
|
||||
|
||||
return useQuery({
|
||||
queryKey: ["providers", appId],
|
||||
placeholderData: keepPreviousData,
|
||||
// 当代理服务运行时,每 10 秒刷新一次供应商列表
|
||||
// 这样可以自动反映后端熔断器自动禁用代理目标的变更
|
||||
refetchInterval: isProxyRunning ? 10000 : false,
|
||||
queryFn: async () => {
|
||||
let providers: Record<string, Provider> = {};
|
||||
let currentProviderId = "";
|
||||
|
||||
@@ -48,6 +48,39 @@ export interface ProviderHealth {
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
// 熔断器相关类型
|
||||
export interface CircuitBreakerConfig {
|
||||
failureThreshold: number;
|
||||
successThreshold: number;
|
||||
timeoutSeconds: number;
|
||||
errorRateThreshold: number;
|
||||
minRequests: number;
|
||||
}
|
||||
|
||||
export type CircuitState = "closed" | "open" | "half_open";
|
||||
|
||||
export interface CircuitBreakerStats {
|
||||
state: CircuitState;
|
||||
consecutiveFailures: number;
|
||||
consecutiveSuccesses: number;
|
||||
totalRequests: number;
|
||||
failedRequests: number;
|
||||
}
|
||||
|
||||
// 供应商健康状态枚举
|
||||
export enum ProviderHealthStatus {
|
||||
Healthy = "healthy",
|
||||
Degraded = "degraded",
|
||||
Failed = "failed",
|
||||
Unknown = "unknown",
|
||||
}
|
||||
|
||||
// 扩展 ProviderHealth 以包含前端计算的状态
|
||||
export interface ProviderHealthWithStatus extends ProviderHealth {
|
||||
status: ProviderHealthStatus;
|
||||
circuitState?: CircuitState;
|
||||
}
|
||||
|
||||
export interface ProxyUsageRecord {
|
||||
provider_id: string;
|
||||
app_type: string;
|
||||
|
||||
Reference in New Issue
Block a user