Commit Graph
25 Commits
Author SHA1 Message Date
im0o b28fc572f7 主机内存总量改从 /proc/meminfo 读
os.totalmem() 底层是 sysinfo() 系统调用,LXC 里 lxcfs 只虚拟化 /proc
文件、拦不住系统调用,导致容器里显示的是宿主机的物理内存总量。
从 /proc/meminfo 读 MemTotal 才是容器限额,且与 MemAvailable 同源。
2026-08-12 16:38:19 +08:00
im0o a40bd6a01b 历史记录:每条可展开 diff,连续保存合并进 5 秒窗
备份存的是「改动前」,所以一条记录的 diff 两端是它自己和更新一份的
状态(最新一条对比当前文件)。行级 diff 先掐公共前后缀再做 LCS,一次
改动通常只碰几行,表撑得住;大面积不同时退化成整段删增。

合并窗做在服务端而不是前端防抖:普通保存(opts.merge)发现最新备份
还在窗内就不新建,连点删除共享一条历史、一步撤回。窗锚定在这一串的
第一下,不滚雪球;还原、导入、撤回重做不合并,各占一条。合并时没有
备份文件,重载失败的还原改用刚读出的原文写回。顺带消除了备份名只精
确到秒、同秒保存互相覆盖的隐患。
2026-08-12 13:17:11 +08:00
im0o 275f536429 追加 Hosts、路径撞名拦截、连接页表头随行滚动、GEO 借内核通路
设置页内核参数加 hosts 行,铅笔进弹窗编辑(顶层 hosts: 键,
config.go:454,值单个或数组 parseHosts config.go:1131)。弹窗里随便改、
保存才一次性提交——写配置有几秒的校验重载,逐行失焦提交会把等待塞进
每次输入。没动过的行原样写回,数组不被重整形。

订阅和规则组新增时查缓存路径撞名:路径由名字净化而来,「订阅 A」和
「订阅/A」是两个名字、同一个文件,名字查重挡不住,会互相覆盖缓存。
纯函数在 shared 带测试,页面提交前拦。

连接页表头挪进滚动容器加 sticky:.scroller 声明 overflow-y 后按规范
连带拿到横向滚动上下文,表头留在外面时窄屏横向滚动两者脱节。表头
40px 的窗口偏移由 overscan 8 行吸收,不配 scrollMargin。

GEO 单文件下载先借内核的混合端口出去再退直连——内核自己更新 GEO 走
的是它的分流,Patchbay 直连 GitHub 不一定通。ECharts 拆独立 chunk,
业务代码改动不再让这 500KB 跟着换 hash。
2026-08-12 02:47:17 +08:00
im0o b276c4a8fe 轻提示铺开到全部页面;内核状态悬停变重启;GEO 支持单文件下载
侧栏动作排撤掉重启图标,内核状态(圆点加版本号)本身变成按钮:悬停
换层显示「重启内核」,不在线时只当状态看。行高压成 1 修掉圆点和等宽
字体不居中的问题。

四个页面的一次性反馈(保存、删除、更新订阅、断开连接、测延迟结论)
全部改走 toast,commit() 改返回成败给「失败不关对话框」用;持续状态
(连接页轮询失败、请求页日志流失败)留在页内,会自动消失的提示装不下
需要一直可见的信息。toast 加 info 类别给中性结论用。

GEO 每行的更新改成单独下载那一个文件:内核只有全量更新的口子,单文件
只能服务端自己下(POST /api/geo/sync),代价是只有形状校验(MaxMind
末尾标记、geodata 首字节,shared/geo.ts 带测试)且内核重启后才换用新
文件;「更新全部」文字按钮保留内核的全量口子,有完整校验和热加载。
上传两行的按钮改「选择或拖放文件」,投放区拖入高亮,恢复策略挪到
恢复行下方。
2026-08-12 02:20:46 +08:00
im0o f761d13579 反馈改右上角轻提示;侧栏加撤回重做等配置动作
设置页的横幅换成 toast(右滑入、几秒后上移淡出,web/toast.ts 单例):
动作结果看一眼就行,不值得在页面里占一块常驻位置。表单校验类的错误
仍留在各自表单里。

