import * as React from 'react'; import { DownloadCloud, LogOut, RotateCw, Trash2 } from 'react-feather'; import { useTranslation } from 'react-i18next'; import { useQuery } from 'react-query'; import * as logsApi from '~/api/logs'; import { fetchVersion } from '~/api/version'; import Select from '~/components/shared/Select'; import { ClashGeneralConfig, DispatchFn, State } from '~/store/types'; import { ClashAPIConfig } from '~/types'; import { getClashAPIConfig, getLatencyTestUrl, getSelectedChartStyleIndex, getUseEmojiFont, } from '../store/app'; import { fetchConfigs, flushFakeIPPool, getConfigs, reloadConfigFile, restartCore, updateConfigs, updateGeoDatabasesFile, upgradeCore, } from '../store/configs'; import { openModal } from '../store/modals'; import Button from './Button'; import s0 from './Config.module.scss'; import ContentHeader from './ContentHeader'; import Input, { SelfControlledInput } from './Input'; import { Selection2 } from './Selection'; import { connect, useStoreActions } from './StateProvider'; import Switch from './SwitchThemed'; import TrafficChartSample from './TrafficChartSample'; const { useEffect, useState, useCallback, useRef } = React; const propsList = [{ id: 0 }, { id: 1 }, { id: 2 }, { id: 3 }]; const logLeveOptions = [ ['debug', 'Debug'], ['info', 'Info'], ['warning', 'Warning'], ['error', 'Error'], ['silent', 'Silent'], ]; const portFields = [ { key: 'port', label: 'Http Port' }, { key: 'socks-port', label: 'Socks5 Port' }, { key: 'mixed-port', label: 'Mixed Port' }, { key: 'redir-port', label: 'Redir Port' }, { key: 'mitm-port', label: 'MITM Port' }, ]; const langOptions = [ ['zh-cn', '简体中文'], ['zh-tw', '繁體中文'], ['en', 'English'], ['vi', 'Vietnamese'], ]; const modeOptions = [ ['direct', 'Direct'], ['rule', 'Rule'], ['script', 'Script'], ['global', 'Global'], ]; const tunStackOptions = [ ['gvisor', 'gVisor'], ['mixed', 'Mixed'], ['system', 'System'], ]; const mapState = (s: State) => ({ configs: getConfigs(s), apiConfig: getClashAPIConfig(s), }); const mapState2 = (s: State) => ({ selectedChartStyleIndex: getSelectedChartStyleIndex(s), latencyTestUrl: getLatencyTestUrl(s), useEmojiFont: getUseEmojiFont(s), apiConfig: getClashAPIConfig(s), }); const Config = connect(mapState2)(ConfigImpl); export default connect(mapState)(ConfigContainer); function ConfigContainer({ dispatch, configs, apiConfig }) { useEffect(() => { dispatch(fetchConfigs(apiConfig)); }, [dispatch, apiConfig]); return ; } type ConfigImplProps = { dispatch: DispatchFn; configs: ClashGeneralConfig; selectedChartStyleIndex: number; latencyTestUrl: string; useEmojiFont: boolean; apiConfig: ClashAPIConfig; }; function getBackendContent(version: any): string { if (version && version.meta && !version.premium) { return 'Clash.Meta '; } else if (version && version.meta && version.premium) { return 'sing-box '; } else { return 'Clash Premium'; } } function ConfigImpl({ dispatch, configs, selectedChartStyleIndex, latencyTestUrl, useEmojiFont, apiConfig, }: ConfigImplProps) { const { t, i18n } = useTranslation(); const [configState, setConfigStateInternal] = useState(configs); const refConfigs = useRef(configs); useEffect(() => { if (refConfigs.current !== configs) { setConfigStateInternal(configs); } refConfigs.current = configs; }, [configs]); const openAPIConfigModal = useCallback(() => { dispatch(openModal('apiConfig')); }, [dispatch]); const setConfigState = useCallback( (name: string, val: any) => { setConfigStateInternal({ ...configState, [name]: val }); }, [configState] ); const setTunConfigState = useCallback( (name: any, val: any) => { const tun = { ...configState.tun, [name]: val }; setConfigStateInternal({ ...configState, tun: { ...tun } }); }, [configState] ); const handleInputOnChange = useCallback( ({ name, value }) => { switch (name) { case 'mode': case 'log-level': case 'allow-lan': case 'sniffing': setConfigState(name, value); dispatch(updateConfigs(apiConfig, { [name]: value })); if (name === 'log-level') { logsApi.reconnect({ ...apiConfig, logLevel: value }); } break; case 'mitm-port': case 'redir-port': case 'socks-port': case 'mixed-port': case 'port': if (value !== '') { const num = parseInt(value, 10); if (num < 0 || num > 65535) return; } setConfigState(name, value); break; case 'enable': case 'stack': setTunConfigState(name, value); dispatch(updateConfigs(apiConfig, { tun: { [name]: value } })); break; default: return; } }, [apiConfig, dispatch, setConfigState, setTunConfigState] ); const { selectChartStyleIndex, updateAppConfig } = useStoreActions(); const handleInputOnBlur = useCallback( ( e: | React.FocusEvent | React.ChangeEvent ) => { const { name, value } = e.target; switch (name) { case 'port': case 'socks-port': case 'mixed-port': case 'redir-port': case 'mitm-port': { const num = parseInt(value, 10); if (num < 0 || num > 65535) return; dispatch(updateConfigs(apiConfig, { [name]: num })); break; } case 'latencyTestUrl': { updateAppConfig(name, value); break; } case 'device name': case 'interface name': break; default: throw new Error(`unknown input name ${name}`); } }, [apiConfig, dispatch, updateAppConfig] ); const handleReloadConfigFile = useCallback(() => { dispatch(reloadConfigFile(apiConfig)); }, [apiConfig, dispatch]); const handleRestartCore = useCallback(() => { dispatch(restartCore(apiConfig)); }, [apiConfig, dispatch]); const handleUpgradeCore = useCallback(() => { dispatch(upgradeCore(apiConfig)); }, [apiConfig, dispatch]); const handleUpdateGeoDatabasesFile = useCallback(() => { dispatch(updateGeoDatabasesFile(apiConfig)); }, [apiConfig, dispatch]); const handleFlushFakeIPPool = useCallback(() => { dispatch(flushFakeIPPool(apiConfig)); }, [apiConfig, dispatch]); const { data: version } = useQuery(['/version', apiConfig], () => fetchVersion('/version', apiConfig) ); return (
{(version.meta && version.premium) || portFields.map((f) => configState[f.key] !== undefined ? (
{f.label}
handleInputOnChange({ name, value })} onBlur={handleInputOnBlur} />
) : null )}
Mode
handleInputOnChange({ name: 'log-level', value: e.target.value })} />
{(version.meta && version.premium) || (
{t('allow_lan')}
handleInputOnChange({ name: 'allow-lan', value: value }) } />
)} {version.meta && !version.premium && (
{t('tls_sniffing')}
handleInputOnChange({ name: 'sniffing', value: value }) } />
)}
{version.meta && ( <> {version.premium || (
{t('enable_tun_device')}
handleInputOnChange({ name: 'enable', value: value }) } />
TUN IP Stack
Interface Name
)}
Reload
{version.meta && !version.premium && (
GEO Databases
)}
FakeIP
{version.meta && !version.premium && (
Restart
)} {version.meta && !version.premium && (
⚠️ Upgrade ⚠️
)}
)}
{t('latency_test_url')}
{t('lang')}