'use client' import { useState } from 'react' import { Plus, KeyRound, ShieldCheck, AlertTriangle, CalendarClock, RefreshCw, CheckCircle2, XCircle, PauseCircle, } from 'lucide-react' import { useFetch } from '@/hooks/use-fetch' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Skeleton } from '@/components/ui/skeleton' import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs' import { PageHeader } from '../primitives' import { formatEuro, formatDate, daysUntil } from '@/lib/format' import { cn } from '@/lib/utils' interface License { id: string key: string product: string plan: string status: string maxActivations: number validUntil: string | null createdAt: string company: { id: string; name: string } activations: { id: string; domain: string; environment: string; version: string | null; lastCheck: string }[] } interface Asset { id: string name: string type: string provider: string | null costCentsPerMonth: number | null renewalDate: string rebill: boolean markupPct: number status: string } interface LicensesData { licenses: License[] assets: Asset[] stats: { activeLicenses: number suspended: number expired: number totalActivations: number expiringSoon: number expiredAssets: number monthlyRebill: number } } const LIC_STATUS: Record = { active: { label: 'Aktiv', icon: CheckCircle2 }, suspended: { label: 'Suspendiert', icon: PauseCircle }, expired: { label: 'Abgelaufen', icon: XCircle }, revoked: { label: 'Widerrufen', icon: XCircle }, } const ASSET_TYPE_LABELS: Record = { domain: 'Domain', hosting: 'Hosting', ssl: 'SSL', 'plugin-seat': 'Plugin-Seat', saas: 'SaaS', } export function LicensesView() { const { data, loading } = useFetch('/api/aos/licenses') if (loading || !data) { return (
) } const { licenses, assets, stats } = data return (
Lizenz ausstellen } />

Aktive Lizenzen

{stats.activeLicenses}

Aktivierungen

{stats.totalActivations}

Erneuerungen fällig

{stats.expiringSoon + stats.expiredAssets}

{stats.expiredAssets} abgelaufen

Monatl. Weiterbelastung

{formatEuro(stats.monthlyRebill)}

Ausgestellte Lizenzen Erneuerungsregister Produktlizenzen ({licenses.length})
{licenses.map((l) => { const st = LIC_STATUS[l.status] ?? LIC_STATUS.active const Icon = st.icon const valid = l.validUntil ? daysUntil(l.validUntil) : null return ( ) })}
Lizenzschlüssel Produkt / Plan Kunde Aktivierungen Status Gültig bis
{l.key}

{l.product}

{l.plan}
{l.company.name} {l.activations.length} / {l.maxActivations} {l.activations[0] && (

{l.activations[0].domain}

)}
{st.label} {l.validUntil ? ( {formatDate(l.validUntil)} {valid !== null && ( {valid < 0 ? `(${Math.abs(valid)} d vorbei)` : `(${valid} d)`} )} ) : '—'}
{/* Signing info card */}

Ed25519-signierte Lizenz-Tokens

Jede Lizenz hat einen menschenlesbaren Schlüssel plus einen kryptografisch signierten Token. Kunden können offline verifizieren. Der private Schlüssel ist verschlüsselt (AES-256-GCM); Rotation wird unterstützt.

Public API: /api/v1/validate WordPress-Updater kompatibel Offline-Grace: 14 d
Asset- & Erneuerungsregister ({assets.length})
{assets.map((a) => { const days = daysUntil(a.renewalDate) return ( ) })}
Asset Typ Anbieter Kosten/Monat Erneuerung Weiterbelasten Status
{a.name} {ASSET_TYPE_LABELS[a.type] ?? a.type} {a.provider ?? '—'} {a.costCentsPerMonth ? formatEuro(a.costCentsPerMonth) : '—'} {formatDate(a.renewalDate)} {days < 0 ? `(${Math.abs(days)} d vorbei)` : `(${days} d)`} {a.rebill ? ( +{a.markupPct}% ) : ( — )} {a.status === 'expired' ? 'Abgelaufen' : a.status === 'expiring' ? 'Läuft ab' : 'Aktiv'}
) }