Compare commits
No commits in common. "5d04ddced3416e57c8e61d4e7ec8dc0c472c1c39" and "1e3f2ca51af16879a6398225b2cdf558d4122ddf" have entirely different histories.
5d04ddced3
...
1e3f2ca51a
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "multi-hoster-uploader",
|
"name": "multi-hoster-uploader",
|
||||||
"version": "3.3.72",
|
"version": "3.3.71",
|
||||||
"description": "Upload files to doodstream, voe, vidmoly, byse simultaneously",
|
"description": "Upload files to doodstream, voe, vidmoly, byse simultaneously",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
454
renderer/app.js
454
renderer/app.js
@ -2906,30 +2906,15 @@ function renderSettings() {
|
|||||||
|
|
||||||
const globalSettings = config.globalSettings || {};
|
const globalSettings = config.globalSettings || {};
|
||||||
const configuredAccounts = getAvailableHosters();
|
const configuredAccounts = getAvailableHosters();
|
||||||
const fm = globalSettings.folderMonitor || {};
|
const generalPanel = document.createElement('div');
|
||||||
const remoteSettings = globalSettings.remote || {};
|
generalPanel.className = 'hoster-settings-panel';
|
||||||
|
generalPanel.innerHTML = `
|
||||||
const subtabBar = document.createElement('div');
|
<div class="hoster-panel-header" data-hoster="global">
|
||||||
subtabBar.className = 'settings-subtabs';
|
<span class="panel-arrow">▼</span>
|
||||||
subtabBar.innerHTML = `
|
<span class="panel-title">Allgemein</span>
|
||||||
<button class="settings-subtab active" data-subtab="allgemein">Allgemein</button>
|
<span class="panel-status active">System</span>
|
||||||
<button class="settings-subtab" data-subtab="automatik">Automatik</button>
|
</div>
|
||||||
<button class="settings-subtab" data-subtab="logs">Logs & Diagnose</button>
|
<div class="hoster-panel-body" data-panel="global" style="display:block">
|
||||||
<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 = `
|
|
||||||
<div class="settings-section-label">Uploads</div>
|
<div class="settings-section-label">Uploads</div>
|
||||||
<div class="settings-row">
|
<div class="settings-row">
|
||||||
<label style="min-width:185px">Globale parallele Uploads</label>
|
<label style="min-width:185px">Globale parallele Uploads</label>
|
||||||
@ -2969,10 +2954,29 @@ function renderSettings() {
|
|||||||
<label>Manuell prüfen</label>
|
<label>Manuell prüfen</label>
|
||||||
<button class="btn btn-xs btn-secondary" id="manualUpdateCheckBtn">Nach Updates suchen</button>
|
<button class="btn btn-xs btn-secondary" id="manualUpdateCheckBtn">Nach Updates suchen</button>
|
||||||
</div>
|
</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>
|
<div class="settings-section-label">Log</div>
|
||||||
`;
|
<div class="settings-row">
|
||||||
|
<label>FileUploader Log</label>
|
||||||
pages.automatik.innerHTML = `
|
<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-section-label">Unbeaufsichtigter Betrieb</div>
|
||||||
<div class="settings-row">
|
<div class="settings-row">
|
||||||
<label>Auto-Retry Runden</label>
|
<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">
|
<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>
|
<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>
|
||||||
<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">
|
<div class="settings-row">
|
||||||
<label>Ordnerpfad</label>
|
<label>Ordnerpfad</label>
|
||||||
<input type="text" class="key-input settings-autosave" id="fmFolderPathInput" value="${escapeAttr(fm.folderPath || '')}" placeholder="Ordner wählen..." style="flex:1">
|
<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>`).join('')}
|
||||||
</div>
|
</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>'}
|
${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 = `
|
// Toggle folder monitor panel
|
||||||
<div class="settings-section-label">Log</div>
|
folderMonitorPanel.querySelector('.hoster-panel-header').addEventListener('click', () => {
|
||||||
<div class="settings-row">
|
const body = folderMonitorPanel.querySelector('.hoster-panel-body');
|
||||||
<label>FileUploader Log</label>
|
const arrow = folderMonitorPanel.querySelector('.panel-arrow');
|
||||||
<input type="text" class="key-input settings-autosave" id="logFilePathInput" value="${escapeAttr(globalSettings.logFilePath || '')}" placeholder="Standardpfad verwenden">
|
const isOpen = body.style.display !== 'none';
|
||||||
<button class="btn btn-xs btn-secondary" id="chooseLogFilePathBtn">Ordner wählen</button>
|
body.style.display = isOpen ? 'none' : 'block';
|
||||||
<button class="btn btn-xs btn-secondary" id="openLogFolderBtn" title="Log-Ordner im Explorer öffnen">Öffnen</button>
|
arrow.innerHTML = isOpen ? '▶' : '▼';
|
||||||
</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>
|
|
||||||
`;
|
|
||||||
|
|
||||||
pages.remote.innerHTML = `
|
// Update badge immediately on checkbox/path change
|
||||||
<div class="settings-section-label">Server <span class="panel-status${remoteSettings.enabled ? ' active' : ''}" id="remoteStatusBadge">${remoteSettings.enabled ? 'Aktiv' : 'Inaktiv'}</span></div>
|
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-grid-mini">
|
||||||
<div class="settings-row checkbox-row">
|
<div class="settings-row checkbox-row">
|
||||||
<label>Aktiviert</label>
|
<label>Aktiviert</label>
|
||||||
@ -3108,103 +3206,20 @@ function renderSettings() {
|
|||||||
<div class="settings-row">
|
<div class="settings-row">
|
||||||
<span id="remoteConnectionStatus" style="color:#94a3b8">Prüfe...</span>
|
<span id="remoteConnectionStatus" style="color:#94a3b8">Prüfe...</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
|
container.appendChild(remotePanel);
|
||||||
|
|
||||||
pages.backup.innerHTML = `
|
// Toggle remote panel
|
||||||
<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>
|
remotePanel.querySelector('.hoster-panel-header').addEventListener('click', () => {
|
||||||
<div style="display:flex;gap:8px">
|
const body = remotePanel.querySelector('.hoster-panel-body');
|
||||||
<button class="btn btn-secondary" id="exportBackupBtn">Backup exportieren</button>
|
const arrow = remotePanel.querySelector('.panel-arrow');
|
||||||
<button class="btn btn-secondary" id="importBackupBtn">Backup importieren</button>
|
const isOpen = body.style.display !== 'none';
|
||||||
</div>
|
body.style.display = isOpen ? 'none' : 'block';
|
||||||
`;
|
arrow.innerHTML = isOpen ? '▶' : '▼';
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Copy token
|
||||||
document.getElementById('remoteCopyTokenBtn').addEventListener('click', async () => {
|
document.getElementById('remoteCopyTokenBtn').addEventListener('click', async () => {
|
||||||
const token = document.getElementById('remoteTokenInput').value;
|
const token = document.getElementById('remoteTokenInput').value;
|
||||||
if (token) {
|
if (token) {
|
||||||
@ -3214,12 +3229,14 @@ function renderSettings() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Regenerate token
|
||||||
document.getElementById('remoteRegenerateTokenBtn').addEventListener('click', async () => {
|
document.getElementById('remoteRegenerateTokenBtn').addEventListener('click', async () => {
|
||||||
const newToken = await window.api.remoteGenerateToken();
|
const newToken = await window.api.remoteGenerateToken();
|
||||||
document.getElementById('remoteTokenInput').value = newToken;
|
document.getElementById('remoteTokenInput').value = newToken;
|
||||||
scheduleSettingsSave();
|
scheduleSettingsSave();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update status
|
||||||
window.api.remoteStatus().then(status => {
|
window.api.remoteStatus().then(status => {
|
||||||
const el = document.getElementById('remoteConnectionStatus');
|
const el = document.getElementById('remoteConnectionStatus');
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
@ -3232,9 +3249,43 @@ function renderSettings() {
|
|||||||
}
|
}
|
||||||
}).catch(() => {});
|
}).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('exportBackupBtn').addEventListener('click', () => doBackupExport());
|
||||||
document.getElementById('importBackupBtn').addEventListener('click', () => doBackupImport());
|
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('chooseLogFilePathBtn')?.addEventListener('click', chooseLogFilePath);
|
||||||
document.getElementById('openLogFolderBtn')?.addEventListener('click', () => window.api.openLogFolder());
|
document.getElementById('openLogFolderBtn')?.addEventListener('click', () => window.api.openLogFolder());
|
||||||
document.getElementById('manualUpdateCheckBtn')?.addEventListener('click', async (e) => {
|
document.getElementById('manualUpdateCheckBtn')?.addEventListener('click', async (e) => {
|
||||||
@ -3282,62 +3333,42 @@ function scheduleSettingsSave() {
|
|||||||
async function saveSettings(options = {}) {
|
async function saveSettings(options = {}) {
|
||||||
const { feedbackText = 'Gespeichert!' } = options;
|
const { feedbackText = 'Gespeichert!' } = options;
|
||||||
const newHosterSettings = { ...(config.hosterSettings || {}) };
|
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 = {
|
const globalSettings = {
|
||||||
...cur,
|
...(config.globalSettings || {}),
|
||||||
logFilePath: elTxt('logFilePathInput', cur.logFilePath || '').trim(),
|
logFilePath: (document.getElementById('logFilePathInput')?.value || '').trim(),
|
||||||
logMode: (() => {
|
logMode: (() => {
|
||||||
const el = document.getElementById('logModeInput');
|
const v = document.getElementById('logModeInput')?.value;
|
||||||
if (!el) return cur.logMode || 'single';
|
|
||||||
const v = el.value;
|
|
||||||
return (v === 'single' || v === 'daily' || v === 'session') ? v : 'single';
|
return (v === 'single' || v === 'daily' || v === 'session') ? v : 'single';
|
||||||
})(),
|
})(),
|
||||||
resumeQueueOnLaunch: elChk('resumeQueueOnLaunchInput', cur.resumeQueueOnLaunch !== false),
|
resumeQueueOnLaunch: !!document.getElementById('resumeQueueOnLaunchInput')?.checked,
|
||||||
parallelUploadCount: elInt('parallelUploadCountInput', cur.parallelUploadCount ?? 0, 0, 0, 100),
|
parallelUploadCount: Math.max(0, Math.min(100, parseInt(document.getElementById('parallelUploadCountInput')?.value || '0', 10) || 0)),
|
||||||
scaleParallelUploads: elChk('scaleParallelUploadsInput', !!cur.scaleParallelUploads),
|
scaleParallelUploads: !!document.getElementById('scaleParallelUploadsInput')?.checked,
|
||||||
removeFromQueueOnDone: elChk('removeFromQueueOnDoneInput', !!cur.removeFromQueueOnDone),
|
removeFromQueueOnDone: !!document.getElementById('removeFromQueueOnDoneInput')?.checked,
|
||||||
showDropTarget: elChk('showDropTargetInput', !!cur.showDropTarget),
|
showDropTarget: !!document.getElementById('showDropTargetInput')?.checked,
|
||||||
globalMaxSpeedKbs: (() => {
|
globalMaxSpeedKbs: Math.max(0, Math.round((parseFloat(document.getElementById('globalMaxSpeedMbsInput')?.value || '0') || 0) * 1024)),
|
||||||
const el = document.getElementById('globalMaxSpeedMbsInput');
|
logVerbose: !!document.getElementById('logVerboseInput')?.checked,
|
||||||
if (!el) return cur.globalMaxSpeedKbs ?? 0;
|
webhookUrl: (document.getElementById('webhookUrlInput')?.value || '').trim(),
|
||||||
return Math.max(0, Math.round((parseFloat(el.value || '0') || 0) * 1024));
|
webhookMention: (document.getElementById('webhookMentionInput')?.value || '').trim(),
|
||||||
})(),
|
autoRetryRounds: Math.max(0, Math.min(5, parseInt(document.getElementById('autoRetryRoundsInput')?.value || '0', 10) || 0)),
|
||||||
logVerbose: elChk('logVerboseInput', !!cur.logVerbose),
|
autoRetryDelayMin: Math.max(1, Math.min(120, parseInt(document.getElementById('autoRetryDelayMinInput')?.value || '5', 10) || 5)),
|
||||||
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),
|
|
||||||
folderMonitor: {
|
folderMonitor: {
|
||||||
...curFm,
|
...((config.globalSettings || {}).folderMonitor || {}),
|
||||||
enabled: elChk('fmEnabledInput', !!curFm.enabled),
|
enabled: !!document.getElementById('fmEnabledInput')?.checked,
|
||||||
folderPath: elTxt('fmFolderPathInput', curFm.folderPath || '').trim(),
|
folderPath: (document.getElementById('fmFolderPathInput')?.value || '').trim(),
|
||||||
recursive: elChk('fmRecursiveInput', !!curFm.recursive),
|
recursive: !!document.getElementById('fmRecursiveInput')?.checked,
|
||||||
filterMode: (() => { const el = document.getElementById('fmFilterModeInput'); return el ? (el.value || 'include') : (curFm.filterMode || 'include'); })(),
|
filterMode: document.getElementById('fmFilterModeInput')?.value || 'include',
|
||||||
extensions: elTxt('fmExtensionsInput', curFm.extensions || '').trim(),
|
extensions: (document.getElementById('fmExtensionsInput')?.value || '').trim(),
|
||||||
skipDuplicates: elChk('fmSkipDuplicatesInput', curFm.skipDuplicates !== false),
|
skipDuplicates: !!document.getElementById('fmSkipDuplicatesInput')?.checked,
|
||||||
delaySec: elInt('fmDelaySecInput', curFm.delaySec ?? 3, 3, 1, 300),
|
delaySec: Math.max(1, parseInt(document.getElementById('fmDelaySecInput')?.value || '3', 10) || 3),
|
||||||
autoStart: elChk('fmAutoStartInput', curFm.autoStart !== false),
|
autoStart: !!document.getElementById('fmAutoStartInput')?.checked,
|
||||||
hosters: document.querySelector('.fm-hoster-checkbox')
|
hosters: Array.from(document.querySelectorAll('.fm-hoster-checkbox:checked')).map(el => el.dataset.fmHoster)
|
||||||
? Array.from(document.querySelectorAll('.fm-hoster-checkbox:checked')).map(el => el.dataset.fmHoster)
|
|
||||||
: (curFm.hosters || [])
|
|
||||||
},
|
},
|
||||||
remote: {
|
remote: {
|
||||||
...curRemote,
|
...((config.globalSettings || {}).remote || {}),
|
||||||
enabled: elChk('remoteEnabledInput', !!curRemote.enabled),
|
enabled: !!document.getElementById('remoteEnabledInput')?.checked,
|
||||||
port: elInt('remotePortInput', curRemote.port || 9100, 9100, 1024, 65535),
|
port: Math.max(1024, Math.min(65535, parseInt(document.getElementById('remotePortInput')?.value || '9100', 10) || 9100)),
|
||||||
token: elTxt('remoteTokenInput', curRemote.token || '').trim(),
|
token: (document.getElementById('remoteTokenInput')?.value || '').trim(),
|
||||||
allowInput: elChk('remoteAllowInputInput', curRemote.allowInput !== false)
|
allowInput: !!document.getElementById('remoteAllowInputInput')?.checked
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -4740,9 +4771,6 @@ function setupListeners() {
|
|||||||
// Virtual scroll for large queues
|
// Virtual scroll for large queues
|
||||||
const queueContainer = document.getElementById('queueContainer');
|
const queueContainer = document.getElementById('queueContainer');
|
||||||
if (queueContainer) queueContainer.addEventListener('scroll', _onQueueScroll, { passive: true });
|
if (queueContainer) queueContainer.addEventListener('scroll', _onQueueScroll, { passive: true });
|
||||||
if (queueContainer && typeof window.ResizeObserver !== 'undefined') {
|
|
||||||
new window.ResizeObserver(_onQueueScroll).observe(queueContainer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Queue table sorting
|
// Queue table sorting
|
||||||
document.querySelectorAll('#queueTable th.sortable').forEach(th => {
|
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; }
|
.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; }
|
.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 {
|
.settings-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user