Client portal auth (Phase 1): per-project link + password gate #63

Merged
serversdown merged 21 commits from feat/portal-auth into dev 2026-06-16 14:59:58 -04:00
Showing only changes of commit 0394f4b0c8 - Show all commits
+25 -12
View File
@@ -2142,6 +2142,8 @@ document.addEventListener('DOMContentLoaded', function() {
<script>
const PA_PROJECT_ID = "{{ project_id }}";
let paEnabled = false;
function paToast(msg) { if (window.showToast) showToast(msg, 'error'); else alert(msg); }
function openPortalAccess() { document.getElementById('portal-access-modal').classList.remove('hidden'); loadPortalAccess(); }
function closePortalAccess() { document.getElementById('portal-access-modal').classList.add('hidden'); }
@@ -2153,27 +2155,38 @@ function copyField(id, btn) {
}
async function loadPortalAccess() {
const j = await (await fetch(`/projects/${PA_PROJECT_ID}/portal-access`)).json();
renderPortalAccess(j);
try {
const r = await fetch(`/projects/${PA_PROJECT_ID}/portal-access`);
if (!r.ok) throw new Error('load failed');
renderPortalAccess(await r.json());
} catch (e) { paToast('Could not load portal access.'); }
}
function renderPortalAccess(j) {
paEnabled = !!j.enabled;
const toggle = document.getElementById('pa-toggle');
const details = document.getElementById('pa-details');
toggle.textContent = j.enabled ? 'On — click to disable' : 'Off — click to enable';
toggle.textContent = paEnabled ? 'On — click to disable' : 'Off — click to enable';
toggle.className = 'px-3 py-1.5 text-sm rounded-lg border ' +
(j.enabled ? 'border-green-500 text-green-600 dark:text-green-400' : 'border-slate-300 dark:border-slate-600');
details.classList.toggle('hidden', !j.enabled);
if (j.enabled && j.link_url) document.getElementById('pa-link').value = j.link_url;
(paEnabled ? 'border-green-500 text-green-600 dark:text-green-400' : 'border-slate-300 dark:border-slate-600');
details.classList.toggle('hidden', !paEnabled);
document.getElementById('pa-link').value = (paEnabled && j.link_url) ? j.link_url : '';
}
async function togglePortalEnabled() {
const on = document.getElementById('pa-toggle').textContent.startsWith('On');
const j = await (await fetch(`/projects/${PA_PROJECT_ID}/portal-access/${on ? 'disable' : 'enable'}`, { method: 'POST' })).json();
if (on) renderPortalAccess({ enabled: false, link_url: null });
else renderPortalAccess(j);
const action = paEnabled ? 'disable' : 'enable';
try {
const r = await fetch(`/projects/${PA_PROJECT_ID}/portal-access/${action}`, { method: 'POST' });
if (!r.ok) throw new Error('toggle failed');
const j = await r.json();
renderPortalAccess(action === 'disable' ? { enabled: false, link_url: null } : j);
} catch (e) { paToast(`Could not ${action} the portal.`); }
}
async function regeneratePassword() {
const j = await (await fetch(`/projects/${PA_PROJECT_ID}/portal-access/password`, { method: 'POST' })).json();
if (j.password) { const f = document.getElementById('pa-pass'); f.value = j.password; f.placeholder = ''; }
try {
const r = await fetch(`/projects/${PA_PROJECT_ID}/portal-access/password`, { method: 'POST' });
if (!r.ok) throw new Error('password failed');
const j = await r.json();
if (j.password) { const f = document.getElementById('pa-pass'); f.value = j.password; f.placeholder = ''; }
} catch (e) { paToast('Could not generate a password.'); }
}
</script>
{% endblock %}