Compare commits

...

5 Commits

Author SHA1 Message Date
Sukka
1667856894
chore: use swr subscription for layout traffic / memory (#1202)
Some checks are pending
Alpha Build / alpha (macos-latest, aarch64-apple-darwin) (push) Waiting to run
Alpha Build / alpha (macos-latest, x86_64-apple-darwin) (push) Waiting to run
Alpha Build / alpha (windows-latest, aarch64-pc-windows-msvc) (push) Waiting to run
Alpha Build / alpha (windows-latest, i686-pc-windows-msvc) (push) Waiting to run
Alpha Build / alpha (windows-latest, x86_64-pc-windows-msvc) (push) Waiting to run
Alpha Build / alpha-for-linux (ubuntu-latest, aarch64-unknown-linux-gnu) (push) Waiting to run
Alpha Build / alpha-for-linux (ubuntu-latest, armv7-unknown-linux-gnueabihf) (push) Waiting to run
Alpha Build / alpha-for-linux (ubuntu-latest, i686-unknown-linux-gnu) (push) Waiting to run
Alpha Build / alpha-for-linux (ubuntu-latest, x86_64-unknown-linux-gnu) (push) Waiting to run
Alpha Build / alpha-for-fixed-webview2 (arm64, windows-latest, aarch64-pc-windows-msvc) (push) Waiting to run
Alpha Build / alpha-for-fixed-webview2 (x64, windows-latest, x86_64-pc-windows-msvc) (push) Waiting to run
Alpha Build / alpha-for-fixed-webview2 (x86, windows-latest, i686-pc-windows-msvc) (push) Waiting to run
Alpha Build / Update tag (push) Blocked by required conditions
* chore: update swr to 2

* refactor: use swr subscription for memory & traffic

* refactor: introduce `sockette`
2024-06-14 18:23:29 +08:00
dongchengjie
c5c76ab539 chore: update metacubexd #1200 2024-06-14 17:09:12 +08:00
dongchengjie
eb060d2e43 feat: local profile name autofill #1191 2024-06-13 16:29:25 +08:00
MystiPanda
b5556613cf
refactor: remove grant logic 2024-06-13 16:07:56 +08:00
MystiPanda
ad1a057edb
fix: check service 2024-06-13 12:58:47 +08:00
14 changed files with 136 additions and 145 deletions

View File

@ -49,7 +49,8 @@
"react-router-dom": "^6.23.1", "react-router-dom": "^6.23.1",
"react-transition-group": "^4.4.5", "react-transition-group": "^4.4.5",
"react-virtuoso": "^4.7.11", "react-virtuoso": "^4.7.11",
"swr": "^1.3.0", "sockette": "^2.0.6",
"swr": "^2.2.5",
"tar": "^6.2.1", "tar": "^6.2.1",
"types-pac": "^1.0.2" "types-pac": "^1.0.2"
}, },

View File

