import { useDispatch, useSelector } from 'react-redux' import { resourceDir, join } from '@tauri-apps/api/path' import { convertFileSrc } from '@tauri-apps/api/core' import { v4 as uuidv4 } from 'uuid' import { cn } from '@/lib/utils' import { bytesToMbps } from '@/utils/tools' import { errorToast } from '@/components/GlobalToast' import { toast } from '@/components/ui/use-toast' import { commands } from '@/bindings' import { // installService, // uninstallService, // startCore, // stopCore, enableProxy, disableProxy, } from '@/store/serviceSlice' import { WebSocketClient } from '@/utils/webSocketClient' import { ServiceControlPanel } from '@/components/ServiceControlPanel' import { useCoreConfig } from '@/hooks/useCoreConfig' import LineChart from '@/pages/home/components/LineChart' import Links from '@/pages/home/components/Links' import UnionSvg from '@/assets/svg/home/union.svg?react' import ArrowDownSvg from '@/assets/svg/home/arrow-down.svg?react' import ArrowUpSvg from '@/assets/svg/home/arrow-up.svg?react' import type { AppDispatch, RootState } from '@/store' import './index.scss' const AgentOpenBtn = () => { return (
开启代理
) } const AgentCloseBtn = ({ className }: { className?: string }) => { return (
关闭代理
) } const AgentInBtn = ({ className, isProxyEnabled, }: { className?: string; isProxyEnabled: boolean }) => { let timer: NodeJS.Timeout | null = null let index = 0 let [opacityList, setOpacityList] = useState([ 'opacity-100', 'opacity-50', 'opacity-25', ]) // todo 后期使用gasp 或者 formwork 替代定时器 useEffect(() => { timer = setInterval(() => { switch (index) { case 0: opacityList = ['opacity-50', 'opacity-100', 'opacity-50'] break case 1: opacityList = ['opacity-25', 'opacity-50', 'opacity-100'] break case 2: opacityList = ['opacity-100', 'opacity-50', 'opacity-25'] break default: } index++ setOpacityList(opacityList) if (index === 3) { index = 0 } }, 300) return () => { timer && clearInterval(timer) } }, []) return (
{isProxyEnabled ? '关闭中' : '代理中'}
) } function Home() { let trafficWs: WebSocketClient | null = null const dispatch = useDispatch() const { loadCoreConfig } = useCoreConfig() const [videoPath, setVideoPath] = useState('') const { countries, exitCountries } = useSelector( (state: RootState) => state.nodesReducer, ) const { circuits, fallbackCircuit } = useSelector( (state: RootState) => state.circuitReducer, ) const { isProxyEnabled, isCoreRunning } = useSelector( (state: RootState) => state.serviceReducer, ) const [isProxyLoading, setIsProxyLoading] = useState(false) const [traffic, setTraffic] = useState<{ send_bytes: number recv_bytes: number }>({ recv_bytes: 0, send_bytes: 0 }) const isCircuitReady = useMemo(() => { // 判断是否有有链路并且至少有一个能用 const circuitReady = circuits.some((circuit) => circuit?.state !== 'failed') if ( circuitReady || (fallbackCircuit && fallbackCircuit?.state !== 'failed') ) { return true } else { return false } }, [circuits, fallbackCircuit]) // 处理代理开关 const handleProxyToggle = async ( isProxyEnabled: boolean, isCircuitReady: boolean, isCoreRunning: boolean, ) => { try { // 如果核心未运行,先启动核心 if (!isCoreRunning) { await commands.startCore() } // 这里要先轮询去判断是否启动成功,如果启动失败,则抛出异常 let isGetNodesResult = await commands.getNodes() let count = 0 while (isGetNodesResult.status !== 'ok') { isGetNodesResult = await commands.getNodes() count++ // 如果50次都没有开启成功核心那么启动失败 if (count > 50) { throw new Error('启动核心失败') } } // 如果代理未启用且链路未就绪,创建默认链路 // if (!isProxyEnabled && !isCircuitReady) { // await dispatch( // createCircuit({ // uid: uuidv4(), // name: '系统默认链路', // inbound: countries[0], // outbound: exitCountries[exitCountries.length - 1], // multi_hop: 3, // fallback: true, // rule_path: null, // is_prefix: false, // }), // ).unwrap() // } setIsProxyLoading(true) // 切换代理状态 await dispatch(isProxyEnabled ? disableProxy() : enableProxy()).unwrap() } catch (error) { console.log(error, 'error') const errorMessage = isProxyEnabled ? '关闭代理失败!' : '开启代理失败,请检查节点配置、或重新尝试开启' errorToast(errorMessage, toast) console.error('Proxy toggle failed:', error) } finally { setIsProxyLoading(false) } } const openWsTraffic = async () => { if (trafficWs) return const { api_port } = await loadCoreConfig() // todo! 后面会把二级制文件启动的参数作为配置项,这里暂时写死 trafficWs = new WebSocketClient(`ws://127.0.0.1:${api_port}/traffic`, { headers: { Authorization: 'Bearer secret', }, }) // 执行 WebSocket 操作 await trafficWs.connect() trafficWs.addListener((msg) => { if (msg.type === 'Text') { const result = JSON.parse(msg.data) setTraffic({ recv_bytes: bytesToMbps(result.data.recv_bytes), send_bytes: bytesToMbps(result.data.send_bytes), }) } }) } const closeWsTraffic = async () => { if (trafficWs) { await trafficWs.disconnect() trafficWs = null } } const getVideoPath = async () => { const resourceDirPath = await resourceDir() // tauri 读取视频和音频不能直接像前端那样import video from 'xxx.mp4' 需要用convertFileSrc并且开启相应的权限,不然打包出来的引用会非常卡媒体文件越大越卡 // https://v2.tauri.app/zh-cn/reference/javascript/api/namespacecore/#convertfilesrc const filePath = await join( resourceDirPath, 'assets/video/crystal-ball.mp4', ) const assetUrl = convertFileSrc(filePath) setVideoPath(assetUrl) } useEffect(() => { getVideoPath() }, []) useEffect(() => { if (isProxyEnabled) { openWsTraffic() } else { closeWsTraffic() } return () => { closeWsTraffic() } }, [isProxyEnabled]) return (
{/* */}
您好呀,欢迎使用匿名反溯源网络系统!
当前链路稳定整体稳定,服务器处于低负载
handleProxyToggle(isProxyEnabled, isCircuitReady, isCoreRunning) } > {isProxyLoading ? (
) : isProxyEnabled ? ( ) : ( )}
{isProxyEnabled && (
{traffic.send_bytes === 0 ? '0.000' : traffic.send_bytes.toFixed(3)}
下载/Mbps
{traffic.recv_bytes === 0 ? '0.000' : traffic.recv_bytes.toFixed(3)}
上传/Mbps
)}
{import.meta.env.DEV && }
) } export default memo(Home)