mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-06-16 13:34:04 +08:00
* feat(db): add circuit breaker config table and provider proxy target APIs Add database support for auto-failover feature: - Add circuit_breaker_config table for storing failover thresholds - Add get/update_circuit_breaker_config methods in proxy DAO - Add reset_provider_health method for manual recovery - Add set_proxy_target and get_proxy_targets methods in providers DAO for managing multi-provider failover configuration * feat(proxy): implement circuit breaker and provider router for auto-failover Add core failover logic: - CircuitBreaker: Tracks provider health with three states: - Closed: Normal operation, requests pass through - Open: Circuit broken after consecutive failures, skip provider - HalfOpen: Testing recovery with limited requests - ProviderRouter: Routes requests across multiple providers with: - Health tracking and automatic failover - Configurable failure/success thresholds - Auto-disable proxy target after reaching failure threshold - Support for manual circuit breaker reset - Export new types in proxy module * feat(proxy): add failover Tauri commands and integrate with forwarder Expose failover functionality to frontend: - Add Tauri commands: get_proxy_targets, set_proxy_target, get_provider_health, reset_circuit_breaker, get/update_circuit_breaker_config, get_circuit_breaker_stats - Register all new commands in lib.rs invoke handler - Update forwarder with improved error handling and logging - Integrate ProviderRouter with proxy server startup - Add provider health tracking in request handlers * 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 * feat(ui): add auto-failover configuration UI and provider health display Add comprehensive UI for failover management: Components: - ProviderHealthBadge: Display provider health status with color coding - CircuitBreakerConfigPanel: Configure failure/success thresholds, timeout duration, and error rate limits - AutoFailoverConfigPanel: Manage proxy targets with drag-and-drop priority ordering and individual enable/disable controls - ProxyPanel: Integrate failover tabs for unified proxy management Provider enhancements: - ProviderCard: Show health badge and proxy target indicator - ProviderActions: Add "Set as Proxy Target" action - EditProviderDialog: Add is_proxy_target toggle - ProviderList: Support proxy target filtering mode Other: - Update App.tsx routing for settings integration - Update useProviderActions hook with proxy target mutation - Fix ProviderList tests for updated component API * fix(usage): stabilize date range to prevent infinite re-renders * feat(backend): add tool version check command Add get_tool_versions command to check local and latest versions of Claude, Codex, and Gemini CLI tools: - Detect local installed versions via command line execution - Fetch latest versions from npm registry (Claude, Gemini) and GitHub releases API (Codex) - Return comprehensive version info including error details for uninstalled tools - Register command in Tauri invoke handler * style(ui): format accordion component code style Apply consistent code formatting to accordion component: - Convert double quotes to semicolons at line endings - Adjust indentation to 2-space standard - Align with project code style conventions * refactor(providers): update provider card styling to use theme tokens Replace hardcoded color classes with semantic design tokens: - Use bg-card, border-border, text-card-foreground instead of glass-card - Replace gray/white color literals with muted/foreground tokens - Change proxy target indicator color from purple to green - Improve hover states with border-border-active - Ensure consistent dark mode support via CSS variables * refactor(proxy): simplify auto-failover config panel structure Restructure AutoFailoverConfigPanel for better integration: - Remove internal Card wrapper and expansion toggle (now handled by parent) - Extract enabled state to props for external control - Simplify loading state display - Clean up redundant CardHeader/CardContent wrappers - ProxyPanel: reduce complexity by delegating to parent components * feat(settings): enhance settings page with accordion layout and tool versions Major settings page improvements: AboutSection: - Add local tool version detection (Claude, Codex, Gemini) - Display installed vs latest version comparison with visual indicators - Show update availability badges and environment check cards SettingsPage: - Reorganize advanced settings into collapsible accordion sections - Add proxy control panel with inline status toggle - Integrate auto-failover configuration with accordion UI - Add database and cost calculation config sections DirectorySettings & WindowSettings: - Minor styling adjustments for consistency settings.ts API: - Add getToolVersions() wrapper for new backend command * refactor(usage): restructure usage dashboard components Comprehensive usage statistics panel refactoring: UsageDashboard: - Reorganize layout with improved section headers - Add better loading states and empty state handling ModelStatsTable & ProviderStatsTable: - Minor styling updates for consistency ModelTestConfigPanel & PricingConfigPanel: - Simplify component structure - Remove redundant Card wrappers - Improve form field organization RequestLogTable: - Enhance table layout with better column sizing - Improve pagination controls UsageSummaryCards: - Update card styling with semantic tokens - Better responsive grid layout UsageTrendChart: - Refine chart container styling - Improve legend and tooltip display * chore(deps): add accordion and animation dependencies Package updates: - Add @radix-ui/react-accordion for collapsible sections - Add cmdk for command palette support - Add framer-motion for enhanced animations Tailwind config: - Add accordion-up/accordion-down animations - Update darkMode config to support both selector and class - Reorganize color and keyframe definitions for clarity * style(app): update header and app switcher styling App.tsx: - Replace glass-header with explicit bg-background/80 backdrop-blur - Update navigation button container to use bg-muted AppSwitcher: - Replace hardcoded gray colors with semantic muted/foreground tokens - Ensure consistent dark mode support via CSS variables - Add group class for better hover state transitions
171 lines
5.5 KiB
Rust
171 lines
5.5 KiB
Rust
//! HTTP代理服务器
|
|
//!
|
|
//! 基于Axum的HTTP服务器,处理代理请求
|
|
|
|
use super::{handlers, types::*, ProxyError};
|
|
use crate::database::Database;
|
|
use axum::{
|
|
routing::{get, post},
|
|
Router,
|
|
};
|
|
use std::net::SocketAddr;
|
|
use std::sync::Arc;
|
|
use tokio::sync::{oneshot, RwLock};
|
|
use tower_http::cors::{Any, CorsLayer};
|
|
|
|
/// 代理服务器状态(共享)
|
|
#[derive(Clone)]
|
|
pub struct ProxyState {
|
|
pub db: Arc<Database>,
|
|
pub config: Arc<RwLock<ProxyConfig>>,
|
|
pub status: Arc<RwLock<ProxyStatus>>,
|
|
pub start_time: Arc<RwLock<Option<std::time::Instant>>>,
|
|
/// 每个应用类型当前使用的 provider (app_type -> (provider_id, provider_name))
|
|
pub current_providers: Arc<RwLock<std::collections::HashMap<String, (String, String)>>>,
|
|
}
|
|
|
|
/// 代理HTTP服务器
|
|
pub struct ProxyServer {
|
|
config: ProxyConfig,
|
|
state: ProxyState,
|
|
shutdown_tx: Arc<RwLock<Option<oneshot::Sender<()>>>>,
|
|
}
|
|
|
|
impl ProxyServer {
|
|
pub fn new(config: ProxyConfig, db: Arc<Database>) -> Self {
|
|
let state = ProxyState {
|
|
db,
|
|
config: Arc::new(RwLock::new(config.clone())),
|
|
status: Arc::new(RwLock::new(ProxyStatus::default())),
|
|
start_time: Arc::new(RwLock::new(None)),
|
|
current_providers: Arc::new(RwLock::new(std::collections::HashMap::new())),
|
|
};
|
|
|
|
Self {
|
|
config,
|
|
state,
|
|
shutdown_tx: Arc::new(RwLock::new(None)),
|
|
}
|
|
}
|
|
|
|
pub async fn start(&self) -> Result<ProxyServerInfo, ProxyError> {
|
|
// 检查是否已在运行
|
|
if self.shutdown_tx.read().await.is_some() {
|
|
return Err(ProxyError::AlreadyRunning);
|
|
}
|
|
|
|
let addr: SocketAddr =
|
|
format!("{}:{}", self.config.listen_address, self.config.listen_port)
|
|
.parse()
|
|
.map_err(|e| ProxyError::BindFailed(format!("无效的地址: {e}")))?;
|
|
|
|
// 创建关闭通道
|
|
let (shutdown_tx, shutdown_rx) = oneshot::channel();
|
|
|
|
// 构建路由
|
|
let app = self.build_router();
|
|
|
|
// 绑定监听器
|
|
let listener = tokio::net::TcpListener::bind(&addr)
|
|
.await
|
|
.map_err(|e| ProxyError::BindFailed(e.to_string()))?;
|
|
|
|
log::info!("代理服务器启动于 {addr}");
|
|
|
|
// 保存关闭句柄
|
|
*self.shutdown_tx.write().await = Some(shutdown_tx);
|
|
|
|
// 更新状态
|
|
let mut status = self.state.status.write().await;
|
|
status.running = true;
|
|
status.address = self.config.listen_address.clone();
|
|
status.port = self.config.listen_port;
|
|
drop(status);
|
|
|
|
// 记录启动时间
|
|
*self.state.start_time.write().await = Some(std::time::Instant::now());
|
|
|
|
// 启动服务器
|
|
let state = self.state.clone();
|
|
tokio::spawn(async move {
|
|
axum::serve(listener, app)
|
|
.with_graceful_shutdown(async {
|
|
shutdown_rx.await.ok();
|
|
})
|
|
.await
|
|
.ok();
|
|
|
|
// 服务器停止后更新状态
|
|
state.status.write().await.running = false;
|
|
*state.start_time.write().await = None;
|
|
});
|
|
|
|
Ok(ProxyServerInfo {
|
|
address: self.config.listen_address.clone(),
|
|
port: self.config.listen_port,
|
|
started_at: chrono::Utc::now().to_rfc3339(),
|
|
})
|
|
}
|
|
|
|
pub async fn stop(&self) -> Result<(), ProxyError> {
|
|
if let Some(tx) = self.shutdown_tx.write().await.take() {
|
|
let _ = tx.send(());
|
|
Ok(())
|
|
} else {
|
|
Err(ProxyError::NotRunning)
|
|
}
|
|
}
|
|
|
|
pub async fn get_status(&self) -> ProxyStatus {
|
|
let mut status = self.state.status.read().await.clone();
|
|
|
|
// 计算运行时间
|
|
if let Some(start) = *self.state.start_time.read().await {
|
|
status.uptime_seconds = start.elapsed().as_secs();
|
|
}
|
|
|
|
// 从 current_providers HashMap 获取每个应用类型当前正在使用的 provider
|
|
let current_providers = self.state.current_providers.read().await;
|
|
status.active_targets = current_providers
|
|
.iter()
|
|
.map(|(app_type, (provider_id, provider_name))| ActiveTarget {
|
|
app_type: app_type.clone(),
|
|
provider_id: provider_id.clone(),
|
|
provider_name: provider_name.clone(),
|
|
})
|
|
.collect();
|
|
|
|
status
|
|
}
|
|
|
|
fn build_router(&self) -> Router {
|
|
let cors = CorsLayer::new()
|
|
.allow_origin(Any)
|
|
.allow_methods(Any)
|
|
.allow_headers(Any);
|
|
|
|
Router::new()
|
|
// 健康检查
|
|
.route("/health", get(handlers::health_check))
|
|
.route("/status", get(handlers::get_status))
|
|
// Claude API
|
|
.route("/v1/messages", post(handlers::handle_messages))
|
|
// OpenAI Chat Completions API (Codex CLI)
|
|
.route(
|
|
"/v1/chat/completions",
|
|
post(handlers::handle_chat_completions),
|
|
)
|
|
// OpenAI Responses API (Codex CLI)
|
|
.route("/v1/responses", post(handlers::handle_responses))
|
|
// Gemini API
|
|
.route("/v1beta/*path", post(handlers::handle_gemini))
|
|
.layer(cors)
|
|
.with_state(self.state.clone())
|
|
}
|
|
|
|
/// 在不重启服务的情况下更新运行时配置
|
|
pub async fn apply_runtime_config(&self, config: &ProxyConfig) {
|
|
*self.state.config.write().await = config.clone();
|
|
}
|
|
}
|