Refine admin panel workflows
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { Search, ShieldAlert, UserCog } from '@lucide/vue'
|
||||
import { FileClock, Search, UserCog } from '@lucide/vue'
|
||||
|
||||
import AdminPageHeader from '../../components/admin/AdminPageHeader.vue'
|
||||
import Card from '../../components/ui/Card.vue'
|
||||
@@ -9,7 +9,6 @@ import { useAwardsStore } from '../../stores/awards'
|
||||
const store = useAwardsStore()
|
||||
const query = ref('')
|
||||
const auditEntries = computed(() => store.admin.auditEntries)
|
||||
const riskFlags = computed(() => store.admin.riskFlags)
|
||||
const filteredAuditEntries = computed(() => {
|
||||
const search = query.value.trim().toLowerCase()
|
||||
if (!search) return auditEntries.value
|
||||
@@ -20,20 +19,6 @@ const filteredAuditEntries = computed(() => {
|
||||
.includes(search),
|
||||
)
|
||||
})
|
||||
const filteredRiskUsers = computed(() => {
|
||||
const search = query.value.trim().toLowerCase()
|
||||
const users = riskFlags.value.map((flag) => ({
|
||||
id: flag.id,
|
||||
twitchUserId: flag.twitchUserId ?? 'unbekannt',
|
||||
source: flag.source,
|
||||
type: flag.type,
|
||||
severity: flag.severity,
|
||||
ip: flag.createdFromIp,
|
||||
createdAt: flag.createdAt,
|
||||
}))
|
||||
if (!search) return users
|
||||
return users.filter((user) => [user.twitchUserId, user.source, user.type, user.ip].join(' ').toLowerCase().includes(search))
|
||||
})
|
||||
const adminCounts = computed(() => {
|
||||
const counts = new Map<string, number>()
|
||||
for (const entry of auditEntries.value) counts.set(entry.adminTwitchUserId, (counts.get(entry.adminTwitchUserId) ?? 0) + 1)
|
||||
@@ -42,16 +27,23 @@ const adminCounts = computed(() => {
|
||||
const logStats = computed(() => [
|
||||
{ label: 'Audit-Einträge', value: auditEntries.value.length },
|
||||
{ label: 'Admins aktiv', value: adminCounts.value.length },
|
||||
{ label: 'Risk-User', value: new Set(riskFlags.value.map((flag) => flag.twitchUserId).filter(Boolean)).size },
|
||||
{ label: 'Sichtbar', value: filteredAuditEntries.value.length },
|
||||
])
|
||||
const actionCounts = computed(() => {
|
||||
const counts = new Map<string, number>()
|
||||
for (const entry of auditEntries.value) counts.set(entry.actionType, (counts.get(entry.actionType) ?? 0) + 1)
|
||||
return [...counts.entries()]
|
||||
.map(([action, count]) => ({ action, count }))
|
||||
.sort((a, b) => b.count - a.count || a.action.localeCompare(b.action))
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<AdminPageHeader
|
||||
eyebrow="User & Logs"
|
||||
title="User-Spuren und Admin-Aktionen"
|
||||
description="Eine kompakte Kontrollansicht für Audit-Einträge, auffällige User und Admin-Aktivität. Für Detailentscheidungen bleibt Risiko & Audit der Hauptbereich."
|
||||
eyebrow="Team-Audit"
|
||||
title="Admin-Aktionen nachvollziehen"
|
||||
description="Diese Seite ist die Log-Quelle für Team-Handlungen: wer hat Kategorien, Kandidaten, Clips, Reviews oder Risk-Flags bearbeitet. Risikoentscheidungen selbst bleiben im Risiko-Bereich."
|
||||
:icon="UserCog"
|
||||
/>
|
||||
|
||||
@@ -108,26 +100,25 @@ const logStats = computed(() => [
|
||||
<Card class="overflow-hidden">
|
||||
<div class="border-b border-violet-100 p-5">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="grid h-10 w-10 place-items-center rounded-2xl bg-rose-50 text-rose-600">
|
||||
<ShieldAlert class="h-5 w-5" />
|
||||
<div class="grid h-10 w-10 place-items-center rounded-2xl bg-violet-100 text-violet-700">
|
||||
<FileClock class="h-5 w-5" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-violet-500">Auffällige User</p>
|
||||
<h2 class="font-[Cormorant_Garamond] text-3xl text-violet-800">Aus Risk-Flags abgeleitet</h2>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-violet-500">Aktionstypen</p>
|
||||
<h2 class="font-[Cormorant_Garamond] text-3xl text-violet-800">Was wurde bearbeitet?</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divide-y divide-violet-50">
|
||||
<div v-for="user in filteredRiskUsers" :key="user.id" class="grid gap-3 px-5 py-4 lg:grid-cols-[minmax(0,1fr)_180px_140px] lg:items-center">
|
||||
<div v-for="item in actionCounts" :key="item.action" class="grid gap-3 px-5 py-4 lg:grid-cols-[minmax(0,1fr)_120px] lg:items-center">
|
||||
<div>
|
||||
<p class="font-semibold text-slate-900">{{ user.twitchUserId }}</p>
|
||||
<p class="mt-1 text-sm text-slate-500">{{ user.type }} · {{ user.ip }}</p>
|
||||
<p class="font-semibold text-slate-900">{{ item.action }}</p>
|
||||
<p class="mt-1 text-sm text-slate-500">Audit-Kategorie für Team-Aktionen</p>
|
||||
</div>
|
||||
<span class="text-sm text-slate-500">{{ new Date(user.createdAt).toLocaleString('de-DE') }}</span>
|
||||
<span class="rounded-full border border-rose-100 bg-rose-50 px-3 py-1 text-center text-xs font-semibold uppercase tracking-[0.14em] text-rose-700">{{ user.severity }}</span>
|
||||
<span class="rounded-full border border-violet-100 bg-violet-50 px-3 py-1 text-center text-sm font-semibold text-violet-700">{{ item.count }}</span>
|
||||
</div>
|
||||
<p v-if="filteredRiskUsers.length === 0" class="px-5 py-10 text-center text-sm text-slate-500">
|
||||
Keine auffälligen User für den aktuellen Filter.
|
||||
<p v-if="actionCounts.length === 0" class="px-5 py-10 text-center text-sm text-slate-500">
|
||||
Noch keine Aktionstypen vorhanden.
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user