Files
deskhub/DeskHub-Webseite/staff/webhooks.php
T
2026-10-16 19:12:24 +00:00

161 lines
9.3 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
require_once __DIR__ . '/../includes/bootstrap.php';
require_once __DIR__ . '/_ui.php';
$staff = require_admin();
$pdo = get_db();
$errors = [];
$eventLabels = webhook_event_labels();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
verify_csrf();
$action = $_POST['action'] ?? '';
if ($action === 'create' || $action === 'update') {
$id = $action === 'update' ? (int) ($_POST['id'] ?? 0) : 0;
try {
webhook_save($pdo, $id, [
'name' => $_POST['name'] ?? '', 'url' => $_POST['url'] ?? '', 'secret' => $_POST['secret'] ?? '',
'is_active' => ($_POST['status'] ?? 'active') === 'active', 'events' => $_POST['events'] ?? [],
]);
audit_log($staff['id'], ($action === 'create' ? 'Webhook erstellt: ' : 'Webhook aktualisiert: ') . trim($_POST['name'] ?? ''));
flash('success', $action === 'create' ? 'Webhook erstellt.' : 'Änderungen gespeichert.');
redirect('/staff/webhooks.php');
} catch (AdminModelException $e) {
$errors[] = $e->getMessage();
}
}
if ($action === 'delete') {
$pdo->prepare('DELETE FROM webhooks WHERE id = ?')->execute([(int) $_POST['id']]);
audit_log($staff['id'], 'Webhook gelöscht: #' . (int) $_POST['id']);
flash('success', 'Webhook gelöscht.');
redirect('/staff/webhooks.php');
}
if ($action === 'test') {
$hook = $pdo->prepare('SELECT * FROM webhooks WHERE id = ?');
$hook->execute([(int) $_POST['id']]);
if ($row = $hook->fetch()) {
$payload = ['event' => 'test', 'occurredAt' => gmdate('c'), 'message' => 'Test-Ereignis von DeskHub'];
[$status, $error] = webhook_deliver($row, $payload);
$error === null ? flash('success', "Test erfolgreich zugestellt (HTTP {$status}).") : flash('error', 'Test fehlgeschlagen: ' . $error);
}
redirect('/staff/webhooks.php?edit=' . (int) $_POST['id']);
}
if ($action === 'reactivate') {
$pdo->prepare('UPDATE webhooks SET is_active = 1, fail_count = 0 WHERE id = ?')->execute([(int) $_POST['id']]);
redirect('/staff/webhooks.php');
}
}
$isNew = isset($_GET['new']);
$editId = (int) ($_GET['edit'] ?? 0);
$showForm = $isNew || $editId || ($errors && in_array($_POST['action'] ?? '', ['create', 'update'], true));
$hooks = $pdo->query('SELECT * FROM webhooks ORDER BY name')->fetchAll();
$editHook = null;
if ($editId) {
$stmt = $pdo->prepare('SELECT * FROM webhooks WHERE id = ?');
$stmt->execute([$editId]);
$editHook = $stmt->fetch() ?: null;
}
$pageTitle = 'Webhooks';
$activePage = 'webhooks';
require __DIR__ . '/_header.php';
?>
<?php foreach ($errors as $err): ?><div class="flash error"><?= e($err) ?></div><?php endforeach; ?>
<?php if ($showForm): ?>
<?php
$f = fn(string $k, $default = '') => $_POST[$k] ?? $default;
$v = fn(string $k, string $col, $default = '') => $f($k, $editHook[$col] ?? $default);
$curActive = $_POST ? (($_POST['status'] ?? 'active') === 'active') : (bool) ($editHook['is_active'] ?? 1);
$curEvents = $_POST ? (array) ($_POST['events'] ?? []) : ($editHook ? explode(',', $editHook['events']) : array_keys($eventLabels));
?>
<h1>
<?= $editHook ? 'Webhook aktualisieren' : 'Neuer Webhook' ?>
<?php if ($editHook): ?><span class="muted" style="font-size:1rem;font-weight:400">— <?= e($editHook['name']) ?></span><?php endif; ?>
</h1>
<form method="post" class="card" style="padding:0" autocomplete="off">
<?= csrf_field() ?>
<input type="hidden" name="action" value="<?= $editHook ? 'update' : 'create' ?>">
<?php if ($editHook): ?><input type="hidden" name="id" value="<?= (int) $editHook['id'] ?>"><?php endif; ?>
<?php ui_tab_bar(['hook' => 'Webhook', 'events' => 'Ereignisse']); ?>
<div class="tab-pane" data-pane="hook" style="padding:16px 20px">
<h3 style="margin-top:0">Ziel</h3>
<label for="name">Name *</label>
<input type="text" id="name" name="name" value="<?= e($v('name', 'name')) ?>" placeholder="z. B. Slack-Benachrichtigung" required>
<label for="url">URL *</label>
<input type="url" id="url" name="url" value="<?= e($v('url', 'url')) ?>" placeholder="https://example.de/hooks/deskhub" required>
<label for="secret">Geheimnis zum Signieren</label>
<input type="password" id="secret" name="secret" placeholder="<?= $editHook ? '(unverändert)' : 'leer = wird automatisch erzeugt' ?>" autocomplete="new-password">
<p class="muted" style="font-size:.8rem">Jede Zustellung enthält den Header <code>X-DeskHub-Signature: sha256=…</code> (HMAC-SHA256 des Körpers mit diesem Geheimnis), damit dein System die Herkunft prüfen kann.</p>
<?php ui_status_radios($curActive); ?>
<?php if ($editHook): ?>
<p class="muted" style="font-size:.82rem">
Letzte Zustellung: <?= e(cron_last_run_label($editHook['last_delivery_at'] ?? null)) ?>
<?= $editHook['last_status'] ? ' · HTTP ' . (int) $editHook['last_status'] : '' ?>
<?= $editHook['last_error'] ? ' · <span style="color:#dc2626">' . e($editHook['last_error']) . '</span>' : '' ?>
<?= (int) $editHook['fail_count'] > 0 ? ' · ' . (int) $editHook['fail_count'] . ' Fehler in Folge (nach 10 wird der Webhook automatisch deaktiviert)' : '' ?>
</p>
<?php endif; ?>
</div>
<div class="tab-pane" data-pane="events" style="padding:16px 20px;display:none">
<h3 style="margin-top:0">Ereignisse</h3>
<?php foreach ($eventLabels as $key => $label): ?>
<label style="display:flex;align-items:center;gap:8px;font-weight:400;margin:6px 0">
<input type="checkbox" name="events[]" value="<?= e($key) ?>" style="width:auto" <?= in_array($key, $curEvents, true) ? 'checked' : '' ?>>
<?= e($label) ?> <code class="muted"><?= e($key) ?></code>
</label>
<?php endforeach; ?>
<p class="muted" style="font-size:.82rem;margin-top:12px">Der Körper ist JSON mit <code>event</code>, <code>occurredAt</code> und den Ticketdaten (Nummer, Betreff, Status, Priorität, Abteilung, Anfragender).</p>
</div>
<div class="btn-row" style="padding:0 20px 18px">
<button type="submit" class="btn"><?= $editHook ? 'Änderungen speichern' : 'Erstellen' ?></button>
<a href="/staff/webhooks.php" class="btn secondary">Abbrechen</a>
</div>
</form>
<?php if ($editHook): ?>
<form method="post" style="margin-top:10px"><?= csrf_field() ?><input type="hidden" name="action" value="test"><input type="hidden" name="id" value="<?= (int) $editHook['id'] ?>">
<button type="submit" class="btn secondary">Test-Ereignis senden</button> <span class="muted" style="font-size:.8rem">(verwendet die gespeicherten Werte)</span></form>
<?php endif; ?>
<?php ui_tab_assets('hook'); ?>
<?php else: ?>
<div style="display:flex;justify-content:space-between;align-items:center;gap:12px">
<h1 style="margin:0">Webhooks</h1>
<a href="/staff/webhooks.php?new=1" class="btn">Neuen Webhook hinzufügen</a>
</div>
<p class="muted">DeskHub meldet Ereignisse (Ticket erstellt, beantwortet, Status geändert, zugewiesen) per HTTP-POST an die hinterlegten URLs – z. B. für Chat-Benachrichtigungen oder Ihr eigenes System. Tickets von außen anlegen: <a href="/staff/api_keys.php">API-Schlüssel</a>.</p>
<div class="card">
<table>
<thead><tr><th>Name</th><th>URL</th><th>Ereignisse</th><th>Status</th><th>Letzte Zustellung</th><th></th></tr></thead>
<tbody>
<?php foreach ($hooks as $h): ?>
<tr>
<td><a href="/staff/webhooks.php?edit=<?= (int) $h['id'] ?>"><?= e($h['name']) ?></a></td>
<td class="muted" style="font-size:.82rem;word-break:break-all"><?= e($h['url']) ?></td>
<td style="font-size:.8rem"><?= e(implode(', ', array_map(fn($k) => $eventLabels[$k] ?? $k, explode(',', $h['events'])))) ?></td>
<td><strong><?= $h['is_active'] ? 'Aktiv' : 'Deaktiviert' ?></strong><?= !$h['is_active'] && (int) $h['fail_count'] >= 10 ? '<div class="muted" style="font-size:.74rem">wegen Fehlern abgeschaltet</div>' : '' ?></td>
<td class="muted" style="font-size:.82rem"><?= e(cron_last_run_label($h['last_delivery_at'] ?? null)) ?><?= $h['last_error'] ? '<div style="color:#dc2626">' . e($h['last_error']) . '</div>' : ($h['last_status'] ? ' · HTTP ' . (int) $h['last_status'] : '') ?></td>
<td style="white-space:nowrap">
<a href="/staff/webhooks.php?edit=<?= (int) $h['id'] ?>">Bearbeiten</a>
<?php if (!$h['is_active']): ?> · <form method="post" style="display:inline"><?= csrf_field() ?><input type="hidden" name="action" value="reactivate"><input type="hidden" name="id" value="<?= (int) $h['id'] ?>"><button type="submit" style="background:none;border:none;color:var(--primary);cursor:pointer;padding:0;font-size:.9rem">Aktivieren</button></form><?php endif; ?> ·
<form method="post" style="display:inline" onsubmit="return confirm('Webhook löschen?')"><?= csrf_field() ?><input type="hidden" name="action" value="delete"><input type="hidden" name="id" value="<?= (int) $h['id'] ?>">
<button type="submit" style="background:none;border:none;color:#dc2626;cursor:pointer;padding:0;font-size:.9rem">Löschen</button></form>
</td>
</tr>
<?php endforeach; ?>
<?php if (!$hooks): ?><tr><td colspan="6" class="muted">Noch keine Webhooks angelegt.</td></tr><?php endif; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<?php require __DIR__ . '/_footer.php'; ?>