feat(settings): move per-hoster upload settings into the Accounts tab

Declutters the Einstellungen tab: the per-hoster panels (Retries, Max Speed,
Parallel Uploads, Restart-below, Interval, Max Size, log-to-file) are no longer
listed there. Each hoster group in the Accounts tab now has a collapsible
"Upload-Einstellungen" section with exactly those controls, so everything about
a hoster lives in one place. The Einstellungen tab shows a short pointer to where
they moved.

Per-hoster edits in Accounts save via a dedicated lightweight handler
(scheduleHosterSettingsSave → saveHosterSettings) that touches only
hosterSettings — it deliberately does NOT route through the global saveSettings,
so tweaking a hoster slider can't re-run the folder-monitor start/stop or clobber
global fields. The global saveSettings still reads .hs-input[data-hoster] wherever
they live, and global inputs (which have no data-hoster) are unaffected. The new
inputs drop the .settings-autosave class and bind via delegation on the persistent
accounts container so they survive re-renders.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Administrator 2026-06-15 01:03:37 +02:00
parent 68c4007c82
commit b1826eae00
2 changed files with 127 additions and 76 deletions

View File

@ -3281,81 +3281,10 @@ function renderSettings() {
document.getElementById('exportBackupBtn').addEventListener('click', () => doBackupExport()); document.getElementById('exportBackupBtn').addEventListener('click', () => doBackupExport());
document.getElementById('importBackupBtn').addEventListener('click', () => doBackupImport()); document.getElementById('importBackupBtn').addEventListener('click', () => doBackupImport());
// --- Separator before hoster panels --- const hosterPointer = document.createElement('div');
const separator = document.createElement('div'); hosterPointer.className = 'settings-hoster-pointer';
separator.style.cssText = 'height:16px'; hosterPointer.innerHTML = 'Die <strong>Upload-Einstellungen pro Hoster</strong> (Retries, Speed, Parallel, Max-Größe, Log) sind jetzt im <strong>Accounts</strong>-Tab — direkt bei den jeweiligen Hostern unter „Upload-Einstellungen".';
container.appendChild(separator); container.appendChild(hosterPointer);
if (configuredAccounts.length === 0) {
const empty = document.createElement('div');
empty.className = 'settings-empty';
empty.innerHTML = '<p>Noch keine Account-Einstellungen vorhanden.</p><span class="hint">Sobald du einen Account anlegst, erscheinen hier die passenden Upload-Einstellungen.</span>';
container.appendChild(empty);
}
for (const { name } of configuredAccounts) {
const hs = hosterSettings[name] || {};
const maxSpeedMbs = hs.maxSpeedKbs > 0 ? (hs.maxSpeedKbs / 1024).toFixed(2).replace(/\.00$/, '') : '0';
const panel = document.createElement('div');
panel.className = 'hoster-settings-panel';
panel.innerHTML = `
<div class="hoster-panel-header" data-hoster="${name}">
<span class="panel-arrow">&#9654;</span>
<span class="panel-title">${escapeHtml(getHosterLabel(name))}</span>
<span class="panel-status active">Aktiv</span>
</div>
<div class="hoster-panel-body" data-panel="${name}" style="display:none">
<h4>Upload-Einstellungen</h4>
<div class="settings-grid-mini">
<div class="settings-row">
<label>Retries</label>
<input type="number" class="hs-input settings-autosave" data-hoster="${name}" data-hs="retries" value="${hs.retries ?? 3}" min="0" max="500">
</div>
<div class="settings-row">
<label>Max Speed (MB/s)</label>
<input type="number" class="hs-input settings-autosave" data-hoster="${name}" data-hs="maxSpeedMbs" value="${maxSpeedMbs}" min="0" step="0.1">
<span class="hint">0 = unbegrenzt</span>
</div>
<div class="settings-row">
<label>Parallele Uploads</label>
<input type="number" class="hs-input settings-autosave" data-hoster="${name}" data-hs="parallelCount" value="${hs.parallelCount ?? 2}" min="1" max="100">
</div>
<div class="settings-row">
<label>Restart unter (kB/s)</label>
<input type="number" class="hs-input settings-autosave" data-hoster="${name}" data-hs="restartBelowKbs" value="${hs.restartBelowKbs ?? 0}" min="0">
<span class="hint">0 = aus</span>
</div>
<div class="settings-row">
<label>Intervall (s)</label>
<input type="number" class="hs-input settings-autosave" data-hoster="${name}" data-hs="timeIntervalSec" value="${hs.timeIntervalSec ?? 0}" min="0">
</div>
<div class="settings-row">
<label>Max Size (MB)</label>
<input type="number" class="hs-input settings-autosave" data-hoster="${name}" data-hs="maxSizeMb" value="${hs.maxSizeMb ?? 0}" min="0">
<span class="hint">0 = unbegrenzt</span>
</div>
<div class="settings-row">
<label>Links in Log schreiben</label>
<input type="checkbox" class="hs-input settings-autosave" data-hoster="${name}" data-hs="logToFile" ${hs.logToFile !== false ? 'checked' : ''}>
<span class="hint">Erfolgreiche Links in fileuploader.log. Aus = auch kein Doppel-Upload-Schutz beim Neustart für diesen Hoster.</span>
</div>
</div>
</div>
`;
container.appendChild(panel);
// Toggle panel
panel.querySelector('.hoster-panel-header').addEventListener('click', () => {
const body = panel.querySelector('.hoster-panel-body');
const arrow = panel.querySelector('.panel-arrow');
const isOpen = body.style.display !== 'none';
body.style.display = isOpen ? 'none' : 'block';
arrow.innerHTML = isOpen ? '&#9654;' : '&#9660;';
});
}
document.getElementById('chooseLogFilePathBtn')?.addEventListener('click', chooseLogFilePath); document.getElementById('chooseLogFilePathBtn')?.addEventListener('click', chooseLogFilePath);
document.getElementById('openLogFolderBtn')?.addEventListener('click', () => window.api.openLogFolder()); document.getElementById('openLogFolderBtn')?.addEventListener('click', () => window.api.openLogFolder());
@ -3708,6 +3637,78 @@ function _hosterGroupOpenState(name, summary) {
const _hosterGroupOpenMemory = new Map(); const _hosterGroupOpenMemory = new Map();
let _hosterSettingsSaveTimer = null;
function scheduleHosterSettingsSave() {
clearTimeout(_hosterSettingsSaveTimer);
_hosterSettingsSaveTimer = setTimeout(() => { saveHosterSettingsFromDom().catch(() => {}); }, 350);
}
async function saveHosterSettingsFromDom() {
const newHosterSettings = { ...(config.hosterSettings || {}) };
for (const name of HOSTERS) {
const inputs = document.querySelectorAll(`.account-hoster-settings-body .hs-input[data-hoster="${name}"]`);
if (!inputs.length) continue;
const hs = { ...(newHosterSettings[name] || {}) };
inputs.forEach(input => {
const field = input.dataset.hs;
if (input.type === 'checkbox') hs[field] = input.checked;
else if (field === 'maxSpeedMbs') hs.maxSpeedKbs = Math.max(0, Math.round((parseFloat(input.value) || 0) * 1024));
else hs[field] = parseInt(input.value, 10) || 0;
});
newHosterSettings[name] = hs;
}
await window.api.saveHosterSettings(newHosterSettings);
config.hosterSettings = newHosterSettings;
hosterSettings = newHosterSettings;
}
function _buildHosterSettingsHtml(name) {
const hs = (config.hosterSettings && config.hosterSettings[name]) || {};
const maxSpeedMbs = hs.maxSpeedKbs > 0 ? String(+(hs.maxSpeedKbs / 1024).toFixed(2)) : '0';
return `<div class="account-hoster-settings">
<div class="account-hoster-settings-header" data-hoster-settings-toggle="${name}">
<span class="panel-arrow">&#9654;</span>
<span>Upload-Einstellungen</span>
</div>
<div class="account-hoster-settings-body" style="display:none">
<div class="settings-grid-mini">
<div class="settings-row">
<label>Retries</label>
<input type="number" class="hs-input" data-hoster="${name}" data-hs="retries" value="${hs.retries ?? 3}" min="0" max="500">
</div>
<div class="settings-row">
<label>Max Speed (MB/s)</label>
<input type="number" class="hs-input" data-hoster="${name}" data-hs="maxSpeedMbs" value="${maxSpeedMbs}" min="0" step="0.1">
<span class="hint">0 = unbegrenzt</span>
</div>
<div class="settings-row">
<label>Parallele Uploads</label>
<input type="number" class="hs-input" data-hoster="${name}" data-hs="parallelCount" value="${hs.parallelCount ?? 2}" min="1" max="100">
</div>
<div class="settings-row">
<label>Restart unter (kB/s)</label>
<input type="number" class="hs-input" data-hoster="${name}" data-hs="restartBelowKbs" value="${hs.restartBelowKbs ?? 0}" min="0">
<span class="hint">0 = aus</span>
</div>
<div class="settings-row">
<label>Intervall (s)</label>
<input type="number" class="hs-input" data-hoster="${name}" data-hs="timeIntervalSec" value="${hs.timeIntervalSec ?? 0}" min="0">
</div>
<div class="settings-row">
<label>Max Size (MB)</label>
<input type="number" class="hs-input" data-hoster="${name}" data-hs="maxSizeMb" value="${hs.maxSizeMb ?? 0}" min="0">
<span class="hint">0 = unbegrenzt</span>
</div>
<div class="settings-row">
<label>Links in Log schreiben</label>
<input type="checkbox" class="hs-input" data-hoster="${name}" data-hs="logToFile" ${hs.logToFile !== false ? 'checked' : ''}>
<span class="hint">Erfolgreiche Links in fileuploader.log.</span>
</div>
</div>
</div>
</div>`;
}
function _buildAccountHosterGroupHtml(name, accounts) { function _buildAccountHosterGroupHtml(name, accounts) {
const summary = _summarizeHosterGroup(accounts); const summary = _summarizeHosterGroup(accounts);
const isOpen = _hosterGroupOpenState(name, summary); const isOpen = _hosterGroupOpenState(name, summary);
@ -3734,7 +3735,7 @@ function _buildAccountHosterGroupHtml(name, accounts) {
${summary.error ? `<span class="account-hoster-group-meta error">${summary.error} Fehler</span>` : ''} ${summary.error ? `<span class="account-hoster-group-meta error">${summary.error} Fehler</span>` : ''}
${lifeMeta} ${lifeMeta}
</div> </div>
<div class="account-hoster-group-body" ${bodyStyle}>${cardsHtml}</div> <div class="account-hoster-group-body" ${bodyStyle}>${cardsHtml}${_buildHosterSettingsHtml(name)}</div>
</div>`; </div>`;
} }
@ -3799,6 +3800,17 @@ function bindAccountListeners(container) {
} }
return; return;
} }
const settingsHeader = e.target.closest('[data-hoster-settings-toggle]');
if (settingsHeader) {
const body = settingsHeader.nextElementSibling;
const arrow = settingsHeader.querySelector('.panel-arrow');
if (body) {
const isOpen = body.style.display !== 'none';
body.style.display = isOpen ? 'none' : '';
if (arrow) arrow.innerHTML = isOpen ? '&#9654;' : '&#9660;';
}
return;
}
const btn = e.target.closest('button'); const btn = e.target.closest('button');
if (!btn) return; if (!btn) return;
if (btn.dataset.accountToggle) return toggleAccount(btn.dataset.accountToggle); if (btn.dataset.accountToggle) return toggleAccount(btn.dataset.accountToggle);
@ -3819,6 +3831,12 @@ function bindAccountListeners(container) {
} }
}); });
const onHosterSettingInput = (e) => {
if (e.target && e.target.classList && e.target.classList.contains('hs-input')) scheduleHosterSettingsSave();
};
container.addEventListener('input', onHosterSettingInput);
container.addEventListener('change', onHosterSettingInput);
let draggedCard = null; let draggedCard = null;
container.addEventListener('dragstart', (e) => { container.addEventListener('dragstart', (e) => {
const card = e.target.closest('.account-card[draggable]'); const card = e.target.closest('.account-card[draggable]');

View File

@ -1024,6 +1024,39 @@ select.hs-input { max-width: none; width: auto; min-width: 140px; }
.account-card.drag-over-above { border-top: 2px solid var(--accent); } .account-card.drag-over-above { border-top: 2px solid var(--accent); }
.account-card.drag-over-below { border-bottom: 2px solid var(--accent); } .account-card.drag-over-below { border-bottom: 2px solid var(--accent); }
.account-hoster-settings {
margin-top: 8px;
border-top: 1px solid var(--border);
padding-top: 6px;
}
.account-hoster-settings-header {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 4px;
cursor: pointer;
color: var(--text-muted);
font-size: 12px;
letter-spacing: 0.02em;
text-transform: uppercase;
border-radius: 6px;
transition: background 0.1s, color 0.1s;
}
.account-hoster-settings-header:hover { background: rgba(255,255,255,0.03); color: var(--text); }
.account-hoster-settings-header .panel-arrow { font-size: 10px; color: var(--text-dim); }
.account-hoster-settings-body { padding: 6px 4px 4px; }
.settings-hoster-pointer {
margin-top: 12px;
padding: 10px 14px;
font-size: 12px;
line-height: 1.5;
color: var(--text-muted);
background: rgba(62, 167, 255, 0.06);
border: 1px solid var(--border);
border-radius: 8px;
}
.account-priority-badge { .account-priority-badge {
font-size: 10px; font-size: 10px;
font-weight: 500; font-weight: 500;