mailwolt/resources/views/livewire/ui/system/api-key-table.blade.php

305 lines
18 KiB
PHP

<x-slot:breadcrumbParent>System</x-slot:breadcrumbParent>
<x-slot:breadcrumb>API Keys</x-slot:breadcrumb>
<div x-data="{activeGroup: 'mailboxes'}"
x-on:token-created.window="$wire.$refresh()"
x-on:token-deleted.window="$wire.$refresh()">
<div class="mbx-page-header">
<div class="mbx-page-title">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M6 10a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z" stroke="currentColor" stroke-width="1.3"/>
<path d="M9.5 8.5L14 13" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
<circle cx="6" cy="6" r="1.5" fill="currentColor" opacity=".4"/>
</svg>
API Keys
<span class="mbx-total-badge">{{ $tokens->count() }}</span>
</div>
<div class="mbx-page-actions">
<button wire:click="$dispatch('openModal',{component:'ui.system.modal.api-key-create-modal'})"
class="mbx-btn-primary">
<svg width="11" height="11" viewBox="0 0 11 11" fill="none"><path d="M5.5 1v9M1 5.5h9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
API Key
</button>
</div>
</div>
<div class="mw-apikey-layout">
{{-- ═══ Left: Keys + Endpoint-Docs ═══ --}}
<div class="mbx-sections">
{{-- Keys --}}
<div class="mbx-section">
<div class="mbx-domain-head">
<div class="mbx-domain-info">
<span class="mbx-badge-mute">Aktive Keys</span>
</div>
</div>
@if($tokens->isEmpty())
<div style="padding:36px 16px;text-align:center">
<svg width="30" height="30" viewBox="0 0 16 16" fill="none" style="color:var(--mw-t4);margin:0 auto 10px;display:block">
<path d="M6 10a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z" stroke="currentColor" stroke-width="1.3"/>
<path d="M9.5 8.5L14 13" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
</svg>
<div style="font-size:12.5px;color:var(--mw-t2);font-weight:500;margin-bottom:4px">Keine API Keys vorhanden</div>
<div style="font-size:11.5px;color:var(--mw-t4)">Erstelle deinen ersten Key um externe Anwendungen zu verbinden.</div>
</div>
@else
{{-- Header --}}
<div class="mw-kl-head">
<span>Name</span>
<span>Scopes</span>
<span>Modus</span>
<span>Erstellt</span>
<span></span>
</div>
{{-- Rows --}}
@foreach($tokens as $token)
@php
$abilities = $token->abilities;
$visible = array_slice($abilities, 0, 2);
$rest = count($abilities) - 2;
@endphp
<div class="mw-kl-row">
{{-- Name --}}
<div class="mw-kl-name" style="display:flex;align-items:center;gap:8px;overflow:hidden">
<div style="width:26px;height:26px;border-radius:7px;background:var(--mw-bg4);border:1px solid var(--mw-b2);display:flex;align-items:center;justify-content:center;flex-shrink:0">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none">
<path d="M6 10a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z" stroke="var(--mw-t3)" stroke-width="1.3"/>
<path d="M9.5 8.5L14 13" stroke="var(--mw-t3)" stroke-width="1.3" stroke-linecap="round"/>
</svg>
</div>
<span style="font-size:12.5px;font-weight:500;color:var(--mw-t1);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1">{{ $token->name }}</span>
</div>
{{-- Scopes --}}
<div class="mw-kl-scopes" style="display:flex;flex-wrap:nowrap;gap:3px;align-items:center;overflow:hidden">
@foreach($visible as $scope)
@php $short = collect(explode(':', $scope))->map(fn($p) => $p[0])->join(':'); @endphp
<span title="{{ $scope }}" style="font-family:monospace;font-size:9.5px;padding:1px 5px;background:var(--mw-bg4);border:1px solid var(--mw-b2);border-radius:4px;color:var(--mw-t4)">{{ $short }}</span>
@endforeach
@if($rest > 0)
<button wire:click="$dispatch('openModal',{component:'ui.system.modal.api-key-scopes-modal',arguments:{tokenId:{{ $token->id }}}})"
style="font-family:monospace;font-size:9.5px;padding:1px 6px;background:var(--mw-bg4);border:1px solid var(--mw-b2);border-radius:4px;color:var(--mw-v2);cursor:pointer;white-space:nowrap;flex-shrink:0">
+{{ $rest }}
</button>
@endif
<span class="mw-kl-date-inline" style="font-size:10px;color:var(--mw-t5);margin-left:2px">· {{ $token->created_at->format('d.m.Y') }}</span>
</div>
{{-- Modus --}}
<div class="mw-kl-modus">
@if($token->sandbox)
<span style="font-size:10px;padding:2px 7px;border-radius:5px;background:rgba(251,191,36,.1);border:1px solid rgba(251,191,36,.25);color:#f59e0b;font-weight:500;white-space:nowrap">Sandbox</span>
@else
<span style="font-size:10px;padding:2px 7px;border-radius:5px;background:rgba(16,185,129,.08);border:1px solid rgba(16,185,129,.2);color:#34d399;font-weight:500;white-space:nowrap">Live</span>
@endif
</div>
{{-- Date (desktop only) --}}
<div class="mw-kl-date" style="font-size:11px;color:var(--mw-t4)">{{ $token->created_at->format('d.m.Y') }}</div>
{{-- Actions --}}
<div class="mw-kl-actions">
<button wire:click="$dispatch('openModal',{component:'ui.system.modal.api-key-delete-modal',arguments:{tokenId:{{ $token->id }}}})"
class="mbx-act-btn mbx-act-danger" title="Löschen">
<svg width="12" height="12" viewBox="0 0 13 13" fill="none"><path d="M2 3h9M5 3V2h3v1M3.5 3l.5 8h5l.5-8" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
</div>
</div>
@endforeach
@endif
</div>
{{-- Endpoint Reference --}}
<div class="mbx-section">
<div class="mbx-domain-head">
<div class="mbx-domain-info">
<span class="mbx-badge-mute">Endpunkte</span>
<span style="font-size:10.5px;color:var(--mw-t4);margin-left:4px">Basis-URL: <code style="font-family:monospace;background:var(--mw-bg3);padding:1px 4px;border-radius:3px">/api/v1</code></span>
</div>
{{-- Tab switcher --}}
<div style="display:flex;gap:4px">
@foreach([
['mailboxes', 'Mailboxen'],
['aliases', 'Aliases'],
['domains', 'Domains'],
] as [$key, $label])
<button @click="activeGroup = '{{ $key }}'"
:style="activeGroup === '{{ $key }}'
? 'background:var(--mw-vbg);border-color:var(--mw-vbd);color:var(--mw-v2);font-weight:600'
: 'background:transparent;border-color:var(--mw-b2);color:var(--mw-t4)'"
style="padding:3px 10px;border-radius:5px;font-size:10.5px;border:1px solid;cursor:pointer;transition:all .1s">
{{ $label }}
</button>
@endforeach
</div>
</div>
<div style="padding:0">
{{-- Mailboxen --}}
<div x-show="activeGroup === 'mailboxes'" x-cloak>
@php $mbRoutes = [
['GET', '/mailboxes', 'mailboxes:read', 'Alle Mailboxen auflisten', '?domain=example.com&active=true'],
['GET', '/mailboxes/{id}', 'mailboxes:read', 'Einzelne Mailbox abrufen', null],
['POST', '/mailboxes', 'mailboxes:write', 'Neue Mailbox erstellen', null],
['PATCH', '/mailboxes/{id}', 'mailboxes:write', 'Mailbox aktualisieren', null],
['DELETE', '/mailboxes/{id}', 'mailboxes:write', 'Mailbox löschen', null],
]; @endphp
@include('livewire.ui.system.partials.api-endpoint-list', ['routes' => $mbRoutes])
<div style="padding:14px 16px;border-top:1px solid var(--mw-b1)">
<div style="font-size:11px;color:var(--mw-t4);margin-bottom:8px;font-weight:500">POST-Body (Erstellen)</div>
<pre style="font-family:monospace;font-size:10.5px;color:var(--mw-t3);background:var(--mw-bg4);padding:10px 12px;border-radius:6px;overflow-x:auto;margin:0;line-height:1.6">{
"email": "user@example.com",
"password": "sicher123",
"display_name": "Max Mustermann",
"quota_mb": 1024,
"is_active": true
}</pre>
</div>
</div>
{{-- Aliases --}}
<div x-show="activeGroup === 'aliases'" x-cloak>
@php $alRoutes = [
['GET', '/aliases', 'aliases:read', 'Alle Aliases auflisten', '?domain=example.com'],
['GET', '/aliases/{id}', 'aliases:read', 'Einzelnen Alias abrufen', null],
['POST', '/aliases', 'aliases:write', 'Neuen Alias erstellen', null],
['DELETE', '/aliases/{id}', 'aliases:write', 'Alias löschen', null],
]; @endphp
@include('livewire.ui.system.partials.api-endpoint-list', ['routes' => $alRoutes])
<div style="padding:14px 16px;border-top:1px solid var(--mw-b1)">
<div style="font-size:11px;color:var(--mw-t4);margin-bottom:8px;font-weight:500">POST-Body (Erstellen)</div>
<pre style="font-family:monospace;font-size:10.5px;color:var(--mw-t3);background:var(--mw-bg4);padding:10px 12px;border-radius:6px;overflow-x:auto;margin:0;line-height:1.6">{
"local": "info",
"domain": "example.com",
"recipients": ["admin@example.com"],
"is_active": true
}</pre>
</div>
</div>
{{-- Domains --}}
<div x-show="activeGroup === 'domains'" x-cloak>
@php $doRoutes = [
['GET', '/domains', 'domains:read', 'Alle Domains auflisten', null],
['GET', '/domains/{id}', 'domains:read', 'Einzelne Domain abrufen', null],
['POST', '/domains', 'domains:write', 'Neue Domain erstellen', null],
['DELETE', '/domains/{id}', 'domains:write', 'Domain löschen', null],
]; @endphp
@include('livewire.ui.system.partials.api-endpoint-list', ['routes' => $doRoutes])
<div style="padding:14px 16px;border-top:1px solid var(--mw-b1)">
<div style="font-size:11px;color:var(--mw-t4);margin-bottom:8px;font-weight:500">POST-Body (Erstellen)</div>
<pre style="font-family:monospace;font-size:10.5px;color:var(--mw-t3);background:var(--mw-bg4);padding:10px 12px;border-radius:6px;overflow-x:auto;margin:0;line-height:1.6">{
"domain": "example.com",
"description": "Hauptdomain",
"max_mailboxes": 100,
"max_aliases": 200,
"default_quota_mb": 1024
}</pre>
</div>
</div>
</div>
</div>
</div>
{{-- ═══ Right: Sidebar ═══ --}}
<div class="mbx-sections">
{{-- Auth-Info --}}
<div class="mbx-section">
<div class="mbx-domain-head">
<div class="mbx-domain-info">
<span class="mbx-badge-mute">Authentifizierung</span>
</div>
</div>
<div style="padding:14px 16px;display:flex;flex-direction:column;gap:12px">
<div style="font-size:11.5px;color:var(--mw-t3)">Sende den Token als HTTP-Header mit jeder Anfrage:</div>
<pre style="font-family:monospace;font-size:10.5px;color:var(--mw-t3);background:var(--mw-bg4);padding:10px 12px;border-radius:6px;overflow-x:auto;margin:0;line-height:1.6">Authorization: Bearer &lt;token&gt;
Content-Type: application/json</pre>
<div>
<div style="font-size:11px;color:var(--mw-t4);margin-bottom:6px">Token prüfen:</div>
<pre style="font-family:monospace;font-size:10px;color:var(--mw-t3);background:var(--mw-bg4);padding:10px 12px;border-radius:6px;overflow-x:auto;margin:0;line-height:1.6">GET /api/v1/me</pre>
</div>
</div>
</div>
{{-- Sandbox --}}
<div class="mbx-section">
<div class="mbx-domain-head">
<div class="mbx-domain-info">
<span style="font-size:10px;padding:2px 7px;border-radius:5px;background:rgba(251,191,36,.1);border:1px solid rgba(251,191,36,.25);color:#f59e0b;font-weight:600">Sandbox</span>
</div>
</div>
<div style="padding:14px 16px;font-size:11.5px;color:var(--mw-t3);display:flex;flex-direction:column;gap:8px">
<div>Sandbox-Keys simulieren alle Schreiboperationen. Die API antwortet realistisch aber es werden <strong style="color:var(--mw-t2)">keine Änderungen</strong> gespeichert.</div>
<div style="padding:8px 10px;background:rgba(251,191,36,.05);border:1px solid rgba(251,191,36,.15);border-radius:6px;font-family:monospace;font-size:10.5px;color:#f59e0b">
"sandbox": true
</div>
<div style="font-size:11px;color:var(--mw-t4)">Jede Write-Response enthält dieses Feld wenn der Key im Sandbox-Modus ist.</div>
</div>
</div>
{{-- Scopes --}}
<div class="mbx-section">
<div class="mbx-domain-head">
<div class="mbx-domain-info">
<span class="mbx-badge-mute">Scopes</span>
</div>
</div>
<div style="padding:14px 16px;display:flex;flex-direction:column;gap:6px">
@foreach([
['mailboxes:read', 'Mailboxen lesen'],
['mailboxes:write', 'Mailboxen erstellen / ändern / löschen'],
['aliases:read', 'Aliases lesen'],
['aliases:write', 'Aliases erstellen / löschen'],
['domains:read', 'Domains lesen'],
['domains:write', 'Domains erstellen / löschen'],
] as [$scope, $desc])
<div style="display:flex;align-items:baseline;gap:8px">
<code style="font-family:monospace;font-size:10px;padding:1px 6px;background:var(--mw-bg4);border:1px solid var(--mw-b2);border-radius:4px;color:var(--mw-t3);flex-shrink:0;white-space:nowrap">{{ $scope }}</code>
<span style="font-size:11px;color:var(--mw-t4)">{{ $desc }}</span>
</div>
@endforeach
</div>
</div>
{{-- Response codes --}}
<div class="mbx-section">
<div class="mbx-domain-head">
<div class="mbx-domain-info">
<span class="mbx-badge-mute">Status-Codes</span>
</div>
</div>
<div style="padding:14px 16px;display:flex;flex-direction:column;gap:6px">
@foreach([
['200', 'var(--mw-t3)', 'OK — Abfrage erfolgreich'],
['201', '#34d399', 'Created — Ressource erstellt'],
['204', 'var(--mw-t3)', 'No Content — Gelöscht'],
['401', '#f87171', 'Unauthorized — Token fehlt'],
['403', '#f87171', 'Forbidden — Scope fehlt'],
['404', '#fb923c', 'Not Found — nicht gefunden'],
['422', '#fb923c', 'Unprocessable — Validierung'],
] as [$code, $color, $desc])
<div style="display:flex;align-items:baseline;gap:8px">
<code style="font-family:monospace;font-size:10px;padding:1px 6px;background:var(--mw-bg4);border:1px solid var(--mw-b2);border-radius:4px;color:{{ $color }};flex-shrink:0;font-weight:600">{{ $code }}</code>
<span style="font-size:11px;color:var(--mw-t4)">{{ $desc }}</span>
</div>
@endforeach
</div>
</div>
</div>
</div>
</div>