Skip to content

Commit 72398df

Browse files
author
FolderView Plus Test
committed
Expand support bundle render diagnostics
1 parent 4a15ec4 commit 72398df

6 files changed

Lines changed: 262 additions & 10 deletions

File tree

archive/folderview.plus-2026.04.05.09.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+
df4deb9beff0cf6fd46b3bc2aa3d75e03226bf87093a8e4fdb1dcd23c0f2d385 folderview.plus-2026.04.08.02.txz

folderview.plus.plg

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,19 @@
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.01">
10-
<!ENTITY md5 "f8bd49f4ec7fe2d7d3e59b38e23205ee">
9+
<!ENTITY version "2026.04.08.02">
10+
<!ENTITY md5 "c4b6b577c7e5cbd144bfe2c0167e57ea">
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.02
17+
- Fix: Docker runtime rows, folder state, and container interactions.
18+
- Fix: Server endpoints, runtime payloads, and persistence or validation paths.
19+
- Quality: Release automation, CI smoke coverage, and packaging guards.
20+
21+
1622
###2026.04.08.01
1723
- Fix: Docker advanced view now resyncs both hidden and expanded folder member rows from per-container runtime state, so `force update` stays visible for dockerman-managed containers after expand.
1824
- Feature: Support bundle runtime diagnostics now capture per-entity manager, managed/update state, and manager counts to make update-column issues diagnosable from one export.

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

Lines changed: 189 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,6 +1388,9 @@ function diagnosticsBuildStateSnapshot(string $type, array $folders, array $pref
13881388
$started = 0;
13891389
$paused = 0;
13901390
$stopped = 0;
1391+
$folderManagerTypes = [];
1392+
$folderManagedCount = 0;
1393+
$folderUpToDate = true;
13911394
foreach ($members as $name) {
13921395
$item = $infoByName[$name] ?? null;
13931396
if (!is_array($item)) {
@@ -1404,6 +1407,19 @@ function diagnosticsBuildStateSnapshot(string $type, array $folders, array $pref
14041407
} else {
14051408
$stopped++;
14061409
}
1410+
if ($type === 'docker') {
1411+
$memberManager = trim((string)($item['info']['State']['manager'] ?? ($item['manager'] ?? '')));
1412+
if ($memberManager !== '') {
1413+
$folderManagerTypes[$memberManager] = true;
1414+
}
1415+
if ($memberManager === 'dockerman') {
1416+
$folderManagedCount++;
1417+
$memberUpdated = $item['info']['State']['Updated'] ?? ($item['Updated'] ?? null);
1418+
if ($memberUpdated === false) {
1419+
$folderUpToDate = false;
1420+
}
1421+
}
1422+
}
14071423
}
14081424

14091425
$total = count($members);
@@ -1423,6 +1439,8 @@ function diagnosticsBuildStateSnapshot(string $type, array $folders, array $pref
14231439
} elseif ($statusKind === 'stopped') {
14241440
$badgeVisible = $showStoppedBadge;
14251441
}
1442+
$hideUpdateColumn = diagnosticsFolderSettingBool(is_array($folder) ? $folder : [], 'update_column', false);
1443+
$previewUpdate = diagnosticsFolderSettingBool(is_array($folder) ? $folder : [], 'preview_update', false);
14261444

14271445
$folderStatusTotals[$statusKind]++;
14281446
$memberTotals['started'] += $started;
@@ -1448,8 +1466,21 @@ function diagnosticsBuildStateSnapshot(string $type, array $folders, array $pref
14481466
'text' => $statusText,
14491467
'badgeVisible' => $badgeVisible,
14501468
'colors' => diagnosticsFolderStatusColors(is_array($folder) ? $folder : [])
1469+
],
1470+
'settings' => [
1471+
'previewUpdate' => $previewUpdate,
1472+
'hideUpdateColumn' => $hideUpdateColumn
14511473
]
14521474
];
1475+
if ($type === 'docker') {
1476+
$snapshotFolders[$safeFolderId]['renderExpectations'] = diagnosticsDockerFolderRenderExpectations(
1477+
array_keys($folderManagerTypes),
1478+
$folderUpToDate,
1479+
$folderManagedCount,
1480+
$showUpdateBadge,
1481+
$hideUpdateColumn
1482+
);
1483+
}
14531484
}
14541485

