234 lines
15 KiB
PHP
234 lines
15 KiB
PHP
<x-slot:breadcrumbParent>System</x-slot:breadcrumbParent>
|
|
<x-slot:breadcrumb>Webhooks</x-slot:breadcrumb>
|
|
|
|
<div wire:poll.10s
|
|
x-on:webhook-saved.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="M2 8a6 6 0 1 0 12 0A6 6 0 0 0 2 8Z" stroke="currentColor" stroke-width="1.3"/>
|
|
<path d="M8 5v3l2 2" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
|
<path d="M1 3l2.5 1.5M15 3l-2.5 1.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
|
|
</svg>
|
|
Webhooks
|
|
<span class="mbx-total-badge">{{ $webhooks->count() }}</span>
|
|
</div>
|
|
<div class="mbx-page-actions">
|
|
<button wire:click="$dispatch('openModal',{component:'ui.system.modal.webhook-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>
|
|
Webhook
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mw-wh-layout">
|
|
|
|
{{-- ═══ Left ═══ --}}
|
|
<div class="mbx-sections">
|
|
|
|
<div class="mbx-section">
|
|
<div class="mbx-domain-head">
|
|
<div class="mbx-domain-info">
|
|
<span class="mbx-badge-mute">Konfigurierte Webhooks</span>
|
|
</div>
|
|
</div>
|
|
|
|
@if($webhooks->isEmpty())
|
|
<div style="padding:40px 16px;text-align:center">
|
|
<svg width="30" height="30" viewBox="0 0 16 16" fill="none" style="color:var(--mw-t4);margin:0 auto 12px;display:block">
|
|
<path d="M2 8a6 6 0 1 0 12 0A6 6 0 0 0 2 8Z" stroke="currentColor" stroke-width="1.3"/>
|
|
<path d="M8 5v3l2 2" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
|
</svg>
|
|
<div style="font-size:12.5px;color:var(--mw-t2);font-weight:500;margin-bottom:4px">Keine Webhooks konfiguriert</div>
|
|
<div style="font-size:11.5px;color:var(--mw-t4)">Verbinde externe Systeme — sie werden bei Events automatisch benachrichtigt.</div>
|
|
</div>
|
|
@else
|
|
|
|
{{-- Header --}}
|
|
<div class="mw-whl-head">
|
|
<span>Webhook</span>
|
|
<span>Events</span>
|
|
<span>Status</span>
|
|
<span>HTTP</span>
|
|
<span>Aktionen</span>
|
|
</div>
|
|
|
|
{{-- Rows --}}
|
|
@foreach($webhooks as $wh)
|
|
@php
|
|
$evVisible = array_slice($wh->events, 0, 2);
|
|
$evRest = count($wh->events) - 2;
|
|
@endphp
|
|
<div class="mw-whl-row">
|
|
{{-- Webhook name + url --}}
|
|
<div class="mw-whl-webhook" style="display:flex;align-items:center;gap:8px;min-width:0">
|
|
<div style="width:8px;height:8px;border-radius:50%;flex-shrink:0;background:{{ $wh->is_active ? '#34d399' : 'var(--mw-t4)' }};box-shadow:{{ $wh->is_active ? '0 0 6px rgba(52,211,153,.5)' : 'none' }}"></div>
|
|
<div style="min-width:0">
|
|
<div style="font-size:12.5px;font-weight:500;color:var(--mw-t1);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">
|
|
{{ $wh->name }}
|
|
<span class="mw-whl-status-sm">
|
|
@if($wh->is_active)
|
|
<span class="mbx-badge-ok" style="font-size:9px;margin-left:4px">Aktiv</span>
|
|
@else
|
|
<span class="mbx-badge-warn" style="font-size:9px;margin-left:4px">Pausiert</span>
|
|
@endif
|
|
</span>
|
|
<span class="mw-whl-http-sm" style="font-family:monospace;font-size:9.5px;margin-left:4px">
|
|
@if($wh->last_status !== null)
|
|
@if($wh->last_status >= 200 && $wh->last_status < 300)
|
|
<span style="color:#34d399;font-weight:600">{{ $wh->last_status }}</span>
|
|
@else
|
|
<span style="color:#f87171;font-weight:600">{{ $wh->last_status === 0 ? 'T/O' : $wh->last_status }}</span>
|
|
@endif
|
|
@endif
|
|
</span>
|
|
</div>
|
|
<div style="font-family:monospace;font-size:10px;color:var(--mw-t5);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">{{ $wh->url }}</div>
|
|
</div>
|
|
</div>
|
|
{{-- Events --}}
|
|
<div class="mw-whl-events" style="display:flex;flex-wrap:wrap;gap:3px;align-items:center">
|
|
@foreach($evVisible as $ev)
|
|
<span style="font-size:9.5px;padding:1px 5px;background:var(--mw-bg4);border:1px solid var(--mw-b2);border-radius:4px;color:var(--mw-t4);font-family:monospace;white-space:nowrap">{{ $ev }}</span>
|
|
@endforeach
|
|
@if($evRest > 0)
|
|
<span style="font-size:9.5px;padding:1px 6px;background:var(--mw-bg4);border:1px solid var(--mw-b2);border-radius:4px;color:var(--mw-t4);white-space:nowrap">+{{ $evRest }}</span>
|
|
@endif
|
|
</div>
|
|
{{-- Status --}}
|
|
<div class="mw-whl-status">
|
|
@if($wh->is_active)
|
|
<span class="mbx-badge-ok">Aktiv</span>
|
|
@else
|
|
<span class="mbx-badge-warn">Pausiert</span>
|
|
@endif
|
|
</div>
|
|
{{-- HTTP --}}
|
|
<div class="mw-whl-http">
|
|
@if($wh->last_status === null)
|
|
<span style="font-size:11px;color:var(--mw-t4)">—</span>
|
|
@elseif($wh->last_status >= 200 && $wh->last_status < 300)
|
|
<span style="font-family:monospace;font-size:11px;color:#34d399;font-weight:600">{{ $wh->last_status }}</span>
|
|
@elseif($wh->last_status === 0)
|
|
<span style="font-family:monospace;font-size:11px;color:#f87171;font-weight:600">Timeout</span>
|
|
@else
|
|
<span style="font-family:monospace;font-size:11px;color:#f87171;font-weight:600">{{ $wh->last_status }}</span>
|
|
@endif
|
|
</div>
|
|
{{-- Actions --}}
|
|
<div class="mw-whl-actions">
|
|
<div class="mbx-actions">
|
|
<button wire:click="$dispatch('openModal',{component:'ui.system.modal.webhook-edit-modal',arguments:{webhookId:{{ $wh->id }}}})"
|
|
class="mbx-act-btn" title="Bearbeiten">
|
|
<svg width="12" height="12" viewBox="0 0 13 13" fill="none"><path d="M9 2l2 2-7 7H2V9l7-7Z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/></svg>
|
|
</button>
|
|
<button wire:click="toggleActive({{ $wh->id }})"
|
|
class="mbx-act-btn" title="{{ $wh->is_active ? 'Pausieren' : 'Aktivieren' }}">
|
|
@if($wh->is_active)
|
|
<svg width="12" height="12" viewBox="0 0 13 13" fill="none"><rect x="3" y="2" width="2.5" height="9" rx="1" stroke="currentColor" stroke-width="1.2"/><rect x="7.5" y="2" width="2.5" height="9" rx="1" stroke="currentColor" stroke-width="1.2"/></svg>
|
|
@else
|
|
<svg width="12" height="12" viewBox="0 0 13 13" fill="none"><path d="M4 2.5l7 4-7 4V2.5Z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/></svg>
|
|
@endif
|
|
</button>
|
|
<button wire:click="$dispatch('openModal',{component:'ui.system.modal.webhook-delete-modal',arguments:{webhookId:{{ $wh->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>
|
|
</div>
|
|
@endforeach
|
|
|
|
@endif
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{{-- ═══ Right: Docs ═══ --}}
|
|
<div class="mbx-sections">
|
|
|
|
{{-- Payload --}}
|
|
<div class="mbx-section">
|
|
<div class="mbx-domain-head">
|
|
<div class="mbx-domain-info">
|
|
<span class="mbx-badge-mute">Payload-Format</span>
|
|
</div>
|
|
</div>
|
|
<div style="padding:14px 16px;display:flex;flex-direction:column;gap:10px">
|
|
<div style="font-size:11.5px;color:var(--mw-t3)">Mailwolt sendet einen HTTP <code style="font-family:monospace;background:var(--mw-bg3);padding:1px 4px;border-radius:3px">POST</code> mit JSON-Body:</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">{
|
|
"event": "mailbox.created",
|
|
"timestamp": "2026-04-21T12:00:00Z",
|
|
"payload": { ... }
|
|
}</pre>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Signatur --}}
|
|
<div class="mbx-section">
|
|
<div class="mbx-domain-head">
|
|
<div class="mbx-domain-info">
|
|
<span class="mbx-badge-mute">Signatur prüfen</span>
|
|
</div>
|
|
</div>
|
|
<div style="padding:14px 16px;display:flex;flex-direction:column;gap:10px">
|
|
<div style="font-size:11.5px;color:var(--mw-t3)">Jeder Request enthält den Header:</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">X-Mailwolt-Sig: sha256=<hmac>
|
|
X-Mailwolt-Event: mailbox.created</pre>
|
|
<div style="font-size:11px;color:var(--mw-t4)">HMAC-SHA256 über den rohen Request-Body mit deinem Webhook-Secret.</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"># PHP
|
|
hash_hmac('sha256', $body, $secret);
|
|
|
|
# Python
|
|
hmac.new(secret, body, sha256).hexdigest()</pre>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Events --}}
|
|
<div class="mbx-section">
|
|
<div class="mbx-domain-head">
|
|
<div class="mbx-domain-info">
|
|
<span class="mbx-badge-mute">Verfügbare Events</span>
|
|
</div>
|
|
</div>
|
|
<div style="padding:14px 16px;display:flex;flex-direction:column;gap:6px">
|
|
@foreach($allEvents as $ev => $label)
|
|
<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">{{ $ev }}</code>
|
|
<span style="font-size:11px;color:var(--mw-t4)">{{ $label }}</span>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Retry --}}
|
|
<div class="mbx-section">
|
|
<div class="mbx-domain-head">
|
|
<div class="mbx-domain-info">
|
|
<span class="mbx-badge-mute">Verhalten</span>
|
|
</div>
|
|
</div>
|
|
<div style="padding:14px 16px;display:flex;flex-direction:column;gap:8px;font-size:11.5px;color:var(--mw-t3)">
|
|
<div style="display:flex;gap:8px">
|
|
<svg width="13" height="13" viewBox="0 0 13 13" fill="none" style="flex-shrink:0;margin-top:1px;color:var(--mw-t4)"><circle cx="6.5" cy="6.5" r="5.5" stroke="currentColor" stroke-width="1.2"/><path d="M4.5 6.5l1.5 1.5 2.5-3" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
<span>Timeout: <strong style="color:var(--mw-t2)">8 Sekunden</strong></span>
|
|
</div>
|
|
<div style="display:flex;gap:8px">
|
|
<svg width="13" height="13" viewBox="0 0 13 13" fill="none" style="flex-shrink:0;margin-top:1px;color:var(--mw-t4)"><circle cx="6.5" cy="6.5" r="5.5" stroke="currentColor" stroke-width="1.2"/><path d="M4.5 6.5l1.5 1.5 2.5-3" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
<span>Kein automatischer Retry — HTTP-Status wird gespeichert</span>
|
|
</div>
|
|
<div style="display:flex;gap:8px">
|
|
<svg width="13" height="13" viewBox="0 0 13 13" fill="none" style="flex-shrink:0;margin-top:1px;color:var(--mw-t4)"><circle cx="6.5" cy="6.5" r="5.5" stroke="currentColor" stroke-width="1.2"/><path d="M4.5 6.5l1.5 1.5 2.5-3" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
<span>Antwort-Code <strong style="color:#34d399">2xx</strong> = Erfolg, alles andere wird als Fehler markiert</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|