Skip to content

Commit f8da68f

Browse files
committed
feat: add mihomo management flow
1 parent 98537f3 commit f8da68f

21 files changed

Lines changed: 2307 additions & 452 deletions

File tree

plugins/layouts/app-layout/src/components/app-sidebar.tsx

Lines changed: 621 additions & 429 deletions
Large diffs are not rendered by default.

plugins/pages/proxy-center/src/i18n/resources.ts

Lines changed: 174 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,91 @@ export const proxyResources = {
88
create: '+ 添加代理',
99
import: '导入',
1010
export: '导出',
11-
},
12-
stats: {
13-
total: '总计',
14-
healthy: '可用',
11+
},
12+
mihomo: {
13+
form: {
14+
title: '连接 Mihomo',
15+
description: '填写 Mihomo external-controller 配置,测试通过后直接进入 Mihomo 页面。',
16+
viewDescription: '查看当前 Mihomo 连接信息。点击编辑连接后可修改配置。',
17+
hintTitle: '连接要求',
18+
hintBody: '请先在本机 Clash/Mihomo 中开启 external-controller,并确认当前实例可访问。',
19+
loading: '正在读取连接信息...',
20+
controller: '控制地址',
21+
controllerRequired: '请输入 Mihomo 控制地址',
22+
secret: 'Secret(可选)',
23+
secretPlaceholder: '如果未设置鉴权,可留空',
24+
secretConfigured: '已配置',
25+
secretEmpty: '未配置',
26+
version: '版本',
27+
providers: '订阅源',
28+
nodes: '节点数',
29+
connectedTitle: '当前已连接',
30+
connectedBody: '当前 Mihomo 已附着到本地会话,可直接读取订阅和节点。',
31+
edit: '编辑连接',
32+
close: '关闭',
33+
backToView: '返回查看',
34+
submit: '测试并进入',
35+
submitting: '测试中...',
36+
cancel: '取消',
37+
errorTitle: '连接失败',
38+
connectSuccess: 'Mihomo 连接成功,正在进入 Mihomo 页面',
39+
connectFailed: 'Mihomo 连接失败',
40+
loadFailed: '读取连接配置失败',
41+
},
42+
page: {
43+
title: 'Mihomo',
44+
subtitle: '在这里按策略浏览节点,并对节点执行测速。',
45+
connectedTo: '已连接到 {{controller}}',
46+
back: '返回代理中心',
47+
settings: '连接设置',
48+
refresh: '刷新数据',
49+
loading: '正在加载 Mihomo 数据...',
50+
loadFailed: '加载 Mihomo 数据失败',
51+
errorTitle: '当前未能读取 Mihomo 数据',
52+
delayTestFailed: '测速失败',
53+
groupDelayTestFailed: '策略组批量测速失败',
54+
retry: '重试',
55+
reconfigure: '重新配置',
56+
providersTitle: '订阅源 / Providers',
57+
providersDesc: '显示当前 Mihomo 可读取到的 provider 概览。',
58+
groupsTitle: '策略组 / Groups',
59+
groupsDesc: '用于了解当前实例中的可选组和当前选中节点。',
60+
groupsEmpty: '当前实例没有可见的策略组。',
61+
groupsUnselected: '未选中',
62+
groupsCandidates: '候选',
63+
nodesTitle: '节点 / Nodes',
64+
nodesDesc: '显示当前实例中的基础节点,后续加入代理中心将基于这里的节点继续编排。',
65+
nodesFilteredDesc: '当前按策略 {{group}} 展示候选节点。',
66+
provider: {
67+
name: '名称',
68+
type: '类型',
69+
vehicle: '载体',
70+
nodes: '节点数',
71+
updatedAt: '更新时间',
72+
empty: '当前实例没有可见的 provider。',
73+
},
74+
node: {
75+
name: '名称',
76+
provider: '来源订阅',
77+
providerUnknown: '未标记来源订阅',
78+
type: '类型',
79+
alive: '存活',
80+
udp: 'UDP',
81+
empty: '当前实例没有可见的基础节点。',
82+
filteredEmpty: '当前策略下没有可见的基础节点。',
83+
aliveTrue: '可用',
84+
aliveFalse: '不可用',
85+
test: '测速',
86+
testing: '测速中',
87+
timeout: '超时',
88+
udpTrue: '支持',
89+
udpFalse: '关闭',
90+
},
91+
},
92+
},
93+
stats: {
94+
total: '总计',
95+
healthy: '可用',
1596
unreachable: '不可用',
1697
testAll: '全部测试',
1798
},
@@ -138,9 +219,95 @@ export const proxyResources = {
138219
create: '+ Add Proxy',
139220
import: 'Import',
140221
export: 'Export',
141-
},
142-
stats: {
143-
total: 'Total',
222+
},
223+
mihomo: {
224+
form: {
225+
title: 'Connect Mihomo',
226+
description:
227+
'Fill in the Mihomo external-controller configuration. After a successful test, the Mihomo page opens immediately.',
228+
viewDescription:
229+
'View the current Mihomo connection details. Click edit to update the configuration.',
230+
hintTitle: 'Requirements',
231+
hintBody:
232+
'Please enable external-controller in your local Clash/Mihomo instance before connecting.',
233+
loading: 'Loading connection info...',
234+
controller: 'Controller Address',
235+
controllerRequired: 'Please enter the Mihomo controller address',
236+
secret: 'Secret (Optional)',
237+
secretPlaceholder: 'Leave empty if your instance does not require auth',
238+
secretConfigured: 'Configured',
239+
secretEmpty: 'Not configured',
240+
version: 'Version',
241+
providers: 'Providers',
242+
nodes: 'Nodes',
243+
connectedTitle: 'Currently Connected',
244+
connectedBody:
245+
'The current Mihomo instance is already attached to this local session and can be used to read providers and nodes.',
246+
edit: 'Edit Connection',
247+
close: 'Close',
248+
backToView: 'Back to Overview',
249+
submit: 'Test and Enter',
250+
submitting: 'Testing...',
251+
cancel: 'Cancel',
252+
errorTitle: 'Connection Failed',
253+
connectSuccess: 'Mihomo connected. Opening the Mihomo page...',
254+
connectFailed: 'Failed to connect to Mihomo',
255+
loadFailed: 'Failed to load connection configuration',
256+
},
257+
page: {
258+
title: 'Mihomo',
259+
subtitle: 'Browse nodes by strategy and run delay tests from this page.',
260+
connectedTo: 'Connected to {{controller}}',
261+
back: 'Back to Proxy Center',
262+
settings: 'Connection Settings',
263+
refresh: 'Refresh',
264+
loading: 'Loading Mihomo data...',
265+
loadFailed: 'Failed to load Mihomo data',
266+
errorTitle: 'Unable to read Mihomo data',
267+
delayTestFailed: 'Failed to test node delay',
268+
groupDelayTestFailed: 'Failed to batch test group delays',
269+
retry: 'Retry',
270+
reconfigure: 'Reconfigure',
271+
providersTitle: 'Providers',
272+
providersDesc: 'Shows the provider overview available in the current Mihomo instance.',
273+
groupsTitle: 'Groups',
274+
groupsDesc: 'Inspect the available groups and their current selected node.',
275+
groupsEmpty: 'No visible groups found in the current instance.',
276+
groupsUnselected: 'Unselected',
277+
groupsCandidates: 'candidates',
278+
nodesTitle: 'Nodes',
279+
nodesDesc:
280+
'Shows the base nodes currently available. The next phase can build proxy-center entries from these nodes.',
281+
nodesFilteredDesc: 'Showing candidate nodes for strategy {{group}}.',
282+
provider: {
283+
name: 'Name',
284+
type: 'Type',
285+
vehicle: 'Vehicle',
286+
nodes: 'Nodes',
287+
updatedAt: 'Updated At',
288+
empty: 'No provider is visible in the current instance.',
289+
},
290+
node: {
291+
name: 'Name',
292+
provider: 'Provider',
293+
providerUnknown: 'Unlabeled provider',
294+
type: 'Type',
295+
alive: 'Alive',
296+
udp: 'UDP',
297+
empty: 'No base node is visible in the current instance.',
298+
filteredEmpty: 'No base node is visible under the current strategy.',
299+
aliveTrue: 'Available',
300+
aliveFalse: 'Unavailable',
301+
test: 'Test',
302+
testing: 'Testing',
303+
timeout: 'Timeout',
304+
udpTrue: 'Enabled',
305+
udpFalse: 'Disabled',
306+
},
307+
},
308+
},
309+
stats: {
310+
total: 'Total',
144311
healthy: 'Healthy',
145312
unreachable: 'Unreachable',
146313
testAll: 'Test All',

plugins/pages/proxy-center/src/index.tsx

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { useProxyHandlers } from './hooks/use-proxy-handlers';
2020
import { selectAndReadProxyFile, type ImportProxyItem } from './utils/import-export';
2121
import type { Proxy } from './types';
2222
import { ITEMS_PER_PAGE } from './constants';
23+
import { MihomoPage } from './mihomo/mihomo-page';
2324

2425
const ProxyCenterPage: React.FC = () => {
2526
useTranslation('proxy'); // 注册 i18n namespace
@@ -264,19 +265,32 @@ const ProxyCenterPage: React.FC = () => {
264265
};
265266

266267
// 在模块加载时贡献路由
267-
try {
268-
extensionRegistry.contribute('routes', {
269-
contributorId: 'proxy-center',
268+
try {
269+
extensionRegistry.contribute('routes', {
270+
contributorId: 'proxy-center',
270271
value: {
271272
path: '/proxy',
272273
Component: ProxyCenterPage,
273274
},
274275
priority: 10,
275276
});
276277
console.log('[proxy-center] Route contributed at module load: /proxy');
277-
} catch (error) {
278-
console.warn('[proxy-center] Failed to contribute route at module load:', error);
279-
}
278+
} catch (error) {
279+
console.warn('[proxy-center] Failed to contribute route at module load:', error);
280+
}
281+
282+
try {
283+
extensionRegistry.contribute('routes', {
284+
contributorId: 'proxy-center-mihomo',
285+
value: {
286+
path: '/proxy/mihomo',
287+
Component: MihomoPage,
288+
},
289+
priority: 10,
290+
});
291+
} catch (error) {
292+
console.warn('[proxy-center] Failed to contribute Mihomo route at module load:', error);
293+
}
280294

281295
try {
282296
extensionRegistry.contribute('i18n:resources', {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { invoke } from '@/lib/tauri';
2+
import type {
3+
MihomoConnectionConfig,
4+
MihomoConnectionInfo,
5+
MihomoOverview,
6+
MihomoProxyDelayResult,
7+
MihomoStatus,
8+
} from './types';
9+
10+
export function testAndAttachMihomo(config: MihomoConnectionConfig): Promise<MihomoStatus> {
11+
return invoke<MihomoStatus>('test_and_attach_mihomo', { config });
12+
}
13+
14+
export function getMihomoStatus(): Promise<MihomoStatus> {
15+
return invoke<MihomoStatus>('get_mihomo_status');
16+
}
17+
18+
export function getMihomoConnectionInfo(): Promise<MihomoConnectionInfo> {
19+
return invoke<MihomoConnectionInfo>('get_mihomo_connection_info');
20+
}
21+
22+
export function getMihomoOverview(): Promise<MihomoOverview> {
23+
return invoke<MihomoOverview>('get_mihomo_overview');
24+
}
25+
26+
export function testMihomoProxyDelay(proxyName: string): Promise<MihomoProxyDelayResult> {
27+
return invoke<MihomoProxyDelayResult>('test_mihomo_proxy_delay', { proxyName });
28+
}
29+
30+
export function testMihomoGroupDelays(groupName: string): Promise<MihomoProxyDelayResult[]> {
31+
return invoke<MihomoProxyDelayResult[]>('test_mihomo_group_delays', { groupName });
32+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import type { SVGProps } from 'react';
2+
3+
import { cn } from '@/lib/utils';
4+
5+
type ClashIconProps = SVGProps<SVGSVGElement>;
6+
7+
const STROKE_WIDTH = 3.2;
8+
9+
export function ClashIcon({ className, ...props }: ClashIconProps) {
10+
return (
11+
<svg
12+
viewBox="8 4 32 40"
13+
fill="none"
14+
xmlns="http://www.w3.org/2000/svg"
15+
className={cn('h-4 w-4', className)}
16+
{...props}
17+
>
18+
<path
19+
d="M27.19 42.5a89.0444 89.0444 0 0 1-14.6813-1.5725S13.94 12.3721 17.9209 5.5357c-.13-.297 2.9919 1.2125 4.4218 6.2665a25.5569 25.5569 0 0 1 4.8471-.47"
20+
stroke="currentColor"
21+
strokeWidth={STROKE_WIDTH}
22+
strokeLinecap="round"
23+
strokeLinejoin="round"
24+
/>
25+
<ellipse
26+
cx="21.2404"
27+
cy="20.3089"
28+
rx="1.6708"
29+
ry="2.1301"
30+
stroke="currentColor"
31+
strokeWidth={STROKE_WIDTH}
32+
strokeLinecap="round"
33+
strokeLinejoin="round"
34+
/>
35+
<path
36+
d="M27.19 42.5a89.0444 89.0444 0 0 0 14.6813-1.5725S40.44 12.3721 36.4583 5.5357c.03-.2006-3.59 1.7549-4.4218 6.2665a25.5582 25.5582 0 0 0-4.8471-.47"
37+
stroke="currentColor"
38+
strokeWidth={STROKE_WIDTH}
39+
strokeLinecap="round"
40+
strokeLinejoin="round"
41+
/>
42+
<ellipse
43+
cx="33.1398"
44+
cy="20.3089"
45+
rx="1.6708"
46+
ry="2.1301"
47+
stroke="currentColor"
48+
strokeWidth={STROKE_WIDTH}
49+
strokeLinecap="round"
50+
strokeLinejoin="round"
51+
/>
52+
<path
53+
d="M12.5083 40.927C10.5777 40.6 7.56 40.6178 6.4685 37.44c-1.0674-3.107.4377-6.6708 3.7411-7.0453"
54+
stroke="currentColor"
55+
strokeWidth={STROKE_WIDTH}
56+
strokeLinecap="round"
57+
strokeMiterlimit="5.7143"
58+
/>
59+
<path
60+
d="M25.4634 26.3872a1.4666 1.4666 0 0 0 1.4726-1.4725"
61+
stroke="currentColor"
62+
strokeWidth={STROKE_WIDTH}
63+
strokeLinecap="round"
64+
strokeMiterlimit="5.7143"
65+
/>
66+
<path
67+
d="M28.4091 26.3872a1.4666 1.4666 0 0 1-1.4726-1.4725"
68+
stroke="currentColor"
69+
strokeWidth={STROKE_WIDTH}
70+
strokeLinecap="round"
71+
strokeMiterlimit="5.7143"
72+
/>
73+
</svg>
74+
);
75+
}

0 commit comments

Comments
 (0)