// Who is signed in, plus a logout link. Hidden entirely when MUSE_AUTH=off.
fetch('/api/me').then(r => r.json()).then(d => {
if (d.user) document.getElementById('who').innerHTML =
'signed in as ' + d.user.replace(/[&<>"]/g, '') + ' log out ';
}).catch(() => {});
const $ = s => document.querySelector(s);
const esc = s => (s??'').replace(/[&<>"]/g, c => ({'&':'&','<':'<','>':'>','"':'"'}[c]));
$('#f').onsubmit = async e => {
e.preventDefault();
const q = $('#q').value.trim();
if (!q) return;
$('#go').disabled = true;
$('#results').innerHTML = '
searching MusicBrainz…
';
try {
const r = await fetch('/api/search?q=' + encodeURIComponent(q));
const { results } = await r.json();
render(results);
} catch (err) {
$('#results').innerHTML = 'search failed: ' + esc(err.message) + '
';
}
$('#go').disabled = false;
};
function render(list) {
if (!list || !list.length) {
$('#results').innerHTML = 'no matches
';
return;
}
$('#results').innerHTML = list.map((x, i) => `
${x.cover ? `
`
: '
'}
Get
`).join('');
$('#results').querySelectorAll('.get').forEach(b => {
b.onclick = async () => {
const x = list[+b.dataset.i];
b.disabled = true; b.textContent = '…';
await fetch('/api/request', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(x)
});
b.textContent = 'Queued'; b.classList.add('done');
poll();
};
});
}
$('#imprun').onclick = async () => {
const text = $('#bulk').value.trim();
if (!text) return;
const b = $('#imprun');
b.disabled = true; b.textContent = 'Queueing…';
$('#impout').innerHTML = '';
try {
const r = await fetch('/api/import', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text })
});
const d = await r.json();
$('#impout').innerHTML =
`Queued ${d.queued.length}.
` +
(d.skipped.length
? `Skipped ${d.skipped.length}: ` +
d.skipped.map(s => esc(s.line) + ' (' + esc(s.why) + ')').join('; ') + '
'
: '');
if (d.queued.length) $('#bulk').value = '';
poll();
} catch (e) {
$('#impout').innerHTML = 'import failed: ' + esc(e.message) + '
';
}
b.disabled = false; b.textContent = 'Queue all';
};
async function poll() {
try {
const { jobs, counts, pending } = await (await fetch('/api/jobs')).json();
const active = (counts.queued || 0) + (counts.searching || 0) + (counts.downloading || 0);
const done = counts.imported || 0;
$('#qcount').textContent = active
? `${active} in flight${pending ? ` · ${pending} waiting` : ''}`
: (done ? `${done} imported` : '');
// Progress only means something during a batch.
const bar = $('#bar'), total = active + done;
bar.classList.toggle('hidden', !(active && total));
if (active && total)
bar.firstElementChild.style.width = (done / total * 100).toFixed(1) + '%';
$('#jobs').innerHTML = jobs.length ? jobs.map(j => `
${j.state === 'failed'
? `
Retry ` : ''}
${esc(j.state)}
`).join('') : 'nothing requested yet
';
$('#jobs').querySelectorAll('.retry').forEach(b => {
b.onclick = async () => {
b.disabled = true;
await fetch('/api/retry/' + b.dataset.id, { method: 'POST' });
poll();
};
});
} catch {}
}
poll();
setInterval(poll, 4000);