const $ = (sel) => document.querySelector(sel); const toast = (msg, kind = '') => { const t = $('#toast'); t.textContent = msg; t.className = 'toast show ' + kind; setTimeout(() => t.classList.remove('show'), 3000); }; const escapeHtml = (str) => (str || '').replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', }[c])); async function loadStorefront() { const res = await fetch('/api/settings'); if (res.status === 401) { location.href = '/login'; return; } const s = await res.json(); $('[name=unraid_url]').value = s.unraid_url || ''; $('[name=unraid_token]').value = s.unraid_token || ''; } async function loadKeys() { const res = await fetch('/api/asc-keys'); if (res.status === 401) { location.href = '/login'; return; } const keys = await res.json(); const container = $('#asc-keys-table'); if (!keys.length) { container.innerHTML = '

No developer accounts configured yet.

'; return; } container.innerHTML = ` ${keys.map((k) => ` `).join('')}
Team Name Team ID Key ID Issuer ID .p8 Actions
${escapeHtml(k.team_name || '')} ${escapeHtml(k.team_id)} ${escapeHtml(k.key_id)} ${escapeHtml(k.issuer_id)} ${k.p8_uploaded ? 'uploaded' : 'missing'}
`; // Wire row actions container.querySelectorAll('tr[data-team-id]').forEach((row) => { const teamId = row.dataset.teamId; row.querySelector('.p8-input').addEventListener('change', (e) => uploadP8(teamId, e.target.files[0])); row.querySelector('.test-btn').addEventListener('click', () => testKey(teamId)); row.querySelector('.delete-btn').addEventListener('click', () => deleteKey(teamId)); }); } async function uploadP8(teamId, file) { if (!file) return; const fd = new FormData(); fd.append('p8', file); const res = await fetch(`/api/asc-keys/${encodeURIComponent(teamId)}/p8`, { method: 'POST', body: fd }); const data = await res.json().catch(() => ({})); if (res.ok) { toast('.p8 uploaded', 'success'); loadKeys(); } else { toast(data.error || 'Upload failed', 'error'); } } async function testKey(teamId) { toast('Testing...', ''); const res = await fetch(`/api/asc-keys/${encodeURIComponent(teamId)}/test`, { method: 'POST' }); const data = await res.json().catch(() => ({})); if (res.ok) toast(`Team ${teamId} authenticated OK`, 'success'); else toast(data.error || 'Test failed', 'error'); } async function deleteKey(teamId) { if (!confirm(`Delete ASC key for team ${teamId}? The .p8 file will be removed from disk.`)) return; const res = await fetch(`/api/asc-keys/${encodeURIComponent(teamId)}`, { method: 'DELETE' }); const data = await res.json().catch(() => ({})); if (res.ok) { toast('Deleted', 'success'); loadKeys(); } else { toast(data.error || 'Delete failed', 'error'); } } $('#add-key-form').addEventListener('submit', async (e) => { e.preventDefault(); const fd = new FormData(e.target); const payload = Object.fromEntries(fd.entries()); const res = await fetch('/api/asc-keys', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await res.json().catch(() => ({})); if (res.ok) { toast('Saved', 'success'); e.target.reset(); loadKeys(); } else { toast(data.error || 'Save failed', 'error'); } }); $('#storefront-form').addEventListener('submit', async (e) => { e.preventDefault(); const data = { unraid_url: e.target.querySelector('[name=unraid_url]').value, unraid_token: e.target.querySelector('[name=unraid_token]').value, }; const res = await fetch('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); if (res.ok) toast('Saved', 'success'); else toast('Save failed', 'error'); }); $('#test-storefront').addEventListener('click', async () => { const res = await fetch('/api/settings/test-storefront', { method: 'POST' }); const data = await res.json(); if (res.ok) toast(`Connected -- ${data.app_count} apps on storefront`, 'success'); else toast(data.error || 'Connection failed', 'error'); }); loadStorefront(); loadKeys();