feat(control-panel): web admin UI for the zero-touch tunnel
Loopback Fastify auth-broker + esbuild SPA. Operator password login (constant-time, signed HttpOnly session cookie, per-forwarded-IP rate-limit) → session-gated proxy that mints a fresh 60s manage capability token per call to the control-plane admin API: list hosts, mint pairing codes (with QR + pair command), revoke hosts. Security headers + CSP, CP_URL pinned loopback (anti-SSRF), hostId dot-segment guard. 55 tests pass; security-reviewed. Deployed behind nginx panel.terminal.yaojia.wang.
This commit is contained in:
37
control-panel/build.mjs
Normal file
37
control-panel/build.mjs
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Frontend build — mirrors the base app's esbuild style (vanilla TS → ESM bundle). Emits
|
||||
* public/build/{app.js, index.html, styles.css}. index.html + styles.css are copied verbatim
|
||||
* (index.html already references ./app.js and ./styles.css, both siblings in the build dir).
|
||||
*/
|
||||
import { build } from 'esbuild'
|
||||
import { mkdir, copyFile } from 'node:fs/promises'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url))
|
||||
const pub = resolve(here, 'public')
|
||||
const out = resolve(pub, 'build')
|
||||
|
||||
async function main() {
|
||||
await mkdir(out, { recursive: true })
|
||||
|
||||
await build({
|
||||
entryPoints: [resolve(pub, 'app.ts')],
|
||||
bundle: true,
|
||||
format: 'esm',
|
||||
target: 'es2022',
|
||||
minify: true,
|
||||
sourcemap: true,
|
||||
outfile: resolve(out, 'app.js'),
|
||||
logLevel: 'info',
|
||||
})
|
||||
|
||||
await copyFile(resolve(pub, 'index.html'), resolve(out, 'index.html'))
|
||||
await copyFile(resolve(pub, 'styles.css'), resolve(out, 'styles.css'))
|
||||
process.stdout.write('[control-panel] frontend build → public/build\n')
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
process.stderr.write(`[control-panel] build failed: ${err instanceof Error ? err.message : String(err)}\n`)
|
||||
process.exit(1)
|
||||
})
|
||||
Reference in New Issue
Block a user