Files
frontend/src/views/Logs.vue
NanamiAdmin a020afbc50 fix(Logs): reverse log order and fix truncation logic
Change log display to show newest first by using unshift instead of push. Fix log truncation to remove oldest entries by using pop instead of shift when exceeding 100 logs.
2026-03-17 22:29:27 +08:00

222 lines
5.4 KiB
Vue

<template>
<div class="logs-page">
<div class="page-header">
<h2>System Logs</h2>
<div class="filter-controls">
<select v-model="selectedLevel" @change="filterLogs">
<option value="">All Levels</option>
<option value="DEBUG">DEBUG</option>
<option value="INFO">INFO</option>
<option value="WARN">WARN</option>
<option value="ERROR">ERROR</option>
<option value="FATAL">FATAL</option>
</select>
</div>
</div>
<div class="logs-container">
<div v-if="filteredLogs.length > 0" class="log-list">
<div v-for="(log, index) in filteredLogs" :key="index" class="log-item">
<span class="log-time">{{ log.time }}</span>
<span :class="['log-level', log.level]">{{ log.level }}</span>
<span class="log-message">{{ log.message }}</span>
</div>
</div>
<div v-else class="empty-state">
<p>No logs available</p>
</div>
</div>
</div>
</template>
<script>
import { ref, onMounted, onUnmounted, computed } from 'vue';
import { showNotification, formatDate, getCookie } from '../utils/functions.js';
export default {
name: 'Logs',
setup() {
const logs = ref([]);
const selectedLevel = ref('');
const socket = ref(null);
const levelMap = {
0: 'DEBUG',
1: 'INFO',
2: 'WARN',
3: 'ERROR',
4: 'FATAL'
};
const filteredLogs = computed(() => {
if (!selectedLevel.value) {
return logs.value;
}
return logs.value.filter(log => log.level === selectedLevel.value);
});
const filterLogs = () => {
// Filtering is handled by computed property
};
const connectWebSocket = () => {
const token = getCookie('token');
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${wsProtocol}//localhost:8080/system/getLogs?token=${token}`;
socket.value = new WebSocket(wsUrl);
socket.value.onopen = () => {
console.log('Connected to log server');
};
socket.value.onmessage = (event) => {
try {
const log = JSON.parse(event.data);
logs.value.unshift({
time: log.timestamp,
level: levelMap[log.level],
message: log.content
});
// Keep only the latest 100 logs
if (logs.value.length > 100) {
logs.value.pop();
}
} catch (error) {
console.error('Error parsing log message:', error);
}
};
socket.value.onclose = () => {
console.log('Disconnected from log server');
};
socket.value.onerror = (error) => {
console.error('WebSocket error:', error);
showNotification('Failed to connect to log server', 'error');
};
};
const disconnectWebSocket = () => {
if (socket.value) {
socket.value.close();
socket.value = null;
}
};
onMounted(() => {
connectWebSocket();
});
onUnmounted(() => {
disconnectWebSocket();
});
return {
logs,
filteredLogs,
selectedLevel,
filterLogs
};
}
};
</script>
<style scoped>
@import '../styles/common.css';
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.logs-container {
background: var(--card-bg);
border-radius: 8px 0 0 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
max-height: calc(100vh - 200px);
overflow-y: auto;
transition: background-color 0.3s;
}
.logs-container::-webkit-scrollbar {
width: 8px;
}
.logs-container::-webkit-scrollbar-track {
background: var(--bg-color);
border-radius: 0 8px 8px 0;
}
.logs-container::-webkit-scrollbar-thumb {
background: var(--primary-color);
border-radius: 4px;
}
.logs-container::-webkit-scrollbar-thumb:hover {
background: var(--sidebar-active-bg);
}
.log-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.log-item {
display: flex;
gap: 12px;
padding: 12px;
background: #f9f9f9;
border-radius: 6px;
font-size: 14px;
font-family: 'Consolas', 'Monaco', monospace;
}
.log-time {
color: #999;
min-width: 160px;
}
.log-level {
min-width: 70px;
font-weight: 600;
text-align: center;
padding: 2px 8px;
border-radius: 4px;
}
.log-level.DEBUG {
color: #52c41a;
background: #f6ffed;
}
.log-level.INFO {
color: #1890ff;
background: #e6f7ff;
}
.log-level.WARN {
color: #faad14;
background: #fffbe6;
}
.log-level.ERROR {
color: #ff4d4f;
background: #fff1f0;
}
.log-level.FATAL {
color: #cf1322;
background: #fff1f0;
}
.log-message {
color: var(--text-color);
flex: 1;
word-break: break-all;
transition: color 0.3s;
}
</style>