搭出前端骨架和入站列表

设计基调定为:层次靠底色和一像素描边而不是投影,一屏最多两层容器,间距给足。
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
+20 -1
View File
@@ -42,12 +42,31 @@ function applyFailureStatus(stage: string): number {
return 500 // write / reload:服务端或内核的问题
}
/** 打包后的前端。开发时前端由 Vite 提供并把 /api 转发过来,走不到这里。 */
const WEB_DIST = new URL('../web-dist/', import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, '$1')
/**
* 静态文件不需要令牌——它们只是一个空壳,所有数据都要带令牌才拿得到。
* 单页应用的路由由前端接管,所以找不到的路径一律回 index.html。
*/
async function serveStatic(path: string): Promise<Response> {
const target = Bun.file(WEB_DIST + (path === '/' ? 'index.html' : path.slice(1)))
if (await target.exists()) return new Response(target)
const shell = Bun.file(`${WEB_DIST}index.html`)
if (await shell.exists()) return new Response(shell)
return new Response('前端还没打包。先运行 bun run build,或者开发时用 bun run dev:web。', {
status: 404,
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
})
}
export function createRouter(env: Env, token: string) {
return async function handle(req: Request): Promise<Response> {
const url = new URL(req.url)
const path = url.pathname
if (!path.startsWith('/api/')) return json({ error: '没有这个接口' }, 404)
if (!path.startsWith('/api/')) return serveStatic(path)
if (!tokenMatches(token, tokenFromRequest(req))) {
return json({ error: '令牌不对。它在配置文件旁边的 .patchbay-token 里。' }, 401)