philippurbschat.de/home/views/admin/users.php

291 lines
No EOL
21 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-4 sm:gap-6 p-2 sm:p-4">
<!-- Sidebar Navigation -->
<aside class="w-full md:w-64 bg-slate-900/80 backdrop-blur-sm border border-slate-700/50 rounded-xl shadow-lg p-4 h-fit shrink-0">
<div class="flex items-center gap-2 mb-6 border-b border-slate-700/50 pb-3">
<span class="text-xl">⚙️</span>
<h2 class="text-emerald-400 font-mono font-bold text-lg tracking-tight">System_Admin</h2>
</div>
<nav class="flex flex-col gap-2 font-mono text-sm">
<a href="<?= $baseUrl ?>/admin" class="px-3 py-2 hover:bg-slate-800 text-slate-400 hover:text-slate-200 rounded-md transition-all">> Dashboard</a>
<a href="<?= $baseUrl ?>/admin/users?tab=users" class="px-3 py-2 <?= $currentTab === 'users' ? 'bg-emerald-500/10 text-emerald-400 border border-emerald-500/20' : 'hover:bg-slate-800 text-slate-400 hover:text-slate-200' ?> rounded-md transition-all">> Benutzer (<?= count($users ?? []) ?>)</a>
<a href="<?= $baseUrl ?>/admin/users?tab=invites" class="px-3 py-2 <?= $currentTab === 'invites' ? 'bg-emerald-500/10 text-emerald-400 border border-emerald-500/20' : 'hover:bg-slate-800 text-slate-400 hover:text-slate-200' ?> rounded-md transition-all">> Einladungslinks (<?= count($invitations ?? []) ?>)</a>
<a href="<?= $baseUrl ?>/admin-tools" class="px-3 py-2 hover:bg-slate-800 text-cyan-400 hover:text-cyan-300 rounded-md transition-all">> Admin-Tools</a>
<a href="<?= $baseUrl ?>/admin/settings" class="px-3 py-2 hover:bg-slate-800 text-slate-400 hover:text-slate-200 rounded-md transition-all">> Einstellungen</a>
</nav>
</aside>
<!-- Main Content Area -->
<main class="flex-1 min-w-0 bg-slate-900/80 backdrop-blur-sm border border-slate-700/50 rounded-xl shadow-2xl overflow-hidden flex flex-col">
<!-- Top Toolbar -->
<div class="bg-slate-800/80 px-4 py-3 border-b border-slate-700/50 flex flex-wrap items-center justify-between gap-3">
<div class="flex items-center gap-2">
<div class="w-3 h-3 rounded-full bg-rose-500 hidden sm:block"></div>
<div class="w-3 h-3 rounded-full bg-amber-500 hidden sm:block"></div>
<div class="w-3 h-3 rounded-full bg-emerald-500 hidden sm:block"></div>
<div class="sm:ml-2 font-mono text-xs text-slate-400">auth-matrix.sh</div>
</div>
<div class="flex items-center gap-2">
<a href="<?= $baseUrl ?>/admin/users?tab=users" class="text-xs font-mono px-3 py-1.5 rounded-lg border transition-all <?= $currentTab === 'users' ? 'bg-emerald-500/20 text-emerald-400 border-emerald-500/50' : 'bg-slate-900/50 text-slate-400 border-slate-700 hover:text-slate-200' ?>">
👥 Benutzer
</a>
<a href="<?= $baseUrl ?>/admin/users?tab=invites" class="text-xs font-mono px-3 py-1.5 rounded-lg border transition-all <?= $currentTab === 'invites' ? 'bg-emerald-500/20 text-emerald-400 border-emerald-500/50' : 'bg-slate-900/50 text-slate-400 border-slate-700 hover:text-slate-200' ?>">
✉️ Einladungslinks
</a>
<a href="<?= $baseUrl ?>/admin/user_form" class="text-xs font-mono bg-emerald-500/20 text-emerald-400 border border-emerald-500/50 px-3 py-1.5 rounded-lg hover:bg-emerald-500/40 transition-colors whitespace-nowrap">
+ NEW_USER
</a>
</div>
</div>
<div class="p-4 sm:p-6 flex-1">
<?php if ($currentTab === 'invites'): ?>
<!-- ================= TAB: EINLADUNGSLINKS ================= -->
<header class="mb-6 border-b border-slate-800 pb-4">
<h1 class="text-xl sm:text-2xl font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-emerald-400 to-cyan-400 font-mono tracking-tight mb-1">
> Einladungs-System_
</h1>
<p class="text-slate-400 font-mono text-xs sm:text-sm">
Erstelle geheime Registrierungs-Links mit vorkonfigurierten Modul-Zugriffsrechten. Nicht öffentlich & vor Suchmaschinen geschützt.
</p>
</header>
<!-- Generator Card -->
<div class="bg-slate-950/70 border border-emerald-500/30 rounded-xl p-5 mb-8">
<h2 class="text-sm font-bold font-mono text-emerald-400 uppercase tracking-wider mb-4 flex items-center gap-2">
<span>⚡</span> 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-xs uppercase mb-1.5">Notiz / Empfänger</label>
<input type="text" name="note" placeholder="z.B. Für Peter Müller" class="w-full bg-slate-900 border border-slate-700 rounded-lg p-2.5 text-slate-200 text-xs font-mono focus:outline-none focus:border-emerald-500">
</div>
<div>
<label class="block text-slate-400 font-mono text-xs uppercase 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-700 rounded-lg p-2.5 text-slate-200 text-xs font-mono focus:outline-none focus:border-emerald-500">
</div>
<div>
<label class="block text-slate-400 font-mono text-xs uppercase 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-700 rounded-lg p-2.5 text-slate-200 text-xs font-mono focus:outline-none focus:border-emerald-500">
</div>
</div>
<div>
<label class="block text-slate-400 font-mono text-xs uppercase mb-2">Automatisch freigeschaltete Module:</label>
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-6 gap-2">
<?php foreach ($allModules as $slug => $mod): ?>
<label class="flex items-center gap-2 p-2 bg-slate-900 border border-slate-800 hover:border-emerald-500/40 rounded-lg cursor-pointer transition-colors text-xs font-mono">
<input type="checkbox" name="projects[]" value="<?= htmlspecialchars($slug) ?>" class="accent-emerald-500">
<span><?= $mod['icon'] ?></span>
<span class="text-slate-300 truncate"><?= htmlspecialchars($mod['name']) ?></span>
</label>
<?php endforeach; ?>
</div>
</div>
<div class="pt-2">
<button type="submit" class="bg-emerald-500/20 border border-emerald-500/50 hover:bg-emerald-500/30 text-emerald-400 font-mono text-xs uppercase tracking-wider py-2.5 px-5 rounded-lg transition-all font-bold">
Link generieren & Speichern →
</button>
</div>
</form>
</div>
<!-- Invites Table -->
<h3 class="text-xs font-mono uppercase text-slate-400 tracking-wider mb-3">Bestehende Einladungslinks</h3>
<div class="overflow-x-auto border border-slate-800/80 rounded-lg">
<table class="w-full text-left font-mono text-xs text-slate-400">
<thead class="bg-slate-950/80 text-emerald-400 uppercase text-[10px]">
<tr>
<th class="px-3 py-2.5 border-b border-slate-800">Token & Link</th>
<th class="px-3 py-2.5 border-b border-slate-800">Notiz</th>
<th class="px-3 py-2.5 border-b border-slate-800">Vordefinierte Module</th>
<th class="px-3 py-2.5 border-b border-slate-800">Nutzungen</th>
<th class="px-3 py-2.5 border-b border-slate-800">Gültig bis</th>
<th class="px-3 py-2.5 border-b border-slate-800">Status</th>
<th class="px-3 py-2.5 border-b border-slate-800 text-right">Aktion</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-800/50 bg-slate-900/50">
<?php if (empty($invitations)): ?>
<tr><td colspan="7" class="px-4 py-4 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/40 transition-colors">
<td class="px-3 py-3">
<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 py-0.5 bg-emerald-500/10 hover:bg-emerald-500/20 text-emerald-400 border border-emerald-500/30 rounded text-[10px] transition-colors cursor-pointer" title="Kopieren">
📋 Kopieren
</button>
</div>
</td>
<td class="px-3 py-3 text-slate-300"><?= htmlspecialchars($inv['note'] ?: '-') ?></td>
<td class="px-3 py-3">
<div class="flex flex-wrap gap-1">
<?php if (empty($inv['assigned_projects'])): ?>
<span class="text-slate-500">-</span>
<?php else: foreach ($inv['assigned_projects'] as $slug):
$mod = $allModules[$slug] ?? null;
?>
<span class="inline-flex items-center gap-1 px-1.5 py-0.5 bg-slate-950 border border-slate-800 rounded text-[10px] text-slate-300">
<?= $mod['icon'] ?? '📦' ?> <?= htmlspecialchars($mod['name'] ?? $slug) ?>
</span>
<?php endforeach; endif; ?>
</div>
</td>
<td class="px-3 py-3 text-slate-300 font-mono"><?= $inv['uses_count'] ?> / <?= $inv['max_uses'] > 0 ? $inv['max_uses'] : '∞' ?></td>
<td class="px-3 py-3 text-slate-400"><?= $inv['expires_at'] ? date('d.m.Y H:i', strtotime($inv['expires_at'])) : 'Unbegrenzt' ?></td>
<td class="px-3 py-3">
<?php if ($inv['is_valid']): ?>
<span class="px-2 py-0.5 bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 rounded text-[10px]">Aktiv</span>
<?php else: ?>
<span class="px-2 py-0.5 bg-rose-500/10 text-rose-400 border border-rose-500/20 rounded text-[10px]">Inaktiv</span>
<?php endif; ?>
</td>
<td class="px-3 py-3 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">Löschen</button>
</form>
</td>
</tr>
<?php endforeach; endif; ?>
</tbody>
</table>
</div>
<?php else: ?>
<!-- ================= TAB: BENUTZERVERWALTUNG ================= -->
<header class="mb-6 border-b border-slate-800 pb-4">
<h1 class="text-xl sm:text-2xl font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-emerald-400 to-cyan-400 font-mono tracking-tight mb-1">
> Benutzerverwaltung_
</h1>
<p class="text-slate-400 font-mono text-xs sm:text-sm">
Registrierte Benutzer und individuelle Modulfreigaben.
</p>
</header>
<div class="overflow-x-auto border border-slate-800/80 rounded-lg">
<table class="w-full text-left font-mono text-xs text-slate-400">
<thead class="bg-slate-950/80 text-emerald-400 text-[10px] sm:text-xs uppercase">
<tr>
<th class="px-3 sm:px-4 py-3 border-b border-slate-800">ID</th>
<th class="px-3 sm:px-4 py-3 border-b border-slate-800">Name</th>
<th class="px-3 sm:px-4 py-3 border-b border-slate-800">Email</th>
<th class="px-3 sm:px-4 py-3 border-b border-slate-800">Rolle</th>
<th class="px-3 sm:px-4 py-3 border-b border-slate-800">Freigeschaltete Module</th>
<th class="px-3 sm:px-4 py-3 border-b border-slate-800">Status</th>
<th class="px-3 sm:px-4 py-3 border-b border-slate-800 text-right">Aktionen</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-800/50 bg-slate-900/50">
<?php if (empty($users)): ?>
<tr><td colspan="7" class="px-4 py-4 text-center text-slate-500">Keine Benutzer vorhanden.</td></tr>
<?php else: foreach ($users as $u): ?>
<tr class="hover:bg-slate-800/40 transition-colors">
<td class="px-3 sm:px-4 py-3">#<?= $u['id'] ?></td>
<td class="px-3 sm:px-4 py-3 text-slate-200 font-bold"><?= htmlspecialchars($u['name']) ?></td>
<td class="px-3 sm:px-4 py-3 text-slate-400 break-all"><?= htmlspecialchars($u['email']) ?></td>
<td class="px-3 sm:px-4 py-3">
<?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 text-[10px] font-bold">Admin</span>
<?php else: ?>
<span class="text-slate-500">User</span>
<?php endif; ?>
</td>
<td class="px-3 sm:px-4 py-3">
<div class="flex flex-wrap gap-1 max-w-xs">
<?php
$proj = $u['projects'] ?? [];
if (!empty($u['is_admin'])): ?>
<span class="inline-flex items-center gap-1 px-2 py-0.5 bg-fuchsia-950/60 border border-fuchsia-700/40 rounded text-[10px] text-fuchsia-300">
★ Alle Module (Vollzugriff)
</span>
<?php elseif (empty($proj)): ?>
<span class="text-slate-600 italic">Keine Module</span>
<?php else: foreach ($proj as $slug):
$mod = $allModules[$slug] ?? null;
?>
<span class="inline-flex items-center gap-1 px-1.5 py-0.5 bg-slate-950 border border-slate-800 rounded text-[10px] text-slate-300">
<?= $mod['icon'] ?? '📦' ?> <?= htmlspecialchars($mod['name'] ?? $slug) ?>
</span>
<?php endforeach; endif; ?>
</div>
</td>
<td class="px-3 sm:px-4 py-3">
<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 text-[11px] font-mono border transition-all cursor-pointer <?= $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-3 sm:px-4 py-3 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; ?>
</div>
</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-emerald-500/10', 'text-emerald-400');
btn.classList.add('bg-emerald-500', 'text-slate-950', 'font-bold');
setTimeout(function() {
btn.innerHTML = originalText;
btn.classList.remove('bg-emerald-500', 'text-slate-950', 'font-bold');
btn.classList.add('bg-emerald-500/10', 'text-emerald-400');
}, 2000);
}
}
</script>
<?php require_once __DIR__ . '/../inc/admin_footer.php'; ?>