Skip to content

Commit 3f01f93

Browse files
feat: improve runtime controls, import/export UX, and release guardrails
1 parent a97a6de commit 3f01f93

21 files changed

Lines changed: 1246 additions & 22 deletions

.github/workflows/ci.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,8 @@ jobs:
4040
- name: Run utility tests
4141
run: |
4242
node --test tests/*.mjs
43+
44+
- name: Release guard checks
45+
run: |
46+
chmod +x scripts/release_guard.sh
47+
bash scripts/release_guard.sh

.github/workflows/release-beta.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ jobs:
7373
exit 1
7474
fi
7575
76+
- name: Run release guard
77+
run: |
78+
chmod +x scripts/release_guard.sh
79+
bash scripts/release_guard.sh
80+
7681
- name: Commit and push to beta
7782
run: |
7883
git add archive/ folderview.plus.plg

.github/workflows/release-main.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ jobs:
3333
chmod +x pkg_build.sh
3434
bash pkg_build.sh
3535
36+
- name: Run release guard
37+
run: |
38+
chmod +x scripts/release_guard.sh
39+
bash scripts/release_guard.sh
40+
3641
- name: Extract stable version
3742
id: version
3843
run: |

.github/workflows/release-stable.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ jobs:
4141
chmod +x pkg_build.sh
4242
bash pkg_build.sh
4343
44+
- name: Run release guard
45+
run: |
46+
chmod +x scripts/release_guard.sh
47+
bash scripts/release_guard.sh
48+
4449
- name: Extract stable version
4550
id: version
4651
run: |

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,15 @@ major quality-of-life, reliability, and UX improvements for Unraid users:
2121
- Rule simulator for full-item assignment preview (assigned/blocked/unassigned)
2222
- Folder templates (save/apply/delete) for reusable folder configurations
2323
- Bulk actions for rules/templates (enable/disable/delete/export where applicable)
24+
- Folder pinning and optional hide-empty folder view in settings
25+
- Runtime action planner with preview + apply (`Start`, `Stop`, `Pause`, `Resume`) per folder
26+
- Change history panel with one-click undo to latest transaction backup
2427
- Sort modes per type: `created`, `manual`, `alpha`
2528
- Search filters in settings for folders, rules, backups, and templates
2629
- Diagnostics support bundle and copyable issue report text for faster issue filing
2730
- Runtime lazy preview controls for large libraries (`lazy preview` + threshold)
2831
- Better release/version metadata handling for more consistent update detection in Unraid
32+
- Release guardrails in CI/workflows for `.plg` validity, archive/version/md5 consistency
2933
- Continued folder-based views across Docker, VMs, and Dashboard pages
3034

3135
## Requirements
@@ -72,6 +76,9 @@ plugin remove folderview.plus
7276
- Rule-based auto-assignment using name regex and Docker labels
7377
- Rule simulator to preview final assignment outcomes across all Docker/VM items
7478
- Bulk rule and template operations in settings
79+
- Folder pin/unpin controls and hide-empty toggle
80+
- Runtime bulk folder action preview/apply workflow for Docker and VMs
81+
- Change history viewer and latest-change undo action
7582
- Search and filtering for settings tables (folders/rules/backups/templates)
7683
- Folder templates to copy folder settings/actions/regex between folders
7784
- Folder sort modes: `created`, `manual`, `alpha`
214 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.08.9">
10-
<!ENTITY md5 "67099f6404923957ab251e0a36316997">
9+
<!ENTITY version "2026.03.09.1">
10+
<!ENTITY md5 "0bf457b05dd00716c7bc7c90c3a38874">
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.09.1
17+
- Major feature expansion and quality pass:
18+
- add folder pinning and optional hide-empty folder behavior in settings/runtime views,
19+
- add runtime action planner (preview + apply) for folder-level Docker/VM start/stop/pause/resume,
20+
- add change-history panel and one-click undo to latest transaction/undo backup,
21+
- add stronger diagnostics integrity checks (pinned IDs, icon path format) and richer update-check telemetry,
22+
- add release guardrails script and enforce guard checks in CI/release workflows,
23+
- expand utility/settings tests for new prefs/runtime planning behavior.
24+
1625
###2026.03.08.9
1726
- Full bugfix pass for regressions and stability:
1827
- scope custom-action dialog styling to its own dialog widget (no global `.ui-dialog` side effects),

scripts/release_guard.sh

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5+
PLG_FILE="${ROOT_DIR}/folderview.plus.plg"
6+
7+
if [[ ! -f "${PLG_FILE}" ]]; then
8+
echo "ERROR: Missing plugin manifest: ${PLG_FILE}" >&2
9+
exit 1
10+
fi
11+
12+
VERSION="$(sed -n 's/^<!ENTITY version "\([^"]*\)".*/\1/p' "${PLG_FILE}" | head -n 1 || true)"
13+
MD5_ENTITY="$(sed -n 's/^<!ENTITY md5 "\([^"]*\)".*/\1/p' "${PLG_FILE}" | head -n 1 || true)"
14+
15+
if [[ -z "${VERSION}" ]]; then
16+
echo "ERROR: Could not parse version entity from folderview.plus.plg" >&2
17+
exit 1
18+
fi
19+
20+
if [[ -z "${MD5_ENTITY}" ]]; then
21+
echo "ERROR: Could not parse md5 entity from folderview.plus.plg" >&2
22+
exit 1
23+
fi
24+
25+
if ! [[ "${VERSION}" =~ ^[0-9]{4}\.[0-9]{2}\.[0-9]{2}([.-][0-9]+|-beta[0-9]*)?$ ]]; then
26+
echo "ERROR: Version has unexpected format: ${VERSION}" >&2
27+
exit 1
28+
fi
29+
30+
if command -v xmllint >/dev/null 2>&1; then
31+
xmllint --noout "${PLG_FILE}"
32+
else
33+
php -r '
34+
libxml_use_internal_errors(true);
35+
$xml = @file_get_contents($argv[1]);
36+
if ($xml === false) { fwrite(STDERR, "ERROR: Failed to read PLG file\n"); exit(1); }
37+
$dom = new DOMDocument();
38+
if (!$dom->loadXML($xml, LIBXML_NONET | LIBXML_NOERROR | LIBXML_NOWARNING)) {
39+
fwrite(STDERR, "ERROR: Invalid PLG XML\n");
40+
exit(1);
41+
}
42+
' "${PLG_FILE}"
43+
fi
44+
45+
ARCHIVE_FILE="${ROOT_DIR}/archive/folderview.plus-${VERSION}.txz"
46+
if [[ ! -f "${ARCHIVE_FILE}" ]]; then
47+
echo "ERROR: Missing archive package for current version: ${ARCHIVE_FILE}" >&2
48+
exit 1
49+
fi
50+
51+
if ! grep -q "###${VERSION}" "${PLG_FILE}"; then
52+
echo "ERROR: CHANGES section does not contain an entry for ${VERSION}" >&2
53+
exit 1
54+
fi
55+
56+
MD5_CALC="$(md5sum "${ARCHIVE_FILE}" | awk '{print $1}')"
57+
if [[ "${MD5_ENTITY}" != "${MD5_CALC}" ]]; then
58+
echo "ERROR: md5 entity mismatch. plg=${MD5_ENTITY}, archive=${MD5_CALC}" >&2
59+
exit 1
60+
fi
61+
62+
echo "Release guard checks passed:"
63+
echo " version: ${VERSION}"
64+
echo " archive: ${ARCHIVE_FILE##*/}"
65+
echo " md5: ${MD5_CALC}"

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

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ require_once('/usr/local/emhttp/plugins/folderview.plus/langs/script.php');
4141
</select>
4242
<div class="hint-line">Use manual mode and the up/down buttons in the first column to set custom order.</div>
4343
<input id="docker-folder-filter" type="text" placeholder="Search Docker folders" oninput="setFilterQuery('folders','docker',this.value)">
44+
<div class="badge-toggle-row">
45+
<span class="badge-toggle-title">Layout</span>
46+
<label><input id="docker-hide-empty-folders" type="checkbox" onchange="changeVisibilityPref('docker', 'hideEmptyFolders', this.checked)"> Hide empty folders</label>
47+
<span class="rules-help">Pin folders with the star button in the Actions column.</span>
48+
</div>
4449
<div class="badge-toggle-row">
4550
<span class="badge-toggle-title">Folder badges</span>
4651
<label><input id="docker-badge-running" type="checkbox" onchange="changeBadgePref('docker', 'running', this.checked)"> Running</label>
@@ -99,6 +104,11 @@ require_once('/usr/local/emhttp/plugins/folderview.plus/langs/script.php');
99104
</select>
100105
<div class="hint-line">Use manual mode and the up/down buttons in the first column to set custom order.</div>
101106
<input id="vm-folder-filter" type="text" placeholder="Search VM folders" oninput="setFilterQuery('folders','vm',this.value)">
107+
<div class="badge-toggle-row">
108+
<span class="badge-toggle-title">Layout</span>
109+
<label><input id="vm-hide-empty-folders" type="checkbox" onchange="changeVisibilityPref('vm', 'hideEmptyFolders', this.checked)"> Hide empty folders</label>
110+
<span class="rules-help">Pin folders with the star button in the Actions column.</span>
111+
</div>
102112
<div class="badge-toggle-row">
103113
<span class="badge-toggle-title">Folder badges</span>
104114
<label><input id="vm-badge-running" type="checkbox" onchange="changeBadgePref('vm', 'running', this.checked)"> Running</label>
@@ -271,6 +281,48 @@ require_once('/usr/local/emhttp/plugins/folderview.plus/langs/script.php');
271281
</div>
272282
<hr>
273283

284+
<h2>Folder runtime actions</h2>
285+
<div class="bulk-assign-grid">
286+
<div class="rules-panel">
287+
<div class="rules-header">
288+
<h3>Docker folder actions</h3>
289+
<span class="rules-help">Preview and apply Start/Stop/Pause/Resume for items currently in a folder.</span>
290+
</div>
291+
<div class="runtime-action-grid">
292+
<select id="docker-runtime-folder"></select>
293+
<select id="docker-runtime-action">
294+
<option value="start">Start</option>
295+
<option value="stop">Stop</option>
296+
<option value="pause">Pause</option>
297+
<option value="resume">Resume</option>
298+
</select>
299+
<button onclick="previewFolderRuntimeAction('docker')"><i class="fa fa-list-alt"></i> Preview</button>
300+
<button onclick="applyFolderRuntimeAction('docker')"><i class="fa fa-play"></i> Apply action</button>
301+
</div>
302+
<pre id="docker-runtime-preview-output" class="diagnostics-output compact-output">Preview a runtime action to see exactly which containers will be changed.</pre>
303+
</div>
304+
305+
<div class="rules-panel">
306+
<div class="rules-header">
307+
<h3>VM folder actions</h3>
308+
<span class="rules-help">Preview and apply Start/Stop/Pause/Resume for VMs currently in a folder.</span>
309+
</div>
310+
<div class="runtime-action-grid">
311+
<select id="vm-runtime-folder"></select>
312+
<select id="vm-runtime-action">
313+
<option value="start">Start</option>
314+
<option value="stop">Stop</option>
315+
<option value="pause">Pause</option>
316+
<option value="resume">Resume</option>
317+
</select>
318+
<button onclick="previewFolderRuntimeAction('vm')"><i class="fa fa-list-alt"></i> Preview</button>
319+
<button onclick="applyFolderRuntimeAction('vm')"><i class="fa fa-play"></i> Apply action</button>
320+
</div>
321+
<pre id="vm-runtime-preview-output" class="diagnostics-output compact-output">Preview a runtime action to see exactly which VMs will be changed.</pre>
322+
</div>
323+
</div>
324+
<hr>
325+
274326
<h2>Backups</h2>
275327
<div class="backup-grid">
276328
<div class="rules-panel">
@@ -397,6 +449,21 @@ require_once('/usr/local/emhttp/plugins/folderview.plus/langs/script.php');
397449
</div>
398450
<hr>
399451

452+
<h2>Change history and undo</h2>
453+
<div class="rules-panel">
454+
<div class="rules-header">
455+
<h3>Recent changes</h3>
456+
<span class="rules-help">Shows recent config actions from diagnostics history and allows one-click undo to the latest backup transaction.</span>
457+
</div>
458+
<div class="backup-actions">
459+
<button onclick="refreshChangeHistory()"><i class="fa fa-refresh"></i> Refresh history</button>
460+
<button onclick="undoLatestChange('docker')"><i class="fa fa-undo"></i> Undo latest Docker change</button>
461+
<button onclick="undoLatestChange('vm')"><i class="fa fa-undo"></i> Undo latest VM change</button>
462+
</div>
463+
<pre id="change-history-output" class="diagnostics-output compact-output">Refresh history to view recent changes.</pre>
464+
</div>
465+
<hr>
466+
400467
<h2>Diagnostics</h2>
401468
<div class="rules-panel">
402469
<div class="rules-header">

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,10 @@ const createFolderDocker = (folder, id, position, order, containersInfo, folders
485485

486486
// replace the old containers array with the newFolder object
487487
folder.containers = newFolder;
488+
if (folderTypePrefs?.docker?.hideEmptyFolders === true && Object.keys(folder.containers).length === 0) {
489+
$(`.folder-showcase-outer-${id}`).remove();
490+
return remBefore;
491+
}
488492

489493
//temp var
490494
const sel = $(`tbody#docker_view span#folder-id-${id}`);
@@ -718,6 +722,10 @@ const createFolderVM = (folder, id, position, order, vmInfo, foldersDone) => {
718722

719723
// replace the old containers array with the newFolder object
720724
folder.containers = newFolder;
725+
if (folderTypePrefs?.vm?.hideEmptyFolders === true && Object.keys(folder.containers).length === 0) {
726+
$(`.folder-showcase-outer-${id}`).remove();
727+
return remBefore;
728+
}
721729

722730

723731
//set tehe status of a folder

0 commit comments

Comments
 (0)