feat(logs): replace API polling with WebSocket for real-time updates

Implement WebSocket connection to receive live log updates instead of periodic API polling. The changes include:
- Add WebSocket connection management with token authentication
- Implement computed property for log filtering
- Maintain only the latest 100 logs in memory
- Handle WebSocket events and errors appropriately
This commit is contained in:
2026-03-17 19:24:05 +08:00
parent 3754829b42
commit ce8376458e

View File

@@ -3,7 +3,7 @@
<div class="page-header">
<h2>System Logs</h2>
<div class="filter-controls">
<select v-model="selectedLevel" @change="loadLogs">
<select v-model="selectedLevel" @change="filterLogs">
<option value="">All Levels</option>
<option value="DEBUG">DEBUG</option>
<option value="INFO">INFO</option>
@@ -14,8 +14,8 @@
</div>
</div>
<div class="logs-container">
<div v-if="logs.length > 0" class="log-list">
<div v-for="(log, index) in logs" :key="index" class="log-item">
<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>
@@ -29,37 +29,93 @@
</template>
<script>
import { ref, onMounted } from 'vue';
import { logApi } from '../api/index.js';
import { showNotification, formatDate } from '../utils/functions.js';
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 loadLogs = async () => {
try {
const result = await logApi.getLogs(selectedLevel.value);
logs.value = result.data.map(log => ({
time: formatDate(log.timestamp),
level: log.level,
message: log.message
}));
} catch (error) {
showNotification('Failed to load logs', 'error');
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.push({
time: log.timestamp,
level: levelMap[log.level],
message: log.content
});
// Keep only the latest 100 logs
if (logs.value.length > 100) {
logs.value.shift();
}
} 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(() => {
loadLogs();
connectWebSocket();
});
onUnmounted(() => {
disconnectWebSocket();
});
return {
logs,
filteredLogs,
selectedLevel,
loadLogs
filterLogs
};
}
};