搭出前端骨架和入站列表

设计基调定为:层次靠底色和一像素描边而不是投影,一屏最多两层容器,间距给足。
tokens.css 顶部写了这三条规则,后面的组件都是它们的推论。

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

后端顺带能提供打包后的静态文件了,一个进程就能跑起来。
This commit is contained in:
im0o
2026-08-10 04:10:17 +08:00
parent ada547adf7
commit 7a764f5fe1
14 changed files with 1054 additions and 5 deletions
+11 -2
View File
@@ -11,7 +11,7 @@
* 用法:bun run scripts/api-smoke.ts [令牌]
* 不给令牌就从配置文件旁边的 .patchbay-token 读。
*/
import { readFile } from 'node:fs/promises'
import { copyFile, readFile } from 'node:fs/promises'
import { dirname, join } from 'node:path'
import { readEnv } from '../server/env.ts'
import type { ConfigObject } from '../vendor/config-editor/index.ts'
@@ -22,6 +22,10 @@ const token =
process.argv[2] ??
(await readFile(join(dirname(env.configPath), '.patchbay-token'), 'utf8')).trim()
// 每次从模板重置。不重置的话第二次运行会写入和上次完全相同的内容,
// 而「内容没变就不重载」是刻意的行为,会让「内核已重载」这一项看起来失败。
await copyFile('./dev/config.yaml', env.configPath)
let failures = 0
function check(name: string, actual: unknown, expected: unknown) {
if (JSON.stringify(actual) === JSON.stringify(expected)) {
@@ -56,7 +60,12 @@ check(
)
check('白名单外的透传被拒', (await call('GET', '/api/kernel/configs/geo')).status, 403)
check('不存在的接口', (await call('GET', '/api/nope')).status, 404)
check('非 api 路径', (await call('GET', '/index.html')).status, 404)
// 静态文件不要令牌:它只是个空壳,所有数据都要带令牌才拿得到。
check(
'前端页面不需要令牌',
(await fetch(`${base}/`)).status,
200,
)
// ── 配置读写 ────────────────────────────────────────────
const snapshot = await call('GET', '/api/config')