mirror of
https://github.com/BetterCorp/BetterFrame.git
synced 2026-05-26 19:06:34 +00:00
fix(displays): use kiosk-local indices
Kiosk heartbeat reports local display positions so the server can sync physical outputs without consuming global display indices. Migrate displays.index away from global uniqueness because display numbering is only meaningful within a kiosk.
This commit is contained in:
parent
54d4dfefa8
commit
7c88d7f733
4 changed files with 83 additions and 11 deletions
|
|
@ -200,8 +200,8 @@ pub fn heartbeat(
|
|||
hw: &crate::hwmon::HwInfo,
|
||||
) {
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let display_info: Vec<_> = displays.iter().map(|(name, w, h)| {
|
||||
serde_json::json!({ "name": name, "width_px": w, "height_px": h })
|
||||
let display_info: Vec<_> = displays.iter().enumerate().map(|(index, (name, w, h))| {
|
||||
serde_json::json!({ "index": index, "name": name, "width_px": w, "height_px": h })
|
||||
}).collect();
|
||||
let _ = client
|
||||
.post(format!("{server}/api/kiosk/heartbeat"))
|
||||
|
|
|
|||
|
|
@ -258,7 +258,7 @@ function registerKioskRoutes(
|
|||
bundle_version?: string;
|
||||
kiosk_app_version?: string;
|
||||
os_version?: string;
|
||||
displays?: Array<{ name: string; width_px: number; height_px: number }>;
|
||||
displays?: Array<{ index?: number; name: string; width_px: number; height_px: number }>;
|
||||
cpu_temp_c?: number | null;
|
||||
fan_rpm?: number | null;
|
||||
fan_pwm?: number | null;
|
||||
|
|
@ -276,24 +276,45 @@ function registerKioskRoutes(
|
|||
// Sync displays reported by the kiosk
|
||||
if (Array.isArray(body?.displays)) {
|
||||
const existing = repo.listDisplaysForKiosk(kiosk.id);
|
||||
for (const reported of body.displays) {
|
||||
const match = existing.find((d) => d.name.endsWith(reported.name));
|
||||
const seenDisplayIds = new Set<number>();
|
||||
for (const [position, reported] of body.displays.entries()) {
|
||||
const reportedIndex = Number.isInteger(reported.index) && reported.index! >= 0
|
||||
? reported.index!
|
||||
: position;
|
||||
const match = existing.find((d) => d.name.endsWith(reported.name))
|
||||
?? existing.find((d) => d.index === reportedIndex);
|
||||
if (match) {
|
||||
if (match.width_px !== reported.width_px || match.height_px !== reported.height_px) {
|
||||
seenDisplayIds.add(match.id);
|
||||
if (
|
||||
match.name !== reported.name
|
||||
|| match.index !== reportedIndex
|
||||
|| match.width_px !== reported.width_px
|
||||
|| match.height_px !== reported.height_px
|
||||
) {
|
||||
repo.updateDisplay(match.id, {
|
||||
name: reported.name,
|
||||
index: reportedIndex,
|
||||
width_px: reported.width_px,
|
||||
height_px: reported.height_px,
|
||||
} as any);
|
||||
}
|
||||
} else {
|
||||
// New display — create it
|
||||
repo.createDisplayForKiosk(kiosk.id, {
|
||||
const created = repo.createDisplayForKiosk(kiosk.id, {
|
||||
name: reported.name,
|
||||
index: reportedIndex,
|
||||
width_px: reported.width_px,
|
||||
height_px: reported.height_px,
|
||||
});
|
||||
seenDisplayIds.add(created.id);
|
||||
}
|
||||
}
|
||||
for (const display of existing) {
|
||||
if (seenDisplayIds.has(display.id) || !display.is_enabled) continue;
|
||||
if (!display.name.endsWith(" HDMI-0")) continue;
|
||||
if (repo.listLayoutsForDisplay(display.id).length > 0) continue;
|
||||
repo.updateDisplay(display.id, { is_enabled: false } as any);
|
||||
}
|
||||
}
|
||||
|
||||
return { ok: true, now: new Date().toISOString() };
|
||||
|
|
|
|||
|
|
@ -657,4 +657,56 @@ export const MIGRATIONS: readonly MigrationEntry[] = [
|
|||
(db: DatabaseSync) => {
|
||||
addColumnIfNotExists(db, "displays", "is_enabled", "INTEGER NOT NULL DEFAULT 1");
|
||||
},
|
||||
|
||||
// ---- displays.index is local to the kiosk, not globally unique -------------
|
||||
(db: DatabaseSync) => {
|
||||
const row = db
|
||||
.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'displays'")
|
||||
.get() as { sql?: string } | undefined;
|
||||
if (!row?.sql || !row.sql.includes('"index" INTEGER NOT NULL UNIQUE')) return;
|
||||
|
||||
db.exec("PRAGMA foreign_keys = OFF");
|
||||
db.exec(`
|
||||
CREATE TABLE displays_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
"index" INTEGER NOT NULL,
|
||||
is_primary INTEGER NOT NULL DEFAULT 0,
|
||||
width_px INTEGER NOT NULL DEFAULT 1920,
|
||||
height_px INTEGER NOT NULL DEFAULT 1080,
|
||||
default_layout_id INTEGER,
|
||||
idle_timeout_seconds INTEGER NOT NULL DEFAULT 600,
|
||||
sleep_timeout_seconds INTEGER NOT NULL DEFAULT 1800,
|
||||
cec_enabled INTEGER NOT NULL DEFAULT 1,
|
||||
cec_device_path TEXT,
|
||||
cec_logical_address INTEGER,
|
||||
desired_power_state TEXT NOT NULL DEFAULT 'follow_layout'
|
||||
CHECK(desired_power_state IN ('follow_layout', 'on', 'standby')),
|
||||
state_check_enabled INTEGER NOT NULL DEFAULT 0,
|
||||
state_check_interval_seconds INTEGER NOT NULL DEFAULT 60,
|
||||
kiosk_id INTEGER REFERENCES kiosks(id) ON DELETE SET NULL,
|
||||
is_enabled INTEGER NOT NULL DEFAULT 1
|
||||
) STRICT;
|
||||
|
||||
INSERT INTO displays_new (
|
||||
id, name, "index", is_primary, width_px, height_px, default_layout_id,
|
||||
idle_timeout_seconds, sleep_timeout_seconds, cec_enabled, cec_device_path,
|
||||
cec_logical_address, desired_power_state, state_check_enabled,
|
||||
state_check_interval_seconds, kiosk_id, is_enabled
|
||||
)
|
||||
SELECT
|
||||
id, name, "index", is_primary, width_px, height_px, default_layout_id,
|
||||
idle_timeout_seconds, sleep_timeout_seconds, cec_enabled, cec_device_path,
|
||||
cec_logical_address, desired_power_state, state_check_enabled,
|
||||
state_check_interval_seconds, kiosk_id, is_enabled
|
||||
FROM displays;
|
||||
|
||||
DROP TABLE displays;
|
||||
ALTER TABLE displays_new RENAME TO displays;
|
||||
CREATE INDEX IF NOT EXISTS idx_displays_kiosk ON displays(kiosk_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_displays_kiosk_index
|
||||
ON displays(kiosk_id, "index");
|
||||
`);
|
||||
db.exec("PRAGMA foreign_keys = ON");
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -373,8 +373,7 @@ export class Repository {
|
|||
width_px?: number;
|
||||
height_px?: number;
|
||||
}): Display {
|
||||
// Find next available index
|
||||
const idx = input.index ?? this.nextDisplayIndex();
|
||||
const idx = input.index ?? this.nextDisplayIndexForKiosk(kioskId);
|
||||
const result = this.prep(
|
||||
`INSERT INTO displays (name, "index", is_primary, kiosk_id, width_px, height_px)
|
||||
VALUES (?, ?, 0, ?, ?, ?)`,
|
||||
|
|
@ -399,8 +398,8 @@ export class Repository {
|
|||
return rs.map((r) => rowToDisplay(r as Record<string, unknown>));
|
||||
}
|
||||
|
||||
private nextDisplayIndex(): number {
|
||||
const r = this.prep('SELECT MAX("index") AS m FROM displays').get() as { m: number | null } | undefined;
|
||||
private nextDisplayIndexForKiosk(kioskId: number): number {
|
||||
const r = this.prep('SELECT MAX("index") AS m FROM displays WHERE kiosk_id = ?').get(kioskId) as { m: number | null } | undefined;
|
||||
return (r?.m ?? -1) + 1;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue