2026-05-09 23:09:13 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* First-run setup routes.
|
|
|
|
|
|
*/
|
2026-05-10 00:50:16 +00:00
|
|
|
|
import { type H3, readBody } from "h3";
|
|
|
|
|
|
import { htmlPage } from "./html-response.js";
|
2026-05-09 23:09:13 +00:00
|
|
|
|
import type { AdminDeps } from "./index.js";
|
|
|
|
|
|
import { SetupPage } from "../../web-templates/auth-pages.js";
|
|
|
|
|
|
|
|
|
|
|
|
export function registerSetupRoutes(app: H3, deps: AdminDeps): void {
|
|
|
|
|
|
app.get("/setup", () => {
|
refactor: collapse 6 non-service plugins into shared modules
BSB plugins should be actual services (own port, lifecycle, resource
ownership). Moved secrets, auth, pairing, bundle, nodered-bridge, and
cec-relay from plugin folders to shared modules under server/src/shared/.
4 BSB plugins remain: service-store, service-admin-http,
service-api-http, service-coordinator-ws.
service-admin-http now initializes secrets + auth as plain modules in
init() using the store repo from the plugin-registry singleton. No
more setSiblings() hack or inter-plugin wiring.
sec-config.yaml updated: secrets/auth config moved into
service-admin-http, pairing config into service-api-http, nodered
config into service-coordinator-ws.
2026-05-10 00:29:25 +00:00
|
|
|
|
if (deps.repo.isSetupComplete()) {
|
2026-05-09 23:09:13 +00:00
|
|
|
|
return new Response(null, { status: 302, headers: { location: "/admin/" } });
|
|
|
|
|
|
}
|
2026-05-10 00:50:16 +00:00
|
|
|
|
return htmlPage(SetupPage({}));
|
2026-05-09 23:09:13 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
app.post("/setup", async (event) => {
|
refactor: collapse 6 non-service plugins into shared modules
BSB plugins should be actual services (own port, lifecycle, resource
ownership). Moved secrets, auth, pairing, bundle, nodered-bridge, and
cec-relay from plugin folders to shared modules under server/src/shared/.
4 BSB plugins remain: service-store, service-admin-http,
service-api-http, service-coordinator-ws.
service-admin-http now initializes secrets + auth as plain modules in
init() using the store repo from the plugin-registry singleton. No
more setSiblings() hack or inter-plugin wiring.
sec-config.yaml updated: secrets/auth config moved into
service-admin-http, pairing config into service-api-http, nodered
config into service-coordinator-ws.
2026-05-10 00:29:25 +00:00
|
|
|
|
if (deps.repo.isSetupComplete()) {
|
2026-05-09 23:09:13 +00:00
|
|
|
|
return new Response(null, { status: 302, headers: { location: "/admin/" } });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const body = await readBody<{ username?: string; password?: string }>(event);
|
|
|
|
|
|
const username = (body?.username ?? "").trim();
|
|
|
|
|
|
const password = body?.password ?? "";
|
|
|
|
|
|
const errors: string[] = [];
|
|
|
|
|
|
|
|
|
|
|
|
if (!username || username.length < 3 || username.length > 64) {
|
|
|
|
|
|
errors.push("Username must be 3–64 characters.");
|
|
|
|
|
|
} else if (!/^[a-zA-Z0-9_-]+$/.test(username)) {
|
|
|
|
|
|
errors.push("Username may only contain letters, digits, underscore, or hyphen.");
|
|
|
|
|
|
}
|
|
|
|
|
|
if (password.length < 12) {
|
|
|
|
|
|
errors.push("Password must be at least 12 characters.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (errors.length > 0) {
|
2026-05-10 00:50:16 +00:00
|
|
|
|
return htmlPage(SetupPage({ error: errors.join(" "), username }));
|
2026-05-09 23:09:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const hash = await deps.auth.hashPassword(password);
|
refactor: collapse 6 non-service plugins into shared modules
BSB plugins should be actual services (own port, lifecycle, resource
ownership). Moved secrets, auth, pairing, bundle, nodered-bridge, and
cec-relay from plugin folders to shared modules under server/src/shared/.
4 BSB plugins remain: service-store, service-admin-http,
service-api-http, service-coordinator-ws.
service-admin-http now initializes secrets + auth as plain modules in
init() using the store repo from the plugin-registry singleton. No
more setSiblings() hack or inter-plugin wiring.
sec-config.yaml updated: secrets/auth config moved into
service-admin-http, pairing config into service-api-http, nodered
config into service-coordinator-ws.
2026-05-10 00:29:25 +00:00
|
|
|
|
deps.repo.createUser({ username, password_hash: hash, role: "admin" });
|
2026-05-09 23:09:13 +00:00
|
|
|
|
|
|
|
|
|
|
const clusterKey = deps.secrets.generateClusterKey();
|
|
|
|
|
|
const encryptedCluster = deps.secrets.encryptString(clusterKey, "cluster");
|
refactor: collapse 6 non-service plugins into shared modules
BSB plugins should be actual services (own port, lifecycle, resource
ownership). Moved secrets, auth, pairing, bundle, nodered-bridge, and
cec-relay from plugin folders to shared modules under server/src/shared/.
4 BSB plugins remain: service-store, service-admin-http,
service-api-http, service-coordinator-ws.
service-admin-http now initializes secrets + auth as plain modules in
init() using the store repo from the plugin-registry singleton. No
more setSiblings() hack or inter-plugin wiring.
sec-config.yaml updated: secrets/auth config moved into
service-admin-http, pairing config into service-api-http, nodered
config into service-coordinator-ws.
2026-05-10 00:29:25 +00:00
|
|
|
|
deps.repo.setSetupExtra("cluster_key_encrypted", encryptedCluster);
|
|
|
|
|
|
deps.repo.markClusterKeyProvisioned();
|
2026-05-09 23:09:13 +00:00
|
|
|
|
|
feat: layout/template/display CRUD + display-chain bundle routing
Major changes:
- Bundle now follows kiosk → display → layouts → cells → cameras
(no label filtering for v0.1)
- Setup creates default Fullscreen template + Default layout with
BetterFrame logo on the primary display
- Pairing auto-assigns kiosk to primary display
- Admin UI: full template CRUD with presets (fullscreen, 2x2, 1+3, 3x3)
- Admin UI: layout CRUD with cell management (assign cameras/web/html
to template regions)
- Admin UI: display editing (default layout, idle/sleep timeouts)
- Repository: added createLayoutTemplate, createLayout, createLayoutCell,
updateLayout, deleteLayout, layoutsForDisplayId, camerasForLayoutIds,
updateDisplay, and more
2026-05-10 01:45:53 +00:00
|
|
|
|
// Create default display, template, and layout
|
|
|
|
|
|
const display = deps.repo.createDefaultDisplay();
|
|
|
|
|
|
const template = deps.repo.createLayoutTemplate({
|
|
|
|
|
|
name: "Fullscreen",
|
|
|
|
|
|
description: "Single region covering the entire display",
|
|
|
|
|
|
regions: [{ name: "main", row: 0, col: 0, rowSpan: 1, colSpan: 1 }],
|
|
|
|
|
|
grid_cols: 1,
|
|
|
|
|
|
grid_rows: 1,
|
|
|
|
|
|
is_builtin: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
const layout = deps.repo.createLayout({
|
|
|
|
|
|
name: "Default",
|
|
|
|
|
|
description: "Default layout — BetterFrame logo",
|
|
|
|
|
|
template_id: template.id,
|
|
|
|
|
|
display_id: display.id,
|
|
|
|
|
|
is_default: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
deps.repo.createLayoutCell({
|
|
|
|
|
|
layout_id: layout.id,
|
|
|
|
|
|
region_name: "main",
|
|
|
|
|
|
content_type: "html",
|
|
|
|
|
|
html_content: '<div style="display:flex;align-items:center;justify-content:center;height:100vh;background:#1a1a2e;color:#fff;font-family:system-ui"><h1 style="font-size:3rem;font-weight:300">BetterFrame</h1></div>',
|
|
|
|
|
|
});
|
|
|
|
|
|
deps.repo.updateDisplay(display.id, { default_layout_id: layout.id });
|
refactor: collapse 6 non-service plugins into shared modules
BSB plugins should be actual services (own port, lifecycle, resource
ownership). Moved secrets, auth, pairing, bundle, nodered-bridge, and
cec-relay from plugin folders to shared modules under server/src/shared/.
4 BSB plugins remain: service-store, service-admin-http,
service-api-http, service-coordinator-ws.
service-admin-http now initializes secrets + auth as plain modules in
init() using the store repo from the plugin-registry singleton. No
more setSiblings() hack or inter-plugin wiring.
sec-config.yaml updated: secrets/auth config moved into
service-admin-http, pairing config into service-api-http, nodered
config into service-coordinator-ws.
2026-05-10 00:29:25 +00:00
|
|
|
|
deps.repo.markSetupComplete();
|
2026-05-09 23:09:13 +00:00
|
|
|
|
|
|
|
|
|
|
return new Response(null, {
|
|
|
|
|
|
status: 302,
|
|
|
|
|
|
headers: { location: "/auth/login?welcome=1" },
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|