161 lines
9.3 KiB
PHP
161 lines
9.3 KiB
PHP
<?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'; ?>
|