Improve admin navigation and local API reachability

This commit is contained in:
AzuTear
2026-06-17 13:24:41 +02:00
parent 953257bcef
commit 4a211189f0
11 changed files with 330 additions and 23 deletions
+60 -4
View File
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import AdminPageHeader from '../../components/admin/AdminPageHeader.vue'
import Button from '../../components/ui/Button.vue'
import Card from '../../components/ui/Card.vue'
import { useAwardsStore } from '../../stores/awards'
@@ -9,9 +10,31 @@ const store = useAwardsStore()
const riskSaving = ref<number | null>(null)
const adminMessage = ref('')
const adminError = ref('')
const riskFilter = ref('')
const auditFilter = ref('')
const riskFlags = computed(() => store.admin.riskFlags)
const auditEntries = computed(() => store.admin.auditEntries)
const filteredRiskFlags = computed(() => {
const query = riskFilter.value.trim().toLowerCase()
if (!query) return riskFlags.value
return riskFlags.value.filter((flag) =>
[flag.source, flag.type, flag.summary, flag.twitchUserId ?? '', flag.createdFromIp]
.join(' ')
.toLowerCase()
.includes(query),
)
})
const filteredAuditEntries = computed(() => {
const query = auditFilter.value.trim().toLowerCase()
if (!query) return auditEntries.value
return auditEntries.value.filter((entry) =>
[entry.summary, entry.adminTwitchUserId, entry.actionType, entry.entityType, entry.entityId]
.join(' ')
.toLowerCase()
.includes(query),
)
})
async function resolveRiskFlag(riskFlagId: number, status = 'resolved') {
riskSaving.value = riskFlagId
@@ -31,6 +54,12 @@ async function resolveRiskFlag(riskFlagId: number, status = 'resolved') {
<template>
<div class="space-y-6">
<AdminPageHeader
eyebrow="Risk & Audit"
title="Auffaellige Muster und Admin-Aktionen verfolgen"
description="Dieser Bereich trennt operative Risiko-Sichtung von der Nachvollziehbarkeit. So findest du sowohl offene Flags als auch bereits ausgefuehrte Eingriffe deutlich schneller."
/>
<div class="grid gap-6 xl:grid-cols-[0.95fr_1.05fr]">
<Card class="p-7">
<div class="flex items-center justify-between gap-4">
@@ -39,7 +68,7 @@ async function resolveRiskFlag(riskFlagId: number, status = 'resolved') {
<p class="mt-2 text-sm text-slate-500">Auffaellige Login-, Nominierungs- und Voting-Muster fuer die manuelle Sichtung.</p>
</div>
<span class="text-sm uppercase tracking-[0.2em] text-slate-500">
{{ riskFlags.length }} offen
{{ filteredRiskFlags.length }} / {{ riskFlags.length }} offen
</span>
</div>
@@ -50,9 +79,21 @@ async function resolveRiskFlag(riskFlagId: number, status = 'resolved') {
{{ adminError }}
</p>
<div class="mt-6 grid gap-4 md:grid-cols-[minmax(0,1fr)_220px]">
<input
v-model="riskFilter"
type="text"
class="rounded-2xl border border-violet-200 px-4 py-3"
placeholder="Risk Flags nach Typ, User oder IP filtern"
/>
<div class="rounded-2xl border border-violet-100 bg-violet-50/60 px-4 py-3 text-sm text-slate-600">
Tipp: Filtere erst auf den Problemtyp und resolve dann nur den geprueften Fall.
</div>
</div>
<div class="mt-6 space-y-4">
<div
v-for="flag in riskFlags"
v-for="flag in filteredRiskFlags"
:key="flag.id"
class="rounded-[26px] border border-violet-100 bg-white/90 p-5"
>
@@ -84,6 +125,9 @@ async function resolveRiskFlag(riskFlagId: number, status = 'resolved') {
<p v-if="riskFlags.length === 0" class="rounded-[26px] border border-dashed border-violet-100 px-5 py-6 text-sm text-slate-500">
Keine offenen Risk Flags vorhanden.
</p>
<p v-else-if="filteredRiskFlags.length === 0" class="rounded-[26px] border border-dashed border-violet-100 px-5 py-6 text-sm text-slate-500">
Keine Risk Flags passen zum aktuellen Filter.
</p>
</div>
</Card>
@@ -94,13 +138,22 @@ async function resolveRiskFlag(riskFlagId: number, status = 'resolved') {
<p class="mt-2 text-sm text-slate-500">Nachvollziehbare Admin-Aktionen fuer Kategorie-, Kandidaten- und Review-Aenderungen.</p>
</div>
<span class="text-sm uppercase tracking-[0.2em] text-slate-500">
{{ auditEntries.length }} Eintraege
{{ filteredAuditEntries.length }} / {{ auditEntries.length }} Eintraege
</span>
</div>
<div class="mt-6">
<input
v-model="auditFilter"
type="text"
class="w-full rounded-2xl border border-violet-200 px-4 py-3"
placeholder="Audit-Eintraege nach Aktion, Admin oder Entitaet filtern"
/>
</div>
<div class="mt-6 space-y-4">
<div
v-for="entry in auditEntries"
v-for="entry in filteredAuditEntries"
:key="entry.id"
class="rounded-[26px] border border-violet-100 bg-violet-50/60 px-5 py-5"
>
@@ -118,6 +171,9 @@ async function resolveRiskFlag(riskFlagId: number, status = 'resolved') {
<p v-if="auditEntries.length === 0" class="rounded-[26px] border border-dashed border-violet-100 px-5 py-6 text-sm text-slate-500">
Noch keine Audit-Eintraege vorhanden.
</p>
<p v-else-if="filteredAuditEntries.length === 0" class="rounded-[26px] border border-dashed border-violet-100 px-5 py-6 text-sm text-slate-500">
Keine Audit-Eintraege passen zum aktuellen Filter.
</p>
</div>
</Card>
</div>