feat: add proxy configuration support
- Add proxy settings UI component with enable/disable toggle - Support HTTP, HTTPS, NO_PROXY, and ALL_PROXY environment variables - Store proxy settings in app database for persistence - Apply proxy settings on app startup and when saved - Pass proxy environment variables to Claude command execution - Integrate proxy settings into main Settings page with unified save - Add proxy support for both system binary and sidecar execution This allows users to configure proxy settings for Claude API requests, which is essential for users behind corporate firewalls or in regions requiring proxy access. Fixes network connectivity issues in restricted environments.
This commit is contained in:
168
src/components/ProxySettings.tsx
Normal file
168
src/components/ProxySettings.tsx
Normal file
@@ -0,0 +1,168 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
|
||||
export interface ProxySettings {
|
||||
http_proxy: string | null;
|
||||
https_proxy: string | null;
|
||||
no_proxy: string | null;
|
||||
all_proxy: string | null;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
interface ProxySettingsProps {
|
||||
setToast: (toast: { message: string; type: 'success' | 'error' } | null) => void;
|
||||
onChange?: (hasChanges: boolean, getSettings: () => ProxySettings, saveSettings: () => Promise<void>) => void;
|
||||
}
|
||||
|
||||
export function ProxySettings({ setToast, onChange }: ProxySettingsProps) {
|
||||
const [settings, setSettings] = useState<ProxySettings>({
|
||||
http_proxy: null,
|
||||
https_proxy: null,
|
||||
no_proxy: null,
|
||||
all_proxy: null,
|
||||
enabled: false,
|
||||
});
|
||||
const [originalSettings, setOriginalSettings] = useState<ProxySettings>({
|
||||
http_proxy: null,
|
||||
https_proxy: null,
|
||||
no_proxy: null,
|
||||
all_proxy: null,
|
||||
enabled: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
loadSettings();
|
||||
}, []);
|
||||
|
||||
// Save settings function
|
||||
const saveSettings = async () => {
|
||||
try {
|
||||
await invoke('save_proxy_settings', { settings });
|
||||
setOriginalSettings(settings);
|
||||
setToast({
|
||||
message: 'Proxy settings saved and applied successfully.',
|
||||
type: 'success',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to save proxy settings:', error);
|
||||
setToast({
|
||||
message: 'Failed to save proxy settings',
|
||||
type: 'error',
|
||||
});
|
||||
throw error; // Re-throw to let parent handle the error
|
||||
}
|
||||
};
|
||||
|
||||
// Notify parent component of changes
|
||||
useEffect(() => {
|
||||
if (onChange) {
|
||||
const hasChanges = JSON.stringify(settings) !== JSON.stringify(originalSettings);
|
||||
onChange(hasChanges, () => settings, saveSettings);
|
||||
}
|
||||
}, [settings, originalSettings, onChange]);
|
||||
|
||||
const loadSettings = async () => {
|
||||
try {
|
||||
const loadedSettings = await invoke<ProxySettings>('get_proxy_settings');
|
||||
setSettings(loadedSettings);
|
||||
setOriginalSettings(loadedSettings);
|
||||
} catch (error) {
|
||||
console.error('Failed to load proxy settings:', error);
|
||||
setToast({
|
||||
message: 'Failed to load proxy settings',
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleInputChange = (field: keyof ProxySettings, value: string) => {
|
||||
setSettings(prev => ({
|
||||
...prev,
|
||||
[field]: value || null,
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">Proxy Settings</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Configure proxy settings for Claude API requests
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="proxy-enabled">Enable Proxy</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Use proxy for all Claude API requests
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="proxy-enabled"
|
||||
checked={settings.enabled}
|
||||
onCheckedChange={(checked) => setSettings(prev => ({ ...prev, enabled: checked }))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4" style={{ opacity: settings.enabled ? 1 : 0.5 }}>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="http-proxy">HTTP Proxy</Label>
|
||||
<Input
|
||||
id="http-proxy"
|
||||
placeholder="http://proxy.example.com:8080"
|
||||
value={settings.http_proxy || ''}
|
||||
onChange={(e) => handleInputChange('http_proxy', e.target.value)}
|
||||
disabled={!settings.enabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="https-proxy">HTTPS Proxy</Label>
|
||||
<Input
|
||||
id="https-proxy"
|
||||
placeholder="http://proxy.example.com:8080"
|
||||
value={settings.https_proxy || ''}
|
||||
onChange={(e) => handleInputChange('https_proxy', e.target.value)}
|
||||
disabled={!settings.enabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="no-proxy">No Proxy</Label>
|
||||
<Input
|
||||
id="no-proxy"
|
||||
placeholder="localhost,127.0.0.1,.example.com"
|
||||
value={settings.no_proxy || ''}
|
||||
onChange={(e) => handleInputChange('no_proxy', e.target.value)}
|
||||
disabled={!settings.enabled}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Comma-separated list of hosts that should bypass the proxy
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="all-proxy">All Proxy (Optional)</Label>
|
||||
<Input
|
||||
id="all-proxy"
|
||||
placeholder="socks5://proxy.example.com:1080"
|
||||
value={settings.all_proxy || ''}
|
||||
onChange={(e) => handleInputChange('all_proxy', e.target.value)}
|
||||
disabled={!settings.enabled}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Proxy URL to use for all protocols if protocol-specific proxies are not set
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@@ -26,6 +26,7 @@ import { ClaudeVersionSelector } from "./ClaudeVersionSelector";
|
||||
import { StorageTab } from "./StorageTab";
|
||||
import { HooksEditor } from "./HooksEditor";
|
||||
import { SlashCommandsManager } from "./SlashCommandsManager";
|
||||
import { ProxySettings } from "./ProxySettings";
|
||||
import { useTheme } from "@/hooks";
|
||||
|
||||
interface SettingsProps {
|
||||
@@ -82,6 +83,10 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
// Theme hook
|
||||
const { theme, setTheme, customColors, setCustomColors } = useTheme();
|
||||
|
||||
// Proxy state
|
||||
const [proxySettingsChanged, setProxySettingsChanged] = useState(false);
|
||||
const saveProxySettings = React.useRef<(() => Promise<void>) | null>(null);
|
||||
|
||||
// Load settings on mount
|
||||
useEffect(() => {
|
||||
loadSettings();
|
||||
@@ -198,6 +203,12 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
setUserHooksChanged(false);
|
||||
}
|
||||
|
||||
// Save proxy settings if changed
|
||||
if (proxySettingsChanged && saveProxySettings.current) {
|
||||
await saveProxySettings.current();
|
||||
setProxySettingsChanged(false);
|
||||
}
|
||||
|
||||
setToast({ message: "Settings saved successfully!", type: "success" });
|
||||
} catch (err) {
|
||||
console.error("Failed to save settings:", err);
|
||||
@@ -363,7 +374,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
) : (
|
||||
<div className="flex-1 overflow-y-auto p-4">
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
||||
<TabsList className="grid grid-cols-7 w-full">
|
||||
<TabsList className="grid grid-cols-8 w-full">
|
||||
<TabsTrigger value="general">General</TabsTrigger>
|
||||
<TabsTrigger value="permissions">Permissions</TabsTrigger>
|
||||
<TabsTrigger value="environment">Environment</TabsTrigger>
|
||||
@@ -371,6 +382,7 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
<TabsTrigger value="hooks">Hooks</TabsTrigger>
|
||||
<TabsTrigger value="commands">Commands</TabsTrigger>
|
||||
<TabsTrigger value="storage">Storage</TabsTrigger>
|
||||
<TabsTrigger value="proxy">Proxy</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* General Settings */}
|
||||
@@ -872,6 +884,19 @@ export const Settings: React.FC<SettingsProps> = ({
|
||||
<TabsContent value="storage">
|
||||
<StorageTab />
|
||||
</TabsContent>
|
||||
|
||||
{/* Proxy Settings */}
|
||||
<TabsContent value="proxy">
|
||||
<Card className="p-6">
|
||||
<ProxySettings
|
||||
setToast={setToast}
|
||||
onChange={(hasChanges, _getSettings, save) => {
|
||||
setProxySettingsChanged(hasChanges);
|
||||
saveProxySettings.current = save;
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
)}
|
||||
|
Reference in New Issue
Block a user