撤回栈就是备份链:撤回把最新备份应用回去并消费掉它,撤回前的状态进
重做栈(.patchbay-redo,同样留 10 份);重做反向;新的正常保存清空
重做栈,不留分叉历史。这两条路复用 applyConfig 的保护链路,用
restoreFrom 参数跳过自建备份——否则备份链会被撤回本身污染,撤两次
等于原地打转。侧栏左下角一排图标:撤回、重做、历史记录(备份列表
弹窗)、更新配置(POST /api/kernel/reload,给 SSH 手改后用)、重启
内核;收拢时容器变窄自动折成一列。

设置页顺手修三处:GEO 行的编辑按钮在输入框开着时再点会闪(blur 关了
click 又开),mousedown.prevent 保焦点、click 显式开关;上传按钮改
「选择或拖放文件」,投放区拖入高亮;恢复策略挪到恢复行下面。
2026-08-12 01:59:22 +08:00
im0o 2580c03bfa 设置页第二轮:行内编辑、GEO 资源展开、恢复与导入分家
单值设置从常驻输入框改成 InlineEdit(纯文本加铅笔,Enter 或失焦提交,
失焦无效直接放弃——失焦是「我走了」,不该把人拽回来)。测速地址加恢复
默认按钮;内核参数逐项提交,不再攒一个保存按钮。

资源一节按文件展开:四类 GEO 文件的大小和更新时间由服务端 stat 内核
工作目录得来(GET /api/geo;文件名和大小写不敏感匹配照 constant/
path.go:18-20,193-224),下载地址可改(geox-url,config.go:363-368,
默认值 578-583)。行内的更新按钮仍是全量更新——内核只有这一个口子,
没变化的文件靠内容哈希跳过。

上传拆成两个入口:恢复只认带 Patchbay 标记的文件,导入收任意配置;
新增恢复策略(兼容只搬接管段落 listeners/proxy-groups/rules/
proxy-providers/rule-providers,覆盖整份替换)。平时是按钮,拖着文件
进窗口时整行变成投放区。
2026-08-12 01:40:49 +08:00
im0o 8eb98e2e9a 设置页重做:配置文件下载上传、内核参数、资源、免责声明
配置每次落盘都打顶层 patchbay: 标记(盐加版本的哈希)。内核忽略未知
顶层键(yaml.Unmarshal 没开严格模式,config/config.go:612)。刻意不对
内容做哈希:本项目只接管部分段落,手改其余段落是正常用法,内容哈希
会把合法手改判成无效。上传配置时靠标记分流:有标记的确认后替换,
没有的用更重的措辞确认导入;两者都走 config-store 完整保护链路
(新端点 POST /api/config/import)。自动备份列表从界面撤下,改成
下载当前配置 + 拖放上传。

内核参数一组编辑五个老式顶层端口和 keep-alive-interval(config.go:444,
整数秒),走带基准指纹的保存链路。资源一组接 POST /configs/geo
(同步接口,透传给了 300 秒超时)。测速地址成为本地偏好,延迟测试
读它;prefs 模块顶层的浏览器全局加了存在性守卫——api.ts 开始引用
prefs,而 bun test 环境没有 localStorage。

退出登录挪到侧栏底部;密度设置从设置页撤下(侧栏按钮还在);
免责声明和关于做成子页(左上角返回),登录页输入框下加同一份
免责声明的弹层入口。
2026-08-12 01:01:33 +08:00
im0o 013f537eaa 订阅和规则组支持从文件导入,配置页与表单一批打磨
文件导入走两步:内容先经新端点 POST /api/provider-files 写进内核工作
目录的 providers/ 下(路径白名单校验,只认两个路径助手生成的形状),
成了再写 file 类型的声明——顺序反了内核重载会去读不存在的文件。
file 类型没有地址和自动更新(内核靠文件监视跟进),详情页照此展示,
编辑按钮禁用。ini 导入的取文件按钮换成同一个拖放模块。

订阅的更新间隔改成开关语义:内核只在 interval > 0 时才起定时拉取
(component/resource/fetcher.go:226-228),关掉开关就是不写这个键。
配置页加「更新所有订阅」,详情的「立刻更新」改叫「更新订阅」。

