Compare commits
No commits in common. "5d04ddced3416e57c8e61d4e7ec8dc0c472c1c39" and "1e3f2ca51af16879a6398225b2cdf558d4122ddf" have entirely different histories.
5d04ddced3
...
1e3f2ca51a
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "multi-hoster-uploader",
|
||||
"version": "3.3.72",
|
||||
"version": "3.3.71",
|
||||
"description": "Upload files to doodstream, voe, vidmoly, byse simultaneously",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
||||
454
renderer/app.js
454
renderer/app.js
@ -2906,30 +2906,15 @@ function renderSettings() {
|
||||
|
||||
const globalSettings = config.globalSettings || {};
|
||||
const configuredAccounts = getAvailableHosters();
|
||||
const fm = globalSettings.folderMonitor || {};
|
||||
const remoteSettings = globalSettings.remote || {};
|
||||
|
||||
const subtabBar = document.createElement('div');
|
||||
subtabBar.className = 'settings-subtabs';
|
||||
subtabBar.innerHTML = `
|
||||
<button class="settings-subtab active" data-subtab="allgemein">Allgemein</button>
|
||||
<button class="settings-subtab" data-subtab="automatik">Automatik</button>
|
||||
<button class="settings-subtab" data-subtab="logs">Logs & Diagnose</button>
|
||||
<button class="settings-subtab" data-subtab="remote">Fernsteuerung</button>
|
||||
<button class="settings-subtab" data-subtab="backup">Backup</button>
|
||||
`;
|
||||
container.appendChild(subtabBar);
|
||||
|
||||
const pages = {};
|
||||
['allgemein', 'automatik', 'logs', 'remote', 'backup'].forEach((id) => {
|
||||
const page = document.createElement('div');
|
||||
page.className = id === 'allgemein' ? 'settings-subpage active' : 'settings-subpage';
|
||||
page.dataset.subpage = id;
|
||||
pages[id] = page;
|
||||
container.appendChild(page);
|
||||
});
|
||||
|
||||
pages.allgemein.innerHTML = `
|
||||
const generalPanel = document.createElement('div');
|
||||
generalPanel.className = 'hoster-settings-panel';
|
||||
generalPanel.innerHTML = `
|
||||
<div class="hoster-panel-header" data-hoster="global">
|
||||
<span class="panel-arrow">▼</span>
|
||||
<span class="panel-title">Allgemein</span>
|
||||
<span class="panel-status active">System</span>
|
||||
</div>
|
||||
<div class="hoster-panel-body" data-panel="global" style="display:block">
|
||||
<div class="settings-section-label">Uploads</div>
|
||||
<div class="settings-row">
|
||||
<label style="min-width:185px">Globale parallele Uploads</label>
|
||||
@ -2969,10 +2954,29 @@ function renderSettings() {
|
||||
<label>Manuell prüfen</label>
|
||||
<button class="btn btn-xs btn-secondary" id="manualUpdateCheckBtn">Nach Updates suchen</button>
|
||||
</div>
|
||||
<div class="settings-hoster-pointer">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".</div>
|
||||
`;
|
||||
|
||||
pages.automatik.innerHTML = `
|
||||
<div class="settings-section-label">Log</div>
|
||||
<div class="settings-row">
|
||||
<label>FileUploader Log</label>
|
||||
<input type="text" class="key-input settings-autosave" id="logFilePathInput" value="${escapeAttr(globalSettings.logFilePath || '')}" placeholder="Standardpfad verwenden">
|
||||
<button class="btn btn-xs btn-secondary" id="chooseLogFilePathBtn">Ordner wählen</button>
|
||||
<button class="btn btn-xs btn-secondary" id="openLogFolderBtn" title="Log-Ordner im Explorer öffnen">Öffnen</button>
|
||||
</div>
|
||||
<div class="settings-row">
|
||||
<label>Log-Datei-Modus</label>
|
||||
<select class="hs-input settings-autosave" id="logModeInput">
|
||||
<option value="single" ${(window.LogMode ? window.LogMode.normalizeLogMode(globalSettings) : (globalSettings.logMode || 'single')) === 'single' ? 'selected' : ''}>Eine Datei</option>
|
||||
<option value="daily" ${(window.LogMode ? window.LogMode.normalizeLogMode(globalSettings) : (globalSettings.logMode || 'single')) === 'daily' ? 'selected' : ''}>Pro Tag</option>
|
||||
<option value="session" ${(window.LogMode ? window.LogMode.normalizeLogMode(globalSettings) : (globalSettings.logMode || 'single')) === 'session' ? 'selected' : ''}>Pro Session</option>
|
||||
</select>
|
||||
<span class="hint">Pro Session = neue Datei bei jedem App-Start; nach komplettem Schließen + erneutem Öffnen beginnt eine neue Session.</span>
|
||||
</div>
|
||||
<div class="settings-row">
|
||||
<label>Verbose Logging</label>
|
||||
<label class="checkbox-row" style="margin:0">
|
||||
<input type="checkbox" class="settings-autosave" id="logVerboseInput" ${globalSettings.logVerbose ? 'checked' : ''}>
|
||||
<span>DEBUG-Einträge in debug.log schreiben (Performance ↓, Diagnostik ↑)</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="settings-section-label">Unbeaufsichtigter Betrieb</div>
|
||||
<div class="settings-row">
|
||||
<label>Auto-Retry Runden</label>
|
||||
@ -2995,7 +2999,97 @@ function renderSettings() {
|
||||
<input type="text" class="key-input settings-autosave" id="webhookMentionInput" value="${escapeAttr(globalSettings.webhookMention || '')}" placeholder="deine User-ID, role:ROLLEN-ID, @here oder @everyone">
|
||||
<span class="hint">Damit Discord dich wirklich benachrichtigt (Push). User-ID: in Discord Entwicklermodus an → Rechtsklick auf deinen Namen → 'User-ID kopieren'. Leer = nur posten, kein Ping.</span>
|
||||
</div>
|
||||
<div class="settings-section-label">Ordnerüberwachung <span class="panel-status${fm.enabled && fm.folderPath ? ' active' : ''}" id="folderMonitorStatusBadge">${fm.enabled && fm.folderPath ? 'Aktiv' : 'Inaktiv'}</span></div>
|
||||
<div class="settings-section-label">Diagnose</div>
|
||||
<div class="settings-row" id="logPathsBlock">
|
||||
<label>Log-Dateien</label>
|
||||
<div class="log-paths-list" id="logPathsList" style="flex:1;display:flex;flex-direction:column;gap:4px">
|
||||
<span class="hint">Wird geladen…</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-row">
|
||||
<label>Support-Paket</label>
|
||||
<button class="btn btn-xs btn-secondary" id="createSupportBundleBtn" title="Sammelt alle Logs + sanitierte Config (Credentials maskiert) + App-Versionen in eine einzelne .txt-Datei zum Teilen.">Diagnose-Paket exportieren</button>
|
||||
<span class="hint" id="supportBundleHint">Eine .txt mit Logs + sanitierter Config; Passwörter/API-Keys werden vor dem Speichern maskiert.</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(generalPanel);
|
||||
_renderLogPathsList(generalPanel.querySelector('#logPathsList'));
|
||||
const testWebhookBtn = generalPanel.querySelector('#testWebhookBtn');
|
||||
if (testWebhookBtn) {
|
||||
testWebhookBtn.addEventListener('click', async () => {
|
||||
const url = (document.getElementById('webhookUrlInput')?.value || '').trim();
|
||||
const mention = (document.getElementById('webhookMentionInput')?.value || '').trim();
|
||||
const hint = document.getElementById('webhookHint');
|
||||
if (!url) { if (hint) hint.textContent = 'Keine URL eingetragen.'; return; }
|
||||
testWebhookBtn.disabled = true;
|
||||
const prev = testWebhookBtn.textContent;
|
||||
testWebhookBtn.textContent = 'Sende…';
|
||||
try {
|
||||
const res = await window.api.testWebhook({ url, mention });
|
||||
if (hint) hint.textContent = res && res.ok
|
||||
? `Test erfolgreich gesendet (HTTP ${res.status}).`
|
||||
: `Test fehlgeschlagen: ${(res && (res.error || 'HTTP ' + res.status)) || 'unbekannt'}`;
|
||||
} catch (err) {
|
||||
if (hint) hint.textContent = `Test fehlgeschlagen: ${err.message || err}`;
|
||||
} finally {
|
||||
testWebhookBtn.disabled = false;
|
||||
testWebhookBtn.textContent = prev;
|
||||
}
|
||||
});
|
||||
}
|
||||
const verboseInput = generalPanel.querySelector('#logVerboseInput');
|
||||
if (verboseInput) {
|
||||
verboseInput.addEventListener('change', () => {
|
||||
if (window.api && window.api.setLogVerbose) window.api.setLogVerbose(verboseInput.checked).catch(() => {});
|
||||
});
|
||||
}
|
||||
const sbBtn = generalPanel.querySelector('#createSupportBundleBtn');
|
||||
if (sbBtn) {
|
||||
sbBtn.addEventListener('click', async () => {
|
||||
const hint = generalPanel.querySelector('#supportBundleHint');
|
||||
sbBtn.disabled = true;
|
||||
const prevText = sbBtn.textContent;
|
||||
sbBtn.textContent = 'Exportiere…';
|
||||
try {
|
||||
const res = await window.api.createSupportBundle();
|
||||
if (res && res.ok) {
|
||||
if (hint) hint.textContent = `Gespeichert: ${res.path} (${(res.bytes/1024).toFixed(1)} KB)`;
|
||||
} else if (res && res.canceled) {
|
||||
if (hint) hint.textContent = 'Abgebrochen.';
|
||||
} else {
|
||||
if (hint) hint.textContent = `Fehler: ${(res && res.error) || 'unbekannt'}`;
|
||||
}
|
||||
} catch (err) {
|
||||
if (hint) hint.textContent = `Fehler: ${err.message || err}`;
|
||||
} finally {
|
||||
sbBtn.disabled = false;
|
||||
sbBtn.textContent = prevText;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Toggle general panel
|
||||
generalPanel.querySelector('.hoster-panel-header').addEventListener('click', () => {
|
||||
const body = generalPanel.querySelector('.hoster-panel-body');
|
||||
const arrow = generalPanel.querySelector('.panel-arrow');
|
||||
const isOpen = body.style.display !== 'none';
|
||||
body.style.display = isOpen ? 'none' : 'block';
|
||||
arrow.innerHTML = isOpen ? '▶' : '▼';
|
||||
});
|
||||
|
||||
// --- Folder Monitor Panel ---
|
||||
const fm = globalSettings.folderMonitor || {};
|
||||
const folderMonitorPanel = document.createElement('div');
|
||||
folderMonitorPanel.className = 'hoster-settings-panel';
|
||||
folderMonitorPanel.innerHTML = `
|
||||
<div class="hoster-panel-header" data-hoster="folderMonitor">
|
||||
<span class="panel-arrow">▶</span>
|
||||
<span class="panel-title">Ordnerüberwachung</span>
|
||||
<span class="panel-status${fm.enabled && fm.folderPath ? ' active' : ''}" id="folderMonitorStatusBadge">${fm.enabled && fm.folderPath ? 'Aktiv' : 'Inaktiv'}</span>
|
||||
</div>
|
||||
<div class="hoster-panel-body" data-panel="folderMonitor" style="display:none">
|
||||
<div class="settings-section-label">Ordner</div>
|
||||
<div class="settings-row">
|
||||
<label>Ordnerpfad</label>
|
||||
<input type="text" class="key-input settings-autosave" id="fmFolderPathInput" value="${escapeAttr(fm.folderPath || '')}" placeholder="Ordner wählen..." style="flex:1">
|
||||
@ -3042,48 +3136,52 @@ function renderSettings() {
|
||||
</div>`).join('')}
|
||||
</div>
|
||||
${configuredAccounts.length === 0 ? '<p class="hint" style="margin:0">Erst Accounts anlegen, dann hier auswählen.</p>' : '<p class="hint" style="margin:2px 0 0">Keine Auswahl = Hoster-Modal bei jeder Datei.</p>'}
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(folderMonitorPanel);
|
||||
|
||||
pages.logs.innerHTML = `
|
||||
<div class="settings-section-label">Log</div>
|
||||
<div class="settings-row">
|
||||
<label>FileUploader Log</label>
|
||||
<input type="text" class="key-input settings-autosave" id="logFilePathInput" value="${escapeAttr(globalSettings.logFilePath || '')}" placeholder="Standardpfad verwenden">
|
||||
<button class="btn btn-xs btn-secondary" id="chooseLogFilePathBtn">Ordner wählen</button>
|
||||
<button class="btn btn-xs btn-secondary" id="openLogFolderBtn" title="Log-Ordner im Explorer öffnen">Öffnen</button>
|
||||
</div>
|
||||
<div class="settings-row">
|
||||
<label>Log-Datei-Modus</label>
|
||||
<select class="hs-input settings-autosave" id="logModeInput">
|
||||
<option value="single" ${(window.LogMode ? window.LogMode.normalizeLogMode(globalSettings) : (globalSettings.logMode || 'single')) === 'single' ? 'selected' : ''}>Eine Datei</option>
|
||||
<option value="daily" ${(window.LogMode ? window.LogMode.normalizeLogMode(globalSettings) : (globalSettings.logMode || 'single')) === 'daily' ? 'selected' : ''}>Pro Tag</option>
|
||||
<option value="session" ${(window.LogMode ? window.LogMode.normalizeLogMode(globalSettings) : (globalSettings.logMode || 'single')) === 'session' ? 'selected' : ''}>Pro Session</option>
|
||||
</select>
|
||||
<span class="hint">Pro Session = neue Datei bei jedem App-Start; nach komplettem Schließen + erneutem Öffnen beginnt eine neue Session.</span>
|
||||
</div>
|
||||
<div class="settings-row">
|
||||
<label>Verbose Logging</label>
|
||||
<label class="checkbox-row" style="margin:0">
|
||||
<input type="checkbox" class="settings-autosave" id="logVerboseInput" ${globalSettings.logVerbose ? 'checked' : ''}>
|
||||
<span>DEBUG-Einträge in debug.log schreiben (Performance ↓, Diagnostik ↑)</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="settings-section-label">Diagnose</div>
|
||||
<div class="settings-row" id="logPathsBlock">
|
||||
<label>Log-Dateien</label>
|
||||
<div class="log-paths-list" id="logPathsList" style="flex:1;display:flex;flex-direction:column;gap:4px">
|
||||
<span class="hint">Wird geladen…</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-row">
|
||||
<label>Support-Paket</label>
|
||||
<button class="btn btn-xs btn-secondary" id="createSupportBundleBtn" title="Sammelt alle Logs + sanitierte Config (Credentials maskiert) + App-Versionen in eine einzelne .txt-Datei zum Teilen.">Diagnose-Paket exportieren</button>
|
||||
<span class="hint" id="supportBundleHint">Eine .txt mit Logs + sanitierter Config; Passwörter/API-Keys werden vor dem Speichern maskiert.</span>
|
||||
</div>
|
||||
`;
|
||||
// Toggle folder monitor panel
|
||||
folderMonitorPanel.querySelector('.hoster-panel-header').addEventListener('click', () => {
|
||||
const body = folderMonitorPanel.querySelector('.hoster-panel-body');
|
||||
const arrow = folderMonitorPanel.querySelector('.panel-arrow');
|
||||
const isOpen = body.style.display !== 'none';
|
||||
body.style.display = isOpen ? 'none' : 'block';
|
||||
arrow.innerHTML = isOpen ? '▶' : '▼';
|
||||
});
|
||||
|
||||
pages.remote.innerHTML = `
|
||||
<div class="settings-section-label">Server <span class="panel-status${remoteSettings.enabled ? ' active' : ''}" id="remoteStatusBadge">${remoteSettings.enabled ? 'Aktiv' : 'Inaktiv'}</span></div>
|
||||
// Update badge immediately on checkbox/path change
|
||||
const updateFmBadge = () => {
|
||||
const b = document.getElementById('folderMonitorStatusBadge');
|
||||
if (!b) return;
|
||||
const enabled = document.getElementById('fmEnabledInput')?.checked;
|
||||
const hasPath = (document.getElementById('fmFolderPathInput')?.value || '').trim();
|
||||
if (enabled && hasPath) { b.textContent = 'Aktiv'; b.className = 'panel-status active'; }
|
||||
else { b.textContent = 'Inaktiv'; b.className = 'panel-status'; }
|
||||
};
|
||||
document.getElementById('fmEnabledInput')?.addEventListener('change', updateFmBadge);
|
||||
document.getElementById('fmFolderPathInput')?.addEventListener('input', updateFmBadge);
|
||||
|
||||
document.getElementById('fmChooseFolderBtn')?.addEventListener('click', async () => {
|
||||
const folder = await window.api.folderMonitorSelectFolder();
|
||||
if (folder) {
|
||||
document.getElementById('fmFolderPathInput').value = folder;
|
||||
updateFmBadge();
|
||||
scheduleSettingsSave();
|
||||
}
|
||||
});
|
||||
|
||||
// --- Remote Control Panel ---
|
||||
const remoteSettings = globalSettings.remote || {};
|
||||
const remotePanel = document.createElement('div');
|
||||
remotePanel.className = 'hoster-settings-panel';
|
||||
remotePanel.innerHTML = `
|
||||
<div class="hoster-panel-header" data-hoster="remote">
|
||||
<span class="panel-arrow">▶</span>
|
||||
<span class="panel-title">Fernsteuerung</span>
|
||||
<span class="panel-status${remoteSettings.enabled ? ' active' : ''}" id="remoteStatusBadge">${remoteSettings.enabled ? 'Aktiv' : 'Inaktiv'}</span>
|
||||
</div>
|
||||
<div class="hoster-panel-body" data-panel="remote" style="display:none">
|
||||
<div class="settings-section-label">Server</div>
|
||||
<div class="settings-grid-mini">
|
||||
<div class="settings-row checkbox-row">
|
||||
<label>Aktiviert</label>
|
||||
@ -3108,103 +3206,20 @@ function renderSettings() {
|
||||
<div class="settings-row">
|
||||
<span id="remoteConnectionStatus" style="color:#94a3b8">Prüfe...</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(remotePanel);
|
||||
|
||||
pages.backup.innerHTML = `
|
||||
<p class="hint" style="margin:0 0 10px">Alle Accounts und Einstellungen exportieren oder importieren. Der Upload-Verlauf bleibt lokal und wird nicht übertragen; nach einem Import ist der Verlauf-Tab leer.</p>
|
||||
<div style="display:flex;gap:8px">
|
||||
<button class="btn btn-secondary" id="exportBackupBtn">Backup exportieren</button>
|
||||
<button class="btn btn-secondary" id="importBackupBtn">Backup importieren</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
subtabBar.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('[data-subtab]');
|
||||
if (!btn) return;
|
||||
const target = btn.dataset.subtab;
|
||||
subtabBar.querySelectorAll('.settings-subtab').forEach((b) => {
|
||||
b.classList.toggle('active', b === btn);
|
||||
});
|
||||
Object.values(pages).forEach((p) => {
|
||||
p.classList.toggle('active', p.dataset.subpage === target);
|
||||
});
|
||||
});
|
||||
|
||||
_renderLogPathsList(document.getElementById('logPathsList'));
|
||||
const testWebhookBtn = document.getElementById('testWebhookBtn');
|
||||
if (testWebhookBtn) {
|
||||
testWebhookBtn.addEventListener('click', async () => {
|
||||
const url = (document.getElementById('webhookUrlInput')?.value || '').trim();
|
||||
const mention = (document.getElementById('webhookMentionInput')?.value || '').trim();
|
||||
const hint = document.getElementById('webhookHint');
|
||||
if (!url) { if (hint) hint.textContent = 'Keine URL eingetragen.'; return; }
|
||||
testWebhookBtn.disabled = true;
|
||||
const prev = testWebhookBtn.textContent;
|
||||
testWebhookBtn.textContent = 'Sende…';
|
||||
try {
|
||||
const res = await window.api.testWebhook({ url, mention });
|
||||
if (hint) hint.textContent = res && res.ok
|
||||
? `Test erfolgreich gesendet (HTTP ${res.status}).`
|
||||
: `Test fehlgeschlagen: ${(res && (res.error || 'HTTP ' + res.status)) || 'unbekannt'}`;
|
||||
} catch (err) {
|
||||
if (hint) hint.textContent = `Test fehlgeschlagen: ${err.message || err}`;
|
||||
} finally {
|
||||
testWebhookBtn.disabled = false;
|
||||
testWebhookBtn.textContent = prev;
|
||||
}
|
||||
});
|
||||
}
|
||||
const verboseInput = document.getElementById('logVerboseInput');
|
||||
if (verboseInput) {
|
||||
verboseInput.addEventListener('change', () => {
|
||||
if (window.api && window.api.setLogVerbose) window.api.setLogVerbose(verboseInput.checked).catch(() => {});
|
||||
});
|
||||
}
|
||||
const sbBtn = document.getElementById('createSupportBundleBtn');
|
||||
if (sbBtn) {
|
||||
sbBtn.addEventListener('click', async () => {
|
||||
const hint = document.getElementById('supportBundleHint');
|
||||
sbBtn.disabled = true;
|
||||
const prevText = sbBtn.textContent;
|
||||
sbBtn.textContent = 'Exportiere…';
|
||||
try {
|
||||
const res = await window.api.createSupportBundle();
|
||||
if (res && res.ok) {
|
||||
if (hint) hint.textContent = `Gespeichert: ${res.path} (${(res.bytes/1024).toFixed(1)} KB)`;
|
||||
} else if (res && res.canceled) {
|
||||
if (hint) hint.textContent = 'Abgebrochen.';
|
||||
} else {
|
||||
if (hint) hint.textContent = `Fehler: ${(res && res.error) || 'unbekannt'}`;
|
||||
}
|
||||
} catch (err) {
|
||||
if (hint) hint.textContent = `Fehler: ${err.message || err}`;
|
||||
} finally {
|
||||
sbBtn.disabled = false;
|
||||
sbBtn.textContent = prevText;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const updateFmBadge = () => {
|
||||
const b = document.getElementById('folderMonitorStatusBadge');
|
||||
if (!b) return;
|
||||
const enabled = document.getElementById('fmEnabledInput')?.checked;
|
||||
const hasPath = (document.getElementById('fmFolderPathInput')?.value || '').trim();
|
||||
if (enabled && hasPath) { b.textContent = 'Aktiv'; b.className = 'panel-status active'; }
|
||||
else { b.textContent = 'Inaktiv'; b.className = 'panel-status'; }
|
||||
};
|
||||
document.getElementById('fmEnabledInput')?.addEventListener('change', updateFmBadge);
|
||||
document.getElementById('fmFolderPathInput')?.addEventListener('input', updateFmBadge);
|
||||
|
||||
document.getElementById('fmChooseFolderBtn')?.addEventListener('click', async () => {
|
||||
const folder = await window.api.folderMonitorSelectFolder();
|
||||
if (folder) {
|
||||
document.getElementById('fmFolderPathInput').value = folder;
|
||||
updateFmBadge();
|
||||
scheduleSettingsSave();
|
||||
}
|
||||
// Toggle remote panel
|
||||
remotePanel.querySelector('.hoster-panel-header').addEventListener('click', () => {
|
||||
const body = remotePanel.querySelector('.hoster-panel-body');
|
||||
const arrow = remotePanel.querySelector('.panel-arrow');
|
||||
const isOpen = body.style.display !== 'none';
|
||||
body.style.display = isOpen ? 'none' : 'block';
|
||||
arrow.innerHTML = isOpen ? '▶' : '▼';
|
||||
});
|
||||
|
||||
// Copy token
|
||||
document.getElementById('remoteCopyTokenBtn').addEventListener('click', async () => {
|
||||
const token = document.getElementById('remoteTokenInput').value;
|
||||
if (token) {
|
||||
@ -3214,12 +3229,14 @@ function renderSettings() {
|
||||
}
|
||||
});
|
||||
|
||||
// Regenerate token
|
||||
document.getElementById('remoteRegenerateTokenBtn').addEventListener('click', async () => {
|
||||
const newToken = await window.api.remoteGenerateToken();
|
||||
document.getElementById('remoteTokenInput').value = newToken;
|
||||
scheduleSettingsSave();
|
||||
});
|
||||
|
||||
// Update status
|
||||
window.api.remoteStatus().then(status => {
|
||||
const el = document.getElementById('remoteConnectionStatus');
|
||||
if (!el) return;
|
||||
@ -3232,9 +3249,43 @@ function renderSettings() {
|
||||
}
|
||||
}).catch(() => {});
|
||||
|
||||
// Live client count updates (listener registered once in init, not here)
|
||||
|
||||
// --- Backup Panel ---
|
||||
const backupPanel = document.createElement('div');
|
||||
backupPanel.className = 'hoster-settings-panel';
|
||||
backupPanel.innerHTML = `
|
||||
<div class="hoster-panel-header" data-hoster="backup">
|
||||
<span class="panel-arrow">▶</span>
|
||||
<span class="panel-title">Backup</span>
|
||||
<span class="panel-status active">System</span>
|
||||
</div>
|
||||
<div class="hoster-panel-body" data-panel="backup" style="display:none">
|
||||
<p class="hint" style="margin:0 0 10px">Alle Accounts und Einstellungen exportieren oder importieren. Der Upload-Verlauf bleibt lokal und wird nicht übertragen; nach einem Import ist der Verlauf-Tab leer.</p>
|
||||
<div style="display:flex;gap:8px">
|
||||
<button class="btn btn-secondary" id="exportBackupBtn">Backup exportieren</button>
|
||||
<button class="btn btn-secondary" id="importBackupBtn">Backup importieren</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(backupPanel);
|
||||
|
||||
backupPanel.querySelector('.hoster-panel-header').addEventListener('click', () => {
|
||||
const body = backupPanel.querySelector('.hoster-panel-body');
|
||||
const arrow = backupPanel.querySelector('.panel-arrow');
|
||||
const isOpen = body.style.display !== 'none';
|
||||
body.style.display = isOpen ? 'none' : 'block';
|
||||
arrow.innerHTML = isOpen ? '▶' : '▼';
|
||||
});
|
||||
|
||||
document.getElementById('exportBackupBtn').addEventListener('click', () => doBackupExport());
|
||||
document.getElementById('importBackupBtn').addEventListener('click', () => doBackupImport());
|
||||
|
||||
const hosterPointer = document.createElement('div');
|
||||
hosterPointer.className = 'settings-hoster-pointer';
|
||||
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(hosterPointer);
|
||||
|
||||
document.getElementById('chooseLogFilePathBtn')?.addEventListener('click', chooseLogFilePath);
|
||||
document.getElementById('openLogFolderBtn')?.addEventListener('click', () => window.api.openLogFolder());
|
||||
document.getElementById('manualUpdateCheckBtn')?.addEventListener('click', async (e) => {
|
||||
@ -3282,62 +3333,42 @@ function scheduleSettingsSave() {
|
||||
async function saveSettings(options = {}) {
|
||||
const { feedbackText = 'Gespeichert!' } = options;
|
||||
const newHosterSettings = { ...(config.hosterSettings || {}) };
|
||||
const cur = config.globalSettings || {};
|
||||
const curFm = cur.folderMonitor || {};
|
||||
const curRemote = cur.remote || {};
|
||||
const elTxt = (id, fb) => { const el = document.getElementById(id); return el ? el.value : fb; };
|
||||
const elChk = (id, fb) => { const el = document.getElementById(id); return el ? !!el.checked : fb; };
|
||||
const elInt = (id, curVal, dflt, lo, hi) => {
|
||||
const el = document.getElementById(id);
|
||||
if (!el) return curVal;
|
||||
const n = parseInt(el.value || String(dflt), 10) || dflt;
|
||||
return Math.max(lo, Math.min(hi, n));
|
||||
};
|
||||
|
||||
const globalSettings = {
|
||||
...cur,
|
||||
logFilePath: elTxt('logFilePathInput', cur.logFilePath || '').trim(),
|
||||
...(config.globalSettings || {}),
|
||||
logFilePath: (document.getElementById('logFilePathInput')?.value || '').trim(),
|
||||
logMode: (() => {
|
||||
const el = document.getElementById('logModeInput');
|
||||
if (!el) return cur.logMode || 'single';
|
||||
const v = el.value;
|
||||
const v = document.getElementById('logModeInput')?.value;
|
||||
return (v === 'single' || v === 'daily' || v === 'session') ? v : 'single';
|
||||
})(),
|
||||
resumeQueueOnLaunch: elChk('resumeQueueOnLaunchInput', cur.resumeQueueOnLaunch !== false),
|
||||
parallelUploadCount: elInt('parallelUploadCountInput', cur.parallelUploadCount ?? 0, 0, 0, 100),
|
||||
scaleParallelUploads: elChk('scaleParallelUploadsInput', !!cur.scaleParallelUploads),
|
||||
removeFromQueueOnDone: elChk('removeFromQueueOnDoneInput', !!cur.removeFromQueueOnDone),
|
||||
showDropTarget: elChk('showDropTargetInput', !!cur.showDropTarget),
|
||||
globalMaxSpeedKbs: (() => {
|
||||
const el = document.getElementById('globalMaxSpeedMbsInput');
|
||||
if (!el) return cur.globalMaxSpeedKbs ?? 0;
|
||||
return Math.max(0, Math.round((parseFloat(el.value || '0') || 0) * 1024));
|
||||
})(),
|
||||
logVerbose: elChk('logVerboseInput', !!cur.logVerbose),
|
||||
webhookUrl: elTxt('webhookUrlInput', cur.webhookUrl || '').trim(),
|
||||
webhookMention: elTxt('webhookMentionInput', cur.webhookMention || '').trim(),
|
||||
autoRetryRounds: elInt('autoRetryRoundsInput', cur.autoRetryRounds ?? 0, 0, 0, 5),
|
||||
autoRetryDelayMin: elInt('autoRetryDelayMinInput', cur.autoRetryDelayMin ?? 5, 5, 1, 120),
|
||||
resumeQueueOnLaunch: !!document.getElementById('resumeQueueOnLaunchInput')?.checked,
|
||||
parallelUploadCount: Math.max(0, Math.min(100, parseInt(document.getElementById('parallelUploadCountInput')?.value || '0', 10) || 0)),
|
||||
scaleParallelUploads: !!document.getElementById('scaleParallelUploadsInput')?.checked,
|
||||
removeFromQueueOnDone: !!document.getElementById('removeFromQueueOnDoneInput')?.checked,
|
||||
showDropTarget: !!document.getElementById('showDropTargetInput')?.checked,
|
||||
globalMaxSpeedKbs: Math.max(0, Math.round((parseFloat(document.getElementById('globalMaxSpeedMbsInput')?.value || '0') || 0) * 1024)),
|
||||
logVerbose: !!document.getElementById('logVerboseInput')?.checked,
|
||||
webhookUrl: (document.getElementById('webhookUrlInput')?.value || '').trim(),
|
||||
webhookMention: (document.getElementById('webhookMentionInput')?.value || '').trim(),
|
||||
autoRetryRounds: Math.max(0, Math.min(5, parseInt(document.getElementById('autoRetryRoundsInput')?.value || '0', 10) || 0)),
|
||||
autoRetryDelayMin: Math.max(1, Math.min(120, parseInt(document.getElementById('autoRetryDelayMinInput')?.value || '5', 10) || 5)),
|
||||
folderMonitor: {
|
||||
...curFm,
|
||||
enabled: elChk('fmEnabledInput', !!curFm.enabled),
|
||||
folderPath: elTxt('fmFolderPathInput', curFm.folderPath || '').trim(),
|
||||
recursive: elChk('fmRecursiveInput', !!curFm.recursive),
|
||||
filterMode: (() => { const el = document.getElementById('fmFilterModeInput'); return el ? (el.value || 'include') : (curFm.filterMode || 'include'); })(),
|
||||
extensions: elTxt('fmExtensionsInput', curFm.extensions || '').trim(),
|
||||
skipDuplicates: elChk('fmSkipDuplicatesInput', curFm.skipDuplicates !== false),
|
||||
delaySec: elInt('fmDelaySecInput', curFm.delaySec ?? 3, 3, 1, 300),
|
||||
autoStart: elChk('fmAutoStartInput', curFm.autoStart !== false),
|
||||
hosters: document.querySelector('.fm-hoster-checkbox')
|
||||
? Array.from(document.querySelectorAll('.fm-hoster-checkbox:checked')).map(el => el.dataset.fmHoster)
|
||||
: (curFm.hosters || [])
|
||||
...((config.globalSettings || {}).folderMonitor || {}),
|
||||
enabled: !!document.getElementById('fmEnabledInput')?.checked,
|
||||
folderPath: (document.getElementById('fmFolderPathInput')?.value || '').trim(),
|
||||
recursive: !!document.getElementById('fmRecursiveInput')?.checked,
|
||||
filterMode: document.getElementById('fmFilterModeInput')?.value || 'include',
|
||||
extensions: (document.getElementById('fmExtensionsInput')?.value || '').trim(),
|
||||
skipDuplicates: !!document.getElementById('fmSkipDuplicatesInput')?.checked,
|
||||
delaySec: Math.max(1, parseInt(document.getElementById('fmDelaySecInput')?.value || '3', 10) || 3),
|
||||
autoStart: !!document.getElementById('fmAutoStartInput')?.checked,
|
||||
hosters: Array.from(document.querySelectorAll('.fm-hoster-checkbox:checked')).map(el => el.dataset.fmHoster)
|
||||
},
|
||||
remote: {
|
||||
...curRemote,
|
||||
enabled: elChk('remoteEnabledInput', !!curRemote.enabled),
|
||||
port: elInt('remotePortInput', curRemote.port || 9100, 9100, 1024, 65535),
|
||||
token: elTxt('remoteTokenInput', curRemote.token || '').trim(),
|
||||
allowInput: elChk('remoteAllowInputInput', curRemote.allowInput !== false)
|
||||
...((config.globalSettings || {}).remote || {}),
|
||||
enabled: !!document.getElementById('remoteEnabledInput')?.checked,
|
||||
port: Math.max(1024, Math.min(65535, parseInt(document.getElementById('remotePortInput')?.value || '9100', 10) || 9100)),
|
||||
token: (document.getElementById('remoteTokenInput')?.value || '').trim(),
|
||||
allowInput: !!document.getElementById('remoteAllowInputInput')?.checked
|
||||
}
|
||||
};
|
||||
|
||||
@ -4740,9 +4771,6 @@ function setupListeners() {
|
||||
// Virtual scroll for large queues
|
||||
const queueContainer = document.getElementById('queueContainer');
|
||||
if (queueContainer) queueContainer.addEventListener('scroll', _onQueueScroll, { passive: true });
|
||||
if (queueContainer && typeof window.ResizeObserver !== 'undefined') {
|
||||
new window.ResizeObserver(_onQueueScroll).observe(queueContainer);
|
||||
}
|
||||
|
||||
// Queue table sorting
|
||||
document.querySelectorAll('#queueTable th.sortable').forEach(th => {
|
||||
|
||||
@ -842,37 +842,6 @@ body.col-resizing, body.col-resizing * { cursor: col-resize !important; user-sel
|
||||
.settings-divider { height: 1px; background: var(--border); margin: 12px 0; }
|
||||
.hoster-panel-body h4 { font-size: 12px; color: var(--text-muted); margin-bottom: 8px; font-weight: 500; }
|
||||
|
||||
.settings-subtabs {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
margin-bottom: 14px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.settings-subtab {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.03em;
|
||||
text-transform: uppercase;
|
||||
padding: 8px 14px;
|
||||
cursor: pointer;
|
||||
border-bottom: 2px solid transparent;
|
||||
transition: color 0.15s, border-color 0.15s;
|
||||
}
|
||||
.settings-subtab:hover { color: var(--text); }
|
||||
.settings-subtab.active { color: var(--text); border-bottom-color: var(--accent); }
|
||||
.settings-subpage {
|
||||
display: none;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 4px 14px 14px;
|
||||
}
|
||||
.settings-subpage.active { display: block; }
|
||||
|
||||
.settings-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user