190 lines
9.5 KiB
Vue
190 lines
9.5 KiB
Vue
<template>
|
|
<div class="log-settings-container">
|
|
<section class="log-settings">
|
|
<header class="log-heading">
|
|
<h2>日志记录</h2>
|
|
<span class="log-retention">保留 30 天</span>
|
|
</header>
|
|
<el-radio-group v-model="category" class="log-categories" aria-label="日志分类">
|
|
<el-radio-button v-for="item in categories" :key="item.value" :value="item.value">
|
|
{{ item.label }}
|
|
</el-radio-button>
|
|
</el-radio-group>
|
|
<div class="log-filters">
|
|
<el-date-picker
|
|
v-model="dateRange"
|
|
type="datetimerange"
|
|
start-placeholder="开始时间"
|
|
end-placeholder="结束时间"
|
|
format="YYYY-MM-DD HH:mm"
|
|
aria-label="日志时间范围"
|
|
/>
|
|
<el-select v-if="canFilterSystemMail" v-model="mailScope" class="mail-scope" aria-label="收发信记录范围">
|
|
<el-option label="全部收发信" value="all" />
|
|
<el-option label="系统收发信" value="system" />
|
|
</el-select>
|
|
<el-input v-if="!isRuntime && canFilterActor && !systemMailSelected" v-model="actorId" clearable placeholder="操作者编号" aria-label="操作者编号" @keyup.enter="search" />
|
|
<div class="log-filter-actions">
|
|
<el-button :disabled="loading" @click="recent">最近 24 小时</el-button>
|
|
<el-button type="primary" :loading="loading" @click="search">查询</el-button>
|
|
</div>
|
|
</div>
|
|
<el-alert v-if="error" :title="error" type="error" :closable="false" show-icon />
|
|
<div v-loading="loading" class="log-content" :aria-busy="loading">
|
|
<div class="log-table-heading">
|
|
<div class="log-table-title">
|
|
<strong>{{ currentCategory }}</strong>
|
|
<span class="log-file-meta">最新在前 · 每页 10 条</span>
|
|
</div>
|
|
<div v-if="isRuntime" class="log-summary" aria-label="本页日志统计">
|
|
<span>本页 {{ runtimeSummary.total }} 条</span>
|
|
<span class="log-stat-info"><i aria-hidden="true" />信息 {{ runtimeSummary.info }}</span>
|
|
<span class="log-stat-warning"><i aria-hidden="true" />警告 {{ runtimeSummary.warning }}</span>
|
|
<span class="log-stat-danger"><i aria-hidden="true" />错误 {{ runtimeSummary.error }}</span>
|
|
</div>
|
|
</div>
|
|
<el-table
|
|
v-if="isRuntime"
|
|
:data="runtimeRows"
|
|
class="log-table runtime-table"
|
|
height="100%"
|
|
row-key="sequence"
|
|
:row-class-name="runtimeRowClass"
|
|
empty-text="暂无运行日志"
|
|
@row-click="row => runtimeDetail = row"
|
|
>
|
|
<el-table-column label="时间(本地)" width="116">
|
|
<template #default="{ row }">
|
|
<div class="log-time"><strong>{{ row.time }}</strong><span>{{ row.date }}</span></div>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="级别" width="76" align="center">
|
|
<template #default="{ row }">
|
|
<el-tag :type="row.type" size="small" effect="light" class="log-level-tag">{{ row.label }}</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="操作与结果说明" min-width="170">
|
|
<template #default="{ row }">
|
|
<strong class="log-action">{{ row.action }}</strong>
|
|
<div class="log-message">{{ row.outcome }}</div>
|
|
<div v-if="row.object" class="log-source">对象:{{ row.object }}</div>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="结果" width="92" align="center">
|
|
<template #default="{ row }">
|
|
<el-tag :type="resultType(row.result)" size="small" class="log-level-tag">{{ resultLabel(row.result) }}</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="详情" width="60" align="center">
|
|
<template #default="{ row }">
|
|
<el-button link type="primary" :aria-label="`查看第 ${row.sequence} 条日志详情`" @click.stop="runtimeDetail = row">查看</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
<el-table v-else :data="items" class="log-table" height="100%" row-key="id" stripe empty-text="暂无日志记录" @row-click="item => detail = item">
|
|
<el-table-column label="时间(本地)" width="116">
|
|
<template #default="{ row }">
|
|
<div class="log-time"><strong>{{ timeParts(row.createdAt).time }}</strong><span>{{ timeParts(row.createdAt).date }}</span></div>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="操作与结果说明" min-width="170">
|
|
<template #default="{ row }">
|
|
<strong class="log-action">{{ actionLabel(row.action) }}</strong>
|
|
<div class="log-message">{{ displayOutcome(row) }}</div>
|
|
<div class="log-source">{{ displayObject(row) }}</div>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="操作者" min-width="85" show-overflow-tooltip>
|
|
<template #default="{ row }">{{ displayActor(row) }}</template>
|
|
</el-table-column>
|
|
<el-table-column label="结果" width="82" align="center">
|
|
<template #default="{ row }">
|
|
<el-tag :type="resultType(row.result)" size="small" class="log-level-tag">{{ resultLabel(row.result) }}</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="详情" width="60" align="center">
|
|
<template #default="{ row }"><el-button link type="primary" @click.stop="detail = row">查看</el-button></template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</div>
|
|
<footer class="log-pagination">
|
|
<span aria-live="polite">{{ pageRange }}</span>
|
|
<div class="log-page-actions">
|
|
<el-button size="small" :disabled="loading || offset === 0" @click="previous">上一页</el-button>
|
|
<el-button size="small" :disabled="loading || !hasMore" @click="next">下一页</el-button>
|
|
</div>
|
|
</footer>
|
|
<el-dialog
|
|
:model-value="Boolean(runtimeDetail)"
|
|
title="运行日志详情"
|
|
width="min(760px, 92vw)"
|
|
append-to-body
|
|
@close="runtimeDetail = null"
|
|
>
|
|
<div v-if="runtimeDetail" class="runtime-detail">
|
|
<div class="runtime-detail-heading">
|
|
<el-tag :type="runtimeDetail.type" effect="light">{{ runtimeDetail.label }}</el-tag>
|
|
<span>{{ formatTime(runtimeDetail.createdAt) }}</span>
|
|
<span class="runtime-detail-sequence">第 {{ runtimeDetail.sequence }} 条</span>
|
|
</div>
|
|
<dl class="log-detail-grid runtime-detail-meta">
|
|
<dt>触发操作</dt><dd>{{ runtimeDetail.action }}</dd>
|
|
<dt>操作对象</dt><dd>{{ runtimeDetail.object || '未记录' }}</dd>
|
|
<dt>操作结果</dt><dd>{{ resultLabel(runtimeDetail.result) }}</dd>
|
|
<dt>结果说明</dt><dd>{{ runtimeDetail.outcome }}</dd>
|
|
<dt>代码来源</dt><dd>{{ runtimeDetail.source || '未记录' }}</dd>
|
|
<template v-if="runtimeDetail.requestId">
|
|
<dt>请求标识</dt><dd>{{ runtimeDetail.requestId }}</dd>
|
|
</template>
|
|
</dl>
|
|
<h3>完整内容</h3>
|
|
<pre class="runtime-detail-message">{{ runtimeDetail.message }}</pre>
|
|
<details class="runtime-raw">
|
|
<summary>原始记录</summary>
|
|
<pre>{{ runtimeDetail.raw }}</pre>
|
|
</details>
|
|
</div>
|
|
</el-dialog>
|
|
<el-dialog :model-value="Boolean(detail)" title="日志详情" width="min(760px, 92vw)" append-to-body @close="detail = null">
|
|
<template v-if="detail">
|
|
<p>{{ formatTime(detail.createdAt) }} · {{ displayActor(detail) }} · {{ actionLabel(detail.action) }}</p>
|
|
<dl class="log-detail-grid">
|
|
<dt>触发操作</dt><dd>{{ actionLabel(detail.action) }}</dd>
|
|
<dt>触发来源</dt><dd>{{ details.trigger || '未记录' }}</dd>
|
|
<dt>{{ category === 'mail' ? '处理阶段' : '操作对象' }}</dt><dd>{{ displayObject(detail) }}</dd>
|
|
<dt>操作结果</dt><dd>{{ resultLabel(detail.result) }}</dd>
|
|
<dt>结果说明</dt><dd>{{ displayOutcome(detail) }}</dd>
|
|
</dl>
|
|
<dl v-if="category === 'mail'" class="log-detail-grid">
|
|
<dt>发件人</dt><dd>{{ details.sender || '—' }}</dd>
|
|
<dt>收件人数</dt><dd>{{ details.recipientCount }}</dd>
|
|
<dt>邮件大小</dt><dd>{{ details.bytes }} 字节</dd>
|
|
<dt>对端接收人数</dt><dd>{{ details.acceptedCount ?? '未记录' }}</dd>
|
|
</dl>
|
|
<dl v-else class="log-detail-grid">
|
|
<dt>权限角色</dt><dd>{{ details.actorRole || '—' }}</dd>
|
|
<dt>结果代码</dt><dd>{{ details.errorCode ?? '—' }}</dd>
|
|
<dt>HTTP 状态</dt><dd>{{ details.httpStatus || '未记录' }}</dd>
|
|
</dl>
|
|
</template>
|
|
</el-dialog>
|
|
</section>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { useLogs } from './LogSettings.js'
|
|
|
|
const {
|
|
category, categories, loading, error, items, offset,
|
|
canFilterActor,
|
|
mailScope, canFilterSystemMail, systemMailSelected,
|
|
hasMore, actorId, dateRange, detail, details, isRuntime,
|
|
runtimeDetail, runtimeRows, runtimeSummary, pageRange,
|
|
currentCategory, resultType, runtimeRowClass, timeParts,
|
|
formatTime, resultLabel, actionLabel, displayActor, displayObject, displayOutcome, search, recent, next, previous
|
|
} = useLogs()
|
|
</script>
|
|
|
|
<style scoped src="./LogSettings.css"></style>
|