refactor: invock mihomo api by use tauri-plugin-mihomo (#4926)
* feat: add tauri-plugin-mihomo * refactor: invock mihomo api by use tauri-plugin-mihomo * chore: todo * chore: update * chore: update * chore: update * chore: update * fix: incorrect delay status and update pretty config * chore: update * chore: remove cache * chore: update * chore: update * fix: app freezed when change group proxy * chore: update * chore: update * chore: add rustfmt.toml to tauri-plugin-mihomo * chore: happy clippy * refactor: connect mihomo websocket * chore: update * chore: update * fix: parse bigint to number * chore: update * Revert "fix: parse bigint to number" This reverts commit 74c006522e23aa52cf8979a8fb47d2b1ae0bb043. * chore: use number instead of bigint * chore: cleanup * fix: rule data not refresh when switch profile * chore: update * chore: cleanup * chore: update * fix: traffic graph data display * feat: add ipc connection pool * chore: update * chore: clippy * fix: incorrect delay status * fix: typo * fix: empty proxies tray menu * chore: clippy * chore: import tauri-plugin-mihomo by using git repo * chore: cleanup * fix: mihomo api * fix: incorrect delay status * chore: update tauri-plugin-mihomo dep chore: update
This commit is contained in:
@@ -4,8 +4,8 @@ import dayjs from "dayjs";
|
||||
import { t } from "i18next";
|
||||
import { useImperativeHandle, useState, type Ref } from "react";
|
||||
|
||||
import { deleteConnection } from "@/services/cmds";
|
||||
import parseTraffic from "@/utils/parse-traffic";
|
||||
import { closeConnections } from "tauri-plugin-mihomo-api";
|
||||
|
||||
export interface ConnectionDetailRef {
|
||||
open: (detail: IConnectionsItem) => void;
|
||||
@@ -97,7 +97,7 @@ const InnerConnectionDetail = ({ data, onClose }: InnerProps) => {
|
||||
{ label: t("Type"), value: `${metadata.type}(${metadata.network})` },
|
||||
];
|
||||
|
||||
const onDelete = useLockFn(async () => deleteConnection(data.id));
|
||||
const onDelete = useLockFn(async () => closeConnections(data.id));
|
||||
|
||||
return (
|
||||
<Box sx={{ userSelect: "text", color: theme.palette.text.secondary }}>
|
||||
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
import { useLockFn } from "ahooks";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { deleteConnection } from "@/services/cmds";
|
||||
import parseTraffic from "@/utils/parse-traffic";
|
||||
import { closeConnections } from "tauri-plugin-mihomo-api";
|
||||
|
||||
const Tag = styled("span")(({ theme }) => ({
|
||||
fontSize: "10px",
|
||||
@@ -34,7 +34,7 @@ export const ConnectionItem = (props: Props) => {
|
||||
|
||||
const { id, metadata, chains, start, curUpload, curDownload } = value;
|
||||
|
||||
const onDelete = useLockFn(async () => deleteConnection(id));
|
||||
const onDelete = useLockFn(async () => closeConnections(id));
|
||||
const showTraffic = curUpload! >= 100 || curDownload! >= 100;
|
||||
|
||||
return (
|
||||
|
||||
@@ -53,7 +53,7 @@ export const ClashInfoCard = () => {
|
||||
{t("Mixed Port")}
|
||||
</Typography>
|
||||
<Typography variant="body2" fontWeight="medium">
|
||||
{clashConfig["mixed-port"] || "-"}
|
||||
{clashConfig.mixedPort || "-"}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Divider />
|
||||
|
||||
@@ -7,10 +7,11 @@ import { Box, Paper, Stack, Typography } from "@mui/material";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { closeAllConnections } from "tauri-plugin-mihomo-api";
|
||||
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { useAppData } from "@/providers/app-data-context";
|
||||
import { closeAllConnections, patchClashMode } from "@/services/cmds";
|
||||
import { patchClashMode } from "@/services/cmds";
|
||||
|
||||
export const ClashModeCard = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -36,8 +36,8 @@ import { EnhancedCard } from "@/components/home/enhanced-card";
|
||||
import { useProxySelection } from "@/hooks/use-proxy-selection";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { useAppData } from "@/providers/app-data-context";
|
||||
import { getGroupProxyDelays, providerHealthCheck } from "@/services/cmds";
|
||||
import delayManager from "@/services/delay";
|
||||
import { delayGroup, healthcheckProxyProvider } from "tauri-plugin-mihomo-api";
|
||||
|
||||
// 本地存储的键名
|
||||
const STORAGE_KEY_GROUP = "clash-verge-selected-proxy-group";
|
||||
@@ -466,7 +466,7 @@ export const CurrentProxyCard = () => {
|
||||
if (providers.size > 0) {
|
||||
console.log(`[CurrentProxyCard] 开始测试提供者节点`);
|
||||
await Promise.allSettled(
|
||||
[...providers].map((p) => providerHealthCheck(p)),
|
||||
[...providers].map((p) => healthcheckProxyProvider(p)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -478,7 +478,7 @@ export const CurrentProxyCard = () => {
|
||||
try {
|
||||
await Promise.race([
|
||||
delayManager.checkListDelay(proxyNames, groupName, timeout),
|
||||
getGroupProxyDelays(groupName, url, timeout),
|
||||
delayGroup(groupName, url, timeout),
|
||||
]);
|
||||
console.log(`[CurrentProxyCard] 延迟测试完成,组: ${groupName}`);
|
||||
} catch (error) {
|
||||
|
||||
@@ -92,7 +92,7 @@ export const EnhancedCanvasTrafficGraph = memo(
|
||||
const { t } = useTranslation();
|
||||
|
||||
// 使用增强版全局流量数据管理
|
||||
const { dataPoints, getDataForTimeRange, isDataFresh, samplerStats } =
|
||||
const { dataPoints, getDataForTimeRange, samplerStats } =
|
||||
useTrafficGraphDataEnhanced();
|
||||
|
||||
// 基础状态
|
||||
@@ -865,6 +865,7 @@ export const EnhancedCanvasTrafficGraph = memo(
|
||||
}}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
onClick={toggleStyle}
|
||||
/>
|
||||
|
||||
{/* 控制层覆盖 */}
|
||||
@@ -962,8 +963,8 @@ export const EnhancedCanvasTrafficGraph = memo(
|
||||
lineHeight: 1.2,
|
||||
}}
|
||||
>
|
||||
Points: {displayData.length} | Fresh: {isDataFresh ? "✓" : "✗"} |
|
||||
Compressed: {samplerStats.compressedBufferSize}
|
||||
Points: {displayData.length} | Compressed:{" "}
|
||||
{samplerStats.compressedBufferSize}
|
||||
</Box>
|
||||
|
||||
{/* 悬浮提示框 */}
|
||||
@@ -988,6 +989,7 @@ export const EnhancedCanvasTrafficGraph = memo(
|
||||
boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
|
||||
backdropFilter: "none",
|
||||
opacity: 1,
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
<Box color="text.secondary" mb={0.2}>
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
MemoryRounded,
|
||||
} from "@mui/icons-material";
|
||||
import {
|
||||
Box,
|
||||
Grid,
|
||||
PaletteColor,
|
||||
Paper,
|
||||
@@ -15,16 +14,16 @@ import {
|
||||
alpha,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { ReactNode, memo, useCallback, useMemo, useRef } from "react";
|
||||
import { useRef, memo, useMemo } from "react";
|
||||
import { ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import useSWR from "swr";
|
||||
|
||||
import { TrafficErrorBoundary } from "@/components/common/traffic-error-boundary";
|
||||
import { useTrafficDataEnhanced } from "@/hooks/use-traffic-monitor";
|
||||
import { useConnectionData } from "@/hooks/use-connection-data";
|
||||
import { useMemoryData } from "@/hooks/use-memory-data";
|
||||
import { useTrafficData } from "@/hooks/use-traffic-data";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { useVisibility } from "@/hooks/use-visibility";
|
||||
import { useAppData } from "@/providers/app-data-context";
|
||||
import { gc, isDebugEnabled } from "@/services/cmds";
|
||||
import parseTraffic from "@/utils/parse-traffic";
|
||||
|
||||
import {
|
||||
@@ -148,51 +147,33 @@ export const EnhancedTrafficStats = () => {
|
||||
const trafficRef = useRef<EnhancedCanvasTrafficGraphRef>(null);
|
||||
const pageVisible = useVisibility();
|
||||
|
||||
// 使用AppDataProvider
|
||||
const { connections } = useAppData();
|
||||
const {
|
||||
response: { data: traffic },
|
||||
} = useTrafficData();
|
||||
|
||||
// 使用增强版的统一流量数据Hook
|
||||
const { traffic, memory, isLoading, isDataFresh, hasValidData } =
|
||||
useTrafficDataEnhanced();
|
||||
const {
|
||||
response: { data: memory },
|
||||
} = useMemoryData();
|
||||
|
||||
const {
|
||||
response: { data: connections },
|
||||
} = useConnectionData();
|
||||
|
||||
// 是否显示流量图表
|
||||
const trafficGraph = verge?.traffic_graph ?? true;
|
||||
|
||||
// 检查是否支持调试
|
||||
// TODO: merge this hook with layout-traffic.tsx
|
||||
const { data: isDebug } = useSWR(
|
||||
`clash-verge-rev-internal://isDebugEnabled`,
|
||||
() => isDebugEnabled(),
|
||||
{
|
||||
// default value before is fetched
|
||||
fallbackData: false,
|
||||
},
|
||||
);
|
||||
|
||||
// Canvas组件现在直接从全局Hook获取数据,无需手动添加数据点
|
||||
|
||||
// 执行垃圾回收
|
||||
const handleGarbageCollection = useCallback(async () => {
|
||||
if (isDebug) {
|
||||
try {
|
||||
await gc();
|
||||
console.log("[Debug] 垃圾回收已执行");
|
||||
} catch (err) {
|
||||
console.error("[Debug] 垃圾回收失败:", err);
|
||||
}
|
||||
}
|
||||
}, [isDebug]);
|
||||
|
||||
// 使用useMemo计算解析后的流量数据
|
||||
const parsedData = useMemo(() => {
|
||||
const [up, upUnit] = parseTraffic(traffic?.raw?.up_rate || 0);
|
||||
const [down, downUnit] = parseTraffic(traffic?.raw?.down_rate || 0);
|
||||
const [inuse, inuseUnit] = parseTraffic(memory?.raw?.inuse || 0);
|
||||
const [up, upUnit] = parseTraffic(traffic?.up || 0);
|
||||
const [down, downUnit] = parseTraffic(traffic?.down || 0);
|
||||
const [inuse, inuseUnit] = parseTraffic(memory?.inuse || 0);
|
||||
const [uploadTotal, uploadTotalUnit] = parseTraffic(
|
||||
connections.uploadTotal,
|
||||
connections?.uploadTotal,
|
||||
);
|
||||
const [downloadTotal, downloadTotalUnit] = parseTraffic(
|
||||
connections.downloadTotal,
|
||||
connections?.downloadTotal,
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -206,7 +187,7 @@ export const EnhancedTrafficStats = () => {
|
||||
uploadTotalUnit,
|
||||
downloadTotal,
|
||||
downloadTotalUnit,
|
||||
connectionsCount: connections.count,
|
||||
connectionsCount: connections?.connections.length,
|
||||
};
|
||||
}, [traffic, memory, connections]);
|
||||
|
||||
@@ -228,33 +209,10 @@ export const EnhancedTrafficStats = () => {
|
||||
>
|
||||
<div style={{ height: "100%", position: "relative" }}>
|
||||
<EnhancedCanvasTrafficGraph ref={trafficRef} />
|
||||
{isDebug && (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "2px",
|
||||
left: "2px",
|
||||
zIndex: 10,
|
||||
backgroundColor: "rgba(0,0,0,0.5)",
|
||||
color: "white",
|
||||
fontSize: "8px",
|
||||
padding: "2px 4px",
|
||||
borderRadius: "4px",
|
||||
}}
|
||||
>
|
||||
DEBUG: {trafficRef.current ? "图表已初始化" : "图表未初始化"}
|
||||
<br />
|
||||
状态: {isDataFresh ? "active" : "inactive"}
|
||||
<br />
|
||||
数据新鲜度: {traffic?.is_fresh ? "Fresh" : "Stale"}
|
||||
<br />
|
||||
{new Date().toISOString().slice(11, 19)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Paper>
|
||||
);
|
||||
}, [trafficGraph, pageVisible, theme.palette.divider, isDebug]);
|
||||
}, [trafficGraph, pageVisible, theme.palette.divider]);
|
||||
|
||||
// 使用useMemo计算统计卡片配置
|
||||
const statCards = useMemo(
|
||||
@@ -300,10 +258,10 @@ export const EnhancedTrafficStats = () => {
|
||||
value: parsedData.inuse,
|
||||
unit: parsedData.inuseUnit,
|
||||
color: "error" as const,
|
||||
onClick: isDebug ? handleGarbageCollection : undefined,
|
||||
onClick: undefined,
|
||||
},
|
||||
],
|
||||
[t, parsedData, isDebug, handleGarbageCollection],
|
||||
[t, parsedData],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -320,28 +278,11 @@ export const EnhancedTrafficStats = () => {
|
||||
</Grid>
|
||||
)}
|
||||
{/* 统计卡片区域 */}
|
||||
{statCards.map((card, index) => (
|
||||
<Grid key={index} size={4}>
|
||||
<CompactStatCard {...card} />
|
||||
{statCards.map((card, _index) => (
|
||||
<Grid key={card.title} size={4}>
|
||||
<CompactStatCard {...(card as StatCardProps)} />
|
||||
</Grid>
|
||||
))}
|
||||
|
||||
{/* 数据状态指示器(调试用)*/}
|
||||
{isDebug && (
|
||||
<Grid size={12}>
|
||||
<Box
|
||||
sx={{
|
||||
p: 1,
|
||||
bgcolor: "action.hover",
|
||||
borderRadius: 1,
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
数据状态: {isDataFresh ? "新鲜" : "过期"} | 有效数据:{" "}
|
||||
{hasValidData ? "是" : "否"} | 加载中: {isLoading ? "是" : "否"}
|
||||
</Box>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</TrafficErrorBoundary>
|
||||
);
|
||||
|
||||
@@ -6,34 +6,19 @@ import {
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import useSWR from "swr";
|
||||
|
||||
import { LightweightTrafficErrorBoundary } from "@/components/common/traffic-error-boundary";
|
||||
import { useClashInfo } from "@/hooks/use-clash";
|
||||
import { useTrafficDataEnhanced } from "@/hooks/use-traffic-monitor";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { useVisibility } from "@/hooks/use-visibility";
|
||||
import { isDebugEnabled, gc, startTrafficService } from "@/services/cmds";
|
||||
import parseTraffic from "@/utils/parse-traffic";
|
||||
|
||||
import { TrafficGraph, type TrafficRef } from "./traffic-graph";
|
||||
import { useTrafficData } from "@/hooks/use-traffic-data";
|
||||
import { useMemoryData } from "@/hooks/use-memory-data";
|
||||
|
||||
// setup the traffic
|
||||
export const LayoutTraffic = () => {
|
||||
const { data: isDebug } = useSWR(
|
||||
"clash-verge-rev-internal://isDebugEnabled",
|
||||
() => isDebugEnabled(),
|
||||
{
|
||||
// default value before is fetched
|
||||
fallbackData: false,
|
||||
},
|
||||
);
|
||||
|
||||
if (isDebug) {
|
||||
console.debug("[Traffic][LayoutTraffic] 组件正在渲染");
|
||||
}
|
||||
const { t } = useTranslation();
|
||||
const { clashInfo } = useClashInfo();
|
||||
const { verge } = useVerge();
|
||||
|
||||
// whether hide traffic graph
|
||||
@@ -42,31 +27,19 @@ export const LayoutTraffic = () => {
|
||||
const trafficRef = useRef<TrafficRef>(null);
|
||||
const pageVisible = useVisibility();
|
||||
|
||||
// 使用增强版的统一流量数据Hook
|
||||
const { traffic, memory } = useTrafficDataEnhanced();
|
||||
|
||||
// 启动流量服务
|
||||
useEffect(() => {
|
||||
console.log(
|
||||
"[Traffic][LayoutTraffic] useEffect 触发,clashInfo:",
|
||||
clashInfo,
|
||||
"pageVisible:",
|
||||
pageVisible,
|
||||
);
|
||||
|
||||
// 简化条件,只要组件挂载就尝试启动服务
|
||||
console.log("[Traffic][LayoutTraffic] 开始启动流量服务");
|
||||
startTrafficService().catch((error) => {
|
||||
console.error("[Traffic][LayoutTraffic] 启动流量服务失败:", error);
|
||||
});
|
||||
}, []); // 移除依赖,只在组件挂载时启动一次
|
||||
const {
|
||||
response: { data: traffic },
|
||||
} = useTrafficData();
|
||||
const {
|
||||
response: { data: memory },
|
||||
} = useMemoryData();
|
||||
|
||||
// 监听数据变化,为图表添加数据点
|
||||
useEffect(() => {
|
||||
if (traffic?.raw && trafficRef.current) {
|
||||
if (trafficRef.current) {
|
||||
trafficRef.current.appendData({
|
||||
up: traffic.raw.up_rate || 0,
|
||||
down: traffic.raw.down_rate || 0,
|
||||
up: traffic?.up || 0,
|
||||
down: traffic?.down || 0,
|
||||
});
|
||||
}
|
||||
}, [traffic]);
|
||||
@@ -75,9 +48,9 @@ export const LayoutTraffic = () => {
|
||||
const displayMemory = verge?.enable_memory_usage ?? true;
|
||||
|
||||
// 使用parseTraffic统一处理转换,保持与首页一致的显示格式
|
||||
const [up, upUnit] = parseTraffic(traffic?.raw?.up_rate || 0);
|
||||
const [down, downUnit] = parseTraffic(traffic?.raw?.down_rate || 0);
|
||||
const [inuse, inuseUnit] = parseTraffic(memory?.raw?.inuse || 0);
|
||||
const [up, upUnit] = parseTraffic(traffic?.up || 0);
|
||||
const [down, downUnit] = parseTraffic(traffic?.down || 0);
|
||||
const [inuse, inuseUnit] = parseTraffic(memory?.inuse || 0);
|
||||
|
||||
const boxStyle: any = {
|
||||
display: "flex",
|
||||
@@ -114,18 +87,16 @@ export const LayoutTraffic = () => {
|
||||
|
||||
<Box display="flex" flexDirection="column" gap={0.75}>
|
||||
<Box
|
||||
title={`${t("Upload Speed")} ${traffic?.is_fresh ? "" : "(Stale)"}`}
|
||||
title={`${t("Upload Speed")}`}
|
||||
{...boxStyle}
|
||||
sx={{
|
||||
...boxStyle.sx,
|
||||
opacity: traffic?.is_fresh ? 1 : 0.6,
|
||||
// opacity: traffic?.is_fresh ? 1 : 0.6,
|
||||
}}
|
||||
>
|
||||
<ArrowUpwardRounded
|
||||
{...iconStyle}
|
||||
color={
|
||||
(traffic?.raw?.up_rate || 0) > 0 ? "secondary" : "disabled"
|
||||
}
|
||||
color={(traffic?.up || 0) > 0 ? "secondary" : "disabled"}
|
||||
/>
|
||||
<Typography {...valStyle} color="secondary">
|
||||
{up}
|
||||
@@ -134,18 +105,16 @@ export const LayoutTraffic = () => {
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
title={`${t("Download Speed")} ${traffic?.is_fresh ? "" : "(Stale)"}`}
|
||||
title={`${t("Download Speed")}`}
|
||||
{...boxStyle}
|
||||
sx={{
|
||||
...boxStyle.sx,
|
||||
opacity: traffic?.is_fresh ? 1 : 0.6,
|
||||
// opacity: traffic?.is_fresh ? 1 : 0.6,
|
||||
}}
|
||||
>
|
||||
<ArrowDownwardRounded
|
||||
{...iconStyle}
|
||||
color={
|
||||
(traffic?.raw?.down_rate || 0) > 0 ? "primary" : "disabled"
|
||||
}
|
||||
color={(traffic?.down || 0) > 0 ? "primary" : "disabled"}
|
||||
/>
|
||||
<Typography {...valStyle} color="primary">
|
||||
{down}
|
||||
@@ -155,15 +124,15 @@ export const LayoutTraffic = () => {
|
||||
|
||||
{displayMemory && (
|
||||
<Box
|
||||
title={`${t(isDebug ? "Memory Cleanup" : "Memory Usage")} ${memory?.is_fresh ? "" : "(Stale)"} ${"usage_percent" in (memory?.formatted || {}) && memory.formatted.usage_percent ? `(${memory.formatted.usage_percent.toFixed(1)}%)` : ""}`}
|
||||
title={`${t("Memory Usage")} `}
|
||||
{...boxStyle}
|
||||
sx={{
|
||||
cursor: isDebug ? "pointer" : "auto",
|
||||
opacity: memory?.is_fresh ? 1 : 0.6,
|
||||
cursor: "auto",
|
||||
// opacity: memory?.is_fresh ? 1 : 0.6,
|
||||
}}
|
||||
color={isDebug ? "success.main" : "disabled"}
|
||||
color={"disabled"}
|
||||
onClick={async () => {
|
||||
isDebug && (await gc());
|
||||
// isDebug && (await gc());
|
||||
}}
|
||||
>
|
||||
<MemoryRounded {...iconStyle} />
|
||||
|
||||
@@ -20,26 +20,12 @@ import { useLockFn } from "ahooks";
|
||||
import dayjs from "dayjs";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { updateProxyProvider } from "tauri-plugin-mihomo-api";
|
||||
|
||||
import { useAppData } from "@/providers/app-data-context";
|
||||
import { proxyProviderUpdate } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import parseTraffic from "@/utils/parse-traffic";
|
||||
|
||||
// 定义代理提供者类型
|
||||
interface ProxyProviderItem {
|
||||
name?: string;
|
||||
proxies: any[];
|
||||
updatedAt: number;
|
||||
vehicleType: string;
|
||||
subscriptionInfo?: {
|
||||
Upload: number;
|
||||
Download: number;
|
||||
Total: number;
|
||||
Expire: number;
|
||||
};
|
||||
}
|
||||
|
||||
// 样式化组件 - 类型框
|
||||
const TypeBox = styled(Box)<{ component?: React.ElementType }>(({ theme }) => ({
|
||||
display: "inline-block",
|
||||
@@ -74,7 +60,7 @@ export const ProviderButton = () => {
|
||||
// 设置更新状态
|
||||
setUpdating((prev) => ({ ...prev, [name]: true }));
|
||||
|
||||
await proxyProviderUpdate(name);
|
||||
await updateProxyProvider(name);
|
||||
|
||||
// 刷新数据
|
||||
await refreshProxy();
|
||||
@@ -115,7 +101,7 @@ export const ProviderButton = () => {
|
||||
// 改为串行逐个更新所有provider
|
||||
for (const name of allProviders) {
|
||||
try {
|
||||
await proxyProviderUpdate(name);
|
||||
await updateProxyProvider(name);
|
||||
// 每个更新完成后更新状态
|
||||
setUpdating((prev) => ({ ...prev, [name]: false }));
|
||||
} catch (err) {
|
||||
@@ -177,161 +163,164 @@ export const ProviderButton = () => {
|
||||
|
||||
<DialogContent>
|
||||
<List sx={{ py: 0, minHeight: 250 }}>
|
||||
{Object.entries(proxyProviders || {}).map(([key, item]) => {
|
||||
const provider = item as ProxyProviderItem;
|
||||
const time = dayjs(provider.updatedAt);
|
||||
const isUpdating = updating[key];
|
||||
{Object.entries(proxyProviders || {})
|
||||
.sort()
|
||||
.map(([key, item]) => {
|
||||
const provider = item;
|
||||
const time = dayjs(provider.updatedAt);
|
||||
const isUpdating = updating[key];
|
||||
|
||||
// 订阅信息
|
||||
const sub = provider.subscriptionInfo;
|
||||
const hasSubInfo = !!sub;
|
||||
const upload = sub?.Upload || 0;
|
||||
const download = sub?.Download || 0;
|
||||
const total = sub?.Total || 0;
|
||||
const expire = sub?.Expire || 0;
|
||||
// 订阅信息
|
||||
const sub = provider.subscriptionInfo;
|
||||
const hasSubInfo = !!sub;
|
||||
const upload = sub?.Upload || 0;
|
||||
const download = sub?.Download || 0;
|
||||
const total = sub?.Total || 0;
|
||||
const expire = sub?.Expire || 0;
|
||||
|
||||
// 流量使用进度
|
||||
const progress =
|
||||
total > 0
|
||||
? Math.min(
|
||||
Math.round(((download + upload) * 100) / total) + 1,
|
||||
100,
|
||||
)
|
||||
: 0;
|
||||
// 流量使用进度
|
||||
const progress =
|
||||
total > 0
|
||||
? Math.min(
|
||||
Math.round(((download + upload) * 100) / total) + 1,
|
||||
100,
|
||||
)
|
||||
: 0;
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
key={key}
|
||||
sx={[
|
||||
{
|
||||
p: 0,
|
||||
mb: "8px",
|
||||
borderRadius: 2,
|
||||
overflow: "hidden",
|
||||
transition: "all 0.2s",
|
||||
},
|
||||
({ palette: { mode, primary } }) => {
|
||||
const bgcolor = mode === "light" ? "#ffffff" : "#24252f";
|
||||
const hoverColor =
|
||||
mode === "light"
|
||||
? alpha(primary.main, 0.1)
|
||||
: alpha(primary.main, 0.2);
|
||||
return (
|
||||
<ListItem
|
||||
key={key}
|
||||
sx={[
|
||||
{
|
||||
p: 0,
|
||||
mb: "8px",
|
||||
borderRadius: 2,
|
||||
overflow: "hidden",
|
||||
transition: "all 0.2s",
|
||||
},
|
||||
({ palette: { mode, primary } }) => {
|
||||
const bgcolor =
|
||||
mode === "light" ? "#ffffff" : "#24252f";
|
||||
const hoverColor =
|
||||
mode === "light"
|
||||
? alpha(primary.main, 0.1)
|
||||
: alpha(primary.main, 0.2);
|
||||
|
||||
return {
|
||||
backgroundColor: bgcolor,
|
||||
"&:hover": {
|
||||
backgroundColor: hoverColor,
|
||||
},
|
||||
};
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ListItemText
|
||||
sx={{ px: 2, py: 1 }}
|
||||
primary={
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
component="div"
|
||||
noWrap
|
||||
title={key}
|
||||
sx={{ display: "flex", alignItems: "center" }}
|
||||
>
|
||||
<span style={{ marginRight: "8px" }}>{key}</span>
|
||||
<TypeBox component="span">
|
||||
{provider.proxies.length}
|
||||
</TypeBox>
|
||||
<TypeBox component="span">
|
||||
{provider.vehicleType}
|
||||
</TypeBox>
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
noWrap
|
||||
>
|
||||
<small>{t("Update At")}: </small>
|
||||
{time.fromNow()}
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
secondary={
|
||||
<>
|
||||
{/* 订阅信息 */}
|
||||
{hasSubInfo && (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
mb: 1,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<span title={t("Used / Total") as string}>
|
||||
{parseTraffic(upload + download)} /{" "}
|
||||
{parseTraffic(total)}
|
||||
</span>
|
||||
<span title={t("Expire Time") as string}>
|
||||
{parseExpire(expire)}
|
||||
</span>
|
||||
</Box>
|
||||
|
||||
{/* 进度条 */}
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={progress}
|
||||
sx={{
|
||||
height: 6,
|
||||
borderRadius: 3,
|
||||
opacity: total > 0 ? 1 : 0,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Divider orientation="vertical" flexItem />
|
||||
<Box
|
||||
sx={{
|
||||
width: 40,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
return {
|
||||
backgroundColor: bgcolor,
|
||||
"&:hover": {
|
||||
backgroundColor: hoverColor,
|
||||
},
|
||||
};
|
||||
},
|
||||
]}
|
||||
>
|
||||
<IconButton
|
||||
size="small"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
updateProvider(key);
|
||||
}}
|
||||
disabled={isUpdating}
|
||||
<ListItemText
|
||||
sx={{ px: 2, py: 1 }}
|
||||
primary={
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
component="div"
|
||||
noWrap
|
||||
title={key}
|
||||
sx={{ display: "flex", alignItems: "center" }}
|
||||
>
|
||||
<span style={{ marginRight: "8px" }}>{key}</span>
|
||||
<TypeBox component="span">
|
||||
{provider.proxies.length}
|
||||
</TypeBox>
|
||||
<TypeBox component="span">
|
||||
{provider.vehicleType}
|
||||
</TypeBox>
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
noWrap
|
||||
>
|
||||
<small>{t("Update At")}: </small>
|
||||
{time.fromNow()}
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
secondary={
|
||||
<>
|
||||
{/* 订阅信息 */}
|
||||
{hasSubInfo && (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
mb: 1,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<span title={t("Used / Total") as string}>
|
||||
{parseTraffic(upload + download)} /{" "}
|
||||
{parseTraffic(total)}
|
||||
</span>
|
||||
<span title={t("Expire Time") as string}>
|
||||
{parseExpire(expire)}
|
||||
</span>
|
||||
</Box>
|
||||
|
||||
{/* 进度条 */}
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={progress}
|
||||
sx={{
|
||||
height: 6,
|
||||
borderRadius: 3,
|
||||
opacity: total > 0 ? 1 : 0,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Divider orientation="vertical" flexItem />
|
||||
<Box
|
||||
sx={{
|
||||
animation: isUpdating
|
||||
? "spin 1s linear infinite"
|
||||
: "none",
|
||||
"@keyframes spin": {
|
||||
"0%": { transform: "rotate(0deg)" },
|
||||
"100%": { transform: "rotate(360deg)" },
|
||||
},
|
||||
width: 40,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
title={t("Update Provider") as string}
|
||||
>
|
||||
<RefreshRounded />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
<IconButton
|
||||
size="small"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
updateProvider(key);
|
||||
}}
|
||||
disabled={isUpdating}
|
||||
sx={{
|
||||
animation: isUpdating
|
||||
? "spin 1s linear infinite"
|
||||
: "none",
|
||||
"@keyframes spin": {
|
||||
"0%": { transform: "rotate(0deg)" },
|
||||
"100%": { transform: "rotate(360deg)" },
|
||||
},
|
||||
}}
|
||||
title={t("Update Provider") as string}
|
||||
>
|
||||
<RefreshRounded />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
</DialogContent>
|
||||
|
||||
|
||||
@@ -34,14 +34,13 @@ import {
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import useSWR from "swr";
|
||||
|
||||
import { useAppData } from "@/providers/app-data-context";
|
||||
import {
|
||||
closeAllConnections,
|
||||
getProxies,
|
||||
updateProxyAndSync,
|
||||
updateProxyChainConfigInRuntime,
|
||||
} from "@/services/cmds";
|
||||
selectNodeForGroup,
|
||||
} from "tauri-plugin-mihomo-api";
|
||||
|
||||
import { useAppData } from "@/providers/app-data-context";
|
||||
import { calcuProxies, updateProxyChainConfigInRuntime } from "@/services/cmds";
|
||||
|
||||
interface ProxyChainItem {
|
||||
id: string;
|
||||
@@ -204,7 +203,7 @@ export const ProxyChain = ({
|
||||
// 获取当前代理信息以检查连接状态
|
||||
const { data: currentProxies, mutate: mutateProxies } = useSWR(
|
||||
"getProxies",
|
||||
getProxies,
|
||||
calcuProxies,
|
||||
{
|
||||
revalidateOnFocus: true,
|
||||
revalidateIfStale: true,
|
||||
@@ -367,7 +366,7 @@ export const ProxyChain = ({
|
||||
|
||||
const targetGroup = mode === "global" ? "GLOBAL" : selectedGroup;
|
||||
|
||||
await updateProxyAndSync(targetGroup || "GLOBAL", lastNode.name);
|
||||
await selectNodeForGroup(targetGroup || "GLOBAL", lastNode.name);
|
||||
localStorage.setItem("proxy-chain-group", targetGroup || "GLOBAL");
|
||||
localStorage.setItem("proxy-chain-exit-node", lastNode.name);
|
||||
|
||||
|
||||
@@ -14,14 +14,13 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Virtuoso, type VirtuosoHandle } from "react-virtuoso";
|
||||
import useSWR from "swr";
|
||||
import { delayGroup, healthcheckProxyProvider } from "tauri-plugin-mihomo-api";
|
||||
|
||||
import { useProxySelection } from "@/hooks/use-proxy-selection";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { useAppData } from "@/providers/app-data-context";
|
||||
import {
|
||||
getGroupProxyDelays,
|
||||
getRuntimeConfig,
|
||||
providerHealthCheck,
|
||||
updateProxyChainConfigInRuntime,
|
||||
} from "@/services/cmds";
|
||||
import delayManager from "@/services/delay";
|
||||
@@ -153,15 +152,14 @@ export const ProxyGroups = (props: Props) => {
|
||||
|
||||
// 添加和清理滚动事件监听器
|
||||
useEffect(() => {
|
||||
const currentScroller = scrollerRef.current;
|
||||
if (currentScroller) {
|
||||
currentScroller.addEventListener("scroll", handleScroll, {
|
||||
passive: true,
|
||||
});
|
||||
return () => {
|
||||
currentScroller.removeEventListener("scroll", handleScroll);
|
||||
};
|
||||
}
|
||||
if (!scrollerRef.current) return;
|
||||
scrollerRef.current.addEventListener("scroll", handleScroll, {
|
||||
passive: true,
|
||||
});
|
||||
|
||||
return () => {
|
||||
scrollerRef.current?.removeEventListener("scroll", handleScroll);
|
||||
};
|
||||
}, [handleScroll]);
|
||||
|
||||
// 滚动到顶部
|
||||
@@ -215,6 +213,7 @@ export const ProxyGroups = (props: Props) => {
|
||||
const currentGroup = getCurrentGroup();
|
||||
const availableGroups = getAvailableGroups();
|
||||
|
||||
// TODO: 频繁点击切换代理节点,导致应用卡死
|
||||
const handleChangeProxy = useCallback(
|
||||
(group: IProxyGroupItem, proxy: IProxyItem) => {
|
||||
if (isChainMode) {
|
||||
@@ -273,7 +272,7 @@ export const ProxyGroups = (props: Props) => {
|
||||
if (providers.size) {
|
||||
console.log(`[ProxyGroups] 发现提供者,数量: ${providers.size}`);
|
||||
Promise.allSettled(
|
||||
[...providers].map((p) => providerHealthCheck(p)),
|
||||
[...providers].map((p) => healthcheckProxyProvider(p)),
|
||||
).then(() => {
|
||||
console.log(`[ProxyGroups] 提供者健康检查完成`);
|
||||
onProxies();
|
||||
@@ -289,7 +288,7 @@ export const ProxyGroups = (props: Props) => {
|
||||
try {
|
||||
await Promise.race([
|
||||
delayManager.checkListDelay(names, groupName, timeout),
|
||||
getGroupProxyDelays(groupName, url, timeout).then((result) => {
|
||||
delayGroup(groupName, url, timeout).then((result) => {
|
||||
console.log(
|
||||
`[ProxyGroups] getGroupProxyDelays返回结果数量:`,
|
||||
Object.keys(result || {}).length,
|
||||
@@ -518,7 +517,7 @@ export const ProxyGroups = (props: Props) => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
{availableGroups.map((group: any, index: number) => (
|
||||
{availableGroups.map((group: any, _index: number) => (
|
||||
<MenuItem
|
||||
key={group.name}
|
||||
onClick={() => handleGroupSelect(group.name)}
|
||||
|
||||
@@ -37,12 +37,12 @@ export const ProxyItemMini = (props: Props) => {
|
||||
return () => {
|
||||
delayManager.removeListener(proxy.name, group.name);
|
||||
};
|
||||
}, [proxy.name, group.name]);
|
||||
}, [isPreset, proxy.name, group.name]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!proxy) return;
|
||||
setDelay(delayManager.getDelayFix(proxy, group.name));
|
||||
}, [proxy]);
|
||||
}, [proxy, group.name]);
|
||||
|
||||
const onDelay = useLockFn(async () => {
|
||||
setDelay(-2);
|
||||
@@ -200,7 +200,7 @@ export const ProxyItemMini = (props: Props) => {
|
||||
</Widget>
|
||||
)}
|
||||
|
||||
{delay > 0 && (
|
||||
{delay >= 0 && (
|
||||
// 显示延迟
|
||||
<Widget
|
||||
className="the-delay"
|
||||
@@ -220,7 +220,7 @@ export const ProxyItemMini = (props: Props) => {
|
||||
{delayManager.formatDelay(delay, timeout)}
|
||||
</Widget>
|
||||
)}
|
||||
{delay !== -2 && delay <= 0 && selected && (
|
||||
{proxy.type !== "Direct" && delay !== -2 && delay < 0 && selected && (
|
||||
// 展示已选择的icon
|
||||
<CheckCircleOutlineRounded
|
||||
className="the-icon"
|
||||
|
||||
@@ -19,19 +19,11 @@ import { useLockFn } from "ahooks";
|
||||
import dayjs from "dayjs";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { updateRuleProvider } from "tauri-plugin-mihomo-api";
|
||||
|
||||
import { useAppData } from "@/providers/app-data-context";
|
||||
import { ruleProviderUpdate } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
|
||||
// 定义规则提供者类型
|
||||
interface RuleProviderItem {
|
||||
behavior: string;
|
||||
ruleCount: number;
|
||||
updatedAt: number;
|
||||
vehicleType: string;
|
||||
}
|
||||
|
||||
// 辅助组件 - 类型框
|
||||
const TypeBox = styled(Box)<{ component?: React.ElementType }>(({ theme }) => ({
|
||||
display: "inline-block",
|
||||
@@ -60,7 +52,7 @@ export const ProviderButton = () => {
|
||||
// 设置更新状态
|
||||
setUpdating((prev) => ({ ...prev, [name]: true }));
|
||||
|
||||
await ruleProviderUpdate(name);
|
||||
await updateRuleProvider(name);
|
||||
|
||||
// 刷新数据
|
||||
await refreshRules();
|
||||
@@ -101,7 +93,7 @@ export const ProviderButton = () => {
|
||||
// 改为串行逐个更新所有provider
|
||||
for (const name of allProviders) {
|
||||
try {
|
||||
await ruleProviderUpdate(name);
|
||||
await updateRuleProvider(name);
|
||||
// 每个更新完成后更新状态
|
||||
setUpdating((prev) => ({ ...prev, [name]: false }));
|
||||
} catch (err) {
|
||||
@@ -160,112 +152,117 @@ export const ProviderButton = () => {
|
||||
|
||||
<DialogContent>
|
||||
<List sx={{ py: 0, minHeight: 250 }}>
|
||||
{Object.entries(ruleProviders || {}).map(([key, item]) => {
|
||||
const provider = item as RuleProviderItem;
|
||||
const time = dayjs(provider.updatedAt);
|
||||
const isUpdating = updating[key];
|
||||
{Object.entries(ruleProviders || {})
|
||||
.sort()
|
||||
.map(([key, item]) => {
|
||||
const provider = item;
|
||||
const time = dayjs(provider.updatedAt);
|
||||
const isUpdating = updating[key];
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
key={key}
|
||||
sx={[
|
||||
{
|
||||
p: 0,
|
||||
mb: "8px",
|
||||
borderRadius: 2,
|
||||
overflow: "hidden",
|
||||
transition: "all 0.2s",
|
||||
},
|
||||
({ palette: { mode, primary } }) => {
|
||||
const bgcolor = mode === "light" ? "#ffffff" : "#24252f";
|
||||
const hoverColor =
|
||||
mode === "light"
|
||||
? alpha(primary.main, 0.1)
|
||||
: alpha(primary.main, 0.2);
|
||||
return (
|
||||
<ListItem
|
||||
key={key}
|
||||
sx={[
|
||||
{
|
||||
p: 0,
|
||||
mb: "8px",
|
||||
borderRadius: 2,
|
||||
overflow: "hidden",
|
||||
transition: "all 0.2s",
|
||||
},
|
||||
({ palette: { mode, primary } }) => {
|
||||
const bgcolor =
|
||||
mode === "light" ? "#ffffff" : "#24252f";
|
||||
const hoverColor =
|
||||
mode === "light"
|
||||
? alpha(primary.main, 0.1)
|
||||
: alpha(primary.main, 0.2);
|
||||
|
||||
return {
|
||||
backgroundColor: bgcolor,
|
||||
"&:hover": {
|
||||
backgroundColor: hoverColor,
|
||||
borderColor: alpha(primary.main, 0.3),
|
||||
},
|
||||
};
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ListItemText
|
||||
sx={{ px: 2, py: 1 }}
|
||||
primary={
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
component="div"
|
||||
noWrap
|
||||
title={key}
|
||||
sx={{ display: "flex", alignItems: "center" }}
|
||||
>
|
||||
<span style={{ marginRight: "8px" }}>{key}</span>
|
||||
<TypeBox component="span">
|
||||
{provider.ruleCount}
|
||||
</TypeBox>
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
noWrap
|
||||
>
|
||||
<small>{t("Update At")}: </small>
|
||||
{time.fromNow()}
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
secondary={
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<TypeBox component="span">
|
||||
{provider.vehicleType}
|
||||
</TypeBox>
|
||||
<TypeBox component="span">{provider.behavior}</TypeBox>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
<Divider orientation="vertical" flexItem />
|
||||
<Box
|
||||
sx={{
|
||||
width: 40,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
return {
|
||||
backgroundColor: bgcolor,
|
||||
"&:hover": {
|
||||
backgroundColor: hoverColor,
|
||||
borderColor: alpha(primary.main, 0.3),
|
||||
},
|
||||
};
|
||||
},
|
||||
]}
|
||||
>
|
||||
<IconButton
|
||||
size="small"
|
||||
color="primary"
|
||||
onClick={() => updateProvider(key)}
|
||||
disabled={isUpdating}
|
||||
<ListItemText
|
||||
sx={{ px: 2, py: 1 }}
|
||||
primary={
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
component="div"
|
||||
noWrap
|
||||
title={key}
|
||||
sx={{ display: "flex", alignItems: "center" }}
|
||||
>
|
||||
<span style={{ marginRight: "8px" }}>{key}</span>
|
||||
<TypeBox component="span">
|
||||
{provider.ruleCount}
|
||||
</TypeBox>
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
noWrap
|
||||
>
|
||||
<small>{t("Update At")}: </small>
|
||||
{time.fromNow()}
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
secondary={
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<TypeBox component="span">
|
||||
{provider.vehicleType}
|
||||
</TypeBox>
|
||||
<TypeBox component="span">
|
||||
{provider.behavior}
|
||||
</TypeBox>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
<Divider orientation="vertical" flexItem />
|
||||
<Box
|
||||
sx={{
|
||||
animation: isUpdating
|
||||
? "spin 1s linear infinite"
|
||||
: "none",
|
||||
"@keyframes spin": {
|
||||
"0%": { transform: "rotate(0deg)" },
|
||||
"100%": { transform: "rotate(360deg)" },
|
||||
},
|
||||
width: 40,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
title={t("Update Provider") as string}
|
||||
>
|
||||
<RefreshRounded />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
<IconButton
|
||||
size="small"
|
||||
color="primary"
|
||||
onClick={() => updateProvider(key)}
|
||||
disabled={isUpdating}
|
||||
sx={{
|
||||
animation: isUpdating
|
||||
? "spin 1s linear infinite"
|
||||
: "none",
|
||||
"@keyframes spin": {
|
||||
"0%": { transform: "rotate(0deg)" },
|
||||
"100%": { transform: "rotate(360deg)" },
|
||||
},
|
||||
}}
|
||||
title={t("Update Provider") as string}
|
||||
>
|
||||
<RefreshRounded />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
</DialogContent>
|
||||
|
||||
|
||||
@@ -16,16 +16,11 @@ import type { Ref } from "react";
|
||||
import { useImperativeHandle, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { mutate } from "swr";
|
||||
import { closeAllConnections, upgradeCore } from "tauri-plugin-mihomo-api";
|
||||
|
||||
import { BaseDialog, DialogRef } from "@/components/base";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import {
|
||||
changeClashCore,
|
||||
closeAllConnections,
|
||||
forceRefreshClashConfig,
|
||||
restartCore,
|
||||
upgradeCore,
|
||||
} from "@/services/cmds";
|
||||
import { changeClashCore, restartCore } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
|
||||
const VALID_CORE = [
|
||||
@@ -66,8 +61,6 @@ export function ClashCoreViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
|
||||
mutateVerge();
|
||||
setTimeout(async () => {
|
||||
// 核心切换后强制刷新配置缓存
|
||||
await forceRefreshClashConfig();
|
||||
mutate("getClashConfig");
|
||||
mutate("getVersion");
|
||||
setChangingCore(null);
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
} from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { getBaseConfig } from "tauri-plugin-mihomo-api";
|
||||
|
||||
import { BaseDialog, DialogRef, Switch } from "@/components/base";
|
||||
import { BaseFieldset } from "@/components/base/base-fieldset";
|
||||
@@ -29,7 +30,6 @@ import { useVerge } from "@/hooks/use-verge";
|
||||
import { useAppData } from "@/providers/app-data-context";
|
||||
import {
|
||||
getAutotemProxy,
|
||||
getClashConfig,
|
||||
getNetworkInterfacesInfo,
|
||||
getSystemHostname,
|
||||
getSystemProxy,
|
||||
@@ -123,26 +123,21 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
return "127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,172.29.0.0/16,localhost,*.local,*.crashlytics.com,<local>";
|
||||
};
|
||||
|
||||
const { data: clashConfig } = useSWR("getClashConfig", getClashConfig, {
|
||||
const { data: clashConfig } = useSWR("getClashConfig", getBaseConfig, {
|
||||
revalidateOnFocus: false,
|
||||
revalidateIfStale: true,
|
||||
dedupingInterval: 1000,
|
||||
errorRetryInterval: 5000,
|
||||
});
|
||||
|
||||
const [prevMixedPort, setPrevMixedPort] = useState(
|
||||
clashConfig?.["mixed-port"],
|
||||
);
|
||||
const [prevMixedPort, setPrevMixedPort] = useState(clashConfig?.mixedPort);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
clashConfig?.["mixed-port"] &&
|
||||
clashConfig?.["mixed-port"] !== prevMixedPort
|
||||
) {
|
||||
setPrevMixedPort(clashConfig?.["mixed-port"]);
|
||||
if (clashConfig?.mixedPort && clashConfig.mixedPort !== prevMixedPort) {
|
||||
setPrevMixedPort(clashConfig.mixedPort);
|
||||
resetSystemProxy();
|
||||
}
|
||||
}, [clashConfig?.["mixed-port"]]);
|
||||
}, [clashConfig?.mixedPort]);
|
||||
|
||||
const resetSystemProxy = async () => {
|
||||
try {
|
||||
@@ -180,7 +175,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
|
||||
if (isPacMode) {
|
||||
const host = value.proxy_host || "127.0.0.1";
|
||||
const port = verge?.verge_mixed_port || clashConfig["mixed-port"] || 7897;
|
||||
const port = verge?.verge_mixed_port || clashConfig.mixedPort || 7897;
|
||||
return `${host}:${port}`;
|
||||
} else {
|
||||
return systemProxyAddress;
|
||||
@@ -332,7 +327,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
|
||||
if (pacContent) {
|
||||
pacContent = pacContent.replace(/%proxy_host%/g, value.proxy_host);
|
||||
// 将 mixed-port 转换为字符串
|
||||
const mixedPortStr = (clashConfig?.["mixed-port"] || "").toString();
|
||||
const mixedPortStr = (clashConfig?.mixedPort || "").toString();
|
||||
pacContent = pacContent.replace(/%mixed-port%/g, mixedPortStr);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,15 @@ import { invoke } from "@tauri-apps/api/core";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { updateGeo } from "tauri-plugin-mihomo-api";
|
||||
|
||||
import { DialogRef, Switch } from "@/components/base";
|
||||
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
|
||||
import { useClash } from "@/hooks/use-clash";
|
||||
import { useVerge } from "@/hooks/use-verge";
|
||||
import { invoke_uwp_tool } from "@/services/cmds";
|
||||
import { updateGeoData } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/noticeService";
|
||||
import { useClashLog } from "@/services/states";
|
||||
import getSystem from "@/utils/get-system";
|
||||
|
||||
import { ClashCoreViewer } from "./mods/clash-core-viewer";
|
||||
@@ -35,6 +36,7 @@ const SettingClash = ({ onError }: Props) => {
|
||||
|
||||
const { clash, version, mutateClash, patchClash } = useClash();
|
||||
const { verge, patchVerge } = useVerge();
|
||||
const [, setClashLog] = useClashLog();
|
||||
|
||||
const {
|
||||
ipv6,
|
||||
@@ -64,7 +66,7 @@ const SettingClash = ({ onError }: Props) => {
|
||||
};
|
||||
const onUpdateGeo = async () => {
|
||||
try {
|
||||
await updateGeoData();
|
||||
await updateGeo();
|
||||
showNotice("success", t("GeoData Updated"));
|
||||
} catch (err: any) {
|
||||
showNotice("error", err?.response.data.message || err.toString());
|
||||
@@ -186,7 +188,10 @@ const SettingClash = ({ onError }: Props) => {
|
||||
onCatch={onError}
|
||||
onFormat={(e: any) => e.target.value}
|
||||
onChange={(e) => onChangeData({ "log-level": e })}
|
||||
onGuard={(e) => patchClash({ "log-level": e })}
|
||||
onGuard={(e) => {
|
||||
setClashLog((pre: any) => ({ ...pre, logLevel: e }));
|
||||
return patchClash({ "log-level": e });
|
||||
}}
|
||||
>
|
||||
<Select size="small" sx={{ width: 100, "> div": { py: "7.5px" } }}>
|
||||
<MenuItem value="debug">Debug</MenuItem>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Box, Divider, MenuItem, Menu, styled, alpha } from "@mui/material";
|
||||
import { convertFileSrc } from "@tauri-apps/api/core";
|
||||
import { UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { useLockFn } from "ahooks";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { BaseLoading } from "@/components/base";
|
||||
@@ -44,11 +44,11 @@ export const TestItem = (props: Props) => {
|
||||
const [iconCachePath, setIconCachePath] = useState("");
|
||||
const { addListener } = useListen();
|
||||
|
||||
const onDelay = async () => {
|
||||
const onDelay = useCallback(async () => {
|
||||
setDelay(-2);
|
||||
const result = await cmdTestDelay(url);
|
||||
setDelay(result);
|
||||
};
|
||||
}, [url]);
|
||||
|
||||
useEffect(() => {
|
||||
initIconCachePath();
|
||||
|
||||
Reference in New Issue
Block a user