feat: API-Keys in Einstellungen gruppiert nach Kunde
This commit is contained in:
parent
cdb2443d8f
commit
56cb19d052
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user