111 lines
7.2 KiB
PHP
111 lines
7.2 KiB
PHP
<?php
|
||
require_once __DIR__ . '/../includes/bootstrap.php';
|
||
require_once __DIR__ . '/_ui.php';
|
||
$staff = require_admin();
|
||
$pdo = get_db();
|
||
|
||
$errors = [];
|
||
$newKey = $_SESSION['new_api_key'] ?? null; // wird nur einmal angezeigt
|
||
unset($_SESSION['new_api_key']);
|
||
|
||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||
verify_csrf();
|
||
$action = $_POST['action'] ?? '';
|
||
|
||
if ($action === 'create') {
|
||
try {
|
||
$created = api_key_create($pdo, (string) ($_POST['name'] ?? ''), !empty($_POST['can_create']), !empty($_POST['can_read']), (string) ($_POST['ip_whitelist'] ?? ''), (int) $staff['id']);
|
||
$_SESSION['new_api_key'] = ['name' => trim($_POST['name']), 'key' => $created['key']];
|
||
audit_log($staff['id'], 'API-Schlüssel erstellt: ' . trim($_POST['name']));
|
||
redirect('/staff/api_keys.php');
|
||
} catch (AdminModelException $e) {
|
||
$errors[] = $e->getMessage();
|
||
}
|
||
}
|
||
if ($action === 'toggle') {
|
||
$pdo->prepare('UPDATE api_keys SET is_active = 1 - is_active WHERE id = ?')->execute([(int) $_POST['id']]);
|
||
audit_log($staff['id'], 'API-Schlüssel umgeschaltet: #' . (int) $_POST['id']);
|
||
redirect('/staff/api_keys.php');
|
||
}
|
||
if ($action === 'delete') {
|
||
$pdo->prepare('DELETE FROM api_keys WHERE id = ?')->execute([(int) $_POST['id']]);
|
||
audit_log($staff['id'], 'API-Schlüssel gelöscht: #' . (int) $_POST['id']);
|
||
flash('success', 'Schlüssel gelöscht. Systeme, die ihn nutzen, werden ab sofort abgewiesen.');
|
||
redirect('/staff/api_keys.php');
|
||
}
|
||
}
|
||
|
||
$keys = $pdo->query('SELECT k.*, s.name AS creator FROM api_keys k LEFT JOIN staff s ON s.id = k.created_by ORDER BY k.created_at DESC')->fetchAll();
|
||
$base = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https://' : 'http://') . ($_SERVER['HTTP_HOST'] ?? 'ihre-domain.de');
|
||
|
||
$pageTitle = 'API-Schlüssel';
|
||
$activePage = 'api_keys';
|
||
require __DIR__ . '/_header.php';
|
||
?>
|
||
|
||
<?php foreach ($errors as $err): ?><div class="flash error"><?= e($err) ?></div><?php endforeach; ?>
|
||
|
||
<?php if ($newKey): ?>
|
||
<div class="flash success">
|
||
<strong>Schlüssel „<?= e($newKey['name']) ?>“ wurde erstellt.</strong> Kopiere ihn jetzt – aus Sicherheitsgründen wird er nie wieder angezeigt:
|
||
<div style="margin-top:8px"><code style="font-size:1rem;user-select:all;padding:6px 10px;background:rgba(0,0,0,.08);border-radius:6px;display:inline-block"><?= e($newKey['key']) ?></code></div>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<div style="display:flex;justify-content:space-between;align-items:center;gap:12px">
|
||
<h1 style="margin:0">API-Schlüssel</h1>
|
||
</div>
|
||
<p class="muted">Mit einem Schlüssel können Fremdsysteme (Website-Formular, Shop, Monitoring …) Tickets über die öffentliche API anlegen oder deren Status abfragen. Ereignisse an andere Systeme meldest du über <a href="/staff/webhooks.php">Webhooks</a>.</p>
|
||
|
||
<div class="card">
|
||
<table>
|
||
<thead><tr><th>Name</th><th>Schlüssel</th><th>Rechte</th><th>Erlaubte IPs</th><th>Status</th><th>Zuletzt genutzt</th><th>Erstellt</th><th></th></tr></thead>
|
||
<tbody>
|
||
<?php foreach ($keys as $k): ?>
|
||
<tr>
|
||
<td><strong><?= e($k['name']) ?></strong><div class="muted" style="font-size:.76rem"><?= e($k['creator'] ?? '') ?></div></td>
|
||
<td><code><?= e($k['key_prefix']) ?>…</code></td>
|
||
<td style="font-size:.85rem"><?= $k['can_create_tickets'] ? 'Tickets anlegen' : '' ?><?= $k['can_create_tickets'] && $k['can_read_tickets'] ? '<br>' : '' ?><?= $k['can_read_tickets'] ? 'Tickets lesen' : '' ?></td>
|
||
<td class="muted" style="font-size:.82rem"><?= e($k['ip_whitelist'] ?: 'alle') ?></td>
|
||
<td><strong><?= $k['is_active'] ? 'Aktiv' : 'Deaktiviert' ?></strong></td>
|
||
<td class="muted" style="font-size:.82rem"><?= e(cron_last_run_label($k['last_used_at'] ?? null)) ?></td>
|
||
<td class="muted" style="font-size:.82rem"><?= e(ui_date($k['created_at'] ?? null)) ?></td>
|
||
<td style="white-space:nowrap">
|
||
<form method="post" style="display:inline"><?= csrf_field() ?><input type="hidden" name="action" value="toggle"><input type="hidden" name="id" value="<?= (int) $k['id'] ?>">
|
||
<button type="submit" style="background:none;border:none;color:var(--primary);cursor:pointer;padding:0;font-size:.9rem"><?= $k['is_active'] ? 'Deaktivieren' : 'Aktivieren' ?></button></form> ·
|
||
<form method="post" style="display:inline" onsubmit="return confirm('Schlüssel endgültig löschen?')"><?= csrf_field() ?><input type="hidden" name="action" value="delete"><input type="hidden" name="id" value="<?= (int) $k['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 (!$keys): ?><tr><td colspan="8" class="muted">Noch keine API-Schlüssel angelegt.</td></tr><?php endif; ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3 style="margin-top:0">Neuen Schlüssel erstellen</h3>
|
||
<form method="post" autocomplete="off">
|
||
<?= csrf_field() ?>
|
||
<input type="hidden" name="action" value="create">
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;max-width:760px">
|
||
<div><label for="name">Name *</label><input type="text" id="name" name="name" placeholder="z. B. Website-Kontaktformular" required></div>
|
||
<div><label for="ip_whitelist">Erlaubte IP-Adressen (optional)</label><input type="text" id="ip_whitelist" name="ip_whitelist" placeholder="leer = von überall; sonst kommagetrennt"></div>
|
||
</div>
|
||
<label style="display:flex;align-items:center;gap:8px;font-weight:400;margin-top:12px"><input type="checkbox" name="can_create" value="1" checked style="width:auto"> Darf Tickets anlegen</label>
|
||
<label style="display:flex;align-items:center;gap:8px;font-weight:400"><input type="checkbox" name="can_read" value="1" style="width:auto"> Darf Ticketstatus lesen (mit Ticketnummer + E-Mail des Anfragenden)</label>
|
||
<div class="btn-row"><button type="submit" class="btn">Schlüssel erstellen</button></div>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h3 style="margin-top:0">So nutzt du die API</h3>
|
||
<p class="muted" style="margin-top:0">Ticket anlegen (JSON). Antwort: <code>201</code> mit der Ticketnummer. Höchstens 60 Tickets pro Minute.</p>
|
||
<pre style="background:var(--panel2,#f1f4f7);padding:12px;border-radius:8px;overflow:auto;font-size:.82rem"><?= e("curl -X POST {$base}/api/public/v1/tickets.php \\\n -H \"X-API-Key: dh_…\" -H \"Content-Type: application/json\" \\\n -d '{\"name\":\"Max Muster\",\"email\":\"max@example.de\",\"subject\":\"Frage zur Rechnung\",\"message\":\"Hallo …\",\"helpTopicId\":1}'") ?></pre>
|
||
<p class="muted">Status abfragen:</p>
|
||
<pre style="background:var(--panel2,#f1f4f7);padding:12px;border-radius:8px;overflow:auto;font-size:.82rem"><?= e("curl \"{$base}/api/public/v1/tickets.php?number=240915-0012&email=max@example.de\" -H \"X-API-Key: dh_…\"") ?></pre>
|
||
<p class="muted" style="font-size:.82rem;margin-bottom:0">Optionale Felder beim Anlegen: <code>departmentId</code>, <code>priorityId</code>, <code>helpTopicId</code>. Ticket-Regeln und SLA-Pläne gelten wie bei jedem anderen Ticket.</p>
|
||
</div>
|
||
|
||
<?php require __DIR__ . '/_footer.php'; ?>
|