Skip to content

Commit 298fa44

Browse files
author
FolderView Plus Test
committed
Clarify support bundle Docker view diagnostics and provenance
1 parent 5450901 commit 298fa44

13 files changed

Lines changed: 204 additions & 10 deletions

archive/folderview.plus-2026.04.05.12.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+
0d53ad893af8f37128791b9ba3dcf061e763968aba2721e2e69d878aeec9dca2 folderview.plus-2026.04.08.05.txz

folderview.plus.plg

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,20 @@
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.08.04">
10-
<!ENTITY md5 "1d68089a0df39087855f68ddae636b28">
9+
<!ENTITY version "2026.04.08.05">
10+
<!ENTITY md5 "7e071cda88a0e68be4f8f80bf9c1c782">
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.08.05
17+
- Fix: Docker runtime rows, folder state, and container interactions.
18+
- UX: Settings workspace layout, section flows, and table behavior.
19+
- Fix: Server endpoints, runtime payloads, and persistence or validation paths.
20+
- Quality: Release automation, CI smoke coverage, and packaging guards.
21+
22+
1623
###2026.04.08.04
1724
- Fix: Docker runtime rows, folder state, and container interactions.
1825

pkg_build.sh

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,45 @@ detect_git_tree_sha() {
5252
printf '%s' "$detected"
5353
}
5454

