Skip to content

Commit 7f9e025

Browse files
Disable runtime toggles by default and guard update migration
1 parent 47fd36c commit 7f9e025

9 files changed

Lines changed: 84 additions & 30 deletions

File tree

207 KB
Binary file not shown.

folderview.plus.plg

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,22 @@
66
<!ENTITY launch "Settings/FolderViewPlus">
77
<!ENTITY plugdir "/usr/local/emhttp/plugins/&name;">
88
<!ENTITY pluginURL "https://raw.githubusercontent.com/&github;/main/folderview.plus.plg">
9-
<!ENTITY version "2026.03.07.1">
10-
<!ENTITY md5 "aa4b59a70cfe7fcc7f232c2cc439b4f6">
9+
<!ENTITY version "2026.03.07.2">
10+
<!ENTITY md5 "3856e22407ecf9a2fe079b7c00b5e855">
1111
]>
1212

1313
<PLUGIN name="&name;" author="&author;" version="&version;" launch="&launch;" pluginURL="&pluginURL;" icon="folder-open-o" support="https://github.com/alexphillips-dev/FolderView-Plus/issues" min="7.0.0">
1414
<CHANGES>
1515

16+
###2026.03.07.2
17+
- Runtime defaults hardening:
18+
- set `Live auto-refresh` default to OFF,
19+
- set `Performance mode` default to OFF,
20+
- set `Lazy previews` default to OFF.
21+
- Add runtime preference schema guard so older installs are migrated to safe OFF defaults on update instead of auto-starting runtime features.
22+
- Tighten runtime checks so lazy-preview behavior only activates when explicitly enabled.
23+
- Update settings rendering to treat runtime toggles as opt-in (`true` only), not enabled-by-missing.
24+
1625
###2026.03.07.1
1726
- Fix folder editor row alignment so settings blocks are left-anchored with section headers instead of centered.
1827
- Keep nested preview/advanced controls aligned to the same left column for consistent spacing.

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/scripts/dashboard.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ const utils = window.FolderViewPlusUtils || {
2121
sortMode: 'created',
2222
manualOrder: [],
2323
autoRules: [],
24-
liveRefreshEnabled: true,
24+
runtimePrefsSchema: 2,
25+
liveRefreshEnabled: false,
2526
liveRefreshSeconds: 20,
2627
performanceMode: false,
27-
lazyPreviewEnabled: true,
28+
lazyPreviewEnabled: false,
2829
lazyPreviewThreshold: 30
2930
}),
3031
getAutoRuleMatches: () => [],
@@ -352,7 +353,7 @@ const createFolderDocker = (folder, id, position, order, containersInfo, folders
352353
type: 'docker'
353354
}).filter((name) => !folder.containers.includes(name)));
354355

355-
const lazyPreviewEnabled = folderTypePrefs?.docker?.lazyPreviewEnabled !== false;
356+
const lazyPreviewEnabled = folderTypePrefs?.docker?.lazyPreviewEnabled === true;
356357
const lazyPreviewThreshold = Number(folderTypePrefs?.docker?.lazyPreviewThreshold || 30);
357358
const isExpandedByDefault = folder?.settings?.expand_dashboard === true;
358359
const lazyPreviewActive = lazyPreviewEnabled
@@ -601,7 +602,7 @@ const createFolderVM = (folder, id, position, order, vmInfo, foldersDone) => {
601602
type: 'vm'
602603
}).filter((name) => !folder.containers.includes(name)));
603604

