设置页第二轮:行内编辑、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,覆盖整份替换)。平时是按钮,拖着文件
进窗口时整行变成投放区。
This commit is contained in:
2026-08-12 01:40:49 +08:00
parent 8eb98e2e9a
commit 2580c03bfa
10 changed files with 790 additions and 229 deletions
+28 -1
View File
@@ -2,7 +2,7 @@ import { afterAll, describe, expect, it } from 'bun:test'
import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { applyConfig, listBackups, readConfig, restoreBackup } from './config-store.ts'
import { applyConfig, importConfig, listBackups, readConfig, restoreBackup } from './config-store.ts'
import type { Env } from './env.ts'
/**
@@ -149,6 +149,33 @@ describe('落盘保护链路(内核在线)', () => {
expect(body).toContain('patchbay:')
})
it('覆盖导入整份替换,兼容导入只搬接管段落', async () => {
const env = makeEnv()
const uploaded = 'mixed-port: 9999\nrules:\n - MATCH,DIRECT\n'
const compat = await importConfig(env, uploaded, 'compatible')
expect(compat.ok).toBe(true)
let body = readFileSync(env.configPath, 'utf8')
// rules 是接管段落被搬过来,mixed-port 属于机器底座维持当前值
expect(body).toContain('MATCH,DIRECT')
expect(body).toContain('mixed-port: 7890')
expect(body).not.toContain('9999')
const over = await importConfig(env, uploaded, 'override')
expect(over.ok).toBe(true)
body = readFileSync(env.configPath, 'utf8')
expect(body).toContain('mixed-port: 9999')
})
it('导入解析不过的文件时一个字节都不动', async () => {
const env = makeEnv()
const result = await importConfig(env, '{{{ 不是 yaml', 'override')
expect(result.ok).toBe(false)
if (!result.ok) expect(result.stage).toBe('self-check')
expect(readFileSync(env.configPath, 'utf8')).toBe(ORIGINAL)
})
it('恢复只认列表里的名字,路径穿越进不来', async () => {
const env = makeEnv()
const result = await restoreBackup(env, '../config.yaml')