114 lines
4.0 KiB
JavaScript
114 lines
4.0 KiB
JavaScript
window.LogViewer = {
|
|
setup() {
|
|
const { ref, computed, onMounted, onUnmounted, nextTick, watch } = Vue;
|
|
const { connected, logs, paused, connect, clear, togglePause, destroy } = useWebSocket();
|
|
|
|
const filterText = ref('');
|
|
const containerRef = ref(null);
|
|
|
|
function onWheel(event) {
|
|
const el = containerRef.value;
|
|
if (!el) return;
|
|
|
|
const atTop = el.scrollTop <= 0;
|
|
const atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 1;
|
|
const scrollingUp = event.deltaY < 0;
|
|
const scrollingDown = event.deltaY > 0;
|
|
|
|
// 防止日志容器触底/触顶时把滚轮事件传递给外层页面
|
|
if ((scrollingUp && atTop) || (scrollingDown && atBottom)) {
|
|
event.preventDefault();
|
|
}
|
|
event.stopPropagation();
|
|
}
|
|
|
|
const filteredLogs = computed(() => {
|
|
const kw = filterText.value.toLowerCase();
|
|
if (!kw) return logs.value;
|
|
return logs.value.filter(line => line.toLowerCase().includes(kw));
|
|
});
|
|
|
|
watch(filteredLogs, () => {
|
|
if (!paused.value) {
|
|
nextTick(() => {
|
|
if (containerRef.value) {
|
|
containerRef.value.scrollTop = containerRef.value.scrollHeight;
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
const levelColors = {
|
|
DEBUG: 'var(--el-text-color-placeholder)',
|
|
INFO: 'var(--el-color-primary)',
|
|
SUCCESS: 'var(--el-color-success)',
|
|
WARNING: 'var(--el-color-warning)',
|
|
ERROR: 'var(--el-color-danger)',
|
|
CRITICAL: '#b42318',
|
|
};
|
|
|
|
function colorize(raw) {
|
|
let s = raw
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(
|
|
/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/,
|
|
'<span style="color:var(--el-text-color-placeholder)">$1</span>'
|
|
);
|
|
const m = raw.match(/\|\s*(DEBUG|INFO|SUCCESS|WARNING|ERROR|CRITICAL)\s*\|/);
|
|
if (m) {
|
|
s = s.replace(
|
|
/\|\s*(DEBUG|INFO|SUCCESS|WARNING|ERROR|CRITICAL)\s*\|/,
|
|
'| <span style="color:' + levelColors[m[1]] + '">' + m[1] + '</span> |'
|
|
);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
onMounted(() => {
|
|
connect();
|
|
if (containerRef.value) {
|
|
containerRef.value.addEventListener('wheel', onWheel, { passive: false });
|
|
}
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
if (containerRef.value) {
|
|
containerRef.value.removeEventListener('wheel', onWheel);
|
|
}
|
|
destroy();
|
|
});
|
|
|
|
return {
|
|
filterText, paused, connected, filteredLogs,
|
|
containerRef, togglePause, clear, colorize
|
|
};
|
|
},
|
|
template: `
|
|
<div class="panel-page">
|
|
<div class="log-toolbar">
|
|
<el-input v-model="filterText" placeholder="搜索过滤..." clearable
|
|
class="log-search" size="small">
|
|
<template #prefix><el-icon><Search /></el-icon></template>
|
|
</el-input>
|
|
<el-button size="small" :type="paused ? 'primary' : ''" @click="togglePause">
|
|
{{ paused ? '恢复' : '暂停' }}
|
|
</el-button>
|
|
<el-button size="small" @click="clear">清空</el-button>
|
|
<span class="log-status" :class="connected ? 'is-online' : 'is-offline'">
|
|
<span class="status-dot"></span>
|
|
{{ connected ? '已连接' : '已断开' }}
|
|
</span>
|
|
</div>
|
|
<div ref="containerRef"
|
|
class="log-stream">
|
|
<div v-for="(line, i) in filteredLogs" :key="i"
|
|
class="log-line"
|
|
v-html="colorize(line)">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`
|
|
};
|