@morndream/mihomo-tui 0.5.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/LICENSE +21 -0
  3. package/README.md +462 -0
  4. package/bin/mihomo-tui +34 -0
  5. package/dist/App.js +161 -0
  6. package/dist/App.js.map +1 -0
  7. package/dist/api/client.js +239 -0
  8. package/dist/api/client.js.map +1 -0
  9. package/dist/api/status.js +43 -0
  10. package/dist/api/status.js.map +1 -0
  11. package/dist/api/stream.js +139 -0
  12. package/dist/api/stream.js.map +1 -0
  13. package/dist/api/types.js +6 -0
  14. package/dist/api/types.js.map +1 -0
  15. package/dist/cli.js +221 -0
  16. package/dist/cli.js.map +1 -0
  17. package/dist/commands/conn.js +122 -0
  18. package/dist/commands/conn.js.map +1 -0
  19. package/dist/commands/logs.js +99 -0
  20. package/dist/commands/logs.js.map +1 -0
  21. package/dist/commands/output.js +177 -0
  22. package/dist/commands/output.js.map +1 -0
  23. package/dist/commands/provider.js +140 -0
  24. package/dist/commands/provider.js.map +1 -0
  25. package/dist/commands/proxy.js +161 -0
  26. package/dist/commands/proxy.js.map +1 -0
  27. package/dist/commands/proxyEnv.js +193 -0
  28. package/dist/commands/proxyEnv.js.map +1 -0
  29. package/dist/commands/rules.js +87 -0
  30. package/dist/commands/rules.js.map +1 -0
  31. package/dist/commands/status.js +75 -0
  32. package/dist/commands/status.js.map +1 -0
  33. package/dist/components/ConfirmDialog.js +31 -0
  34. package/dist/components/ConfirmDialog.js.map +1 -0
  35. package/dist/components/DelayBadge.js +27 -0
  36. package/dist/components/DelayBadge.js.map +1 -0
  37. package/dist/components/InputDialog.js +325 -0
  38. package/dist/components/InputDialog.js.map +1 -0
  39. package/dist/components/ListDialog.js +86 -0
  40. package/dist/components/ListDialog.js.map +1 -0
  41. package/dist/components/ProgressDialog.js +34 -0
  42. package/dist/components/ProgressDialog.js.map +1 -0
  43. package/dist/components/ScrollList.js +26 -0
  44. package/dist/components/ScrollList.js.map +1 -0
  45. package/dist/components/StatusBar.js +58 -0
  46. package/dist/components/StatusBar.js.map +1 -0
  47. package/dist/components/TextEditor.js +112 -0
  48. package/dist/components/TextEditor.js.map +1 -0
  49. package/dist/components/textEditorModel.js +154 -0
  50. package/dist/components/textEditorModel.js.map +1 -0
  51. package/dist/config/manager.js +296 -0
  52. package/dist/config/manager.js.map +1 -0
  53. package/dist/config/preserveGroups.js +9 -0
  54. package/dist/config/preserveGroups.js.map +1 -0
  55. package/dist/config/ruleDocument.js +88 -0
  56. package/dist/config/ruleDocument.js.map +1 -0
  57. package/dist/config/ruleService.js +256 -0
  58. package/dist/config/ruleService.js.map +1 -0
  59. package/dist/config/ruleValidation.js +98 -0
  60. package/dist/config/ruleValidation.js.map +1 -0
  61. package/dist/config/service.js +63 -0
  62. package/dist/config/service.js.map +1 -0
  63. package/dist/config/settingsService.js +162 -0
  64. package/dist/config/settingsService.js.map +1 -0
  65. package/dist/config/skeleton.js +324 -0
  66. package/dist/config/skeleton.js.map +1 -0
  67. package/dist/config/subscriptionService.js +284 -0
  68. package/dist/config/subscriptionService.js.map +1 -0
  69. package/dist/config/subscriptions.js +200 -0
  70. package/dist/config/subscriptions.js.map +1 -0
  71. package/dist/config/types.js +5 -0
  72. package/dist/config/types.js.map +1 -0
  73. package/dist/config.js +101 -0
  74. package/dist/config.js.map +1 -0
  75. package/dist/hooks/useProviders.js +138 -0
  76. package/dist/hooks/useProviders.js.map +1 -0
  77. package/dist/hooks/useProxies.js +298 -0
  78. package/dist/hooks/useProxies.js.map +1 -0
  79. package/dist/hooks/useStream.js +127 -0
  80. package/dist/hooks/useStream.js.map +1 -0
  81. package/dist/kernel/installer.js +317 -0
  82. package/dist/kernel/installer.js.map +1 -0
  83. package/dist/kernel/releases.js +111 -0
  84. package/dist/kernel/releases.js.map +1 -0
  85. package/dist/kernel/sources.js +98 -0
  86. package/dist/kernel/sources.js.map +1 -0
  87. package/dist/rules/editor.js +181 -0
  88. package/dist/rules/editor.js.map +1 -0
  89. package/dist/rules/matcher.js +83 -0
  90. package/dist/rules/matcher.js.map +1 -0
  91. package/dist/rules/model.js +25 -0
  92. package/dist/rules/model.js.map +1 -0
  93. package/dist/rules/runtime.js +46 -0
  94. package/dist/rules/runtime.js.map +1 -0
  95. package/dist/rules/types.js +10 -0
  96. package/dist/rules/types.js.map +1 -0
  97. package/dist/ui/FooterLine.js +62 -0
  98. package/dist/ui/FooterLine.js.map +1 -0
  99. package/dist/ui/Panel.js +47 -0
  100. package/dist/ui/Panel.js.map +1 -0
  101. package/dist/ui/RowIndicator.js +25 -0
  102. package/dist/ui/RowIndicator.js.map +1 -0
  103. package/dist/ui/SectionHeader.js +14 -0
  104. package/dist/ui/SectionHeader.js.map +1 -0
  105. package/dist/ui/StatusDot.js +15 -0
  106. package/dist/ui/StatusDot.js.map +1 -0
  107. package/dist/ui/TopBar.js +81 -0
  108. package/dist/ui/TopBar.js.map +1 -0
  109. package/dist/ui/exitGuard.js +20 -0
  110. package/dist/ui/exitGuard.js.map +1 -0
  111. package/dist/ui/keyCapture.js +28 -0
  112. package/dist/ui/keyCapture.js.map +1 -0
  113. package/dist/ui/layout.js +32 -0
  114. package/dist/ui/layout.js.map +1 -0
  115. package/dist/ui/rowIndicator.js +42 -0
  116. package/dist/ui/rowIndicator.js.map +1 -0
  117. package/dist/ui/tableHeader.js +33 -0
  118. package/dist/ui/tableHeader.js.map +1 -0
  119. package/dist/ui/theme.js +47 -0
  120. package/dist/ui/theme.js.map +1 -0
  121. package/dist/ui/tone.js +131 -0
  122. package/dist/ui/tone.js.map +1 -0
  123. package/dist/version.js +27 -0
  124. package/dist/version.js.map +1 -0
  125. package/dist/views/Conns.js +107 -0
  126. package/dist/views/Conns.js.map +1 -0
  127. package/dist/views/Logs.js +89 -0
  128. package/dist/views/Logs.js.map +1 -0
  129. package/dist/views/Providers.js +392 -0
  130. package/dist/views/Providers.js.map +1 -0
  131. package/dist/views/Proxies.js +233 -0
  132. package/dist/views/Proxies.js.map +1 -0
  133. package/dist/views/RuleEditor.js +356 -0
  134. package/dist/views/RuleEditor.js.map +1 -0
  135. package/dist/views/Rules.js +286 -0
  136. package/dist/views/Rules.js.map +1 -0
  137. package/dist/views/Settings.js +734 -0
  138. package/dist/views/Settings.js.map +1 -0
  139. package/dist/views/groupDisplay.js +53 -0
  140. package/dist/views/groupDisplay.js.map +1 -0
  141. package/dist/views/subscriptionFields.js +59 -0
  142. package/dist/views/subscriptionFields.js.map +1 -0
  143. package/dist/views/yamlFile.js +32 -0
  144. package/dist/views/yamlFile.js.map +1 -0
  145. package/package.json +66 -0