规则表从零开始写入时自动垫上内网直连——保留地址不直连的话第一条
代理规则一生效,访问局域网的流量就全进了代理;按钮改成「补全内网
直连」,只在有缺口时出现。

所有表单对话框点遮罩关闭;失败横幅右侧加关闭按钮。
2026-08-12 00:46:17 +08:00
im0o 60fb015e36 关掉 Bun.serve 的空闲超时,静默的日志流不再被掐成 500
内核 /logs 在没有日志产生时一个字节都不发(响应头随第一行日志才写出),
Bun 默认 10 秒空闲就关连接,经 vite 代理变成无响应体的 500,界面上只剩
一句指不到病根的「日志接口请求失败」。只监听本机的工具,不设空闲超时
没有被拖死的风险。
2026-08-11 23:25:52 +08:00
im0o 07a43b5a56 仪表盘细节修整,内核卡片搬左列并加重启
- 曲线空态和图共用一个定高容器,数据到来不再挤下面的内容
- 路由行去掉行间分割线:它会吃掉 hover 高亮的圆角
- 每路走势线先合并成 28 桶再画。3 秒一个采样直接全画,在一条
  百来像素宽的线上只剩噪声;没采集的桶画断口,不画假零
- 环形图两段加圆角,交界用底色描边让出间隙
- 内核卡片从右列搬到服务器和路由之间(左列本来就矮一截),加
  「重启内核」:POST /restart 后内核自我 exec、PID 不变,恢复没有
  任何通知,只能每 2 秒探一次,半分钟探不到交还给外壳心跳
2026-08-11 22:42:40 +08:00
im0o ce181e9ae5 服务端代答主机指标与公网 IP 检测
mihomo 的管理接口只报自己的内存,CPU、主机内存、网关、内网地址只有
跑在同一台机器上的 Patchbay 服务端答得出,所以加 /api/host 由它代答。
/api/host/exit 把本机某个入站端口设成代理去访问 ip.sb——浏览器发不出
「经由某个端口」的请求,这条只能走服务端。
2026-08-11 22:26:17 +08:00
im0o 6c8a1c4e0e 并行开发前的清场:白名单、读流模块、流量格式化
任务书承诺这些在分发前已存在。/traffic /logs 补进读白名单(在流式清单
里却不在读白名单里,仪表盘和请求页一开工就 403),PUT /providers/rules
补进写白名单(规则页的立刻更新)。web/stream.ts 把分块流缝成完整的行,
分块边界和行边界无关是它存在的全部理由;shared/format.ts 是连接页和
仪表盘共用的流量数字格式化。
2026-08-11 18:02:21 +08:00
im0o 4da501ded6 给落盘保护链路补上单元测试
全项目最关键的一条链路此前只有手动脚本在守。跑在一个假内核上,覆盖
在线路径的八种情形:正常保存、基准过时、内容未变、自检断引用、内核
拒绝后还原、恢复备份、路径穿越、备份轮换。mihomo -t 那条离线路径需要
真内核二进制,测不到,离线行为仍靠人工验证。
2026-08-11 14:41:56 +08:00
im0o 17f8b3f3fa 保存链路:内核在线时跳过 mihomo -t,离线时写盘不再回滚
实测 mihomo -t 占整条保存链路九成以上耗时(4-9.4 秒),而 PUT /configs
自带同一套解析、失败时什么都不应用,配合既有的还原备份等于校验,重载
只要几十到几百毫秒。代价是写盘到还原之间的短暂窗口里磁盘上存在一份
可能有错的配置。

顺带修正离线行为:原来重载连不上会把刚写好的文件还原掉,但内核起不来
时用这个界面修配置正是核心场景,现在离线走 mihomo -t 校验后写盘不重载。
2026-08-11 14:39:09 +08:00
im0o e9e2eb555d 每路入站配一组专属策略组,换线不再改配置文件
原本换一条线要改 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。
2026-08-11 03:10:38 +08:00
im0o afe53b9790 后端补上流式透传和两个写操作,给后面几个页面铺路
原以为流量和日志要写 WebSocket 转发,实测不用:内核在没有 WebSocket 升级时把
traffic、memory、logs 退化成分块的 HTTP 流,一行一个 JSON(server.go:380-383)。
现有的透传本来就能流式传,唯一的问题是那个 15 秒超时会每隔一会儿掐断一次,
给这三个路径去掉超时即可。

