wip: app-deploy dependency tracking (incomplete — needs endpoint wiring)
Half-finished feature for declaring and resolving app dependencies
when deploying. Preserved here for later finishing.
What's done:
- app-templates.js: dependsOn declarations on 7 templates
(sonarr, radarr, lidarr, readarr, bazarr, overseerr, tautulli).
- routes/apps/deploy.js: helper functions checkDependencies(),
topologicalSortTemplates(), buildDefaultDepConfig().
- routes/recipes/deploy.js: wait-for-health between recipe components
via appsHelpers.waitForHealthCheck() (verify export exists).
- status/js/app-selector.js: dependency-warning modal injected into
app-selector flow, with a "deploy with deps" checkbox.
What's missing (blockers for merge):
- POST /api/v1/apps/check-dependencies endpoint — frontend calls it
(app-selector.js around line 395) but the route is never registered.
Helper functions exist; just need to expose them. Frontend currently
404s and falls back to plain deploy (line 401), so the dep-aware
flow is non-functional.
- Auto-deploy-with-dependencies handler in the modal — checkbox
exists but nothing wires the "yes deploy them" choice into actually
deploying the listed dependencies before the target app.
- No tests around topological sort behaviour (circular deps,
diamond deps, missing deps).
Lifted out of wip/cloud-backups-and-history when the cloud-backups +
resource-history features were merged to main (commit d81d118).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,22 @@
|
||||
</div>
|
||||
</div>`);
|
||||
|
||||
injectModal('dep-warning-modal', `<div id="dep-warning-modal" class="weather-modal">
|
||||
<div class="weather-modal-content" style="min-width: 460px; max-width: 600px;">
|
||||
<h3 id="dep-warning-title">Missing Dependencies</h3>
|
||||
<p id="dep-warning-subtitle" class="modal-subtitle"></p>
|
||||
<div id="dep-warning-list" style="display: flex; flex-direction: column; gap: 8px; margin: 16px 0;"></div>
|
||||
<p style="font-size: 0.82rem; color: var(--muted); margin-top: 8px;">
|
||||
Recommended: deploy these dependencies first so the app works correctly on first launch.
|
||||
</p>
|
||||
<div class="weather-modal-buttons" style="margin-top: 20px;">
|
||||
<button id="dep-warning-cancel">Cancel</button>
|
||||
<button id="dep-warning-skip">Deploy anyway</button>
|
||||
<button id="dep-warning-confirm" class="btn-accent">Deploy with dependencies</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>`);
|
||||
|
||||
injectModal('app-deploy-modal', `<div id="app-deploy-modal" class="weather-modal">
|
||||
<div class="weather-modal-content" style="min-width: 600px; max-width: 700px;">
|
||||
<h3 id="app-deploy-title">Deploy Application</h3>
|
||||
@@ -338,7 +354,7 @@
|
||||
if (isWidget) {
|
||||
option.onclick = () => toggleDashboardWidget(app, option);
|
||||
} else {
|
||||
option.onclick = () => showDeployConfig(app);
|
||||
option.onclick = () => checkAndShowDeployConfig(app);
|
||||
}
|
||||
grid.appendChild(option);
|
||||
});
|
||||
@@ -375,6 +391,93 @@
|
||||
showNotification(`${app.name} widget ${newState ? 'enabled' : 'disabled'}`, 'success', 2000);
|
||||
}
|
||||
|
||||
// Check declared dependencies for an app before opening the deploy modal.
|
||||
// If any are missing, show the dep-warning-modal so the user can pick which to auto-deploy.
|
||||
async function checkAndShowDeployConfig(appTemplate) {
|
||||
// Reset any leftover dep selection from a previous flow
|
||||
delete appTemplate._deployDeps;
|
||||
|
||||
let depResult = null;
|
||||
try {
|
||||
const resp = await secureFetch('/api/v1/apps/check-dependencies', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ appId: appTemplate.id })
|
||||
});
|
||||
depResult = await resp.json();
|
||||
} catch (e) {
|
||||
// Network/server error — fall through to normal deploy flow
|
||||
console.warn('Dep check failed, continuing without it:', e);
|
||||
return showDeployConfig(appTemplate);
|
||||
}
|
||||
|
||||
// No deps declared, or all already deployed
|
||||
if (!depResult || !Array.isArray(depResult.missing) || depResult.missing.length === 0) {
|
||||
return showDeployConfig(appTemplate);
|
||||
}
|
||||
|
||||
// Show warning modal
|
||||
const depModal = document.getElementById('dep-warning-modal');
|
||||
const titleEl = document.getElementById('dep-warning-title');
|
||||
const subtitleEl = document.getElementById('dep-warning-subtitle');
|
||||
const listEl = document.getElementById('dep-warning-list');
|
||||
const cancelBtn = document.getElementById('dep-warning-cancel');
|
||||
const skipBtn = document.getElementById('dep-warning-skip');
|
||||
const confirmBtn = document.getElementById('dep-warning-confirm');
|
||||
|
||||
titleEl.textContent = `${appTemplate.name} has missing dependencies`;
|
||||
subtitleEl.textContent = `${appTemplate.name} works best when these apps are deployed first:`;
|
||||
|
||||
// Render checkboxes for each missing dep
|
||||
listEl.innerHTML = depResult.missingDetails.map(d => `
|
||||
<label class="radio-option" style="display: flex; align-items: center; gap: 10px; cursor: pointer;">
|
||||
<input type="checkbox" class="dep-warning-checkbox" value="${escapeHtml(d.id)}" checked style="margin: 0;" />
|
||||
<span style="font-size: 1.2rem;">${escapeHtml(d.icon || '📦')}</span>
|
||||
<div>
|
||||
<div class="fw-500">${escapeHtml(d.name)}</div>
|
||||
<div class="text-hint">Not deployed</div>
|
||||
</div>
|
||||
</label>
|
||||
`).join('');
|
||||
|
||||
// Close the app selector behind it
|
||||
modal.classList.remove('show');
|
||||
depModal.classList.add('show');
|
||||
|
||||
// Helper to clean up event listeners after one of the buttons is pressed
|
||||
const cleanup = () => {
|
||||
cancelBtn.onclick = null;
|
||||
skipBtn.onclick = null;
|
||||
confirmBtn.onclick = null;
|
||||
};
|
||||
|
||||
cancelBtn.onclick = () => {
|
||||
cleanup();
|
||||
depModal.classList.remove('show');
|
||||
// Re-open app selector so user can pick something else
|
||||
modal.classList.add('show');
|
||||
};
|
||||
|
||||
skipBtn.onclick = () => {
|
||||
cleanup();
|
||||
depModal.classList.remove('show');
|
||||
// Continue to normal deploy flow without any deps
|
||||
showDeployConfig(appTemplate);
|
||||
};
|
||||
|
||||
confirmBtn.onclick = () => {
|
||||
// Collect checked deps
|
||||
const chosen = Array.from(listEl.querySelectorAll('.dep-warning-checkbox'))
|
||||
.filter(cb => cb.checked)
|
||||
.map(cb => cb.value);
|
||||
cleanup();
|
||||
depModal.classList.remove('show');
|
||||
// Stash chosen deps on the template; addAppToGrid will pick them up
|
||||
if (chosen.length > 0) appTemplate._deployDeps = chosen;
|
||||
showDeployConfig(appTemplate);
|
||||
};
|
||||
}
|
||||
|
||||
// Show deployment configuration modal
|
||||
async function showDeployConfig(appTemplate) {
|
||||
const deployModal = document.getElementById('app-deploy-modal');
|
||||
@@ -765,6 +868,11 @@
|
||||
}
|
||||
};
|
||||
|
||||
// Include user-selected dependencies (from dep warning modal)
|
||||
if (Array.isArray(appTemplate._deployDeps) && appTemplate._deployDeps.length > 0) {
|
||||
apiDeployConfig.deployDependencies = appTemplate._deployDeps;
|
||||
}
|
||||
|
||||
// Add existing container info if using existing
|
||||
if (usingExisting) {
|
||||
apiDeployConfig.config.useExisting = true;
|
||||
|
||||
Reference in New Issue
Block a user