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>
This commit is contained in:
138
web/templates/settings.html
Normal file
138
web/templates/settings.html
Normal file
@@ -0,0 +1,138 @@
|
||||
{{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}}
|
||||
Reference in New Issue
Block a user