diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 12db319..83a6f3d 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -803,6 +803,7 @@ dependencies = [ "log 0.4.22", "log4rs", "nanoid", + "network-interface", "once_cell", "open 5.2.0", "parking_lot", @@ -3243,6 +3244,19 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "network-interface" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433419f898328beca4f2c6c73a1b52540658d92b0a99f0269330457e0fd998d5" +dependencies = [ + "cc", + "libc", + "serde", + "thiserror", + "winapi", +] + [[package]] name = "new_debug_unreachable" version = "1.0.6" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 32a320c..a1de09f 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -38,6 +38,7 @@ serde = { version = "1.0", features = ["derive"] } reqwest = { version = "0.12", features = ["json", "rustls-tls"] } sysproxy = { git="https://github.com/zzzgydi/sysproxy-rs", branch = "main" } tauri = { version="1", features = [ "fs-read-file", "fs-exists", "path-all", "protocol-asset", "dialog-open", "notification-all", "icon-png", "icon-ico", "clipboard-all", "global-shortcut-all", "process-all", "shell-all", "system-tray", "updater", "window-all", "devtools"] } +network-interface = { version = "2.0.0", features = ["serde"] } [target.'cfg(windows)'.dependencies] runas = "=1.2.0" deelevate = "0.2.0" diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index 899f495..2d2eb77 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -6,6 +6,7 @@ use crate::{ }; use crate::{ret_err, wrap_err}; use anyhow::{Context, Result}; +use network_interface::NetworkInterface; use serde_yaml::Mapping; use std::collections::{HashMap, VecDeque}; use sysproxy::{Autoproxy, Sysproxy}; @@ -339,6 +340,25 @@ pub fn get_network_interfaces() -> Vec { return result; } +#[tauri::command] +pub fn get_network_interfaces_info() -> CmdResult> { + use network_interface::NetworkInterface; + use network_interface::NetworkInterfaceConfig; + + let names = get_network_interfaces(); + let interfaces = wrap_err!(NetworkInterface::show())?; + + let mut result = Vec::new(); + + for interface in interfaces { + if names.contains(&interface.name) { + result.push(interface); + } + } + + Ok(result) +} + #[tauri::command] pub fn open_devtools(app_handle: tauri::AppHandle) { if let Some(window) = app_handle.get_window("main") { diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index cbdffce..380a78a 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -74,6 +74,7 @@ fn main() -> std::io::Result<()> { cmds::download_icon_cache, cmds::open_devtools, cmds::exit_app, + cmds::get_network_interfaces_info, // cmds::update_hotkeys, // profile cmds::get_profiles, diff --git a/src/components/setting/mods/network-interface-viewer.tsx b/src/components/setting/mods/network-interface-viewer.tsx new file mode 100644 index 0000000..5b33721 --- /dev/null +++ b/src/components/setting/mods/network-interface-viewer.tsx @@ -0,0 +1,135 @@ +import { forwardRef, useEffect, useImperativeHandle, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { BaseDialog, DialogRef, Notice } from "@/components/base"; +import { getNetworkInterfacesInfo } from "@/services/cmds"; +import { alpha, Box, Button, Chip, IconButton } from "@mui/material"; +import { ContentCopyRounded } from "@mui/icons-material"; +import { writeText } from "@tauri-apps/api/clipboard"; + +export const NetworkInterfaceViewer = forwardRef((props, ref) => { + const { t } = useTranslation(); + const [open, setOpen] = useState(false); + const [networkInterfaces, setNetworkInterfaces] = useState< + INetworkInterface[] + >([]); + const [isV4, setIsV4] = useState(true); + + useImperativeHandle(ref, () => ({ + open: () => { + setOpen(true); + }, + close: () => setOpen(false), + })); + + useEffect(() => { + if (!open) return; + getNetworkInterfacesInfo().then((res) => { + console.log(res); + setNetworkInterfaces(res); + }); + }, [open]); + + return ( + + {t("Network Interface")} + + + + + } + contentSx={{ width: 450, maxHeight: 330 }} + okBtn={t("Save")} + cancelBtn={t("Cancel")} + onClose={() => setOpen(false)} + onCancel={() => setOpen(false)} + > + {networkInterfaces.map((item) => ( + +

{item.name}

+ + {isV4 && ( + <> + {item.addr.map( + (address) => + address.V4 && ( + + ) + )} + + + )} + {!isV4 && ( + <> + {item.addr.map( + (address) => + address.V6 && ( + + ) + )} + + + )} + +
+ ))} +
+ ); +}); + +const AddressDisplay = (props: { label: string; content: string }) => { + const { t } = useTranslation(); + + return ( + + {props.label} + ({ + borderRadius: "8px", + padding: "2px", + background: + palette.mode === "dark" + ? alpha(palette.background.paper, 0.3) + : alpha(palette.grey[400], 0.3), + })} + > + + {props.content} + + { + await writeText(props.content); + Notice.success(t("Copy Success")); + }} + > + + + + + ); +}; diff --git a/src/components/setting/setting-clash.tsx b/src/components/setting/setting-clash.tsx index 830aabd..0505614 100644 --- a/src/components/setting/setting-clash.tsx +++ b/src/components/setting/setting-clash.tsx @@ -2,7 +2,11 @@ import { useRef } from "react"; import { useTranslation } from "react-i18next"; import { TextField, Select, MenuItem, Typography } from "@mui/material"; -import { Settings, Shuffle } from "@mui/icons-material"; +import { + SettingsRounded, + ShuffleRounded, + LanRounded, +} from "@mui/icons-material"; import { DialogRef, Notice, Switch } from "@/components/base"; import { useClash } from "@/hooks/use-clash"; import { GuardState } from "./mods/guard-state"; @@ -16,6 +20,7 @@ import getSystem from "@/utils/get-system"; import { useVerge } from "@/hooks/use-verge"; import { updateGeoData } from "@/services/api"; import { TooltipIcon } from "@/components/base/base-tooltip-icon"; +import { NetworkInterfaceViewer } from "./mods/network-interface-viewer"; const isWIN = getSystem() === "windows"; @@ -37,6 +42,7 @@ const SettingClash = ({ onError }: Props) => { const portRef = useRef(null); const ctrlRef = useRef(null); const coreRef = useRef(null); + const networkRef = useRef(null); const onSwitchFormat = (_e: any, value: boolean) => value; const onChangeData = (patch: Partial) => { @@ -60,8 +66,21 @@ const SettingClash = ({ onError }: Props) => { + - + { + networkRef.current?.open(); + }} + /> + } + > { { Notice.success( t("Restart Application to Apply Modifications"), @@ -148,7 +167,7 @@ const SettingClash = ({ onError }: Props) => { label={t("Clash Core")} extra={ coreRef.current?.open()} /> } diff --git a/src/services/cmds.ts b/src/services/cmds.ts index 04da49d..1aff9f2 100644 --- a/src/services/cmds.ts +++ b/src/services/cmds.ts @@ -241,3 +241,7 @@ export async function downloadIconCache(url: string, name: string) { export async function getNetworkInterfaces() { return invoke("get_network_interfaces"); } + +export async function getNetworkInterfacesInfo() { + return invoke("get_network_interfaces_info"); +} diff --git a/src/services/types.d.ts b/src/services/types.d.ts index e450090..459635a 100644 --- a/src/services/types.d.ts +++ b/src/services/types.d.ts @@ -197,6 +197,24 @@ interface IVergeTestItem { icon?: string; url: string; } +interface IAddress { + V4?: { + ip: string; + broadcast?: string; + netmask?: string; + }; + V6?: { + ip: string; + broadcast?: string; + netmask?: string; + }; +} +interface INetworkInterface { + name: string; + addr: IAddress[]; + mac_addr?: string; + index: number; +} interface ISeqProfileConfig { prepend: [];