Files
businesscard/www/admin.php
2026-02-13 23:05:18 +01:00

889 lines
28 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 '_sql.php';
require '_func.php';
require '_user.php';
session_start();
$ip = $_SERVER['REMOTE_ADDR'];
const MAX_UPLOAD_FILE_SIZE_MB = 32;
const MAX_UPLOAD_FILE_SIZE_BYTES = MAX_UPLOAD_FILE_SIZE_MB * 1024 * 1024;
ini_set('upload_max_filesize', MAX_UPLOAD_FILE_SIZE_MB . 'M');
ini_set('post_max_size', (MAX_UPLOAD_FILE_SIZE_MB + 1) . 'M');
/* ─────────────────────────────
Security
───────────────────────────── */
if (isIpLocked($ip, $sql)) {
http_response_code(403);
exit('Zu viele Fehlversuche.');
}
/* ─────────────────────────────
Login
───────────────────────────── */
if (!($_SESSION['is_admin'] ?? false)) {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$u = $_POST['username'] ?? '';
$p = $_POST['password'] ?? '';
if (
$u !== $admin_user ||
$p !== $admin_password
) {
registerFailedLogin($ip, $sql);
$error = 'Login fehlgeschlagen';
} else {
clearLoginAttempts($ip, $sql);
$_SESSION['is_admin'] = true;
header('Location: admin.php');
exit;
}
}
?>
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Admin Login</title>
<style>
body {
font-family: system-ui, sans-serif;
background:#0f172a;
color:#e5e7eb;
display:flex;
justify-content:center;
align-items:center;
height:100vh;
}
form {
background:#020617;
padding:2rem;
border-radius:12px;
width:320px;
}
input,button {
width:100%;
padding:.6rem;
margin-top:.75rem;
}
button {
background:#38bdf8;
border:0;
cursor:pointer;
}
.err { color:#f87171; margin-top:.5rem; }
</style>
</head>
<body>
<form method="post">
<h2>Admin Login</h2>
<input name="username" placeholder="Benutzername" required>
<input name="password" type="password" placeholder="Passwort" required>
<button>Login</button>
<?php if (!empty($error)): ?>
<div class="err"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
</form>
</body>
</html>
<?php
exit;
}
/* ─────────────────────────────
Action Routing
───────────────────────────── */
$action = $_GET['action'] ?? null;
/* ─────────────────────────────
UUID DIRECT ACCESS (?uuid=...)
Create flow if missing
───────────────────────────── */
if (!$action && isset($_GET['uuid'])) {
$uuid = $_GET['uuid'];
// Prüfen ob UUID existiert
$token = $sql->single(
"SELECT * FROM access_tokens WHERE uuid = ?",
"s",
[$uuid]
);
if ($token) {
// UUID existiert → weiter zum edit-Formular
$action = 'uuid_edit';
$_GET['uuid'] = $uuid;
} else {
// UUID existiert nicht → Initial-Form
$action = 'uuid_create_initial';
$_GET['uuid'] = $uuid;
}
}
/* ─────────────────────────────
CREATE IDENTITY
───────────────────────────── */
if ($action === 'identity_create') {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
if ($name !== '') {
$sql->set(
"INSERT INTO identities (name) VALUES (?)",
"s",
[$name]
);
header('Location: admin.php');
exit;
}
}
?>
<!doctype html>
<html><head><meta charset="utf-8"><title>Identität anlegen</title><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
</head>
<body>
<h1>Neue Identität</h1>
<form method="post">
<input name="name" placeholder="Name der Identität" required>
<button>Speichern</button>
</form>
<p><a href="admin.php">← zurück</a></p>
</body></html>
<?php
exit;
}
/* ─────────────────────────────
INITIAL UUID CREATION
───────────────────────────── */
if ($action === 'uuid_create_initial') {
$uuid = $_GET['uuid'];
// Alle Identitäten für Auswahl
$identities = $sql->get("SELECT * FROM identities ORDER BY name ASC");
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$identityId = (int)($_POST['identity_id'] ?? 0);
$notes = trim($_POST['notes'] ?? '');
if (!$identityId) {
$error = 'Bitte eine Identität auswählen.';
} else {
// UUID anlegen
$sql->set(
"INSERT INTO access_tokens (identity_id, uuid, notes) VALUES (?, ?, ?)",
"iss",
[$identityId, $uuid, $notes]
);
// Weiterleiten zum Bearbeitungsformular
header("Location: admin.php?action=uuid_edit&uuid=$uuid");
exit;
}
}
?>
<!doctype html>
<html>
<head><meta charset="utf-8"><title>Neue UUID anlegen</title><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
</head>
<body>
<h1>Neue UUID anlegen</h1>
<form method="post">
<label>Identität auswählen:
<select name="identity_id" required>
<option value="">-- bitte wählen --</option>
<?php foreach ($identities as $i): ?>
<option value="<?= $i['id'] ?>"><?= htmlspecialchars($i['name']) ?></option>
<?php endforeach; ?>
</select>
</label>
<br><br>
<label>Notiz (optional):<br>
<textarea name="notes" rows="3"></textarea>
</label>
<br><br>
<button>Speichern</button>
</form>
<?php if (!empty($error)): ?>
<p style="color:red"><?= htmlspecialchars($error) ?></p>
<?php endif; ?>
<p><a href="admin.php">← zurück zum Dashboard</a></p>
</body>
</html>
<?php
exit;
}
/* ─────────────────────────────
EDIT IDENTITY
───────────────────────────── */
if ($action === 'identity_edit') {
function renderValueFieldElement(string $type, string $value, string $name = 'value', string $id = null, array $extraAttrs = [], array $filesForIdentity = []): string
{
$safeValue = htmlspecialchars($value);
$idAttr = $id ? ' id="' . htmlspecialchars($id) . '"' : '';
$extraAttrString = '';
foreach ($extraAttrs as $attr => $attrVal) {
$extraAttrString .= ' ' . htmlspecialchars($attr) . '="' . htmlspecialchars($attrVal) . '"';
}
if ($type === 'file') {
$options = '<option value="">-- Datei wählen --</option>';
foreach ($filesForIdentity as $file) {
$fileId = (string)(int)$file['id'];
$selected = $fileId === $value ? ' selected' : '';
$options .= sprintf(
'<option value="%s"%s>%s</option>',
htmlspecialchars($fileId),
$selected,
htmlspecialchars($file['filename'])
);
}
return "<select name=\"" . htmlspecialchars($name) . "\" style=\"width:100%\"{$idAttr}{$extraAttrString}>{$options}</select>";
}
if ($type === 'multi') {
return "<textarea name=\"" . htmlspecialchars($name) . "\" rows=\"3\" style=\"width:100%\"{$idAttr}{$extraAttrString}>{$safeValue}</textarea>";
}
$inputType = $type === 'url' ? 'url' : 'text';
return "<input type=\"{$inputType}\" name=\"" . htmlspecialchars($name) . "\" value=\"{$safeValue}\" style=\"width:100%\"{$idAttr}{$extraAttrString}>";
}
$id = (int)($_GET['id'] ?? 0);
$identity = $sql->single(
"SELECT * FROM identities WHERE id = ?",
"i",
[$id]
);
if (!$identity) {
exit('Identität nicht gefunden');
}
$uploadErrorLabel = function (int $error): string {
return match ($error) {
UPLOAD_ERR_INI_SIZE => 'Datei überschreitet upload_max_filesize',
UPLOAD_ERR_FORM_SIZE => 'Datei überschreitet MAX_FILE_SIZE',
UPLOAD_ERR_PARTIAL => 'Datei wurde nur teilweise hochgeladen',
UPLOAD_ERR_NO_FILE => 'keine Datei ausgewählt',
UPLOAD_ERR_NO_TMP_DIR => 'temporäres Verzeichnis fehlt',
UPLOAD_ERR_CANT_WRITE => 'Datei konnte nicht geschrieben werden',
UPLOAD_ERR_EXTENSION => 'Upload durch eine Erweiterung abgebrochen',
default => 'unbekannter Upload-Fehler',
};
};
$fileUploadErrors = $_SESSION['fileUploadErrors'] ?? [];
unset($_SESSION['fileUploadErrors']);
$fileUploadSuccess = (int)($_SESSION['fileUploadSuccess'] ?? 0);
unset($_SESSION['fileUploadSuccess']);
$fileDeleteMessage = $_SESSION['fileDeleteMessage'] ?? '';
unset($_SESSION['fileDeleteMessage']);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$fileUploadErrors = [];
$fileUploadSuccess = 0;
$fileDeleteMessage = '';
if (isset($_POST['upload_files'])) {
$filesInput = $_FILES['files'] ?? null;
$hasSelection = false;
if ($filesInput) {
if (is_array($filesInput['name'])) {
foreach ($filesInput['name'] as $fileName) {
if (trim((string)$fileName) !== '') {
$hasSelection = true;
break;
}
}
} else {
$hasSelection = trim((string)$filesInput['name']) !== '';
}
}
if ($filesInput && $hasSelection) {
$uploadDir = __DIR__ . '/_files/';
if (!is_dir($uploadDir) && !mkdir($uploadDir, 0755, true) && !is_dir($uploadDir)) {
$fileUploadErrors[] = 'Upload-Verzeichnis kann nicht erstellt werden.';
} else {
$total = is_array($filesInput['name']) ? count($filesInput['name']) : 1;
$uploaded = 0;
$finfo = finfo_open(FILEINFO_MIME_TYPE);
for ($i = 0; $i < $total; $i++) {
$originalName = is_array($filesInput['name']) ? $filesInput['name'][$i] : $filesInput['name'];
$error = is_array($filesInput['error']) ? $filesInput['error'][$i] : $filesInput['error'];
$tmpName = is_array($filesInput['tmp_name']) ? $filesInput['tmp_name'][$i] : $filesInput['tmp_name'];
if ($error === UPLOAD_ERR_NO_FILE) {
continue;
}
$originalName = trim((string)$originalName);
if ($originalName === '') {
continue;
}
if ($error !== UPLOAD_ERR_OK) {
$fileUploadErrors[] = sprintf(
'Fehler beim Hochladen von %s: %s.',
$originalName,
$uploadErrorLabel($error)
);
continue;
}
if (!is_uploaded_file($tmpName)) {
$fileUploadErrors[] = sprintf('Datei %s konnte nicht verarbeitet werden.', $originalName);
continue;
}
$safeName = preg_replace('/[^A-Za-z0-9._-]/', '_', basename($originalName));
if ($safeName === '') {
$safeName = 'file';
}
$storedName = bin2hex(random_bytes(12)) . '_' . $safeName;
$destination = $uploadDir . $storedName;
if (!move_uploaded_file($tmpName, $destination)) {
$fileUploadErrors[] = sprintf('Speichern von %s fehlgeschlagen.', $originalName);
continue;
}
$mimeType = $finfo ? finfo_file($finfo, $destination) : 'application/octet-stream';
$sql->set(
"INSERT INTO files (identity_id, filename, stored_name, mime_type)
VALUES (?, ?, ?, ?)",
"isss",
[$id, $originalName, $storedName, $mimeType]
);
$uploaded++;
}
if ($finfo) {
finfo_close($finfo);
}
if ($uploaded > 0) {
$fileUploadSuccess = $uploaded;
}
}
} else {
$fileUploadErrors[] = 'Bitte wählen Sie mindestens eine Datei aus.';
}
}
if (isset($_POST['delete_file'])) {
$fileId = (int)($_POST['file_id'] ?? 0);
if ($fileId > 0) {
$file = $sql->single(
"SELECT stored_name, filename FROM files WHERE id = ? AND identity_id = ?",
"ii",
[$fileId, $id]
);
if ($file) {
$diskPath = __DIR__ . '/_files/' . $file['stored_name'];
if (is_file($diskPath)) {
@unlink($diskPath);
}
$sql->set(
"DELETE FROM files WHERE id = ?",
"i",
[$fileId]
);
$fileDeleteMessage = sprintf('Datei "%s" gelöscht.', $file['filename']);
} else {
$fileUploadErrors[] = 'Datei nicht gefunden oder gehört nicht zu dieser Identität.';
}
} else {
$fileUploadErrors[] = 'Ungültige Datei.';
}
}
// Identität umbenennen
if (isset($_POST['rename'])) {
$sql->set(
"UPDATE identities SET name = ? WHERE id = ?",
"si",
[trim($_POST['name']), $id]
);
}
// Neues Feld
if (isset($_POST['add_field'])) {
$sql->set(
"INSERT INTO identity_fields (identity_id, field_key, field_value, typ)
VALUES (?, ?, ?, ?)",
"isss",
[
$id,
trim($_POST['key']),
trim($_POST['value']),
$_POST['typ'] ?? 'single'
]
);
}
// Feld aktualisieren
if (isset($_POST['update_field'])) {
$sql->set(
"UPDATE identity_fields
SET field_key = ?, field_value = ?, typ = ?
WHERE id = ? AND identity_id = ?",
"sssii",
[
trim($_POST['key']),
trim($_POST['value']),
$_POST['typ'] ?? 'single',
(int)$_POST['field_id'],
$id
]
);
}
// Feld löschen
if (isset($_POST['delete_field'])) {
$sql->set(
"DELETE FROM identity_fields
WHERE id = ? AND identity_id = ?",
"ii",
[(int)$_POST['field_id'], $id]
);
}
$_SESSION['fileUploadErrors'] = $fileUploadErrors;
$_SESSION['fileUploadSuccess'] = $fileUploadSuccess;
$_SESSION['fileDeleteMessage'] = $fileDeleteMessage;
header("Location: admin.php?action=identity_edit&id=$id");
exit;
}
$fields = $sql->get(
"SELECT * FROM identity_fields WHERE identity_id = ? ORDER BY id ASC",
"i",
[$id]
);
$identityFiles = $sql->get(
"SELECT id, filename FROM files WHERE identity_id = ? ORDER BY uploaded_at DESC",
"i",
[$id]
);
if ($identityFiles === false) {
$identityFiles = [];
}
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Identität bearbeiten</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
</head>
<body>
<h1><?= htmlspecialchars($identity['name']) ?></h1>
<form method="post">
<input name="name" value="<?= htmlspecialchars($identity['name']) ?>">
<button name="rename">Umbenennen</button>
</form>
<h2>Felder</h2>
<table border="1" cellpadding="6" cellspacing="0">
<thead>
<tr>
<th>Key</th>
<th>Wert</th>
<th>Typ</th>
<th>Aktion</th>
</tr>
</thead>
<tbody>
<?php foreach ($fields as $f): ?>
<tr>
<form method="post">
<td>
<input name="key"
value="<?= htmlspecialchars($f['field_key']) ?>">
</td>
<td class="value-cell">
<div data-value-wrapper class="value-field-wrapper">
<?= renderValueFieldElement($f['typ'], $f['field_value'], 'value', null, [], $identityFiles) ?>
</div>
</td>
<td>
<select name="typ" data-typ-switch>
<option value="single" <?= $f['typ']==='single'?'selected':'' ?>>einzeilig</option>
<option value="multi" <?= $f['typ']==='multi'?'selected':'' ?>>mehrzeilig</option>
<option value="file" <?= $f['typ']==='file'?'selected':'' ?>>Datei</option>
<option value="url" <?= $f['typ']==='url'?'selected':'' ?>>URL</option>
</select>
</td>
<td>
<input type="hidden" name="field_id" value="<?= (int)$f['id'] ?>">
<button name="update_field">💾</button>
<button name="delete_field"
onclick="return confirm('Feld wirklich löschen?')">🗑</button>
</td>
</form>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<h3>Neues Feld</h3>
<form method="post">
<input name="key" placeholder="Feldname" required>
<div data-value-wrapper class="value-field-wrapper" data-placeholder="Wert">
<?= renderValueFieldElement('single', '', 'value', 'new-field-value', ['placeholder' => 'Wert'], $identityFiles) ?>
</div>
<select name="typ" data-typ-switch>
<option value="single">einzeilig</option>
<option value="multi">mehrzeilig</option>
<option value="file">Datei</option>
<option value="url">URL</option>
</select>
<button name="add_field"> Feld hinzufügen</button>
</form>
<h3>Dateien hochladen</h3>
<form method="post" enctype="multipart/form-data">
<input type="file" name="files[]" multiple>
<button name="upload_files">Hochladen</button>
</form>
<?php if ($fileUploadSuccess > 0): ?>
<p style="color:#22c55e; margin-top:.5rem;">
<?= $fileUploadSuccess ?> Datei<?= $fileUploadSuccess === 1 ? '' : 'en' ?> hochgeladen.
</p>
<?php endif; ?>
<?php if (!empty($fileUploadErrors)): ?>
<ul style="color:#f87171; margin-top:.5rem;">
<?php foreach ($fileUploadErrors as $error): ?>
<li><?= htmlspecialchars($error) ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<?php if (!empty($identityFiles)): ?>
<h3>Vorhandene Dateien</h3>
<?php if ($fileDeleteMessage !== ''): ?>
<p style="color:#22c55e; margin-top:.25rem;"><?= htmlspecialchars($fileDeleteMessage) ?></p>
<?php endif; ?>
<ul>
<?php foreach ($identityFiles as $file): ?>
<li>
<?= htmlspecialchars($file['filename']) ?> (ID <?= (int)$file['id'] ?>)
<form method="post" style="display:inline; margin-left:.75rem;">
<input type="hidden" name="file_id" value="<?= (int)$file['id'] ?>">
<button name="delete_file" type="submit"
onclick="return confirm('Datei wirklich löschen?')">Löschen</button>
</form>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<script>
document.addEventListener('DOMContentLoaded', function () {
const identityFiles = <?= json_encode(array_map(function ($file) {
return [
'id' => (int)$file['id'],
'filename' => $file['filename'],
];
}, $identityFiles), JSON_UNESCAPED_UNICODE | JSON_HEX_TAG) ?: '[]' ?>;
function createValueFieldElement(type, opts) {
opts = opts || {};
var element;
if (type === 'multi') {
element = document.createElement('textarea');
element.rows = 3;
} else if (type === 'file') {
element = document.createElement('select');
} else {
element = document.createElement('input');
element.type = type === 'url' ? 'url' : 'text';
}
element.name = opts.name || 'value';
element.style.width = '100%';
if (opts.id) {
element.id = opts.id;
}
if (type === 'file') {
var placeholder = opts.placeholder || '-- Datei wählen --';
var defaultOption = document.createElement('option');
defaultOption.value = '';
defaultOption.textContent = placeholder;
element.appendChild(defaultOption);
identityFiles.forEach(function (file) {
var option = document.createElement('option');
option.value = file.id;
option.textContent = file.filename;
if (opts.value && String(opts.value) === String(file.id)) {
option.selected = true;
}
element.appendChild(option);
});
return element;
}
if (opts.placeholder) {
element.setAttribute('placeholder', opts.placeholder);
}
element.value = opts.value || '';
return element;
}
function updateValueField(select) {
var form = select.closest('form');
if (!form) {
return;
}
var wrapper = form.querySelector('[data-value-wrapper]');
if (!wrapper) {
return;
}
var existing = wrapper.querySelector('[name="value"]');
var opts = {
name: existing ? existing.name : 'value',
id: existing ? existing.id : '',
placeholder: existing ? existing.getAttribute('placeholder') : wrapper.getAttribute('data-placeholder') || '',
value: existing ? existing.value : ''
};
if (select.value === 'file') {
delete opts.value;
}
wrapper.textContent = '';
wrapper.appendChild(createValueFieldElement(select.value, opts));
}
var switchers = document.querySelectorAll('select[data-typ-switch]');
for (var i = 0; i < switchers.length; i++) {
switchers[i].addEventListener('change', function () {
updateValueField(this);
});
}
});
</script>
<p><a href="admin.php">← zurück</a></p>
</body>
</html>
<?php
exit;
}
/* ─────────────────────────────
CREATE UUID
───────────────────────────── */
if ($action === 'uuid_create') {
$identityId = (int)($_GET['identity_id'] ?? 0);
$uuid = uuid_create(UUID_TYPE_RANDOM);
$sql->set(
"INSERT INTO access_tokens (identity_id, uuid)
VALUES (?, ?)",
"is",
[$identityId, $uuid]
);
header("Location: admin.php?action=uuid_edit&uuid=$uuid");
exit;
}
/* ─────────────────────────────
EDIT UUID
───────────────────────────── */
if ($action === 'uuid_edit') {
$uuid = $_GET['uuid'] ?? '';
$token = $sql->single(
"SELECT * FROM access_tokens WHERE uuid = ?",
"s",
[$uuid]
);
if (!$token) exit('UUID nicht gefunden');
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$sql->set(
"DELETE FROM token_permissions WHERE token_id = ?",
"i",
[$token['id']]
);
foreach ($_POST['fields'] ?? [] as $key) {
$sql->set(
"INSERT INTO token_permissions (token_id, field_key)
VALUES (?, ?)",
"is",
[$token['id'], $key]
);
}
$sql->set(
"UPDATE access_tokens SET notes = ? WHERE id = ?",
"si",
[trim($_POST['notes']), $token['id']]
);
}
// Alle Felder der zugehörigen Identität
$fields = $sql->get(
"SELECT field_key, field_value FROM identity_fields WHERE identity_id = ?",
"i",
[$token['identity_id']]
);
// Welche Felder aktuell für diesen Token erlaubt sind
$allowed = array_column(
$sql->get(
"SELECT field_key FROM token_permissions WHERE token_id = ?",
"i",
[$token['id']]
),
'field_key'
);
// Name der Identität
$identity = $sql->single(
"SELECT name FROM identities WHERE id = ?",
"i",
[$token['identity_id']]
);
?>
<!doctype html>
<html><head><meta charset="utf-8"><title>UUID bearbeiten</title><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
</head>
<body>
<h1>UUID bearbeiten</h1>
<p><strong>UUID:</strong> <code><?= htmlspecialchars($uuid) ?></code></p>
<p><strong>Identität:</strong> <?= htmlspecialchars($identity['name']) ?></p>
<form method="post">
<h3>Sichtbare Felder</h3>
<?php foreach ($fields as $f): ?>
<label>
<input type="checkbox" name="fields[]"
value="<?= htmlspecialchars($f['field_key']) ?>"
<?= in_array($f['field_key'], $allowed) ? 'checked' : '' ?>>
<?= htmlspecialchars($f['field_key']) ?>:
<em><?= htmlspecialchars($f['field_value']) ?></em>
</label><br>
<?php endforeach; ?>
<h3>Notiz</h3>
<textarea name="notes" rows="4"><?= htmlspecialchars($token['notes']) ?></textarea>
<br>
<button>Speichern</button>
</form>
<p><a href="admin.php">← zurück</a></p>
</body></html>
<?php
exit;
}
/* ─────────────────────────────
DASHBOARD (UUIDs + Identitäten)
───────────────────────────── */
$tokens = $sql->get(
"SELECT t.uuid, t.notes, i.name AS identity_name
FROM access_tokens t
JOIN identities i ON t.identity_id = i.id
ORDER BY t.created_at DESC"
);
$identities = $sql->get("SELECT * FROM identities ORDER BY id DESC");
?>
<!doctype html>
<html lang="de">
<head><meta charset="utf-8"><title>Admin Dashboard</title><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
</head>
<body>
<h1>Admin Dashboard</h1>
<h2>Alle UUIDs</h2>
<?php if (!empty($tokens)): ?>
<table border="1" cellpadding="5" cellspacing="0">
<thead>
<tr>
<th>UUID</th>
<th>Identität</th>
<th>Notiz</th>
<th>Aktion</th>
</tr>
</thead>
<tbody>
<?php foreach ($tokens as $t): ?>
<tr>
<td><code><?= htmlspecialchars($t['uuid']) ?></code></td>
<td><?= htmlspecialchars($t['identity_name']) ?></td>
<td><?= htmlspecialchars($t['notes']) ?></td>
<td><a href="admin.php?action=uuid_edit&uuid=<?= urlencode($t['uuid']) ?>">bearbeiten</a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else: ?>
<p>Keine UUIDs vorhanden.</p>
<?php endif; ?>
<hr>
<h2>Identitäten</h2>
<p><a href="admin.php?action=identity_create"> Identität anlegen</a></p>
<ul>
<?php foreach ($identities as $i): ?>
<li>
<strong><?= htmlspecialchars($i['name']) ?></strong>
<a href="admin.php?action=identity_edit&id=<?= $i['id'] ?>">bearbeiten</a>
</li>
<?php endforeach; ?>
</ul>
<p><a href="logout.php">Logout</a></p>
</body>
</html>
<?php
//TODO einheitliches dunkles design
//TODO im bereich UUID bearbeiten sollen files mit dem dateinamen und einem link (target=_blank) dargestellt werden und nicht mit der id
//TODO prüfen was passiert, wenn mehrere dateien mit dem gleichen dateinamen hochgeladen werden
//TODO option schaffen eine bestehende datei zu überschreiben ??? bzw prüfen ob das notwendig ist
//TODO anzeige von einer gelöschten oder leeren datei ausblenden, bei vollzogenem löschvorgang
?>