14551486
$entityDetails = [];
@@ -1477,6 +1508,22 @@ function diagnosticsBuildStateSnapshot(string $type, array $folders, array $pref
14771508
if ($entityDetailsTotal > $entityDetailsMaxEntries) {
14781509
continue;
14791510
}
1511+
$provenance = [
1512+
'managerSource' => 'missing',
1513+
'updateSource' => 'missing'
1514+
];
1515+
$renderExpectations = [
1516+
'statusToken' => $updated === true ? 'upToDate' : ($updated === false ? 'available' : 'unknown'),
1517+
'action' => 'none',
1518+
'forceUpdateEligible' => false
1519+
];
1520+
if ($type === 'docker') {
1521+
$provenance = [
1522+
'managerSource' => diagnosticsDockerStateFieldSource($item, 'manager'),
1523+
'updateSource' => diagnosticsDockerStateFieldSource($item, 'updated')
1524+
];
1525+
$renderExpectations = diagnosticsDockerMemberRenderExpectations($manager !== '' ? $manager : null, $updated);
1526+
}
14801527
$entityDetails[] = [
14811528
'name' => normalizeDiagnosticsPrivacyMode($privacyMode) === 'full' ? (string)$name : null,
14821529
'nameHash' => diagnosticsHashShort((string)$name),
@@ -1485,7 +1532,9 @@ function diagnosticsBuildStateSnapshot(string $type, array $folders, array $pref
14851532
'manager' => $manager !== '' ? $manager : null,
14861533
'managed' => $manager === 'dockerman',
14871534
'updated' => $updated,
1488-
'updateState' => $updated === true ? 'upToDate' : ($updated === false ? 'available' : 'unknown')
1535+
'updateState' => $updated === true ? 'upToDate' : ($updated === false ? 'available' : 'unknown'),
1536+
'provenance' => $provenance,
1537+
'renderExpectations' => $renderExpectations
14891538
];
14901539
}
14911540
if (!empty($managerCounts)) {
@@ -1633,6 +1682,105 @@ function diagnosticsBuildIntegrityIssueDetail(array $integrity): string {
16331682
return '';
16341683
}
16351684

1685+
function diagnosticsFolderSettingBool(array $folder, string $key, bool $default = false): bool {
1686+
$settings = is_array($folder['settings'] ?? null) ? $folder['settings'] : [];
1687+
if (!array_key_exists($key, $settings)) {
1688+
return $default;
1689+
}
1690+
return normalizeBool($settings[$key], $default);
1691+
}
1692+
1693+
function diagnosticsDockerStateFieldSource(array $item, string $field): string {
1694+
$state = is_array($item['info']['State'] ?? null) ? $item['info']['State'] : [];
1695+
if ($field === 'manager') {
1696+
if (trim((string)($state['manager'] ?? '')) !== '') {
1697+
return 'infoState';
1698+
}
1699+
if (trim((string)($item['manager'] ?? '')) !== '') {
1700+
return 'topLevelFallback';
1701+
}
1702+
return 'missing';
1703+
}
1704+
if ($field === 'updated') {
1705+
if (is_bool($state['Updated'] ?? null)) {
1706+
return 'infoState';
1707+
}
1708+
if (is_bool($item['Updated'] ?? null)) {
1709+
return 'topLevelFallback';
1710+
}
1711+
return 'missing';
1712+
}
1713+
return 'missing';
1714+
}
1715+
1716+
function diagnosticsDockerMemberRenderExpectations(?string $manager, ?bool $updated): array {
1717+
$safeManager = trim((string)($manager ?? ''));
1718+
if ($safeManager === 'composeman') {
1719+
return [
1720+
'statusToken' => 'compose',
1721+
'action' => 'none',
1722+
'forceUpdateEligible' => false
1723+
];
1724+
}
1725+
if ($safeManager !== '' && $safeManager !== 'dockerman') {
1726+
return [
1727+
'statusToken' => 'thirdParty',
1728+
'action' => 'none',
1729+
'forceUpdateEligible' => false
1730+
];
1731+
}
1732+
if ($safeManager === 'dockerman' && $updated === false) {
1733+
return [
1734+
'statusToken' => 'updateReady',
1735+
'action' => 'applyUpdate',
1736+
'forceUpdateEligible' => false
1737+
];
1738+
}
1739+
return [
1740+
'statusToken' => 'upToDate',
1741+
'action' => 'forceUpdate',
1742+
'forceUpdateEligible' => true
1743+
];
1744+
}
1745+
1746+
function diagnosticsDockerFolderRenderExpectations(array $managerTypes, bool $upToDate, int $managedCount, bool $showUpdateBadge, bool $hideUpdateColumn): array {
1747+
$safeManagerTypes = array_values(array_filter(array_map('strval', $managerTypes), static function ($value): bool {
1748+
return trim($value) !== '';
1749+
}));
1750+
$safeManagerTypes = array_values(array_unique($safeManagerTypes));
1751+
sort($safeManagerTypes);
1752+
1753+
$hasDockerMan = in_array('dockerman', $safeManagerTypes, true);
1754+
$hasCompose = in_array('composeman', $safeManagerTypes, true);
1755+
$hasThirdParty = count(array_filter($safeManagerTypes, static function ($value): bool {
1756+
return $value !== 'dockerman' && $value !== 'composeman';
1757+
})) > 0;
1758+
1759+
$statusToken = 'upToDate';
1760+
$action = 'none';
1761+
if (!$hasDockerMan && $hasCompose && $hasThirdParty) {
1762+
$statusToken = 'composeAndThirdParty';
1763+
} elseif (!$hasDockerMan && $hasCompose) {
1764+
$statusToken = 'compose';
1765+
} elseif (!$hasDockerMan) {
1766+
$statusToken = 'thirdParty';
1767+
} elseif (!$upToDate) {
1768+
$statusToken = 'updateReady';
1769+
$action = 'applyUpdate';
1770+
} elseif ($managedCount > 0) {
1771+
$action = 'forceUpdate';
1772+
}
1773+
1774+
return [
1775+
'updateColumnVisible' => $showUpdateBadge && !$hideUpdateColumn,
1776+
'statusToken' => $statusToken,
1777+
'action' => $action,
1778+
'actionRequiresAdvancedView' => in_array($action, ['applyUpdate', 'forceUpdate'], true),
1779+
'forceUpdateEligible' => $action === 'forceUpdate',
1780+
'managerTypes' => $safeManagerTypes
1781+
];
1782+
}
1783+
16361784
function diagnosticsBuildRecommendedActions(array $typesData, array $customIcons): array {
16371785
$actions = [];
16381786
$addAction = static function (string $action, string $label, string $reason) use (&$actions): void {
@@ -2201,7 +2349,24 @@ function diagnosticsBuildSupportBundleRuntimeEntityDetails(string $type, array $
22012349
'updated' => $updated,
22022350
'updateState' => in_array((string)($entry['updateState'] ?? ''), ['available', 'upToDate', 'unknown'], true)
22032351
? (string)$entry['updateState']
2204-
: 'unknown'
2352+
: 'unknown',
2353+
'provenance' => [
2354+
'managerSource' => in_array((string)($entry['provenance']['managerSource'] ?? ''), ['infoState', 'topLevelFallback', 'missing'], true)
2355+
? (string)$entry['provenance']['managerSource']
2356+
: 'missing',
2357+
'updateSource' => in_array((string)($entry['provenance']['updateSource'] ?? ''), ['infoState', 'topLevelFallback', 'missing'], true)
2358+
? (string)$entry['provenance']['updateSource']
2359+
: 'missing'
2360+
],
2361+
'renderExpectations' => [
2362+
'statusToken' => in_array((string)($entry['renderExpectations']['statusToken'] ?? ''), ['compose', 'thirdParty', 'updateReady', 'upToDate', 'available', 'unknown'], true)
2363+
? (string)$entry['renderExpectations']['statusToken']
2364+
: 'unknown',
2365+
'action' => in_array((string)($entry['renderExpectations']['action'] ?? ''), ['none', 'applyUpdate', 'forceUpdate'], true)
2366+
? (string)$entry['renderExpectations']['action']
2367+
: 'none',
2368+
'forceUpdateEligible' => (bool)($entry['renderExpectations']['forceUpdateEligible'] ?? false)
2369+
]
22052370
];
22062371
}
22072372
if ((bool)($details['truncated'] ?? false)) {
@@ -2235,6 +2400,26 @@ function diagnosticsBuildSupportBundleRuntimeTypeSection(string $type, array $ty
22352400
if (!isset($folder['members']) || !is_array($folder['members'])) {
22362401
$folder['members'] = [];
22372402
}
2403+
$folder['settings'] = [
2404+
'previewUpdate' => (bool)($folder['settings']['previewUpdate'] ?? false),
2405+
'hideUpdateColumn' => (bool)($folder['settings']['hideUpdateColumn'] ?? false)
2406+
];
2407+
if ($type === 'docker') {
2408+
$folder['renderExpectations'] = [
2409+
'updateColumnVisible' => (bool)($folder['renderExpectations']['updateColumnVisible'] ?? false),
2410+
'statusToken' => in_array((string)($folder['renderExpectations']['statusToken'] ?? ''), ['compose', 'composeAndThirdParty', 'thirdParty', 'updateReady', 'upToDate'], true)
2411+
? (string)$folder['renderExpectations']['statusToken']
2412+
: 'upToDate',
2413+
'action' => in_array((string)($folder['renderExpectations']['action'] ?? ''), ['none', 'applyUpdate', 'forceUpdate'], true)
2414+
? (string)$folder['renderExpectations']['action']
2415+
: 'none',
2416+
'actionRequiresAdvancedView' => (bool)($folder['renderExpectations']['actionRequiresAdvancedView'] ?? false),
2417+
'forceUpdateEligible' => (bool)($folder['renderExpectations']['forceUpdateEligible'] ?? false),
2418+
'managerTypes' => array_values(array_filter(array_map('strval', is_array($folder['renderExpectations']['managerTypes'] ?? null) ? $folder['renderExpectations']['managerTypes'] : []), static function ($value): bool {
2419+
return trim($value) !== '';
2420+
}))
2421+
];
2422+
}
22382423
if (($redactor['mode'] ?? FVPLUS_DIAGNOSTICS_DEFAULT_PRIVACY) === 'full') {
22392424
$folder['members']['items'] = array_slice(array_map('strval', $memberItems), 0, 40);
22402425
} else {
@@ -2254,7 +2439,8 @@ static function ($name) use (&$redactor, $fieldPath): ?string {
22542439
$prefsPath = (string)($typeData['prefsPath'] ?? '');
22552440

22562441
return [
2257-
'hostPageDetected' => true,
2442+
'runtimeSnapshotAvailable' => !empty($stateSnapshot),
2443+
'snapshotSource' => 'serverDiagnostics',
22582444
'entitySummary' => [
22592445
'total' => (int)($stateSnapshot['totalItems'] ?? 0),
22602446
'assigned' => (int)($stateSnapshot['assignedItems'] ?? 0),

0 commit comments

Comments
 (0)