补上真正日常要点的那个动作:切换策略组当前选中谁
之前的面板只做了「这个入站指向哪个出站」,那是改配置文件。但 metacubexd 里点一条线路
改的根本不是入站,是策略组的当前选中——调 PUT /proxies/{组},不写文件、立刻生效。
mixed-port 走规则表,末尾 MATCH,PROXY 把流量交给 PROXY 组,所以换那个组的选中就等于
换了这个端口的出口。这是每天都要点的动作,之前整个漏了。
面板顶部现在显示完整链条(PROXY → AUTO → 某条线路),并让人在最后一个策略组上换选中。
只显示第一层等于没回答「这个端口现在走哪条线」。
透传的白名单里为此开了唯一一个写操作,单独一条路由而不是放开整个转发。
This commit is contained in:
@@ -13,13 +13,49 @@ const props = defineProps<{
|
||||
groups: string[]
|
||||
ownProxies: string[]
|
||||
providerLines: string[]
|
||||
/** 流量最终交给哪个出站。走规则的入站要查到规则表末尾的 MATCH */
|
||||
destination: string | null
|
||||
/** 运行时状态,键是出站名字。内核没连接时是空的 */
|
||||
runtime: Record<string, ProxyInfo>
|
||||
kernelOnline: boolean
|
||||
saving: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{ pick: [mode: OutboundMode]; edit: []; remove: []; convert: [] }>()
|
||||
const emit = defineEmits<{
|
||||
pick: [mode: OutboundMode]
|
||||
edit: []
|
||||
remove: []
|
||||
convert: []
|
||||
select: [group: string, member: string]
|
||||
}>()
|
||||
|
||||
/**
|
||||
* 从落脚的出站一路跟着「当前选中」往下走,直到走到一条真正的线路。
|
||||
*
|
||||
* 策略组可以选中另一个策略组,所以「这个端口现在走哪条线」往往要跳好几层:
|
||||
* PROXY 选了 AUTO,AUTO 选了某条线路。只显示第一层等于没回答问题。
|
||||
*/
|
||||
function resolveChain(start: string | null): string[] {
|
||||
const chain: string[] = []
|
||||
let cursor = start
|
||||
while (cursor && !chain.includes(cursor)) {
|
||||
chain.push(cursor)
|
||||
cursor = props.runtime[cursor]?.now ?? null
|
||||
}
|
||||
return chain
|
||||
}
|
||||
|
||||
const chain = computed(() => resolveChain(props.destination))
|
||||
|
||||
/** 链条上最后一个还是策略组的环节——用户能在这里换选中。 */
|
||||
const switchable = computed(() => {
|
||||
for (let i = chain.value.length - 1; i >= 0; i -= 1) {
|
||||
const name = chain.value[i]!
|
||||
const info = props.runtime[name]
|
||||
if (info?.all?.length) return { group: name, members: info.all, now: info.now ?? '' }
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
interface Option {
|
||||
key: string
|
||||
@@ -130,6 +166,35 @@ function delayClass(ms: number | null): string {
|
||||
</dl>
|
||||
</header>
|
||||
|
||||
<section v-if="chain.length" class="pick">
|
||||
<h3>流量去向</h3>
|
||||
<p class="chain mono">
|
||||
<template v-for="(hop, i) in chain" :key="hop">
|
||||
<span v-if="i" class="arrow">→</span><span :class="i === chain.length - 1 && 'tail'">{{ hop }}</span>
|
||||
</template>
|
||||
</p>
|
||||
|
||||
<template v-if="switchable">
|
||||
<h4>{{ switchable.group }} 当前选中</h4>
|
||||
<div class="grid">
|
||||
<button
|
||||
v-for="m in switchable.members"
|
||||
:key="m"
|
||||
type="button"
|
||||
:class="['card', m === switchable.now && 'is-on']"
|
||||
:disabled="saving"
|
||||
@click="emit('select', switchable.group, m)"
|
||||
>
|
||||
<span class="card-name">{{ m }}</span>
|
||||
<span class="card-sub">{{ runtime[m]?.now ? `${runtime[m]!.type}(${runtime[m]!.now})` : (runtime[m]?.type ?? '—') }}</span>
|
||||
<span :class="['card-delay', delayClass(latestDelay(runtime[m]))]">
|
||||
{{ latestDelay(runtime[m]) === null ? '' : `${latestDelay(runtime[m])} ms` }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</section>
|
||||
|
||||
<section class="pick">
|
||||
<h3>出站</h3>
|
||||
|
||||
@@ -351,6 +416,30 @@ function delayClass(ms: number | null): string {
|
||||
padding-top: var(--s-4);
|
||||
}
|
||||
|
||||
.pick + .pick {
|
||||
margin-top: var(--s-5);
|
||||
border-top: 1px solid var(--c-border);
|
||||
}
|
||||
|
||||
.chain {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: var(--s-2);
|
||||
font-size: var(--t-sm);
|
||||
color: var(--c-text-soft);
|
||||
margin-bottom: var(--s-2);
|
||||
}
|
||||
|
||||
.chain .arrow {
|
||||
color: var(--c-text-faint);
|
||||
}
|
||||
|
||||
.chain .tail {
|
||||
color: var(--c-accent-text);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: var(--t-sm);
|
||||
font-weight: 600;
|
||||
|
||||
Reference in New Issue
Block a user