fix: 审计事件详情

This commit is contained in:
luo
2026-08-10 14:44:59 +08:00
parent 9f7f619083
commit 373ad4c2a5
3 changed files with 101 additions and 33 deletions

View File

@@ -3,15 +3,19 @@
<ElButton v-if="eventId" link type="primary" @click="openEvent">事件详情</ElButton>
<ElButton v-if="actorRef" link type="primary" @click="openActor"> 操作者行为时间线 </ElButton>
<ElButton
v-show="showResources"
v-for="resource in stableResourceRefs"
:key="`${resource.resource_type}:${resource.resource_id}`"
link
type="primary"
:title="resourceTimelineLabel(resource)"
:aria-label="resourceTimelineLabel(resource)"
@click="openResource(resource)"
>
资源审计时间线
{{ resourceTimelineLabel(resource) }}
</ElButton>
<ElButton
v-show="showResources"
v-for="resource in searchableResourceRefs"
:key="`${resource.resource_type}:${resource.resource_key}`"
link
@@ -52,6 +56,7 @@
AuditSearchResourceType
} from '@/types/api'
import { openAuditInvestigation, openAuditResourceSearch } from './investigationController'
import { auditResourceTypeLabels } from '@/utils/business/audit'
import { resolveInvestigationReference } from '@/utils/business/auditNavigation'
const props = withDefaults(
@@ -59,8 +64,9 @@
refs?: AuditInvestigationRefs | null
currentEventId?: string
showEmpty?: boolean
showResources?: boolean
}>(),
{ currentEventId: '', showEmpty: true }
{ currentEventId: '', showEmpty: true, showResources: true }
)
const searchableTypes = new Set<AuditSearchResourceType>([
'iot_card',
@@ -111,8 +117,8 @@
actorRef.value ||
requestId.value ||
correlationId.value ||
stableResourceRefs.value.length ||
searchableResourceRefs.value.length ||
(props.showResources &&
(stableResourceRefs.value.length || searchableResourceRefs.value.length)) ||
stableIntegrationRefs.value.length
)
)
@@ -135,6 +141,10 @@
resourceType: resource.resource_type,
id: resource.resource_id
})
const resourceTimelineLabel = (
resource: AuditInvestigationResourceRef & { resource_id: string }
) =>
`资源审计时间线 · ${auditResourceTypeLabels[resource.resource_type] || resource.resource_type} · ${resource.resource_id}`
const searchResource = (
resource: AuditInvestigationResourceRef & {
resource_type: AuditSearchResourceType

View File

@@ -99,6 +99,8 @@ declare module 'vue' {
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElCol: typeof import('element-plus/es')['ElCol']
ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']

View File

@@ -99,32 +99,11 @@
><AuditInvestigationLinks
:refs="detail.investigation_refs"
:current-event-id="detail.event_id"
:show-resources="false"
/></ElCard>
<ElCard shadow="never" class="section-card">
<template #header>相关资源与快照</template>
<ElTable :data="detail.resources || []" :row-key="resourceRowKey">
<ElTableColumn type="expand" width="48">
<template #default="{ row }">
<div class="resource-snapshot-grid">
<section>
<h4>身份快照</h4>
<pre>{{ auditJson(row.identity_snapshot) }}</pre>
</section>
<section>
<h4>变更前</h4>
<pre>{{ auditJson(row.before_data) }}</pre>
</section>
<section>
<h4>变更后</h4>
<pre>{{ auditJson(row.after_data) }}</pre>
</section>
<section>
<h4>主体安全数据</h4>
<pre>{{ auditJson(row.subject_data) }}</pre>
</section>
</div>
</template>
</ElTableColumn>
<ElTableColumn
prop="display_name"
label="资源"
@@ -141,7 +120,7 @@
auditResourceRelationLabels[row.relation] || row.relation
}}</template>
</ElTableColumn>
<ElTableColumn prop="role" label="业务角色" min-width="150" />
<ElTableColumn prop="role" label="业务角色" min-width="160" show-overflow-tooltip />
<ElTableColumn label="主体可见性" min-width="150">
<template #default="{ row }">{{
auditSubjectVisibilityLabels[row.subject_visibility] ||
@@ -160,6 +139,19 @@
row.created_at ? formatDateTime(row.created_at) : '-'
}}</template>
</ElTableColumn>
<ElTableColumn label="操作" width="220" fixed="right">
<template #default="{ row }">
<ElButton link type="primary" @click="openSnapshotDialog(row)">查看快照</ElButton>
<ElButton
link
type="primary"
:disabled="!row.resource_id"
@click="openResourceTimeline(row)"
>
资源审计时间线
</ElButton>
</template>
</ElTableColumn>
</ElTable>
</ElCard>
<ElCard v-if="userStore.isSuperAdmin" shadow="never">
@@ -170,6 +162,37 @@
<ElEmpty v-else-if="!loading" description="未找到审计事件" />
</div>
</ElCard>
<ElDialog
v-model="snapshotDialogVisible"
:title="snapshotDialogTitle"
width="min(960px, calc(100% - 32px))"
destroy-on-close
>
<div v-if="selectedResource" class="resource-snapshot-grid">
<ElCollapse class="resource-snapshot-section">
<ElCollapseItem title="身份快照" name="identity">
<pre>{{ auditJson(selectedResource.identity_snapshot) }}</pre>
</ElCollapseItem>
</ElCollapse>
<ElCollapse class="resource-snapshot-section">
<ElCollapseItem title="主体安全数据" name="subject">
<pre>{{ auditJson(selectedResource.subject_data) }}</pre>
</ElCollapseItem>
</ElCollapse>
<section class="resource-snapshot-diff">
<ElCollapse class="resource-snapshot-section">
<ElCollapseItem title="变更前" name="before">
<pre>{{ auditJson(selectedResource.before_data) }}</pre>
</ElCollapseItem>
</ElCollapse>
<ElCollapse class="resource-snapshot-section">
<ElCollapseItem title="变更后" name="after">
<pre>{{ auditJson(selectedResource.after_data) }}</pre>
</ElCollapseItem>
</ElCollapse>
</section>
</div>
</ElDialog>
</div>
</template>
@@ -195,6 +218,7 @@
import { formatDateTime } from '@/utils/business/format'
import { useUserStore } from '@/store/modules/user'
import AuditInvestigationLinks from '@/components/business/audit/AuditInvestigationLinks.vue'
import { openAuditInvestigation } from '@/components/business/audit/investigationController'
defineOptions({ name: 'AuditEventDetail' })
const route = useRoute()
@@ -202,6 +226,14 @@
const userStore = useUserStore()
const loading = ref(false)
const detail = ref<AuditEventDetail>()
const snapshotDialogVisible = ref(false)
const selectedResource = ref<AuditResourceView>()
const snapshotDialogTitle = computed(() => {
const resource = selectedResource.value
if (!resource) return '资源快照'
const type = auditResourceTypeLabels[resource.resource_type] || resource.resource_type
return `资源快照 · ${resource.display_name || resource.resource_id || type}`
})
const scopeDisplay = computed(() => {
if (!detail.value) return '-'
const type = auditScopeTypeLabels[detail.value.scope_type] || detail.value.scope_type
@@ -223,6 +255,18 @@
[row.resource_type, row.resource_id || row.resource_key || row.sort_order || row.display_name]
.filter((item) => item !== null && item !== undefined && item !== '')
.join(':')
const openSnapshotDialog = (row: AuditResourceView) => {
selectedResource.value = row
snapshotDialogVisible.value = true
}
const openResourceTimeline = (row: AuditResourceView) => {
if (!row.resource_id) return
openAuditInvestigation({
mode: 'resource',
resourceType: row.resource_type,
id: row.resource_id
})
}
onMounted(async () => {
const id = String(route.params.eventId || '')
if (!id) return
@@ -279,14 +323,22 @@
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
padding: 8px 16px 16px;
padding: 8px 0;
}
.resource-snapshot-grid h4 {
margin: 0 0 8px;
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
.resource-snapshot-diff {
display: grid;
grid-column: 1 / -1;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
}
.resource-snapshot-section {
min-width: 0;
}
.resource-snapshot-section :deep(.el-collapse-item__content) {
padding-bottom: 0;
}
.resource-snapshot-grid pre {
@@ -319,5 +371,9 @@
.resource-snapshot-grid {
grid-template-columns: 1fr;
}
.resource-snapshot-diff {
grid-template-columns: 1fr;
}
}
</style>