From b72e964433c012f8e3f8ba468c1c0a2e0085f558 Mon Sep 17 00:00:00 2001 From: ScreenTinker Date: Thu, 9 Jul 2026 19:55:28 -0500 Subject: [PATCH] feat(dashboard): surface per-device settings PIN + backfill existing fleet (#152) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The server provisions a unique settings-menu PIN per device, but nothing surfaced it — leaving the on-device hidden settings menu effectively unopenable. Show the PIN on the device Info tab (native players only), with i18n across 6 locales. Also backfill a unique 6-digit PIN for already-paired devices that predate the settings_pin column, so the existing fleet isn't locked out (delivered on their next reconnect via the existing device:paired re-send). Idempotent UPDATE. Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend/js/i18n/de.js | 2 ++ frontend/js/i18n/en.js | 2 ++ frontend/js/i18n/es.js | 2 ++ frontend/js/i18n/fr.js | 2 ++ frontend/js/i18n/it.js | 2 ++ frontend/js/i18n/pt.js | 2 ++ frontend/js/views/device-detail.js | 5 +++++ server/db/database.js | 6 ++++++ 8 files changed, 23 insertions(+) diff --git a/frontend/js/i18n/de.js b/frontend/js/i18n/de.js index 9e25d48..bec630d 100644 --- a/frontend/js/i18n/de.js +++ b/frontend/js/i18n/de.js @@ -254,6 +254,8 @@ export default { 'device.info.uptime': 'Betriebszeit', 'device.info.android_version': 'Android-Version', 'device.info.app_version': 'App-Version', + 'device.info.settings_pin': 'Einstellungs-PIN', + 'device.info.settings_pin_hint': 'Geräte-Einstellungsmenü (2× Zurück)', 'device.info.screen_resolution': 'Bildschirmauflösung', 'device.info.ram': 'RAM', 'device.info.cpu_usage': 'CPU-Auslastung', diff --git a/frontend/js/i18n/en.js b/frontend/js/i18n/en.js index cce3843..55091f5 100644 --- a/frontend/js/i18n/en.js +++ b/frontend/js/i18n/en.js @@ -296,6 +296,8 @@ export default { 'device.info.uptime': 'Uptime', 'device.info.android_version': 'Android Version', 'device.info.app_version': 'App Version', + 'device.info.settings_pin': 'Settings PIN', + 'device.info.settings_pin_hint': 'On-device settings menu (2× Back)', 'device.info.screen_resolution': 'Screen Resolution', 'device.info.ram': 'RAM', 'device.info.cpu_usage': 'CPU Usage', diff --git a/frontend/js/i18n/es.js b/frontend/js/i18n/es.js index edd7bc3..777bf39 100644 --- a/frontend/js/i18n/es.js +++ b/frontend/js/i18n/es.js @@ -253,6 +253,8 @@ export default { 'device.info.uptime': 'Tiempo activo', 'device.info.android_version': 'Versión de Android', 'device.info.app_version': 'Versión de la app', + 'device.info.settings_pin': 'PIN de ajustes', + 'device.info.settings_pin_hint': 'Menú de ajustes del dispositivo (2× Atrás)', 'device.info.screen_resolution': 'Resolución de pantalla', 'device.info.ram': 'RAM', 'device.info.cpu_usage': 'Uso de CPU', diff --git a/frontend/js/i18n/fr.js b/frontend/js/i18n/fr.js index 6eda700..bbb6016 100644 --- a/frontend/js/i18n/fr.js +++ b/frontend/js/i18n/fr.js @@ -254,6 +254,8 @@ export default { 'device.info.uptime': 'Disponibilité', 'device.info.android_version': 'Version d\'Android', 'device.info.app_version': 'Version de l\'app', + 'device.info.settings_pin': 'Code PIN des réglages', + 'device.info.settings_pin_hint': 'Menu des réglages sur l\'appareil (2× Retour)', 'device.info.screen_resolution': 'Résolution', 'device.info.ram': 'RAM', 'device.info.cpu_usage': 'Utilisation CPU', diff --git a/frontend/js/i18n/it.js b/frontend/js/i18n/it.js index 4b5dd1d..bbaee53 100644 --- a/frontend/js/i18n/it.js +++ b/frontend/js/i18n/it.js @@ -270,6 +270,8 @@ export default { 'device.info.uptime': 'Tempo di attività', 'device.info.android_version': 'Versione Android', 'device.info.app_version': 'Versione App', + 'device.info.settings_pin': 'PIN impostazioni', + 'device.info.settings_pin_hint': 'Menu impostazioni sul dispositivo (2× Indietro)', 'device.info.screen_resolution': 'Risoluzione Schermo', 'device.info.ram': 'RAM', 'device.info.cpu_usage': 'Utilizzo CPU', diff --git a/frontend/js/i18n/pt.js b/frontend/js/i18n/pt.js index 5dfde5f..db9e87f 100644 --- a/frontend/js/i18n/pt.js +++ b/frontend/js/i18n/pt.js @@ -254,6 +254,8 @@ export default { 'device.info.uptime': 'Tempo ativo', 'device.info.android_version': 'Versão do Android', 'device.info.app_version': 'Versão do app', + 'device.info.settings_pin': 'PIN de configurações', + 'device.info.settings_pin_hint': 'Menu de configurações no dispositivo (2× Voltar)', 'device.info.screen_resolution': 'Resolução', 'device.info.ram': 'RAM', 'device.info.cpu_usage': 'Uso de CPU', diff --git a/frontend/js/views/device-detail.js b/frontend/js/views/device-detail.js index a505c4a..33499a7 100644 --- a/frontend/js/views/device-detail.js +++ b/frontend/js/views/device-detail.js @@ -305,6 +305,11 @@ async function loadDevice(deviceId, activeTab = null) {
${t('device.info.app_version')}
${device.app_version || '--'}
+
+
${t('device.info.settings_pin')}
+
${device.settings_pin || '--'}
+
${t('device.info.settings_pin_hint')}
+
` : ''}
${t('device.info.screen_resolution')}
diff --git a/server/db/database.js b/server/db/database.js index e802b3b..9bbc70f 100644 --- a/server/db/database.js +++ b/server/db/database.js @@ -267,6 +267,12 @@ const migrations = [ // settings_pin: 6-digit PIN for the in-app hidden settings menu, provisioned by // the server during pairing so each device gets a unique PIN (never a hardcoded default). "ALTER TABLE devices ADD COLUMN settings_pin TEXT", + // Backfill a unique 6-digit PIN for already-paired devices that predate the + // settings_pin column (their next reconnect re-sends device:paired with it, so + // the existing fleet isn't locked out of the on-device menu). Idempotent: the + // IS NULL guard means it only ever touches un-provisioned rows. Unpaired rows + // (user_id IS NULL) are skipped — they get a PIN when they pair. + "UPDATE devices SET settings_pin = CAST(abs(random()) % 900000 + 100000 AS TEXT) WHERE settings_pin IS NULL AND user_id IS NOT NULL", // #150: fingerprint-keyed device settings that SURVIVE device-row deletion, so a // delete + re-pair (MDM churn) restores orientation/name/playlist/etc for the SAME // physical device instead of silently resetting to defaults. NO FK to devices -> it