mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-31 11:01:36 +08:00
b642ef0633
- Forwarder buffers non-streaming bodies and primes streaming first chunk before signaling success, so body timeouts and SSE first-chunk failures route through the circuit breaker instead of being recorded as success on response-header arrival - Atomic enable-failover: switch to P1 before persisting the flag, and roll back auto-added queue entries when the switch is rejected (e.g. official providers) - Hot-reload circuit breaker config on per-app proxy config change instead of waiting for a proxy restart - FailoverToggle / FailoverQueueManager / AutoFailoverConfigPanel require proxy takeover for the active app; the backend command also rejects enabling when takeover is off - ProviderHealthBadge consumes the backend is_healthy flag instead of hardcoding the 5-failure threshold Cleanup: - impl From<&AppProxyConfig> for CircuitBreakerConfig and use it from the command layer - Collapse three identical TabsContent blocks into a single map
78 lines
2.1 KiB
TypeScript
78 lines
2.1 KiB
TypeScript
import { cn } from "@/lib/utils";
|
|
import { ProviderHealthStatus } from "@/types/proxy";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
interface ProviderHealthBadgeProps {
|
|
consecutiveFailures: number;
|
|
isHealthy?: boolean;
|
|
className?: string;
|
|
}
|
|
|
|
/**
|
|
* 供应商健康状态徽章
|
|
* 根据连续失败次数显示不同颜色的状态指示器
|
|
*/
|
|
export function ProviderHealthBadge({
|
|
consecutiveFailures,
|
|
isHealthy,
|
|
className,
|
|
}: ProviderHealthBadgeProps) {
|
|
const { t } = useTranslation();
|
|
|
|
// 根据失败次数计算状态
|
|
const getStatus = () => {
|
|
if (consecutiveFailures === 0) {
|
|
return {
|
|
labelKey: "health.operational",
|
|
labelFallback: "正常",
|
|
status: ProviderHealthStatus.Healthy,
|
|
color: "bg-green-500",
|
|
// 使用更深/柔和的背景色,去除可能的白色内容感
|
|
bgColor: "bg-green-500/10",
|
|
textColor: "text-green-600 dark:text-green-400",
|
|
};
|
|
} else if (isHealthy !== false) {
|
|
return {
|
|
labelKey: "health.degraded",
|
|
labelFallback: "降级",
|
|
status: ProviderHealthStatus.Degraded,
|
|
color: "bg-yellow-500",
|
|
bgColor: "bg-yellow-500/10",
|
|
textColor: "text-yellow-600 dark:text-yellow-400",
|
|
};
|
|
} else {
|
|
return {
|
|
labelKey: "health.circuitOpen",
|
|
labelFallback: "熔断",
|
|
status: ProviderHealthStatus.Failed,
|
|
color: "bg-red-500",
|
|
bgColor: "bg-red-500/10",
|
|
textColor: "text-red-600 dark:text-red-400",
|
|
};
|
|
}
|
|
};
|
|
|
|
const statusConfig = getStatus();
|
|
const label = t(statusConfig.labelKey, {
|
|
defaultValue: statusConfig.labelFallback,
|
|
});
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"inline-flex items-center gap-1.5 px-2 py-1 rounded-full text-xs font-medium",
|
|
statusConfig.bgColor,
|
|
statusConfig.textColor,
|
|
className,
|
|
)}
|
|
title={t("health.consecutiveFailures", {
|
|
count: consecutiveFailures,
|
|
defaultValue: `连续失败 ${consecutiveFailures} 次`,
|
|
})}
|
|
>
|
|
<div className={cn("w-2 h-2 rounded-full", statusConfig.color)} />
|
|
<span>{label}</span>
|
|
</div>
|
|
);
|
|
}
|