写操作照旧是白名单,逐条列出:断开连接(DELETE /connections 和 /connections/{id})、
刷新订阅(PUT /providers/proxies/{名字})。两者都只改内核运行状态、不碰配置文件,
所以不走落盘那条保护链路。方法和路径都要匹配才放行,测试里有反例。
2026-08-10 19:45:31 +08:00
im0o c5ac59ecf9 按审查结论重构:先补测试网,再搬代码
顺序是刻意的。前端一行测试都没有,直接挪代码挪坏了 bun test 不会红,所以先把纯逻辑
抽到能被测试的位置并补上用例,最后才动组件。测试从 37 条增加到 77 条。

抽出来的四块:
- shared/outbound-chain.ts  「这个端口现在走哪条线」的链路解析,带防环,9 条测试
- web/delay-test.ts          延迟测试失败时说什么,6 个分支原本一条测试都没有
- web/runtime.ts             运行时状态的模块级单例,照 prefs.ts 已有的模式
- web/components/InboundList.vue  入站列表

出站的文本原本散成四份(读一份、显示一份、写回一份、表单又抄一份),加一种出站模式
要同时改四处,漏一个就是显示与写回不一致。合并成 OUTBOUND_PRESETS 一张表。

给认证和透传白名单补了 20 条测试。两者都是安全边界、都是纯函数、都不需要内核在线,
之前却只靠需要活内核的冒烟脚本间接覆盖。其中「形似但不是健康检查的路径」几条确认了
正则没写松。

另外:配置快照不再把整份文件原文下发(前端从不读,白白让响应体积翻倍);
yaml-cosmetics 的测试改走生产路径,之前它自己拼了一遍三步流程,落盘链路漏掉其中一步
测试照样会绿。
2026-08-10 18:21:23 +08:00
im0o 6b208847a2 修掉审查查出的五个问题
保存失败会丢掉整张表单:对话框在 await commit 之前就关了,重开是空的。改成只有成功才关。

错误响应原本有两种形状({error} 和 {message,hint}),前端只读 message,于是服务端
写好的中文提示一律退化成「请求失败(HTTP 4xx)」。统一成 { message, hint? }。

路由处理器没有兜底:请求体不是合法 JSON、配置文件运行中被删,都会返回一个没有响应体
的 500。这条链路的立意是出事时把用户救回来,而出事时给的偏偏是最没信息量的一句话。

延迟测试失败被写进 done,而 done 的样式是成功绿。拆出中性的 notice——测不通是关于
线路的结论,既不是操作失败也不是成功。

刷新原本只有一个动词,测一条线路的延迟会导致整份配置被重读、重新解析、跑一遍引用校验,
外加四个往返。拆成 saved 和 runtime-changed 两个事件,运行时动作只重读运行时状态。
2026-08-10 14:29:46 +08:00
im0o 6029eacf50 修掉资源 404 被当成页面返回的 bug;加一条命令起开发模式
单页应用的兜底原本对任何找不到的路径都回 index.html,包括带扩展名的资源。
资源文件是带哈希的,每次打包换名字,浏览器缓存了旧 index.html 时会去请求一个已经
不存在的 .js,然后拿到一份 HTML,报出「MIME 类型不对」——一句完全指不到病根的错误。
现在带扩展名的路径找不到就回 404,index.html 也不再允许缓存。

另外 dev:all 一条命令同时起后端(--hot)和前端(Vite),省掉记两个终端和两个端口。
2026-08-10 14:15:22 +08:00
im0o 860da5d28f 订阅线路的类型、延迟和测速都改走 provider 接口
404 不是 bug。内核的 /proxies 只返回全局名字表(内置出站、自有线路、策略组),
订阅线路不在里面——跟「订阅线路不能被入站直接引用」是同一条约束,我没想到它同样
适用于运行时接口。所以 /proxies/{订阅线路}/delay 一律 404,而订阅线路的卡片也一直
没有协议和延迟,因为那些数据同样查不到。

