mirror of
https://github.com/screentinker/screentinker.git
synced 2026-08-13 13:53:12 -06:00
1. devices_connected (always on, never gated): a top-level /api/status field next to loop_lag = LIVE WS socket count from the heartbeat connection map (getConnectedCount), NOT devices.status='online' (which lags by the offline-timeout). The single most-glanced operational number, so it can't disappear when debug is off. Also dropped 4 dead per-poll COUNT(*) queries the route computed but never returned. 2. debug block behind an admin flag: new minimal app_settings KV table (none existed; ai_settings is per-workspace, white_labels is branding) + lib/app-settings.js (cached, refresh-on-write so status polls read a cached boolean, not a DB row). routes/status.js includes `debug` ONLY when status_debug_enabled is on (persisted value overrides the STATUS_DEBUG_ENABLED env default); when off the key is omitted entirely. 3. Admin toggle: GET/PUT /api/admin/status-debug (requirePlatformAdmin, mirrors the branding endpoints) + a checkbox in the Admin tab "Status endpoint" section (mirrors the branding checkbox). Takes effect on the next poll, no restart. Tests: devices_connected always present+numeric and rises with a live socket (booted + socket.io-client); debug present by default, admin flips OFF -> key omitted (loop_lag + devices_connected remain) -> ON again, no restart; non-admin 403, anon 401; unit coverage for getConnectedCount + app-settings default/override. Suite 289/289. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
42 lines
1.6 KiB
JavaScript
42 lines
1.6 KiB
JavaScript
'use strict';
|
|
// #146 — minimal global key/value settings for admin-toggleable RUNTIME flags. No
|
|
// generic settings table existed (ai_settings is per-workspace, white_labels is
|
|
// branding), so this adds one (app_settings). Values are CACHED in memory and refreshed
|
|
// on write, so a hot path — e.g. /api/status, polled under load — reads a cached boolean,
|
|
// never a per-poll DB read.
|
|
|
|
const { db } = require('../db/database');
|
|
|
|
const cache = new Map(); // key -> string value
|
|
let loaded = false;
|
|
|
|
function loadAll() {
|
|
cache.clear();
|
|
try { for (const r of db.prepare('SELECT key, value FROM app_settings').all()) cache.set(r.key, r.value); } catch (_) { /* table may not exist yet */ }
|
|
loaded = true;
|
|
}
|
|
|
|
function get(key, dflt) {
|
|
if (!loaded) loadAll();
|
|
return cache.has(key) ? cache.get(key) : dflt;
|
|
}
|
|
|
|
// Persist + refresh the cache so the change takes effect immediately (no restart).
|
|
function set(key, value) {
|
|
const v = String(value);
|
|
db.prepare("INSERT INTO app_settings (key, value, updated_at) VALUES (?, ?, strftime('%s','now')) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at").run(key, v);
|
|
cache.set(key, v);
|
|
loaded = true;
|
|
}
|
|
|
|
// Boolean read with an env-default fallback: the PERSISTED value overrides once set,
|
|
// else the caller's env default applies.
|
|
function getBool(key, envDefault) {
|
|
const v = get(key, undefined);
|
|
if (v === undefined) return !!envDefault;
|
|
return v === 'true' || v === '1';
|
|
}
|
|
function setBool(key, value) { set(key, value ? 'true' : 'false'); }
|
|
|
|
module.exports = { get, set, getBool, setBool, __reload: loadAll };
|