AuditView.vue 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041
  1. <script setup lang="ts">
  2. import { onMounted, ref, watch } from 'vue'
  3. import PageHeader from '../components/PageHeader.vue'
  4. import { admin, errorMessage, type AuditRow } from '../lib/api'
  5. import { formatTime, toast } from '../lib/ui'
  6. const KINDS = { '': '全部', command: '命令执行', file_access_denied: '越权文件访问', network_denied: '网络拦截', approval: '审批', gateway_call: '网关工具', admin: '后台操作' } as const
  7. const rows = ref<AuditRow[]>([])
  8. const kind = ref<keyof typeof KINDS>('')
  9. const expanded = ref<number | null>(null)
  10. async function load() {
  11. rows.value = await admin.audit({ kind: kind.value || undefined, limit: 500 }).catch(err => (toast(errorMessage(err), 'error'), []))
  12. }
  13. watch(kind, load)
  14. onMounted(load)
  15. </script>
  16. <template>
  17. <div class="h-full flex flex-col">
  18. <PageHeader title="审计日志" subtitle="Agent 执行的命令、被拦截的访问、审批记录与后台操作">
  19. <select v-model="kind" class="field w-40" aria-label="类型">
  20. <option v-for="(label, k) in KINDS" :key="k" :value="k">{{ label }}</option>
  21. </select>
  22. <button type="button" class="btn-secondary" @click="load"><span class="codicon codicon-refresh" /></button>
  23. </PageHeader>
  24. <div class="flex-1 overflow-y-auto">
  25. <div v-for="r in rows" :key="r.id" class="border-b border-b-solid border-border">
  26. <button type="button" class="w-full flex items-baseline gap-3 px-6 py-1.5 border-none bg-transparent cursor-pointer text-left hover:bg-hover" @click="expanded = expanded === r.id ? null : r.id">
  27. <span class="text-fg-muted w-36 flex-none text-[12px]">{{ formatTime(r.ts) }}</span>
  28. <span class="w-24 flex-none text-[12px]" :class="r.kind.endsWith('denied') ? 'text-error' : 'text-fg-muted'">{{ KINDS[r.kind as keyof typeof KINDS] ?? r.kind }}</span>
  29. <span class="flex-1 min-w-0 truncate font-mono text-[12px]">{{ r.summary }}</span>
  30. <span class="text-fg-muted text-[11px] flex-none">{{ r.actor }}</span>
  31. </button>
  32. <pre v-if="expanded === r.id" class="m-0 px-6 pb-2 text-[11.5px] font-mono whitespace-pre-wrap break-all text-fg-muted">session={{ r.sessionId ?? '—' }}
  33. {{ JSON.stringify(r.detail, null, 2) }}</pre>
  34. </div>
  35. <div v-if="!rows.length" class="p-6 hint">没有记录</div>
  36. </div>
  37. </div>
  38. </template>