Mit dem dreizehnten Eintrag verschluckte die Leiste erneut Punkte am rechten Rand — nach M6 zum zweiten Mal. Das Problem ist nicht die Breite, sondern die Vermischung: Einrichtung und Verwaltung sucht man nicht beim Arbeiten. Alltag bleibt in der Leiste, alles andere liegt hinter "Mehr". Ein <details> statt eines Menues aus Skript: Es klappt ohne JavaScript auf und braucht keine Zustandsverwaltung. Zurueckgezogene Tokens stehen jetzt unten. Was noch Zugang hat, ist das, wonach man sucht. Zwei E2E-Tests greifen den neuen Weg ueber "Mehr" auf. Ein dritter wartet das Neuladen ab, bevor er navigiert — die Meldung kommt frueher als die fertige Liste, und Chromium bricht die Navigation sonst ab. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
80 lines
2.5 KiB
TypeScript
80 lines
2.5 KiB
TypeScript
import type { Metadata } from 'next'
|
|
import { KeyRound } from 'lucide-react'
|
|
import { TokenVerwaltung } from '@/app/(app)/verwaltung/api/token-verwaltung'
|
|
import {
|
|
Card,
|
|
CardBeschreibung,
|
|
CardInhalt,
|
|
CardKopf,
|
|
CardTitel,
|
|
} from '@/components/ui/card'
|
|
import { rechteLesen } from '@/lib/api'
|
|
import { alsDatumUhrzeit } from '@/lib/zeit'
|
|
import { verlangeRolle } from '@/server/berechtigung'
|
|
import { db } from '@/server/db'
|
|
import { umgebung } from '@/server/umgebung'
|
|
|
|
export const metadata: Metadata = { title: 'API-Tokens' }
|
|
|
|
export default async function ApiSeite() {
|
|
await verlangeRolle('ADMIN', '/verwaltung/api')
|
|
|
|
const tokens = await db.apiToken.findMany({
|
|
// Gueltige zuerst, Zurueckgezogenes ans Ende: Was noch Zugang hat, ist
|
|
// das, wonach man sucht.
|
|
orderBy: [{ widerrufenAm: { sort: 'asc', nulls: 'first' } }, { erstelltAm: 'desc' }],
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
praefix: true,
|
|
rechte: true,
|
|
gueltigBis: true,
|
|
letzteNutzung: true,
|
|
widerrufenAm: true,
|
|
erstelltAm: true,
|
|
},
|
|
})
|
|
|
|
const adresse = umgebung().APP_URL
|
|
|
|
return (
|
|
<div className="flex flex-col gap-6">
|
|
<div className="flex items-center gap-3">
|
|
<KeyRound className="text-gedaempft-vordergrund size-6" aria-hidden />
|
|
<div>
|
|
<h1 className="text-2xl font-semibold tracking-tight">API-Tokens</h1>
|
|
<p className="text-gedaempft-vordergrund text-sm">
|
|
Zugänge für fremde Anwendungen — mit so wenig Rechten wie möglich.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<Card>
|
|
<CardKopf>
|
|
<CardTitel className="text-base">Tokens</CardTitel>
|
|
<CardBeschreibung>
|
|
Ein Token ist ein Passwort. Er wird beim Anlegen ein einziges Mal angezeigt;
|
|
danach liegt in der Datenbank nur noch sein Fingerabdruck. Wer ihn verliert,
|
|
legt einen neuen an — nachschlagen lässt er sich nicht.
|
|
</CardBeschreibung>
|
|
</CardKopf>
|
|
<CardInhalt>
|
|
<TokenVerwaltung
|
|
adresse={adresse}
|
|
tokens={tokens.map((t) => ({
|
|
id: t.id,
|
|
name: t.name,
|
|
praefix: t.praefix,
|
|
rechte: rechteLesen(t.rechte),
|
|
gueltigBis: t.gueltigBis ? alsDatumUhrzeit(t.gueltigBis) : null,
|
|
letzteNutzung: t.letzteNutzung ? alsDatumUhrzeit(t.letzteNutzung) : null,
|
|
widerrufen: t.widerrufenAm !== null,
|
|
erstelltAm: alsDatumUhrzeit(t.erstelltAm),
|
|
}))}
|
|
/>
|
|
</CardInhalt>
|
|
</Card>
|
|
</div>
|
|
)
|
|
}
|