API-Keys: Source-URL, einheitliches Status-Schema, passive Usage-Erfassung, Usage-Bars
- SourceUrl-Feld in api_keys: GitHub-URL bei adoptierten Scan-Keys gespeichert - HandleAdopt: Name = user/repo (aus URL extrahiert), SourceUrl = vollständige URL - Status-Schema vereinheitlicht: überall 'valid' statt 'ok'/'usage_limited' - AnthropicKeyChecker.ExtractMetaFromHeaders(): passive Header-Auswertung - AnthropicService: TryUpdateKeyMeta() nach jedem Chat-Request (fire-and-forget) - DatabaseService: UpdateApiKeyMetaByToken() für passive Updates per Token - Frontend: formatKeyStatus() zentrale Status-Labels für API-Keys + Scanned-Keys - Frontend: createUsageBadge() zweizeiliger Balken (Sitzung/Woche) mit Farbstufen - CSS: Usage-Badge mit Track/Fill, warn (>=70%) und danger (>=90%) Klassen
This commit is contained in:
1 parent
608118a823
commit
de388806cb
8 files changed
+226
-32
No files matched your search
@@ -1,5 +1,56 @@
|
||||
// ── 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) {
|
||||
@@ -48,19 +99,24 @@ registerPage('api-keys', {
|
||||
metaSpan.className = 'admin-user-meta';
|
||||
var parts = [key.provider];
|
||||
if (key.isActive) parts.push('✅ Aktiv');
|
||||
if (key.status === 'ok') parts.push('✅ Gültig');
|
||||
else if (key.status === 'invalid') parts.push('❌ Ungültig');
|
||||
else if (key.status === 'rate_limited') parts.push('⚠️ Rate Limited');
|
||||
else if (key.status === 'quota_exceeded') parts.push('⚠️ Quota');
|
||||
else if (key.status === 'error') parts.push('⚠️ Fehler');
|
||||
parts.push(formatKeyStatus(key.status));
|
||||
if (key.plan) parts.push(key.plan);
|
||||
if (key.usage7d != null) parts.push('7d: ' + key.usage7d + '%');
|
||||
if (key.usage5h != null) parts.push('5h: ' + key.usage5h + '%');
|
||||
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);
|
||||
var usageBadge = createUsageBadge(key.usage5h, key.usage7d);
|
||||
if (usageBadge) info.appendChild(usageBadge);
|
||||
|
||||
var actions = document.createElement('div');
|
||||
actions.className = 'admin-user-actions';
|
||||
@@ -75,7 +131,7 @@ registerPage('api-keys', {
|
||||
var checkBtn = document.createElement('button');
|
||||
checkBtn.className = 'icon-btn';
|
||||
checkBtn.title = 'Key prüfen';
|
||||
checkBtn.innerHTML = '<i class="fa fa-heartbeat"></i>';
|
||||
checkBtn.innerHTML = '<i class="fa fa-refresh"></i>';
|
||||
checkBtn.addEventListener('click', function() { checkKey(key, checkBtn, statusLine); });
|
||||
actions.appendChild(checkBtn);
|
||||
}
|
||||
@@ -140,17 +196,15 @@ registerPage('api-keys', {
|
||||
try {
|
||||
var result = await apiCheckApiKey(key.keyId);
|
||||
statusLine.style.opacity = '1';
|
||||
if (result.status === 'ok') {
|
||||
var parts = ['✅ Gültig', result.keyType];
|
||||
if (result.usage5h != null) parts.push('5h: ' + result.usage5h + '%');
|
||||
if (result.usage7d != null) {
|
||||
var s7d = '7d: ' + result.usage7d + '%';
|
||||
if (result.status7d === 'allowed_warning') s7d += ' ⚠️';
|
||||
else if (result.status7d === 'exceeded') s7d += ' 🛑';
|
||||
parts.push(s7d);
|
||||
}
|
||||
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 + '%');
|
||||
statusLine.textContent = parts.join(' · ');
|
||||
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');
|
||||
}
|
||||
@@ -294,23 +348,21 @@ registerPage('api-keys', {
|
||||
nameSpan.href = sk.sourceUrl;
|
||||
nameSpan.target = '_blank';
|
||||
nameSpan.rel = 'noopener';
|
||||
nameSpan.style.cssText = 'word-break:break-all;font-size:.85em';
|
||||
nameSpan.textContent = sk.sourceUrl.replace('https://github.com/', '');
|
||||
info.appendChild(nameSpan);
|
||||
}
|
||||
|
||||
var metaSpan = document.createElement('span');
|
||||
metaSpan.className = 'admin-user-meta';
|
||||
var statusIcon = sk.status === 'valid' ? '✅' : sk.status === 'invalid' ? '❌' : sk.status === 'unchecked' ? '⏳' : '⚠️';
|
||||
var metaParts = isAdopted ? ['📥 Übernommen'] : [];
|
||||
metaParts.push(statusIcon + ' ' + sk.status);
|
||||
metaParts.push(formatKeyStatus(sk.status));
|
||||
if (sk.plan && sk.plan !== 'Unknown') metaParts.push(sk.plan);
|
||||
if (sk.usage7d != null) metaParts.push('7d: ' + sk.usage7d + '%');
|
||||
if (sk.usage5h != null) metaParts.push('5h: ' + sk.usage5h + '%');
|
||||
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';
|
||||
|
||||
Reference in new issue
Block a user