现在运行时状态从两处合成:/proxies 给内置出站和策略组,/providers/proxies 给订阅线路
(它返回的线路本来就带完整的协议和延迟历史)。测速按线路归属选接口,订阅线路走
{订阅}/{线路}/healthcheck。实测该接口对同一条线路返回 200 和 325 毫秒。
2026-08-10 13:41:42 +08:00
im0o 5cdc183190 去掉转换入口,加测延迟,改掉刷新时丢状态的毛病
最要紧的是第三条:之前每次改完都会先切到「读取中」,页面被拆掉重建,
选中的那一路入站随之丢失,改个设置还得重新找回原来的位置。现在只有首次加载显示占位,
之后都是原地更新。切换策略组还做成了乐观更新——那一步不写配置文件,
没必要让点击等一个往返,真失败了随后的刷新会纠正回来。

测延迟按钮让内核并发测整个策略组。测不通是关于线路的结论而不是 Patchbay 出了问题,
所以全部超时用中性提示,不弹红框。
2026-08-10 12:53:56 +08:00
im0o 0062a261f7 补上真正日常要点的那个动作:切换策略组当前选中谁
之前的面板只做了「这个入站指向哪个出站」,那是改配置文件。但 metacubexd 里点一条线路
改的根本不是入站,是策略组的当前选中——调 PUT /proxies/{组},不写文件、立刻生效。
mixed-port 走规则表,末尾 MATCH,PROXY 把流量交给 PROXY 组,所以换那个组的选中就等于
换了这个端口的出口。这是每天都要点的动作,之前整个漏了。

面板顶部现在显示完整链条(PROXY → AUTO → 某条线路),并让人在最后一个策略组上换选中。
只显示第一层等于没回答「这个端口现在走哪条线」。

透传的白名单里为此开了唯一一个写操作,单独一条路由而不是放开整个转发。
2026-08-10 12:44:31 +08:00
im0o 7a764f5fe1 搭出前端骨架和入站列表
设计基调定为:层次靠底色和一像素描边而不是投影,一屏最多两层容器,间距给足。
tokens.css 顶部写了这三条规则,后面的组件都是它们的推论。

入站列表把两套写法合并成一张表:老式顶层端口字段(mixed-port 这些)和 listeners 数组。
只画 listeners 的话,一台只用 mixed-port 的机器打开会是空板,而那个端口正在跑——
这等于对用户撒谎。合并逻辑在 shared/inbounds.ts,带 10 个测试。

后端顺带能提供打包后的静态文件了,一个进程就能跑起来。
2026-08-10 04:10:17 +08:00
im0o ada547adf7 加上 HTTP 接口层和令牌认证
接口分三类:配置读写走保护链路,内核状态和只读透传,静态字段表。
透传用白名单而不是黑名单——写操作属于运行时面板,等做到那步再单独开,
现在不留一个什么都能转发的口子。

令牌即便在只监听本机时也强制校验:同一台机器上跑着容器和别的服务,
「只监听 127.0.0.1」不等于「只有你能访问」。比较用常数时间,避免靠响应耗时猜。

scripts/api-smoke.ts 对着真服务和真内核跑 17 项检查,端到端确认过
改配置之后内核会重载、新端口真的被监听起来。
2026-08-10 03:37:49 +08:00
im0o ac91b96ef8 写出配置落盘的保护链路
顺序是:检查基准是否过时 → 自己校验 → 临时文件上跑 mihomo -t → 备份 → 写入 → 重载,
最后两步任一失败就从备份还原并让内核载回原样。

自己校验那一步不能省:实测 v1.19.29,入站的 proxy 指向不存在的出站时 mihomo -t 会通过、
重载会成功,直到有流量走到那个入站才在日志里报错。这类错误只有 Patchbay 查得出来。

路径在读环境变量时就转成绝对路径,因为内核的 PUT /configs 拒绝相对路径。
2026-08-10 02:56:04 +08:00