Improve admin navigation and local API reachability
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user