import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue' import { useRouter } from 'vue-router' import EnterpriseListPage from '../../components/shared/EnterpriseListPage.vue' import { useSystemState } from '../../composables/useSystemState.js' import { useToast } from '../../composables/useToast.js' import { fetchSystemLogEntries } from '../../services/systemLogs.js' import { AGENT_RUN_POLL_INTERVAL_MS } from '../../utils/agentRunMonitor.js' import { isManagerUser } from '../../utils/accessControl.js' function formatDateTime(value) { if (!value) { return '未结束' } const date = new Date(value) if (Number.isNaN(date.getTime())) { return String(value) } return date.toLocaleString('zh-CN', { hour12: false }) } function formatSummary(summary) { const text = String(summary || '').trim() if (!text) { return '暂无摘要。' } if (text.length <= 64) { return text } return `${text.slice(0, 64)}...` } function resolveSystemLevelTone(level) { if (level === 'ERROR' || level === 'CRITICAL') { return 'danger' } if (level === 'WARNING' || level === 'WARN') { return 'warning' } if (level === 'INFO') { return 'info' } return 'muted' } function resolveSystemOutcomeTone(outcome) { if (outcome === '失败') { return 'danger' } if (outcome === '异常' || outcome === '告警') { return 'warning' } if (outcome === '成功') { return 'success' } return 'muted' } export default { name: 'LogsView', components: { EnterpriseListPage }, emits: ['summary-change'], setup(_, { emit }) { const router = useRouter() const { currentUser } = useSystemState() const { toast } = useToast() const systemLogLoading = ref(false) const systemSearchKeyword = ref('') const systemLevelFilter = ref('') const systemEventTypeFilter = ref('') const systemLogEntries = ref([]) const openFilterKey = ref('') const currentPage = ref(1) const pageSize = ref(10) const pageSizes = [10, 20, 50] const pageSizeOptions = pageSizes.map((size) => ({ label: `${size} 条/页`, value: size })) let pollTimer = 0 const isAdmin = computed(() => isManagerUser(currentUser.value)) const systemLevelOptions = computed(() => Array.from(new Set(systemLogEntries.value.map((entry) => entry.level).filter(Boolean))) ) const systemEventTypeOptions = computed(() => Array.from(new Set(systemLogEntries.value.map((entry) => entry.event_type).filter(Boolean))) ) const systemLevelFilterOptions = computed(() => [ { label: '全部级别', value: '' }, ...systemLevelOptions.value.map((level) => ({ label: level, value: level })) ]) const systemEventTypeFilterOptions = computed(() => [ { label: '全部类型', value: '' }, ...systemEventTypeOptions.value.map((eventType) => ({ label: eventType, value: eventType })) ]) const systemLevelFilterLabel = computed(() => systemLevelFilterOptions.value.find((item) => item.value === systemLevelFilter.value)?.label || '全部级别' ) const systemEventTypeFilterLabel = computed(() => systemEventTypeFilterOptions.value.find((item) => item.value === systemEventTypeFilter.value)?.label || '全部类型' ) const hasActiveFilters = computed(() => Boolean(systemSearchKeyword.value.trim() || systemLevelFilter.value || systemEventTypeFilter.value) ) const filteredSystemLogEntries = computed(() => { const keyword = systemSearchKeyword.value.trim().toLowerCase() return systemLogEntries.value.filter((entry) => { if (systemLevelFilter.value && entry.level !== systemLevelFilter.value) { return false } if (systemEventTypeFilter.value && entry.event_type !== systemEventTypeFilter.value) { return false } if (!keyword) { return true } const haystack = [ entry.summary, entry.message, entry.logger, entry.request_id, entry.path, entry.event_type, entry.outcome, entry.source_file ] .filter(Boolean) .join(' ') .toLowerCase() return haystack.includes(keyword) }) }) const totalCount = computed(() => filteredSystemLogEntries.value.length) const errorCount = computed(() => filteredSystemLogEntries.value.filter((entry) => ['ERROR', 'CRITICAL'].includes(entry.level)).length ) const warningCount = computed(() => filteredSystemLogEntries.value.filter((entry) => ['WARNING', 'WARN'].includes(entry.level)).length ) const infoCount = computed(() => filteredSystemLogEntries.value.filter((entry) => entry.level === 'INFO').length ) const totalPages = computed(() => Math.max(1, Math.ceil(totalCount.value / pageSize.value))) const visiblePageItems = computed(() => { if (totalPages.value <= 6) { return Array.from({ length: totalPages.value }, (_, index) => index + 1) } return [1, 2, 3, 4, 5, 'ellipsis', totalPages.value] }) const visibleSystemLogEntries = computed(() => { const start = (currentPage.value - 1) * pageSize.value return filteredSystemLogEntries.value.slice(start, start + pageSize.value) }) function changePageSize(size) { pageSize.value = size currentPage.value = 1 } function toggleFilter(key) { openFilterKey.value = openFilterKey.value === key ? '' : key } function selectLevelFilter(value) { systemLevelFilter.value = value openFilterKey.value = '' } function selectEventTypeFilter(value) { systemEventTypeFilter.value = value openFilterKey.value = '' } function resetFilters() { systemSearchKeyword.value = '' systemLevelFilter.value = '' systemEventTypeFilter.value = '' openFilterKey.value = '' } async function loadSystemLogs(showToast = false) { if (!isAdmin.value) { return } systemLogLoading.value = true try { const payload = await fetchSystemLogEntries(300) systemLogEntries.value = Array.isArray(payload) ? payload : [] } catch (error) { if (showToast) { toast(error.message || '系统日志加载失败。') } } finally { systemLogLoading.value = false } } function selectSystemLog(entryId) { router.push({ name: 'app-log-detail', params: { logKind: 'system', logId: entryId } }) } function startPolling() { stopPolling() pollTimer = window.setInterval(() => { loadSystemLogs(false) }, AGENT_RUN_POLL_INTERVAL_MS) } function stopPolling() { if (pollTimer) { window.clearInterval(pollTimer) pollTimer = 0 } } watch( [systemSearchKeyword, systemLevelFilter, systemEventTypeFilter], () => { currentPage.value = 1 } ) watch(totalPages, (value) => { if (currentPage.value > value) { currentPage.value = value } }) watch( () => [totalCount.value, errorCount.value, warningCount.value, infoCount.value], ([total, errors, warnings, info]) => { emit('summary-change', { total, errors, warnings, info }) }, { immediate: true } ) onMounted(async () => { await loadSystemLogs(false) startPolling() }) onBeforeUnmount(() => { stopPolling() }) return { changePageSize, currentPage, filteredSystemLogEntries, formatDateTime, formatSummary, hasActiveFilters, isAdmin, loadSystemLogs, openFilterKey, pageSize, pageSizeOptions, resetFilters, resolveSystemLevelTone, resolveSystemOutcomeTone, selectEventTypeFilter, selectLevelFilter, selectSystemLog, toggleFilter, systemEventTypeFilter, systemEventTypeFilterLabel, systemEventTypeFilterOptions, systemEventTypeOptions, systemLevelFilter, systemLevelFilterLabel, systemLevelFilterOptions, systemLevelOptions, systemLogEntries, systemLogLoading, systemSearchKeyword, totalCount, totalPages, visiblePageItems, visibleSystemLogEntries } } }