WebHooker/app/components/AuditLog.vue
RhenCloud e59b10f739
refactor(admin): unify webui fetch/copy/format helpers; fix audit tab load and delete-group count
- apiFetch + shared needLogin (any 401 shows the login card) replaces six
  duplicated fetch/401/error-handling blocks in the admin composables
- useCopy composable: clipboard + execCommand fallback with timed reset,
  used by MembersPanel and WebhookPanel
- utils/format.ts (fmtTime/splitList/parseMatch) removes inline duplicates
  across SendLogs, AuditLog, RouteEditor and GroupEditor
- fix: load the audit log when the audit tab becomes active (client-side
  navigation never remounted the page, leaving the list empty on first visit)
- fix: fetch the real route count for a group before confirming deletion
  instead of reusing the currently open group's route list
2026-08-14 07:18:55 +08:00

81 lines
2.4 KiB
Vue

<template>
<div>
<div class="log-toolbar">
<span class="kpi-label">{{ t("audit.title") }}</span>
<div class="log-filters">
<label class="filter-label">{{ t("audit.filterGroup") }}</label>
<select
class="filter-select"
:value="selectedGroupId"
:disabled="loading"
@change="onGroupFilter"
>
<option value="">{{ t("audit.allGroups") }}</option>
<option v-for="g in groups" :key="g.id" :value="g.id">
{{ g.name || g.id }}
</option>
</select>
</div>
<button class="btn btn-ghost btn-sm" :disabled="loading" @click="refresh">
{{ t("audit.refresh") }}
</button>
</div>
<p v-if="error" class="err">{{ error }}</p>
<p v-else-if="!loading && !entries.length" class="empty-log">{{ t("audit.empty") }}</p>
<div class="log-list">
<article v-for="e in entries" :key="e.id ?? e.ts" class="log-entry">
<div class="log-head">
<span class="dot ok"></span>
<span class="log-route">{{ e.action }}</span>
<span class="log-event">{{ e.actorLogin || e.actorId || "-" }}</span>
<span class="log-status ok">{{ e.targetType || "-" }}</span>
<span class="log-time">{{ fmtTime(e.ts) }}</span>
</div>
<div class="log-meta">
<span
><b>{{ t("audit.target") }}</b
><code>{{ e.targetId || "-" }}</code></span
>
<span
><b>{{ t("audit.group") }}</b
><code>{{ e.groupId || "-" }}</code></span
>
<span v-if="e.detail && Object.keys(e.detail).length > 0"
><b>{{ t("audit.detail") }}</b
><code>{{ JSON.stringify(e.detail) }}</code></span
>
</div>
</article>
</div>
</div>
</template>
<script setup lang="ts">
import type { AuditEntry, Group } from "~/types";
const { t } = useI18n();
defineProps<{
entries: AuditEntry[];
loading: boolean;
error: string;
groups: Group[];
selectedGroupId: string;
}>();
const emit = defineEmits<{
(e: "refresh"): void;
(e: "filter"): void;
(e: "update:selectedGroupId", value: string): void;
}>();
function refresh(): void {
emit("refresh");
}
function onGroupFilter(e: Event): void {
emit("update:selectedGroupId", (e.target as HTMLSelectElement).value);
emit("filter");
}
</script>