补上真正日常要点的那个动作:切换策略组当前选中谁
之前的面板只做了「这个入站指向哪个出站」,那是改配置文件。但 metacubexd 里点一条线路
改的根本不是入站,是策略组的当前选中——调 PUT /proxies/{组},不写文件、立刻生效。
mixed-port 走规则表,末尾 MATCH,PROXY 把流量交给 PROXY 组,所以换那个组的选中就等于
换了这个端口的出口。这是每天都要点的动作,之前整个漏了。
面板顶部现在显示完整链条(PROXY → AUTO → 某条线路),并让人在最后一个策略组上换选中。
只显示第一层等于没回答「这个端口现在走哪条线」。
透传的白名单里为此开了唯一一个写操作,单独一条路由而不是放开整个转发。
This commit is contained in:
+40
-1
@@ -2,7 +2,13 @@ import { describe, expect, it } from 'bun:test'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { openDocument } from '../vendor/config-editor/index.ts'
|
||||
import { collectInbounds, collectOutboundNames, outboundToProxyField } from './inbounds.ts'
|
||||
import {
|
||||
collectInbounds,
|
||||
collectOutboundNames,
|
||||
fallbackTarget,
|
||||
outboundToProxyField,
|
||||
resolveDestination,
|
||||
} from './inbounds.ts'
|
||||
|
||||
const fixture = openDocument(
|
||||
readFileSync(join(import.meta.dir, '__fixtures__', 'config-with-comments.yaml'), 'utf8'),
|
||||
@@ -106,3 +112,36 @@ describe('能被入站引用的出站名字', () => {
|
||||
expect([...names.groups, ...names.ownProxies, ...names.builtins]).not.toContain('subs')
|
||||
})
|
||||
})
|
||||
|
||||
describe('流量最终去哪', () => {
|
||||
const cfg = {
|
||||
'mixed-port': 7777,
|
||||
rules: ['IP-CIDR,127.0.0.0/8,DIRECT,no-resolve', 'MATCH,PROXY'],
|
||||
listeners: [
|
||||
{ name: '固定', type: 'mixed', port: 7778, proxy: 'AUTO' },
|
||||
{ name: '直连', type: 'mixed', port: 7779, proxy: 'DIRECT' },
|
||||
],
|
||||
}
|
||||
|
||||
it('走规则的入站要继续查到规则表末尾的 MATCH', () => {
|
||||
// 用户想知道的是流量去哪,不是 proxy 字段写没写
|
||||
const legacy = collectInbounds(cfg)[0]!
|
||||
expect(legacy.outbound).toEqual({ kind: 'rules' })
|
||||
expect(resolveDestination(cfg, legacy)).toBe('PROXY')
|
||||
})
|
||||
|
||||
it('指定了出站的入站就是那个出站', () => {
|
||||
const list = collectInbounds(cfg)
|
||||
expect(resolveDestination(cfg, list[1]!)).toBe('AUTO')
|
||||
expect(resolveDestination(cfg, list[2]!)).toBe('DIRECT')
|
||||
})
|
||||
|
||||
it('MATCH 之后的规则到不了,取第一条', () => {
|
||||
expect(fallbackTarget({ rules: ['MATCH,A', 'MATCH,B'] })).toBe('A')
|
||||
})
|
||||
|
||||
it('没有 MATCH 时没有兜底', () => {
|
||||
expect(fallbackTarget({ rules: ['MATCH-NOT-REALLY,X'] })).toBeNull()
|
||||
expect(fallbackTarget({})).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -138,6 +138,37 @@ export function collectInbounds(data: ConfigObject): Inbound[] {
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 走规则表的流量最后交给谁。
|
||||
*
|
||||
* `MATCH` 匹配一切,所以第一条 MATCH 之后的规则都到不了,它就是兜底出站。
|
||||
* 没有 MATCH 时内核会直连,返回 null。
|
||||
*/
|
||||
export function fallbackTarget(data: ConfigObject): string | null {
|
||||
const rules = Array.isArray(data.rules) ? data.rules : []
|
||||
for (const rule of rules) {
|
||||
if (typeof rule !== 'string') continue
|
||||
const parts = rule.split(',').map((p) => p.trim())
|
||||
if (parts[0]?.toUpperCase() === 'MATCH' && parts[1]) return parts[1]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 这一路入站的流量最终交给哪个出站。
|
||||
*
|
||||
* 和 `inbound.outbound` 的区别在于「跟着规则走」会被继续解析下去:那种情况下真正决定
|
||||
* 去向的是规则表末尾的 MATCH。界面上要显示的是这个结果——用户想知道的是流量去哪,
|
||||
* 不是配置里那个字段写没写。
|
||||
*/
|
||||
export function resolveDestination(data: ConfigObject, inbound: Inbound): string | null {
|
||||
const mode = inbound.outbound
|
||||
if (mode.kind === 'direct') return 'DIRECT'
|
||||
if (mode.kind === 'global') return 'GLOBAL'
|
||||
if (mode.kind === 'named') return mode.name
|
||||
return fallbackTarget(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 所有可以被入站的出站字段引用的名字。
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user