291 lines
No EOL
21 KiB
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'; ?>
|