271 lines
No EOL
19 KiB
PHP
271 lines
No EOL
19 KiB
PHP
<?php require_once __DIR__ . '/../inc/admin_header.php'; ?>
|
|
<?php
|
|
$baseUrl = rtrim(Config::get('BASE_URL', 'https://philippurbschat.de/'), '/');
|
|
$currentTab = $activeTab ?? ($_GET['tab'] ?? 'users');
|
|
?>
|
|
<div class="max-w-7xl w-full mx-auto flex flex-col md:flex-row gap-6 p-4 sm:p-6">
|
|
<!-- Sidebar Navigation -->
|
|
<aside class="w-full md:w-56 bg-slate-900/60 backdrop-blur-md border border-slate-800 rounded-2xl p-4 h-fit shrink-0">
|
|
<div class="mb-4 pb-3 border-b border-slate-800">
|
|
<h2 class="text-xs font-mono uppercase tracking-[0.2em] text-slate-400 font-bold">Navigation</h2>
|
|
</div>
|
|
<nav class="flex flex-col gap-1.5 font-mono text-xs">
|
|
<a href="<?= $baseUrl ?>/admin" class="px-3 py-2 text-slate-400 hover:text-slate-200 hover:bg-slate-800/60 rounded-xl transition-all">Dashboard</a>
|
|
<a href="<?= $baseUrl ?>/admin/users?tab=users" class="px-3 py-2 <?= $currentTab === 'users' ? 'bg-cyan-500/10 text-cyan-400 border border-cyan-500/30' : 'text-slate-400 hover:text-slate-200 hover:bg-slate-800/60' ?> rounded-xl transition-all">Benutzer (<?= count($users ?? []) ?>)</a>
|
|
<a href="<?= $baseUrl ?>/admin/users?tab=invites" class="px-3 py-2 <?= $currentTab === 'invites' ? 'bg-cyan-500/10 text-cyan-400 border border-cyan-500/30' : 'text-slate-400 hover:text-slate-200 hover:bg-slate-800/60' ?> rounded-xl transition-all">Einladungslinks (<?= count($invitations ?? []) ?>)</a>
|
|
<a href="<?= $baseUrl ?>/admin/settings" class="px-3 py-2 text-slate-400 hover:text-slate-200 hover:bg-slate-800/60 rounded-xl transition-all">Einstellungen</a>
|
|
</nav>
|
|
</aside>
|
|
|
|
<!-- Main Content Area -->
|
|
<main class="flex-1 min-w-0 bg-slate-900/60 backdrop-blur-md border border-slate-800 rounded-3xl p-6 sm:p-8 shadow-2xl relative overflow-hidden flex flex-col">
|
|
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-transparent via-cyan-500/40 to-transparent"></div>
|
|
|
|
<!-- Top Header & Tabs -->
|
|
<div class="flex flex-wrap items-center justify-between gap-4 mb-8 pb-6 border-b border-slate-800">
|
|
<div>
|
|
<h1 class="text-xl sm:text-2xl font-black text-slate-100 font-mono tracking-tight">
|
|
<span class="text-cyan-400">/</span> <?= $currentTab === 'invites' ? 'Einladungslinks' : 'Benutzerverwaltung' ?>
|
|
</h1>
|
|
<p class="text-slate-400 text-xs font-mono mt-1">
|
|
<?= $currentTab === 'invites' ? 'Geheime Registrierungs-Links mit vorkonfigurierten Modul-Rechten.' : 'Verwalte Benutzerkonten und individuelle Modul-Zugriffe.' ?>
|
|
</p>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-2">
|
|
<a href="<?= $baseUrl ?>/admin/users?tab=users" class="text-xs font-mono px-3.5 py-2 rounded-xl border transition-all <?= $currentTab === 'users' ? 'bg-cyan-500/10 text-cyan-400 border-cyan-500/40' : 'bg-slate-950/60 text-slate-400 border-slate-800 hover:text-slate-200' ?>">
|
|
Benutzer
|
|
</a>
|
|
<a href="<?= $baseUrl ?>/admin/users?tab=invites" class="text-xs font-mono px-3.5 py-2 rounded-xl border transition-all <?= $currentTab === 'invites' ? 'bg-cyan-500/10 text-cyan-400 border-cyan-500/40' : 'bg-slate-950/60 text-slate-400 border-slate-800 hover:text-slate-200' ?>">
|
|
Einladungslinks
|
|
</a>
|
|
<a href="<?= $baseUrl ?>/admin/user_form" class="text-xs font-mono bg-cyan-500/10 text-cyan-400 border border-cyan-500/30 px-3.5 py-2 rounded-xl hover:bg-cyan-500/20 transition-all font-bold whitespace-nowrap">
|
|
+ Neuer Benutzer
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if ($currentTab === 'invites'): ?>
|
|
<!-- ================= TAB: EINLADUNGSLINKS ================= -->
|
|
<!-- Link Generator -->
|
|
<div class="bg-slate-950/80 border border-slate-800 rounded-2xl p-5 sm:p-6 mb-8 relative group">
|
|
<h2 class="text-xs font-mono uppercase tracking-wider text-cyan-400 font-bold mb-4">
|
|
Neuen Einladungslink generieren
|
|
</h2>
|
|
<form action="<?= $baseUrl ?>/admin/invite_create" method="POST" class="space-y-4">
|
|
<input type="hidden" name="csrf_token" value="<?= Security::csrf() ?>">
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<div>
|
|
<label class="block text-slate-400 font-mono text-[11px] uppercase tracking-wider mb-1.5">Notiz / Empfänger</label>
|
|
<input type="text" name="note" placeholder="z.B. Für Alex" class="w-full bg-slate-900 border border-slate-800 rounded-xl p-3 text-slate-200 text-xs font-mono focus:outline-none focus:border-cyan-500 transition-colors">
|
|
</div>
|
|
<div>
|
|
<label class="block text-slate-400 font-mono text-[11px] uppercase tracking-wider mb-1.5">Max. Nutzungen</label>
|
|
<input type="number" name="max_uses" value="1" min="1" max="100" class="w-full bg-slate-900 border border-slate-800 rounded-xl p-3 text-slate-200 text-xs font-mono focus:outline-none focus:border-cyan-500 transition-colors">
|
|
</div>
|
|
<div>
|
|
<label class="block text-slate-400 font-mono text-[11px] uppercase tracking-wider mb-1.5">Gültigkeit in Tagen (optional)</label>
|
|
<input type="number" name="days_valid" placeholder="z.B. 7 (leer = unbegrenzt)" min="1" max="365" class="w-full bg-slate-900 border border-slate-800 rounded-xl p-3 text-slate-200 text-xs font-mono focus:outline-none focus:border-cyan-500 transition-colors">
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-slate-400 font-mono text-[11px] uppercase tracking-wider mb-2">Automatisch freizuschaltende Module</label>
|
|
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-2.5">
|
|
<?php foreach ($allModules as $slug => $mod): ?>
|
|
<label class="flex items-center gap-2.5 p-3 bg-slate-900/80 border border-slate-800 hover:border-cyan-500/40 rounded-xl cursor-pointer transition-colors text-xs font-mono">
|
|
<input type="checkbox" name="projects[]" value="<?= htmlspecialchars($slug) ?>" class="accent-cyan-500">
|
|
<span class="text-slate-300 uppercase tracking-wider text-[11px] font-bold"><?= htmlspecialchars($mod['name']) ?></span>
|
|
</label>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="pt-2">
|
|
<button type="submit" class="bg-cyan-500/10 border border-cyan-500/30 hover:bg-cyan-500/20 text-cyan-400 font-mono text-xs uppercase tracking-wider py-3 px-5 rounded-xl transition-all font-bold cursor-pointer">
|
|
Link generieren →
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Existing Invites Table -->
|
|
<div class="overflow-x-auto border border-slate-800 rounded-2xl">
|
|
<table class="w-full text-left font-mono text-xs text-slate-400">
|
|
<thead class="bg-slate-950 text-cyan-400 uppercase text-[10px] tracking-wider">
|
|
<tr>
|
|
<th class="px-4 py-3 border-b border-slate-800">Token & Link</th>
|
|
<th class="px-4 py-3 border-b border-slate-800">Notiz</th>
|
|
<th class="px-4 py-3 border-b border-slate-800">Module</th>
|
|
<th class="px-4 py-3 border-b border-slate-800">Nutzungen</th>
|
|
<th class="px-4 py-3 border-b border-slate-800">Gültig bis</th>
|
|
<th class="px-4 py-3 border-b border-slate-800">Status</th>
|
|
<th class="px-4 py-3 border-b border-slate-800 text-right">Aktion</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-slate-800/60 bg-slate-900/30">
|
|
<?php if (empty($invitations)): ?>
|
|
<tr><td colspan="7" class="px-4 py-6 text-center text-slate-500">Noch keine Einladungslinks erstellt.</td></tr>
|
|
<?php else: foreach ($invitations as $inv):
|
|
$fullUrl = $baseUrl . '/register?token=' . $inv['token'];
|
|
?>
|
|
<tr class="hover:bg-slate-800/30 transition-colors">
|
|
<td class="px-4 py-3.5">
|
|
<div class="flex items-center gap-2">
|
|
<span class="text-slate-300 font-mono text-[11px] select-all"><?= substr($inv['token'], 0, 10) ?>...</span>
|
|
<button type="button" onclick="copyInviteLink('<?= $fullUrl ?>', this)" class="px-2.5 py-1 bg-cyan-500/10 hover:bg-cyan-500/20 text-cyan-400 border border-cyan-500/30 rounded-lg text-[10px] transition-colors cursor-pointer tracking-wider uppercase">
|
|
Kopieren
|
|
</button>
|
|
</div>
|
|
</td>
|
|
<td class="px-4 py-3.5 text-slate-200"><?= htmlspecialchars($inv['note'] ?: '-') ?></td>
|
|
<td class="px-4 py-3.5">
|
|
<div class="flex flex-wrap gap-1">
|
|
<?php if (empty($inv['assigned_projects'])): ?>
|
|
<span class="text-slate-600">-</span>
|
|
<?php else: foreach ($inv['assigned_projects'] as $slug):
|
|
$mod = $allModules[$slug] ?? null;
|
|
?>
|
|
<span class="px-2 py-0.5 bg-slate-950 border border-slate-800 rounded-md text-[10px] text-slate-300 uppercase">
|
|
<?= htmlspecialchars($mod['name'] ?? $slug) ?>
|
|
</span>
|
|
<?php endforeach; endif; ?>
|
|
</div>
|
|
</td>
|
|
<td class="px-4 py-3.5 text-slate-300"><?= $inv['uses_count'] ?> / <?= $inv['max_uses'] > 0 ? $inv['max_uses'] : '∞' ?></td>
|
|
<td class="px-4 py-3.5 text-slate-400"><?= $inv['expires_at'] ? date('d.m.Y H:i', strtotime($inv['expires_at'])) : 'Unbegrenzt' ?></td>
|
|
<td class="px-4 py-3.5">
|
|
<?php if ($inv['is_valid']): ?>
|
|
<span class="px-2 py-0.5 bg-emerald-500/10 text-emerald-400 border border-emerald-500/30 rounded-md text-[10px] uppercase">Aktiv</span>
|
|
<?php else: ?>
|
|
<span class="px-2 py-0.5 bg-rose-500/10 text-rose-400 border border-rose-500/30 rounded-md text-[10px] uppercase">Inaktiv</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td class="px-4 py-3.5 text-right">
|
|
<form action="<?= $baseUrl ?>/admin/invite_delete/<?= $inv['id'] ?>" method="POST" class="inline" onsubmit="return confirm('Diesen Einladungslink wirklich löschen?');">
|
|
<input type="hidden" name="csrf_token" value="<?= Security::csrf() ?>">
|
|
<button type="submit" class="text-rose-400 hover:text-rose-300 text-xs cursor-pointer">Löschen</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<?php else: ?>
|
|
<!-- ================= TAB: BENUTZERVERWALTUNG ================= -->
|
|
<div class="overflow-x-auto border border-slate-800 rounded-2xl">
|
|
<table class="w-full text-left font-mono text-xs text-slate-400">
|
|
<thead class="bg-slate-950 text-cyan-400 uppercase text-[10px] tracking-wider">
|
|
<tr>
|
|
<th class="px-4 py-3.5 border-b border-slate-800">ID</th>
|
|
<th class="px-4 py-3.5 border-b border-slate-800">Name</th>
|
|
<th class="px-4 py-3.5 border-b border-slate-800">Email</th>
|
|
<th class="px-4 py-3.5 border-b border-slate-800">Rolle</th>
|
|
<th class="px-4 py-3.5 border-b border-slate-800">Freigeschaltete Module</th>
|
|
<th class="px-4 py-3.5 border-b border-slate-800">Status</th>
|
|
<th class="px-4 py-3.5 border-b border-slate-800 text-right">Aktionen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-slate-800/60 bg-slate-900/30">
|
|
<?php if (empty($users)): ?>
|
|
<tr><td colspan="7" class="px-4 py-6 text-center text-slate-500">Keine Benutzer vorhanden.</td></tr>
|
|
<?php else: foreach ($users as $u): ?>
|
|
<tr class="hover:bg-slate-800/30 transition-colors">
|
|
<td class="px-4 py-3.5 text-slate-500">#<?= $u['id'] ?></td>
|
|
<td class="px-4 py-3.5 text-slate-200 font-bold"><?= htmlspecialchars($u['name']) ?></td>
|
|
<td class="px-4 py-3.5 text-slate-400 break-all"><?= htmlspecialchars($u['email']) ?></td>
|
|
<td class="px-4 py-3.5">
|
|
<?php if (!empty($u['is_admin'])): ?>
|
|
<span class="px-2 py-0.5 bg-fuchsia-500/10 text-fuchsia-400 border border-fuchsia-500/30 rounded-md text-[10px] font-bold uppercase tracking-wider">Admin</span>
|
|
<?php else: ?>
|
|
<span class="text-slate-500 uppercase tracking-wider text-[10px]">User</span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td class="px-4 py-3.5">
|
|
<div class="flex flex-wrap gap-1 max-w-xs">
|
|
<?php
|
|
$proj = $u['projects'] ?? [];
|
|
if (!empty($u['is_admin'])): ?>
|
|
<span class="px-2 py-0.5 bg-fuchsia-950/60 border border-fuchsia-800/40 rounded-md text-[10px] text-fuchsia-300 uppercase tracking-wider">
|
|
Alle Module (Vollzugriff)
|
|
</span>
|
|
<?php elseif (empty($proj)): ?>
|
|
<span class="text-slate-600 italic text-[10px]">Keine</span>
|
|
<?php else: foreach ($proj as $slug):
|
|
$mod = $allModules[$slug] ?? null;
|
|
?>
|
|
<span class="px-2 py-0.5 bg-slate-950 border border-slate-800 rounded-md text-[10px] text-slate-300 uppercase">
|
|
<?= htmlspecialchars($mod['name'] ?? $slug) ?>
|
|
</span>
|
|
<?php endforeach; endif; ?>
|
|
</div>
|
|
</td>
|
|
<td class="px-4 py-3.5">
|
|
<form action="<?= $baseUrl ?>/admin/user_toggle_status/<?= $u['id'] ?>" method="POST" class="inline">
|
|
<input type="hidden" name="csrf_token" value="<?= Security::csrf() ?>">
|
|
<button type="submit" class="px-2.5 py-1 rounded-lg text-[10px] font-mono border transition-all cursor-pointer uppercase tracking-wider <?= $u['status'] === 'Active' ? 'bg-emerald-500/10 text-emerald-400 border-emerald-500/30 hover:bg-emerald-500/20' : 'bg-rose-500/10 text-rose-400 border-rose-500/30 hover:bg-rose-500/20' ?>" title="Klicken zum Umschalten">
|
|
<?= $u['status'] === 'Active' ? 'Aktiv' : 'Inaktiv' ?>
|
|
</button>
|
|
</form>
|
|
</td>
|
|
<td class="px-4 py-3.5 text-right whitespace-nowrap">
|
|
<div class="flex justify-end items-center gap-3">
|
|
<a href="<?= $baseUrl ?>/admin/user_form/<?= $u['id'] ?>" class="text-cyan-400 hover:text-cyan-300">Bearbeiten</a>
|
|
<form action="<?= $baseUrl ?>/admin/user_delete/<?= $u['id'] ?>" method="POST" class="inline" onsubmit="return confirm('Benutzer <?= htmlspecialchars(addslashes($u['name'])) ?> wirklich löschen?');">
|
|
<input type="hidden" name="csrf_token" value="<?= Security::csrf() ?>">
|
|
<button type="submit" class="text-rose-400 hover:text-rose-300">Löschen</button>
|
|
</form>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<?php endif; ?>
|
|
</main>
|
|
</div>
|
|
|
|
<script>
|
|
function copyInviteLink(url, btn) {
|
|
if (navigator.clipboard && window.isSecureContext) {
|
|
navigator.clipboard.writeText(url).then(showSuccess, fallback);
|
|
} else {
|
|
fallback();
|
|
}
|
|
|
|
function fallback() {
|
|
var textArea = document.createElement("textarea");
|
|
textArea.value = url;
|
|
textArea.style.position = "fixed";
|
|
textArea.style.left = "-999999px";
|
|
document.body.appendChild(textArea);
|
|
textArea.focus();
|
|
textArea.select();
|
|
try {
|
|
document.execCommand('copy');
|
|
showSuccess();
|
|
} catch (err) {
|
|
alert('Link konnte nicht kopiert werden: ' + url);
|
|
}
|
|
document.body.removeChild(textArea);
|
|
}
|
|
|
|
function showSuccess() {
|
|
var originalText = btn.innerHTML;
|
|
btn.innerHTML = 'Kopiert';
|
|
btn.classList.remove('bg-cyan-500/10', 'text-cyan-400');
|
|
btn.classList.add('bg-cyan-500', 'text-slate-950', 'font-bold');
|
|
setTimeout(function() {
|
|
btn.innerHTML = originalText;
|
|
btn.classList.remove('bg-cyan-500', 'text-slate-950', 'font-bold');
|
|
btn.classList.add('bg-cyan-500/10', 'text-cyan-400');
|
|
}, 2000);
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<?php require_once __DIR__ . '/../inc/admin_footer.php'; ?>
|