feat: display network interface

This commit is contained in:
MystiPanda 2024-07-13 14:10:50 +08:00
parent 32212a46e2
commit 48f7c15035
No known key found for this signature in database
8 changed files with 216 additions and 4 deletions

14
src-tauri/Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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<String> {
return result;
}
#[tauri::command]
pub fn get_network_interfaces_info() -> CmdResult<Vec<NetworkInterface>> {
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") {

View File

@ -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,

View File

@ -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<DialogRef>((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 (
<BaseDialog
open={open}
title={
<Box display="flex" justifyContent="space-between">
{t("Network Interface")}
<Box>
<Button
variant="contained"
size="small"
onClick={() => {
setIsV4((prev) => !prev);
}}
>
{isV4 ? t("Ipv6") : t("Ipv4")}
</Button>
</Box>
</Box>
}
contentSx={{ width: 450, maxHeight: 330 }}
okBtn={t("Save")}
cancelBtn={t("Cancel")}
onClose={() => setOpen(false)}
onCancel={() => setOpen(false)}
>
{networkInterfaces.map((item) => (
<Box key={item.name}>
<h4>{item.name}</h4>
<Box>
{isV4 && (
<>
{item.addr.map(
(address) =>
address.V4 && (
<AddressDisplay
key={address.V4.ip}
label="Address"
content={address.V4.ip}
/>
)
)}
<AddressDisplay label="Mac" content={item.mac_addr ?? ""} />
</>
)}
{!isV4 && (
<>
{item.addr.map(
(address) =>
address.V6 && (
<AddressDisplay
key={address.V6.ip}
label="Address"
content={address.V6.ip}
/>
)
)}
<AddressDisplay label="Mac" content={item.mac_addr ?? ""} />
</>
)}
</Box>
</Box>
))}
</BaseDialog>
);
});
const AddressDisplay = (props: { label: string; content: string }) => {
const { t } = useTranslation();
return (
<Box
sx={{
display: "flex",
justifyContent: "space-between",
margin: "8px 0",
}}
>
<Box>{props.label}</Box>
<Box
sx={({ palette }) => ({
borderRadius: "8px",
padding: "2px",
background:
palette.mode === "dark"
? alpha(palette.background.paper, 0.3)
: alpha(palette.grey[400], 0.3),
})}
>
<Box sx={{ display: "inline", userSelect: "text" }}>
{props.content}
</Box>
<IconButton
size="small"
onClick={async () => {
await writeText(props.content);
Notice.success(t("Copy Success"));
}}
>
<ContentCopyRounded sx={{ fontSize: "18px" }} />
</IconButton>
</Box>
</Box>
);
};

View File

@ -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<DialogRef>(null);
const ctrlRef = useRef<DialogRef>(null);
const coreRef = useRef<DialogRef>(null);
const networkRef = useRef<DialogRef>(null);
const onSwitchFormat = (_e: any, value: boolean) => value;
const onChangeData = (patch: Partial<IConfigData>) => {
@ -60,8 +66,21 @@ const SettingClash = ({ onError }: Props) => {
<ClashPortViewer ref={portRef} />
<ControllerViewer ref={ctrlRef} />
<ClashCoreViewer ref={coreRef} />
<NetworkInterfaceViewer ref={networkRef} />
<SettingItem label={t("Allow Lan")}>
<SettingItem
label={t("Allow Lan")}
extra={
<TooltipIcon
title={t("Network Interface")}
color={"inherit"}
icon={LanRounded}
onClick={() => {
networkRef.current?.open();
}}
/>
}
>
<GuardState
value={allowLan ?? false}
valueProps="checked"
@ -112,7 +131,7 @@ const SettingClash = ({ onError }: Props) => {
<TooltipIcon
title={t("Random Port")}
color={enable_random_port ? "primary" : "inherit"}
icon={Shuffle}
icon={ShuffleRounded}
onClick={() => {
Notice.success(
t("Restart Application to Apply Modifications"),
@ -148,7 +167,7 @@ const SettingClash = ({ onError }: Props) => {
label={t("Clash Core")}
extra={
<TooltipIcon
icon={Settings}
icon={SettingsRounded}
onClick={() => coreRef.current?.open()}
/>
}

View File

@ -241,3 +241,7 @@ export async function downloadIconCache(url: string, name: string) {
export async function getNetworkInterfaces() {
return invoke<string[]>("get_network_interfaces");
}
export async function getNetworkInterfacesInfo() {
return invoke<INetworkInterface[]>("get_network_interfaces_info");
}

View File

@ -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: [];