- 40+ Utility-Klassen in form.css (clickable, text-danger, flex-row, loading-state, etc.) - share-menu position/z-index in chat.css statt inline - index.html: 0 Inline-Styles (hidden-Attribut, CSS-Klassen) - 12 JS-Dateien bereinigt: style.cssText/style.* durch classList ersetzt - Bugfix: var(--danger) → var(--dangerous) (nicht existierende Variable) - display:none Toggles → hidden-Attribut wo passend - Hardcoded font-sizes in chat.css → var(--font-size) 15 Dateien, nur Frontend (CSS/JS/HTML), kein Backend
766 lines
28 KiB
JavaScript
766 lines
28 KiB
JavaScript
// ── API Keys Page (Admin) ──
|
||
|
||
function formatKeyStatus(status) {
|
||
var map = {
|
||
'valid': '✅ Gültig',
|
||
'invalid': '❌ Ungültig',
|
||
'rate_limited': '⚠️ Rate Limited',
|
||
'quota_exceeded': '⚠️ Quota Exceeded',
|
||
'error': '⚠️ Fehler',
|
||
'unchecked': '⏳ Ungeprüft'
|
||
};
|
||
return map[status] || status;
|
||
}
|
||
|
||
function createUsageBadge(usage5h, usage7d) {
|
||
if (usage5h == null && usage7d == null) return null;
|
||
var badge = document.createElement('div');
|
||
badge.className = 'usage-badge';
|
||
|
||
function addRow(label, pct) {
|
||
if (pct == null) return;
|
||
var row = document.createElement('div');
|
||
row.className = 'usage-row';
|
||
|
||
var lbl = document.createElement('span');
|
||
lbl.className = 'usage-label';
|
||
lbl.textContent = label;
|
||
|
||
var track = document.createElement('div');
|
||
track.className = 'usage-track';
|
||
|
||
var fill = document.createElement('div');
|
||
fill.className = 'usage-fill';
|
||
if (pct >= 90) fill.className += ' danger';
|
||
else if (pct >= 70) fill.className += ' warn';
|
||
fill.style.width = Math.min(pct, 100) + '%';
|
||
track.appendChild(fill);
|
||
|
||
var val = document.createElement('span');
|
||
val.className = 'usage-pct';
|
||
val.textContent = pct + '%';
|
||
|
||
row.appendChild(lbl);
|
||
row.appendChild(track);
|
||
row.appendChild(val);
|
||
badge.appendChild(row);
|
||
}
|
||
|
||
addRow('Sitzung', usage5h);
|
||
addRow('Woche', usage7d);
|
||
return badge;
|
||
}
|
||
|
||
registerPage('api-keys', {
|
||
async init() {
|
||
if (!currentUser || !currentUser.isAdmin) {
|
||
navigateTo('dashboard');
|
||
return;
|
||
}
|
||
|
||
var keys = [];
|
||
var editingKeyId = null;
|
||
|
||
var list = document.getElementById('apiKeysList');
|
||
var newBtn = document.getElementById('apiKeysNewBtn');
|
||
var checkAllBtn = document.getElementById('apiKeysCheckAllBtn');
|
||
var form = document.getElementById('apiKeysForm');
|
||
var formTitle = document.getElementById('apiKeysFormTitle');
|
||
var providerSelect = document.getElementById('apiKeysProvider');
|
||
var nameInput = document.getElementById('apiKeysName');
|
||
var keyInput = document.getElementById('apiKeysKey');
|
||
var saveBtn = document.getElementById('apiKeysFormSaveBtn');
|
||
|
||
function renderKeys() {
|
||
list.innerHTML = '';
|
||
|
||
if (keys.length === 0) {
|
||
var empty = document.createElement('div');
|
||
empty.className = 'admin-user-meta pad-v';
|
||
empty.textContent = 'Keine API-Keys konfiguriert.';
|
||
list.appendChild(empty);
|
||
return;
|
||
}
|
||
|
||
keys.forEach(function(key) {
|
||
var wrapper = document.createElement('div');
|
||
|
||
var row = document.createElement('div');
|
||
row.className = 'admin-user-row';
|
||
|
||
var info = document.createElement('div');
|
||
info.className = 'admin-user-info';
|
||
|
||
var nameSpan = document.createElement('span');
|
||
nameSpan.className = 'admin-user-name';
|
||
nameSpan.textContent = key.name;
|
||
|
||
var metaSpan = document.createElement('span');
|
||
metaSpan.className = 'admin-user-meta';
|
||
var parts = [key.provider];
|
||
parts.push(formatKeyStatus(key.status));
|
||
if (key.plan) parts.push(key.plan);
|
||
if (key.checkedAt) parts.push(new Date(key.checkedAt).toLocaleString());
|
||
metaSpan.textContent = parts.join(' · ');
|
||
|
||
info.appendChild(nameSpan);
|
||
if (key.sourceUrl) {
|
||
var urlSpan = document.createElement('a');
|
||
urlSpan.className = 'admin-user-meta';
|
||
urlSpan.href = key.sourceUrl;
|
||
urlSpan.target = '_blank';
|
||
urlSpan.rel = 'noopener';
|
||
urlSpan.textContent = key.sourceUrl.replace('https://github.com/', '');
|
||
info.appendChild(urlSpan);
|
||
}
|
||
info.appendChild(metaSpan);
|
||
|
||
// Toggle + Usage-Badge nebeneinander
|
||
var badgeRow = document.createElement('div');
|
||
badgeRow.className = 'flex-row-tight';
|
||
|
||
var publicBtn = document.createElement('button');
|
||
publicBtn.className = 'icon-btn';
|
||
publicBtn.title = key.isPublic ? 'Öffentlich (klicken zum Privatschalten)' : 'Privat (klicken zum Veröffentlichen)';
|
||
publicBtn.innerHTML = key.isPublic
|
||
? '<i class="fa fa-globe text-accent"></i>'
|
||
: '<i class="fa fa-lock"></i>';
|
||
publicBtn.addEventListener('click', function() { togglePublic(key); });
|
||
badgeRow.appendChild(publicBtn);
|
||
|
||
var usageBadge = createUsageBadge(key.usage5h, key.usage7d);
|
||
if (usageBadge) badgeRow.appendChild(usageBadge);
|
||
|
||
// Status-Zeile (für Check-Ergebnis)
|
||
var statusLine = document.createElement('div');
|
||
statusLine.className = 'admin-user-meta';
|
||
statusLine.className = 'admin-user-meta check-status-line';
|
||
|
||
// Check – rechts neben Usage-Badge
|
||
if (key.provider === 'anthropic' || key.provider === 'openai' || key.provider === 'google') {
|
||
var checkBtn = document.createElement('button');
|
||
checkBtn.className = 'icon-btn';
|
||
checkBtn.title = 'Key prüfen';
|
||
checkBtn.innerHTML = '<i class="fa fa-refresh"></i>';
|
||
checkBtn.addEventListener('click', function() { checkKey(key, checkBtn, statusLine); });
|
||
badgeRow.appendChild(checkBtn);
|
||
}
|
||
|
||
info.appendChild(badgeRow);
|
||
|
||
var actions = document.createElement('div');
|
||
actions.className = 'admin-user-actions';
|
||
|
||
// Bearbeiten
|
||
var editBtn = document.createElement('button');
|
||
editBtn.className = 'icon-btn';
|
||
editBtn.title = 'Bearbeiten';
|
||
editBtn.innerHTML = '<i class="fa fa-pencil"></i>';
|
||
editBtn.addEventListener('click', function() { startEdit(key); });
|
||
actions.appendChild(editBtn);
|
||
|
||
// Löschen
|
||
var deleteBtn = document.createElement('button');
|
||
deleteBtn.className = 'icon-btn';
|
||
deleteBtn.title = 'Löschen';
|
||
deleteBtn.innerHTML = '<i class="fa fa-trash"></i>';
|
||
deleteBtn.addEventListener('click', function() { deleteKey(key); });
|
||
actions.appendChild(deleteBtn);
|
||
|
||
row.appendChild(info);
|
||
row.appendChild(actions);
|
||
wrapper.appendChild(row);
|
||
wrapper.appendChild(statusLine);
|
||
list.appendChild(wrapper);
|
||
});
|
||
}
|
||
|
||
async function loadKeys() {
|
||
try {
|
||
keys = await apiGetApiKeys();
|
||
renderKeys();
|
||
} catch (err) {
|
||
showToast(err.message, 'error');
|
||
}
|
||
}
|
||
|
||
async function checkKey(key, btn, statusLine) {
|
||
var origHtml = btn.innerHTML;
|
||
btn.innerHTML = '<i class="fa fa-spinner fa-spin"></i>';
|
||
btn.disabled = true;
|
||
statusLine.style.display = 'none';
|
||
statusLine.innerHTML = '';
|
||
try {
|
||
var result = await apiCheckApiKey(key.keyId);
|
||
statusLine.style.opacity = '1';
|
||
statusLine.innerHTML = '';
|
||
if (result.status === 'valid') {
|
||
var text = document.createElement('span');
|
||
var parts = [formatKeyStatus('valid'), result.plan];
|
||
if (result.overage != null && result.overage > 0) parts.push('Overage: ' + result.overage + '%');
|
||
text.textContent = parts.join(' · ');
|
||
statusLine.appendChild(text);
|
||
var badge = createUsageBadge(result.usage5h, result.usage7d);
|
||
if (badge) { badge.classList.add('mt-xs'); statusLine.appendChild(badge); }
|
||
} else {
|
||
statusLine.textContent = '❌ ' + (result.message || 'Unbekannter Fehler');
|
||
}
|
||
await loadKeys();
|
||
} catch (err) {
|
||
statusLine.style.opacity = '1';
|
||
statusLine.textContent = '❌ ' + err.message;
|
||
} finally {
|
||
btn.innerHTML = origHtml;
|
||
btn.disabled = false;
|
||
}
|
||
}
|
||
|
||
function startEdit(key) {
|
||
editingKeyId = key.keyId;
|
||
formTitle.textContent = 'API-Key bearbeiten';
|
||
providerSelect.value = key.provider;
|
||
providerSelect.disabled = true;
|
||
nameInput.value = key.name;
|
||
keyInput.value = key.apiKey;
|
||
saveBtn.textContent = 'Speichern';
|
||
form.hidden = false;
|
||
nameInput.focus();
|
||
}
|
||
|
||
async function togglePublic(key) {
|
||
try {
|
||
await apiToggleApiKeyPublic(key.keyId);
|
||
showToast(key.name + (key.isPublic ? ' privat geschaltet' : ' veröffentlicht'));
|
||
await loadKeys();
|
||
} catch (err) {
|
||
showToast(err.message, 'error');
|
||
}
|
||
}
|
||
|
||
async function deleteKey(key) {
|
||
if (!confirm('API-Key "' + key.name + '" wirklich löschen?')) return;
|
||
try {
|
||
await apiDeleteApiKey(key.keyId);
|
||
showToast('API-Key gelöscht');
|
||
await loadKeys();
|
||
} catch (err) {
|
||
showToast(err.message, 'error');
|
||
}
|
||
}
|
||
|
||
// Alle Keys prüfen
|
||
checkAllBtn.addEventListener('click', function() {
|
||
withButtonLoading(checkAllBtn, 'Alle prüfen', async function() {
|
||
try {
|
||
var result = await apiCheckAllApiKeys();
|
||
showToast(result.checked_ + ' Keys geprüft');
|
||
await loadKeys();
|
||
} catch (err) {
|
||
showToast(err.message, 'error');
|
||
}
|
||
});
|
||
});
|
||
|
||
// Neuer Key
|
||
newBtn.addEventListener('click', function() {
|
||
editingKeyId = null;
|
||
formTitle.textContent = 'Neuer API-Key';
|
||
providerSelect.value = 'anthropic';
|
||
providerSelect.disabled = false;
|
||
nameInput.value = '';
|
||
keyInput.value = '';
|
||
saveBtn.textContent = 'Erstellen';
|
||
form.hidden = false;
|
||
nameInput.focus();
|
||
});
|
||
|
||
// Speichern (Erstellen oder Bearbeiten)
|
||
saveBtn.addEventListener('click', () => withButtonLoading(saveBtn, saveBtn.textContent, async () => {
|
||
var name = nameInput.value.trim();
|
||
|
||
if (!name) {
|
||
showToast('Name erforderlich', 'error');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
if (editingKeyId) {
|
||
// Bearbeiten
|
||
var data = { name: name };
|
||
var newKey = keyInput.value.trim();
|
||
if (newKey) data.apiKey = newKey;
|
||
await apiUpdateApiKey(editingKeyId, data);
|
||
showToast('API-Key aktualisiert');
|
||
} else {
|
||
// Erstellen
|
||
var provider = providerSelect.value;
|
||
var apiKey = keyInput.value.trim();
|
||
if (!apiKey) {
|
||
showToast('API-Key erforderlich', 'error');
|
||
return;
|
||
}
|
||
await apiCreateApiKey({ provider: provider, name: name, apiKey: apiKey });
|
||
showToast('API-Key erstellt');
|
||
}
|
||
form.hidden = true;
|
||
editingKeyId = null;
|
||
await loadKeys();
|
||
} catch (err) {
|
||
showToast(err.message, 'error');
|
||
}
|
||
}));
|
||
|
||
await loadKeys();
|
||
|
||
// ── Scanner UI ──
|
||
|
||
var scanQuickBtn = document.getElementById('scanQuickBtn');
|
||
var scanWebBtn = document.getElementById('scanWebBtn');
|
||
var scanFullBtn = document.getElementById('scanFullBtn');
|
||
var recheckAllScannedBtn = document.getElementById('recheckAllScannedBtn');
|
||
var scanBtns = [scanQuickBtn, scanWebBtn, scanFullBtn];
|
||
var scanStatusEl = document.getElementById('scanStatus');
|
||
var scannedList = document.getElementById('scannedKeysList');
|
||
var scannedKeys = [];
|
||
var selectedType = null;
|
||
var checkUncheckedAbort = false;
|
||
var pollTimer = null;
|
||
|
||
var typeNames = {
|
||
anthropic_oauth: 'Anthropic OAuth', anthropic_api: 'Anthropic API', anthropic_unknown: 'Anthropic',
|
||
openai_project: 'OpenAI Project', openai_service: 'OpenAI Service', openai_legacy: 'OpenAI Legacy',
|
||
google_api: 'Google / Gemini', aws_access_key: 'AWS Access Key',
|
||
github_pat: 'GitHub PAT', github_oauth: 'GitHub OAuth', gitlab_pat: 'GitLab PAT',
|
||
huggingface: 'HuggingFace', stripe_live: 'Stripe Live', stripe_test: 'Stripe Test',
|
||
sendgrid: 'SendGrid', slack: 'Slack', krea_ai: 'Krea AI'
|
||
};
|
||
|
||
var typeIcons = {
|
||
anthropic_oauth: '🟠', anthropic_api: '🟠', anthropic_unknown: '🟠',
|
||
openai_project: '🟢', openai_service: '🟢', openai_legacy: '🟢',
|
||
google_api: '🔵', aws_access_key: '🟡',
|
||
github_pat: '⚫', github_oauth: '⚫', gitlab_pat: '🟤',
|
||
huggingface: '🤗', stripe_live: '💳', stripe_test: '💳',
|
||
sendgrid: '📧', slack: '💬', krea_ai: '🎨'
|
||
};
|
||
|
||
function renderSummary(summary) {
|
||
scannedList.innerHTML = '';
|
||
if (!summary || summary.length === 0) {
|
||
var empty = document.createElement('div');
|
||
empty.className = 'admin-user-meta';
|
||
empty.className = 'admin-user-meta pad-v';
|
||
empty.textContent = 'Keine gescannten Keys vorhanden.';
|
||
scannedList.appendChild(empty);
|
||
return;
|
||
}
|
||
|
||
summary.forEach(function(s) {
|
||
var row = document.createElement('div');
|
||
row.className = 'admin-user-row clickable';
|
||
|
||
var info = document.createElement('div');
|
||
info.className = 'admin-user-info';
|
||
|
||
var nameSpan = document.createElement('span');
|
||
nameSpan.className = 'admin-user-name';
|
||
var icon = typeIcons[s.type] || '🔑';
|
||
nameSpan.textContent = icon + ' ' + (typeNames[s.type] || s.type);
|
||
info.appendChild(nameSpan);
|
||
|
||
var metaSpan = document.createElement('span');
|
||
metaSpan.className = 'admin-user-meta';
|
||
var parts = [s.total + ' Keys'];
|
||
if (s.valid > 0) parts.push('✅ ' + s.valid + ' gültig');
|
||
if (s.invalid > 0) parts.push('❌ ' + s.invalid + ' ungültig');
|
||
if (s.unchecked > 0) parts.push('⏳ ' + s.unchecked + ' ungeprüft');
|
||
if (s.adopted > 0) parts.push('📌 ' + s.adopted + ' übernommen');
|
||
metaSpan.textContent = parts.join(' · ');
|
||
info.appendChild(metaSpan);
|
||
|
||
var badge = document.createElement('div');
|
||
badge.className = 'admin-user-actions';
|
||
badge.innerHTML = '<i class="fa fa-chevron-right icon-dim"></i>';
|
||
|
||
row.appendChild(info);
|
||
row.appendChild(badge);
|
||
row.addEventListener('click', function() { loadScannedKeysByType(s.type); });
|
||
scannedList.appendChild(row);
|
||
});
|
||
}
|
||
|
||
function renderScannedKeys() {
|
||
scannedList.innerHTML = '';
|
||
|
||
// Zurück-Button
|
||
var backRow = document.createElement('div');
|
||
backRow.className = 'admin-user-row';
|
||
backRow.classList.add('clickable', 'semi-dim');
|
||
var backInfo = document.createElement('div');
|
||
backInfo.className = 'admin-user-info';
|
||
var backName = document.createElement('span');
|
||
backName.className = 'admin-user-name';
|
||
backName.innerHTML = '<i class="fa fa-arrow-left"></i> Zurück zur Übersicht';
|
||
backInfo.appendChild(backName);
|
||
backRow.appendChild(backInfo);
|
||
backRow.addEventListener('click', function() { selectedType = null; checkUncheckedAbort = true; loadSummary(); });
|
||
scannedList.appendChild(backRow);
|
||
|
||
// Toolbar: "Ungeprüfte prüfen"-Button
|
||
var visibleKeys = scannedKeys.filter(function(sk) { return !sk.adoptedApiKeyId; });
|
||
var uncheckedKeys = visibleKeys.filter(function(sk) { return sk.status === 'unchecked'; });
|
||
if (uncheckedKeys.length > 0) {
|
||
var toolbar = document.createElement('div');
|
||
toolbar.className = 'scanner-toolbar';
|
||
|
||
var checkUncheckedBtn = document.createElement('button');
|
||
checkUncheckedBtn.className = 'section-add-btn';
|
||
checkUncheckedBtn.innerHTML = '<i class="fa fa-search"></i> ' + uncheckedKeys.length + ' Ungeprüfte prüfen';
|
||
|
||
var progressSpan = document.createElement('span');
|
||
progressSpan.className = 'admin-user-meta';
|
||
progressSpan.classList.add('ml-sm');
|
||
|
||
toolbar.appendChild(checkUncheckedBtn);
|
||
toolbar.appendChild(progressSpan);
|
||
scannedList.appendChild(toolbar);
|
||
|
||
checkUncheckedBtn.addEventListener('click', async function() {
|
||
checkUncheckedAbort = false;
|
||
checkUncheckedBtn.disabled = true;
|
||
|
||
var stopBtn = document.createElement('button');
|
||
stopBtn.className = 'icon-btn';
|
||
stopBtn.title = 'Abbrechen';
|
||
stopBtn.innerHTML = '<i class="fa fa-stop text-danger"></i>';
|
||
stopBtn.addEventListener('click', function() { checkUncheckedAbort = true; });
|
||
toolbar.appendChild(stopBtn);
|
||
|
||
var checked = 0;
|
||
var found = 0;
|
||
for (var i = 0; i < uncheckedKeys.length; i++) {
|
||
if (checkUncheckedAbort) {
|
||
progressSpan.textContent = 'Abgebrochen bei ' + checked + '/' + uncheckedKeys.length;
|
||
break;
|
||
}
|
||
var sk = uncheckedKeys[i];
|
||
progressSpan.textContent = (i + 1) + '/' + uncheckedKeys.length + ' prüfe…';
|
||
checkUncheckedBtn.innerHTML = '<i class="fa fa-spinner fa-spin"></i> ' + (i + 1) + '/' + uncheckedKeys.length;
|
||
try {
|
||
var result = await apiRecheckScannedKey(sk.scannedKeyId);
|
||
checked++;
|
||
if (result && result.status === 'valid') {
|
||
found++;
|
||
progressSpan.textContent = '✅ Gültigen Key gefunden! Stoppe.';
|
||
showToast('Gültigen Key gefunden!', 'success');
|
||
break;
|
||
}
|
||
} catch (err) {
|
||
checked++;
|
||
}
|
||
}
|
||
if (!checkUncheckedAbort && found === 0) {
|
||
progressSpan.textContent = checked + ' geprüft, keinen gültigen gefunden.';
|
||
}
|
||
checkUncheckedBtn.disabled = false;
|
||
checkUncheckedBtn.innerHTML = '<i class="fa fa-search"></i> Ungeprüfte prüfen';
|
||
if (stopBtn.parentNode) stopBtn.parentNode.removeChild(stopBtn);
|
||
await loadScannedKeysByType(selectedType);
|
||
});
|
||
}
|
||
if (visibleKeys.length === 0) {
|
||
var empty = document.createElement('div');
|
||
empty.className = 'admin-user-meta';
|
||
empty.className = 'admin-user-meta pad-v';
|
||
empty.textContent = 'Keine nicht-übernommenen Keys in dieser Kategorie.';
|
||
scannedList.appendChild(empty);
|
||
return;
|
||
}
|
||
|
||
visibleKeys.forEach(function(sk) {
|
||
|
||
var row = document.createElement('div');
|
||
row.className = 'admin-user-row';
|
||
|
||
var info = document.createElement('div');
|
||
info.className = 'admin-user-info';
|
||
info.classList.add('min-w-0');
|
||
|
||
if (sk.sourceUrl) {
|
||
var nameSpan = document.createElement('a');
|
||
nameSpan.className = 'admin-user-name';
|
||
nameSpan.href = sk.sourceUrl;
|
||
nameSpan.target = '_blank';
|
||
nameSpan.rel = 'noopener';
|
||
var displayUrl = sk.sourceUrl.replace('https://github.com/', '').replace('https://www.', '').replace('https://', '');
|
||
if (displayUrl.length > 80) displayUrl = displayUrl.substring(0, 77) + '…';
|
||
nameSpan.textContent = displayUrl;
|
||
info.appendChild(nameSpan);
|
||
}
|
||
|
||
var metaSpan = document.createElement('span');
|
||
metaSpan.className = 'admin-user-meta';
|
||
var metaParts = [];
|
||
var sourceIcons = { github: '🐙 GitHub', serper: '🔍 Google', gist: '📋 Gist', pastebin: '📄 Pastebin', github_events: '📡 Events' };
|
||
metaParts.push(sourceIcons[sk.source] || sk.source || 'github');
|
||
metaParts.push(formatKeyStatus(sk.status));
|
||
if (sk.plan && sk.plan !== 'Unknown') metaParts.push(sk.plan);
|
||
if (sk.checkedAt) metaParts.push(new Date(sk.checkedAt).toLocaleString());
|
||
metaSpan.textContent = metaParts.join(' · ');
|
||
|
||
info.appendChild(metaSpan);
|
||
var skUsageBadge = createUsageBadge(sk.usage5h, sk.usage7d);
|
||
if (skUsageBadge) info.appendChild(skUsageBadge);
|
||
|
||
var actions = document.createElement('div');
|
||
actions.className = 'admin-user-actions';
|
||
|
||
// Recheck
|
||
var recheckBtn = document.createElement('button');
|
||
recheckBtn.className = 'icon-btn';
|
||
recheckBtn.title = 'Erneut prüfen';
|
||
recheckBtn.innerHTML = '<i class="fa fa-refresh"></i>';
|
||
recheckBtn.addEventListener('click', async function(e) {
|
||
e.stopPropagation();
|
||
recheckBtn.innerHTML = '<i class="fa fa-spinner fa-spin"></i>';
|
||
recheckBtn.disabled = true;
|
||
try {
|
||
await apiRecheckScannedKey(sk.scannedKeyId);
|
||
await loadScannedKeysByType(selectedType);
|
||
} catch (err) { showToast(err.message, 'error'); }
|
||
finally { recheckBtn.innerHTML = '<i class="fa fa-refresh"></i>'; recheckBtn.disabled = false; }
|
||
});
|
||
actions.appendChild(recheckBtn);
|
||
|
||
// Übernehmen (bei valid oder quota_exceeded)
|
||
if (sk.status === 'valid' || sk.status === 'quota_exceeded') {
|
||
var adoptBtn = document.createElement('button');
|
||
adoptBtn.className = 'icon-btn';
|
||
adoptBtn.title = 'Als API-Key übernehmen';
|
||
adoptBtn.innerHTML = '<i class="fa fa-plus-circle text-accent"></i>';
|
||
adoptBtn.addEventListener('click', async function(e) {
|
||
e.stopPropagation();
|
||
try {
|
||
await apiAdoptScannedKey(sk.scannedKeyId);
|
||
showToast('Key übernommen');
|
||
await loadScannedKeysByType(selectedType);
|
||
await loadKeys();
|
||
} catch (err) { showToast(err.message, 'error'); }
|
||
});
|
||
actions.appendChild(adoptBtn);
|
||
}
|
||
|
||
// Löschen
|
||
var delBtn = document.createElement('button');
|
||
delBtn.className = 'icon-btn';
|
||
delBtn.title = 'Ignorieren';
|
||
delBtn.innerHTML = '<i class="fa fa-trash"></i>';
|
||
delBtn.addEventListener('click', async function(e) {
|
||
e.stopPropagation();
|
||
try {
|
||
await apiDeleteScannedKey(sk.scannedKeyId);
|
||
await loadScannedKeysByType(selectedType);
|
||
} catch (err) { showToast(err.message, 'error'); }
|
||
});
|
||
actions.appendChild(delBtn);
|
||
|
||
row.appendChild(info);
|
||
row.appendChild(actions);
|
||
scannedList.appendChild(row);
|
||
});
|
||
}
|
||
|
||
async function loadSummary() {
|
||
try {
|
||
var summary = await apiGetScannedKeysSummary();
|
||
renderSummary(summary);
|
||
} catch (err) { showToast(err.message, 'error'); }
|
||
}
|
||
|
||
async function loadScannedKeysByType(type) {
|
||
selectedType = type;
|
||
scannedList.innerHTML = '<div class="admin-user-meta pad-v"><i class="fa fa-spinner fa-spin"></i> Lade Keys…</div>';
|
||
try {
|
||
scannedKeys = await apiGetScannedKeys(type);
|
||
renderScannedKeys();
|
||
} catch (err) { showToast(err.message, 'error'); }
|
||
}
|
||
|
||
async function loadScannedKeys() {
|
||
if (selectedType) {
|
||
await loadScannedKeysByType(selectedType);
|
||
} else {
|
||
await loadSummary();
|
||
}
|
||
}
|
||
|
||
var scanBtnDefaults = [
|
||
{ btn: scanQuickBtn, icon: 'fa-bolt', label: 'Quick' },
|
||
{ btn: scanWebBtn, icon: 'fa-globe', label: 'Web' },
|
||
{ btn: scanFullBtn, icon: 'fa-search', label: 'Full' }
|
||
];
|
||
|
||
function setScanButtonsDisabled(disabled) {
|
||
scanBtnDefaults.forEach(function(d) { d.btn.disabled = disabled; });
|
||
}
|
||
|
||
function resetScanButtons() {
|
||
scanBtnDefaults.forEach(function(d) {
|
||
d.btn.innerHTML = '<i class="fa ' + d.icon + '"></i> ' + d.label;
|
||
d.btn.disabled = false;
|
||
});
|
||
}
|
||
|
||
async function updateEventScannerStatus() {
|
||
var el = document.getElementById('eventScannerStatus');
|
||
if (!el) return;
|
||
try {
|
||
var s = await apiEventScannerStatus();
|
||
if (!s.enabled) { el.hidden = true; return; }
|
||
el.hidden = false;
|
||
|
||
// Status-Zeile
|
||
var parts = ['🔴 Event Scanner'];
|
||
if (s.running) parts = ['🟢 Event Scanner'];
|
||
if (s.pollCount > 0) parts.push('Polls: ' + s.pollCount);
|
||
if (s.eventsPerHour > 0) parts.push('Events/h: ' + s.eventsPerHour);
|
||
if (s.keysFoundTotal > 0) parts.push('🔑 ' + s.keysFoundTotal + ' Key(s) gefunden');
|
||
if (s.lastPoll) {
|
||
var ago = Math.round((Date.now() - new Date(s.lastPoll).getTime()) / 1000);
|
||
parts.push('Letzter Poll: vor ' + (ago < 60 ? ago + 's' : Math.round(ago / 60) + 'min'));
|
||
}
|
||
if (s.lastError) parts.push('⚠️ ' + s.lastError);
|
||
|
||
var statusLine = parts.join(' · ');
|
||
|
||
// Log abrufen
|
||
var logData = await apiEventScannerLog();
|
||
var logLines = [];
|
||
if (logData.entries && logData.entries.length > 0) {
|
||
var last10 = logData.entries.slice(-10).reverse();
|
||
logLines = last10.map(function(e) {
|
||
var t = new Date(e.time).toLocaleTimeString();
|
||
var icon = e.type === 'key' ? '🔑' : e.type === 'error' ? '❌' : e.type === 'rate_limit' ? '⚠️' : '📡';
|
||
return icon + ' ' + t + ' ' + e.message;
|
||
});
|
||
}
|
||
|
||
// Immer klickbare Status-Zeile rendern
|
||
var wasOpen = false;
|
||
var oldLog = document.getElementById('eventScannerLog');
|
||
if (oldLog) wasOpen = !oldLog.hidden;
|
||
|
||
el.innerHTML = '';
|
||
var statusEl = document.createElement('div');
|
||
statusEl.className = 'clickable';
|
||
statusEl.title = logLines.length > 0 ? 'Klicken für Log' : '';
|
||
statusEl.textContent = statusLine;
|
||
el.appendChild(statusEl);
|
||
|
||
var logEl = document.createElement('div');
|
||
logEl.id = 'eventScannerLog';
|
||
logEl.className = 'event-scanner-log';
|
||
logEl.hidden = true;
|
||
logEl.textContent = logLines.length > 0 ? logLines.join('\n') : 'Noch keine Log-Einträge…';
|
||
if (wasOpen) logEl.hidden = false;
|
||
el.appendChild(logEl);
|
||
|
||
statusEl.addEventListener('click', function() {
|
||
logEl.hidden = !logEl.hidden;
|
||
});
|
||
} catch(e) { el.hidden = true; }
|
||
}
|
||
|
||
function formatScanProgress(s) {
|
||
var parts = [];
|
||
if (s.phase) parts.push(s.phase);
|
||
if (s.total > 0) parts.push(s.current + '/' + s.total);
|
||
if (s.detail && s.detail !== s.phase) {
|
||
var d = s.detail.length > 60 ? s.detail.substring(0, 57) + '…' : s.detail;
|
||
parts.push(d);
|
||
}
|
||
if (s.keysFound > 0) parts.push('🔑 ' + s.keysFound + ' gefunden');
|
||
return parts.join(' · ');
|
||
}
|
||
|
||
async function updateScanStatus() {
|
||
try {
|
||
var s = await apiScanStatus();
|
||
if (s.running) {
|
||
var progressText = formatScanProgress(s);
|
||
scanStatusEl.innerHTML = '<i class="fa fa-spinner fa-spin"></i> ' + (progressText || 'Scan läuft…');
|
||
setScanButtonsDisabled(true);
|
||
if (!pollTimer) {
|
||
pollTimer = setInterval(async function() {
|
||
var st = await apiScanStatus();
|
||
if (st.running) {
|
||
var pt = formatScanProgress(st);
|
||
scanStatusEl.innerHTML = '<i class="fa fa-spinner fa-spin"></i> ' + (pt || 'Scan läuft…');
|
||
} else {
|
||
clearInterval(pollTimer);
|
||
pollTimer = null;
|
||
resetScanButtons();
|
||
await loadScannedKeys();
|
||
scanStatusEl.innerHTML = st.lastResult ? '✅ ' + st.lastResult : '';
|
||
}
|
||
}, 1500);
|
||
}
|
||
} else {
|
||
resetScanButtons();
|
||
if (s.lastResult) {
|
||
var timeStr = s.lastRun ? new Date(s.lastRun).toLocaleString() + ' – ' : '';
|
||
scanStatusEl.innerHTML = timeStr + s.lastResult;
|
||
}
|
||
}
|
||
} catch (err) { /* ignore */ }
|
||
}
|
||
|
||
async function triggerScan(mode, btn) {
|
||
try {
|
||
btn.innerHTML = '<i class="fa fa-spinner fa-spin"></i> Läuft…';
|
||
setScanButtonsDisabled(true);
|
||
await apiTriggerScan(mode);
|
||
showToast(mode.charAt(0).toUpperCase() + mode.slice(1) + '-Scan gestartet');
|
||
await updateScanStatus();
|
||
} catch (err) {
|
||
showToast(err.message, 'error');
|
||
resetScanButtons();
|
||
}
|
||
}
|
||
|
||
scanQuickBtn.addEventListener('click', function() { triggerScan('quick', scanQuickBtn); });
|
||
scanWebBtn.addEventListener('click', function() { triggerScan('web', scanWebBtn); });
|
||
scanFullBtn.addEventListener('click', function() { triggerScan('full', scanFullBtn); });
|
||
|
||
recheckAllScannedBtn.addEventListener('click', async function() {
|
||
recheckAllScannedBtn.disabled = true;
|
||
recheckAllScannedBtn.innerHTML = '<i class="fa fa-spinner fa-spin"></i> Prüfe…';
|
||
try {
|
||
var result = await apiRecheckAllScannedKeys();
|
||
showToast('Alle geprüft: ' + result.success + '/' + result.total + ' OK' + (result.errors > 0 ? ', ' + result.errors + ' Fehler' : ''), result.errors > 0 ? 'warning' : 'success');
|
||
await loadScannedKeys();
|
||
} catch(e) {
|
||
showToast('Fehler beim Prüfen', 'error');
|
||
} finally {
|
||
recheckAllScannedBtn.disabled = false;
|
||
recheckAllScannedBtn.innerHTML = '<i class="fa fa-refresh"></i> Alle prüfen';
|
||
}
|
||
});
|
||
|
||
await loadScannedKeys();
|
||
await updateScanStatus();
|
||
await updateEventScannerStatus();
|
||
|
||
// Event Scanner Status auto-refresh
|
||
var eventScannerTimer = setInterval(updateEventScannerStatus, 30000);
|
||
this._eventScannerTimer = eventScannerTimer;
|
||
},
|
||
|
||
destroy() {
|
||
if (this._eventScannerTimer) {
|
||
clearInterval(this._eventScannerTimer);
|
||
this._eventScannerTimer = null;
|
||
}
|
||
}
|
||
});
|