Smail/frontend/src/views/components/LogSettings/LogSettings.js

144 lines
6.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { http } from '@/utils/axios'
import { useGlobalStatusStore } from '@/stores/useGlobalStatusStore'
import { parseRuntimeLine, summarizeRuntime, timeParts } from './RuntimeLog.js'
export function useLogs() {
const store = useGlobalStatusStore()
const canFilterActor = computed(() => ['super_admin', 'admin'].includes(store.role))
const category = ref('operation')
const loading = ref(false)
const error = ref('')
const items = ref([])
const lines = ref([])
const offset = ref(0)
const hasMore = ref(false)
const actorId = ref('')
const mailScope = ref('all')
const canFilterSystemMail = computed(() => store.role === 'super_admin' && category.value === 'mail')
const systemMailSelected = computed(() => canFilterSystemMail.value && mailScope.value === 'system')
const recentRange = () => {
const end = new Date()
return [new Date(end.getTime() - 24 * 60 * 60 * 1000), end]
}
const dateRange = ref(recentRange())
const detail = ref(null)
const runtimeDetail = ref(null)
const pageSize = 10
let requestId = 0
let aborter
const previousOffsets = []
let appliedFilters = {}
const categories = [
{ label: '操作记录', value: 'operation' },
{ label: '收发信记录', value: 'mail' },
{ label: '运行日志', value: 'runtime' }
]
const isRuntime = computed(() => category.value === 'runtime')
const formatTime = value => value ? new Date(value).toLocaleString('zh-CN', { hour12: false }) : '—'
const resultLabel = value => ({ success: '成功', failure: '失败', unknown: '未确认', accepted: '对端接收', pending: '待验证', started: '已开始', recorded: '状态记录', partial: '部分接收', discarded: '已丢弃', skipped: '已跳过' }[value] || value || '未记录')
const actionLabel = value => ({ update: '保存配置', rollback: '回滚配置', migration: '迁移配置' }[value] || value || '未记录操作')
const displayObject = item => item.payload?.object || item.payload?.stage || '未记录对象'
const displayOutcome = item => item.payload?.outcome || '历史记录未提供结果说明'
const displayActor = item => !item.actorId || item.actorId === '0' ? '系统' : (item.payload?.actorName || item.actorId)
const details = computed(() => detail.value?.payload || {})
const runtimeRows = computed(() => lines.value.map((line, index) => parseRuntimeLine(line, offset.value + index + 1)))
const runtimeSummary = computed(() => summarizeRuntime(runtimeRows.value))
const visibleCount = computed(() => isRuntime.value ? lines.value.length : items.value.length)
const pageRange = computed(() => visibleCount.value ? `第 ${offset.value + 1}–${offset.value + visibleCount.value} 条` : '暂无记录')
const currentCategory = computed(() => categories.find(item => item.value === category.value)?.label)
const resultType = value => ({ success: 'success', accepted: 'primary', failure: 'danger', pending: 'warning', partial: 'warning', discarded: 'warning', started: 'primary' }[value] || 'info')
const runtimeRowClass = ({ row }) => ['warning', 'danger'].includes(row.type) ? `log-row-${row.type}` : ''
function requireData(result) {
if (result?.errorNo !== 0 || !result.data) throw new Error(result?.errorMsg || '日志查询失败')
return result.data
}
async function load() {
const id = ++requestId
aborter?.abort()
aborter = new AbortController()
loading.value = true
error.value = ''
try {
const params = { ...appliedFilters, offset: offset.value, limit: pageSize }
const data = requireData(await http.get('/api/v1/logs', { params, signal: aborter.signal }))
if (id !== requestId) return
items.value = data.items || []
lines.value = data.lines || []
hasMore.value = Boolean(data.hasMore)
} catch (cause) {
if (id === requestId && cause?.code !== 'ERR_CANCELED') {
error.value = cause.message || '日志查询失败'
items.value = []
lines.value = []
hasMore.value = false
}
} finally {
if (id === requestId) loading.value = false
}
}
function search() {
// 只在查询时采纳筛选条件,翻页沿用快照,不随当前时间或未提交输入漂移。
if (!dateRange.value?.length) dateRange.value = recentRange()
const [from, to] = (dateRange.value || []).map(value => new Date(value))
if (!from || !to || !Number.isFinite(from.getTime()) || !Number.isFinite(to.getTime()) || from > to) {
++requestId
aborter?.abort()
loading.value = false
error.value = '请选择有效的起止时间'
items.value = []
lines.value = []
hasMore.value = false
return
}
appliedFilters = { kind: category.value, from: from.toISOString(), to: to.toISOString() }
if (systemMailSelected.value) appliedFilters.systemOnly = true
else if (canFilterActor.value && !isRuntime.value && actorId.value.trim()) appliedFilters.actorId = actorId.value.trim()
offset.value = 0
previousOffsets.length = 0
detail.value = null
runtimeDetail.value = null
return load()
}
function recent() {
dateRange.value = recentRange()
return search()
}
function next() {
if (loading.value || !hasMore.value) return
previousOffsets.push(offset.value)
offset.value += isRuntime.value ? lines.value.length : pageSize
return load()
}
function previous() {
if (loading.value || offset.value === 0) return
offset.value = previousOffsets.pop() ?? 0
return load()
}
watch(category, () => {
detail.value = null
runtimeDetail.value = null
search()
})
watch(mailScope, search)
onMounted(search)
onBeforeUnmount(() => {
++requestId
aborter?.abort()
})
return {
category, categories, loading, error, items, lines, offset,
canFilterActor,
mailScope, canFilterSystemMail, systemMailSelected,
hasMore, actorId, dateRange, detail, details, isRuntime,
runtimeDetail, runtimeRows, runtimeSummary, visibleCount, pageRange,
currentCategory, resultType, runtimeRowClass, timeParts,
formatTime, resultLabel, actionLabel, displayActor, displayObject, displayOutcome, search, recent, next, previous
}
}