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 {
|
|
|
|
|
region_name: string;
|
|
|
|
|
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-10 01:12:07 +00:00
|
|
|
export interface BundleLayout {
|
|
|
|
|
id: number;
|
|
|
|
|
name: string;
|
2026-05-10 19:39:09 +00:00
|
|
|
regions: unknown;
|
|
|
|
|
grid_cols: number;
|
|
|
|
|
grid_rows: number;
|
2026-05-10 01:12:07 +00:00
|
|
|
priority: string;
|
|
|
|
|
cooling_timeout_seconds: number | null;
|
|
|
|
|
preload_camera_ids: number[];
|
|
|
|
|
is_default: boolean;
|
|
|
|
|
resets_idle_timer: 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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface KioskBundle {
|
|
|
|
|
kiosk_id: number;
|
|
|
|
|
kiosk_name: string;
|
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-10 01:12:07 +00:00
|
|
|
layouts: BundleLayout[];
|
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-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-10 19:39:09 +00:00
|
|
|
// Find display for this kiosk (displays now point to kiosks via kiosk_id)
|
|
|
|
|
const kioskDisplays = repo.listDisplaysForKiosk(kioskId);
|
|
|
|
|
// Fall back to legacy kiosk.display_id if no displays point to this kiosk yet
|
|
|
|
|
let display = kioskDisplays[0] ?? null;
|
|
|
|
|
if (!display && kiosk.display_id) {
|
|
|
|
|
display = repo.getDisplayById(kiosk.display_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
|
|
|
if (!display) return null;
|
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
|
|
|
const layouts = repo.layoutsForDisplayId(display.id);
|
|
|
|
|
const layoutIds = layouts.map((l) => l.id);
|
|
|
|
|
|
|
|
|
|
// Collect all cameras referenced by cells in these layouts
|
|
|
|
|
const cameras = repo.camerasForLayoutIds(layoutIds);
|
|
|
|
|
|
|
|
|
|
const bundleLayouts: BundleLayout[] = layouts.map((l) => {
|
|
|
|
|
const cells = repo.layoutCells(l.id);
|
|
|
|
|
return {
|
|
|
|
|
id: l.id,
|
|
|
|
|
name: l.name,
|
2026-05-10 19:39:09 +00:00
|
|
|
regions: l.regions,
|
|
|
|
|
grid_cols: l.grid_cols,
|
|
|
|
|
grid_rows: l.grid_rows,
|
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
|
|
|
priority: l.priority,
|
|
|
|
|
cooling_timeout_seconds: l.cooling_timeout_seconds,
|
|
|
|
|
preload_camera_ids: l.preload_camera_ids,
|
|
|
|
|
is_default: l.is_default,
|
|
|
|
|
resets_idle_timer: l.resets_idle_timer,
|
|
|
|
|
cells: cells.map((c) => ({
|
|
|
|
|
region_name: c.region_name,
|
|
|
|
|
content_type: c.content_type,
|
|
|
|
|
camera_id: c.camera_id,
|
|
|
|
|
stream_selector: c.stream_selector,
|
|
|
|
|
web_url: c.web_url,
|
|
|
|
|
html_content: c.html_content,
|
|
|
|
|
cooling_timeout_seconds: c.cooling_timeout_seconds,
|
|
|
|
|
})),
|
|
|
|
|
};
|
|
|
|
|
});
|
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,
|
|
|
|
|
})),
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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: {
|
|
|
|
|
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,
|
|
|
|
|
},
|
2026-05-10 01:12:07 +00:00
|
|
|
layouts: bundleLayouts,
|
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-10 01:12:07 +00:00
|
|
|
version: "",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
bundle.version = createHash("sha256")
|
|
|
|
|
.update(JSON.stringify(bundle))
|
|
|
|
|
.digest("hex");
|
|
|
|
|
|
|
|
|
|
return bundle;
|
|
|
|
|
}
|