604-
const lazyPreviewEnabled = folderTypePrefs?.vm?.lazyPreviewEnabled !== false;
605+
const lazyPreviewEnabled = folderTypePrefs?.vm?.lazyPreviewEnabled === true;
605606
const lazyPreviewThreshold = Number(folderTypePrefs?.vm?.lazyPreviewThreshold || 30);
606607
const isExpandedByDefault = folder?.settings?.expand_dashboard === true;
607608
const lazyPreviewActive = lazyPreviewEnabled

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/scripts/docker.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ const utils = window.FolderViewPlusUtils || {
2323
manualOrder: [],
2424
autoRules: [],
2525
badges: { running: true, stopped: false, updates: true },
26-
liveRefreshEnabled: true,
26+
runtimePrefsSchema: 2,
27+
liveRefreshEnabled: false,
2728
liveRefreshSeconds: 20,
2829
performanceMode: false,
29-
lazyPreviewEnabled: true,
30+
lazyPreviewEnabled: false,
3031
lazyPreviewThreshold: 30
3132
}),
3233
getAutoRuleMatches: () => [],
@@ -328,7 +329,7 @@ const createFolder = (folder, id, positionInMainOrder, liveOrderArray, container
328329
console.log(`[FV3_DEBUG] createFolder (id: ${id}): Containers matched by auto rules:`, ruleMatches);
329330
console.log(`[FV3_DEBUG] createFolder (id: ${id}): Final combined list of containers for folder processing (combinedContainers):`, [...combinedContainers]);
330331
}
331-
const lazyPreviewEnabled = folderTypePrefs?.lazyPreviewEnabled !== false;
332+
const lazyPreviewEnabled = folderTypePrefs?.lazyPreviewEnabled === true;
332333
const lazyPreviewThreshold = Number(folderTypePrefs?.lazyPreviewThreshold || 30);
333334
const isExpandedByDefault = folder?.settings?.expand_tab === true;
334335
const lazyPreviewActive = lazyPreviewEnabled

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/scripts/folderviewplus.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -737,10 +737,10 @@ const renderBadgeToggles = (type) => {
737737

738738
const renderRuntimeControls = (type) => {
739739
const prefs = utils.normalizePrefs(prefsByType[type]);
740-
$(`#${type}-live-refresh-enabled`).prop('checked', prefs.liveRefreshEnabled !== false);
740+
$(`#${type}-live-refresh-enabled`).prop('checked', prefs.liveRefreshEnabled === true);
741741
$(`#${type}-live-refresh-seconds`).val(String(prefs.liveRefreshSeconds || 20));
742742
$(`#${type}-performance-mode`).prop('checked', prefs.performanceMode === true);
743-
$(`#${type}-lazy-preview-enabled`).prop('checked', prefs.lazyPreviewEnabled !== false);
743+
$(`#${type}-lazy-preview-enabled`).prop('checked', prefs.lazyPreviewEnabled === true);
744744
$(`#${type}-lazy-preview-threshold`).val(String(prefs.lazyPreviewThreshold || 30));
745745
};
746746

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/scripts/folderviewplus.utils.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
];
1717
const RULE_EFFECTS = ['include', 'exclude'];
1818
const LEGACY_FOLDER_LABEL_KEYS = ['folderview.plus', 'folder.view3', 'folder.view2', 'folder.view'];
19+
const RUNTIME_PREFS_SCHEMA = 2;
1920
const DEFAULT_FOLDER_STATUS_COLORS = {
2021
started: '#ffffff',
2122
paused: '#b8860b',
@@ -126,17 +127,20 @@
126127
retention: clampNumber(backupScheduleRaw.retention, 1, 200, defaultSchedule.retention),
127128
lastRunAt: typeof backupScheduleRaw.lastRunAt === 'string' ? backupScheduleRaw.lastRunAt : ''
128129
};
129-
const liveRefreshEnabled = !Object.prototype.hasOwnProperty.call(incoming, 'liveRefreshEnabled') ? true : incoming.liveRefreshEnabled !== false;
130+
const runtimePrefsSchema = clampNumber(incoming.runtimePrefsSchema, 0, RUNTIME_PREFS_SCHEMA, 0);
131+
const runtimePrefsReady = runtimePrefsSchema >= RUNTIME_PREFS_SCHEMA;
132+
const liveRefreshEnabled = runtimePrefsReady ? incoming.liveRefreshEnabled === true : false;
130133
const liveRefreshSeconds = clampNumber(incoming.liveRefreshSeconds, 10, 300, 20);
131-
const performanceMode = incoming.performanceMode === true;
132-
const lazyPreviewEnabled = !Object.prototype.hasOwnProperty.call(incoming, 'lazyPreviewEnabled') ? true : incoming.lazyPreviewEnabled !== false;
134+
const performanceMode = runtimePrefsReady ? incoming.performanceMode === true : false;
135+
const lazyPreviewEnabled = runtimePrefsReady ? incoming.lazyPreviewEnabled === true : false;
133136
const lazyPreviewThreshold = clampNumber(incoming.lazyPreviewThreshold, 10, 200, 30);
134137

