feat: API-Keys in Einstellungen gruppiert nach Kunde

This commit is contained in:
cynfo3000 2026-03-10 00:36:31 +01:00
parent cdb2443d8f
commit 56cb19d052

View File

@ -347,61 +347,86 @@ function APIKeysSection() {
</div> </div>
)} )}
{/* Key Liste */} {/* Key Liste — gruppiert nach Kunde */}
{loading ? ( {loading ? (
<div className="px-4 py-3 text-gray-500 text-xs">Laden...</div> <div className="px-4 py-3 text-gray-500 text-xs">Laden...</div>
) : keys.length === 0 ? ( ) : keys.length === 0 ? (
<div className="px-4 py-4 text-center text-gray-500 text-xs">Keine API-Keys vorhanden</div> <div className="px-4 py-4 text-center text-gray-500 text-xs">Keine API-Keys vorhanden</div>
) : ( ) : (() => {
<div className="divide-y divide-gray-800/60"> // Gruppen bauen: null-Kunde zuerst, dann nach Kundennummer sortiert
{keys.map((k) => { const groups = {}
const perm = PERM_LABELS[k.permissions] || PERM_LABELS.read keys.forEach(k => {
const customer = k.customer_id ? customers.find(c => c.id === k.customer_id) : null const gid = k.customer_id ?? '__global__'
const lastUsedAgo = k.last_used if (!groups[gid]) groups[gid] = []
? (() => { groups[gid].push(k)
const s = (Date.now() - new Date(k.last_used)) / 1000 })
if (s < 120) return 'gerade eben' const sorted = Object.entries(groups).sort(([a], [b]) => {
if (s < 3600) return `${Math.floor(s/60)}m` if (a === '__global__') return -1
if (s < 86400) return `${Math.floor(s/3600)}h` if (b === '__global__') return 1
return `${Math.floor(s/86400)}d` const ca = customers.find(c => c.id === parseInt(a))
})() const cb = customers.find(c => c.id === parseInt(b))
: null return (ca?.number || '').localeCompare(cb?.number || '')
return ( })
<div key={k.id} className="px-4 py-1.5 flex items-center gap-2.5 hover:bg-gray-800/30 group"> return (
<span className={`text-[10px] font-bold px-1.5 py-0.5 rounded border leading-none w-16 text-center flex-shrink-0 ${perm.color}`}> <div className="divide-y divide-gray-800">
{perm.label} {sorted.map(([gid, gkeys]) => {
</span> const customer = gid !== '__global__' ? customers.find(c => c.id === parseInt(gid)) : null
<span className="text-xs text-gray-300 flex-1 min-w-0 truncate">{k.name}</span> return (
{customer && ( <div key={gid}>
<span className="text-[10px] text-gray-600 flex-shrink-0 hidden sm:block">{customer.number}</span> {/* Gruppen-Header */}
)} <div className="px-4 py-1.5 bg-gray-800/40 flex items-center gap-2">
<code className="text-xs text-gray-600 font-mono flex-shrink-0"> <span className="text-[10px] font-semibold text-gray-500 uppercase tracking-wide">
{k.key.substring(0, 8)}{k.key.substring(k.key.length - 4)} {customer ? `${customer.number}${customer.name}` : 'Global'}
</code> </span>
{lastUsedAgo && ( <span className="text-[10px] text-gray-700">({gkeys.length})</span>
<span className="text-[10px] text-gray-700 flex-shrink-0 hidden md:block" title={`Zuletzt benutzt: ${new Date(k.last_used).toLocaleString('de-DE')}`}> </div>
{lastUsedAgo} {/* Keys der Gruppe */}
</span> {gkeys.map(k => {
)} const perm = PERM_LABELS[k.permissions] || PERM_LABELS.read
<button const lastUsedAgo = k.last_used
onClick={() => copyText(k.key, k.id)} ? (() => {
className="text-gray-600 hover:text-orange-400 transition-colors flex-shrink-0 opacity-0 group-hover:opacity-100" const s = (Date.now() - new Date(k.last_used)) / 1000
title="Key kopieren" if (s < 120) return 'gerade eben'
> if (s < 3600) return `${Math.floor(s/60)}m`
{copied === k.id ? <Check className="w-3.5 h-3.5 text-green-400" /> : <Copy className="w-3.5 h-3.5" />} if (s < 86400) return `${Math.floor(s/3600)}h`
</button> return `${Math.floor(s/86400)}d`
<button })()
onClick={() => handleDelete(k.id, k.name)} : null
className="text-gray-700 hover:text-red-400 transition-colors flex-shrink-0 opacity-0 group-hover:opacity-100" return (
title="Key loeschen" <div key={k.id} className="px-4 py-1.5 flex items-center gap-2.5 hover:bg-gray-800/30 group">
> <span className={`text-[10px] font-bold px-1.5 py-0.5 rounded border leading-none w-16 text-center flex-shrink-0 ${perm.color}`}>
<Trash2 className="w-3.5 h-3.5" /> {perm.label}
</button> </span>
</div> <span className="text-xs text-gray-300 flex-1 min-w-0 truncate">{k.name}</span>
) <code className="text-xs text-gray-600 font-mono flex-shrink-0">
})} {k.key.substring(0, 8)}{k.key.substring(k.key.length - 4)}
</div> </code>
)} {lastUsedAgo && (
<span className="text-[10px] text-gray-700 flex-shrink-0 hidden md:block"
title={`Zuletzt: ${new Date(k.last_used).toLocaleString('de-DE')}`}>
{lastUsedAgo}
</span>
)}
<button onClick={() => copyText(k.key, k.id)}
className="text-gray-600 hover:text-orange-400 transition-colors flex-shrink-0 opacity-0 group-hover:opacity-100"
title="Key kopieren">
{copied === k.id ? <Check className="w-3.5 h-3.5 text-green-400" /> : <Copy className="w-3.5 h-3.5" />}
</button>
<button onClick={() => handleDelete(k.id, k.name)}
className="text-gray-700 hover:text-red-400 transition-colors flex-shrink-0 opacity-0 group-hover:opacity-100"
title="Key loeschen">
<Trash2 className="w-3.5 h-3.5" />
</button>
</div>
)
})}
</div>
)
})}
</div>
)
})()
}
</div> </div>
) )
} }