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>
139 lines
5.9 KiB
HTML
139 lines
5.9 KiB
HTML
{{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}}
|