Lizenzverwaltung: Übersicht, Vergabe, Aktivierungen, Lebenszyklus und Kundensicht
Neuer Menüpunkt "Lizenzen" (Personal) bzw. "Meine Lizenzen" (Kunden): - Übersicht mit Kennzahlen, Filtern (aktiv, läuft in 30 Tagen ab, gesperrt, abgelaufen, ohne Kunde) und Suche; Dashboard-Kachel - Vergabe mit Vertrag (normaler Bestellweg) oder ohne Berechnung direkt im Lizenzsystem: Lizenz, Test (Trial) oder Add-on zu einer Basislizenz - Detailseite: Schlüssel, Geräte (Aktivierungen) freigeben, Sperren/Entsperren, Verlängern, Widerrufen, Limits, Funktionsumfang (Entitlement), Produktwechsel bzw. Testumwandlung, Add-ons, Verlauf - Kunden-Selbstbedienung: Inhaber/Admin geben eigene Geräte frei Verwaltungs-Client für das Lizenzsystem in @kc/connector-licensing (createLicensingAdmin), Fehlermeldungen des Lizenzsystems werden verständlich weitergereicht. Alle Änderungen im Audit-Protokoll; der lokale Stand wird danach sofort aktualisiert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
9336f46d0a
commit
a322166d01
11 changed files with 784 additions and 31 deletions
|
|
@ -9,6 +9,7 @@ interface Sys { jobs: Record<string, number>; oldestPendingJob: string | null; b
|
|||
export default function Dashboard() {
|
||||
const { me, can } = useSession();
|
||||
const [mine, setMine] = useState<{ contracts: { status: string; cancelEffectiveAt: string | null }[]; resources: { state: string; stale: boolean }[]; orders: { status: string }[] } | null>(null);
|
||||
const [licenses, setLicenses] = useState<{ active: number; expiring: number; unassigned: number } | null>(null);
|
||||
const [customers, setCustomers] = useState<number | null>(null); const [sys, setSys] = useState<Sys | null>(null); const [err, setErr] = useState('');
|
||||
useEffect(() => {
|
||||
if (me?.kind === 'customer') {
|
||||
|
|
@ -18,6 +19,7 @@ export default function Dashboard() {
|
|||
if (!me || me.kind !== 'staff') return;
|
||||
if (can('customers.read')) api<unknown[]>('GET', '/admin/customers').then((l) => setCustomers(l.length)).catch((e) => setErr(errMsg(e)));
|
||||
if (can('jobs.read')) api<Sys>('GET', '/admin/system').then(setSys).catch((e) => setErr(errMsg(e)));
|
||||
if (can('licenses.read')) api<{ active: number; expiring: number; unassigned: number }>('GET', '/admin/licenses/summary').then(setLicenses).catch(() => undefined);
|
||||
}, [me, can]);
|
||||
if (!me) return null;
|
||||
const failed = sys ? (sys.jobs.failed ?? 0) + (sys.jobs.needs_review ?? 0) : 0;
|
||||
|
|
@ -37,6 +39,7 @@ export default function Dashboard() {
|
|||
</>) : (<>
|
||||
<div className="grid">
|
||||
{customers !== null && <div className="card"><div className="stat">{customers}</div><p className="muted">Kunden</p><Link href="/admin/kunden">Kunden verwalten</Link></div>}
|
||||
{licenses && <div className="card"><div className="stat">{licenses.active}</div><p className="muted">Aktive Lizenzen</p>{licenses.expiring > 0 && <p className="small"><span className="badge warn"><span aria-hidden="true">▲</span>{licenses.expiring} laufen in 30 Tagen ab</span></p>}{licenses.unassigned > 0 && <p className="small"><span className="badge warn"><span aria-hidden="true">▲</span>{licenses.unassigned} ohne Kunde</span></p>}<Link href="/lizenzen">Lizenzen verwalten</Link></div>}
|
||||
{sys && <div className="card"><div className="stat">{sys.jobs.scheduled ?? 0}</div><p className="muted">Wartende Aufträge</p></div>}
|
||||
{sys?.backup && <div className="card"><h3>Backup</h3><p className="small"><Link href="/einstellungen/backup">Details und Einstellungen</Link></p>
|
||||
{!sys.backup.configured ? <><p className="muted">Nicht eingerichtet.</p><span className="badge warn"><span aria-hidden="true">▲</span>Kein Backup</span></>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue