mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 09:31:20 -04:00
this is a big slop that might backfire lol... new config system and UI!
This commit is contained in:
@@ -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'] }),
|
||||
};
|
||||
@@ -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().
|
||||
*/
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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,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',
|
||||
|
||||
Reference in New Issue
Block a user