路由详情加附加规则:每路入站自己的分流表
用内核的 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:
+193
-1
@@ -18,7 +18,9 @@ import {
|
||||
fallbackGroupName,
|
||||
linesMatching,
|
||||
proxyGroupName,
|
||||
subRuleName,
|
||||
} from '@shared/inbound-groups.ts'
|
||||
import { missingLanRules } from '@shared/rules.ts'
|
||||
import type { GroupSpec, OutboundSetup, PanelGroup } from '@shared/inbound-groups.ts'
|
||||
import { resolveChain } from '@shared/outbound-chain.ts'
|
||||
import { latestDelay } from '../api'
|
||||
@@ -32,6 +34,9 @@ import IconEdit from '~icons/ri/edit-line'
|
||||
import IconDelete from '~icons/ri/delete-bin-line'
|
||||
import IconRight from '~icons/ri/arrow-right-s-line'
|
||||
import IconOffline from '~icons/ri/cloud-off-line'
|
||||
import IconAdd from '~icons/ri/add-line'
|
||||
import IconUp from '~icons/ri/arrow-up-line'
|
||||
import IconDown from '~icons/ri/arrow-down-line'
|
||||
|
||||
const props = defineProps<{
|
||||
inbound: Inbound
|
||||
@@ -45,6 +50,8 @@ const props = defineProps<{
|
||||
subscriptions: string[]
|
||||
/** 当前的来源和筛选,配置表单的初值 */
|
||||
setup: OutboundSetup | null
|
||||
/** 这一路的附加规则(不含表底的兜底 MATCH,那条由 Patchbay 自动维护) */
|
||||
subRules: string[]
|
||||
/** 正在测延迟的线路 */
|
||||
testing: string[]
|
||||
/** 配置正在保存 */
|
||||
@@ -61,8 +68,22 @@ const emit = defineEmits<{
|
||||
save: [setup: OutboundSetup, picks: { proxy: string | null; chain: string | null }]
|
||||
/** 把这一路改成专属代理组模式 */
|
||||
adopt: []
|
||||
/** 附加规则的增删改和排序。规则文本的编辑走父组件的 RuleForm */
|
||||
'sub-add': []
|
||||
'sub-edit': [index: number]
|
||||
'sub-remove': [index: number]
|
||||
'sub-move': [index: number, delta: -1 | 1]
|
||||
'sub-lan': []
|
||||
}>()
|
||||
|
||||
/** 手写的 rule 字段(不是 Patchbay 的附加规则表)才在事实行里原样展示 */
|
||||
const foreignRuleSet = computed(
|
||||
() => props.inbound.ruleSet !== null && props.inbound.ruleSet !== subRuleName(props.inbound.name),
|
||||
)
|
||||
|
||||
/** 内网直连还缺几条。全齐了按钮就不出现 */
|
||||
const lanMissing = computed(() => missingLanRules(props.subRules).length)
|
||||
|
||||
/** 默认入站由配置顶层的端口字段声明,没有 proxy 字段可写,也不能删。 */
|
||||
const locked = computed(() => props.inbound.isDefault)
|
||||
|
||||
@@ -434,7 +455,8 @@ const complete = computed(
|
||||
<dd class="mono">{{ inbound.type }}</dd>
|
||||
<dt>监听</dt>
|
||||
<dd class="mono">{{ inbound.listen ?? '0.0.0.0' }}</dd>
|
||||
<template v-if="inbound.ruleSet">
|
||||
<!-- Patchbay 自己的附加规则表有专门的编辑区块,这里只展示手写的 -->
|
||||
<template v-if="foreignRuleSet">
|
||||
<dt>附加规则</dt>
|
||||
<dd class="mono">{{ inbound.ruleSet }}</dd>
|
||||
</template>
|
||||
@@ -671,6 +693,71 @@ const complete = computed(
|
||||
</div>
|
||||
</Transition>
|
||||
</section>
|
||||
|
||||
<!-- 附加规则:只对从这一路进来的流量生效的分流。表底的兜底 MATCH 是生成物,
|
||||
把没被分走的流量送回这一路自己的出站,单独画、不参与编辑 -->
|
||||
<section v-if="setup !== null" class="block">
|
||||
<h3>
|
||||
附加规则
|
||||
<span class="h3-acts">
|
||||
<button
|
||||
v-if="lanMissing > 0"
|
||||
type="button"
|
||||
class="mini"
|
||||
:disabled="saving"
|
||||
:title="`加入 ${lanMissing} 条内网直连规则:回环、私网和链路本地地址不进代理`"
|
||||
@click="emit('sub-lan')"
|
||||
>
|
||||
内网直连
|
||||
</button>
|
||||
<button type="button" class="mini" :disabled="saving" @click="emit('sub-add')">
|
||||
<IconAdd /> 添加
|
||||
</button>
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<ul class="srules">
|
||||
<li v-for="(rule, i) in subRules" :key="i" class="srule">
|
||||
<span class="mono s-text" :title="rule">{{ rule }}</span>
|
||||
<span class="s-acts">
|
||||
<button
|
||||
type="button"
|
||||
title="上移"
|
||||
:disabled="saving || i === 0"
|
||||
@click="emit('sub-move', i, -1)"
|
||||
>
|
||||
<IconUp />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
title="下移"
|
||||
:disabled="saving || i === subRules.length - 1"
|
||||
@click="emit('sub-move', i, 1)"
|
||||
>
|
||||
<IconDown />
|
||||
</button>
|
||||
<button type="button" title="编辑" :disabled="saving" @click="emit('sub-edit', i)">
|
||||
<IconEdit />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="s-danger"
|
||||
title="删除"
|
||||
:disabled="saving"
|
||||
@click="emit('sub-remove', i)"
|
||||
>
|
||||
<IconDelete />
|
||||
</button>
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
class="srule is-final"
|
||||
title="兜底:没被上面的规则分走的流量走这一路自己的出站。这一条由 Patchbay 自动维护"
|
||||
>
|
||||
<span class="mono s-text">MATCH,{{ proxyGroupName(inbound.name) }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</template>
|
||||
</div>
|
||||
</Transition>
|
||||
@@ -798,6 +885,111 @@ const complete = computed(
|
||||
border-top: 1px solid var(--c-border);
|
||||
}
|
||||
|
||||
/* ── 附加规则 ────────────────────────────────── */
|
||||
.h3-acts {
|
||||
margin-left: auto;
|
||||
display: inline-flex;
|
||||
gap: var(--s-2);
|
||||
}
|
||||
|
||||
.mini {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--s-1);
|
||||
padding: 2px var(--s-2);
|
||||
border: none;
|
||||
border-radius: var(--r-sm);
|
||||
background: none;
|
||||
color: var(--c-text-soft);
|
||||
font-size: var(--t-xs);
|
||||
font-weight: 400;
|
||||
cursor: pointer;
|
||||
}
|
||||
.mini:hover:not(:disabled) {
|
||||
background: var(--c-surface-sunken);
|
||||
color: var(--c-text);
|
||||
}
|
||||
.mini:disabled {
|
||||
color: var(--c-text-faint);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.mini svg {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
.srules {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.srule {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--s-2);
|
||||
padding: var(--s-1) var(--s-2);
|
||||
border-radius: var(--r-sm);
|
||||
}
|
||||
.srule:hover {
|
||||
background: var(--c-surface-sunken);
|
||||
}
|
||||
|
||||
.s-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: var(--t-xs);
|
||||
}
|
||||
|
||||
/* 兜底那行是生成物:淡色示意,不给操作 */
|
||||
.is-final .s-text {
|
||||
color: var(--c-text-faint);
|
||||
}
|
||||
|
||||
/* 操作按钮悬停到行上才现身,规则文本是主角 */
|
||||
.s-acts {
|
||||
flex: none;
|
||||
display: inline-flex;
|
||||
gap: 1px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
.srule:hover .s-acts,
|
||||
.s-acts:focus-within {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.s-acts button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border: none;
|
||||
border-radius: var(--r-sm);
|
||||
background: none;
|
||||
color: var(--c-text-faint);
|
||||
cursor: pointer;
|
||||
}
|
||||
.s-acts button:hover:not(:disabled) {
|
||||
background: var(--c-surface);
|
||||
color: var(--c-text);
|
||||
}
|
||||
.s-acts button:disabled {
|
||||
opacity: 0.35;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.s-acts .s-danger:hover:not(:disabled) {
|
||||
color: var(--c-danger);
|
||||
}
|
||||
.s-acts svg {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user