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