反馈改右上角轻提示;侧栏加撤回重做等配置动作

设置页的横幅换成 toast(右滑入、几秒后上移淡出,web/toast.ts 单例):
动作结果看一眼就行,不值得在页面里占一块常驻位置。表单校验类的错误
仍留在各自表单里。

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

设置页顺手修三处:GEO 行的编辑按钮在输入框开着时再点会闪(blur 关了
click 又开),mousedown.prevent 保焦点、click 显式开关;上传按钮改
「选择或拖放文件」,投放区拖入高亮;恢复策略挪到恢复行下面。
This commit is contained in:
2026-08-12 01:59:22 +08:00
parent 2580c03bfa
commit f761d13579
9 changed files with 798 additions and 106 deletions
+32 -2
View File
@@ -12,13 +12,23 @@
import { resolve, sep } from 'node:path'
import listenerFields from '../shared/listener-fields.json' with { type: 'json' }
import { isProviderFilePath } from '../shared/provider-files.ts'
import { applyConfig, importConfig, listBackups, readConfig, restoreBackup } from './config-store.ts'
import {
applyConfig,
importConfig,
listBackups,
listRedo,
readConfig,
redoConfig,
restoreBackup,
undoConfig,
} from './config-store.ts'
import { geoStatus } from './geo.ts'
import { hostInfo, probeExit } from './host.ts'
import {
kernelVersion,
proxyGet,
proxyWrite,
reloadConfig,
restartKernel,
selectProxy,
updateGeoDatabases,
@@ -181,7 +191,19 @@ function makeRoute(env: Env, token: string) {
}
if (path === '/api/config/backups' && req.method === 'GET') {
return json({ backups: await listBackups(env) })
// redo 是重做栈深度,侧栏用它决定重做按钮亮不亮
return json({ backups: await listBackups(env), redo: (await listRedo(env)).length })
}
// 撤回/重做。走 config-store 的保护链路,备份链的进出由它们自己管
if (path === '/api/config/undo' && req.method === 'POST') {
const result = await undoConfig(env)
return result.ok ? json(result) : json(result, applyFailureStatus(result.stage))
}
if (path === '/api/config/redo' && req.method === 'POST') {
const result = await redoConfig(env)
return result.ok ? json(result) : json(result, applyFailureStatus(result.stage))
}
// 配置文件原文,给「下载当前配置」用。带 attachment 让浏览器按下载处理
@@ -269,6 +291,14 @@ function makeRoute(env: Env, token: string) {
: json({ message: upstream.message || '内核拒绝了这次切换' }, 422)
}
// 让内核重新加载磁盘上的配置文件。给 SSH 手改过配置的场景用;运行时动作
if (path === '/api/kernel/reload' && req.method === 'POST') {
const upstream = await reloadConfig(env, env.configPath)
return upstream.ok
? json({ ok: true })
: json({ message: upstream.message || '内核重载失败' }, 502)
}
// GEO 数据库文件的本地状态(大小、更新时间)和生效中的下载地址
if (path === '/api/geo' && req.method === 'GET') {
const snapshot = await readConfig(env)