this is a big slop that might backfire lol... new config system and UI!

This commit is contained in:
legop3
2026-09-14 02:31:12 -04:00
parent 17b1404157
commit bfdb6555d8
108 changed files with 3212 additions and 701 deletions
@@ -0,0 +1,25 @@
// Bandwidth-Savings Configuration
// Purpose: Defines the server-owned live-video and snapshot policy interpreted by this helper.
// Scope: Exports configuration metadata without reading sessions or calculating policy.
const { strictObject, string, boolean, integer } = require('../configuration/schemaHelpers');
module.exports = {
key: 'bandwidthSavings',
defaultValue: {
multiTabProtection: 'verifiedOnly',
pauseHiddenRoverVideo: false,
nonTurnVideo: { mode: 'snapshots', userThreshold: 0 },
externalSpectatorVideo: 'snapshots',
externalSpectatorAccess: 'on',
},
schema: strictObject({
multiTabProtection: string({ enum: ['allowed', 'verifiedOnly', 'notAllowed'] }),
pauseHiddenRoverVideo: boolean(),
nonTurnVideo: strictObject({
mode: string({ enum: ['snapshots', 'live'] }),
userThreshold: integer({ minimum: 0, maximum: 100000 }),
}, { required: ['mode', 'userThreshold'] }),
externalSpectatorVideo: string({ enum: ['snapshots', 'live'] }),
externalSpectatorAccess: string({ enum: ['off', 'on', 'verifiedOnly', 'admin'] }),
}, { title: 'Bandwidth savings', required: ['multiTabProtection', 'pauseHiddenRoverVideo', 'nonTurnVideo', 'externalSpectatorVideo', 'externalSpectatorAccess'] }),
};
+9 -9
View File
@@ -1,8 +1,8 @@
// Bandwidth Savings Helper
// Purpose: Normalizes bandwidth-saving config and exposes tiny policy helpers.
// Scope: Keeps cross-service video/tab/spectator decisions consistent without
// making individual services know raw YAML defaults or legacy config shapes.
const { loadConfig } = require('./configLoader');
// making individual services duplicate the validated database configuration contract.
const { loadConfig } = require('../configuration');
const MULTI_TAB_MODES = new Set(['allowed', 'verifiedOnly', 'notAllowed']);
const VIDEO_MODES = new Set(['snapshots', 'live']);
@@ -21,9 +21,9 @@ const DEFAULT_BANDWIDTH_SAVINGS = Object.freeze({
function normalizeEnum(value, allowed, fallback) {
/*
Config files are hand-edited on the server, so a typo should not crash the
process or silently broaden access. Each option falls back to the current
conservative behavior unless it exactly matches a known value.
Tests and direct helper callers can still supply incomplete objects even
though the database rejects invalid persisted values. Conservative fallback
here keeps policy behavior safe at that secondary boundary.
*/
const normalized = typeof value === 'string' ? value.trim() : '';
return allowed.has(normalized) ? normalized : fallback;
@@ -31,9 +31,9 @@ function normalizeEnum(value, allowed, fallback) {
function normalizeBoolean(value, fallback) {
/*
YAML booleans must stay real booleans. Treating strings such as "false" as
truthy would silently enable a bandwidth policy that the operator intended
to disable, so invalid values fall back to the documented server default.
Treating strings such as "false" as truthy would silently enable a policy.
Persisted values are schema-validated, while this guard protects direct
helper calls and focused tests from the same JavaScript coercion trap.
*/
return typeof value === 'boolean' ? value : fallback;
}
@@ -83,7 +83,7 @@ function buildBandwidthSavingsPolicy(config = loadConfig()) {
function getBandwidthSavingsPolicy() {
/*
loadConfig() is cached by configLoader, so rebuilding this small object per
loadConfig() is cached by configuration service, so rebuilding this small object per
caller is cheap while still letting tests pass explicit config objects into
buildBandwidthSavingsPolicy().
*/
-22
View File
@@ -1,22 +0,0 @@
// Config Loader Helper
// Purpose: Loads and validates YAML server configuration from configured paths. Scope: Provides normalized config access with sane defaults and cache behavior.
const fs = require('fs');
const path = require('path');
const yaml = require('js-yaml');
const CONFIG_PATH = process.env.SERVER_CONFIG || path.join(__dirname, '..', '..', 'config.yaml');
let cachedConfig;
function loadConfig() {
if (cachedConfig) {
return cachedConfig;
}
const file = fs.readFileSync(CONFIG_PATH, 'utf8');
cachedConfig = yaml.load(file);
return cachedConfig;
}
module.exports = {
loadConfig,
};
-126
View File
@@ -1,126 +0,0 @@
// Feature Flags Helper
// Purpose: Normalizes optional server feature availability from config in one place.
// Scope: Keeps hardware/social visibility decisions out of individual UI panels and service callers.
const { loadConfig } = require('./configLoader');
function asBoolean(value, fallback = false) {
/*
Optional feature config is intentionally explicit. A missing `enabled` flag
means "off" for specialty hardware, which makes a fresh public install a
rover-only server until the operator opts into extra devices.
*/
if (typeof value === 'boolean') return value;
return fallback;
}
function asTrimmedString(value) {
return typeof value === 'string' ? value.trim() : '';
}
function getRoomCameraEntries(config) {
const raw = config.roomCameras;
/*
The public config uses `{ enabled, cameras }` so the feature gate is obvious.
Accepting the old array shape here keeps the rest of the server from needing
to know which shape the local config file currently uses.
*/
if (Array.isArray(raw)) return raw;
if (raw && typeof raw === 'object' && Array.isArray(raw.cameras)) return raw.cameras;
return [];
}
function getConfiguredSocials(config) {
/*
Social links have an explicit feature switch. Entries under `links` are just
available data; they do not enable the Links panel by existing.
*/
const links = config.socials && typeof config.socials === 'object' ? config.socials.links : [];
return Array.isArray(links)
? links.filter((entry) => asTrimmedString(entry?.url))
: [];
}
function buildFeatureFlags(config = loadConfig()) {
const homeAssistantConfig = config.homeAssistant || {};
const roomCameraConfig = config.roomCameras || {};
const kinectConfig = config.kinect || {};
const buttonBoxConfig = config.buttonBox || {};
const barcodeScannerConfig = config.barcodeScanner || {};
const balanceBoardConfig = config.balanceBoard || {};
const barcodeGamesConfig = config.barcodeGames || {};
const socialsConfig = config.socials || {};
const interInstanceConfig = config.interInstance || {};
const ptzCameraConfig = config.ptzCamera || {};
const discordConfig = config.discord || {};
const fleetReportsConfig = config.fleetReports || {};
const homeAssistant = Boolean(
asBoolean(homeAssistantConfig.enabled) &&
asTrimmedString(homeAssistantConfig.url) &&
asTrimmedString(homeAssistantConfig.token),
);
const roomCameraEntries = getRoomCameraEntries(config);
const roomCamerasEnabled = Array.isArray(config.roomCameras)
? false
: asBoolean(roomCameraConfig.enabled);
const barcodeScanner = asBoolean(barcodeScannerConfig.enabled);
return {
homeAssistant,
roomCameras: Boolean(roomCamerasEnabled && roomCameraEntries.length),
kinect: asBoolean(kinectConfig.enabled),
buttonBox: asBoolean(buttonBoxConfig.enabled),
barcodeScanner,
// The worker performs its own runtime availability reporting. Advertising
// the feature from the explicit config switch lets the UI show useful
// commissioning and hardware-error states even before a board is paired.
balanceBoard: asBoolean(balanceBoardConfig.enabled),
barcodeGames: Boolean(barcodeScanner && asBoolean(barcodeGamesConfig.enabled)),
lift: Boolean(
homeAssistant &&
asBoolean(homeAssistantConfig.lift?.enabled) &&
asTrimmedString(homeAssistantConfig.lift?.upSwitch) &&
asTrimmedString(homeAssistantConfig.lift?.downSwitch),
),
neato: Boolean(
homeAssistant &&
asBoolean(homeAssistantConfig.neato?.enabled) &&
asTrimmedString(homeAssistantConfig.neato?.device),
),
socials: Boolean(asBoolean(socialsConfig.enabled) && getConfiguredSocials(config).length > 0),
interInstance: asBoolean(interInstanceConfig.enabled),
ptzCamera: Boolean(
asBoolean(ptzCameraConfig.enabled) &&
asTrimmedString(ptzCameraConfig.host) &&
asTrimmedString(ptzCameraConfig.username) &&
asTrimmedString(ptzCameraConfig.password),
),
/*
Discord is an optional transport, not a prerequisite for chat commands.
Requiring both the explicit switch and a token prevents an old token from
silently enabling external connections on installations that have chosen
to run without the integration.
*/
discord: Boolean(asBoolean(discordConfig.enabled) && asTrimmedString(discordConfig.token)),
// Fleet reports are deliberately controlled by one explicit server switch.
// Storage contents, Discord availability, or historical database files must
// never cause the reporting UI to appear on an installation that has not
// opted into the collector.
fleetReports: asBoolean(fleetReportsConfig.enabled),
};
}
function getFeatureFlags() {
return buildFeatureFlags(loadConfig());
}
function isFeatureEnabled(featureName) {
return Boolean(getFeatureFlags()[featureName]);
}
module.exports = {
buildFeatureFlags,
getFeatureFlags,
isFeatureEnabled,
getRoomCameraEntries,
getConfiguredSocials,
};
+1 -1
View File
@@ -1,7 +1,7 @@
// Site Metadata Helper
// Purpose: Resolves the public name, description, and colors used before the web UI starts.
// Scope: Keeps document/PWA branding server-rendered and independent of Socket.IO session state.
const { loadConfig } = require('./configLoader');
const { loadConfig } = require('../configuration');
const DEFAULT_SITE_METADATA = Object.freeze({
name: 'Multi Roomba Rover',