Files
jukebox_maker/web/templates/settings.html
Michael Chus 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

139 lines
5.9 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{define "content"}}
<form id="settingsForm" onsubmit="saveSettings(event)">
<section class="panel">
<h2>Источники копирования</h2>
<div class="source-list" id="sourceList">
<div class="text-muted" style="padding:12px 16px">Загрузка…</div>
</div>
<div class="btn-row">
<button type="button" class="button-secondary button-sm" onclick="loadSources()">↻ Обновить список</button>
</div>
</section>
<section class="panel">
<h2>Параметры копирования</h2>
<div class="form-body">
<div class="form-group">
<label class="form-label" for="reserveGB">Оставить свободным на диске (ГБ)</label>
<input class="form-input" type="number" id="reserveGB" min="0" max="1000" step="0.5" value="2">
<span class="form-hint">Копирование остановится, когда свободного места останется меньше этого значения.</span>
</div>
<div class="form-group">
<label class="form-label" for="fileSelectMode">Какие файлы копировать</label>
<select class="form-select" id="fileSelectMode" style="width:auto;max-width:420px">
<option value="new">Только новые (не копировавшиеся на этот диск)</option>
<option value="all">Все подряд</option>
</select>
<span class="form-hint">«Только новые» — пропускает файлы, уже скопированные на данный диск, даже если они были удалены с него (считаются просмотренными).</span>
</div>
<div class="form-group">
<label class="form-label" for="overwriteMode">Режим записи</label>
<select class="form-select" id="overwriteMode" style="width:auto;max-width:420px">
<option value="skip">Пропустить существующие файлы</option>
<option value="delete">Удалить наши данные с диска и перезаписать заново</option>
</select>
<span class="form-hint">«Удалить и перезаписать» — удаляет с диска всё кроме папки .jukebox, затем копирует заново.</span>
</div>
</div>
</section>
<section class="panel">
<h2>Автоматизация</h2>
<div class="form-body">
<div class="form-group">
<label style="display:flex;align-items:center;gap:8px;cursor:pointer">
<input type="checkbox" id="autoCopy" style="width:15px;height:15px;accent-color:var(--accent)">
<span class="form-label" style="margin:0">Автоматическое копирование</span>
</label>
<span class="form-hint">При обнаружении знакомого накопителя копирование запустится автоматически.</span>
</div>
</div>
</section>
<div style="display:flex;gap:8px;margin-bottom:24px">
<button type="submit" class="button-primary">Сохранить настройки</button>
<button type="button" class="button-secondary" onclick="loadSettings()">Сбросить</button>
</div>
</form>
<script>
let allSources = [];
let enabledSources = {};
async function loadSources() {
try {
const r = await fetch('/api/sources');
if (!r.ok) return;
const d = await r.json();
allSources = d.items || [];
renderSources();
} catch(e) {}
}
function renderSources() {
const el = document.getElementById('sourceList');
if (!allSources.length) {
el.innerHTML = '<div class="text-muted" style="padding:12px 16px">Папки в /media не найдены.</div>';
return;
}
el.innerHTML = allSources.map(path => {
const checked = enabledSources[path] !== false;
return `<label class="source-item">
<input type="checkbox" data-source="${path}" ${checked ? 'checked' : ''}>
<span class="source-item-name">${path}</span>
<span class="source-item-path">/media/${path}</span>
</label>`;
}).join('');
}
async function loadSettings() {
try {
const r = await fetch('/api/config');
if (!r.ok) return;
const cfg = await r.json();
document.getElementById('reserveGB').value = cfg.reserve_free_gb ?? 2;
document.getElementById('fileSelectMode').value = cfg.file_select_mode || 'new';
document.getElementById('overwriteMode').value = cfg.overwrite_mode || 'skip';
document.getElementById('autoCopy').checked = !!cfg.auto_copy;
enabledSources = {};
(cfg.sources || []).forEach(s => { enabledSources[s.path] = s.enabled; });
renderSources();
} catch(e) {}
}
async function saveSettings(e) {
e.preventDefault();
const checkboxes = document.querySelectorAll('[data-source]');
const sources = Array.from(checkboxes).map(cb => ({ path: cb.dataset.source, enabled: cb.checked }));
Object.keys(enabledSources).forEach(path => {
if (!sources.find(s => s.path === path)) sources.push({ path, enabled: false });
});
const body = {
reserve_free_gb: parseFloat(document.getElementById('reserveGB').value) || 2,
file_select_mode: document.getElementById('fileSelectMode').value,
overwrite_mode: document.getElementById('overwriteMode').value,
auto_copy: document.getElementById('autoCopy').checked,
sources,
};
try {
const r = await fetch('/api/config', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
if (r.ok) { toast('Настройки сохранены', 'ok'); await loadSettings(); }
else { const d = await r.json(); toast(d.error || 'Ошибка сохранения', 'error'); }
} catch(e) { toast('Ошибка связи', 'error'); }
}
loadSettings();
loadSources();
</script>
{{end}}