Auto-select TAP adapters and persist edge settings

This commit is contained in:
2026-09-01 21:39:58 +08:00
parent 3694d157d1
commit 57e6ba8e1e
5 changed files with 187 additions and 14 deletions
+3
View File
@@ -36,4 +36,7 @@ export const checkFirewall = (fallback) => nativeCall('firewall_status', {}, fal
export const tapTraffic = (tapDevice) => nativeCall('tap_traffic', { request: { tapDevice } }, () => {
throw new Error('浏览器预览无法读取 TAP 流量')
})
export const resolveTapAdapter = (tapDevice) => nativeCall('resolve_tap_adapter', { request: { tapDevice } }, () => {
throw new Error('浏览器预览无法识别 TAP 网卡')
})
export const exportLog = (contents) => nativeCall('export_log', { contents }, () => null)
+47 -13
View File
@@ -4,7 +4,7 @@ import {
ArrowDown, ArrowUp, CheckCircle2, ChevronDown, CircleAlert, Gauge, Globe2, Network, Play,
Download, Router, ScrollText, Server, Settings2, Square, Wifi,
} from 'lucide-react'
import { exportLog, isTauri, serviceStatus, startService, stopService, tapTraffic } from './lib/tauri'
import { exportLog, isTauri, resolveTapAdapter, serviceStatus, startService, stopService, tapTraffic } from './lib/tauri'
import './styles.css'
const initialEdge = {
@@ -15,13 +15,39 @@ const initialEdge = {
ip: '',
encryptionKey: '',
managementPort: '5644',
tapDevice: 'n2n0',
tapDevice: '',
}
const edgeConfigStorageKey = 'super-n2n.edge-config.v1'
function loadEdgeConfig() {
const fallback = { ...initialEdge }
if (typeof window === 'undefined') return fallback
try {
const saved = JSON.parse(window.localStorage.getItem(edgeConfigStorageKey) || 'null')
if (!saved || typeof saved !== 'object' || Array.isArray(saved)) return fallback
for (const [field, defaultValue] of Object.entries(initialEdge)) {
if (typeof saved[field] === typeof defaultValue) fallback[field] = saved[field]
}
} catch {
// A damaged or unavailable browser store must not block the connection form.
}
return fallback
}
function saveEdgeConfig(config) {
try {
window.localStorage.setItem(edgeConfigStorageKey, JSON.stringify(config))
} catch {
// The form stays usable when the platform denies local storage access.
}
}
function App() {
const [home, setHome] = useState('edge')
const [edge, setEdge] = useState(initialEdge)
const [edge, setEdge] = useState(loadEdgeConfig)
const [edgeRunning, setEdgeRunning] = useState(false)
const [activeTapDevice, setActiveTapDevice] = useState('')
const [advancedOpen, setAdvancedOpen] = useState(false)
const [logOpen, setLogOpen] = useState(false)
const [logs, setLogs] = useState([])
@@ -34,6 +60,10 @@ function App() {
setLogs((current) => [{ time, level, text }, ...current].slice(0, 100))
}
useEffect(() => {
saveEdgeConfig(edge)
}, [edge])
useEffect(() => {
let active = true
serviceStatus('edge')
@@ -47,7 +77,7 @@ function App() {
}, [])
useEffect(() => {
if (!edgeRunning) {
if (!edgeRunning || !activeTapDevice) {
lastTapSample.current = null
setTraffic(null)
return undefined
@@ -57,7 +87,7 @@ function App() {
let timer
const poll = async () => {
try {
const sample = await tapTraffic(edge.tapDevice)
const sample = await tapTraffic(activeTapDevice)
const sampledAt = Date.now()
const previous = lastTapSample.current
const elapsedSeconds = previous ? Math.max((sampledAt - previous.sampledAt) / 1000, .25) : 1
@@ -76,7 +106,7 @@ function App() {
active = false
window.clearTimeout(timer)
}
}, [edgeRunning, edge.tapDevice])
}, [activeTapDevice, edgeRunning])
const updateEdge = (field, value) => setEdge((current) => ({ ...current, [field]: value }))
const notify = (text, tone = 'error') => {
@@ -89,6 +119,7 @@ function App() {
try {
const status = await stopService('edge')
setEdgeRunning(Boolean(status.running))
setActiveTapDevice('')
appendLog('已断开 N2N 网络')
notify('已断开 N2N 网络', 'neutral')
} catch (error) {
@@ -115,12 +146,15 @@ function App() {
}
try {
const tapAdapter = await resolveTapAdapter(edge.tapDevice)
appendLog('已选择 TAP 设备:' + tapAdapter.name)
const status = await startService({
service: 'edge',
binaryPath: './n2n/edge.exe',
args: buildEdgeArgs(edge),
args: buildEdgeArgs(edge, tapAdapter.name),
})
setEdgeRunning(Boolean(status.running))
setActiveTapDevice(tapAdapter.name)
appendLog('已连接到群组 ' + edge.community, 'success')
notify('已连接到 ' + edge.community, 'success')
} catch (error) {
@@ -146,7 +180,7 @@ function App() {
</div>
{home === 'edge'
? <EdgeHome edge={edge} updateEdge={updateEdge} running={edgeRunning} traffic={traffic} advancedOpen={advancedOpen} setAdvancedOpen={setAdvancedOpen} logOpen={logOpen} setLogOpen={setLogOpen} logs={logs} onToggle={toggleEdge} onExportLog={() => exportLogs(logs, notify)} />
? <EdgeHome edge={edge} updateEdge={updateEdge} running={edgeRunning} traffic={traffic} activeTapDevice={activeTapDevice} advancedOpen={advancedOpen} setAdvancedOpen={setAdvancedOpen} logOpen={logOpen} setLogOpen={setLogOpen} logs={logs} onToggle={toggleEdge} onExportLog={() => exportLogs(logs, notify)} />
: <SupernodeHome />}
</section>
@@ -155,7 +189,7 @@ function App() {
</main>
}
function EdgeHome({ edge, updateEdge, running, traffic, advancedOpen, setAdvancedOpen, logOpen, setLogOpen, logs, onToggle, onExportLog }) {
function EdgeHome({ edge, updateEdge, running, traffic, activeTapDevice, advancedOpen, setAdvancedOpen, logOpen, setLogOpen, logs, onToggle, onExportLog }) {
const toggleAdvanced = () => {
setLogOpen(false)
setAdvancedOpen((current) => !current)
@@ -203,7 +237,7 @@ function EdgeHome({ edge, updateEdge, running, traffic, advancedOpen, setAdvance
{running ? '断开连接' : '连接网络'}
</button>
{running && <TrafficStrip traffic={traffic} tapDevice={edge.tapDevice} />}
{running && <TrafficStrip traffic={traffic} tapDevice={activeTapDevice} />}
</section>
<div className="secondary-actions">
@@ -214,7 +248,7 @@ function EdgeHome({ edge, updateEdge, running, traffic, advancedOpen, setAdvance
{advancedOpen && <div className="advanced-fields">
<label><span>加密密钥</span><input type="password" value={edge.encryptionKey} onChange={(event) => updateEdge('encryptionKey', event.target.value)} placeholder="可选" disabled={running} /></label>
<label><span>管理端口</span><input inputMode="numeric" value={edge.managementPort} onChange={(event) => updateEdge('managementPort', event.target.value)} disabled={running} /></label>
<label><span>TAP 设备名称</span><input value={edge.tapDevice} onChange={(event) => updateEdge('tapDevice', event.target.value)} disabled={running} /></label>
<label><span>TAP 设备可选</span><input value={edge.tapDevice} onChange={(event) => updateEdge('tapDevice', event.target.value)} placeholder="留空自动选择" disabled={running} /></label>
</div>}
</section>
@@ -323,12 +357,12 @@ function addOption(args, flag, value) {
if (String(value ?? '').trim()) args.push(flag, String(value).trim())
}
function buildEdgeArgs(config) {
function buildEdgeArgs(config, tapDevice) {
const args = []
addOption(args, '-c', config.community)
addOption(args, '-l', config.serverHost + ':' + (config.serverPort || '7777'))
addOption(args, '-a', config.dhcp ? 'dhcp:0.0.0.0' : config.ip)
addOption(args, '-d', config.tapDevice)
addOption(args, '-d', tapDevice)
addOption(args, '-t', config.managementPort)
if (config.encryptionKey) addOption(args, '-k', config.encryptionKey)
args.push('-f', '-v', '-v')