Compare commits

..

No commits in common. "294f6a4de701f0cc6fae3f3fe53914e1ae2284ab" and "d9b00f8fd7f719e24f08691434b194e59a627322" have entirely different histories.

4 changed files with 76 additions and 453 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "multi-hoster-uploader", "name": "multi-hoster-uploader",
"version": "1.3.0", "version": "1.2.1",
"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": {

View File

@ -7,8 +7,6 @@ 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';
@ -35,7 +33,6 @@ async function init() {
renderHosterSummary(); renderHosterSummary();
renderHosterModal(); renderHosterModal();
renderSettings(); renderSettings();
renderAccounts();
setupListeners(); setupListeners();
setupDragDrop(); setupDragDrop();
loadHistory(); loadHistory();
@ -153,10 +150,9 @@ 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 st = accountStatuses[item.name]; const subtitle = item.name === 'vidmoly.me' ? 'Login hinterlegt'
const subtitle = st && st.status === 'ok' ? 'Bereit' : (item.name === 'voe.sx' && h.username && h.password) ? 'Login hinterlegt'
: st && st.status === 'error' ? 'Login-Fehler' : 'API-Key hinterlegt';
: 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' : ''}>
@ -901,6 +897,44 @@ 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">&#128065;</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">&#128065;</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">&#128065;</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">&#128065;</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">&#9654;</span> <span class="panel-arrow">&#9654;</span>
@ -908,6 +942,8 @@ 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">
@ -951,6 +987,14 @@ function renderSettings() {
body.style.display = isOpen ? 'none' : 'block'; body.style.display = isOpen ? 'none' : 'block';
arrow.innerHTML = isOpen ? '&#9654;' : '&#9660;'; arrow.innerHTML = isOpen ? '&#9654;' : '&#9660;';
}); });
// 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);
@ -964,6 +1008,7 @@ async function chooseLogFilePath() {
} }
async function saveSettings() { async function saveSettings() {
const hosters = {};
const newHosterSettings = {}; const newHosterSettings = {};
const globalSettings = { const globalSettings = {
...(config.globalSettings || {}), ...(config.globalSettings || {}),
@ -972,6 +1017,22 @@ 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;
@ -980,303 +1041,22 @@ 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">&#128065;</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">&#128065;</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">&#128065;</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">&#128065;</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();
@ -1470,41 +1250,6 @@ 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 ---

View File

@ -9,7 +9,6 @@
<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>
@ -102,67 +101,11 @@
</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">&times;</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">&times;</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>Upload Einstellungen</h2> <h2>Hoster Konfiguration</h2>
<p class="settings-hint">Upload-Einstellungen pro Hoster. Zugangsdaten werden im Accounts-Tab verwaltet.</p> <p class="settings-hint">API-Keys und Upload-Einstellungen pro Hoster.</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>

View File

@ -577,71 +577,6 @@ 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; }