feat: OpenAI Provider, Key-Checker Quota-Erkennung, persistierte Scanner-Logs

- OpenAI als vollwertiger Chat-Provider (Modell-Auswahl, Key-Routing, StreamChat)
- Hardcoded OpenAI-Modelle im Cache (gpt-5.x, gpt-4.1, o3, o4-mini etc.)
- /api/models/openai Endpoint, OpenAI-Optgroup im Agent-Dropdown
- Key-Routing: OpenAI-Key aus api_keys-Tabelle, Fallback via GetFirstActiveKeyByProvider
- OpenAI-Checker: 2-Schritt-Check (Models + chat/completions mit max_tokens=1)
- Google-Checker: 2-Schritt-Check (Models + generateContent mit maxOutputTokens=1)
- 429 insufficient_quota und billing_not_active korrekt als quota_exceeded erkannt
- Provider-Erkennung beim Adopt via KeyPatterns.DetectType() statt hardcoded anthropic
- HandleCheck/HandleCheckAll unterstützt jetzt Anthropic, OpenAI und Google
- Neue scanner_logs-Tabelle mit details-Spalte (JSON) für strukturierte Auswertung
- GitHubEventScannerService: AddLog persistiert in DB, detaillierte Key-Fund-Logs
- KeyScannerService: AddLog-Methode mit DB-Persistierung, detaillierte Logs
- Frontend: Ungeprüfte-prüfen-Button (sequenziell, stoppt bei valid)
- Frontend: Provider-Indicator im Key-Switch-Modal (Anthropic/OpenAI/Google)
- Frontend: Check-Button für alle Provider (nicht nur Anthropic)
This commit is contained in:
Meleki committed 2026-03-14 11:19:33 +01:00
1 parent c8859a89d0
commit 542a031172
15 files changed
+614 -90

No files matched your search

+5
View File
@@ -192,6 +192,9 @@
<option value="opus-4-5">opus-4-5</option>
<option value="opus-4-6" selected>opus-4-6</option>
<option value="haiku-4-5">haiku-4-5</option>
<optgroup label="OpenAI" id="openaiOptgroup">
<option value="" disabled>Wird geladen...</option>
</optgroup>
<optgroup label="Google Gemini" id="geminiOptgroup">
<option value="" disabled>Wird geladen...</option>
</optgroup>
@@ -572,6 +575,8 @@
<label>Provider</label>
<select id="apiKeysProvider">
<option value="anthropic">Anthropic</option>
<option value="openai">OpenAI</option>
<option value="google">Google / Gemini</option>
</select>
<label>Name</label>
<input type="text" id="apiKeysName" placeholder="z.B. Mein Pro Key" autocomplete="off">
+5 -1
View File
@@ -21,6 +21,7 @@ registerPage('agent', {
const nvidiaOptgroup = document.getElementById('nvidiaOptgroup');
const pollinationsOptgroup = document.getElementById('pollinationsOptgroup');
const geminiOptgroup = document.getElementById('geminiOptgroup');
const openaiOptgroup = document.getElementById('openaiOptgroup');
// Modelle dynamisch laden
const loadProviderModels = async (url, prefix, optgroup) => {
@@ -35,13 +36,16 @@ registerPage('agent', {
opt.textContent = m.displayName;
optgroup.appendChild(opt);
}
} else if (optgroup) {
optgroup.remove();
}
} catch (e) {
if (optgroup) optgroup.innerHTML = '<option value="" disabled>Fehler beim Laden</option>';
if (optgroup) optgroup.remove();
}
};
await Promise.all([
loadProviderModels('/api/models/openai', 'openai', openaiOptgroup),
loadProviderModels('/api/models/nvidia', 'nvidia', nvidiaOptgroup),
loadProviderModels('/api/models/pollinations', 'pollinations', pollinationsOptgroup),
loadProviderModels('/api/models/gemini', 'gemini', geminiOptgroup)
+65 -3
View File
@@ -137,8 +137,8 @@ registerPage('api-keys', {
statusLine.className = 'admin-user-meta';
statusLine.style.cssText = 'padding:0 var(--box-padding) .5rem;display:none;font-size:.85em';
// Check (nur Anthropic) – rechts neben Usage-Badge
if (key.provider === 'anthropic') {
// 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';
@@ -324,6 +324,7 @@ registerPage('api-keys', {
var scannedList = document.getElementById('scannedKeysList');
var scannedKeys = [];
var selectedType = null;
var checkUncheckedAbort = false;
var pollTimer = null;
var typeNames = {
@@ -405,10 +406,71 @@ registerPage('api-keys', {
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; loadSummary(); });
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';
+2
View File
@@ -168,6 +168,8 @@ async function _showKeySwitchModal() {
var meta = document.createElement('div');
meta.className = 'key-switch-meta';
var parts = [];
var providerLabels = { anthropic: '🟤 Anthropic', openai: '🟢 OpenAI', google: '🔵 Google' };
parts.push(providerLabels[key.provider] || key.provider || 'Anthropic');
if (key.plan) parts.push(key.plan);
if (key.status) parts.push(key.status === 'valid' ? '✅' : '⚠️');
meta.textContent = parts.join(' · ');