mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-27 08:14:33 +08:00
feat(failover): add auto-failover master switch with proxy integration (#427)
* feat(failover): add auto-failover master switch with proxy integration
- Add persistent auto_failover_enabled setting in database
- Add get/set_auto_failover_enabled commands
- Provider router respects master switch state
- Proxy shutdown automatically disables failover
- Enabling failover auto-starts proxy server
- Optimistic updates for failover queue toggle
* feat(proxy): persist proxy takeover state across app restarts
- Add proxy_takeover_{app_type} settings for per-app state tracking
- Restore proxy takeover state automatically on app startup
- Preserve state on normal exit, clear on manual stop
- Add stop_with_restore_keep_state method for graceful shutdown
* fix(proxy): set takeover state for all apps in start_with_takeover
This commit is contained in:
@@ -51,9 +51,7 @@ export function ModelTestConfigPanel() {
|
||||
closeButton: true,
|
||||
});
|
||||
} catch (e) {
|
||||
toast.error(
|
||||
t("streamCheck.configSaveFailed") + ": " + String(e),
|
||||
);
|
||||
toast.error(t("streamCheck.configSaveFailed") + ": " + String(e));
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
@@ -82,9 +80,7 @@ export function ModelTestConfigPanel() {
|
||||
</h4>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="claudeModel">
|
||||
{t("streamCheck.claudeModel")}
|
||||
</Label>
|
||||
<Label htmlFor="claudeModel">{t("streamCheck.claudeModel")}</Label>
|
||||
<Input
|
||||
id="claudeModel"
|
||||
value={config.claudeModel}
|
||||
@@ -96,9 +92,7 @@ export function ModelTestConfigPanel() {
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="codexModel">
|
||||
{t("streamCheck.codexModel")}
|
||||
</Label>
|
||||
<Label htmlFor="codexModel">{t("streamCheck.codexModel")}</Label>
|
||||
<Input
|
||||
id="codexModel"
|
||||
value={config.codexModel}
|
||||
@@ -110,9 +104,7 @@ export function ModelTestConfigPanel() {
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="geminiModel">
|
||||
{t("streamCheck.geminiModel")}
|
||||
</Label>
|
||||
<Label htmlFor="geminiModel">{t("streamCheck.geminiModel")}</Label>
|
||||
<Input
|
||||
id="geminiModel"
|
||||
value={config.geminiModel}
|
||||
@@ -132,9 +124,7 @@ export function ModelTestConfigPanel() {
|
||||
</h4>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="timeoutSecs">
|
||||
{t("streamCheck.timeout")}
|
||||
</Label>
|
||||
<Label htmlFor="timeoutSecs">{t("streamCheck.timeout")}</Label>
|
||||
<Input
|
||||
id="timeoutSecs"
|
||||
type="number"
|
||||
@@ -151,9 +141,7 @@ export function ModelTestConfigPanel() {
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="maxRetries">
|
||||
{t("streamCheck.maxRetries")}
|
||||
</Label>
|
||||
<Label htmlFor="maxRetries">{t("streamCheck.maxRetries")}</Label>
|
||||
<Input
|
||||
id="maxRetries"
|
||||
type="number"
|
||||
|
||||
@@ -93,8 +93,7 @@ export function PricingConfigPanel() {
|
||||
<CardContent>
|
||||
<Alert variant="destructive">
|
||||
<AlertDescription>
|
||||
{t("usage.loadPricingError")}:{" "}
|
||||
{String(error)}
|
||||
{t("usage.loadPricingError")}: {String(error)}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</CardContent>
|
||||
@@ -124,9 +123,7 @@ export function PricingConfigPanel() {
|
||||
<div className="space-y-4">
|
||||
{!pricing || pricing.length === 0 ? (
|
||||
<Alert>
|
||||
<AlertDescription>
|
||||
{t("usage.noPricingData")}
|
||||
</AlertDescription>
|
||||
<AlertDescription>{t("usage.noPricingData")}</AlertDescription>
|
||||
</Alert>
|
||||
) : (
|
||||
<div className="rounded-md bg-card/60 shadow-sm">
|
||||
@@ -220,9 +217,7 @@ export function PricingConfigPanel() {
|
||||
>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{t("usage.deleteConfirmTitle")}
|
||||
</DialogTitle>
|
||||
<DialogTitle>{t("usage.deleteConfirmTitle")}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{t("usage.deleteConfirmDesc")}
|
||||
</DialogDescription>
|
||||
|
||||
@@ -87,9 +87,7 @@ export function RequestLogTable() {
|
||||
<SelectValue placeholder={t("usage.appType")} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">
|
||||
{t("usage.allApps")}
|
||||
</SelectItem>
|
||||
<SelectItem value="all">{t("usage.allApps")}</SelectItem>
|
||||
<SelectItem value="claude">Claude</SelectItem>
|
||||
<SelectItem value="codex">Codex</SelectItem>
|
||||
<SelectItem value="gemini">Gemini</SelectItem>
|
||||
@@ -149,9 +147,7 @@ export function RequestLogTable() {
|
||||
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<span className="whitespace-nowrap">
|
||||
{t("usage.timeRange")}:
|
||||
</span>
|
||||
<span className="whitespace-nowrap">{t("usage.timeRange")}:</span>
|
||||
<Input
|
||||
type="datetime-local"
|
||||
className="h-8 w-[200px] bg-background"
|
||||
@@ -283,8 +279,7 @@ export function RequestLogTable() {
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{log.providerName ||
|
||||
t("usage.unknownProvider")}
|
||||
{log.providerName || t("usage.unknownProvider")}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
className="font-mono text-sm max-w-[280px] truncate"
|
||||
|
||||
@@ -26,9 +26,7 @@ export function UsageDashboard() {
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||
<div className="flex flex-col gap-1">
|
||||
<h2 className="text-2xl font-bold">{t("usage.title")}</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("usage.subtitle")}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">{t("usage.subtitle")}</p>
|
||||
</div>
|
||||
|
||||
<Tabs
|
||||
|
||||
@@ -56,7 +56,7 @@ export function UsageSummaryCards({ days }: UsageSummaryCardsProps) {
|
||||
color: "text-purple-500",
|
||||
bg: "bg-purple-500/10",
|
||||
subValue: (
|
||||
<div className="flex flex-col gap-1 text-xs text-muted-foreground mt-3 pt-3 border-t border-border/50">
|
||||
<div className="flex flex-col gap-1 text-xs text-muted-foreground mt-3 pt-3 border-t border-border/50">
|
||||
<div className="flex justify-between items-center">
|
||||
<span>{t("usage.input")}</span>
|
||||
<span className="text-foreground/80">
|
||||
@@ -78,21 +78,21 @@ export function UsageSummaryCards({ days }: UsageSummaryCardsProps) {
|
||||
icon: Database,
|
||||
color: "text-orange-500",
|
||||
bg: "bg-orange-500/10",
|
||||
subValue: (
|
||||
<div className="flex flex-col gap-1 text-xs text-muted-foreground mt-3 pt-3 border-t border-border/50">
|
||||
<div className="flex justify-between items-center">
|
||||
<span>{t("usage.cacheWrite")}</span>
|
||||
<span className="text-foreground/80">
|
||||
{(cacheWriteTokens / 1000).toFixed(1)}k
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span>{t("usage.cacheRead")}</span>
|
||||
<span className="text-foreground/80">
|
||||
{(cacheReadTokens / 1000).toFixed(1)}k
|
||||
</span>
|
||||
</div>
|
||||
subValue: (
|
||||
<div className="flex flex-col gap-1 text-xs text-muted-foreground mt-3 pt-3 border-t border-border/50">
|
||||
<div className="flex justify-between items-center">
|
||||
<span>{t("usage.cacheWrite")}</span>
|
||||
<span className="text-foreground/80">
|
||||
{(cacheWriteTokens / 1000).toFixed(1)}k
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span>{t("usage.cacheRead")}</span>
|
||||
<span className="text-foreground/80">
|
||||
{(cacheReadTokens / 1000).toFixed(1)}k
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user