Skip to content

Commit f93e66c

Browse files
author
FolderView Plus Test
committed
Add Docker host-list page view mode
1 parent b7bf2ea commit f93e66c

14 files changed

+177
-17
lines changed

archive/folderview.plus-2026.04.14.02.txz.sha256

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
c5524ecf1b4d021071e6f06f66b925c6809b9573eb97d8e73c44071ce5a0ebcd folderview.plus-2026.04.15.12.txz

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;/dev/folderview.plus.plg">
9-
<!ENTITY version "2026.04.15.11">
10-
<!ENTITY md5 "d01fe1451050c8cb7d527a719108af02">
9+
<!ENTITY version "2026.04.15.12">
10+
<!ENTITY md5 "d3806cd6cab6f5a6d69eaf13e8a053d7">
1111
]>
1212

1313
<PLUGIN name="&name;" author="&author;" version="&version;" launch="&launch;" pluginURL="&pluginURL;" icon="folder-icon.png" support="https://forums.unraid.net/topic/197631-plugin-folderview-plus/" min="7.0.0">
1414
<CHANGES>
1515

16+
###2026.04.15.12
17+
- Fix: Docker support-bundle snapshots, trace storage caps, and rendered-state diagnostics.
18+
- Fix: Docker runtime rows, folder state, and container interactions.
19+
- UX: Settings workspace layout, section flows, and table behavior.
20+
- Feature: Setup Assistant, rules, smart-detect, and starter-template workflows.
21+
- Refactor: Shared runtime contracts, request plumbing, and cross-page foundations.
22+
- Fix: Server endpoints, runtime payloads, and persistence or validation paths.
23+
24+
1625
###2026.04.15.11
1726
- Perf: Debounce legacy folder-regex matching while typing and stop running the duplicate editor recalculation path on every regex keystroke.
1827
- Perf: Avoid the extra pre-worker member-table redraw so large regex edits render once per settled pattern instead of twice.

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/FolderViewPlus.page

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,11 @@ if (!empty($fvplusRuntimeConflicts)) {
144144
<option value="alpha">Name (A-Z)</option>
145145
<option value="name_desc">Name (Z-A)</option>
146146
</select>
147+
<label class="sort-label" for="docker-page-view-mode">Docker page view</label>
148+
<select id="docker-page-view-mode" onchange="changeRuntimePref('docker', 'pageViewMode', this.value)">
149+
<option value="folderview">FolderView</option>
150+
<option value="host">No FolderView (host list only)</option>
151+
</select>
147152
<span class="sort-hint-chip" title="Use manual mode with up/down and tree move controls in the first column to set custom nested order."><i class="fa fa-info-circle"></i> Manual uses up/down + tree move in Order column</span>
148153
<span class="tree-visibility-controls">
149154
<button type="button" onclick="expandAllFolderTrees('docker')"><i class="fa fa-expand"></i> Expand all</button>

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/folderview.plus.Docker.page

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ $fvplusRuntimePreflightHasFatal = runtimePreflightHasFatal($fvplusRuntimePreflig
3737
<script>
3838
$(document).ready(() => {
3939
folderi18n = () => {
40-
$(`<input type="button" onclick="createFolderBtn()" value="Add Folder" data-i18n="[value]add-folder">`).insertAfter('table#docker_containers');
40+
$(`<input id="fvplus-docker-add-folder-btn" type="button" onclick="createFolderBtn()" value="Add Folder" data-i18n="[value]add-folder">`).insertAfter('table#docker_containers');
4141
$('body').i18n();
4242
$('[type="button"]').i18n();
4343
}

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

Lines changed: 99 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2932,6 +2932,97 @@ const scheduleDockerPostRenderPolish = (folderIds = []) => {
29322932
window.setTimeout(run, 0);
29332933
};
29342934

2935+
const normalizeDockerPageViewMode = (value) => (
2936+
typeof utils.normalizeRuntimePageViewMode === 'function'
2937+
? utils.normalizeRuntimePageViewMode(value)
2938+
: (String(value || '').trim().toLowerCase() === 'host' ? 'host' : 'folderview')
2939+
);
2940+
2941+
const resolveDockerPageViewMode = (prefs = folderTypePrefs) => normalizeDockerPageViewMode(
2942+
utils.normalizePrefs(prefs || {}).pageViewMode
2943+
);
2944+
2945+
const syncDockerAddFolderButtonVisibility = (mode = 'folderview') => {
2946+
const resolvedMode = normalizeDockerPageViewMode(mode);
2947+
const existing = document.getElementById('fvplus-docker-add-folder-btn');
2948+
if (resolvedMode === 'host') {
2949+
if (existing) {
2950+
existing.remove();
2951+
}
2952+
return;
2953+
}
2954+
if (existing) {
2955+
return;
2956+
}
2957+
const table = document.querySelector('table#docker_containers');
2958+
if (!table || typeof table.insertAdjacentHTML !== 'function') {
2959+
return;
2960+
}
2961+
table.insertAdjacentHTML(
2962+
'afterend',
2963+
'<input id="fvplus-docker-add-folder-btn" type="button" onclick="createFolderBtn()" value="Add Folder" data-i18n="[value]add-folder">'
2964+
);
2965+
if (typeof $('body').i18n === 'function') {
2966+
$('body').i18n();
2967+
}
2968+
if (typeof $('[type="button"]').i18n === 'function') {
2969+
$('[type="button"]').i18n();
2970+
}
2971+
};
2972+
2973+
const fetchDockerBootstrapPrefs = async () => {
2974+
const response = await $.get(`/plugins/folderview.plus/server/prefs.php?type=docker&_=${Date.now()}`).promise();
2975+
const parsed = parseJsonPayloadSafe(response);
2976+
const nextPrefs = utils.normalizePrefs(parsed?.prefs || {});
2977+
folderTypePrefs = nextPrefs;
2978+
applyRuntimePrefs(nextPrefs);
2979+
return nextPrefs;
2980+
};
2981+
2982+
const ensureDockerBootstrapPrefs = () => {
2983+
if (lastAppliedRuntimePrefs && typeof lastAppliedRuntimePrefs === 'object' && Object.keys(lastAppliedRuntimePrefs).length > 0) {
2984+
return Promise.resolve(lastAppliedRuntimePrefs);
2985+
}
2986+
if (dockerBootstrapPrefsPromise) {
2987+
return dockerBootstrapPrefsPromise;
2988+
}
2989+
dockerBootstrapPrefsPromise = Promise.resolve()
2990+
.then(() => fetchDockerBootstrapPrefs())
2991+
.catch(() => utils.normalizePrefs(folderTypePrefs || {}))
2992+
.finally(() => {
2993+
dockerBootstrapPrefsPromise = null;
2994+
});
2995+
return dockerBootstrapPrefsPromise;
2996+
};
2997+
2998+
const queueDockerRuntimeRenderForPageViewMode = () => {
2999+
Promise.resolve()
3000+
.then(() => ensureDockerBootstrapPrefs())
3001+
.then((prefs) => {
3002+
if (resolveDockerPageViewMode(prefs) === 'host') {
3003+
markDockerFatalBannerStep('Docker host list mode active');
3004+
recordDockerFatalBannerAction('Docker host list mode active');
3005+
return;
3006+
}
3007+
if (!folderReq || !Array.isArray(folderReq.render) || folderReq.render.length === 0) {
3008+
folderReq = buildDockerFolderReq({
3009+
liveUpdateStatus: isDockerHostUpdateSyncSuspended()
3010+
});
3011+
}
3012+
dockerHostLoadOwnsLoadingUi = true;
3013+
queueCreateFoldersRender();
3014+
})
3015+
.catch(() => {
3016+
if (!folderReq || !Array.isArray(folderReq.render) || folderReq.render.length === 0) {
3017+
folderReq = buildDockerFolderReq({
3018+
liveUpdateStatus: isDockerHostUpdateSyncSuspended()
3019+
});
3020+
}
3021+
dockerHostLoadOwnsLoadingUi = true;
3022+
queueCreateFoldersRender();
3023+
});
3024+
};
3025+
29353026
const syncDockerVisibleFoldersFromRuntimeCache = () => {
29363027
Object.entries(globalFolders || {}).forEach(([id, folder]) => {
29373028
if (!folder || typeof folder !== 'object') {
@@ -5512,16 +5603,10 @@ window.listview = () => {
55125603
}
55135604

55145605
if (!loadedFolder) {
5515-
if (!folderReq || !Array.isArray(folderReq.render) || folderReq.render.length === 0) {
5516-
folderReq = buildDockerFolderReq({
5517-
liveUpdateStatus: isDockerHostUpdateSyncSuspended()
5518-
});
5519-
}
5520-
dockerHostLoadOwnsLoadingUi = true;
5521-
if (FOLDER_VIEW_DEBUG_MODE) console.log('[FV3_DEBUG] Patched listview: loadedFolder is false. Queueing createFolders render.');
5522-
queueCreateFoldersRender();
55235606
loadedFolder = true;
5524-
if (FOLDER_VIEW_DEBUG_MODE) console.log('[FV3_DEBUG] Patched listview: Set loadedFolder to true.');
5607+
if (FOLDER_VIEW_DEBUG_MODE) console.log('[FV3_DEBUG] Patched listview: loadedFolder is false. Resolving Docker page view render path.');
5608+
queueDockerRuntimeRenderForPageViewMode();
5609+
if (FOLDER_VIEW_DEBUG_MODE) console.log('[FV3_DEBUG] Patched listview: Set loadedFolder to true.');
55255610
} else {
55265611
if (FOLDER_VIEW_DEBUG_MODE) console.log('[FV3_DEBUG] Patched listview: loadedFolder is true. Skipped createFolders.');
55275612
}
@@ -5724,6 +5809,7 @@ let folderTypePrefs = utils.normalizePrefs({});
57245809
let liveRefreshTimer = null;
57255810
let liveRefreshMs = 0;
57265811
let liveRefreshInFlight = false;
5812+
let dockerBootstrapPrefsPromise = null;
57275813
let queuedLoadlistTimer = null;
57285814
let queuedLoadlistOptions = null;
57295815
let queuedLoadlistRequestedAt = 0;
@@ -6012,6 +6098,10 @@ const scheduleLiveRefresh = (prefs) => {
60126098
const applyRuntimePrefs = (prefs) => {
60136099
const normalized = utils.normalizePrefs(prefs || {});
60146100
lastAppliedRuntimePrefs = normalized;
6101+
if (document.body && typeof document.body.setAttribute === 'function') {
6102+
document.body.setAttribute('data-fvplus-docker-page-view', resolveDockerPageViewMode(normalized));
6103+
}
6104+
syncDockerAddFolderButtonVisibility(resolveDockerPageViewMode(normalized));
60156105
const appColumnWidth = typeof utils.normalizeAppColumnWidth === 'function'
60166106
? utils.normalizeAppColumnWidth(normalized.appColumnWidth)
60176107
: (['compact', 'wide'].includes(String(normalized.appColumnWidth || '').toLowerCase()) ? String(normalized.appColumnWidth || '').toLowerCase() : 'standard');

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6488,6 +6488,11 @@ const renderRuntimeControls = (type) => {
64886488
$(`#${type}-performance-mode`).prop('checked', prefs.performanceMode === true);
64896489
$(`#${type}-lazy-preview-enabled`).prop('checked', prefs.lazyPreviewEnabled === true);
64906490
$(`#${type}-lazy-preview-threshold`).val(String(prefs.lazyPreviewThreshold || 30));
6491+
$(`#${type}-page-view-mode`).val(
6492+
typeof utils.normalizeRuntimePageViewMode === 'function'
6493+
? utils.normalizeRuntimePageViewMode(prefs.pageViewMode)
6494+
: (String(prefs.pageViewMode || '').trim().toLowerCase() === 'host' ? 'host' : 'folderview')
6495+
);
64916496
$(`#${type}-theme-compat-mode`).val(resolveThemeCompatibilityMode(prefs.themeCompatibilityMode));
64926497
syncRuntimeDependentFields(type);
64936498
applySettingsResolvedThemeTokens(`render-runtime-${type}`);
@@ -7960,6 +7965,10 @@ const changeRuntimePref = async (type, key, value) => {
79607965
} else if (key === 'lazyPreviewThreshold') {
79617966
const parsed = Number(value);
79627967
next.lazyPreviewThreshold = Number.isFinite(parsed) ? Math.min(200, Math.max(10, Math.round(parsed))) : current.lazyPreviewThreshold;
7968+
} else if (key === 'pageViewMode') {
7969+
next.pageViewMode = typeof utils.normalizeRuntimePageViewMode === 'function'
7970+
? utils.normalizeRuntimePageViewMode(value)
7971+
: (String(value || '').trim().toLowerCase() === 'host' ? 'host' : 'folderview');
79637972
} else if (key === 'themeCompatibilityMode') {
79647973
next.themeCompatibilityMode = resolveThemeCompatibilityMode(value);
79657974
} else {

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
const RUNTIME_PREFS_SCHEMA = 2;
2222
const APP_COLUMN_WIDTH_OPTIONS = ['compact', 'standard', 'wide'];
2323
const THEME_COMPATIBILITY_MODE_OPTIONS = ['auto', 'host', 'safe', 'highcontrast'];
24+
const RUNTIME_PAGE_VIEW_MODE_OPTIONS = ['folderview', 'host'];
2425
const DEFAULT_FOLDER_STATUS_COLORS = {
2526
started: '#ffffff',
2627
paused: '#b8860b',
@@ -432,6 +433,11 @@
432433
return THEME_COMPATIBILITY_MODE_OPTIONS.includes(normalized) ? normalized : 'auto';
433434
};
434435

436+
const normalizeRuntimePageViewMode = (value) => {
437+
const normalized = String(value || '').trim().toLowerCase();
438+
return RUNTIME_PAGE_VIEW_MODE_OPTIONS.includes(normalized) ? normalized : 'folderview';
439+
};
440+
435441
const normalizeFolderMembers = (value) => {
436442
if (Array.isArray(value)) {
437443
return Array.from(
@@ -692,6 +698,7 @@
692698
const performanceMode = runtimePrefsReady ? incoming.performanceMode === true : false;
693699
const lazyPreviewEnabled = runtimePrefsReady ? incoming.lazyPreviewEnabled === true : false;
694700
const lazyPreviewThreshold = clampNumber(incoming.lazyPreviewThreshold, 10, 200, 30);
701+
const pageViewMode = normalizeRuntimePageViewMode(incoming.pageViewMode);
695702
const themeCompatibilityMode = normalizeThemeCompatibilityMode(incoming.themeCompatibilityMode);
696703
const incomingDashboard = isPlainObject(incoming.dashboard) ? incoming.dashboard : {};
697704
const dashboard = {
@@ -827,6 +834,7 @@
827834
performanceMode,
828835
lazyPreviewEnabled,
829836
lazyPreviewThreshold,
837+
pageViewMode,
830838
themeCompatibilityMode,
831839
dashboard,
832840
health,
@@ -2129,6 +2137,7 @@
21292137
normalizeFolderEditorMode,
21302138
normalizeDashboardLayout,
21312139
normalizeDashboardOverflowMode,
2140+
normalizeRuntimePageViewMode,
21322141
normalizeThemeCompatibilityMode,
21332142
normalizePrefs,
21342143
orderFoldersByPrefs,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2041,6 +2041,7 @@ function getDiagnosticsSnapshot(string $privacyMode = FVPLUS_DIAGNOSTICS_DEFAULT
20412041
'performanceMode' => normalizeBool($prefs['performanceMode'] ?? false, false),
20422042
'lazyPreviewEnabled' => normalizeBool($prefs['lazyPreviewEnabled'] ?? false, false),
20432043
'lazyPreviewThreshold' => normalizeIntInRange($prefs['lazyPreviewThreshold'] ?? 30, 10, 200, 30),
2044+
'pageViewMode' => normalizeRuntimePageViewMode($prefs['pageViewMode'] ?? 'folderview'),
20442045
'themeCompatibilityMode' => normalizeThemeCompatibilityMode($prefs['themeCompatibilityMode'] ?? 'auto'),
20452046
'dashboard' => [
20462047
'layout' => normalizeDashboardLayout($prefs['dashboard']['layout'] ?? 'classic'),

0 commit comments

Comments
 (0)