feat: add account management tab with login validation
New "Accounts" tab for managing hoster credentials separately from upload settings. Accounts can be added, edited, and deleted via modal dialogs. Login credentials are automatically verified on save, showing status (Bereit/Fehler) in the account list. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d9b00f8fd7
commit
d9dec33ecc
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "multi-hoster-uploader",
|
||||
"version": "1.2.1",
|
||||
"version": "1.3.0",
|
||||
"description": "Upload files to doodstream, voe, vidmoly, byse simultaneously",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
||||
401
renderer/app.js
401
renderer/app.js
@ -7,6 +7,8 @@ let config = { hosters: {}, hosterSettings: {}, globalSettings: {} };
|
||||
let hosterSettings = {};
|
||||
let uploading = 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 queuePersistTimer = null;
|
||||
const AUTO_CHECK_PREF_KEY = 'autoHealthCheckBeforeUpload';
|
||||
@ -33,6 +35,7 @@ async function init() {
|
||||
renderHosterSummary();
|
||||
renderHosterModal();
|
||||
renderSettings();
|
||||
renderAccounts();
|
||||
setupListeners();
|
||||
setupDragDrop();
|
||||
loadHistory();
|
||||
@ -150,9 +153,10 @@ function renderHosterModal() {
|
||||
list.innerHTML = available.map(item => {
|
||||
const checked = selectedUploadHosters.includes(item.name);
|
||||
const h = config.hosters[item.name] || {};
|
||||
const subtitle = item.name === 'vidmoly.me' ? 'Login hinterlegt'
|
||||
: (item.name === 'voe.sx' && h.username && h.password) ? 'Login hinterlegt'
|
||||
: 'API-Key hinterlegt';
|
||||
const st = accountStatuses[item.name];
|
||||
const subtitle = st && st.status === 'ok' ? 'Bereit'
|
||||
: st && st.status === 'error' ? 'Login-Fehler'
|
||||
: getCredentialLabel(item.name, h) + ' hinterlegt';
|
||||
return `
|
||||
<label class="hoster-option${checked ? ' selected' : ''}" data-hoster-option="${item.name}">
|
||||
<input type="checkbox" data-hoster-modal="${item.name}" ${checked ? 'checked' : ''}>
|
||||
@ -897,44 +901,6 @@ function renderSettings() {
|
||||
const panel = document.createElement('div');
|
||||
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 = `
|
||||
<div class="hoster-panel-header" data-hoster="${name}">
|
||||
<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>
|
||||
</div>
|
||||
<div class="hoster-panel-body" data-panel="${name}" style="display:none">
|
||||
${credsHtml}
|
||||
<div class="settings-divider"></div>
|
||||
<h4>Upload Einstellungen</h4>
|
||||
<div class="settings-grid-mini">
|
||||
<div class="settings-row">
|
||||
@ -987,14 +951,6 @@ function renderSettings() {
|
||||
body.style.display = isOpen ? 'none' : 'block';
|
||||
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);
|
||||
@ -1008,7 +964,6 @@ async function chooseLogFilePath() {
|
||||
}
|
||||
|
||||
async function saveSettings() {
|
||||
const hosters = {};
|
||||
const newHosterSettings = {};
|
||||
const globalSettings = {
|
||||
...(config.globalSettings || {}),
|
||||
@ -1017,22 +972,6 @@ async function saveSettings() {
|
||||
};
|
||||
|
||||
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 = {};
|
||||
document.querySelectorAll(`.hs-input[data-hoster="${name}"]`).forEach(input => {
|
||||
const field = input.dataset.hs;
|
||||
@ -1041,22 +980,303 @@ async function saveSettings() {
|
||||
newHosterSettings[name] = hs;
|
||||
}
|
||||
|
||||
await window.api.saveConfig({ hosters });
|
||||
await window.api.saveHosterSettings(newHosterSettings);
|
||||
await window.api.saveGlobalSettings(globalSettings);
|
||||
config = await window.api.getConfig();
|
||||
hosterSettings = config.hosterSettings || {};
|
||||
syncSelectedUploadHosters();
|
||||
renderHosterSummary();
|
||||
renderHosterModal();
|
||||
renderSettings();
|
||||
renderHealthCheckResults([]);
|
||||
|
||||
const feedback = document.getElementById('saveFeedback');
|
||||
feedback.textContent = 'Gespeichert!';
|
||||
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 ---
|
||||
async function loadHistory() {
|
||||
const history = await window.api.getHistory();
|
||||
@ -1250,6 +1470,41 @@ function setupListeners() {
|
||||
document.getElementById('hosterModal').addEventListener('click', (e) => {
|
||||
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 ---
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
<body>
|
||||
<nav class="tab-bar">
|
||||
<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="history">Verlauf</button>
|
||||
<span class="version-label" id="versionLabel"></span>
|
||||
@ -101,11 +102,67 @@
|
||||
</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 -->
|
||||
<div id="settings-view" class="view">
|
||||
<div class="settings-container">
|
||||
<h2>Hoster Konfiguration</h2>
|
||||
<p class="settings-hint">API-Keys und Upload-Einstellungen pro Hoster.</p>
|
||||
<h2>Upload Einstellungen</h2>
|
||||
<p class="settings-hint">Upload-Einstellungen pro Hoster. Zugangsdaten werden im Accounts-Tab verwaltet.</p>
|
||||
<div class="settings-hosters" id="settingsHosters"></div>
|
||||
<button class="btn btn-primary" id="saveSettingsBtn">Alles Speichern</button>
|
||||
<span class="save-feedback" id="saveFeedback"></span>
|
||||
|
||||
@ -577,6 +577,71 @@ body {
|
||||
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-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; }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user