api usage cooldown

This commit is contained in:
MDeeApp
2025-10-05 20:12:13 +02:00
parent 1b0389b63d
commit 3bcc7b08b4
4 changed files with 1266 additions and 141 deletions

View File

@@ -173,6 +173,68 @@
color: #65676b;
}
.credential-item__status {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 8px;
}
.credential-status {
display: inline-flex;
align-items: center;
padding: 4px 10px;
border-radius: 999px;
font-size: 12px;
font-weight: 600;
background: #f0f2f5;
color: #1c1e21;
border: 1px solid rgba(0, 0, 0, 0.05);
}
.credential-status.status-badge--success {
background: rgba(66, 183, 42, 0.12);
color: #2d7a32;
border-color: rgba(66, 183, 42, 0.2);
}
.credential-status.status-badge--warning {
background: rgba(252, 160, 0, 0.12);
color: #9f580a;
border-color: rgba(252, 160, 0, 0.2);
}
.credential-status.status-badge--danger {
background: rgba(231, 76, 60, 0.12);
color: #a5281b;
border-color: rgba(231, 76, 60, 0.2);
}
.credential-status.status-badge--info {
background: rgba(24, 119, 242, 0.1);
color: #1659c7;
border-color: rgba(24, 119, 242, 0.2);
}
.credential-status.status-badge--neutral {
background: rgba(101, 103, 107, 0.12);
color: #42464b;
border-color: rgba(101, 103, 107, 0.2);
}
.credential-status.status-badge--muted {
background: rgba(148, 153, 160, 0.12);
color: #4b4f56;
border-color: rgba(148, 153, 160, 0.2);
}
.credential-item__meta {
margin-top: 6px;
font-size: 12px;
color: #65676b;
line-height: 1.4;
}
.credential-item__actions {
display: flex;
gap: 8px;

View File

@@ -123,6 +123,134 @@ async function loadSettings() {
'Schreibe einen freundlichen, authentischen Kommentar auf Deutsch zu folgendem Facebook-Post. Der Kommentar soll natürlich wirken und maximal 2-3 Sätze lang sein:\n\n';
}
function shorten(text, maxLength = 80) {
if (typeof text !== 'string') {
return '';
}
if (text.length <= maxLength) {
return text;
}
return `${text.slice(0, maxLength - 3)}...`;
}
function escapeHtmlAttr(text) {
return escapeHtml(text || '').replace(/"/g, '&quot;');
}
function formatTimeLabel(iso) {
if (!iso) return '';
const date = new Date(iso);
if (Number.isNaN(date.getTime())) return '';
return date.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
}
function formatRelativePast(iso) {
if (!iso) return '';
const date = new Date(iso);
if (Number.isNaN(date.getTime())) return '';
const diffMs = Date.now() - date.getTime();
if (diffMs < 0) return 'gerade eben';
const diffMinutes = Math.round(diffMs / 60000);
if (diffMinutes <= 1) return 'gerade eben';
if (diffMinutes < 60) return `vor ${diffMinutes} Min`;
const diffHours = Math.round(diffMinutes / 60);
if (diffHours < 24) return `vor ${diffHours} Std`;
const diffDays = Math.round(diffHours / 24);
if (diffDays === 1) return 'gestern';
if (diffDays < 7) return `vor ${diffDays} Tagen`;
return date.toLocaleDateString('de-DE');
}
function formatRelativeFuture(iso) {
if (!iso) return '';
const date = new Date(iso);
if (Number.isNaN(date.getTime())) return '';
const diffMs = date.getTime() - Date.now();
if (diffMs <= 0) return 'gleich';
const diffMinutes = Math.round(diffMs / 60000);
if (diffMinutes < 1) return 'gleich';
if (diffMinutes < 60) return `in ${diffMinutes} Min`;
const diffHours = Math.round(diffMinutes / 60);
if (diffHours < 24) return `in ${diffHours} Std`;
return date.toLocaleString('de-DE', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });
}
function buildCredentialBadges(credential) {
const badges = [];
if (!credential.is_active) {
badges.push({
label: 'Deaktiviert',
className: 'status-badge--muted',
title: 'Dieser Login ist derzeit deaktiviert'
});
} else if (credential.auto_disabled) {
const untilText = credential.auto_disabled_until ? formatRelativeFuture(credential.auto_disabled_until) : 'läuft';
const untilTime = credential.auto_disabled_until ? formatTimeLabel(credential.auto_disabled_until) : '';
const reason = credential.auto_disabled_reason ? credential.auto_disabled_reason.replace(/^AUTO:/, '').trim() : 'Automatisch deaktiviert';
badges.push({
label: `Cooldown ${untilText}${untilTime ? ` (${untilTime})` : ''}`.trim(),
className: 'status-badge--warning',
title: reason || 'Automatisch deaktiviert'
});
} else {
badges.push({
label: 'Aktiv',
className: 'status-badge--success',
title: 'Login ist aktiv'
});
}
if (credential.last_error_message) {
badges.push({
label: `Fehler ${formatRelativePast(credential.last_error_at)}`.trim(),
className: 'status-badge--danger',
title: credential.last_error_message
});
}
if (credential.usage_24h_count) {
const resetHint = credential.usage_24h_reset_at ? `Reset ${formatRelativeFuture(credential.usage_24h_reset_at)}` : '24h Nutzung';
badges.push({
label: `24h: ${credential.usage_24h_count}`,
className: 'status-badge--info',
title: resetHint
});
}
if (credential.last_rate_limit_remaining) {
badges.push({
label: `Limit: ${credential.last_rate_limit_remaining}`,
className: 'status-badge--neutral',
title: 'Letzter „rate limit remaining“-Wert'
});
}
return badges;
}
function buildCredentialMetaLines(credential) {
const lines = [];
if (credential.last_success_at) {
lines.push(`Zuletzt erfolgreich: ${formatRelativePast(credential.last_success_at)}`);
}
if (!credential.last_success_at && credential.last_used_at) {
lines.push(`Zuletzt genutzt: ${formatRelativePast(credential.last_used_at)}`);
}
if (credential.rate_limit_reset_at && !credential.auto_disabled) {
lines.push(`Limit-Reset ${formatRelativeFuture(credential.rate_limit_reset_at)}`);
}
if (credential.latest_event && credential.latest_event.type) {
const typeLabel = credential.latest_event.type.replace(/_/g, ' ');
const eventTime = credential.latest_event.created_at ? formatRelativePast(credential.latest_event.created_at) : '';
const message = credential.latest_event.message ? shorten(credential.latest_event.message, 90) : '';
const parts = [`Letztes Event (${typeLabel})`];
if (eventTime) parts.push(eventTime);
if (message) parts.push(` ${message}`);
lines.push(parts.join(' '));
}
return lines;
}
function renderCredentials() {
const list = document.getElementById('credentialsList');
if (!credentials.length) {
@@ -133,6 +261,14 @@ function renderCredentials() {
const providerName = escapeHtml(PROVIDER_INFO[c.provider]?.name || c.provider);
const modelLabel = c.model ? ` · ${escapeHtml(c.model)}` : '';
const endpointLabel = c.base_url ? ` · ${escapeHtml(c.base_url)}` : '';
const badges = buildCredentialBadges(c);
const badgesHtml = badges.length
? `<div class="credential-item__status">${badges.map(badge => `<span class="credential-status ${badge.className}"${badge.title ? ` title="${escapeHtmlAttr(badge.title)}"` : ''}>${escapeHtml(badge.label)}</span>`).join('')}</div>`
: '';
const metaLines = buildCredentialMetaLines(c);
const metaHtml = metaLines.length
? `<div class="credential-item__meta">${metaLines.map(line => `<div>${escapeHtml(line)}</div>`).join('')}</div>`
: '';
return `
<div class="credential-item" draggable="true" data-credential-id="${c.id}" data-index="${index}">
@@ -145,6 +281,8 @@ function renderCredentials() {
<div>
<div class="credential-item__name">${escapeHtml(c.name)}</div>
<div class="credential-item__provider">${providerName}${modelLabel}${endpointLabel}</div>
${badgesHtml}
${metaHtml}
</div>
</label>
</div>