Benutzerverwaltung: übersichtliches Layout mit klaren Aktionen
- Header mit Icon + Statistikleiste (Benutzer / Admins / Bewerbungen) - Karten-Layout (rund, Shadow), Sektionen mit Icons - Aktionen als einheitliche Icon+Label Buttons (Jobsuche, Anmelden als, Passwort, Löschen) statt gequetschter Textlinks, je eigene Farbe - Avatar-Initial + Admin-Badge mit Icon in der Tabelle - Passwort-Reset als eingeblendete Karte mit Abbrechen + Hinweis - Relative Zeitangaben (Erstellt, Audit) mit Tooltips - Audit-Sektion als eigene Karte Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+152
-49
@@ -6,7 +6,7 @@
|
||||
<body class="min-h-screen flex flex-col transition-colors duration-300 bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100" id="body">
|
||||
<%- include('partials/header') %>
|
||||
|
||||
<main class="flex-1 container mx-auto px-4 py-8 max-w-4xl">
|
||||
<main class="flex-1 container mx-auto px-4 py-8 max-w-5xl">
|
||||
<a href="/" class="inline-flex items-center gap-2 text-sm text-blue-600 dark:text-blue-400 hover:underline mb-6">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
|
||||
@@ -14,100 +14,168 @@
|
||||
Zurück zur Übersicht
|
||||
</a>
|
||||
|
||||
<h2 class="text-2xl font-bold mb-1">Benutzerverwaltung</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-6">Benutzer anlegen, Passwörter zurücksetzen und Benutzer inkl. aller ihrer Daten löschen.</p>
|
||||
<% var totalBewerbungen = users.reduce((s,u) => s + (u.anzahl_bewerbungen||0), 0); var adminCount = users.filter(u=>u.is_admin).length; %>
|
||||
|
||||
<div class="flex flex-wrap items-end justify-between gap-3 mb-6">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold flex items-center gap-2.5">
|
||||
<span class="grid h-9 w-9 place-items-center rounded-xl bg-blue-600/10 text-blue-600 dark:bg-blue-500/15 dark:text-blue-300 ring-1 ring-blue-600/15">
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M17 20h5v-2a4 4 0 00-3-3.87M9 20H4v-2a4 4 0 013-3.87m6-2a4 4 0 100-8 4 4 0 000 8z"></path>
|
||||
</svg>
|
||||
</span>
|
||||
Benutzerverwaltung
|
||||
</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">Konten anlegen, Passwörter zurücksetzen, ein Konto übernehmen oder Benutzer inkl. aller Daten löschen.</p>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<div class="rounded-xl border border-gray-200 bg-white px-4 py-2.5 text-center shadow-sm dark:border-white/5 dark:bg-gray-800">
|
||||
<div class="text-xl font-bold leading-none"><%= users.length %></div>
|
||||
<div class="text-[11px] uppercase tracking-wide text-gray-500 dark:text-gray-400 mt-1">Benutzer</div>
|
||||
</div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white px-4 py-2.5 text-center shadow-sm dark:border-white/5 dark:bg-gray-800">
|
||||
<div class="text-xl font-bold leading-none text-blue-600 dark:text-blue-300"><%= adminCount %></div>
|
||||
<div class="text-[11px] uppercase tracking-wide text-gray-500 dark:text-gray-400 mt-1">Admins</div>
|
||||
</div>
|
||||
<div class="rounded-xl border border-gray-200 bg-white px-4 py-2.5 text-center shadow-sm dark:border-white/5 dark:bg-gray-800">
|
||||
<div class="text-xl font-bold leading-none"><%= totalBewerbungen %></div>
|
||||
<div class="text-[11px] uppercase tracking-wide text-gray-500 dark:text-gray-400 mt-1">Bewerbungen</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6">
|
||||
<!-- Neuen Benutzer anlegen -->
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mb-8">
|
||||
<h3 class="text-lg font-semibold mb-4">Neuen Benutzer anlegen</h3>
|
||||
<form method="POST" action="/admin/users" class="grid sm:grid-cols-3 gap-4 items-end">
|
||||
<section class="bg-white dark:bg-gray-800 rounded-xl shadow-md p-6">
|
||||
<h3 class="text-lg font-semibold mb-1 flex items-center gap-2">
|
||||
<svg class="h-5 w-5 text-blue-600 dark:text-blue-300" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z"></path>
|
||||
</svg>
|
||||
Neuen Benutzer anlegen
|
||||
</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-5">Legt ein neues Konto an. Ein Admin kann ebenfalls Benutzer verwalten.</p>
|
||||
<form method="POST" action="/admin/users" class="grid sm:grid-cols-2 lg:grid-cols-4 gap-4 items-end">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="newUsername">Benutzername</label>
|
||||
<input id="newUsername" name="username" type="text" required maxlength="64"
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5" for="newUsername">Benutzername</label>
|
||||
<input id="newUsername" name="username" type="text" required maxlength="64" placeholder="z. B. maxm"
|
||||
class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-gray-800 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1" for="newPassword">Passwort</label>
|
||||
<input id="newPassword" name="password" type="password" required
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5" for="newPassword">Passwort</label>
|
||||
<input id="newPassword" name="password" type="password" required placeholder="Startpasswort"
|
||||
class="w-full rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-gray-800 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none" />
|
||||
</div>
|
||||
<div class="flex items-end gap-4">
|
||||
<label class="inline-flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
<input type="checkbox" name="is_admin" value="1" class="rounded border-gray-300 dark:border-gray-600" />
|
||||
Admin
|
||||
<div class="flex items-center h-[42px]">
|
||||
<label class="inline-flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300 cursor-pointer">
|
||||
<input type="checkbox" name="is_admin" value="1" class="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500" />
|
||||
Admin-Rechte
|
||||
</label>
|
||||
<button type="submit" class="rounded-lg bg-blue-600 hover:bg-blue-700 transition-colors text-white font-medium px-4 py-2">
|
||||
</div>
|
||||
<button type="submit" class="inline-flex justify-center items-center gap-2 rounded-lg bg-blue-600 hover:bg-blue-700 transition-colors text-white font-medium px-4 py-2 shadow-sm">
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4"></path></svg>
|
||||
Anlegen
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Bestehende Benutzer -->
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
|
||||
<h3 class="text-lg font-semibold mb-4">Bestehende Benutzer</h3>
|
||||
<section class="bg-white dark:bg-gray-800 rounded-xl shadow-md overflow-hidden">
|
||||
<div class="px-6 pt-6">
|
||||
<h3 class="text-lg font-semibold flex items-center gap-2">
|
||||
<svg class="h-5 w-5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"></path>
|
||||
</svg>
|
||||
Bestehende Benutzer
|
||||
</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1 mb-4">Über „Anmelden als" übernimmst du ein Konto komplett — Admin-Rechte sind dabei pausiert, zurück über das Banner.</p>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="text-left text-gray-500 dark:text-gray-400 border-b border-gray-200 dark:border-gray-700">
|
||||
<th class="py-2 pr-4 font-medium">Benutzername</th>
|
||||
<th class="py-2 pr-4 font-medium">Rolle</th>
|
||||
<th class="py-2 pr-4 font-medium">Bewerbungen</th>
|
||||
<th class="py-2 pr-4 font-medium">Erstellt</th>
|
||||
<th class="py-2 pr-4 font-medium text-right">Aktionen</th>
|
||||
<tr class="text-left text-gray-500 dark:text-gray-400 border-b border-gray-200 dark:border-gray-700 bg-gray-50/60 dark:bg-white/5">
|
||||
<th class="py-2.5 px-6 font-medium">Benutzername</th>
|
||||
<th class="py-2.5 pr-4 font-medium">Rolle</th>
|
||||
<th class="py-2.5 pr-4 font-medium text-center">Bewerbungen</th>
|
||||
<th class="py-2.5 pr-4 font-medium">Erstellt</th>
|
||||
<th class="py-2.5 pr-6 font-medium text-right">Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
<% users.forEach(function(u) { %>
|
||||
<tr>
|
||||
<td class="py-3 pr-4 font-medium text-gray-800 dark:text-gray-100">
|
||||
<%= u.username %>
|
||||
<td class="py-3 px-6 font-medium text-gray-800 dark:text-gray-100">
|
||||
<div class="flex items-center gap-2.5">
|
||||
<span class="grid h-8 w-8 place-items-center rounded-full text-xs font-semibold <%= u.is_admin ? 'bg-blue-600/10 text-blue-700 dark:bg-blue-500/15 dark:text-blue-300' : 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300' %>">
|
||||
<%= String(u.username || '?').charAt(0).toUpperCase() %>
|
||||
</span>
|
||||
<span><%= u.username %></span>
|
||||
<% if (u.id === currentUserId) { %><span class="text-xs text-gray-400">(du)</span><% } %>
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-3 pr-4">
|
||||
<% if (u.is_admin) { %>
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300">Admin</span>
|
||||
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300">
|
||||
<svg class="h-3 w-3" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0a1.724 1.724 0 002.573 1.066c.78-.448 1.707.479 1.26 1.26a1.724 1.724 0 001.065 2.572c.921.3.921 1.603 0 1.902a1.724 1.724 0 00-1.066 2.573c.448.78-.479 1.707-1.26 1.26a1.724 1.724 0 00-2.572 1.065c-.3.921-1.603.921-1.902 0a1.724 1.724 0 00-2.573-1.066c-.78.448-1.707-.479-1.26-1.26a1.724 1.724 0 00-1.065-2.572c-.921-.3-.921-1.603 0-1.902a1.724 1.724 0 001.066-2.573c-.448-.78.479-1.707 1.26-1.26.996.608 2.296.07 2.572-1.065z"></path><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
|
||||
Admin
|
||||
</span>
|
||||
<% } else { %>
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300">Benutzer</span>
|
||||
<% } %>
|
||||
</td>
|
||||
<td class="py-3 pr-4 text-gray-600 dark:text-gray-300"><%= u.anzahl_bewerbungen %></td>
|
||||
<td class="py-3 pr-4 text-gray-500 dark:text-gray-400"><%= u.created_at %></td>
|
||||
<td class="py-3 pr-4 text-right whitespace-nowrap">
|
||||
<td class="py-3 pr-4 text-center text-gray-600 dark:text-gray-300"><%= u.anzahl_bewerbungen %></td>
|
||||
<td class="py-3 pr-4 text-gray-500 dark:text-gray-400">
|
||||
<time data-dt="<%= u.created_at %>"><%= u.created_at %></time>
|
||||
</td>
|
||||
<td class="py-3 pr-6">
|
||||
<div class="flex flex-wrap items-center justify-end gap-1.5">
|
||||
<a href="/jobsuche?user=<%= u.id %>"
|
||||
class="text-sm text-blue-600 dark:text-blue-400 hover:underline mr-3"
|
||||
title="Suchprofil und Zeitplan dieses Benutzers verwalten">Jobsuche</a>
|
||||
class="act-btn act-neutral" title="Suchprofil & Zeitplan dieses Benutzers verwalten">
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||
<span>Jobsuche</span>
|
||||
</a>
|
||||
<% if (u.id !== currentUserId) { %>
|
||||
<form method="POST" action="/admin/users/<%= u.id %>/impersonate" class="inline"
|
||||
onsubmit="return confirm('Als „<%= u.username %>“ anmelden? Du siehst dann dessen Konto. Admin-Rechte sind dabei pausiert; über das Banner oben kannst du zurückwechseln.');">
|
||||
<button type="submit" class="text-sm text-emerald-600 dark:text-emerald-400 hover:underline mr-3"
|
||||
title="Dieses Konto übernehmen (Impersonation)">Anmelden als</button>
|
||||
<button type="submit" class="act-btn act-emerald" title="Dieses Konto übernehmen (Impersonation)">
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14v7m-4-4h8"></path></svg>
|
||||
<span>Anmelden als</span>
|
||||
</button>
|
||||
</form>
|
||||
<% } %>
|
||||
<button type="button"
|
||||
onclick="document.getElementById('resetForm<%= u.id %>').classList.toggle('hidden')"
|
||||
class="text-sm text-blue-600 dark:text-blue-400 hover:underline mr-3">
|
||||
Passwort
|
||||
class="act-btn act-blue" title="Passwort zurücksetzen">
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"></path></svg>
|
||||
<span>Passwort</span>
|
||||
</button>
|
||||
<% if (u.id !== currentUserId) { %>
|
||||
<form method="POST" action="/admin/users/<%= u.id %>/delete" class="inline"
|
||||
onsubmit="return confirm('Benutzer „<%= u.username %>“ inkl. aller Daten wirklich löschen? Das lässt sich nicht rückgängig machen.');">
|
||||
<button type="submit" class="text-sm text-red-600 dark:text-red-400 hover:underline">Löschen</button>
|
||||
<button type="submit" class="act-btn act-danger" title="Benutzer inkl. aller Daten löschen">
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg>
|
||||
<span>Löschen</span>
|
||||
</button>
|
||||
</form>
|
||||
<% } %>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="resetForm<%= u.id %>" class="hidden">
|
||||
<td colspan="5" class="py-3 pr-4">
|
||||
<form method="POST" action="/admin/users/<%= u.id %>/reset-password" class="flex items-end gap-3 flex-wrap">
|
||||
<td colspan="5" class="px-6 pb-4">
|
||||
<form method="POST" action="/admin/users/<%= u.id %>/reset-password" class="flex items-end gap-3 flex-wrap rounded-lg border border-blue-200 bg-blue-50/50 p-4 dark:border-blue-500/20 dark:bg-blue-500/5">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
Neues Passwort
|
||||
Neues Passwort für <%= u.username %>
|
||||
<input name="password" type="password" required
|
||||
class="mt-1 block w-full sm:w-64 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-gray-800 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none" />
|
||||
class="mt-1.5 block w-full sm:w-72 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 px-3 py-2 text-gray-800 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none" />
|
||||
</label>
|
||||
<button type="submit" class="rounded-lg bg-blue-600 hover:bg-blue-700 transition-colors text-white font-medium px-4 py-2">
|
||||
Speichern (alle Sessions ungültigen)
|
||||
<button type="submit" class="rounded-lg bg-blue-600 hover:bg-blue-700 transition-colors text-white font-medium px-4 py-2 shadow-sm">
|
||||
Speichern
|
||||
</button>
|
||||
<button type="button" onclick="document.getElementById('resetForm<%= u.id %>').classList.toggle('hidden')"
|
||||
class="rounded-lg px-3 py-2 text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
|
||||
Abbrechen
|
||||
</button>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400 self-center">Alle Sitzungen dieses Benutzers werden ungültig.</span>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -115,11 +183,16 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Impersonations-Audit: wer hat als wen gehandelt, wann -->
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mt-8">
|
||||
<h3 class="text-lg font-semibold mb-1">Impersonationen (Audit)</h3>
|
||||
<!-- Impersonations-Audit -->
|
||||
<section class="bg-white dark:bg-gray-800 rounded-xl shadow-md p-6">
|
||||
<h3 class="text-lg font-semibold mb-1 flex items-center gap-2">
|
||||
<svg class="h-5 w-5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
Impersonationen (Audit)
|
||||
</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4">Nachvollziehbar protokolliert: jeder Wechsel auf ein anderes Konto und jeder Rückwechsel.</p>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
@@ -135,12 +208,42 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.act-btn{display:inline-flex;align-items:center;gap:.375rem;border-radius:.5rem;padding:.375rem .625rem;font-size:.75rem;font-weight:500;transition:background-color .15s,color .15s;line-height:1;}
|
||||
.act-neutral{color:#4b5563}.act-neutral:hover{background:#f3f4f6}
|
||||
.dark .act-neutral{color:#d1d5db}.dark .act-neutral:hover{background:rgba(255,255,255,.06)}
|
||||
.act-emerald{color:#047857}.act-emerald:hover{background:#ecfdf5}
|
||||
.dark .act-emerald{color:#6ee7b7}.dark .act-emerald:hover{background:rgba(16,185,129,.12)}
|
||||
.act-blue{color:#2563eb}.act-blue:hover{background:#eff6ff}
|
||||
.dark .act-blue{color:#93c5fd}.dark .act-blue:hover{background:rgba(59,130,246,.12)}
|
||||
.act-danger{color:#e11d48}.act-danger:hover{background:#fff1f2}
|
||||
.dark .act-danger{color:#fda4af}.dark .act-danger:hover{background:rgba(244,63,94,.12)}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
function esc(s) { var d = document.createElement('div'); d.textContent = s == null ? '' : String(s); return d.innerHTML; }
|
||||
// Relative Zeitangaben für die „Erstellt"-Spalte.
|
||||
function fmtRel(v) {
|
||||
if (!v) return '';
|
||||
var dt = new Date(String(v).replace(' ', 'T') + 'Z');
|
||||
if (isNaN(dt)) return v;
|
||||
var diff = (Date.now() - dt.getTime()) / 1000, out;
|
||||
if (diff < 60) out = 'gerade eben';
|
||||
else if (diff < 3600) out = Math.floor(diff / 60) + ' Min.';
|
||||
else if (diff < 86400) out = Math.floor(diff / 3600) + ' Std.';
|
||||
else if (diff < 2592000) out = Math.floor(diff / 86400) + ' T';
|
||||
else out = dt.toLocaleDateString('de-DE');
|
||||
var full = dt.toLocaleString('de-DE');
|
||||
return '<time title="' + esc(full) + '">' + out + '</time>';
|
||||
}
|
||||
document.querySelectorAll('time[data-dt]').forEach(function (el) { el.outerHTML = fmtRel(el.getAttribute('data-dt')); });
|
||||
|
||||
// Audit-Liste laden.
|
||||
fetch('/admin/audit/impersonations', { cache: 'no-store' })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (d) {
|
||||
@@ -159,7 +262,7 @@
|
||||
return '<tr>'
|
||||
+ '<td class="py-2 pr-4 font-medium text-gray-800 dark:text-gray-100">' + esc(it.actor || '?') + '</td>'
|
||||
+ '<td class="py-2 pr-4">' + badge + ziel + '</td>'
|
||||
+ '<td class="py-2 pr-4 text-gray-500 dark:text-gray-400">' + esc(it.created_at) + '</td>'
|
||||
+ '<td class="py-2 pr-4 text-gray-500 dark:text-gray-400">' + fmtRel(it.created_at) + '</td>'
|
||||
+ '</tr>';
|
||||
}).join('');
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user