每路入站配一组专属策略组,换线不再改配置文件
原本换一条线要改 config.yaml、跑校验、等内核重载,几秒起步;而换线恰恰是这个工具 最高频的动作,把它放在最重的路径上方向就错了。现在每一路配一个 select 组加 AUTO、 FALLBACK 两个,入站的 proxy 永久指向 select,换线只是调一次内核接口。这样做的前提是 选中不会丢:内核把它写进 cache 文件,重启后由 patchSelectGroup 恢复,而 profile.store-selected 默认为真。 链式代理靠给来源订阅复制一份声明、挂上 override.dialer-proxy 实现,因为这个字段写在 策略组上内核只会打条日志然后忽略。副本是 type: file 读原订阅已经落盘的缓存,而不是 自己再拉一次:自己拉的话订阅地址一不可达副本就是空的,那一路入站会一条线都不剩, 表现是端口还在监听却没有任何出口。 顺带把 shared/outbound-chain.ts 里「只有 Selector 接受手动选择」那条断言删了,它是 错的(内核检查的是 SelectAble 接口,URLTest 和 Fallback 也实现了)。界面仍然只让 Selector 可点,但理由换成了另外两种的手动指定会被它们自己撤销。 完整取舍见工作区决策记录 0007。
This commit is contained in:
+87
-6
@@ -10,11 +10,21 @@
|
||||
import { ref } from 'vue'
|
||||
import { api } from './api'
|
||||
import type { ProxyInfo } from './api'
|
||||
import { isGenerated } from '@shared/inbound-groups.ts'
|
||||
|
||||
/** 出站名字 → 运行时信息。内核没连接时是空的。 */
|
||||
/**
|
||||
* 出站名字 → 运行时信息。内核没连接时是空的。
|
||||
*
|
||||
* 合并了内核全局名字表和用户订阅里的线路。Patchbay 自己生成的订阅副本**不并进来**:
|
||||
* 副本里的线路和原版同名(决策记录 0007 里那套复制订阅声明的做法),并进来只会互相
|
||||
* 覆盖。要看副本里那一份,用下面的 `providers`。
|
||||
*/
|
||||
export const runtime = ref<Record<string, ProxyInfo>>({})
|
||||
|
||||
/** 订阅带来的线路,按订阅返回的顺序。 */
|
||||
/** 订阅名 → 它当前提供的线路。含 Patchbay 生成的副本,按订阅隔开所以不会撞。 */
|
||||
export const providers = ref<Record<string, ProxyInfo[]>>({})
|
||||
|
||||
/** 用户自己的订阅带来的线路,按订阅返回的顺序。不含生成的副本。 */
|
||||
export const providerLines = ref<string[]>([])
|
||||
|
||||
/** 订阅线路 → 它所属的订阅名。测它的延迟必须从所属订阅进去。 */
|
||||
@@ -26,8 +36,7 @@ export const kernel = ref<{ online: boolean; version?: string }>({ online: false
|
||||
* 重新读一遍运行时状态。
|
||||
*
|
||||
* 要读两处:`/proxies` 只包含内核全局名字表里的东西(内置出站、自有线路、策略组),
|
||||
* 订阅线路不在那张表上,它们的协议和延迟只能从 `/providers/proxies` 拿。合成一张表
|
||||
* 之后,界面就不用关心某个名字是从哪来的了。
|
||||
* 订阅线路不在那张表上,它们的协议和延迟只能从 `/providers/proxies` 拿。
|
||||
*
|
||||
* 任何一处取不到都不算错误——界面照常显示,只是少了延迟数字。内核没起来的时候正是
|
||||
* 最需要这个界面的时候。
|
||||
@@ -36,6 +45,7 @@ export async function reloadRuntime(): Promise<void> {
|
||||
kernel.value = await api.kernel().catch(() => ({ online: false }))
|
||||
if (!kernel.value.online) {
|
||||
runtime.value = {}
|
||||
providers.value = {}
|
||||
providerLines.value = []
|
||||
lineProvider.value = {}
|
||||
return
|
||||
@@ -48,13 +58,16 @@ export async function reloadRuntime(): Promise<void> {
|
||||
// 拿不到就当没有
|
||||
}
|
||||
|
||||
const byProvider: Record<string, ProxyInfo[]> = {}
|
||||
const lines: string[] = []
|
||||
const owner: Record<string, string> = {}
|
||||
try {
|
||||
const { providers } = await api.providers()
|
||||
for (const [name, provider] of Object.entries(providers)) {
|
||||
const result = await api.providers()
|
||||
for (const [name, provider] of Object.entries(result.providers)) {
|
||||
// 内核会给每个用了 proxies 的策略组造一个内部 provider,那些不是订阅
|
||||
if (provider.vehicleType === 'Compatible') continue
|
||||
byProvider[name] = provider.proxies
|
||||
if (isGenerated(name)) continue
|
||||
for (const line of provider.proxies) {
|
||||
merged[line.name] = line
|
||||
lines.push(line.name)
|
||||
@@ -66,10 +79,78 @@ export async function reloadRuntime(): Promise<void> {
|
||||
}
|
||||
|
||||
runtime.value = merged
|
||||
providers.value = byProvider
|
||||
providerLines.value = lines
|
||||
lineProvider.value = owner
|
||||
}
|
||||
|
||||
/**
|
||||
* 界面渲染一个策略组需要知道的最少信息。
|
||||
*
|
||||
* `uses` 来自配置文件,`now` 来自内核,两边缺一不可:光有名字查不到它从哪取成员,
|
||||
* 而开了链式代理之后同名线路有两份,不知道来源就分不清该显示哪一份。
|
||||
*/
|
||||
export interface GroupView {
|
||||
name: string
|
||||
uses: string[]
|
||||
}
|
||||
|
||||
/** 一条线路在界面上要显示的样子,外加测它的延迟该从哪个订阅进去。 */
|
||||
export interface LineEntry {
|
||||
name: string
|
||||
/** 它来自哪个订阅。测延迟必须带上 */
|
||||
provider: string
|
||||
info: ProxyInfo | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* 这条线路是不是来自 Patchbay 生成的订阅副本。
|
||||
*
|
||||
* 副本整份挂着 `dialer-proxy`,所以来自副本就一定经过入口。点名加进组里的成员
|
||||
* (`DIRECT`、AUTO 和 FALLBACK 那两个组)不属于任何订阅,不在副本里。
|
||||
*/
|
||||
export function isChainedLine(name: string): boolean {
|
||||
return Object.entries(providers.value).some(
|
||||
([provider, lines]) => isGenerated(provider) && lines.some((line) => line.name === name),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 这几个订阅里一共有哪些线路。
|
||||
*
|
||||
* 只给高级配置里那个「匹配到几条」的预览用:那时用户选的来源还没保存,内核也还不知道
|
||||
* 有这么个组,只能自己按来源算一遍。已经存在的组要用下面的 `membersOf`。
|
||||
*/
|
||||
export function linesFrom(uses: string[]): string[] {
|
||||
const seen = new Set<string>()
|
||||
for (const provider of uses) {
|
||||
for (const line of providers.value[provider] ?? []) seen.add(line.name)
|
||||
}
|
||||
return [...seen]
|
||||
}
|
||||
|
||||
/**
|
||||
* 一个策略组的成员,按内核给的顺序。
|
||||
*
|
||||
* 成员名单直接用内核算好的(`/proxies` 里那个 `all`),不自己从来源订阅里推——组上的
|
||||
* `filter` 已经在内核那边生效过了,重推一遍就是把同一条规则实现两次,两边一旦不一致
|
||||
* 界面显示的就是假的。
|
||||
*
|
||||
* 每个成员还要标出它来自哪个订阅,因为测延迟得从所属订阅进去。顺着组自己的 `use:` 找而
|
||||
* 不是全局按名字查:开了链式代理之后同一个线路名在原订阅和副本里各有一份,只有顺着来源
|
||||
* 才知道该用哪一份。副本那份挂着 `dialer-proxy`,测出来是走完两跳的延迟,这正是这一路
|
||||
* 真实的延迟。点名加进来的成员(DIRECT、别的策略组)不属于任何订阅,`provider` 是空串。
|
||||
*/
|
||||
export function membersOf(view: GroupView): LineEntry[] {
|
||||
return (runtime.value[view.name]?.all ?? []).map((name) => {
|
||||
for (const provider of view.uses) {
|
||||
const line = (providers.value[provider] ?? []).find((item) => item.name === name)
|
||||
if (line) return { name, provider, info: line }
|
||||
}
|
||||
return { name, provider: '', info: runtime.value[name] }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换策略组选中谁时先在本地改掉,不等内核回话。
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user