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
|
|
|
/**
|
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
|
|
|
* Bundle generation — display-chain routing.
|
2026-05-10 01:12:07 +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
|
|
|
* kiosk.display_id → layouts for display → cells → cameras
|
|
|
|
|
* No label filtering for v0.1.
|
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
|
|
|
*/
|
2026-05-10 01:12:07 +00:00
|
|
|
import { createHash } from "node:crypto";
|
|
|
|
|
import type { Repository } from "../plugins/service-store/repository.js";
|
|
|
|
|
import type { SecretsApi } from "./secrets.js";
|
|
|
|
|
|
|
|
|
|
export interface BundleCamera {
|
|
|
|
|
id: number;
|
|
|
|
|
name: string;
|
|
|
|
|
type: string;
|
|
|
|
|
rtsp_url: string | null;
|
|
|
|
|
onvif_host: string | null;
|
|
|
|
|
onvif_port: number | null;
|
|
|
|
|
onvif_username: string | null;
|
|
|
|
|
onvif_password_encrypted: string | null;
|
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
|
|
|
stream_policy: string;
|
2026-05-10 01:12:07 +00:00
|
|
|
streams: Array<{
|
|
|
|
|
id: number;
|
|
|
|
|
role: string;
|
|
|
|
|
name: string;
|
|
|
|
|
rtsp_uri: string;
|
|
|
|
|
width: number | null;
|
|
|
|
|
height: number | null;
|
|
|
|
|
encoding: string | null;
|
|
|
|
|
framerate: number | null;
|
|
|
|
|
}>;
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
export interface BundleCell {
|
2026-05-10 19:55:19 +00:00
|
|
|
row: number;
|
|
|
|
|
col: number;
|
|
|
|
|
row_span: number;
|
|
|
|
|
col_span: number;
|
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
|
|
|
content_type: string;
|
|
|
|
|
camera_id: number | null;
|
|
|
|
|
stream_selector: string | null;
|
|
|
|
|
web_url: string | null;
|
|
|
|
|
html_content: string | null;
|
|
|
|
|
cooling_timeout_seconds: number | null;
|
2026-05-11 11:52:22 +00:00
|
|
|
fit: "cover" | "contain" | "fill";
|
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
|
|
|
}
|
|
|
|
|
|
2026-05-10 01:12:07 +00:00
|
|
|
export interface BundleLayout {
|
|
|
|
|
id: number;
|
|
|
|
|
name: string;
|
2026-05-10 19:55:19 +00:00
|
|
|
/** Computed from cells: max(col + col_span). 1 if no cells. */
|
2026-05-10 19:39:09 +00:00
|
|
|
grid_cols: number;
|
2026-05-10 19:55:19 +00:00
|
|
|
/** Computed from cells: max(row + row_span). 1 if no cells. */
|
2026-05-10 19:39:09 +00:00
|
|
|
grid_rows: number;
|
2026-05-10 01:12:07 +00:00
|
|
|
priority: string;
|
|
|
|
|
cooling_timeout_seconds: number | null;
|
|
|
|
|
preload_camera_ids: number[];
|
|
|
|
|
resets_idle_timer: boolean;
|
2026-05-10 19:55:19 +00:00
|
|
|
/** True if the kiosk's display has this layout as its default_layout_id. */
|
|
|
|
|
is_default: boolean;
|
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
|
|
|
cells: BundleCell[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface BundleDisplay {
|
|
|
|
|
id: number;
|
|
|
|
|
name: string;
|
|
|
|
|
width_px: number;
|
|
|
|
|
height_px: number;
|
|
|
|
|
idle_timeout_seconds: number;
|
|
|
|
|
sleep_timeout_seconds: number;
|
|
|
|
|
default_layout_id: number | null;
|
2026-05-10 01:12:07 +00:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 23:18:22 +00:00
|
|
|
export interface BundleDisplayWithLayouts extends BundleDisplay {
|
|
|
|
|
layouts: BundleLayout[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface BundleGpioBinding {
|
|
|
|
|
id: number;
|
|
|
|
|
chip: string;
|
|
|
|
|
pin: number;
|
|
|
|
|
direction: "in" | "out";
|
|
|
|
|
pull: "up" | "down" | "none" | null;
|
|
|
|
|
edge: "rising" | "falling" | "both" | null;
|
|
|
|
|
topic: string;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 01:12:07 +00:00
|
|
|
export interface KioskBundle {
|
|
|
|
|
kiosk_id: number;
|
|
|
|
|
kiosk_name: string;
|
2026-05-12 23:18:22 +00:00
|
|
|
/**
|
|
|
|
|
* @deprecated Use `displays` (array). Kept for backward compat with older
|
|
|
|
|
* kiosk builds that consume a single display. Mirrors `displays[0]`.
|
|
|
|
|
*/
|
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
|
|
|
display: BundleDisplay;
|
2026-05-12 23:18:22 +00:00
|
|
|
/**
|
|
|
|
|
* @deprecated Use `displays[N].layouts`. Mirrors `displays[0].layouts` for
|
|
|
|
|
* older kiosk builds.
|
|
|
|
|
*/
|
2026-05-10 01:12:07 +00:00
|
|
|
layouts: BundleLayout[];
|
2026-05-12 23:18:22 +00:00
|
|
|
/** All physical displays driven by this kiosk. New (multi-display) shape. */
|
|
|
|
|
displays: BundleDisplayWithLayouts[];
|
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
|
|
|
cameras: BundleCamera[];
|
2026-05-12 23:18:22 +00:00
|
|
|
gpio_bindings: BundleGpioBinding[];
|
2026-05-10 01:12:07 +00:00
|
|
|
version: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function generateBundle(
|
|
|
|
|
repo: Repository,
|
|
|
|
|
secrets: SecretsApi,
|
|
|
|
|
kioskId: number,
|
|
|
|
|
clusterKey: string | undefined,
|
|
|
|
|
): KioskBundle | null {
|
|
|
|
|
const kiosk = repo.getKioskById(kioskId);
|
2026-05-10 19:39:09 +00:00
|
|
|
if (!kiosk) return null;
|
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
|
|
|
|
2026-05-12 23:18:22 +00:00
|
|
|
// Find all displays for this kiosk (displays now point to kiosks via kiosk_id)
|
2026-05-10 19:39:09 +00:00
|
|
|
const kioskDisplays = repo.listDisplaysForKiosk(kioskId);
|
|
|
|
|
// Fall back to legacy kiosk.display_id if no displays point to this kiosk yet
|
2026-05-12 23:18:22 +00:00
|
|
|
const displays = kioskDisplays.length > 0
|
|
|
|
|
? kioskDisplays
|
|
|
|
|
: (kiosk.display_id ? [repo.getDisplayById(kiosk.display_id)].filter((d): d is NonNullable<typeof d> => d != null) : []);
|
|
|
|
|
|
|
|
|
|
if (displays.length === 0) return null;
|
|
|
|
|
|
|
|
|
|
// Collect camera IDs across ALL displays' layouts (de-duped).
|
|
|
|
|
const allLayoutIds = new Set<number>();
|
|
|
|
|
for (const d of displays) {
|
|
|
|
|
for (const l of repo.layoutsForDisplayId(d.id)) allLayoutIds.add(l.id);
|
2026-05-10 19:39:09 +00:00
|
|
|
}
|
2026-05-12 23:18:22 +00:00
|
|
|
const cameras = repo.camerasForLayoutIds([...allLayoutIds]);
|
|
|
|
|
|
|
|
|
|
function buildLayouts(displayId: number, defaultLayoutId: number | null): BundleLayout[] {
|
|
|
|
|
const layouts = repo.layoutsForDisplayId(displayId);
|
|
|
|
|
return layouts.map((l) => {
|
|
|
|
|
const cells = repo.layoutCells(l.id);
|
|
|
|
|
let gridCols = 1;
|
|
|
|
|
let gridRows = 1;
|
|
|
|
|
for (const c of cells) {
|
|
|
|
|
const right = c.col + c.col_span;
|
|
|
|
|
const bottom = c.row + c.row_span;
|
|
|
|
|
if (right > gridCols) gridCols = right;
|
|
|
|
|
if (bottom > gridRows) gridRows = bottom;
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
id: l.id,
|
|
|
|
|
name: l.name,
|
|
|
|
|
grid_cols: gridCols,
|
|
|
|
|
grid_rows: gridRows,
|
|
|
|
|
priority: l.priority,
|
|
|
|
|
cooling_timeout_seconds: l.cooling_timeout_seconds,
|
|
|
|
|
preload_camera_ids: l.preload_camera_ids,
|
|
|
|
|
resets_idle_timer: l.resets_idle_timer,
|
|
|
|
|
is_default: defaultLayoutId === l.id,
|
|
|
|
|
cells: cells.map((c) => {
|
|
|
|
|
// If the cell has an entity, prefer its current content so admin
|
|
|
|
|
// edits to the entity propagate without forcing a cell-touch. The
|
|
|
|
|
// bundle still ships the legacy camera_id/web_url/html_content shape
|
|
|
|
|
// so the existing Rust kiosk consumes it unchanged.
|
|
|
|
|
let contentType = c.content_type;
|
|
|
|
|
let cameraId = c.camera_id;
|
|
|
|
|
let webUrl = c.web_url;
|
|
|
|
|
let htmlContent = c.html_content;
|
|
|
|
|
if (c.entity_id != null) {
|
|
|
|
|
const ent = repo.getEntityById(c.entity_id);
|
|
|
|
|
if (ent) {
|
|
|
|
|
contentType = ent.type;
|
|
|
|
|
cameraId = ent.type === "camera" ? ent.camera_id : null;
|
|
|
|
|
webUrl = ent.type === "web" ? ent.web_url : null;
|
|
|
|
|
htmlContent = ent.type === "html" ? ent.html_content : null;
|
|
|
|
|
}
|
2026-05-10 21:18:44 +00:00
|
|
|
}
|
2026-05-12 23:18:22 +00:00
|
|
|
return {
|
|
|
|
|
row: c.row,
|
|
|
|
|
col: c.col,
|
|
|
|
|
row_span: c.row_span,
|
|
|
|
|
col_span: c.col_span,
|
|
|
|
|
content_type: contentType,
|
|
|
|
|
camera_id: cameraId,
|
|
|
|
|
stream_selector: c.stream_selector,
|
|
|
|
|
web_url: webUrl,
|
|
|
|
|
html_content: htmlContent,
|
|
|
|
|
cooling_timeout_seconds: c.cooling_timeout_seconds,
|
|
|
|
|
fit: c.fit,
|
|
|
|
|
};
|
|
|
|
|
}),
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bundleDisplays: BundleDisplayWithLayouts[] = displays.map((display) => ({
|
|
|
|
|
id: display.id,
|
|
|
|
|
name: display.name,
|
|
|
|
|
width_px: display.width_px,
|
|
|
|
|
height_px: display.height_px,
|
|
|
|
|
idle_timeout_seconds: display.idle_timeout_seconds,
|
|
|
|
|
sleep_timeout_seconds: display.sleep_timeout_seconds,
|
|
|
|
|
default_layout_id: display.default_layout_id,
|
|
|
|
|
layouts: buildLayouts(display.id, display.default_layout_id),
|
|
|
|
|
}));
|
2026-05-10 01:12:07 +00:00
|
|
|
|
|
|
|
|
const bundleCameras: BundleCamera[] = cameras.map((cam) => {
|
|
|
|
|
const streams = repo.listCameraStreams(cam.id);
|
|
|
|
|
let onvifPwEncrypted: string | null = null;
|
|
|
|
|
if (cam.onvif_password && clusterKey) {
|
|
|
|
|
onvifPwEncrypted = secrets.encryptForCluster(cam.onvif_password, clusterKey);
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
id: cam.id,
|
|
|
|
|
name: cam.name,
|
|
|
|
|
type: cam.type,
|
|
|
|
|
rtsp_url: cam.rtsp_url,
|
|
|
|
|
onvif_host: cam.onvif_host,
|
|
|
|
|
onvif_port: cam.onvif_port,
|
|
|
|
|
onvif_username: cam.onvif_username,
|
|
|
|
|
onvif_password_encrypted: onvifPwEncrypted,
|
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
|
|
|
stream_policy: cam.stream_policy,
|
2026-05-10 01:12:07 +00:00
|
|
|
streams: streams.map((s) => ({
|
|
|
|
|
id: s.id,
|
|
|
|
|
role: s.role,
|
|
|
|
|
name: s.name,
|
|
|
|
|
rtsp_uri: s.rtsp_uri,
|
|
|
|
|
width: s.width,
|
|
|
|
|
height: s.height,
|
|
|
|
|
encoding: s.encoding,
|
|
|
|
|
framerate: s.framerate,
|
|
|
|
|
})),
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-12 23:18:22 +00:00
|
|
|
const gpioBindings: BundleGpioBinding[] = repo.listGpioBindings(kioskId).map((g) => ({
|
|
|
|
|
id: g.id,
|
|
|
|
|
chip: g.chip,
|
|
|
|
|
pin: g.pin,
|
|
|
|
|
direction: g.direction,
|
|
|
|
|
pull: g.pull,
|
|
|
|
|
edge: g.edge,
|
|
|
|
|
topic: g.topic,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// Mirror first display into the legacy top-level `display` + `layouts` so
|
|
|
|
|
// older kiosk builds keep working unchanged. New builds should read
|
|
|
|
|
// `displays`.
|
|
|
|
|
const primary = bundleDisplays[0]!;
|
2026-05-10 01:12:07 +00:00
|
|
|
const bundle: KioskBundle = {
|
|
|
|
|
kiosk_id: kioskId,
|
|
|
|
|
kiosk_name: kiosk.name,
|
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
|
|
|
display: {
|
2026-05-12 23:18:22 +00:00
|
|
|
id: primary.id,
|
|
|
|
|
name: primary.name,
|
|
|
|
|
width_px: primary.width_px,
|
|
|
|
|
height_px: primary.height_px,
|
|
|
|
|
idle_timeout_seconds: primary.idle_timeout_seconds,
|
|
|
|
|
sleep_timeout_seconds: primary.sleep_timeout_seconds,
|
|
|
|
|
default_layout_id: primary.default_layout_id,
|
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
|
|
|
},
|
2026-05-12 23:18:22 +00:00
|
|
|
layouts: primary.layouts,
|
|
|
|
|
displays: bundleDisplays,
|
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
|
|
|
cameras: bundleCameras,
|
2026-05-12 23:18:22 +00:00
|
|
|
gpio_bindings: gpioBindings,
|
2026-05-10 01:12:07 +00:00
|
|
|
version: "",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
bundle.version = createHash("sha256")
|
|
|
|
|
.update(JSON.stringify(bundle))
|
|
|
|
|
.digest("hex");
|
|
|
|
|
|
|
|
|
|
return bundle;
|
|
|
|
|
}
|