Files
mihomo-patchbay/shared/connections.ts
T
im0o b1b251b897
release / release (push) Successful in 26s
legacy 入站可升级为命名入站
顶层端口字段没有 proxy 可写(listener/inbound/base.go:97),界面上
切不了订阅。升级删掉顶层键、建同端口的 listener:listen 按 allow-lan
推导后写死,全局规则里 MATCH 之前的部分复制成附加规则,来源照抄
MATCH 目标组的 use:,让流量去向尽量不变。udp 不写,命名入站解析时
默认就是开的(listener/parse.go:52)。
「默认」tag 随术语表改成 legacy,旧叫法进 avoid 列表。
2026-08-12 17:30:47 +08:00

137 lines
4.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 连接页的领域逻辑:内核当前连接的显示、筛选、排序和按入站聚合。
*
* 类型按 v1.19.29 管理接口 GET /connections 的实际返回写(开发内核实测),
* 只保留界面用到的字段。`connections` 在没有连接时是 null 而不是空数组。
*/
/** 一条连接里界面关心的元数据。内核返回的字段远不止这些,用不到的不收。 */
export interface ConnectionMeta {
host: string
destinationIP: string
destinationPort: string
/** 从哪一路入站进来:命名入站是它的名字,legacy 入站是内核造的 DEFAULT-* */
inboundName: string
inboundPort: string
}
export interface ConnectionItem {
id: string
upload: number
download: number
/** ISO 时间,带时区偏移(内核按本机时区输出) */
start: string
/** 经过的出站链。第一个是最终出口,最后一个是最先接触的——和直觉相反 */
chains: string[]
/** 命中的分流规则类型,如 DomainSuffix;兜底是 Match */
rule: string
rulePayload: string
metadata: ConnectionMeta
}
export interface ConnectionSnapshot {
downloadTotal: number
uploadTotal: number
connections: ConnectionItem[] | null
}
/** 这条连接连往哪。有域名显示域名,没有退回 IP,都带端口。 */
export function hostLabel(meta: ConnectionMeta): string {
const target = meta.host || meta.destinationIP
return meta.destinationPort ? `${target}:${meta.destinationPort}` : target
}
/**
* 出站链按流量经过的顺序显示:最先接触的在左,最终出口在右。
* 内核给的数组恰好相反,所以要倒过来。
*/
export function chainLabel(chains: string[]): string {
return [...chains].reverse().join(' → ')
}
/**
* 规则显示成「类型 载荷」,兜底规则没有载荷。
* 类型和载荷都是配置文件 rules: 里的原词,不翻译。
*/
export function ruleLabel(rule: string, payload: string): string {
return payload ? `${rule} ${payload}` : rule
}
/** 持续时间。start 解析不出来(坏数据)时返回空串,由界面决定摆什么。 */
export function formatDuration(startIso: string, now: number): string {
const started = Date.parse(startIso)
if (Number.isNaN(started)) return ''
const seconds = Math.max(0, Math.floor((now - started) / 1000))
if (seconds < 60) return `${String(seconds)} 秒`
const minutes = Math.floor(seconds / 60)
if (minutes < 60) return `${String(minutes)} 分 ${String(seconds % 60)} 秒`
return `${String(Math.floor(minutes / 60))} 小时 ${String(minutes % 60)} 分`
}
export type SortKey = 'download' | 'duration'
/**
* 排序。都是降序:下载多的、活得久的排前面。
* 持续时间没有现成字段,从 start 现算;排坏的 start 沉底。
*/
export function sortConnections(
list: ConnectionItem[],
key: SortKey,
now: number,
): ConnectionItem[] {
const age = (c: ConnectionItem) => {
const started = Date.parse(c.start)
return Number.isNaN(started) ? -1 : now - started
}
// 平局按 id 决胜。输入顺序来自内核快照的 map 遍历,本身无序且每秒在变;
// 空闲连接的下载量普遍相同,没有决胜键的话行会在指针(和断开按钮)下面跳
return [...list].sort((a, b) => {
const main = key === 'download' ? b.download - a.download : age(b) - age(a)
return main !== 0 ? main : a.id.localeCompare(b.id)
})
}
/** 按主机名筛选。大小写不敏感,空串不筛。 */
export function filterConnections(list: ConnectionItem[], query: string): ConnectionItem[] {
const q = query.trim().toLowerCase()
if (!q) return list
return list.filter((c) => hostLabel(c.metadata).toLowerCase().includes(q))
}
/** 一路入站当前有多少条连接。 */
export interface InboundUse {
/** 命名入站显示名字;legacy 入站内核只给 DEFAULT-* 这类内部名,附上端口好认 */
label: string
count: number
upload: number
download: number
}
/**
* 按入站聚合。这是连接页对本项目的独特价值:每一路入站(每个容器)现在有多少条
* 连接、走了多少流量。
*/
export function groupByInbound(list: ConnectionItem[]): InboundUse[] {
const byName = new Map<string, InboundUse>()
for (const c of list) {
const name = c.metadata.inboundName || c.metadata.inboundPort
let entry = byName.get(name)
if (!entry) {
entry = {
label: c.metadata.inboundName
? `${c.metadata.inboundName}(${c.metadata.inboundPort})`
: c.metadata.inboundPort,
count: 0,
upload: 0,
download: 0,
}
byName.set(name, entry)
}
entry.count += 1
entry.upload += c.upload
entry.download += c.download
}
// 连接多的排前面,和表格的默认排序一个取向
return [...byName.values()].sort((a, b) => b.count - a.count)
}