305 lines
18 KiB
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 <token>
|
|
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>
|