135138
return {
136139
sortMode,
137140
manualOrder,
138141
autoRules,
139142
badges,
143+
runtimePrefsSchema: RUNTIME_PREFS_SCHEMA,
140144
liveRefreshEnabled,
141145
liveRefreshSeconds,
142146
performanceMode,
@@ -735,6 +739,7 @@
735739
RULE_KINDS,
736740
RULE_EFFECTS,
737741
LEGACY_FOLDER_LABEL_KEYS,
742+
RUNTIME_PREFS_SCHEMA,
738743
DEFAULT_FOLDER_STATUS_COLORS,
739744
normalizeFolderMap,
740745
normalizePrefs,

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/scripts/vm.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ const utils = window.FolderViewPlusUtils || {
2222
manualOrder: [],
2323
autoRules: [],
2424
badges: { running: true, stopped: false, updates: true },
25-
liveRefreshEnabled: true,
25+
runtimePrefsSchema: 2,
26+
liveRefreshEnabled: false,
2627
liveRefreshSeconds: 20,
2728
performanceMode: false,
28-
lazyPreviewEnabled: true,
29+
lazyPreviewEnabled: false,
2930
lazyPreviewThreshold: 30
3031
}),
3132
getAutoRuleMatches: () => [],
@@ -213,7 +214,7 @@ const createFolder = (folder, id, position, order, vmInfo, foldersDone) => {
213214
}).filter((vmName) => !folder.containers.includes(vmName));
214215
folder.containers = folder.containers.concat(ruleMatches);
215216

