路由详情加附加规则:每路入站自己的分流表

用内核的 sub-rules 机制:listener 写 rule: 指向顶层 sub-rules 里的表
(listener/inbound/base.go:96,tunnel.go:713-721)。两个硬约束决定了
形态:proxy 优先级更高且完全短路规则匹配(tunnel.go:318-325),有附加
规则时 listener 只能写 rule;子规则表没命中时兜底是 DIRECT 而不是这一
路的出站(tunnel.go match 循环末尾),表底那条 MATCH,专属组 是承重的,
由 Patchbay 自动维护、界面上单独画不参与编辑。不变式:附加规则空 ⇔
proxy 直连专属组,非空 ⇔ rule 形态。

三处既有逻辑教会认 rule 形态:applySetup 不再无条件写 proxy(会把附加
规则静默短路掉);referencesTo 扫 sub-rules 里的规则——表底 MATCH 是
rule 形态下专属组活着的唯一理由,不数它就会被当孤儿清掉;pruneGenerated
清理没有 listener 引用的生成表。panelGroups 和 syncGenerated 的「是否
由 Patchbay 接线」收敛成一个 managedWiring。

listener 的 rule 指向不存在的表时内核静默退回全局规则(getRules 的
else 分支),-t 和重载都不拦,自检里补上这条(vendor 本地改动,记入
ORIGIN.md)。界面:详情列新增附加规则区块,逐条增删改和排序复用
RuleForm,带内网直连预设;生成形状过了 mihomo -t 实测。
This commit is contained in:
2026-08-12 04:01:08 +08:00
parent d1214d4cbd
commit c3c0f56e56
6 changed files with 507 additions and 11 deletions
+94
View File
@@ -11,16 +11,19 @@ import { collectInbounds } from '@shared/inbounds.ts'
import type { Inbound } from '@shared/inbounds.ts'
import {
applySetup,
applySubRules,
chainGroupName,
groupUses,
panelGroups,
proxyGroupName,
pruneGenerated,
readSubRules,
sharedPeers,
syncGenerated,
readSetup,
userProviders,
} from '@shared/inbound-groups.ts'
import { missingLanRules, moveRule, ruleProvidersOf } from '@shared/rules.ts'
import type { OutboundSetup } from '@shared/inbound-groups.ts'
import type { ConfigObject } from '../../vendor/config-editor/index.ts'
import { ApiError, api } from '../api'
@@ -33,6 +36,7 @@ import ConfirmDialog from '../components/ConfirmDialog.vue'
import InboundList from '../components/InboundList.vue'
import InboundDetail from './InboundDetail.vue'
import InboundForm from './InboundForm.vue'
import RuleForm from './RuleForm.vue'
import IconAdd from '~icons/ri/add-line'
const props = defineProps<{
@@ -219,6 +223,71 @@ async function selectMember(group: string, member: string) {
}
}
/* ── 附加规则 ─────────────────────────────────── */
/** 选中这一路的附加规则(不含表底的兜底 MATCH)。 */
const subRules = computed(() =>
selected.value && setup.value !== null ? readSubRules(props.data, selected.value.name) : [],
)
/** RULE-SET 载荷的候选,给规则表单用 */
const ruleProviderNames = computed(() => Object.keys(ruleProvidersOf(props.data)))
const subFormOpen = ref(false)
/** 编辑时是下标,新增时是 null */
const subEditing = ref<number | null>(null)
/** 待确认删除的附加规则下标 */
const removingSubRule = ref<number | null>(null)
function openSubAdd() {
subEditing.value = null
subFormOpen.value = true
}
function openSubEdit(index: number) {
subEditing.value = index
subFormOpen.value = true
}
/** 所有附加规则的改动都收敛到这里:算出新表,整表写回。 */
async function commitSubRules(next: string[], success: string): Promise<boolean> {
const target = selected.value
if (!target || saving.value) return false
return commit(applySubRules(clone(), target.name, next), success)
}
async function submitSubRule(ruleText: string) {
const editing = subEditing.value
const next =
editing === null
? [...subRules.value, ruleText]
: subRules.value.map((rule, i) => (i === editing ? ruleText : rule))
const ok = await commitSubRules(next, editing === null ? '已新增附加规则' : '已保存')
// 失败时不能关对话框——关了再打开就是空表单
if (ok) subFormOpen.value = false
}
async function moveSubRule(index: number, delta: -1 | 1) {
if (saving.value) return
await commitSubRules(moveRule(subRules.value, index, delta), '顺序已保存')
}
async function removeSubRuleConfirmed() {
const index = removingSubRule.value
removingSubRule.value = null
if (index === null) return
await commitSubRules(
subRules.value.filter((_, i) => i !== index),
'已删除这条附加规则',
)
}
async function addLanSubRules() {
const lan = missingLanRules(subRules.value)
if (lan.length === 0) return
await commitSubRules([...subRules.value, ...lan], `已加入 ${lan.length} 条内网直连规则`)
}
/** 待确认删除的那一路。非 null 时确认对话框打开着。 */
const removing = ref<Inbound | null>(null)
@@ -280,6 +349,7 @@ async function removeConfirmed() {
:chain="chainView"
:subscriptions="subscriptions"
:setup="setup"
:sub-rules="subRules"
:testing="testing"
:saving="saving"
@edit="openEdit"
@@ -288,6 +358,11 @@ async function removeConfirmed() {
@test="test"
@save="saveSetup"
@adopt="adopt"
@sub-add="openSubAdd"
@sub-edit="openSubEdit"
@sub-remove="removingSubRule = $event"
@sub-move="moveSubRule"
@sub-lan="addLanSubRules"
/>
</div>
@@ -310,6 +385,25 @@ async function removeConfirmed() {
@close="removing = null"
@confirm="removeConfirmed"
/>
<RuleForm
:open="subFormOpen"
:editing="subEditing === null ? null : { index: subEditing, raw: subRules[subEditing]! }"
:provider-names="ruleProviderNames"
:saving="saving"
@close="subFormOpen = false"
@submit="submitSubRule"
/>
<ConfirmDialog
:open="removingSubRule !== null"
:message="
removingSubRule !== null ? `删除附加规则「${subRules[removingSubRule] ?? ''}」?` : ''
"
action="删除"
@close="removingSubRule = null"
@confirm="removeSubRuleConfirmed"
/>
</div>
</template>