把入站固定到某条订阅线路
订阅线路不在内核的全局名字表里,入站的 proxy 指不到它(决策记录 0005)。做法是自动建 一个只装它的策略组再指过去,用户看到的只是「这个口固定走这条线」,组是实现细节: 不出现在策略组列表里,列表和详情里都显示成线路本身的名字,没人再引用时自动删掉。 筛选条件必须做对两件事,都写了测试:转义正则元字符(线路名形如 [vmess]DMIT.LAX.Pro 里的方括号和点不转义会匹配错),以及前后加锚点(内核是部分匹配,不加锚点时「香港01」 会把「香港011」也捞进来)。线路名含反引号时表达不了——那是内核切分多个筛选条件的 分隔符——这种情况明说做不到而不是生成一个会静默匹配错的组。 实测:生成的配置 mihomo -t 通过;起一个临时内核问 /proxies,这个组解析出来的成员 恰好只有那一条线路,且确实进了名字表所以入站指得到。
This commit is contained in:
@@ -18,6 +18,12 @@ import type { Inbound, OutboundMode } from '@shared/inbounds.ts'
|
||||
import type { ConfigObject } from '../../vendor/config-editor/index.ts'
|
||||
import { ApiError, api } from '../api'
|
||||
import type { ListenerFields } from '../api'
|
||||
import {
|
||||
generatedGroupName,
|
||||
isGeneratedGroup,
|
||||
makeFilterGroup,
|
||||
pruneGeneratedGroups,
|
||||
} from '@shared/filter-groups.ts'
|
||||
import { lineProvider, optimisticSelect, providerLines, reloadRuntime } from '../runtime'
|
||||
import { useDelayTest } from '../delay-test'
|
||||
import { density } from '../prefs'
|
||||
@@ -32,7 +38,12 @@ const props = defineProps<{ data: ConfigObject; revision: string }>()
|
||||
const emit = defineEmits<{ saved: [] }>()
|
||||
|
||||
const inbounds = computed(() => collectInbounds(props.data))
|
||||
const outbounds = computed(() => collectOutboundNames(props.data))
|
||||
const outbounds = computed(() => {
|
||||
const names = collectOutboundNames(props.data)
|
||||
// 生成的筛选组不出现在可选列表里:它们是「固定到某条线路」的实现细节,
|
||||
// 用户从没建过,让他在策略组那一栏看到只会莫名其妙。
|
||||
return { ...names, groups: names.groups.filter((g) => !isGeneratedGroup(g)) }
|
||||
})
|
||||
|
||||
const selectedId = ref<string | null>(null)
|
||||
const saving = ref(false)
|
||||
@@ -77,7 +88,10 @@ const takenNames = computed(() => inbounds.value.map((i) => i.name))
|
||||
const clone = () => JSON.parse(JSON.stringify(props.data)) as ConfigObject
|
||||
|
||||
/** 所有改配置文件的动作都从这里走。 */
|
||||
async function commit(next: ConfigObject, success: string) {
|
||||
async function commit(raw: ConfigObject, success: string) {
|
||||
// 顺手清掉没人再引用的生成组。不清理的话,改几次出站文件里就攒下一堆
|
||||
// 用户从没主动建过、也不知道是干嘛的组。
|
||||
const next = pruneGeneratedGroups(raw)
|
||||
saving.value = true
|
||||
failure.value = null
|
||||
done.value = ''
|
||||
@@ -115,6 +129,45 @@ async function pickOutbound(mode: OutboundMode) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 把当前这一路入站固定到某条订阅线路。
|
||||
*
|
||||
* 订阅线路不能被入站直接引用,所以要先建一个只装它的策略组再指过去(决策记录 0005)。
|
||||
* 这一步对用户是透明的:他看到的只是「这个口固定走这条线」。
|
||||
*/
|
||||
async function pinLine(line: string) {
|
||||
const target = selected.value
|
||||
if (!target || target.listenerIndex === null || pendingOutbound.value) return
|
||||
|
||||
const provider = lineProvider.value[line]
|
||||
if (!provider) {
|
||||
notice.value = `找不到 ${line} 属于哪个订阅,无法固定`
|
||||
return
|
||||
}
|
||||
|
||||
const group = makeFilterGroup(line, provider)
|
||||
if (!group) {
|
||||
notice.value = `${line} 的名字里含反引号,内核会把它当成筛选条件的分隔符,无法固定`
|
||||
return
|
||||
}
|
||||
|
||||
const groupName = generatedGroupName(line)
|
||||
pendingOutbound.value = groupName
|
||||
|
||||
const next = clone()
|
||||
const groups = Array.isArray(next['proxy-groups']) ? (next['proxy-groups'] as ConfigObject[]) : []
|
||||
// 同一条线路被两路入站固定时共用一个组,不重复建
|
||||
if (!groups.some((g) => g.name === groupName)) groups.push(group)
|
||||
next['proxy-groups'] = groups
|
||||
;(next.listeners as ConfigObject[])[target.listenerIndex]!.proxy = groupName
|
||||
|
||||
try {
|
||||
await commit(next, `${target.name} 已固定走 ${line}`)
|
||||
} finally {
|
||||
pendingOutbound.value = null
|
||||
}
|
||||
}
|
||||
|
||||
function openAdd() {
|
||||
formEditing.value = null
|
||||
formOpen.value = true
|
||||
@@ -223,6 +276,7 @@ async function removeSelected() {
|
||||
@remove="removeSelected"
|
||||
@select="selectMember"
|
||||
@test="(name: string) => test(name, lineProvider[name])"
|
||||
@pin="pinLine"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user