修掉审查查出的五个问题
保存失败会丢掉整张表单:对话框在 await commit 之前就关了,重开是空的。改成只有成功才关。
错误响应原本有两种形状({error} 和 {message,hint}),前端只读 message,于是服务端
写好的中文提示一律退化成「请求失败(HTTP 4xx)」。统一成 { message, hint? }。
路由处理器没有兜底:请求体不是合法 JSON、配置文件运行中被删,都会返回一个没有响应体
的 500。这条链路的立意是出事时把用户救回来,而出事时给的偏偏是最没信息量的一句话。
延迟测试失败被写进 done,而 done 的样式是成功绿。拆出中性的 notice——测不通是关于
线路的结论,既不是操作失败也不是成功。
刷新原本只有一个动词,测一条线路的延迟会导致整份配置被重读、重新解析、跑一遍引用校验,
外加四个往返。拆成 saved 和 runtime-changed 两个事件,运行时动作只重读运行时状态。
This commit is contained in:
+29
-6
@@ -98,14 +98,37 @@ async function serveStatic(path: string): Promise<Response> {
|
||||
}
|
||||
|
||||
export function createRouter(env: Env, token: string) {
|
||||
const route = makeRoute(env, token)
|
||||
|
||||
/**
|
||||
* 兜底。没有它的话,请求体不是合法 JSON、或者配置文件在运行中被删,都会抛出一个
|
||||
* 没有响应体的 500,前端只能显示「请求失败(HTTP 500)」——这条链路的全部立意是
|
||||
* 出事时把用户救回来,而出事时给出的偏偏是最没信息量的一句话。
|
||||
*/
|
||||
return async function handle(req: Request): Promise<Response> {
|
||||
try {
|
||||
return await route(req)
|
||||
} catch (error) {
|
||||
return json(
|
||||
{
|
||||
message: String(error),
|
||||
hint: '这是 Patchbay 自身的故障,配置文件没有被改动。',
|
||||
},
|
||||
500,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function makeRoute(env: Env, token: string) {
|
||||
return async function route(req: Request): Promise<Response> {
|
||||
const url = new URL(req.url)
|
||||
const path = url.pathname
|
||||
|
||||
if (!path.startsWith('/api/')) return serveStatic(path)
|
||||
|
||||
if (!tokenMatches(token, tokenFromRequest(req))) {
|
||||
return json({ error: '令牌不对。它在配置文件旁边的 .patchbay-token 里。' }, 401)
|
||||
return json({ message: '令牌不正确', hint: '它在服务端配置文件的同级目录 .patchbay-token 里。' }, 401)
|
||||
}
|
||||
|
||||
// ── 配置 ────────────────────────────────────────────
|
||||
@@ -117,7 +140,7 @@ export function createRouter(env: Env, token: string) {
|
||||
if (path === '/api/config' && req.method === 'PUT') {
|
||||
const body = (await req.json()) as { data?: ConfigObject; baseRevision?: string }
|
||||
if (!body.data || typeof body.data !== 'object') {
|
||||
return json({ error: '请求里没有 data 字段' }, 400)
|
||||
return json({ message: '请求缺少 data 字段' }, 400)
|
||||
}
|
||||
const result = await applyConfig(env, body.data, body.baseRevision)
|
||||
return result.ok ? json(result) : json(result, applyFailureStatus(result.stage))
|
||||
@@ -145,11 +168,11 @@ export function createRouter(env: Env, token: string) {
|
||||
if (selectMatch && req.method === 'PUT') {
|
||||
const group = decodeURIComponent(selectMatch[1]!)
|
||||
const body = (await req.json()) as { name?: string }
|
||||
if (!body.name) return json({ error: '请求里没有 name 字段' }, 400)
|
||||
if (!body.name) return json({ message: '请求缺少 name 字段' }, 400)
|
||||
const upstream = await selectProxy(env, group, body.name)
|
||||
return upstream.ok
|
||||
? json({ ok: true })
|
||||
: json({ error: upstream.message || `内核拒绝了这次切换` }, 422)
|
||||
: json({ message: upstream.message || '内核拒绝了这次切换' }, 422)
|
||||
}
|
||||
|
||||
if (path.startsWith('/api/kernel/') && req.method === 'GET') {
|
||||
@@ -157,7 +180,7 @@ export function createRouter(env: Env, token: string) {
|
||||
const allowed =
|
||||
READABLE_KERNEL_PATHS.has(target) || DELAY_PATH.test(target) || HEALTHCHECK_PATH.test(target)
|
||||
if (!allowed) {
|
||||
return json({ error: `不允许透传这个路径:${target}` }, 403)
|
||||
return json({ message: `不允许访问该路径:${target}` }, 403)
|
||||
}
|
||||
const upstream = await proxyGet(env, target + url.search)
|
||||
return new Response(upstream.body, {
|
||||
@@ -171,6 +194,6 @@ export function createRouter(env: Env, token: string) {
|
||||
return json(listenerFields)
|
||||
}
|
||||
|
||||
return json({ error: '没有这个接口' }, 404)
|
||||
return json({ message: '没有这个接口' }, 404)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user