@ -100,9 +100,12 @@ importers:
react-virtuoso: react-virtuoso:
specifier: ^4.7.11 specifier: ^4.7.11
version: 4.7.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 4.7.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
sockette:
specifier: ^2.0.6
version: 2.0.6
swr: swr:
specifier: ^1.3.0 specifier: ^2.2.5
version: 1.3.0(react@18.3.1) version: 2.2.5(react@18.3.1)
tar: tar:
specifier: ^6.2.1 specifier: ^6.2.1
version: 6.2.1 version: 6.2.1
@ -4032,6 +4035,12 @@ packages:
integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==, integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==,
} }
sockette@2.0.6:
resolution:
{
integrity: sha512-W6iG8RGV6Zife3Cj+FhuyHV447E6fqFM2hKmnaQrTvg3OydINV3Msj3WPFbX76blUlUxvQSMMMdrJxce8NqI5Q==,
}
source-map-js@1.2.0: source-map-js@1.2.0:
resolution: resolution:
{ {
@ -4110,10 +4119,10 @@ packages:
integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==, integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==,
} }
swr@1.3.0: swr@2.2.5:
resolution: resolution:
{ {
integrity: sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw==, integrity: sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==,
} }
peerDependencies: peerDependencies:
react: ^16.11.0 || ^17.0.0 || ^18.0.0 react: ^16.11.0 || ^17.0.0 || ^18.0.0
@ -4303,6 +4312,14 @@ packages:
peerDependencies: peerDependencies:
browserslist: ">= 4.21.0" browserslist: ">= 4.21.0"
use-sync-external-store@1.2.2:
resolution:
{
integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==,
}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
vfile-message@4.0.2: vfile-message@4.0.2:
resolution: resolution:
{ {
@ -7096,6 +7113,8 @@ snapshots:
dot-case: 3.0.4 dot-case: 3.0.4
tslib: 2.6.3 tslib: 2.6.3
sockette@2.0.6: {}
source-map-js@1.2.0: {} source-map-js@1.2.0: {}
source-map-support@0.5.21: source-map-support@0.5.21:
@ -7130,9 +7149,11 @@ snapshots:
svg-parser@2.0.4: {} svg-parser@2.0.4: {}
swr@1.3.0(react@18.3.1): swr@2.2.5(react@18.3.1):
dependencies: dependencies:
client-only: 0.0.1
react: 18.3.1 react: 18.3.1
use-sync-external-store: 1.2.2(react@18.3.1)
systemjs@6.15.1: {} systemjs@6.15.1: {}
@ -7237,6 +7258,10 @@ snapshots:
escalade: 3.1.2 escalade: 3.1.2
picocolors: 1.0.1 picocolors: 1.0.1
use-sync-external-store@1.2.2(react@18.3.1):
dependencies:
react: 18.3.1
vfile-message@4.0.2: vfile-message@4.0.2:
dependencies: dependencies:
"@types/unist": 3.0.2 "@types/unist": 3.0.2

View File

@ -181,15 +181,6 @@ pub async fn restart_sidecar() -> CmdResult {
wrap_err!(CoreManager::global().run_core().await) wrap_err!(CoreManager::global().run_core().await)
} }
#[tauri::command]
pub fn grant_permission(_core: String) -> CmdResult {
#[cfg(any(target_os = "macos", target_os = "linux"))]
return wrap_err!(manager::grant_permission(_core));
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
return Err("Unsupported target".into());
}
/// get the system proxy /// get the system proxy
#[tauri::command] #[tauri::command]
pub fn get_sys_proxy() -> CmdResult<Mapping> { pub fn get_sys_proxy() -> CmdResult<Mapping> {

View File

@ -1,48 +0,0 @@
/// 给clash内核的tun模式授权
#[cfg(any(target_os = "macos", target_os = "linux"))]
pub fn grant_permission(core: String) -> anyhow::Result<()> {
use std::process::Command;
use tauri::utils::platform::current_exe;
let path = current_exe()?.with_file_name(core).canonicalize()?;
let path = path.display().to_string();
log::debug!("grant_permission path: {path}");
#[cfg(target_os = "macos")]
let output = {
let path = path.replace(' ', "\\\\ ");
let shell = format!("chown root:admin {path}\nchmod +sx {path}");
let command = format!(r#"do shell script "{shell}" with administrator privileges"#);
Command::new("osascript")
.args(vec!["-e", &command])
.output()?
};
#[cfg(target_os = "linux")]
let output = {
let path = path.replace(' ', "\\ "); // 避免路径中有空格
let shell =
format!("setcap cap_net_bind_service,cap_net_admin,cap_dac_override=+ep {path}");
let sudo = match Command::new("which").arg("pkexec").output() {
Ok(output) => {
if output.stdout.is_empty() {
"sudo"
} else {
"pkexec"
}
}
Err(_) => "sudo",
};
Command::new(sudo).arg("sh").arg("-c").arg(shell).output()?
};
if output.status.success() {
Ok(())
} else {
let stderr = std::str::from_utf8(&output.stderr).unwrap_or("");
anyhow::bail!("{stderr}");
}
}

View File

@ -4,7 +4,6 @@ mod core;
pub mod handle; pub mod handle;
pub mod hotkey; pub mod hotkey;
pub mod logger; pub mod logger;
pub mod manager;
pub mod service; pub mod service;
pub mod sysopt; pub mod sysopt;
pub mod timer; pub mod timer;

View File

@ -44,7 +44,6 @@ fn main() -> std::io::Result<()> {
cmds::get_portable_flag, cmds::get_portable_flag,
// cmds::kill_sidecar, // cmds::kill_sidecar,
cmds::restart_sidecar, cmds::restart_sidecar,
cmds::grant_permission,
// clash // clash
cmds::get_clash_info, cmds::get_clash_info,
cmds::get_clash_logs, cmds::get_clash_logs,

View File

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from "react"; import { useRef } from "react";
import { Box, Typography } from "@mui/material"; import { Box, Typography } from "@mui/material";
import { import {
ArrowDownward, ArrowDownward,
@ -10,8 +10,14 @@ import { useVerge } from "@/hooks/use-verge";
import { TrafficGraph, type TrafficRef } from "./traffic-graph"; import { TrafficGraph, type TrafficRef } from "./traffic-graph";
import { useLogSetup } from "./use-log-setup"; import { useLogSetup } from "./use-log-setup";
import { useVisibility } from "@/hooks/use-visibility"; import { useVisibility } from "@/hooks/use-visibility";
import { useWebsocket } from "@/hooks/use-websocket";
import parseTraffic from "@/utils/parse-traffic"; import parseTraffic from "@/utils/parse-traffic";
import useSWRSubscription from "swr/subscription";
import Sockette from "sockette";
interface MemoryUsage {
inuse: number;
oslimit?: number;
}
// setup the traffic // setup the traffic
export const LayoutTraffic = () => { export const LayoutTraffic = () => {
@ -22,51 +28,92 @@ export const LayoutTraffic = () => {
const trafficGraph = verge?.traffic_graph ?? true; const trafficGraph = verge?.traffic_graph ?? true;
const trafficRef = useRef<TrafficRef>(null); const trafficRef = useRef<TrafficRef>(null);
const [traffic, setTraffic] = useState({ up: 0, down: 0 });
const [memory, setMemory] = useState({ inuse: 0 });
const pageVisible = useVisibility(); const pageVisible = useVisibility();
// setup log ws during layout // setup log ws during layout
useLogSetup(); useLogSetup();
const trafficWs = useWebsocket( const { data: traffic = { up: 0, down: 0 } } = useSWRSubscription<
(event) => { ITrafficItem,
const data = JSON.parse(event.data) as ITrafficItem; any,
trafficRef.current?.appendData(data); "getRealtimeTraffic" | null
setTraffic(data); >(
clashInfo && pageVisible ? "getRealtimeTraffic" : null,
(_key, { next }) => {
const { server = "", secret = "" } = clashInfo!;
let errorCount = 10;
const s = new Sockette(
`ws://${server}/traffic?token=${encodeURIComponent(secret)}`,
{
onmessage(event) {
errorCount = 0; // reset counter
const data = JSON.parse(event.data) as ITrafficItem;
trafficRef.current?.appendData(data);
next(null, data);
},
onerror(event) {
errorCount -= 1;
if (errorCount <= 0) {
this.close();
next(event, { up: 0, down: 0 });
}
},
}
);
return () => {
s.close();
};
}, },
{ onError: () => setTraffic({ up: 0, down: 0 }), errorCount: 10 } {
fallbackData: { up: 0, down: 0 },
keepPreviousData: true,
}
); );
useEffect(() => {
if (!clashInfo || !pageVisible) return;
const { server = "", secret = "" } = clashInfo;
trafficWs.connect(
`ws://${server}/traffic?token=${encodeURIComponent(secret)}`
);
return () => trafficWs.disconnect();
}, [clashInfo, pageVisible]);
/* --------- meta memory information --------- */ /* --------- meta memory information --------- */
const isMetaCore = verge?.clash_core?.includes("clash-meta"); const isMetaCore = verge?.clash_core?.includes("clash-meta");
const displayMemory = isMetaCore && (verge?.enable_memory_usage ?? true); const displayMemory = isMetaCore && (verge?.enable_memory_usage ?? true);
const memoryWs = useWebsocket( const { data: memory = { inuse: 0 } } = useSWRSubscription<
(event) => { MemoryUsage,
setMemory(JSON.parse(event.data)); any,
}, "getRealtimeMemory" | null
{ onError: () => setMemory({ inuse: 0 }), errorCount: 10 } >(
); clashInfo && pageVisible && displayMemory ? "getRealtimeMemory" : null,
(_key, { next }) => {
const { server = "", secret = "" } = clashInfo!;
const ws = new WebSocket(
`ws://${server}/memory?token=${encodeURIComponent(secret)}`
);
useEffect(() => { let errorCount = 10;
if (!clashInfo || !pageVisible || !displayMemory) return;
const { server = "", secret = "" } = clashInfo; ws.addEventListener("message", (event) => {
memoryWs.connect( errorCount = 0; // reset counter
`ws://${server}/memory?token=${encodeURIComponent(secret)}` next(null, JSON.parse(event.data));
); });
return () => memoryWs.disconnect(); ws.addEventListener("error", (event) => {
}, [clashInfo, pageVisible, displayMemory]); errorCount -= 1;
if (errorCount <= 0) {
ws.close();
next(event, { inuse: 0 });
}
});
return () => {
ws.close();
};
},
{
fallbackData: { inuse: 0 },
keepPreviousData: true,
}
);
const [up, upUnit] = parseTraffic(traffic.up); const [up, upUnit] = parseTraffic(traffic.up);
const [down, downUnit] = parseTraffic(traffic.down); const [down, downUnit] = parseTraffic(traffic.down);

View File

@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next";
import { Box, Button, Typography } from "@mui/material"; import { Box, Button, Typography } from "@mui/material";
interface Props { interface Props {
onChange: (value: string) => void; onChange: (file: File, value: string) => void;
} }
export const FileInput = (props: Props) => { export const FileInput = (props: Props) => {
@ -28,7 +28,7 @@ export const FileInput = (props: Props) => {
const reader = new FileReader(); const reader = new FileReader();
reader.onload = (event) => { reader.onload = (event) => {
resolve(null); resolve(null);
onChange(event.target?.result as string); onChange(file, event.target?.result as string);
}; };
reader.onerror = reject; reader.onerror = reject;
reader.readAsText(file); reader.readAsText(file);

View File

@ -246,7 +246,15 @@ export const ProfileViewer = forwardRef<ProfileViewerRef, Props>(
)} )}
{isLocal && openType === "new" && ( {isLocal && openType === "new" && (
<FileInput onChange={(val) => (fileDataRef.current = val)} /> <FileInput
onChange={(file, val) => {
if (!formIns.getValues("name")) {
const name = file.name.substring(0, file.name.lastIndexOf("."));
formIns.setValue("name", name);
}
fileDataRef.current = val;
}}
/>
)} )}
{isRemote && ( {isRemote && (

View File

@ -6,17 +6,9 @@ import { useVerge } from "@/hooks/use-verge";
import { useLockFn } from "ahooks"; import { useLockFn } from "ahooks";
import { LoadingButton } from "@mui/lab"; import { LoadingButton } from "@mui/lab";
import { SwitchAccessShortcut, RestartAlt } from "@mui/icons-material"; import { SwitchAccessShortcut, RestartAlt } from "@mui/icons-material";
import { import { Box, Button, List, ListItemButton, ListItemText } from "@mui/material";
Box,
Button,
Tooltip,
List,
ListItemButton,
ListItemText,
} from "@mui/material";
import { changeClashCore, restartSidecar } from "@/services/cmds"; import { changeClashCore, restartSidecar } from "@/services/cmds";
import { closeAllConnections, upgradeCore } from "@/services/api"; import { closeAllConnections, upgradeCore } from "@/services/api";
import { grantPermission } from "@/services/cmds";
import getSystem from "@/utils/get-system"; import getSystem from "@/utils/get-system";
const VALID_CORE = [ const VALID_CORE = [
@ -58,17 +50,6 @@ export const ClashCoreViewer = forwardRef<DialogRef>((props, ref) => {
} }
}); });
const onGrant = useLockFn(async (core: string) => {
try {
await grantPermission(core);
// 自动重启
if (core === clash_core) await restartSidecar();
Notice.success(t("Permissions Granted Successfully for _clash Core", { core: `${core}` }), 1000);
} catch (err: any) {
Notice.error(err?.message || err.toString());
}
});
const onRestart = useLockFn(async () => { const onRestart = useLockFn(async () => {
try { try {
await restartSidecar(); await restartSidecar();
@ -140,22 +121,6 @@ export const ClashCoreViewer = forwardRef<DialogRef>((props, ref) => {
onClick={() => onCoreChange(each.core)} onClick={() => onCoreChange(each.core)}
> >
<ListItemText primary={each.name} secondary={`/${each.core}`} /> <ListItemText primary={each.name} secondary={`/${each.core}`} />
{(OS === "macos" || OS === "linux") && (
<Tooltip title={t("Tun mode requires")}>
<Button
variant="outlined"
size="small"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onGrant(each.core);
}}
>
{t("Grant")}
</Button>
</Tooltip>
)}
</ListItemButton> </ListItemButton>
))} ))}
</List> </List>

View File

@ -41,8 +41,11 @@ export const ServiceViewer = forwardRef<DialogRef, Props>((props, ref) => {
const onInstall = useLockFn(async () => { const onInstall = useLockFn(async () => {
try { try {
await installService(); await installService();
mutateCheck(); await mutateCheck();
setOpen(false); setOpen(false);
setTimeout(() => {
mutateCheck();
}, 2000);
Notice.success(t("Service Installed Successfully")); Notice.success(t("Service Installed Successfully"));
} catch (err: any) { } catch (err: any) {
mutateCheck(); mutateCheck();

View File

@ -4,6 +4,7 @@ import { checkService } from "@/services/cmds";
import { useVerge } from "@/hooks/use-verge"; import { useVerge } from "@/hooks/use-verge";
import getSystem from "@/utils/get-system"; import getSystem from "@/utils/get-system";
import useSWR from "swr"; import useSWR from "swr";
import { useEffect } from "react";
const isWIN = getSystem() === "windows"; const isWIN = getSystem() === "windows";
@ -17,7 +18,7 @@ export const StackModeSwitch = (props: Props) => {
const { verge } = useVerge(); const { verge } = useVerge();
const { enable_service_mode } = verge ?? {}; const { enable_service_mode } = verge ?? {};
// service mode // service mode
const { data: serviceStatus } = useSWR( const { data: serviceStatus, mutate: mutateCheck } = useSWR(
isWIN ? "checkService" : null, isWIN ? "checkService" : null,
checkService, checkService,
{ {
@ -28,6 +29,10 @@ export const StackModeSwitch = (props: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
useEffect(() => {
mutateCheck();
}, []);
return ( return (
<Tooltip <Tooltip
title={ title={

View File

@ -23,7 +23,7 @@ export const WebUIViewer = forwardRef<DialogRef>((props, ref) => {
})); }));
const webUIList = verge?.web_ui_list || [ const webUIList = verge?.web_ui_list || [
"https://d.metacubex.one/#?hostname=%host&port=%port&secret=%secret", "https://metacubex.github.io/metacubexd/#/setup?http=true&hostname=%host&port=%port&secret=%secret",
"https://yacd.metacubex.one/?host=%host&port=%port&secret=%secret", "https://yacd.metacubex.one/?host=%host&port=%port&secret=%secret",
]; ];

View File

@ -142,10 +142,6 @@ export async function restartSidecar() {
return invoke<void>("restart_sidecar"); return invoke<void>("restart_sidecar");
} }
export async function grantPermission(core: string) {
return invoke<void>("grant_permission", { core });
}
export async function getAppDir() { export async function getAppDir() {
return invoke<string>("get_app_dir"); return invoke<string>("get_app_dir");
} }