Compare commits
2 Commits
d9b00f8fd7
...
294f6a4de7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
294f6a4de7 | ||
|
|
d9dec33ecc |
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "multi-hoster-uploader",
|
"name": "multi-hoster-uploader",
|
||||||
"version": "1.2.1",
|
"version": "1.3.0",
|
||||||
"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": {
|
||||||
|
|||||||
401
renderer/app.js
401
renderer/app.js
@ -7,6 +7,8 @@ let config = { hosters: {}, hosterSettings: {}, globalSettings: {} };
|
|||||||
let hosterSettings = {};
|
let hosterSettings = {};
|
||||||
let uploading = false;
|
let uploading = false;
|
||||||
let healthCheckRunning = false;
|
let healthCheckRunning = false;
|
||||||
|
let accountStatuses = {}; // { 'voe.sx': { status: 'ok'|'error'|'checking'|'unchecked', message: '' } }
|
||||||
|
let editingAccountHoster = null; // null = adding, string = editing
|
||||||
let autoHealthCheckEnabled = true;
|
let autoHealthCheckEnabled = true;
|
||||||
let queuePersistTimer = null;
|
let queuePersistTimer = null;
|
||||||
const AUTO_CHECK_PREF_KEY = 'autoHealthCheckBeforeUpload';
|
const AUTO_CHECK_PREF_KEY = 'autoHealthCheckBeforeUpload';
|
||||||
@ -33,6 +35,7 @@ async function init() {
|
|||||||
renderHosterSummary();
|
renderHosterSummary();
|
||||||
renderHosterModal();
|
renderHosterModal();
|
||||||
renderSettings();
|
renderSettings();
|
||||||
|
renderAccounts();
|
||||||
setupListeners();
|
setupListeners();
|
||||||
setupDragDrop();
|
setupDragDrop();
|
||||||
loadHistory();
|
loadHistory();
|
||||||
@ -150,9 +153,10 @@ function renderHosterModal() {
|
|||||||
list.innerHTML = available.map(item => {
|
list.innerHTML = available.map(item => {
|
||||||
const checked = selectedUploadHosters.includes(item.name);
|
const checked = selectedUploadHosters.includes(item.name);
|
||||||
const h = config.hosters[item.name] || {};
|
const h = config.hosters[item.name] || {};
|
||||||
const subtitle = item.name === 'vidmoly.me' ? 'Login hinterlegt'
|
const st = accountStatuses[item.name];
|
||||||
: (item.name === 'voe.sx' && h.username && h.password) ? 'Login hinterlegt'
|
const subtitle = st && st.status === 'ok' ? 'Bereit'
|
||||||
: 'API-Key hinterlegt';
|
: st && st.status === 'error' ? 'Login-Fehler'
|
||||||
|
: getCredentialLabel(item.name, h) + ' hinterlegt';
|
||||||
return `
|
return `
|
||||||
<label class="hoster-option${checked ? ' selected' : ''}" data-hoster-option="${item.name}">
|
<label class="hoster-option${checked ? ' selected' : ''}" data-hoster-option="${item.name}">
|
||||||
<input type="checkbox" data-hoster-modal="${item.name}" ${checked ? 'checked' : ''}>
|
<input type="checkbox" data-hoster-modal="${item.name}" ${checked ? 'checked' : ''}>
|
||||||
@ -897,44 +901,6 @@ function renderSettings() {
|
|||||||
const panel = document.createElement('div');
|
const panel = document.createElement('div');
|
||||||
panel.className = 'hoster-settings-panel';
|
panel.className = 'hoster-settings-panel';
|
||||||
|
|
||||||
let credsHtml = '';
|
|
||||||
if (name === 'vidmoly.me') {
|
|
||||||
credsHtml = `
|
|
||||||
<div class="settings-row">
|
|
||||||
<label>Username</label>
|
|
||||||
<input type="text" class="key-input" data-hoster="${name}" data-field="username" value="${escapeAttr(hoster.username || '')}" placeholder="Username">
|
|
||||||
</div>
|
|
||||||
<div class="settings-row">
|
|
||||||
<label>Passwort</label>
|
|
||||||
<input type="password" class="key-input" data-hoster="${name}" data-field="password" value="${escapeAttr(hoster.password || '')}" placeholder="Passwort">
|
|
||||||
<button class="toggle-vis" title="Anzeigen">👁</button>
|
|
||||||
</div>`;
|
|
||||||
} else if (name === 'voe.sx') {
|
|
||||||
credsHtml = `
|
|
||||||
<div class="settings-row">
|
|
||||||
<label>E-Mail (Login)</label>
|
|
||||||
<input type="text" class="key-input" data-hoster="${name}" data-field="username" value="${escapeAttr(hoster.username || '')}" placeholder="E-Mail fuer Login">
|
|
||||||
</div>
|
|
||||||
<div class="settings-row">
|
|
||||||
<label>Passwort (Login)</label>
|
|
||||||
<input type="password" class="key-input" data-hoster="${name}" data-field="password" value="${escapeAttr(hoster.password || '')}" placeholder="Passwort fuer Login">
|
|
||||||
<button class="toggle-vis" title="Anzeigen">👁</button>
|
|
||||||
</div>
|
|
||||||
<div class="settings-row">
|
|
||||||
<label>API Key (optional)</label>
|
|
||||||
<input type="password" class="key-input" data-hoster="${name}" data-field="apiKey" value="${escapeAttr(hoster.apiKey || '')}" placeholder="API Key (Fallback)">
|
|
||||||
<button class="toggle-vis" title="Anzeigen">👁</button>
|
|
||||||
</div>
|
|
||||||
<p class="hint" style="margin:4px 0 8px;opacity:0.6">Login wird bevorzugt. API-Key nur als Fallback.</p>`;
|
|
||||||
} else {
|
|
||||||
credsHtml = `
|
|
||||||
<div class="settings-row">
|
|
||||||
<label>API Key</label>
|
|
||||||
<input type="password" class="key-input" data-hoster="${name}" data-field="apiKey" value="${escapeAttr(hoster.apiKey || '')}" placeholder="API Key">
|
|
||||||
<button class="toggle-vis" title="Anzeigen">👁</button>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
panel.innerHTML = `
|
panel.innerHTML = `
|
||||||
<div class="hoster-panel-header" data-hoster="${name}">
|
<div class="hoster-panel-header" data-hoster="${name}">
|
||||||
<span class="panel-arrow">▶</span>
|
<span class="panel-arrow">▶</span>
|
||||||
@ -942,8 +908,6 @@ function renderSettings() {
|
|||||||
<span class="panel-status ${hosterHasCredentials(name, hoster) ? 'active' : 'inactive'}">${hosterHasCredentials(name, hoster) ? 'Aktiv' : 'Inaktiv'}</span>
|
<span class="panel-status ${hosterHasCredentials(name, hoster) ? 'active' : 'inactive'}">${hosterHasCredentials(name, hoster) ? 'Aktiv' : 'Inaktiv'}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="hoster-panel-body" data-panel="${name}" style="display:none">
|
<div class="hoster-panel-body" data-panel="${name}" style="display:none">
|
||||||
${credsHtml}
|
|
||||||
<div class="settings-divider"></div>
|
|
||||||
<h4>Upload Einstellungen</h4>
|
<h4>Upload Einstellungen</h4>
|
||||||
<div class="settings-grid-mini">
|
<div class="settings-grid-mini">
|
||||||
<div class="settings-row">
|
<div class="settings-row">
|
||||||
@ -987,14 +951,6 @@ function renderSettings() {
|
|||||||
body.style.display = isOpen ? 'none' : 'block';
|
body.style.display = isOpen ? 'none' : 'block';
|
||||||
arrow.innerHTML = isOpen ? '▶' : '▼';
|
arrow.innerHTML = isOpen ? '▶' : '▼';
|
||||||
});
|
});
|
||||||
|
|
||||||
// Toggle visibility
|
|
||||||
panel.querySelectorAll('.toggle-vis').forEach(btn => {
|
|
||||||
btn.addEventListener('click', () => {
|
|
||||||
const input = btn.previousElementSibling;
|
|
||||||
input.type = input.type === 'password' ? 'text' : 'password';
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('chooseLogFilePathBtn')?.addEventListener('click', chooseLogFilePath);
|
document.getElementById('chooseLogFilePathBtn')?.addEventListener('click', chooseLogFilePath);
|
||||||
@ -1008,7 +964,6 @@ async function chooseLogFilePath() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function saveSettings() {
|
async function saveSettings() {
|
||||||
const hosters = {};
|
|
||||||
const newHosterSettings = {};
|
const newHosterSettings = {};
|
||||||
const globalSettings = {
|
const globalSettings = {
|
||||||
...(config.globalSettings || {}),
|
...(config.globalSettings || {}),
|
||||||
@ -1017,22 +972,6 @@ async function saveSettings() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (const name of HOSTERS) {
|
for (const name of HOSTERS) {
|
||||||
// Credentials
|
|
||||||
if (name === 'vidmoly.me') {
|
|
||||||
const username = (document.querySelector(`.key-input[data-hoster="${name}"][data-field="username"]`)?.value || '').trim();
|
|
||||||
const password = (document.querySelector(`.key-input[data-hoster="${name}"][data-field="password"]`)?.value || '').trim();
|
|
||||||
hosters[name] = { enabled: !!(username && password), authType: 'login', username, password };
|
|
||||||
} else if (name === 'voe.sx') {
|
|
||||||
const username = (document.querySelector(`.key-input[data-hoster="${name}"][data-field="username"]`)?.value || '').trim();
|
|
||||||
const password = (document.querySelector(`.key-input[data-hoster="${name}"][data-field="password"]`)?.value || '').trim();
|
|
||||||
const apiKey = (document.querySelector(`.key-input[data-hoster="${name}"][data-field="apiKey"]`)?.value || '').trim();
|
|
||||||
hosters[name] = { enabled: !!(username && password) || !!apiKey, username, password, apiKey };
|
|
||||||
} else {
|
|
||||||
const apiKey = (document.querySelector(`.key-input[data-hoster="${name}"][data-field="apiKey"]`)?.value || '').trim();
|
|
||||||
hosters[name] = { enabled: !!apiKey, apiKey };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload settings
|
|
||||||
const hs = {};
|
const hs = {};
|
||||||
document.querySelectorAll(`.hs-input[data-hoster="${name}"]`).forEach(input => {
|
document.querySelectorAll(`.hs-input[data-hoster="${name}"]`).forEach(input => {
|
||||||
const field = input.dataset.hs;
|
const field = input.dataset.hs;
|
||||||
@ -1041,22 +980,303 @@ async function saveSettings() {
|
|||||||
newHosterSettings[name] = hs;
|
newHosterSettings[name] = hs;
|
||||||
}
|
}
|
||||||
|
|
||||||
await window.api.saveConfig({ hosters });
|
|
||||||
await window.api.saveHosterSettings(newHosterSettings);
|
await window.api.saveHosterSettings(newHosterSettings);
|
||||||
await window.api.saveGlobalSettings(globalSettings);
|
await window.api.saveGlobalSettings(globalSettings);
|
||||||
config = await window.api.getConfig();
|
config = await window.api.getConfig();
|
||||||
hosterSettings = config.hosterSettings || {};
|
hosterSettings = config.hosterSettings || {};
|
||||||
syncSelectedUploadHosters();
|
|
||||||
renderHosterSummary();
|
|
||||||
renderHosterModal();
|
|
||||||
renderSettings();
|
renderSettings();
|
||||||
renderHealthCheckResults([]);
|
|
||||||
|
|
||||||
const feedback = document.getElementById('saveFeedback');
|
const feedback = document.getElementById('saveFeedback');
|
||||||
feedback.textContent = 'Gespeichert!';
|
feedback.textContent = 'Gespeichert!';
|
||||||
setTimeout(() => { feedback.textContent = ''; }, 2000);
|
setTimeout(() => { feedback.textContent = ''; }, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Accounts ---
|
||||||
|
function getAccountsWithCreds() {
|
||||||
|
return HOSTERS
|
||||||
|
.map(name => ({ name, hoster: config.hosters[name] || {} }))
|
||||||
|
.filter(item => hosterHasCredentials(item.name, item.hoster));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHostersWithoutCreds() {
|
||||||
|
return HOSTERS.filter(name => !hosterHasCredentials(name, config.hosters[name] || {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCredentialLabel(name, hoster) {
|
||||||
|
if (name === 'vidmoly.me') return hoster.username || 'Login';
|
||||||
|
if (name === 'voe.sx') return hoster.username && hoster.password ? (hoster.username || 'Login') : 'API-Key';
|
||||||
|
return 'API-Key';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderAccounts() {
|
||||||
|
const container = document.getElementById('accountsList');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const accounts = getAccountsWithCreds();
|
||||||
|
|
||||||
|
if (accounts.length === 0) {
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="accounts-empty">
|
||||||
|
<p>Keine Accounts vorhanden</p>
|
||||||
|
<span class="hint">Klicke auf "Account hinzufuegen" um einen Hoster einzurichten.</span>
|
||||||
|
</div>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = accounts.map(({ name, hoster }) => {
|
||||||
|
const st = accountStatuses[name] || { status: 'unchecked', message: '' };
|
||||||
|
const statusLabels = { ok: 'Bereit', checking: 'Pruefe...', error: 'Fehler', unchecked: 'Nicht geprueft' };
|
||||||
|
const statusLabel = statusLabels[st.status] || 'Nicht geprueft';
|
||||||
|
const credLabel = getCredentialLabel(name, hoster);
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="account-card" data-account="${name}">
|
||||||
|
<div class="account-card-info">
|
||||||
|
<div class="account-card-title">${escapeHtml(name)}</div>
|
||||||
|
<div class="account-card-subtitle" title="${escapeAttr(credLabel)}">${escapeHtml(credLabel)}${st.status === 'error' && st.message ? ' — ' + escapeHtml(st.message) : ''}</div>
|
||||||
|
</div>
|
||||||
|
<span class="account-status status-${st.status}">
|
||||||
|
<span class="account-status-dot"></span>
|
||||||
|
${statusLabel}
|
||||||
|
</span>
|
||||||
|
<div class="account-card-actions">
|
||||||
|
<button class="btn btn-xs btn-secondary" data-account-check="${name}">Pruefen</button>
|
||||||
|
<button class="btn btn-xs btn-secondary" data-account-edit="${name}">Bearbeiten</button>
|
||||||
|
<button class="btn btn-xs btn-danger" data-account-delete="${name}">Loeschen</button>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
// Wire up buttons
|
||||||
|
container.querySelectorAll('[data-account-edit]').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => openAccountModal(btn.dataset.accountEdit));
|
||||||
|
});
|
||||||
|
container.querySelectorAll('[data-account-delete]').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => openDeleteAccountModal(btn.dataset.accountDelete));
|
||||||
|
});
|
||||||
|
container.querySelectorAll('[data-account-check]').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => checkSingleAccount(btn.dataset.accountCheck));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkSingleAccount(hosterName) {
|
||||||
|
accountStatuses[hosterName] = { status: 'checking', message: '' };
|
||||||
|
renderAccounts();
|
||||||
|
try {
|
||||||
|
const rows = await executeHealthCheck([hosterName], 'auto');
|
||||||
|
const row = rows.find(r => r.hoster === hosterName);
|
||||||
|
if (row && row.status === 'ok') {
|
||||||
|
accountStatuses[hosterName] = { status: 'ok', message: row.message || '' };
|
||||||
|
} else {
|
||||||
|
accountStatuses[hosterName] = { status: 'error', message: (row && row.message) || 'Pruefung fehlgeschlagen' };
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
accountStatuses[hosterName] = { status: 'error', message: err.message || 'Pruefung fehlgeschlagen' };
|
||||||
|
}
|
||||||
|
renderAccounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCredsFieldsHtml(name, hoster) {
|
||||||
|
hoster = hoster || {};
|
||||||
|
if (name === 'vidmoly.me') {
|
||||||
|
return `
|
||||||
|
<div class="settings-row">
|
||||||
|
<label>Username</label>
|
||||||
|
<input type="text" class="key-input" id="accField_username" value="${escapeAttr(hoster.username || '')}" placeholder="Username">
|
||||||
|
</div>
|
||||||
|
<div class="settings-row">
|
||||||
|
<label>Passwort</label>
|
||||||
|
<input type="password" class="key-input" id="accField_password" value="${escapeAttr(hoster.password || '')}" placeholder="Passwort">
|
||||||
|
<button class="toggle-vis" type="button" title="Anzeigen">👁</button>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
if (name === 'voe.sx') {
|
||||||
|
return `
|
||||||
|
<div class="settings-row">
|
||||||
|
<label>E-Mail (Login)</label>
|
||||||
|
<input type="text" class="key-input" id="accField_username" value="${escapeAttr(hoster.username || '')}" placeholder="E-Mail fuer Login">
|
||||||
|
</div>
|
||||||
|
<div class="settings-row">
|
||||||
|
<label>Passwort (Login)</label>
|
||||||
|
<input type="password" class="key-input" id="accField_password" value="${escapeAttr(hoster.password || '')}" placeholder="Passwort fuer Login">
|
||||||
|
<button class="toggle-vis" type="button" title="Anzeigen">👁</button>
|
||||||
|
</div>
|
||||||
|
<div class="settings-row">
|
||||||
|
<label>API Key (optional)</label>
|
||||||
|
<input type="password" class="key-input" id="accField_apiKey" value="${escapeAttr(hoster.apiKey || '')}" placeholder="API Key (Fallback)">
|
||||||
|
<button class="toggle-vis" type="button" title="Anzeigen">👁</button>
|
||||||
|
</div>
|
||||||
|
<p class="hint" style="margin:4px 0 0;opacity:0.6">Login wird bevorzugt. API-Key nur als Fallback.</p>`;
|
||||||
|
}
|
||||||
|
// Default: API key only
|
||||||
|
return `
|
||||||
|
<div class="settings-row">
|
||||||
|
<label>API Key</label>
|
||||||
|
<input type="password" class="key-input" id="accField_apiKey" value="${escapeAttr(hoster.apiKey || '')}" placeholder="API Key">
|
||||||
|
<button class="toggle-vis" type="button" title="Anzeigen">👁</button>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openAccountModal(editHoster) {
|
||||||
|
editingAccountHoster = editHoster || null;
|
||||||
|
const modal = document.getElementById('accountModal');
|
||||||
|
const title = document.getElementById('accountModalTitle');
|
||||||
|
const subtitle = document.getElementById('accountModalSubtitle');
|
||||||
|
const hosterRow = document.getElementById('accountHosterRow');
|
||||||
|
const hosterSelect = document.getElementById('accountHosterSelect');
|
||||||
|
const credsContainer = document.getElementById('accountCredsFields');
|
||||||
|
const statusEl = document.getElementById('accountModalStatus');
|
||||||
|
const saveBtn = document.getElementById('saveAccountBtn');
|
||||||
|
|
||||||
|
statusEl.textContent = '';
|
||||||
|
statusEl.className = 'account-modal-status';
|
||||||
|
|
||||||
|
if (editingAccountHoster) {
|
||||||
|
// Edit mode
|
||||||
|
title.textContent = 'Account bearbeiten';
|
||||||
|
subtitle.textContent = `Zugangsdaten fuer ${editingAccountHoster} bearbeiten.`;
|
||||||
|
hosterRow.style.display = 'none';
|
||||||
|
saveBtn.textContent = 'Speichern & Pruefen';
|
||||||
|
const hoster = config.hosters[editingAccountHoster] || {};
|
||||||
|
credsContainer.innerHTML = getCredsFieldsHtml(editingAccountHoster, hoster);
|
||||||
|
} else {
|
||||||
|
// Add mode
|
||||||
|
title.textContent = 'Account hinzufuegen';
|
||||||
|
subtitle.textContent = 'Waehle einen Hoster und gib deine Zugangsdaten ein.';
|
||||||
|
hosterRow.style.display = 'flex';
|
||||||
|
saveBtn.textContent = 'Anlegen & Pruefen';
|
||||||
|
const available = getHostersWithoutCreds();
|
||||||
|
if (available.length === 0) {
|
||||||
|
hosterSelect.innerHTML = '<option value="">Alle Hoster bereits eingerichtet</option>';
|
||||||
|
credsContainer.innerHTML = '';
|
||||||
|
} else {
|
||||||
|
hosterSelect.innerHTML = available.map(name => `<option value="${name}">${name}</option>`).join('');
|
||||||
|
credsContainer.innerHTML = getCredsFieldsHtml(available[0], {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle visibility buttons
|
||||||
|
credsContainer.querySelectorAll('.toggle-vis').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
const input = btn.previousElementSibling;
|
||||||
|
input.type = input.type === 'password' ? 'text' : 'password';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modal.style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeAccountModal() {
|
||||||
|
document.getElementById('accountModal').style.display = 'none';
|
||||||
|
editingAccountHoster = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openDeleteAccountModal(hosterName) {
|
||||||
|
const modal = document.getElementById('deleteAccountModal');
|
||||||
|
const msg = document.getElementById('deleteAccountMessage');
|
||||||
|
msg.textContent = `Account fuer "${hosterName}" wirklich loeschen? Alle Zugangsdaten werden entfernt.`;
|
||||||
|
modal.dataset.hoster = hosterName;
|
||||||
|
modal.style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDeleteModal() {
|
||||||
|
document.getElementById('deleteAccountModal').style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteAccount(hosterName) {
|
||||||
|
const hosters = { ...config.hosters };
|
||||||
|
// Reset credentials to defaults
|
||||||
|
if (hosterName === 'vidmoly.me') {
|
||||||
|
hosters[hosterName] = { enabled: false, authType: 'login', username: '', password: '' };
|
||||||
|
} else if (hosterName === 'voe.sx') {
|
||||||
|
hosters[hosterName] = { enabled: false, username: '', password: '', apiKey: '' };
|
||||||
|
} else {
|
||||||
|
hosters[hosterName] = { enabled: false, apiKey: '' };
|
||||||
|
}
|
||||||
|
delete accountStatuses[hosterName];
|
||||||
|
await window.api.saveConfig({ hosters });
|
||||||
|
config = await window.api.getConfig();
|
||||||
|
syncSelectedUploadHosters();
|
||||||
|
renderAccounts();
|
||||||
|
renderHosterSummary();
|
||||||
|
renderHosterModal();
|
||||||
|
renderSettings();
|
||||||
|
closeDeleteModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
function readAccountCredsFromModal(hosterName) {
|
||||||
|
if (hosterName === 'vidmoly.me') {
|
||||||
|
const username = (document.getElementById('accField_username')?.value || '').trim();
|
||||||
|
const password = (document.getElementById('accField_password')?.value || '').trim();
|
||||||
|
return { enabled: !!(username && password), authType: 'login', username, password };
|
||||||
|
}
|
||||||
|
if (hosterName === 'voe.sx') {
|
||||||
|
const username = (document.getElementById('accField_username')?.value || '').trim();
|
||||||
|
const password = (document.getElementById('accField_password')?.value || '').trim();
|
||||||
|
const apiKey = (document.getElementById('accField_apiKey')?.value || '').trim();
|
||||||
|
return { enabled: !!(username && password) || !!apiKey, username, password, apiKey };
|
||||||
|
}
|
||||||
|
const apiKey = (document.getElementById('accField_apiKey')?.value || '').trim();
|
||||||
|
return { enabled: !!apiKey, apiKey };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveAccount() {
|
||||||
|
const hosterName = editingAccountHoster || document.getElementById('accountHosterSelect')?.value;
|
||||||
|
if (!hosterName) return;
|
||||||
|
|
||||||
|
const creds = readAccountCredsFromModal(hosterName);
|
||||||
|
if (!creds.enabled) {
|
||||||
|
const statusEl = document.getElementById('accountModalStatus');
|
||||||
|
statusEl.textContent = 'Bitte Zugangsdaten eingeben.';
|
||||||
|
statusEl.className = 'account-modal-status error';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save credentials
|
||||||
|
const hosters = { ...config.hosters };
|
||||||
|
hosters[hosterName] = creds;
|
||||||
|
await window.api.saveConfig({ hosters });
|
||||||
|
config = await window.api.getConfig();
|
||||||
|
|
||||||
|
// Show checking status
|
||||||
|
const statusEl = document.getElementById('accountModalStatus');
|
||||||
|
const saveBtn = document.getElementById('saveAccountBtn');
|
||||||
|
statusEl.textContent = 'Pruefe Login...';
|
||||||
|
statusEl.className = 'account-modal-status checking';
|
||||||
|
saveBtn.disabled = true;
|
||||||
|
|
||||||
|
accountStatuses[hosterName] = { status: 'checking', message: '' };
|
||||||
|
syncSelectedUploadHosters();
|
||||||
|
renderAccounts();
|
||||||
|
renderHosterSummary();
|
||||||
|
renderHosterModal();
|
||||||
|
renderSettings();
|
||||||
|
|
||||||
|
// Run health check
|
||||||
|
try {
|
||||||
|
const rows = await executeHealthCheck([hosterName], 'auto');
|
||||||
|
const row = rows.find(r => r.hoster === hosterName);
|
||||||
|
if (row && row.status === 'ok') {
|
||||||
|
accountStatuses[hosterName] = { status: 'ok', message: row.message || '' };
|
||||||
|
statusEl.textContent = 'Login erfolgreich!';
|
||||||
|
statusEl.className = 'account-modal-status ok';
|
||||||
|
setTimeout(() => closeAccountModal(), 1200);
|
||||||
|
} else {
|
||||||
|
const msg = (row && row.message) || 'Login fehlgeschlagen';
|
||||||
|
accountStatuses[hosterName] = { status: 'error', message: msg };
|
||||||
|
statusEl.textContent = msg;
|
||||||
|
statusEl.className = 'account-modal-status error';
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
accountStatuses[hosterName] = { status: 'error', message: err.message || 'Pruefung fehlgeschlagen' };
|
||||||
|
statusEl.textContent = err.message || 'Pruefung fehlgeschlagen';
|
||||||
|
statusEl.className = 'account-modal-status error';
|
||||||
|
} finally {
|
||||||
|
saveBtn.disabled = false;
|
||||||
|
renderAccounts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- History ---
|
// --- History ---
|
||||||
async function loadHistory() {
|
async function loadHistory() {
|
||||||
const history = await window.api.getHistory();
|
const history = await window.api.getHistory();
|
||||||
@ -1250,6 +1470,41 @@ function setupListeners() {
|
|||||||
document.getElementById('hosterModal').addEventListener('click', (e) => {
|
document.getElementById('hosterModal').addEventListener('click', (e) => {
|
||||||
if (e.target.id === 'hosterModal') closeHosterModal();
|
if (e.target.id === 'hosterModal') closeHosterModal();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Account management
|
||||||
|
document.getElementById('addAccountBtn').addEventListener('click', () => openAccountModal(null));
|
||||||
|
document.getElementById('closeAccountModalBtn').addEventListener('click', closeAccountModal);
|
||||||
|
document.getElementById('cancelAccountModalBtn').addEventListener('click', closeAccountModal);
|
||||||
|
document.getElementById('saveAccountBtn').addEventListener('click', saveAccount);
|
||||||
|
document.getElementById('accountModal').addEventListener('click', (e) => {
|
||||||
|
if (e.target.id === 'accountModal') closeAccountModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Account hoster select change → update credential fields
|
||||||
|
document.getElementById('accountHosterSelect').addEventListener('change', (e) => {
|
||||||
|
const credsContainer = document.getElementById('accountCredsFields');
|
||||||
|
credsContainer.innerHTML = getCredsFieldsHtml(e.target.value, {});
|
||||||
|
credsContainer.querySelectorAll('.toggle-vis').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
const input = btn.previousElementSibling;
|
||||||
|
input.type = input.type === 'password' ? 'text' : 'password';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
document.getElementById('accountModalStatus').textContent = '';
|
||||||
|
document.getElementById('accountModalStatus').className = 'account-modal-status';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete account modal
|
||||||
|
document.getElementById('closeDeleteModalBtn').addEventListener('click', closeDeleteModal);
|
||||||
|
document.getElementById('cancelDeleteBtn').addEventListener('click', closeDeleteModal);
|
||||||
|
document.getElementById('confirmDeleteBtn').addEventListener('click', () => {
|
||||||
|
const modal = document.getElementById('deleteAccountModal');
|
||||||
|
const hoster = modal.dataset.hoster;
|
||||||
|
if (hoster) deleteAccount(hoster);
|
||||||
|
});
|
||||||
|
document.getElementById('deleteAccountModal').addEventListener('click', (e) => {
|
||||||
|
if (e.target.id === 'deleteAccountModal') closeDeleteModal();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Update UI ---
|
// --- Update UI ---
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<nav class="tab-bar">
|
<nav class="tab-bar">
|
||||||
<button class="tab active" data-view="upload">Upload</button>
|
<button class="tab active" data-view="upload">Upload</button>
|
||||||
|
<button class="tab" data-view="accounts">Accounts</button>
|
||||||
<button class="tab" data-view="settings">Einstellungen</button>
|
<button class="tab" data-view="settings">Einstellungen</button>
|
||||||
<button class="tab" data-view="history">Verlauf</button>
|
<button class="tab" data-view="history">Verlauf</button>
|
||||||
<span class="version-label" id="versionLabel"></span>
|
<span class="version-label" id="versionLabel"></span>
|
||||||
@ -101,11 +102,67 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Accounts View -->
|
||||||
|
<div id="accounts-view" class="view">
|
||||||
|
<div class="accounts-container">
|
||||||
|
<div class="accounts-header">
|
||||||
|
<div>
|
||||||
|
<h2>Accounts</h2>
|
||||||
|
<p class="settings-hint">Hoster-Zugangsdaten verwalten</p>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary" id="addAccountBtn">+ Account hinzufuegen</button>
|
||||||
|
</div>
|
||||||
|
<div class="accounts-list" id="accountsList"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Account Modal -->
|
||||||
|
<div class="modal-overlay" id="accountModal" style="display:none">
|
||||||
|
<div class="modal-card">
|
||||||
|
<div class="modal-header">
|
||||||
|
<div>
|
||||||
|
<h3 id="accountModalTitle">Account hinzufuegen</h3>
|
||||||
|
<p id="accountModalSubtitle">Waehle einen Hoster und gib deine Zugangsdaten ein.</p>
|
||||||
|
</div>
|
||||||
|
<button class="icon-btn" id="closeAccountModalBtn" aria-label="Schliessen">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="settings-row" id="accountHosterRow">
|
||||||
|
<label>Hoster</label>
|
||||||
|
<select class="key-input" id="accountHosterSelect" style="max-width:300px"></select>
|
||||||
|
</div>
|
||||||
|
<div id="accountCredsFields"></div>
|
||||||
|
<div class="account-modal-status" id="accountModalStatus"></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn btn-secondary" id="cancelAccountModalBtn">Abbrechen</button>
|
||||||
|
<button class="btn btn-primary" id="saveAccountBtn">Anlegen & Pruefen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Delete Confirm Modal -->
|
||||||
|
<div class="modal-overlay" id="deleteAccountModal" style="display:none">
|
||||||
|
<div class="modal-card" style="width:min(400px,100%)">
|
||||||
|
<div class="modal-header">
|
||||||
|
<div><h3>Account loeschen?</h3></div>
|
||||||
|
<button class="icon-btn" id="closeDeleteModalBtn" aria-label="Schliessen">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p id="deleteAccountMessage">Account wirklich loeschen?</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn btn-secondary" id="cancelDeleteBtn">Abbrechen</button>
|
||||||
|
<button class="btn btn-danger" id="confirmDeleteBtn">Loeschen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Settings View -->
|
<!-- Settings View -->
|
||||||
<div id="settings-view" class="view">
|
<div id="settings-view" class="view">
|
||||||
<div class="settings-container">
|
<div class="settings-container">
|
||||||
<h2>Hoster Konfiguration</h2>
|
<h2>Upload Einstellungen</h2>
|
||||||
<p class="settings-hint">API-Keys und Upload-Einstellungen pro Hoster.</p>
|
<p class="settings-hint">Upload-Einstellungen pro Hoster. Zugangsdaten werden im Accounts-Tab verwaltet.</p>
|
||||||
<div class="settings-hosters" id="settingsHosters"></div>
|
<div class="settings-hosters" id="settingsHosters"></div>
|
||||||
<button class="btn btn-primary" id="saveSettingsBtn">Alles Speichern</button>
|
<button class="btn btn-primary" id="saveSettingsBtn">Alles Speichern</button>
|
||||||
<span class="save-feedback" id="saveFeedback"></span>
|
<span class="save-feedback" id="saveFeedback"></span>
|
||||||
|
|||||||
@ -577,6 +577,71 @@ body {
|
|||||||
gap: 4px 16px;
|
gap: 4px 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Accounts View */
|
||||||
|
.accounts-container { padding: 16px; overflow: auto; flex: 1; background: linear-gradient(180deg, rgba(255,255,255,0.015), transparent 24%); }
|
||||||
|
.accounts-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 16px; }
|
||||||
|
.accounts-header h2 { font-size: 18px; margin-bottom: 2px; }
|
||||||
|
.accounts-list { display: grid; gap: 8px; }
|
||||||
|
|
||||||
|
.account-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
background: var(--bg-card);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 10px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
.account-card:hover { background: var(--bg-card-hover); }
|
||||||
|
.account-card-info { flex: 1; min-width: 0; }
|
||||||
|
.account-card-title { font-size: 14px; font-weight: 600; }
|
||||||
|
.account-card-subtitle { font-size: 11px; color: var(--text-muted); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
.account-card-actions { display: flex; gap: 6px; flex-shrink: 0; }
|
||||||
|
|
||||||
|
.account-status {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 600;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.account-status.status-ok { background: rgba(0, 184, 148, 0.2); color: var(--success); }
|
||||||
|
.account-status.status-checking { background: rgba(253, 203, 110, 0.2); color: var(--warning); }
|
||||||
|
.account-status.status-error { background: rgba(231, 76, 60, 0.2); color: var(--danger); }
|
||||||
|
.account-status.status-unchecked { background: rgba(255, 255, 255, 0.05); color: var(--text-dim); }
|
||||||
|
|
||||||
|
.account-status-dot {
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.status-ok .account-status-dot { background: var(--success); }
|
||||||
|
.status-checking .account-status-dot { background: var(--warning); }
|
||||||
|
.status-error .account-status-dot { background: var(--danger); }
|
||||||
|
.status-unchecked .account-status-dot { background: var(--text-dim); }
|
||||||
|
|
||||||
|
.account-modal-status {
|
||||||
|
margin-top: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
min-height: 20px;
|
||||||
|
}
|
||||||
|
.account-modal-status.checking { color: var(--warning); }
|
||||||
|
.account-modal-status.ok { color: var(--success); }
|
||||||
|
.account-modal-status.error { color: var(--danger); }
|
||||||
|
|
||||||
|
.accounts-empty {
|
||||||
|
text-align: center;
|
||||||
|
padding: 48px 16px;
|
||||||
|
color: var(--text-dim);
|
||||||
|
}
|
||||||
|
.accounts-empty p { font-size: 14px; margin-bottom: 4px; }
|
||||||
|
.accounts-empty .hint { font-size: 12px; }
|
||||||
|
|
||||||
/* History View */
|
/* History View */
|
||||||
.history-container { padding: 16px; overflow: auto; flex: 1; background: linear-gradient(180deg, rgba(255,255,255,0.015), transparent 24%); }
|
.history-container { padding: 16px; overflow: auto; flex: 1; background: linear-gradient(180deg, rgba(255,255,255,0.015), transparent 24%); }
|
||||||
.history-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
|
.history-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user