Fix proxy modals never saving — all were passing callbacks as footerHtml

Nova.modal(title, body, footerHtml) expects an HTML string for the third
parameter. proxyAddHost, proxyEditHost, proxySwitchLocal, and proxyUninstall
were all passing async functions instead, which got stringified as garbage
text with no actual buttons rendered.

Each modal now gets proper Cancel + action button footer HTML, with the
save/action logic wired via addEventListener after modal creation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-09 13:09:26 +00:00
parent a900c5d490
commit 7e89ab6709
+40 -14
View File
@@ -2749,7 +2749,7 @@ window.proxySync = async () => {
}; };
window.proxyAddHost = () => { window.proxyAddHost = () => {
Nova.modal('Add Proxy Host', ` const ov = Nova.modal('Add Proxy Host', `
<div class="form-group"><label>Domain</label> <div class="form-group"><label>Domain</label>
<input id="ph-domain" type="text" placeholder="example.com" class="form-control"></div> <input id="ph-domain" type="text" placeholder="example.com" class="form-control"></div>
<div class="form-group"><label>Upstream URL</label> <div class="form-group"><label>Upstream URL</label>
@@ -2759,16 +2759,23 @@ window.proxyAddHost = () => {
<label><input type="checkbox" id="ph-ssl"> Enable SSL</label></div> <label><input type="checkbox" id="ph-ssl"> Enable SSL</label></div>
<div class="form-group"><label>Notes (optional)</label> <div class="form-group"><label>Notes (optional)</label>
<input id="ph-notes" type="text" class="form-control"></div> <input id="ph-notes" type="text" class="form-control"></div>
`, async () => { `,
`<button class="btn btn-ghost" onclick="this.closest('.modal-overlay').remove()">Cancel</button>
<button class="btn btn-primary" id="ph-save-btn">Add Host</button>`
);
ov.querySelector('#ph-save-btn').addEventListener('click', async () => {
const domain = document.getElementById('ph-domain')?.value?.trim(); const domain = document.getElementById('ph-domain')?.value?.trim();
const upstream = document.getElementById('ph-upstream')?.value?.trim(); const upstream = document.getElementById('ph-upstream')?.value?.trim();
if (!domain || !upstream) { Nova.toast('Domain and upstream required', 'error'); return; } if (!domain || !upstream) { Nova.toast('Domain and upstream required', 'error'); return; }
const btn = ov.querySelector('#ph-save-btn');
btn.disabled = true; btn.textContent = 'Adding…';
const r = await Nova.api('proxy', 'hosts', { const r = await Nova.api('proxy', 'hosts', {
method: 'POST', method: 'POST',
body: { domain, upstream, ssl_enabled: document.getElementById('ph-ssl')?.checked ? 1 : 0 } body: { domain, upstream, ssl_enabled: document.getElementById('ph-ssl')?.checked ? 1 : 0 }
}); });
Nova.toast(r?.success ? 'Host added' : (r?.message || 'Failed'), r?.success ? 'success' : 'error'); Nova.toast(r?.success ? 'Host added' : (r?.message || 'Failed'), r?.success ? 'success' : 'error');
if (r?.success) Nova.loadPage('nginx-proxy', window._novaPages); if (r?.success) { ov.remove(); Nova.loadPage('nginx-proxy', window._novaPages); }
else { btn.disabled = false; btn.textContent = 'Add Host'; }
}); });
}; };
@@ -2777,7 +2784,7 @@ window.proxyEditHost = async (id) => {
const hosts = hostsR?.data || (Array.isArray(hostsR) ? hostsR : []); const hosts = hostsR?.data || (Array.isArray(hostsR) ? hostsR : []);
const h = hosts.find(x => x.id == id); const h = hosts.find(x => x.id == id);
if (!h) return; if (!h) return;
Nova.modal('Edit Proxy Host', ` const ov = Nova.modal('Edit Proxy Host', `
<div class="form-group"><label>Domain</label> <div class="form-group"><label>Domain</label>
<input id="phe-domain" type="text" value="${Nova.escHtml(h.domain)}" class="form-control"></div> <input id="phe-domain" type="text" value="${Nova.escHtml(h.domain)}" class="form-control"></div>
<div class="form-group"><label>Upstream URL</label> <div class="form-group"><label>Upstream URL</label>
@@ -2787,7 +2794,13 @@ window.proxyEditHost = async (id) => {
<div class="form-group"><label>Custom Nginx Config (overrides auto-generated)</label> <div class="form-group"><label>Custom Nginx Config (overrides auto-generated)</label>
<textarea id="phe-custom" rows="6" class="form-control" style="font-family:monospace;font-size:0.78rem">${Nova.escHtml(h.custom_config || '')}</textarea> <textarea id="phe-custom" rows="6" class="form-control" style="font-family:monospace;font-size:0.78rem">${Nova.escHtml(h.custom_config || '')}</textarea>
<small class="text-muted">Leave blank to use auto-generated config</small></div> <small class="text-muted">Leave blank to use auto-generated config</small></div>
`, async () => { `,
`<button class="btn btn-ghost" onclick="this.closest('.modal-overlay').remove()">Cancel</button>
<button class="btn btn-primary" id="phe-save-btn">Save Changes</button>`
);
ov.querySelector('#phe-save-btn').addEventListener('click', async () => {
const btn = ov.querySelector('#phe-save-btn');
btn.disabled = true; btn.textContent = 'Saving…';
const r = await Nova.api('proxy', 'host', { const r = await Nova.api('proxy', 'host', {
method: 'PUT', method: 'PUT',
body: { id, body: { id,
@@ -2798,7 +2811,8 @@ window.proxyEditHost = async (id) => {
} }
}); });
Nova.toast(r?.success ? 'Updated' : (r?.message || 'Failed'), r?.success ? 'success' : 'error'); Nova.toast(r?.success ? 'Updated' : (r?.message || 'Failed'), r?.success ? 'success' : 'error');
if (r?.success) Nova.loadPage('nginx-proxy', window._novaPages); if (r?.success) { ov.remove(); Nova.loadPage('nginx-proxy', window._novaPages); }
else { btn.disabled = false; btn.textContent = 'Save Changes'; }
}); });
}; };
@@ -2892,7 +2906,7 @@ window.proxySetupInstructions = async () => {
}; };
window.proxySwitchLocal = () => { window.proxySwitchLocal = () => {
Nova.modal('Enable Local Nginx Proxy', ` const slOv = Nova.modal('Enable Local Nginx Proxy', `
<p style="margin-bottom:1rem">Nginx will be installed on <em>this server</em> and take over ports 80/443. Apache moves to an internal port and keeps serving all PHP sites — end users see no change.</p> <p style="margin-bottom:1rem">Nginx will be installed on <em>this server</em> and take over ports 80/443. Apache moves to an internal port and keeps serving all PHP sites — end users see no change.</p>
<div style="background:var(--bg-secondary);padding:0.75rem;border-radius:6px;font-size:0.85rem;margin-bottom:1rem"> <div style="background:var(--bg-secondary);padding:0.75rem;border-radius:6px;font-size:0.85rem;margin-bottom:1rem">
<strong>What will happen:</strong><br> <strong>What will happen:</strong><br>
@@ -2909,7 +2923,12 @@ window.proxySwitchLocal = () => {
<input id="sl-port" type="number" class="form-control" value="8090" min="1024" max="65535" <input id="sl-port" type="number" class="form-control" value="8090" min="1024" max="65535"
oninput="document.getElementById('sl-port-preview').textContent=this.value"> oninput="document.getElementById('sl-port-preview').textContent=this.value">
</div> </div>
`, () => { `,
`<button class="btn btn-ghost" onclick="this.closest('.modal-overlay').remove()">Cancel</button>
<button class="btn btn-primary" id="sl-switch-btn">Switch Now</button>`
);
slOv.querySelector('#sl-switch-btn').addEventListener('click', () => {
slOv.remove();
const port = parseInt(document.getElementById('sl-port')?.value) || 8090; const port = parseInt(document.getElementById('sl-port')?.value) || 8090;
const ov = Nova.modal('Switching to Local Proxy Mode', ` const ov = Nova.modal('Switching to Local Proxy Mode', `
<p style="color:var(--text-muted);margin-bottom:0.75rem">Moving Apache to port ${port} and starting nginx on 80/443…</p> <p style="color:var(--text-muted);margin-bottom:0.75rem">Moving Apache to port ${port} and starting nginx on 80/443…</p>
@@ -2947,7 +2966,7 @@ window.proxySwitchLocal = () => {
}).catch(e => { log.textContent += '\n— Connection error: ' + e.message + '\n'; }); }).catch(e => { log.textContent += '\n— Connection error: ' + e.message + '\n'; });
ov.querySelector('.modal-close')?.addEventListener('click', () => { done = true; }); ov.querySelector('.modal-close')?.addEventListener('click', () => { done = true; });
}, { confirmLabel: 'Switch Now' }); });
}; };
window.proxyDisableLocal = () => { window.proxyDisableLocal = () => {
@@ -3017,18 +3036,25 @@ window.proxyRunSetup = () => {
}; };
window.proxyUninstall = () => { window.proxyUninstall = () => {
Nova.modal('Uninstall Nginx Proxy', ` const ov = Nova.modal('Uninstall Nginx Proxy', `
<p>Choose what to remove from the <strong>remote proxy VM</strong>:</p> <p>Choose what to remove from the <strong>remote proxy VM</strong>:</p>
<div class="form-group" style="margin-top:1rem"> <div class="form-group" style="margin-top:1rem">
<label><input type="radio" name="uninst" value="configs" checked> Remove proxy host configs only <small class="text-muted">(keep nginx running)</small></label><br> <label><input type="radio" name="uninst" value="configs" checked> Remove proxy host configs only <small class="text-muted">(keep nginx running)</small></label><br>
<label style="margin-top:0.5rem"><input type="radio" name="uninst" value="full"> Remove everything <small class="text-muted">(uninstall nginx, delete all configs, disable proxy mode)</small></label> <label style="margin-top:0.5rem"><input type="radio" name="uninst" value="full"> Remove everything <small class="text-muted">(uninstall nginx, delete all configs, disable proxy mode)</small></label>
</div> </div>
`, async () => { `,
const full = document.querySelector('input[name="uninst"]:checked')?.value === 'full'; `<button class="btn btn-ghost" onclick="this.closest('.modal-overlay').remove()">Cancel</button>
<button class="btn btn-danger" id="uninst-btn">Uninstall</button>`
);
ov.querySelector('#uninst-btn').addEventListener('click', async () => {
const btn = ov.querySelector('#uninst-btn');
btn.disabled = true; btn.textContent = 'Removing…';
const full = ov.querySelector('input[name="uninst"]:checked')?.value === 'full';
const r = await Nova.api('proxy', 'uninstall', { method: 'DELETE', body: { remove_nginx: full } }); const r = await Nova.api('proxy', 'uninstall', { method: 'DELETE', body: { remove_nginx: full } });
Nova.toast(r?.data?.result || r?.message || 'Done', r?.success ? 'success' : 'error'); Nova.toast(r?.data?.result || r?.message || 'Done', r?.success ? 'success' : 'error');
if (r?.success) Nova.loadPage('nginx-proxy', window._novaPages); if (r?.success) { ov.remove(); Nova.loadPage('nginx-proxy', window._novaPages); }
}, { confirmLabel: 'Uninstall', danger: true }); else { btn.disabled = false; btn.textContent = 'Uninstall'; }
});
}; };
window.proxySettings = async () => { window.proxySettings = async () => {