Files
jukebox_maker/web/templates/dashboard.html
T
mchusandClaude Sonnet 4.6 29f3ad9576 Add jukebox_maker web app v1.0
Go web application for filling USB drives with media files.
Runs in Docker on Unraid with /media, /mnt/usb, /config volumes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 21:33:43 +03:00

138 lines
5.2 KiB
HTML

{{define "content"}}
<section class="panel">
<h2>Накопитель</h2>
<table class="kv-table">
<tbody>
<tr>
<th>Статус</th>
<td id="diskState"><span class="badge badge-unknown">Не подключён</span></td>
</tr>
<tr id="rowDiskID" class="hidden">
<th>ID диска</th>
<td><span class="mono" id="valDiskID"></span></td>
</tr>
<tr id="rowTotal" class="hidden">
<th>Всего на диске</th>
<td id="valTotal"></td>
</tr>
<tr id="rowFree" class="hidden">
<th>Свободно</th>
<td id="valFree"></td>
</tr>
</tbody>
</table>
</section>
<section class="panel hidden" id="progressPanel">
<h2>Копирование</h2>
<div style="padding:14px 16px">
<div class="progress-bar-bg">
<div class="progress-bar-fill" id="progressFill" style="width:0%"></div>
</div>
<div class="progress-label" id="progressMsg">Подготовка…</div>
</div>
</section>
<div class="btn-row" style="background:transparent;border:none;padding:0;margin-bottom:24px">
<button class="button-primary" id="btnStart" onclick="startCopy()" disabled>▶ Запустить копирование</button>
<button class="button-danger hidden" id="btnCancel" onclick="cancelCopy()">✕ Отменить</button>
</div>
<script>
let pollInterval = null;
let activeTaskId = null;
async function refreshDisk() {
try {
const r = await fetch('/api/disk');
if (!r.ok) return;
const d = await r.json();
const labels = { absent: 'Не подключён', foreign: 'Незнакомый диск', known: 'Диск подключён' };
const cls = { absent: 'badge-unknown', foreign: 'badge-warn', known: 'badge-ok' };
document.getElementById('diskState').innerHTML =
`<span class="badge ${cls[d.state]||'badge-unknown'}">${labels[d.state]||'—'}</span>`;
const known = d.state === 'known';
['rowDiskID','rowTotal','rowFree'].forEach(id =>
document.getElementById(id).classList.toggle('hidden', !known));
if (known) {
document.getElementById('valDiskID').textContent = d.disk_id;
document.getElementById('valTotal').textContent = fmtBytes(d.total_bytes);
document.getElementById('valFree').textContent = fmtBytes(d.free_bytes);
}
const hasTask = !!d.active_task_id;
document.getElementById('btnStart').disabled = !known || hasTask;
document.getElementById('btnStart').classList.toggle('hidden', hasTask);
document.getElementById('btnCancel').classList.toggle('hidden', !hasTask);
document.getElementById('progressPanel').classList.toggle('hidden', !hasTask);
if (d.active_task_id && d.active_task_id !== activeTaskId) {
activeTaskId = d.active_task_id;
startTaskPoll(activeTaskId);
}
if (!d.active_task_id && activeTaskId) {
activeTaskId = null; stopTaskPoll();
document.getElementById('progressPanel').classList.add('hidden');
}
} catch(e) {}
}
function startTaskPoll(id) { stopTaskPoll(); pollInterval = setInterval(() => pollTask(id), 1500); }
function stopTaskPoll() { if (pollInterval) { clearInterval(pollInterval); pollInterval = null; } }
async function pollTask(id) {
try {
const r = await fetch('/api/tasks/' + id);
if (!r.ok) return;
const t = await r.json();
document.getElementById('progressFill').style.width = t.progress + '%';
document.getElementById('progressMsg').textContent = t.message || '…';
if (['success','failed','canceled'].includes(t.status)) {
stopTaskPoll(); activeTaskId = null;
document.getElementById('btnStart').disabled = false;
document.getElementById('btnStart').classList.remove('hidden');
document.getElementById('btnCancel').classList.add('hidden');
document.getElementById('progressPanel').classList.add('hidden');
if (t.status === 'success') toast(t.message || 'Готово', 'ok');
if (t.status === 'failed') toast('Ошибка: ' + t.error, 'error');
if (t.status === 'canceled') toast('Копирование отменено', 'error');
refreshDisk();
}
} catch(e) {}
}
async function startCopy() {
document.getElementById('btnStart').disabled = true;
try {
const r = await fetch('/api/copy/start', { method: 'POST' });
const d = await r.json();
if (!r.ok) {
toast(d.error || 'Ошибка запуска', 'error');
document.getElementById('btnStart').disabled = false;
return;
}
activeTaskId = d.task_id;
document.getElementById('btnStart').classList.add('hidden');
document.getElementById('btnCancel').classList.remove('hidden');
document.getElementById('progressPanel').classList.remove('hidden');
document.getElementById('progressFill').style.width = '0%';
document.getElementById('progressMsg').textContent = 'Подготовка…';
startTaskPoll(activeTaskId);
} catch(e) {
toast('Ошибка связи', 'error');
document.getElementById('btnStart').disabled = false;
}
}
async function cancelCopy() {
try { await fetch('/api/copy/cancel', { method: 'POST' }); toast('Отмена…', 'ok'); } catch(e) {}
}
refreshDisk();
setInterval(refreshDisk, 5000);
</script>
{{end}}