Spaces:
Paused
Paused
File size: 4,791 Bytes
a5784e9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | /**
* Port Configuration Component
* Configure service ports (changes require restart)
*/
import { useState, useEffect } from 'react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { Save, AlertTriangle, Loader2 } from 'lucide-react';
import { useI18n } from '@/contexts';
import { fetchPortConfig, updatePortConfig } from '@/api';
import type { PortConfig } from '@/api';
import styles from './SettingsPanel.module.css';
export function PortConfiguration() {
const { t } = useI18n();
const queryClient = useQueryClient();
const [localConfig, setLocalConfig] = useState<PortConfig>({
fastapi_port: 2048,
camoufox_debug_port: 9222,
stream_proxy_port: 3120,
stream_proxy_enabled: true,
});
const [hasChanges, setHasChanges] = useState(false);
// Fetch current config
const { data: config, isLoading } = useQuery({
queryKey: ['portConfig'],
queryFn: fetchPortConfig,
});
// Update local state when config loads
useEffect(() => {
if (config) {
setLocalConfig(config);
setHasChanges(false);
}
}, [config]);
// Update config mutation
const updateMutation = useMutation({
mutationFn: updatePortConfig,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['portConfig'] });
setHasChanges(false);
},
});
const handleChange = (field: keyof PortConfig, value: number | boolean) => {
setLocalConfig((prev) => ({ ...prev, [field]: value }));
setHasChanges(true);
};
const handleSave = () => {
updateMutation.mutate(localConfig);
};
const validatePort = (port: number): boolean => {
return port >= 1024 && port <= 65535;
};
if (isLoading) {
return (
<div className={styles.loading}>
<Loader2 size={16} className={styles.spinning} />
<span>{t.common.loading}</span>
</div>
);
}
return (
<div className={styles.portConfig}>
{/* Restart Warning */}
{hasChanges && (
<div className={`${styles.alertBox} ${styles.warning}`}>
<AlertTriangle size={16} />
<span>{t.settingsPage.restartWarning}</span>
</div>
)}
{/* FastAPI Port */}
<div className={styles.formGroup}>
<label className={styles.label}>{t.settingsPage.fastapiPort}</label>
<input
type="number"
className={styles.numberInput}
value={localConfig.fastapi_port}
min={1024}
max={65535}
onChange={(e) => handleChange('fastapi_port', parseInt(e.target.value) || 2048)}
/>
{!validatePort(localConfig.fastapi_port) && (
<span className={styles.errorText}>{t.settingsPage.portRangeError}</span>
)}
</div>
{/* Camoufox Debug Port */}
<div className={styles.formGroup}>
<label className={styles.label}>{t.settingsPage.camoufoxPort}</label>
<input
type="number"
className={styles.numberInput}
value={localConfig.camoufox_debug_port}
min={1024}
max={65535}
onChange={(e) => handleChange('camoufox_debug_port', parseInt(e.target.value) || 9222)}
/>
</div>
{/* Stream Proxy Toggle + Port */}
<div className={styles.toggle}>
<div className={styles.toggleLabel}>
<span className={styles.label}>{t.settingsPage.streamProxy}</span>
</div>
<button
className={`${styles.switch} ${localConfig.stream_proxy_enabled ? styles.active : ''}`}
onClick={() => handleChange('stream_proxy_enabled', !localConfig.stream_proxy_enabled)}
role="switch"
aria-checked={localConfig.stream_proxy_enabled}
>
<span className={styles.switchThumb} aria-hidden="true" />
</button>
</div>
{localConfig.stream_proxy_enabled && (
<div className={styles.formGroup}>
<label className={styles.label}>{t.settingsPage.streamProxyPort}</label>
<input
type="number"
className={styles.numberInput}
value={localConfig.stream_proxy_port}
min={1024}
max={65535}
onChange={(e) => handleChange('stream_proxy_port', parseInt(e.target.value) || 3120)}
/>
</div>
)}
{/* Save Button */}
<div className={styles.buttonGroup}>
<button
className={styles.primaryButton}
onClick={handleSave}
disabled={!hasChanges || updateMutation.isPending}
>
{updateMutation.isPending ? (
<Loader2 size={14} className={styles.spinning} />
) : (
<Save size={14} />
)}
{t.settingsPage.saveConfig}
</button>
</div>
</div>
);
}
|