@@ -0,0 +1,233 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * 标签页 1:节点。
4
+ *
5
+ * 只显示 AUTO 和全部机场分组(机场- 前缀,随订阅增删动态变化),
6
+ * 每个分组显示当前真正在用的节点。
7
+ *
8
+ * 布局(spec 4.4):双栏各套内嵌标题 Panel;行标记分离——
9
+ * ▌ accent 指示条 = 键盘焦点行(替代整行反色,CJK 行不再闪白)
10
+ * ❯ muted = 光标停在另一栏时的驻留位置
11
+ * ● 业务选中 = 当前在用节点 / 被 PROXY 选中的组,可与 ▌ 同时出现
12
+ */
13
+ import { useEffect, useMemo, useState } from 'react';
14
+ import { Box, Text, useInput } from 'ink';
15
+ import { DelayBadge, spinnerFrame } from '../components/DelayBadge.js';
16
+ import { ScrollList } from '../components/ScrollList.js';
17
+ import { loadSubscriptions } from '../config/subscriptions.js';
18
+ import { FooterLine } from '../ui/FooterLine.js';
19
+ import { Panel } from '../ui/Panel.js';
20
+ import { colors, styles } from '../ui/theme.js';
21
+ import { fitDisplay, truncateDisplay } from '../commands/output.js';
22
+ import { buildGroupDisplay } from './groupDisplay.js';
23
+ /** 行首指示条(纯函数):▌=焦点行、❯=驻留光标、空格=普通行 */
24
+ export function rowBar(isCursor, paneFocused) {
25
+ if (!isCursor)
26
+ return ' ';
27
+ return paneFocused ? '▌' : '❯';
28
+ }
29
+ /** 指示条颜色(纯函数):焦点行 accent、驻留光标 muted */
30
+ export function rowBarColor(isCursor, paneFocused) {
31
+ if (!isCursor)
32
+ return undefined;
33
+ return paneFocused ? colors.accent : colors.muted;
34
+ }
35
+ /** 右栏标题(纯函数):`节点 · AUTO [按延迟] 190/228 可用`,测速时附进度 */
36
+ export function nodePanelTitle(opts) {
37
+ const parts = [
38
+ `节点 · ${opts.groupName ? truncateDisplay(opts.groupName, 24) : '—'}`,
39
+ opts.onlyAlive ? ' [只看可用]' : '',
40
+ opts.sortByDelay ? ' [按延迟]' : '',
41
+ opts.fixed ? ' [已钉选]' : '',
42
+ opts.groupName ? ` ${opts.aliveCount}/${opts.nodeCount} 可用` : '',
43
+ ];
44
+ if (opts.testing) {
45
+ parts.push(` ${spinnerFrame(opts.tick)} 测速 ${opts.testing.done}/${opts.testing.total}`);
46
+ }
47
+ return parts.join('');
48
+ }
49
+ const HINTS = [
50
+ { key: '↑↓', label: '移动' },
51
+ { key: '←→', label: '切栏' },
52
+ { key: 'Enter', label: '选用' },
53
+ { key: 'u', label: '解除钉选' },
54
+ { key: 't', label: '测速' },
55
+ { key: 's', label: '排序' },
56
+ { key: 'v', label: '只看可用' },
57
+ { key: 'r', label: '刷新' },
58
+ ];
59
+ export function ProxiesView({ proxies, height, width, tick, active, onMessage, }) {
60
+ const [groupIndex, setGroupIndex] = useState(0);
61
+ const [nodeIndex, setNodeIndex] = useState(0);
62
+ const [focus, setFocus] = useState('groups');
63
+ const [onlyAlive, setOnlyAlive] = useState(false);
64
+ // 默认按延迟排序:可用节点浮到顶部,直接对应「保证节点可用」的诉求
65
+ const [sortByDelay, setSortByDelay] = useState(true);
66
+ const [subscriptions, setSubscriptions] = useState([]);
67
+ // 订阅清单决定左侧分组归拢;读取失败时退化为全部未分组
68
+ useEffect(() => {
69
+ try {
70
+ setSubscriptions(loadSubscriptions());
71
+ }
72
+ catch {
73
+ setSubscriptions([]);
74
+ }
75
+ }, [proxies.groups]);
76
+ // 左侧按订阅分组归拢:组头行 + `分组名 - 订阅名`。只改显示,
77
+ // currentGroup 仍取真实代理组,选中与切换逻辑不变。
78
+ const displayRows = useMemo(() => buildGroupDisplay(proxies.groups, subscriptions), [proxies.groups, subscriptions]);
79
+ const selectableRows = displayRows.filter((row) => !row.header);
80
+ const currentRow = selectableRows[Math.min(groupIndex, selectableRows.length - 1)];
81
+ const currentGroup = currentRow?.group;
82
+ const allNodes = useMemo(() => (currentGroup ? proxies.nodesOf(currentGroup.name) : []), [currentGroup, proxies]);
83
+ const nodes = useMemo(() => {
84
+ // 「只看可用」把超时和未测试的滤掉,直接得到能用的节点
85
+ let list = onlyAlive ? allNodes.filter((n) => n.delay !== undefined) : allNodes;
86
+ if (sortByDelay) {
87
+ // 有延迟的按值升序在前,未测试的居中,超时的垫底
88
+ const rank = (node) => node.delay !== undefined ? 0 : node.status === 'untested' ? 1 : 2;
89
+ list = [...list].sort((a, b) => rank(a) - rank(b) || (a.delay ?? 0) - (b.delay ?? 0));
90
+ }
91
+ return list;
92
+ }, [allNodes, onlyAlive, sortByDelay]);
93
+ // 组或过滤条件变化时把光标收回顶部,避免指向不存在的行
94
+ useEffect(() => {
95
+ setNodeIndex(0);
96
+ }, [currentGroup?.name, onlyAlive, sortByDelay]);
97
+ useInput((input, key) => {
98
+ if (key.leftArrow || input === 'h') {
99
+ setFocus('groups');
100
+ return;
101
+ }
102
+ if (key.rightArrow || input === 'l') {
103
+ if (nodes.length > 0)
104
+ setFocus('nodes');
105
+ return;
106
+ }
107
+ if (key.upArrow || input === 'k') {
108
+ if (focus === 'groups')
109
+ setGroupIndex((i) => Math.max(0, i - 1));
110
+ else
111
+ setNodeIndex((i) => Math.max(0, i - 1));
112
+ return;
113
+ }
114
+ if (key.downArrow || input === 'j') {
115
+ if (focus === 'groups')
116
+ setGroupIndex((i) => Math.min(selectableRows.length - 1, i + 1));
117
+ else
118
+ setNodeIndex((i) => Math.min(nodes.length - 1, i + 1));
119
+ return;
120
+ }
121
+ if (key.return) {
122
+ if (focus === 'nodes' && currentGroup) {
123
+ const node = nodes[nodeIndex];
124
+ if (node) {
125
+ // 对于 url-test/fallback 组,测速后会自动清除 fixed 并切回延迟最低节点
126
+ // 为了让用户的选择生效,需要找到节点所属的区域组,然后切换 PROXY → 区域组 → 节点
127
+ const isAutoGroup = currentGroup.type === 'URLTest' || currentGroup.type === 'Fallback';
128
+ if (isAutoGroup) {
129
+ // 找到节点所属的区域组并切换
130
+ void proxies.findRegionAndSelect(node.name).then((region) => {
131
+ if (region) {
132
+ onMessage(`已切换到 ${node.name}(通过 ${region} 组)`);
133
+ }
134
+ else {
135
+ onMessage(`已切换到 ${node.name}`);
136
+ }
137
+ });
138
+ }
139
+ else {
140
+ // 普通 select 组:切换组内节点 + 将 PROXY 切换到该组
141
+ void proxies.select(currentGroup.name, node.name).then(async () => {
142
+ // 同时将 PROXY 组切换到当前组(如果 PROXY 组包含该组)
143
+ const proxyGroupData = proxies.rawProxies['PROXY'];
144
+ if (proxyGroupData?.all?.includes(currentGroup.name)) {
145
+ await proxies.select('PROXY', currentGroup.name);
146
+ onMessage(`已切换 ${currentGroup.name} → ${node.name}`);
147
+ }
148
+ else {
149
+ onMessage(`已切换 ${currentGroup.name} → ${node.name}(PROXY 组未包含此组)`);
150
+ }
151
+ });
152
+ }
153
+ }
154
+ }
155
+ else if (nodes.length > 0) {
156
+ setFocus('nodes');
157
+ }
158
+ return;
159
+ }
160
+ if (input === 'u') {
161
+ // 切换 PROXY 组回到 AUTO,恢复自动选路
162
+ void proxies.select('PROXY', 'AUTO').then(() => {
163
+ onMessage('已恢复自动选路(PROXY → AUTO)');
164
+ });
165
+ return;
166
+ }
167
+ if (input === 't') {
168
+ // 小写 t:测当前选中的单个节点
169
+ if (focus === 'nodes') {
170
+ const node = nodes[nodeIndex];
171
+ if (node)
172
+ void proxies.testNode(node.name);
173
+ }
174
+ else if (currentGroup) {
175
+ void proxies.testGroup(currentGroup.name);
176
+ onMessage(`开始测速 ${currentGroup.name}(${currentGroup.nodeCount} 个节点)`);
177
+ }
178
+ return;
179
+ }
180
+ if (input === 'T' && currentGroup) {
181
+ void proxies.testGroup(currentGroup.name);
182
+ onMessage(`开始测速 ${currentGroup.name}(${currentGroup.nodeCount} 个节点)`);
183
+ return;
184
+ }
185
+ if (input === 'v') {
186
+ setOnlyAlive((v) => !v);
187
+ return;
188
+ }
189
+ if (input === 's') {
190
+ setSortByDelay((v) => !v);
191
+ onMessage(sortByDelay ? '排序:配置顺序' : '排序:延迟升序');
192
+ return;
193
+ }
194
+ if (input === 'r') {
195
+ proxies.refresh();
196
+ onMessage('已刷新');
197
+ }
198
+ }, { isActive: active });
199
+ // 高度预算:Panel 顶线 1 + 列表溢出计数 1 + 底边 1 + 页脚 1
200
+ const listHeight = Math.max(3, height - 4);
201
+ // 窄屏降级为单栏:只显示当前焦点那个面板
202
+ const narrow = width < 100;
203
+ const groupPanelWidth = narrow ? width : Math.min(32, Math.floor(width * 0.3));
204
+ // 右栏宽度 = 总宽度 - 左栏宽度,确保双栏总宽与 TopBar 对齐
205
+ const nodePanelWidth = narrow ? width : width - groupPanelWidth;
206
+ // 内容宽 = 面板宽 - 左右边框 2 - paddingX 2;组行额外让位给计数列。
207
+ // 额外留 2 列 emoji 余量:国旗 emoji 的显示宽随终端在 2~4 列间浮动
208
+ // (本项目的 displayWidth 按 2 计),行内容一旦顶到边框就会折行撑爆面板。
209
+ const groupNameBudget = Math.max(8, groupPanelWidth - 16);
210
+ const nodeNameBudget = Math.max(8, nodePanelWidth - 16);
211
+ const rawTitle = nodePanelTitle({
212
+ groupName: currentGroup?.name,
213
+ onlyAlive,
214
+ sortByDelay,
215
+ fixed: (currentGroup?.fixed ?? '') !== '',
216
+ aliveCount: currentGroup?.aliveCount ?? 0,
217
+ nodeCount: currentGroup?.nodeCount ?? 0,
218
+ testing: proxies.testingGroup === currentGroup?.name ? proxies.progress : undefined,
219
+ tick,
220
+ });
221
+ // Panel 按自家预算截标题(width-7);这里再收紧 2 列 emoji 余量,
222
+ // 保证标题里即便有宽理解分歧的 emoji 也不会把顶线挤折行。
223
+ const title = truncateDisplay(rawTitle, Math.max(1, nodePanelWidth - 9));
224
+ const groupTitle = `代理组 · ${selectableRows.length}`;
225
+ const groupPanel = (_jsx(Panel, { title: groupTitle, width: groupPanelWidth, fillHeight: true, children: _jsx(ScrollList, { items: displayRows, selected: displayRows.findIndex((row) => row.key === currentRow?.key), height: listHeight, emptyText: "\u65E0\u53EF\u7528\u4EE3\u7406\u7EC4", renderItem: (row, _index, isSelected) => row.header ? (_jsx(Text, { dimColor: true, children: ` ─ ${fitDisplay(row.label, groupNameBudget)}` })) : (_jsxs(Text, { children: [_jsx(Text, { color: rowBarColor(isSelected, focus === 'groups'), children: rowBar(isSelected, focus === 'groups') }), proxies.rawProxies['PROXY']?.now === row.group?.name ? (_jsx(Text, { ...styles.rowSelected, children: "\u25CF" })) : (' '), ` ${fitDisplay(row.label, groupNameBudget)}`, _jsx(Text, { dimColor: true, children: ` ${row.group?.aliveCount ?? 0}/${row.group?.nodeCount ?? 0}` })] })) }) }));
226
+ const nodePanel = (_jsx(Panel, { title: title, width: nodePanelWidth, fillHeight: true, children: _jsx(ScrollList, { items: nodes, selected: nodeIndex, height: listHeight, emptyText: currentGroup ? '该组无节点' : '请先选择代理组', renderItem: (node, index, isSelected) => (_jsxs(Text, { children: [_jsx(Text, { color: rowBarColor(isSelected, focus === 'nodes'), children: rowBar(isSelected, focus === 'nodes') }), node.current ? _jsx(Text, { ...styles.rowSelected, children: "\u25CF" }) : ' ', ` ${fitDisplay(node.name, nodeNameBudget)}`, _jsx(DelayBadge, { status: node.status, delay: node.delay, testing: node.testing })] })) }) }));
227
+ return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [narrow ? (
228
+ // 窄终端降级为单栏:只显示当前焦点面板,不做横向截断。
229
+ // row 容器让纵轴变交叉轴(默认 stretch)拉伸 Panel 外层盒,
230
+ // 内层 flexGrow 才有空间可分——直接挂在 column 下是空操作
231
+ _jsx(Box, { flexDirection: "row", flexGrow: 1, children: focus === 'groups' ? groupPanel : nodePanel })) : (_jsxs(Box, { flexDirection: "row", flexGrow: 1, children: [groupPanel, nodePanel] })), _jsx(Box, { paddingLeft: 1, children: _jsx(FooterLine, { hints: HINTS, width: width - 2 }) })] }));
232
+ }
233
+ //# sourceMappingURL=Proxies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Proxies.js","sourceRoot":"","sources":["../../src/views/Proxies.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,356 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useRef, useState } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ import { MihomoClient } from '../api/client.js';
5
+ import { ConfigManager } from '../config/manager.js';
6
+ import { RuleService, localController } from '../config/ruleService.js';
7
+ import { safeDiagnostic } from '../config/ruleValidation.js';
8
+ import { addRule, changeSummary, checkForm, checkRaw, deleteRule, draftErrors, editRule, FORM_TYPES, formatForm, isDirty, isProtected, movedRowIds, moveRule, parseForm, ruleType } from '../rules/editor.js';
9
+ import { InputDialog } from '../components/InputDialog.js';
10
+ import { ConfirmDialog } from '../components/ConfirmDialog.js';
11
+ import { ListDialog } from '../components/ListDialog.js';
12
+ import { ProgressDialog } from '../components/ProgressDialog.js';
13
+ import { ScrollList } from '../components/ScrollList.js';
14
+ import { FooterLine } from '../ui/FooterLine.js';
15
+ import { Panel } from '../ui/Panel.js';
16
+ import { colors } from '../ui/theme.js';
17
+ import { useKeyCapture } from '../ui/keyCapture.js';
18
+ import { useExitGuard } from '../ui/exitGuard.js';
19
+ import { displayWidth, truncateDisplay } from '../commands/output.js';
20
+ const PHASES = ['prepare', 'conflict', 'validate', 'backup', 'write', 'postvalidate', 'reload', 'verify', 'recover'];
21
+ const LABELS = { prepare: '检查草稿', conflict: '检查文件冲突', validate: '隔离校验(可取消)', backup: '创建唯一备份',
22
+ write: '原子写入', postvalidate: '写后复验', reload: '重载完整配置', verify: '读取运行规则', recover: '恢复文件与运行状态' };
23
+ const DISK_LABELS = { original: '原文件', candidate: '已保存候选配置', external: '检测到外部变更', unknown: '未知' };
24
+ const KERNEL_LABELS = { confirmed: '已回读确认', 'original-confirmed': '已恢复并回读确认', 'reload-accepted': '重载已接受,回读未确认', unknown: '未知', unchanged: '未发起重载' };
25
+ const wrap = (text, width) => {
26
+ const lines = [];
27
+ for (const paragraph of text.split('\n')) {
28
+ let line = '';
29
+ for (const char of paragraph) {
30
+ if (displayWidth(line + char) > width) {
31
+ lines.push(line);
32
+ line = '';
33
+ }
34
+ line += char;
35
+ }
36
+ lines.push(line);
37
+ }
38
+ return lines;
39
+ };
40
+ export function RuleEditor({ config, width, height, onClose, onSaved, createService }) {
41
+ const [phase, setPhase] = useState('prepare');
42
+ const phaseRef = useRef('prepare');
43
+ const progress = (next) => { phaseRef.current = next; setPhase(next); };
44
+ const service = useMemo(() => createService ? createService(progress) : new RuleService({
45
+ manager: new ConfigManager(config.mihomoDir, config.mihomoBin), client: new MihomoClient(config),
46
+ controller: config.api, secret: config.secret, onProgress: progress,
47
+ }), [config, createService]);
48
+ const [loaded] = useState(() => {
49
+ try {
50
+ return { snapshot: service.open() };
51
+ }
52
+ catch (err) {
53
+ return { error: safeDiagnostic(err, [config.secret]) };
54
+ }
55
+ });
56
+ const snapshot = loaded.snapshot;
57
+ const [draft, setDraft] = useState(snapshot?.draft);
58
+ const [dialog, setDialog] = useState('list');
59
+ const [selected, setSelected] = useState(draft?.rows[0]?.id);
60
+ const [filter, setFilter] = useState('');
61
+ const [notice, setNotice] = useState('');
62
+ const [editing, setEditing] = useState();
63
+ const [form, setForm] = useState({ type: 'DOMAIN', value: '', target: 'DIRECT', noResolve: false });
64
+ const [result, setResult] = useState();
65
+ const [detailOffset, setDetailOffset] = useState(0);
66
+ const busy = useRef(false);
67
+ const abort = useRef(undefined);
68
+ const exitIntent = useRef(undefined);
69
+ const cardWidth = Math.min(64, width);
70
+ const inner = Math.max(1, cardWidth - 4);
71
+ const clip = (value) => truncateDisplay(value, inner);
72
+ const visible = draft?.rows.filter(r => r.raw.toLowerCase().includes(filter.toLowerCase())) ?? [];
73
+ const index = Math.max(0, visible.findIndex(r => r.id === selected));
74
+ const row = visible[index];
75
+ const movedIds = useMemo(() => draft ? movedRowIds(draft) : new Set(), [draft]);
76
+ const locked = result?.status === 'pending' || result?.status === 'recovery-required';
77
+ const blocked = draft?.blocked ?? (!localController(config.api) ? '非本机回环控制器,仅可查看' : undefined);
78
+ useKeyCapture(true);
79
+ const close = (exit) => {
80
+ if (busy.current)
81
+ return;
82
+ if (result?.status === 'pending' && !exit) {
83
+ setNotice('运行状态待确认,请按 r 重新读取');
84
+ setDialog('result');
85
+ return;
86
+ }
87
+ if (draft && isDirty(draft)) {
88
+ exitIntent.current = exit;
89
+ setDialog('discard');
90
+ }
91
+ else
92
+ (exit ?? onClose)();
93
+ };
94
+ useExitGuard(exit => {
95
+ if (busy.current) {
96
+ if (['prepare', 'conflict', 'validate'].includes(phaseRef.current)) {
97
+ exitIntent.current = exit;
98
+ abort.current?.abort();
99
+ setNotice('正在取消校验,请等待子进程退出');
100
+ }
101
+ else
102
+ setNotice('正在保存,请等待完成');
103
+ return;
104
+ }
105
+ close(exit);
106
+ });
107
+ const update = (operation) => {
108
+ try {
109
+ const next = operation();
110
+ const nextVisible = next.rows.filter(r => r.raw.toLowerCase().includes(filter.toLowerCase()));
111
+ if (!nextVisible.some(r => r.id === selected))
112
+ setSelected(nextVisible[Math.min(index, nextVisible.length - 1)]?.id);
113
+ setDraft(next);
114
+ setNotice('');
115
+ setDialog('list');
116
+ }
117
+ catch (err) {
118
+ setNotice(safeDiagnostic(err, [config.secret]));
119
+ }
120
+ };
121
+ const save = async (reread = false) => {
122
+ if (!snapshot || !draft || busy.current)
123
+ return;
124
+ busy.current = true;
125
+ abort.current = new AbortController();
126
+ setDialog('progress');
127
+ setNotice('');
128
+ try {
129
+ const next = reread ? await service.reread() : await service.save(snapshot, draft, abort.current.signal);
130
+ setResult(next);
131
+ setDetailOffset(0);
132
+ if (next.status === 'confirmed') {
133
+ onSaved(next);
134
+ onClose();
135
+ return;
136
+ }
137
+ setNotice(next.message);
138
+ setDialog('result');
139
+ if (exitIntent.current && next.status === 'not-applied')
140
+ setDialog('discard');
141
+ }
142
+ catch (err) {
143
+ setNotice(safeDiagnostic(err, [config.secret]));
144
+ setDialog('list');
145
+ }
146
+ finally {
147
+ busy.current = false;
148
+ abort.current = undefined;
149
+ }
150
+ };
151
+ const submit = (raw, formMatch = false) => {
152
+ if (!draft)
153
+ return;
154
+ update(() => editing ? editRule(draft, editing, raw) : addRule(draft, raw, row?.id, Boolean(filter), formMatch));
155
+ };
156
+ const detailLines = wrap([snapshot?.path ?? '', row?.raw ?? '', row && draft && isProtected(draft, row)
157
+ ? '订阅直连 · 自动维护(按规则内容识别)' : '普通配置规则', '位置:' + (draft?.rows.findIndex(r => r.id === row?.id) + 1)].join('\n'), inner);
158
+ const resultLines = result ? wrap([result.message, '阶段:' + LABELS[result.phase], '磁盘:' + DISK_LABELS[result.disk],
159
+ '内核:' + KERNEL_LABELS[result.kernel], ...(result.backupPath ? ['备份:' + result.backupPath] : [])].join('\n'), inner) : [];
160
+ useInput((input, key) => {
161
+ if (key.ctrl && input === 'c')
162
+ return; // App routes the exit intent through the guard.
163
+ if (dialog === 'progress')
164
+ return;
165
+ if (width < 40 || height < 16) {
166
+ if (dialog === 'discard') {
167
+ if (input === 'y' || input === 'Y')
168
+ (exitIntent.current ?? onClose)();
169
+ else if (key.escape || input === 'n' || input === 'N') {
170
+ exitIntent.current = undefined;
171
+ setDialog(locked ? 'result' : 'list');
172
+ }
173
+ }
174
+ else if (input === 'r' && result?.status === 'pending')
175
+ void save(true);
176
+ // 窄屏下 pending 不经 close()(它会弹回不可见的 result 页),直接进确认退出
177
+ else if (key.escape) {
178
+ if (result?.status === 'pending')
179
+ setDialog('discard');
180
+ else
181
+ close();
182
+ }
183
+ return;
184
+ }
185
+ if (dialog === 'detail') {
186
+ if (key.escape || key.return)
187
+ setDialog('list');
188
+ if (key.downArrow || input === 'j')
189
+ setDetailOffset(n => Math.min(Math.max(0, detailLines.length - 1), n + 1));
190
+ if (key.upArrow || input === 'k')
191
+ setDetailOffset(n => Math.max(0, n - 1));
192
+ return;
193
+ }
194
+ if (dialog === 'result') {
195
+ if (key.downArrow || input === 'j')
196
+ setDetailOffset(n => Math.min(Math.max(0, resultLines.length - 1), n + 1));
197
+ if (key.upArrow || input === 'k')
198
+ setDetailOffset(n => Math.max(0, n - 1));
199
+ if (input === 'r' && result?.status === 'pending')
200
+ void save(true);
201
+ else if (key.escape || key.return) {
202
+ if (result?.status !== 'pending')
203
+ setDialog('list');
204
+ }
205
+ return;
206
+ }
207
+ if (dialog !== 'list')
208
+ return;
209
+ if (key.escape) {
210
+ close();
211
+ return;
212
+ }
213
+ if (!draft)
214
+ return;
215
+ if (key.upArrow || input === 'k')
216
+ setSelected(visible[Math.max(0, index - 1)]?.id);
217
+ else if (key.downArrow || input === 'j')
218
+ setSelected(visible[Math.min(visible.length - 1, index + 1)]?.id);
219
+ else if (input === '/')
220
+ setDialog('filter');
221
+ else if (key.return && row) {
222
+ setDetailOffset(0);
223
+ setDialog('detail');
224
+ }
225
+ else if (input === 'a' || input === 'e' || input === 'd' || input === 'J' || input === 'K' || (key.ctrl && input === 's')) {
226
+ if (blocked || locked) {
227
+ setNotice(blocked ?? '运行状态尚未确认,禁止修改或再次保存');
228
+ return;
229
+ }
230
+ if (key.ctrl && input === 's') {
231
+ const errors = draftErrors(draft);
232
+ if (!isDirty(draft))
233
+ setNotice('没有待保存修改');
234
+ else if (errors.length)
235
+ setNotice(errors.join(';'));
236
+ else
237
+ setDialog('save');
238
+ }
239
+ else if (input === 'a') {
240
+ if (filter) {
241
+ setNotice('清除筛选后可新增或调整顺序');
242
+ return;
243
+ }
244
+ setEditing(undefined);
245
+ setDialog('mode');
246
+ setNotice('');
247
+ }
248
+ else if (row) {
249
+ if (isProtected(draft, row)) {
250
+ setNotice('订阅直连 · 自动维护(按规则内容识别),不可修改');
251
+ return;
252
+ }
253
+ if (input === 'e') {
254
+ setEditing(row.id);
255
+ const parsed = parseForm(row.raw, draft);
256
+ if (parsed) {
257
+ setForm(parsed);
258
+ setDialog('mode');
259
+ }
260
+ else
261
+ setDialog('raw');
262
+ setNotice('');
263
+ }
264
+ else if (input === 'd')
265
+ setDialog('delete');
266
+ else
267
+ update(() => moveRule(draft, row.id, input === 'J' ? 1 : -1, Boolean(filter)));
268
+ }
269
+ }
270
+ });
271
+ const cancel = () => { setDialog('list'); setNotice(''); };
272
+ let content;
273
+ if ((width < 40 || height < 16) && dialog !== 'progress') {
274
+ content = dialog === 'discard'
275
+ ? _jsx(Text, { children: result?.status === 'pending' ? '运行待确认,退出?y/n' : '放弃草稿?y 确认 / n 取消' })
276
+ : result?.status === 'pending'
277
+ ? _jsx(Text, { children: "\u8BF7\u6269\u5927\u7EC8\u7AEF\uFF08\u81F3\u5C11 40\u00D722\uFF09\uFF1B\u8FD0\u884C\u5F85\u786E\u8BA4\uFF1Ar \u91CD\u65B0\u8BFB\u53D6 / ESC \u9000\u51FA\u7F16\u8F91" })
278
+ : _jsx(Text, { children: "\u8BF7\u6269\u5927\u7EC8\u7AEF\uFF08\u81F3\u5C11 40\u00D722\uFF09\uFF1BESC \u8FD4\u56DE" });
279
+ }
280
+ else if (dialog === 'progress') {
281
+ content = _jsx(ProgressDialog, { title: "\u4FDD\u5B58\u914D\u7F6E\u89C4\u5219", current: LABELS[phase], step: PHASES.indexOf(phase), total: PHASES.length, detail: notice ? clip(notice) : undefined, width: cardWidth, cancelable: ['prepare', 'conflict', 'validate'].includes(phase), onCancel: () => abort.current?.abort() });
282
+ }
283
+ else if (dialog === 'mode') {
284
+ content = _jsx(ListDialog, { borderTitle: editing ? '编辑方式' : '新增规则', width: cardWidth, items: [{ value: 'form', label: '常用表单' }, { value: 'raw', label: '单条原文' }], onSubmit: value => setDialog(value === 'raw' ? 'raw' : editing ? 'form' : 'type'), onCancel: cancel });
285
+ }
286
+ else if (dialog === 'type') {
287
+ content = _jsx(ListDialog, { borderTitle: "\u89C4\u5219\u7C7B\u578B", width: cardWidth, maxVisible: Math.max(1, height - 6), items: FORM_TYPES.map(type => ({ value: type, label: type })), onSubmit: type => { setForm({ type, value: '', target: 'DIRECT', noResolve: false }); setDialog('form'); }, onCancel: cancel });
288
+ }
289
+ else if (dialog === 'filter' || dialog === 'raw' || dialog === 'form') {
290
+ const fields = dialog === 'filter' ? [{ key: 'filter', label: '关键字(留空清除)', value: filter }]
291
+ : dialog === 'raw' ? [{ key: 'raw', label: '完整单条原文', value: draft?.rows.find(r => r.id === editing)?.raw ?? '', required: true,
292
+ validate: value => { try {
293
+ checkRaw(value);
294
+ }
295
+ catch (err) {
296
+ return safeDiagnostic(err);
297
+ } } }]
298
+ : [
299
+ ...(form.type === 'MATCH' ? [] : [{ key: 'value', label: form.type === 'RULE-SET' ? '已有规则集' : '匹配值', value: form.value, required: true,
300
+ ...(form.type === 'RULE-SET' ? { options: draft?.providers.map(value => ({ value, label: value })) } : {}) }]),
301
+ { key: 'target', label: '目标(e 选择)', value: form.target, required: true, options: draft?.targets.map(value => ({ value, label: value })) },
302
+ ...(['IP-CIDR', 'IP-CIDR6', 'RULE-SET'].includes(form.type) ? [{ key: 'noResolve', label: 'no-resolve(跳过域名解析)', value: form.noResolve ? 'yes' : 'no',
303
+ options: [{ value: 'no', label: '关闭' }, { value: 'yes', label: '开启' }] }] : []),
304
+ ];
305
+ content = _jsxs(Box, { flexDirection: "column", children: [_jsx(InputDialog, { title: dialog === 'filter' ? '规则过滤' : dialog === 'raw' ? '单条规则原文' : form.type, fields: fields, width: cardWidth, singleLinePaste: true, onCancel: cancel, onSubmit: values => {
306
+ if (dialog === 'filter') {
307
+ setFilter(values.filter ?? '');
308
+ setSelected(undefined);
309
+ cancel();
310
+ }
311
+ else if (dialog === 'raw')
312
+ submit(values.raw ?? '');
313
+ else {
314
+ const next = { ...form, value: values.value ?? '', target: values.target ?? '', noResolve: values.noResolve === 'yes' };
315
+ try {
316
+ checkForm(next, draft);
317
+ submit(formatForm(next), true);
318
+ }
319
+ catch (err) {
320
+ setNotice(safeDiagnostic(err));
321
+ }
322
+ }
323
+ } }, dialog), notice && _jsx(Text, { color: colors.danger, children: clip(notice) })] });
324
+ }
325
+ else if (dialog === 'delete' && row && draft) {
326
+ content = _jsx(ConfirmDialog, { width: cardWidth, danger: true, message: ['删除 #' + (draft.rows.indexOf(row) + 1) + '?仅修改草稿', clip(row.raw),
327
+ '该行附属注释也会删除', ...(ruleType(row.raw) === 'MATCH' ? ['将移除显式兜底规则'] : [])], onConfirm: () => update(() => deleteRule(draft, row.id)), onCancel: cancel });
328
+ }
329
+ else if (dialog === 'discard') {
330
+ content = _jsx(ConfirmDialog, { width: cardWidth, danger: true, message: [result?.status === 'pending' ? '配置已保存但运行状态待确认,确认退出?' : '放弃未保存草稿' + (exitIntent.current ? '并退出程序?' : '并返回?'),
331
+ ...(locked ? ['磁盘/运行状态尚未确认,备份需保留'] : [])], onConfirm: () => (exitIntent.current ?? onClose)(), onCancel: () => { exitIntent.current = undefined; setDialog(locked ? 'result' : 'list'); } });
332
+ }
333
+ else if (dialog === 'save' && draft && snapshot) {
334
+ const summary = changeSummary(draft);
335
+ const lines = ['确认保存并重载完整配置?', `新增 ${summary.added} · 修改 ${summary.edited} · 删除 ${summary.deleted} · 移动 ${summary.moved}`,
336
+ snapshot.path, new URL(config.api).origin, '确认此控制器属于本机读取上述路径的 mihomo', '临时禁用和本地标记将清除',
337
+ ...(draft.rows.some(r => ruleType(r.raw) === 'MATCH') ? [] : ['无显式 MATCH(兜底规则)'])];
338
+ content = wrap(lines.join('\n'), inner).length + 8 > height
339
+ ? _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: "\u786E\u8BA4\u5185\u5BB9\u8F83\u957F\uFF0C\u8BF7\u6269\u5927\u7EC8\u7AEF\uFF1BESC \u8FD4\u56DE" }), _jsx(ConfirmDialog, { width: cardWidth, message: "\u8FD4\u56DE\u8349\u7A3F\u540E\u53EF\u67E5\u770B\u5B8C\u6574\u8DEF\u5F84", onConfirm: cancel, onCancel: cancel })] })
340
+ : _jsx(ConfirmDialog, { width: cardWidth, message: lines, onConfirm: () => void save(), onCancel: cancel });
341
+ }
342
+ else if (dialog === 'detail') {
343
+ content = _jsxs(Panel, { title: "\u914D\u7F6E\u89C4\u5219\u539F\u6587 \u00B7 \u2191\u2193 \u6EDA\u52A8", width: cardWidth, children: [detailLines.slice(detailOffset, detailOffset + Math.max(1, height - 5)).map((line, i) => _jsx(Text, { children: line }, i)), _jsx(Text, { dimColor: true, children: "Enter / ESC \u8FD4\u56DE" })] });
344
+ }
345
+ else if (dialog === 'result' && result) {
346
+ content = _jsxs(Panel, { title: result.status === 'pending' ? '已保存 · 待确认' : result.status === 'recovery-required' ? '需要人工恢复' : '保存未完成 · 草稿保留', width: cardWidth, children: [resultLines.slice(detailOffset, detailOffset + Math.max(1, height - 5)).map((line, i) => _jsx(Text, { children: line }, i)), _jsx(Text, { dimColor: true, children: result.status === 'pending' ? '↑↓ 滚动 · r 重新读取(不重复重载)' : '↑↓ 滚动 · Enter / ESC 返回草稿' })] });
347
+ }
348
+ else {
349
+ const errors = draft ? draftErrors(draft) : [];
350
+ content = _jsxs(Panel, { title: '配置规则' + (draft && isDirty(draft) ? ' · 未保存' : ''), width: cardWidth, children: [_jsx(Text, { dimColor: true, children: clip(snapshot?.path ?? config.mihomoDir) }), _jsx(Text, { dimColor: true, children: clip(`共 ${draft?.rows.length ?? 0} 条 · 从上到下匹配(PASS 等依内核语义)`) }), filter && _jsx(Text, { children: clip('过滤:' + filter) }), (loaded.error || blocked || notice || errors[0]) && _jsx(Text, { color: colors.warning, children: clip(loaded.error || blocked || notice || errors[0] || '') }), _jsx(ScrollList, { items: visible, selected: index, height: Math.max(1, height - 10 - (filter ? 1 : 0)), emptyText: "\u6CA1\u6709\u5339\u914D\u89C4\u5219\uFF1Ba \u65B0\u589E\uFF0C/ \u6E05\u9664\u8FC7\u6EE4", renderItem: (item, i) => _jsx(Text, { color: i === index ? colors.accent : undefined, children: clip((i === index ? '▌' : ' ') + '#' + (draft.rows.indexOf(item) + 1) +
351
+ (isProtected(draft, item) ? ' [保护]' : item.original === undefined ? ' +' : item.original !== item.raw ? ' *' : '') +
352
+ (movedIds.has(item.id) ? ' ↕' : '') + ' ' + item.raw) }) }), _jsx(FooterLine, { width: inner, hints: [{ key: 'a/e/d', label: '增改删' }, { key: 'J/K', label: '调序' }, { key: '/', label: '过滤' }] }), _jsx(FooterLine, { width: inner, hints: [{ key: 'Enter', label: '原文' }, { key: 'Ctrl+S', label: '保存' }, { key: 'ESC', label: '返回' }] })] });
353
+ }
354
+ return _jsx(Box, { flexGrow: 1, flexDirection: "column", alignItems: "center", justifyContent: "center", children: content });
355
+ }
356
+ //# sourceMappingURL=RuleEditor.js.map