规则列表标出不可达行,「分流规则」改名「全局规则」
MATCH 的实现是无条件返回 true(rules/common/final.go:16-18),第一条 MATCH 之后的行永远轮不到。全局规则和附加规则的列表都把这类行标红、 拦路的 MATCH 标黄,hover 给原因;表底作兜底的 MATCH 不标黄,那是正常 用法。附加规则里用户自己的 MATCH 总会架空表底的自动兜底,兜底行跟着 标红。 规则页的「分流规则」改名「全局规则」,和「附加规则」对仗;术语表里 「分流规则」退为两者的统称。 顺手修 ini 导入的兜底判断:firstMatchIndex 没有 MATCH 时返回 -1, 之前误写成 === null,条件恒假,配方的兜底规则从来没被导入过。
This commit is contained in:
@@ -20,7 +20,7 @@ import {
|
||||
proxyGroupName,
|
||||
subRuleName,
|
||||
} from '@shared/inbound-groups.ts'
|
||||
import { missingLanRules } from '@shared/rules.ts'
|
||||
import { firstMatchIndex, 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'
|
||||
@@ -84,6 +84,12 @@ const foreignRuleSet = computed(
|
||||
/** 内网直连还缺几条。全齐了按钮就不出现 */
|
||||
const lanMissing = computed(() => missingLanRules(props.subRules).length)
|
||||
|
||||
/**
|
||||
* 用户自己写的第一条 MATCH 的下标,没有时是 -1。它之后的行——包括表底的
|
||||
* 自动兜底——永远轮不到,标红;它自己标黄,hover 说明原因。
|
||||
*/
|
||||
const subMatchAt = computed(() => firstMatchIndex(props.subRules))
|
||||
|
||||
/** 默认入站由配置顶层的端口字段声明,没有 proxy 字段可写,也不能删。 */
|
||||
const locked = computed(() => props.inbound.isDefault)
|
||||
|
||||
@@ -717,8 +723,22 @@ const complete = computed(
|
||||
</h3>
|
||||
|
||||
<ul class="srules">
|
||||
<li v-for="(rule, i) in subRules" :key="i" class="srule">
|
||||
<span class="mono s-text" :title="rule">{{ rule }}</span>
|
||||
<li
|
||||
v-for="(rule, i) in subRules"
|
||||
:key="i"
|
||||
:class="['srule', subMatchAt >= 0 && i > subMatchAt && 'is-dead', i === subMatchAt && 'is-culprit']"
|
||||
>
|
||||
<span
|
||||
class="mono s-text"
|
||||
:title="
|
||||
i === subMatchAt
|
||||
? 'MATCH 命中所有流量,它之后的规则永远不会被检查'
|
||||
: subMatchAt >= 0 && i > subMatchAt
|
||||
? '排在 MATCH 之后,永远不会被检查'
|
||||
: rule
|
||||
"
|
||||
>{{ rule }}</span
|
||||
>
|
||||
<span class="s-acts">
|
||||
<button
|
||||
type="button"
|
||||
@@ -751,8 +771,12 @@ const complete = computed(
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
class="srule is-final"
|
||||
title="兜底:没被上面的规则分走的流量走这一路自己的出站。这一条由 Patchbay 自动维护"
|
||||
:class="['srule is-final', subMatchAt >= 0 && 'is-dead']"
|
||||
:title="
|
||||
subMatchAt >= 0
|
||||
? '上面的 MATCH 已拦下所有流量,这条自动兜底不会再被使用'
|
||||
: '兜底:没被上面的规则分走的流量走这一路自己的出站。这一条由 Patchbay 自动维护'
|
||||
"
|
||||
>
|
||||
<span class="mono s-text">MATCH,{{ proxyGroupName(inbound.name) }}</span>
|
||||
</li>
|
||||
@@ -949,6 +973,16 @@ const complete = computed(
|
||||
color: var(--c-text-faint);
|
||||
}
|
||||
|
||||
/* 不可达:在用户自己的 MATCH 之后(含自动兜底),永远轮不到 */
|
||||
.srule.is-dead .s-text {
|
||||
color: var(--c-danger);
|
||||
}
|
||||
|
||||
/* 拦路的那条 MATCH:它后面的行全是死行 */
|
||||
.srule.is-culprit .s-text {
|
||||
color: var(--c-warn);
|
||||
}
|
||||
|
||||
/* 操作按钮悬停到行上才现身,规则文本是主角 */
|
||||
.s-acts {
|
||||
flex: none;
|
||||
|
||||
+41
-15
@@ -52,10 +52,21 @@ const emit = defineEmits<{ saved: [] }>()
|
||||
const clone = () => JSON.parse(JSON.stringify(props.data)) as ConfigObject
|
||||
|
||||
const rules = computed(() => rulesOf(props.data))
|
||||
/** 每行拆好的样子,模板直接读,不在渲染里反复拆。 */
|
||||
const rows = computed(() =>
|
||||
rules.value.map((rule, index) => ({ index, raw: rule, ...parseRule(rule) })),
|
||||
)
|
||||
/**
|
||||
* 每行拆好的样子,模板直接读,不在渲染里反复拆。
|
||||
* culprit 是拦路的那条 MATCH(后面还有行时才算,作为表底兜底是正常用法);
|
||||
* dead 是被它拦住、永远轮不到的行。
|
||||
*/
|
||||
const rows = computed(() => {
|
||||
const at = firstMatchIndex(rules.value)
|
||||
return rules.value.map((rule, index) => ({
|
||||
index,
|
||||
raw: rule,
|
||||
...parseRule(rule),
|
||||
culprit: index === at && index < rules.value.length - 1,
|
||||
dead: at >= 0 && index > at,
|
||||
}))
|
||||
})
|
||||
const providers = computed(() =>
|
||||
Object.entries(ruleProvidersOf(props.data)).map(([name, decl]) => ({
|
||||
name,
|
||||
@@ -88,7 +99,7 @@ const targetOptions = computed(() => [
|
||||
...outbounds.value.ownProxies,
|
||||
])
|
||||
|
||||
/** 第一条 MATCH 的下标。它之后的行标成不可达。 */
|
||||
/** 第一条 MATCH 的下标,没有时是 -1。ini 导入拿它判断要不要收配方的兜底。 */
|
||||
const matchAt = computed(() => firstMatchIndex(rules.value))
|
||||
|
||||
/** 断引用按行归好,模板里直接查。 */
|
||||
@@ -131,7 +142,7 @@ function withRules(next: string[]): ConfigObject {
|
||||
return data
|
||||
}
|
||||
|
||||
// ── 分流规则 ────────────────────────────────────
|
||||
// ── 全局规则 ────────────────────────────────────
|
||||
|
||||
const ruleFormOpen = ref(false)
|
||||
/** 编辑时是下标,新增时是 null */
|
||||
@@ -177,7 +188,7 @@ async function move(index: number, delta: -1 | 1) {
|
||||
async function removeRule(index: number) {
|
||||
const rule = rules.value[index]
|
||||
if (rule === undefined || saving.value) return
|
||||
await commit(withRules(rules.value.filter((_, i) => i !== index)), `已删除规则 [ ${rule} ]`)
|
||||
await commit(withRules(rules.value.filter((_, i) => i !== index)), `已删除全局规则 [ ${rule} ]`)
|
||||
}
|
||||
|
||||
async function applyLanPreset() {
|
||||
@@ -265,8 +276,9 @@ async function submitImport(plan: IniImportPlan) {
|
||||
const next = clone()
|
||||
setRuleProviders(next, { ...ruleProvidersOf(next), ...plan.providers })
|
||||
// 表里已有 MATCH 时丢弃配方的兜底:那是你自己安排的分流终点,配方不该
|
||||
// 偷偷替换;追加在 MATCH 之后则永远是死行
|
||||
const keepFinals = matchAt.value === null
|
||||
// 偷偷替换;追加在 MATCH 之后则永远是死行。firstMatchIndex 没有时给 -1,
|
||||
// 之前误写成 === null,配方兜底从来没被导入过
|
||||
const keepFinals = matchAt.value < 0
|
||||
const finals = keepFinals ? plan.finals : []
|
||||
setRules(next, [...insertBeforeMatch(rules.value, plan.rules), ...finals])
|
||||
const added = plan.rules.length + finals.length
|
||||
@@ -350,7 +362,7 @@ async function updateNow(name: string) {
|
||||
<div class="sums">
|
||||
<div class="sum">
|
||||
<span class="sum-n">{{ rules.length }}</span>
|
||||
<span class="sum-k">分流规则</span>
|
||||
<span class="sum-k">全局规则</span>
|
||||
</div>
|
||||
<div class="sum">
|
||||
<span class="sum-n">{{ providers.length }}</span>
|
||||
@@ -361,7 +373,7 @@ async function updateNow(name: string) {
|
||||
|
||||
<section class="sec">
|
||||
<header class="sec-head">
|
||||
<h2>分流规则</h2>
|
||||
<h2>全局规则</h2>
|
||||
<div class="sec-acts">
|
||||
<button
|
||||
v-if="missingLan.length > 0 && rules.length > 0"
|
||||
@@ -382,7 +394,16 @@ async function updateNow(name: string) {
|
||||
|
||||
<ol v-if="rules.length > 0" class="rows">
|
||||
<li v-for="row in rows" :key="row.index">
|
||||
<div :class="['line', matchAt >= 0 && row.index > matchAt && 'is-dead']">
|
||||
<div
|
||||
:class="['line', row.dead && 'is-dead', row.culprit && 'is-culprit']"
|
||||
:title="
|
||||
row.culprit
|
||||
? 'MATCH 命中所有流量,它之后的规则永远不会被检查'
|
||||
: row.dead
|
||||
? '排在 MATCH 之后,永远不会被检查'
|
||||
: undefined
|
||||
"
|
||||
>
|
||||
<span class="idx mono">{{ row.index + 1 }}</span>
|
||||
<span class="rtype mono">{{ row.type }}</span>
|
||||
<span class="payload mono">
|
||||
@@ -400,7 +421,6 @@ async function updateNow(name: string) {
|
||||
{{ row.target }}
|
||||
</span>
|
||||
<span v-if="issuesByIndex.has(row.index)" class="tag tag-bad">断引用</span>
|
||||
<span v-if="matchAt >= 0 && row.index > matchAt" class="tag">不可达</span>
|
||||
</span>
|
||||
<span class="acts">
|
||||
<button
|
||||
@@ -443,7 +463,7 @@ async function updateNow(name: string) {
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
<p v-else class="empty">没有分流规则,走规则的流量都会直连。</p>
|
||||
<p v-else class="empty">没有全局规则,走规则的流量都会直连。</p>
|
||||
</section>
|
||||
|
||||
<section class="sec">
|
||||
@@ -688,7 +708,13 @@ async function updateNow(name: string) {
|
||||
.is-dead .rtype,
|
||||
.is-dead .payload,
|
||||
.is-dead .rtarget {
|
||||
color: var(--c-text-faint);
|
||||
color: var(--c-danger);
|
||||
}
|
||||
|
||||
/* 拦路的那条 MATCH:它后面还有行,后面全是死行 */
|
||||
.is-culprit .rtype,
|
||||
.is-culprit .rtarget {
|
||||
color: var(--c-warn);
|
||||
}
|
||||
|
||||
.bad {
|
||||
|
||||
Reference in New Issue
Block a user