216-
const lazyPreviewEnabled = folderTypePrefs?.lazyPreviewEnabled !== false;
217+
const lazyPreviewEnabled = folderTypePrefs?.lazyPreviewEnabled === true;
217218
const lazyPreviewThreshold = Number(folderTypePrefs?.lazyPreviewThreshold || 30);
218219
const isExpandedByDefault = folder?.settings?.expand_tab === true;
219220
const lazyPreviewActive = lazyPreviewEnabled

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/server/lib.php

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ function fv3_get_tailscale_fqdn_from_container(string $containerName): ?string {
7878
const FVPLUS_DIAGNOSTICS_DEFAULT_PRIVACY = 'sanitized';
7979
const FVPLUS_RULE_KINDS = ['name_regex', 'label', 'label_contains', 'label_starts_with', 'image_regex', 'compose_project_regex'];
8080
const FVPLUS_RULE_EFFECTS = ['include', 'exclude'];
81+
const FVPLUS_RUNTIME_PREFS_SCHEMA = 2;
8182
const FVPLUS_DOCKER_FOLDER_LABEL_KEYS = ['folderview.plus', 'folder.view3', 'folder.view2', 'folder.view'];
8283
const FVPLUS_DEFAULT_FOLDER_STATUS_COLORS = [
8384
'started' => '#ffffff',
@@ -288,10 +289,11 @@ function defaultTypePrefs(): array {
288289
'stopped' => false,
289290
'updates' => true
290291
],
291-
'liveRefreshEnabled' => true,
292+
'runtimePrefsSchema' => FVPLUS_RUNTIME_PREFS_SCHEMA,
293+
'liveRefreshEnabled' => false,
292294
'liveRefreshSeconds' => 20,
293295
'performanceMode' => false,
294-
'lazyPreviewEnabled' => true,
296+
'lazyPreviewEnabled' => false,
295297
'lazyPreviewThreshold' => 30,
296298
'backupSchedule' => [
297299
'enabled' => false,
@@ -357,14 +359,19 @@ function normalizeTypePrefs(array $prefs): array {
357359
}
358360
$normalized['autoRules'] = $normalizedRules;
359361
$normalized['badges'] = normalizeBadgePrefs($prefs['badges'] ?? []);
360-
$normalized['liveRefreshEnabled'] = !array_key_exists('liveRefreshEnabled', $prefs)
361-
? true
362-
: normalizeBool($prefs['liveRefreshEnabled'], true);
362+
$runtimePrefsSchema = normalizeIntInRange($prefs['runtimePrefsSchema'] ?? 0, 0, FVPLUS_RUNTIME_PREFS_SCHEMA, 0);
363+
$runtimePrefsReady = $runtimePrefsSchema >= FVPLUS_RUNTIME_PREFS_SCHEMA;
364+
$normalized['runtimePrefsSchema'] = FVPLUS_RUNTIME_PREFS_SCHEMA;
365+
$normalized['liveRefreshEnabled'] = $runtimePrefsReady
366+
? normalizeBool($prefs['liveRefreshEnabled'] ?? false, false)
367+
: false;
363368
$normalized['liveRefreshSeconds'] = normalizeIntInRange($prefs['liveRefreshSeconds'] ?? 20, 10, 300, 20);
364-
$normalized['performanceMode'] = normalizeBool($prefs['performanceMode'] ?? false, false);
365-
$normalized['lazyPreviewEnabled'] = !array_key_exists('lazyPreviewEnabled', $prefs)
366-
? true
367-
: normalizeBool($prefs['lazyPreviewEnabled'], true);
369+
$normalized['performanceMode'] = $runtimePrefsReady
370+
? normalizeBool($prefs['performanceMode'] ?? false, false)
371+
: false;
372+
$normalized['lazyPreviewEnabled'] = $runtimePrefsReady
373+
? normalizeBool($prefs['lazyPreviewEnabled'] ?? false, false)
374+
: false;
368375
$normalized['lazyPreviewThreshold'] = normalizeIntInRange($prefs['lazyPreviewThreshold'] ?? 30, 10, 200, 30);
369376

370377
$scheduleIncoming = is_array($prefs['backupSchedule'] ?? null) ? $prefs['backupSchedule'] : [];
@@ -2018,10 +2025,11 @@ function getDiagnosticsSnapshot(string $privacyMode = FVPLUS_DIAGNOSTICS_DEFAULT
20182025
'sortMode' => $prefs['sortMode'] ?? 'created',
20192026
'ruleCount' => count($prefs['autoRules'] ?? []),
20202027
'manualOrderCount' => count($prefs['manualOrder'] ?? []),
2021-
'liveRefreshEnabled' => normalizeBool($prefs['liveRefreshEnabled'] ?? true, true),
2028+
'runtimePrefsSchema' => normalizeIntInRange($prefs['runtimePrefsSchema'] ?? FVPLUS_RUNTIME_PREFS_SCHEMA, 0, FVPLUS_RUNTIME_PREFS_SCHEMA, FVPLUS_RUNTIME_PREFS_SCHEMA),
2029+
'liveRefreshEnabled' => normalizeBool($prefs['liveRefreshEnabled'] ?? false, false),
20222030
'liveRefreshSeconds' => normalizeIntInRange($prefs['liveRefreshSeconds'] ?? 20, 10, 300, 20),
20232031
'performanceMode' => normalizeBool($prefs['performanceMode'] ?? false, false),
2024-
'lazyPreviewEnabled' => normalizeBool($prefs['lazyPreviewEnabled'] ?? true, true),
2032+
'lazyPreviewEnabled' => normalizeBool($prefs['lazyPreviewEnabled'] ?? false, false),
20252033
'lazyPreviewThreshold' => normalizeIntInRange($prefs['lazyPreviewThreshold'] ?? 30, 10, 200, 30),
20262034
'backupSchedule' => getTypeBackupSchedule($type),
20272035
'lastBackup' => $backups[0] ?? null,

tests/folderviewplus-utils.test.mjs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,10 +200,11 @@ test('getAutoRuleDecision supports exclude precedence and advanced docker kinds'
200200

201201
test('normalizePrefs includes live refresh, performance mode, and backup schedule defaults', () => {
202202
const prefs = utils.normalizePrefs({});
203-
assert.equal(prefs.liveRefreshEnabled, true);
203+
assert.equal(prefs.runtimePrefsSchema, 2);
204+
assert.equal(prefs.liveRefreshEnabled, false);
204205
assert.equal(prefs.liveRefreshSeconds, 20);
205206
assert.equal(prefs.performanceMode, false);
206-
assert.equal(prefs.lazyPreviewEnabled, true);
207+
assert.equal(prefs.lazyPreviewEnabled, false);
207208
assert.equal(prefs.lazyPreviewThreshold, 30);
208209
assert.deepEqual(prefs.backupSchedule, {
209210
enabled: false,
@@ -213,6 +214,34 @@ test('normalizePrefs includes live refresh, performance mode, and backup schedul
213214
});
214215
});
215216

217+
test('normalizePrefs disables legacy runtime toggles until schema is upgraded', () => {
218+
const legacy = utils.normalizePrefs({
219+
liveRefreshEnabled: true,
220+
liveRefreshSeconds: 45,
221+
performanceMode: true,
222+
lazyPreviewEnabled: true,
223+
lazyPreviewThreshold: 77
224+
});
225+
assert.equal(legacy.runtimePrefsSchema, 2);
226+
assert.equal(legacy.liveRefreshEnabled, false);
227+
assert.equal(legacy.performanceMode, false);
228+
assert.equal(legacy.lazyPreviewEnabled, false);
229+
assert.equal(legacy.liveRefreshSeconds, 45);
230+
assert.equal(legacy.lazyPreviewThreshold, 77);
231+
232+
const upgraded = utils.normalizePrefs({
233+
runtimePrefsSchema: 2,
234+
liveRefreshEnabled: true,
235+
liveRefreshSeconds: 45,
236+
performanceMode: true,
237+
lazyPreviewEnabled: true,
238+
lazyPreviewThreshold: 77
239+
});
240+
assert.equal(upgraded.liveRefreshEnabled, true);
241+
assert.equal(upgraded.performanceMode, true);
242+
assert.equal(upgraded.lazyPreviewEnabled, true);
243+
});
244+
216245
test('buildImportDiffRows reports row-level changed fields', () => {
217246
const existing = {
218247
apps: {

0 commit comments

Comments
 (0)