Add tray controls and clean up child processes
Build Windows Portable Package / portable (release) Successful in 12m13s
Build Windows Portable Package / portable (release) Successful in 12m13s
This commit is contained in:
@@ -9,11 +9,17 @@ edition = "2021"
|
|||||||
name = "super_n2n_lib"
|
name = "super_n2n_lib"
|
||||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "super-n2n"
|
||||||
|
path = "src/main.rs"
|
||||||
|
test = false
|
||||||
|
bench = false
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "2", features = [] }
|
tauri-build = { version = "2", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tauri = { version = "2", features = [] }
|
tauri = { version = "2", features = ["tray-icon"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -1,3 +1,5 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
tauri_build::build()
|
let windows = tauri_build::WindowsAttributes::new().app_manifest(include_str!("manifest.xml"));
|
||||||
|
let attributes = tauri_build::Attributes::new().windows_attributes(windows);
|
||||||
|
tauri_build::try_build(attributes).expect("failed to run build script");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||||
|
<dependency>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity
|
||||||
|
type="win32"
|
||||||
|
name="Microsoft.Windows.Common-Controls"
|
||||||
|
version="6.0.0.0"
|
||||||
|
processorArchitecture="*"
|
||||||
|
publicKeyToken="6595b64144ccf1df"
|
||||||
|
language="*"
|
||||||
|
/>
|
||||||
|
</dependentAssembly>
|
||||||
|
</dependency>
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges>
|
||||||
|
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
|
||||||
|
</requestedPrivileges>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
</assembly>
|
||||||
+216
-16
@@ -3,18 +3,55 @@ use serde_json::Value;
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fs,
|
fs,
|
||||||
net::{SocketAddr, TcpStream, UdpSocket},
|
net::{Ipv4Addr, SocketAddr, TcpStream, UdpSocket},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
process::{Child, Command, Stdio},
|
process::{Child, Command, Stdio},
|
||||||
sync::Mutex,
|
sync::Mutex,
|
||||||
time::{Duration, Instant, SystemTime, UNIX_EPOCH},
|
time::{Duration, Instant, SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
use tauri::State;
|
use tauri::{
|
||||||
|
menu::{Menu, MenuItem},
|
||||||
|
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
|
||||||
|
Emitter, Manager, State,
|
||||||
|
};
|
||||||
|
|
||||||
struct AppState {
|
struct AppState {
|
||||||
processes: Mutex<HashMap<String, Child>>,
|
processes: Mutex<HashMap<String, Child>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn stop_all_processes(state: &AppState) {
|
||||||
|
let Ok(mut processes) = state.processes.lock() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
for (_, mut child) in processes.drain() {
|
||||||
|
let _ = child.kill();
|
||||||
|
let _ = child.wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tray_icon_image() -> tauri::image::Image<'static> {
|
||||||
|
let mut rgba = vec![0_u8; 16 * 16 * 4];
|
||||||
|
for y in 0..16 {
|
||||||
|
for x in 0..16 {
|
||||||
|
let inside = x > 1 && x < 14 && y > 1 && y < 14;
|
||||||
|
if !inside {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let index = (y * 16 + x) * 4;
|
||||||
|
rgba[index..index + 4].copy_from_slice(&[8, 137, 123, 255]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (x, y) in [(5, 8), (11, 5), (11, 11)] {
|
||||||
|
let index = (y * 16 + x) * 4;
|
||||||
|
rgba[index..index + 4].copy_from_slice(&[255, 255, 255, 255]);
|
||||||
|
}
|
||||||
|
for (x, y) in [(6, 8), (10, 6), (10, 10)] {
|
||||||
|
let index = (y * 16 + x) * 4;
|
||||||
|
rgba[index..index + 4].copy_from_slice(&[255, 255, 255, 255]);
|
||||||
|
}
|
||||||
|
tauri::image::Image::new_owned(rgba, 16, 16)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct StartRequest {
|
struct StartRequest {
|
||||||
@@ -87,6 +124,7 @@ struct TapAdapter {
|
|||||||
name: String,
|
name: String,
|
||||||
description: String,
|
description: String,
|
||||||
guid: String,
|
guid: String,
|
||||||
|
if_index: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
const CONFIG_FILE_NAME: &str = "super-n2n.config.json";
|
const CONFIG_FILE_NAME: &str = "super-n2n.config.json";
|
||||||
@@ -102,6 +140,17 @@ struct StartupStatus {
|
|||||||
tap_installer_path: Option<String>,
|
tap_installer_path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_tap_windows_description(description: &str) -> bool {
|
||||||
|
let normalized = description.trim().to_ascii_lowercase();
|
||||||
|
if normalized == "tap-windows adapter v9" {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let Some(suffix) = normalized.strip_prefix("tap-windows adapter v9 #") else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
!suffix.is_empty() && suffix.chars().all(|character| character.is_ascii_digit())
|
||||||
|
}
|
||||||
|
|
||||||
fn now_epoch() -> u64 {
|
fn now_epoch() -> u64 {
|
||||||
SystemTime::now()
|
SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)
|
.duration_since(UNIX_EPOCH)
|
||||||
@@ -223,7 +272,7 @@ fn windows_tap_traffic(tap_device: &str) -> Result<TapTraffic, String> {
|
|||||||
|| description.eq_ignore_ascii_case(tap_device)
|
|| description.eq_ignore_ascii_case(tap_device)
|
||||||
|| guid.eq_ignore_ascii_case(tap_device))
|
|| guid.eq_ignore_ascii_case(tap_device))
|
||||||
.then_some(TapTraffic {
|
.then_some(TapTraffic {
|
||||||
interface_name: alias,
|
interface_name: description,
|
||||||
received_bytes: row.InOctets,
|
received_bytes: row.InOctets,
|
||||||
sent_bytes: row.OutOctets,
|
sent_bytes: row.OutOctets,
|
||||||
})
|
})
|
||||||
@@ -278,14 +327,12 @@ fn windows_tap_adapters() -> Result<Vec<TapAdapter>, String> {
|
|||||||
rows.iter()
|
rows.iter()
|
||||||
.filter_map(|row| {
|
.filter_map(|row| {
|
||||||
let description = wide_string(&row.Description);
|
let description = wide_string(&row.Description);
|
||||||
description
|
is_tap_windows_description(&description).then_some(TapAdapter {
|
||||||
.to_ascii_lowercase()
|
name: wide_string(&row.Alias),
|
||||||
.contains("tap-windows")
|
description,
|
||||||
.then_some(TapAdapter {
|
guid: guid_string(&row.InterfaceGuid),
|
||||||
name: wide_string(&row.Alias),
|
if_index: row.InterfaceIndex,
|
||||||
description,
|
})
|
||||||
guid: guid_string(&row.InterfaceGuid),
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
@@ -328,11 +375,19 @@ fn start_service(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut child = Command::new(relative_binary)
|
let mut command = Command::new(relative_binary);
|
||||||
|
command
|
||||||
.current_dir(&working_directory)
|
.current_dir(&working_directory)
|
||||||
.args(&request.args)
|
.args(&request.args)
|
||||||
.stdout(Stdio::null())
|
.stdout(Stdio::null())
|
||||||
.stderr(Stdio::null())
|
.stderr(Stdio::null());
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
use std::os::windows::process::CommandExt;
|
||||||
|
// Keep the console-subsystem edge process out of the user's desktop.
|
||||||
|
command.creation_flags(0x08000000);
|
||||||
|
}
|
||||||
|
let mut child = command
|
||||||
.spawn()
|
.spawn()
|
||||||
.map_err(|e| format!("unable to start {}: {}", binary.display(), e))?;
|
.map_err(|e| format!("unable to start {}: {}", binary.display(), e))?;
|
||||||
let pid = child.id();
|
let pid = child.id();
|
||||||
@@ -354,6 +409,77 @@ fn start_service(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct TapAddressRequest {
|
||||||
|
tap_device: String,
|
||||||
|
dhcp: bool,
|
||||||
|
ip: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn configure_tap_adapter(request: TapAddressRequest) -> Result<(), String> {
|
||||||
|
let requested = request.tap_device.trim();
|
||||||
|
if requested.is_empty()
|
||||||
|
|| requested.contains(['/', '\\'])
|
||||||
|
|| requested == "."
|
||||||
|
|| requested == ".."
|
||||||
|
{
|
||||||
|
return Err("invalid TAP device name".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
let adapter = windows_tap_adapters()?
|
||||||
|
.into_iter()
|
||||||
|
.find(|adapter| {
|
||||||
|
adapter.name.eq_ignore_ascii_case(requested)
|
||||||
|
|| adapter.description.eq_ignore_ascii_case(requested)
|
||||||
|
|| adapter.guid.eq_ignore_ascii_case(requested)
|
||||||
|
})
|
||||||
|
.ok_or_else(|| format!("找不到指定的 TAP 设备:{requested}"))?;
|
||||||
|
if adapter.if_index == 0 {
|
||||||
|
return Err("TAP 网卡没有有效的接口索引".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let interface = format!("name={}", adapter.if_index);
|
||||||
|
let mut command = Command::new("netsh");
|
||||||
|
command.args(["interface", "ipv4", "set", "address", &interface]);
|
||||||
|
if request.dhcp {
|
||||||
|
command.arg("source=dhcp");
|
||||||
|
} else {
|
||||||
|
let address = request
|
||||||
|
.ip
|
||||||
|
.trim()
|
||||||
|
.parse::<Ipv4Addr>()
|
||||||
|
.map_err(|_| format!("无效的本机 IP:{}", request.ip.trim()))?;
|
||||||
|
command.args([
|
||||||
|
"source=static",
|
||||||
|
&format!("address={address}"),
|
||||||
|
"mask=255.255.255.0",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
let output = command
|
||||||
|
.output()
|
||||||
|
.map_err(|error| format!("无法配置 TAP 网卡:{error}"))?;
|
||||||
|
if !output.status.success() {
|
||||||
|
let detail = if output.stderr.is_empty() {
|
||||||
|
String::from_utf8_lossy(&output.stdout)
|
||||||
|
} else {
|
||||||
|
String::from_utf8_lossy(&output.stderr)
|
||||||
|
};
|
||||||
|
return Err(format!("TAP 网卡地址配置失败:{}", detail.trim()));
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
{
|
||||||
|
let _ = request;
|
||||||
|
Err("TAP 网卡地址配置仅支持 Windows".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn stop_service(service: String, state: State<'_, AppState>) -> Result<ServiceStatus, String> {
|
fn stop_service(service: String, state: State<'_, AppState>) -> Result<ServiceStatus, String> {
|
||||||
let mut processes = state
|
let mut processes = state
|
||||||
@@ -578,6 +704,7 @@ fn resolve_tap_adapter(request: TapAdapterRequest) -> Result<TapAdapter, String>
|
|||||||
name: requested.to_string(),
|
name: requested.to_string(),
|
||||||
description: requested.to_string(),
|
description: requested.to_string(),
|
||||||
guid: String::new(),
|
guid: String::new(),
|
||||||
|
if_index: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -711,7 +838,19 @@ fn export_log(contents: String) -> Result<String, String> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{relative_binary_path, StartRequest};
|
use super::{is_tap_windows_description, relative_binary_path, StartRequest};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tap_description_filter_excludes_network_filter_interfaces() {
|
||||||
|
assert!(is_tap_windows_description("TAP-Windows Adapter V9"));
|
||||||
|
assert!(is_tap_windows_description("TAP-Windows Adapter V9 #2"));
|
||||||
|
assert!(!is_tap_windows_description(
|
||||||
|
"TAP-Windows Adapter V9 #2-WFP Native MAC Layer LightWeight Filter-0000"
|
||||||
|
));
|
||||||
|
assert!(!is_tap_windows_description(
|
||||||
|
"TAP-Windows Adapter V9-Npcap Packet Driver (NPCAP)-0000"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
#[test]
|
#[test]
|
||||||
@@ -748,10 +887,62 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
let app = tauri::Builder::default()
|
||||||
.manage(AppState {
|
.manage(AppState {
|
||||||
processes: Mutex::new(HashMap::new()),
|
processes: Mutex::new(HashMap::new()),
|
||||||
})
|
})
|
||||||
|
.setup(|app| {
|
||||||
|
let connect = MenuItem::with_id(app, "tray-connect", "连接", true, None::<&str>)?;
|
||||||
|
let disconnect = MenuItem::with_id(app, "tray-disconnect", "断开", true, None::<&str>)?;
|
||||||
|
let quit = MenuItem::with_id(app, "tray-quit", "退出", true, None::<&str>)?;
|
||||||
|
let menu = Menu::with_items(app, &[&connect, &disconnect, &quit])?;
|
||||||
|
TrayIconBuilder::with_id("main")
|
||||||
|
.icon(tray_icon_image())
|
||||||
|
.menu(&menu)
|
||||||
|
.tooltip("Super N2N")
|
||||||
|
.show_menu_on_left_click(false)
|
||||||
|
.build(app)?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.on_menu_event(|app, event| match event.id().as_ref() {
|
||||||
|
"tray-connect" => {
|
||||||
|
let _ = app.emit("tray-connect", ());
|
||||||
|
if let Some(window) = app.get_webview_window("main") {
|
||||||
|
let _ = window.show();
|
||||||
|
let _ = window.set_focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"tray-disconnect" => {
|
||||||
|
stop_all_processes(app.state::<AppState>().inner());
|
||||||
|
let _ = app.emit("tray-service-stopped", ());
|
||||||
|
}
|
||||||
|
"tray-quit" => {
|
||||||
|
stop_all_processes(app.state::<AppState>().inner());
|
||||||
|
app.exit(0);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
})
|
||||||
|
.on_tray_icon_event(|app, event| {
|
||||||
|
if let TrayIconEvent::Click {
|
||||||
|
button: MouseButton::Left,
|
||||||
|
button_state: MouseButtonState::Up,
|
||||||
|
..
|
||||||
|
} = event
|
||||||
|
{
|
||||||
|
if let Some(window) = app.get_webview_window("main") {
|
||||||
|
let _ = window.show();
|
||||||
|
let _ = window.set_focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on_window_event(|window, event| {
|
||||||
|
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
|
||||||
|
api.prevent_close();
|
||||||
|
stop_all_processes(window.state::<AppState>().inner());
|
||||||
|
let _ = window.emit("tray-service-stopped", ());
|
||||||
|
let _ = window.hide();
|
||||||
|
}
|
||||||
|
})
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
start_service,
|
start_service,
|
||||||
stop_service,
|
stop_service,
|
||||||
@@ -761,6 +952,7 @@ pub fn run() {
|
|||||||
run_tcping,
|
run_tcping,
|
||||||
tap_traffic,
|
tap_traffic,
|
||||||
resolve_tap_adapter,
|
resolve_tap_adapter,
|
||||||
|
configure_tap_adapter,
|
||||||
startup_status,
|
startup_status,
|
||||||
read_config,
|
read_config,
|
||||||
write_config,
|
write_config,
|
||||||
@@ -768,6 +960,14 @@ pub fn run() {
|
|||||||
firewall_status,
|
firewall_status,
|
||||||
export_log
|
export_log
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.build(tauri::generate_context!())
|
||||||
.expect("error while running Super N2N");
|
.expect("error while running Super N2N");
|
||||||
|
app.run(|app, event| {
|
||||||
|
if matches!(
|
||||||
|
event,
|
||||||
|
tauri::RunEvent::ExitRequested { .. } | tauri::RunEvent::Exit
|
||||||
|
) {
|
||||||
|
stop_all_processes(app.state::<AppState>().inner());
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ export const tapTraffic = (tapDevice) => nativeCall('tap_traffic', { request: {
|
|||||||
export const resolveTapAdapter = (tapDevice) => nativeCall('resolve_tap_adapter', { request: { tapDevice } }, () => {
|
export const resolveTapAdapter = (tapDevice) => nativeCall('resolve_tap_adapter', { request: { tapDevice } }, () => {
|
||||||
throw new Error('浏览器预览无法识别 TAP 网卡')
|
throw new Error('浏览器预览无法识别 TAP 网卡')
|
||||||
})
|
})
|
||||||
|
export const configureTapAdapter = (request) => nativeCall('configure_tap_adapter', { request }, () => {
|
||||||
|
throw new Error('浏览器预览无法配置 TAP 网卡')
|
||||||
|
})
|
||||||
export const startupStatus = () => nativeCall('startup_status', {}, () => ({
|
export const startupStatus = () => nativeCall('startup_status', {}, () => ({
|
||||||
configExists: false,
|
configExists: false,
|
||||||
firstLaunch: false,
|
firstLaunch: false,
|
||||||
|
|||||||
+48
-3
@@ -1,11 +1,13 @@
|
|||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
|
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||||
|
import { listen } from '@tauri-apps/api/event'
|
||||||
import {
|
import {
|
||||||
ArrowDown, ArrowUp, CheckCircle2, ChevronDown, CircleAlert, Gauge, Globe2, Network, Play,
|
ArrowDown, ArrowUp, CheckCircle2, ChevronDown, CircleAlert, Gauge, Globe2, Network, Play,
|
||||||
Download, Router, ScrollText, Server, Settings2, Square, Wifi,
|
Download, Router, ScrollText, Server, Settings2, Square, Wifi,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import {
|
import {
|
||||||
exportLog, isTauri, openTapInstaller, readConfig, resolveTapAdapter, serviceStatus, startService,
|
configureTapAdapter, exportLog, isTauri, openTapInstaller, readConfig, resolveTapAdapter, serviceStatus, startService,
|
||||||
startupStatus, stopService, tapTraffic, writeConfig,
|
startupStatus, stopService, tapTraffic, writeConfig,
|
||||||
} from './lib/tauri'
|
} from './lib/tauri'
|
||||||
import './styles.css'
|
import './styles.css'
|
||||||
@@ -22,6 +24,7 @@ const initialEdge = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const edgeConfigStorageKey = 'super-n2n.edge-config.v1'
|
const edgeConfigStorageKey = 'super-n2n.edge-config.v1'
|
||||||
|
const isWindowsRuntime = typeof navigator !== 'undefined' && /Windows/i.test(navigator.userAgent)
|
||||||
|
|
||||||
function parseEdgeConfig(value) {
|
function parseEdgeConfig(value) {
|
||||||
const fallback = { ...initialEdge }
|
const fallback = { ...initialEdge }
|
||||||
@@ -119,6 +122,38 @@ function App() {
|
|||||||
return () => { active = false }
|
return () => { active = false }
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isTauri) return undefined
|
||||||
|
let active = true
|
||||||
|
let unlistenConnect
|
||||||
|
let unlistenDisconnect
|
||||||
|
let unlistenStopped
|
||||||
|
const bindTrayEvents = async () => {
|
||||||
|
unlistenConnect = await listen('tray-connect', async () => {
|
||||||
|
if (!active) return
|
||||||
|
await getCurrentWindow().show()
|
||||||
|
await getCurrentWindow().setFocus()
|
||||||
|
if (!edgeRunning) await toggleEdge()
|
||||||
|
})
|
||||||
|
unlistenDisconnect = await listen('tray-disconnect', async () => {
|
||||||
|
if (!active || !edgeRunning) return
|
||||||
|
await toggleEdge()
|
||||||
|
})
|
||||||
|
unlistenStopped = await listen('tray-service-stopped', () => {
|
||||||
|
if (!active) return
|
||||||
|
setEdgeRunning(false)
|
||||||
|
setActiveTapDevice('')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
bindTrayEvents()
|
||||||
|
return () => {
|
||||||
|
active = false
|
||||||
|
unlistenConnect?.()
|
||||||
|
unlistenDisconnect?.()
|
||||||
|
unlistenStopped?.()
|
||||||
|
}
|
||||||
|
}, [edgeRunning, edge])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let active = true
|
let active = true
|
||||||
serviceStatus('edge')
|
serviceStatus('edge')
|
||||||
@@ -203,10 +238,18 @@ function App() {
|
|||||||
try {
|
try {
|
||||||
const tapAdapter = await resolveTapAdapter(edge.tapDevice)
|
const tapAdapter = await resolveTapAdapter(edge.tapDevice)
|
||||||
appendLog('已选择 TAP 设备:' + tapAdapter.name)
|
appendLog('已选择 TAP 设备:' + tapAdapter.name)
|
||||||
|
if (isWindowsRuntime) {
|
||||||
|
await configureTapAdapter({
|
||||||
|
tapDevice: tapAdapter.guid || tapAdapter.name,
|
||||||
|
dhcp: edge.dhcp,
|
||||||
|
ip: edge.ip,
|
||||||
|
})
|
||||||
|
appendLog('已配置 TAP 网卡地址', 'success')
|
||||||
|
}
|
||||||
const status = await startService({
|
const status = await startService({
|
||||||
service: 'edge',
|
service: 'edge',
|
||||||
binaryPath: './n2n/edge.exe',
|
binaryPath: './n2n/edge.exe',
|
||||||
args: buildEdgeArgs(edge, tapAdapter.name),
|
args: buildEdgeArgs(edge, tapAdapter.guid || tapAdapter.name),
|
||||||
})
|
})
|
||||||
setEdgeRunning(Boolean(status.running))
|
setEdgeRunning(Boolean(status.running))
|
||||||
setActiveTapDevice(tapAdapter.name)
|
setActiveTapDevice(tapAdapter.name)
|
||||||
@@ -444,7 +487,9 @@ function buildEdgeArgs(config, tapDevice) {
|
|||||||
addOption(args, '-d', tapDevice)
|
addOption(args, '-d', tapDevice)
|
||||||
addOption(args, '-t', config.managementPort)
|
addOption(args, '-t', config.managementPort)
|
||||||
if (config.encryptionKey) addOption(args, '-k', config.encryptionKey)
|
if (config.encryptionKey) addOption(args, '-k', config.encryptionKey)
|
||||||
args.push('-f', '-v', '-v')
|
// Windows edge does not implement the POSIX-only -f daemon flag.
|
||||||
|
if (!isWindowsRuntime) args.push('-f')
|
||||||
|
args.push('-v', '-v')
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user