fix(app-store): harden template deploy, registry fetch, and catalogue refresh (#1250)

* fix(app-store): harden template deploy, registry fetch, and catalogue refresh

Serialize generated compose through the YAML emitter so registry-supplied values are escaped correctly instead of interpolated into hand-built lines. Cap the registry response size so an oversized or runaway catalogue cannot exhaust backend memory, and surface the fetch failure to the caller. Reload the catalogue when the active node changes, since the registry is node-scoped. Add developer-mode deploy diagnostics (counts only, no values) and extend the unit tests with YAML round-trip, LinuxServer.io mapping, and size-cap coverage.

* fix(app-store): reset node-scoped catalogue state on fetch and bound deploy diagnostics

Clear the templates list and Trivy availability at the start of each catalogue load so a failed fetch after a node switch shows the new node's empty state instead of the previous node's catalogue or scan toggle. Bound the developer-mode diagnostic template title/source length, and document that the registry cache serves the last-known-good catalogue on a transient fetch failure (the size cap still protects memory in every case).
This commit is contained in:
Anso
2026-05-29 11:46:51 -04:00
committed by GitHub
parent 2844f606cd
commit 96c5f05cdc
6 changed files with 298 additions and 185 deletions
+25 -16
View File
@@ -59,28 +59,37 @@ export function AppStoreView({ onDeploySuccess }: AppStoreViewProps) {
const [trivyAvailable, setTrivyAvailable] = useState(false);
const [sheetTab, setSheetTab] = useState<'essentials' | 'advanced'>('essentials');
// The template registry is node-scoped, so the catalogue (and Trivy
// availability) must reload when the active node changes. Both are reset at
// the start of every run so a failed fetch shows the new node's empty state
// rather than leaving the previous node's catalogue or scan toggle on
// screen. The cancelled flag drops a slow response from a previous node so
// it cannot overwrite the current node's catalogue after a fast switch.
useEffect(() => {
fetchTemplates();
let cancelled = false;
(async () => {
setLoading(true);
setTemplates([]);
setTrivyAvailable(false);
try {
const res = await apiFetch('/templates');
if (!res.ok) throw new Error('Failed to fetch templates');
const data = await res.json();
if (!cancelled) setTemplates(data || []);
} catch (err) {
if (!cancelled) toast.error(err instanceof Error ? err.message : 'Failed to load App Store');
} finally {
if (!cancelled) setLoading(false);
}
})();
apiFetch('/security/trivy-status')
.then(r => r.ok ? r.json() : null)
.then(d => { if (d) setTrivyAvailable(!!d.available); })
.then(d => { if (!cancelled && d) setTrivyAvailable(!!d.available); })
.catch((err) => {
console.error('Failed to fetch Trivy status:', err);
});
}, []);
const fetchTemplates = async () => {
try {
const res = await apiFetch('/templates');
if (!res.ok) throw new Error('Failed to fetch templates');
const data = await res.json();
setTemplates(data || []);
} catch (err) {
toast.error((err as Error).message || 'Failed to load App Store');
} finally {
setLoading(false);
}
};
return () => { cancelled = true; };
}, [activeNode?.id]);
const handleSelectTemplate = (t: Template) => {
const envsCopy = [...(t.env || [])];