55+
detect_git_head_tree_sha() {
56+
local detected=""
57+
if command -v git >/dev/null 2>&1 && git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
58+
detected="$(git rev-parse 'HEAD^{tree}' 2>/dev/null || true)"
59+
fi
60+
printf '%s' "$detected"
61+
}
62+
63+
detect_git_source_snapshot_mode() {
64+
if ! command -v git >/dev/null 2>&1 || ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
65+
printf '%s' "unknown"
66+
return
67+
fi
68+
if ! git diff --quiet -- . ':(exclude)archive' ':(exclude)folderview.plus.plg' ':(exclude)folderview.plus.xml' 2>/dev/null; then
69+
printf '%s' "worktree"
70+
return
71+
fi
72+
if ! git diff --cached --quiet -- . ':(exclude)archive' ':(exclude)folderview.plus.plg' ':(exclude)folderview.plus.xml' 2>/dev/null; then
73+
printf '%s' "index"
74+
return
75+
fi
76+
printf '%s' "head"
77+
}
78+
79+
detect_git_source_tree_sha() {
80+
local snapshot_mode="${1:-}"
81+
case "$snapshot_mode" in
82+
head)
83+
detect_git_head_tree_sha
84+
;;
85+
index)
86+
detect_git_tree_sha
87+
;;
88+
*)
89+
printf '%s' ""
90+
;;
91+
esac
92+
}
93+
5594
rewrite_manifest_branch_metadata() {
5695
local target_file="${1:-}"
5796
local target_version="${2:-}"
@@ -537,14 +576,24 @@ done < <(find . -type f ! \( -iname "pkg_build.sh" -o -iname "sftp-config.json"
537576
apply_branch_channel_messaging "$tmpdir" "$branch"
538577

539578
build_metadata_path="$tmpdir/usr/local/emhttp/plugins/folderview.plus/build-metadata.json"
540-
build_git_commit_sha="$(detect_git_commit_sha)"
541-
build_git_tree_sha="$(detect_git_tree_sha)"
579+
build_git_head_commit_sha="$(detect_git_commit_sha)"
580+
build_git_snapshot_mode="$(detect_git_source_snapshot_mode)"
581+
build_git_tree_sha="$(detect_git_source_tree_sha "$build_git_snapshot_mode")"
582+
build_git_source_commit_sha=""
583+
build_git_commit_exact=false
584+
if [ "$build_git_snapshot_mode" = "head" ] && [ -n "$build_git_head_commit_sha" ]; then
585+
build_git_source_commit_sha="$build_git_head_commit_sha"
586+
build_git_commit_exact=true
587+
fi
542588
build_manifest_url="https://raw.githubusercontent.com/alexphillips-dev/FolderView-Plus/${branch}/folderview.plus.plg"
543589
build_archive_url="https://raw.githubusercontent.com/alexphillips-dev/FolderView-Plus/${branch}/archive/${archive_prefix}-${version}.txz"
544590
cat > "$build_metadata_path" <<EOF
545591
{
546-
"sourceCommitSha": "${build_git_commit_sha}",
592+
"sourceCommitSha": "${build_git_source_commit_sha}",
593+
"headCommitSha": "${build_git_head_commit_sha}",
547594
"sourceTreeSha": "${build_git_tree_sha}",
595+
"sourceSnapshotMode": "${build_git_snapshot_mode}",
596+
"sourceCommitExact": ${build_git_commit_exact},
548597
"sourceBranch": "${branch}",
549598
"manifestUrl": "${build_manifest_url}",
550599
"archiveUrl": "${build_archive_url}",

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1733,6 +1733,10 @@ const buildDockerTooltipContent = (ct) => {
17331733
const labels = runtimeEntry?.Labels && typeof runtimeEntry.Labels === 'object' ? runtimeEntry.Labels : {};
17341734
const tooltipWebUiUrl = getSafeWebuiUrl(runtimeEntry?.info?.State?.WebUi);
17351735
const tooltipTsWebUiUrl = getSafeWebuiUrl(runtimeEntry?.info?.State?.TSWebUi);
1736+
const tooltipShowAdvanced = $.cookie('docker_listview_mode') == 'advanced';
1737+
const tooltipForceUpdateHtml = tooltipShowAdvanced
1738+
? `<br><a class="exec" onclick="hideAllTips(); updateContainer('${runtimeEntry.info.Name}');"><span style="white-space:nowrap;"><i class="fa fa-cloud-download fa-fw"></i>${$.i18n('force-update')}</span></a>`
1739+
: '';
17361740
const $content = $(`
17371741
<div class="preview-outbox preview-outbox-${ct.shortId}">
17381742
<div class="first-row">
@@ -1747,7 +1751,7 @@ const buildDockerTooltipContent = (ct) => {
17471751
<table class="preview-status">
17481752
<thead class="status-header"><tr><th class="status-header-version">${$.i18n('version')}</th><th class="status-header-stats">CPU/MEM</th><th class="status-header-autostart">${$.i18n('autostart')}</th></tr></thead>
17491753
<tbody><tr>
1750-
<td><div class="status-version">${runtimeEntry.info.State.manager === 'composeman' ? `<span class="folder-update-text"><i class="fa fa-docker fa-fw"></i> ${$.i18n('compose')}</span>` : runtimeEntry.info.State.manager !== 'dockerman' ? `<span class="folder-update-text"><i class="fa fa-docker fa-fw"></i> ${$.i18n('third-party')}</span>` : runtimeEntry.info.State.Updated !== false ? `<span class="green-text folder-update-text"><i class="fa fa-check fa-fw"></i>${$.i18n('up-to-date')}</span><br><a class="exec" onclick="hideAllTips(); updateContainer('${runtimeEntry.info.Name}');"><span style="white-space:nowrap;"><i class="fa fa-cloud-download fa-fw"></i>${$.i18n('force-update')}</span></a>` : `<span class="orange-text folder-update-text" style="white-space:nowrap;"><i class="fa fa-flash fa-fw"></i>${$.i18n('update-ready')}</span><br><a class="exec" onclick="hideAllTips(); updateContainer('${runtimeEntry.info.Name}');"><span style="white-space:nowrap;"><i class="fa fa-cloud-download fa-fw"></i>${$.i18n('apply-update')}</span></a>`}<br><i class="fa fa-info-circle fa-fw"></i> ${runtimeEntry.info.Config.Image.split(':').pop()}</div></td>
1754+
<td><div class="status-version">${runtimeEntry.info.State.manager === 'composeman' ? `<span class="folder-update-text"><i class="fa fa-docker fa-fw"></i> ${$.i18n('compose')}</span>` : runtimeEntry.info.State.manager !== 'dockerman' ? `<span class="folder-update-text"><i class="fa fa-docker fa-fw"></i> ${$.i18n('third-party')}</span>` : runtimeEntry.info.State.Updated !== false ? `<span class="green-text folder-update-text"><i class="fa fa-check fa-fw"></i>${$.i18n('up-to-date')}</span>${tooltipForceUpdateHtml}` : `<span class="orange-text folder-update-text" style="white-space:nowrap;"><i class="fa fa-flash fa-fw"></i>${$.i18n('update-ready')}</span><br><a class="exec" onclick="hideAllTips(); updateContainer('${runtimeEntry.info.Name}');"><span style="white-space:nowrap;"><i class="fa fa-cloud-download fa-fw"></i>${$.i18n('apply-update')}</span></a>`}<br><i class="fa fa-info-circle fa-fw"></i> ${runtimeEntry.info.Config.Image.split(':').pop()}</div></td>
17511755
<td><div class="status-stats"><span class="cpu-${ct.shortId}">0%</span><div class="usage-disk mm"><span id="cpu-${ct.shortId}" style="width: 0%;"></span><span></span></div><br><span class="mem-${ct.shortId}">0 / 0</span></div></td>
17521756
<td><div class="status-autostart"><input type="checkbox" style="display:none" class="staus-autostart-checkbox"></div></td>
17531757
</tr></tbody>

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/scripts/folderviewplus.support-bundle-browser.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,35 @@
2525
? deps.storageKeys
2626
: {};
2727

28+
const readCookieValue = (name) => {
29+
const safeName = String(name || '').trim();
30+
const rawCookie = String(root?.document?.cookie || '');
31+
if (!safeName || !rawCookie) {
32+
return '';
33+
}
34+
const prefix = `${safeName}=`;
35+
const parts = rawCookie.split(';');
36+
for (const part of parts) {
37+
const candidate = String(part || '').trim();
38+
if (candidate.startsWith(prefix)) {
39+
try {
40+
return decodeURIComponent(candidate.slice(prefix.length));
41+
} catch (_error) {
42+
return candidate.slice(prefix.length);
43+
}
44+
}
45+
}
46+
return '';
47+
};
48+
49+
const normalizeDockerListViewMode = (value) => {
50+
const raw = String(value || '').trim().toLowerCase();
51+
if (raw === 'advanced' || raw === 'basic') {
52+
return raw;
53+
}
54+
return null;
55+
};
56+
2857
const normalizeAssetVersionToken = (value) => {
2958
const raw = String(value || '').trim();
3059
if (!raw || raw === '0' || raw === 'null' || raw === 'undefined' || raw === 'false') {
@@ -51,6 +80,7 @@
5180
const collectClientStorageDiagnostics = () => ({
5281
localStorageAvailable: clientStorageIsAvailable('localStorage'),
5382
sessionStorageAvailable: clientStorageIsAvailable('sessionStorage'),
83+
dockerListViewModeCookie: normalizeDockerListViewMode(readCookieValue('docker_listview_mode')),
5484
folderEditorDebug: {
5585
launchPresent: Boolean(readClientDiagnosticsStorageRecord(storageKeys.launch || '')),
5686
bootstrapPresent: Boolean(readClientDiagnosticsStorageRecord(storageKeys.bootstrap || '')),

src/folderview.plus/usr/local/emhttp/plugins/folderview.plus/scripts/folderviewplus.support-bundle-telemetry.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@
217217
const collectClientStorageDiagnostics = browserCollectors?.collectClientStorageDiagnostics || (() => ({
218218
localStorageAvailable: false,
219219
sessionStorageAvailable: false,
220+
dockerListViewModeCookie: null,
220221
folderEditorDebug: {
221222
launchPresent: false,
222223
bootstrapPresent: false,

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1719,26 +1719,30 @@ function diagnosticsDockerMemberRenderExpectations(?string $manager, ?bool $upda
17191719
return [
17201720
'statusToken' => 'compose',
17211721
'action' => 'none',
1722+
'actionRequiresAdvancedView' => false,
17221723
'forceUpdateEligible' => false
17231724
];
17241725
}
17251726
if ($safeManager !== '' && $safeManager !== 'dockerman') {
17261727
return [
17271728
'statusToken' => 'thirdParty',
17281729
'action' => 'none',
1730+
'actionRequiresAdvancedView' => false,
17291731
'forceUpdateEligible' => false
17301732
];
17311733
}
17321734
if ($safeManager === 'dockerman' && $updated === false) {
17331735
return [
17341736
'statusToken' => 'updateReady',
17351737
'action' => 'applyUpdate',
1738+
'actionRequiresAdvancedView' => false,
17361739
'forceUpdateEligible' => false
17371740
];
17381741
}
17391742
return [
17401743
'statusToken' => 'upToDate',
17411744
'action' => 'forceUpdate',
1745+
'actionRequiresAdvancedView' => true,
17421746
'forceUpdateEligible' => true
17431747
];
17441748
}
@@ -2203,7 +2207,12 @@ function diagnosticsBuildSupportBundleBuildIdentitySection(array $diagnostics):
22032207
$buildMetadata = diagnosticsReadSupportBundleBuildMetadata();
22042208
$manifestMetadata = diagnosticsResolveSupportBundleManifestMetadata();
22052209
$sourceCommitSha = trim((string)($buildMetadata['sourceCommitSha'] ?? ''));
2210+
$headCommitSha = trim((string)($buildMetadata['headCommitSha'] ?? ''));
22062211
$sourceTreeSha = trim((string)($buildMetadata['sourceTreeSha'] ?? ''));
2212+
$sourceSnapshotMode = trim((string)($buildMetadata['sourceSnapshotMode'] ?? ''));
2213+
$sourceCommitExact = array_key_exists('sourceCommitExact', $buildMetadata)
2214+
? filter_var($buildMetadata['sourceCommitExact'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE)
2215+
: null;
22072216
$sourceBranch = trim((string)($buildMetadata['sourceBranch'] ?? diagnosticsResolveSupportBundleChannel()));
22082217
$buildManifestUrl = trim((string)($buildMetadata['manifestUrl'] ?? ''));
22092218
$buildArchiveUrl = trim((string)($buildMetadata['archiveUrl'] ?? ''));
@@ -2223,7 +2232,12 @@ function diagnosticsBuildSupportBundleBuildIdentitySection(array $diagnostics):
22232232
: diagnosticsResolveSupportBundleChannel(),
22242233
'sourceBranch' => $sourceBranch !== '' ? $sourceBranch : null,
22252234
'sourceCommitSha' => $sourceCommitSha !== '' ? $sourceCommitSha : null,
2235+
'headCommitSha' => $headCommitSha !== '' ? $headCommitSha : null,
22262236
'sourceTreeSha' => $sourceTreeSha !== '' ? $sourceTreeSha : null,
2237+
'sourceSnapshotMode' => in_array($sourceSnapshotMode, ['head', 'index', 'worktree', 'unknown'], true)
2238+
? $sourceSnapshotMode
2239+
: null,
2240+
'sourceCommitExact' => is_bool($sourceCommitExact) ? $sourceCommitExact : null,
22272241
'packageVersion' => trim((string)($buildMetadata['packageVersion'] ?? '')) ?: (string)($diagnostics['pluginVersion'] ?? readInstalledVersion()),
22282242
'manifestPath' => $manifestMetadata['manifestPath'] ?? null,
22292243
'manifestPathHash' => $manifestMetadata['manifestPathHash'] ?? null,
@@ -2365,6 +2379,9 @@ function diagnosticsBuildSupportBundleRuntimeEntityDetails(string $type, array $
23652379
'action' => in_array((string)($entry['renderExpectations']['action'] ?? ''), ['none', 'applyUpdate', 'forceUpdate'], true)
23662380
? (string)$entry['renderExpectations']['action']
23672381
: 'none',
2382+
'actionRequiresAdvancedView' => array_key_exists('actionRequiresAdvancedView', (array)($entry['renderExpectations'] ?? []))
2383+
? (bool)$entry['renderExpectations']['actionRequiresAdvancedView']
2384+
: ((string)($entry['renderExpectations']['action'] ?? '') === 'forceUpdate'),
23682385
'forceUpdateEligible' => (bool)($entry['renderExpectations']['forceUpdateEligible'] ?? false)
23692386
]
23702387
];
@@ -2413,7 +2430,9 @@ function diagnosticsBuildSupportBundleRuntimeTypeSection(string $type, array $ty
24132430
'action' => in_array((string)($folder['renderExpectations']['action'] ?? ''), ['none', 'applyUpdate', 'forceUpdate'], true)
24142431
? (string)$folder['renderExpectations']['action']
24152432
: 'none',
2416-
'actionRequiresAdvancedView' => (bool)($folder['renderExpectations']['actionRequiresAdvancedView'] ?? false),
2433+
'actionRequiresAdvancedView' => array_key_exists('actionRequiresAdvancedView', (array)($folder['renderExpectations'] ?? []))
2434+
? (bool)$folder['renderExpectations']['actionRequiresAdvancedView']
2435+
: in_array((string)($folder['renderExpectations']['action'] ?? ''), ['applyUpdate', 'forceUpdate'], true),
24172436
'forceUpdateEligible' => (bool)($folder['renderExpectations']['forceUpdateEligible'] ?? false),
24182437
'managerTypes' => array_values(array_filter(array_map('strval', is_array($folder['renderExpectations']['managerTypes'] ?? null) ? $folder['renderExpectations']['managerTypes'] : []), static function ($value): bool {
24192438
return trim($value) !== '';

tests/docker-update-status-regression.test.mjs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,9 @@ test('docker runtime sync rewrites both hidden and expanded member rows', () =>
173173
assert.match(dockerPreviewActionsJs, /const findDockerFolderMemberRow = \(id,\s*containerName\) => \{/);
174174
assert.match(dockerPreviewActionsJs, /tr\.folder-id-\$\{folderId\} div\.folder-storage > tr, tr\.folder-\$\{folderId\}-element/);
175175
});
176+
177+
test('docker tooltip update action also respects the Docker advanced/basic cookie', () => {
178+
assert.match(dockerJs, /const tooltipShowAdvanced = \$\.cookie\('docker_listview_mode'\) == 'advanced';/);
179+
assert.match(dockerJs, /const tooltipForceUpdateHtml = tooltipShowAdvanced/);
180+
assert.match(dockerJs, /tooltipForceUpdateHtml/);
181+
});

0 commit comments

Comments
 (0)