first commit
This commit is contained in:
123
templates/logs.html
Normal file
123
templates/logs.html
Normal file
@@ -0,0 +1,123 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}自动签名系统 - 日志{% endblock %}
|
||||
{% block content %}
|
||||
<div style="display:flex;flex-direction:column;height:100%;">
|
||||
<div class="log-toolbar">
|
||||
<span style="font-size:13px;font-weight:600;color:#1a237e;"><i class="bi bi-terminal"></i> 系统日志</span>
|
||||
<select class="form-select" id="logSourceFilter">
|
||||
<option value="all">全部来源</option>
|
||||
<option value="前端操作">前端操作</option>
|
||||
<option value="外部API">外部API</option>
|
||||
</select>
|
||||
<select class="form-select" id="logLevelFilter">
|
||||
<option value="all">全部级别</option>
|
||||
<option value="INFO">INFO</option>
|
||||
<option value="WARN">WARN</option>
|
||||
<option value="ERROR">ERROR</option>
|
||||
</select>
|
||||
<input type="text" class="form-control" id="logKeyword" placeholder="关键字搜索" style="width:200px;">
|
||||
<div class="form-check form-check-inline ms-2">
|
||||
<input class="form-check-input" type="checkbox" id="autoScroll" checked>
|
||||
<label class="form-check-label" for="autoScroll" style="font-size:12px;">自动滚动</label>
|
||||
</div>
|
||||
<button class="btn btn-outline-secondary btn-sm" id="btnClearLog"><i class="bi bi-trash"></i> 清空</button>
|
||||
<button class="btn btn-outline-danger btn-sm" id="btnLocateError"><i class="bi bi-exclamation-triangle"></i> 定位异常</button>
|
||||
<span class="ms-auto" style="font-size:12px;color:#757575;" id="logCount">0 条</span>
|
||||
</div>
|
||||
<div class="log-content" id="logContent"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
(function() {
|
||||
const logContent = document.getElementById('logContent');
|
||||
const logSourceFilter = document.getElementById('logSourceFilter');
|
||||
const logLevelFilter = document.getElementById('logLevelFilter');
|
||||
const logKeyword = document.getElementById('logKeyword');
|
||||
const autoScroll = document.getElementById('autoScroll');
|
||||
const logCountEl = document.getElementById('logCount');
|
||||
let allLogs = [];
|
||||
let logId = 0;
|
||||
|
||||
function addLogEntry(level, source, message) {
|
||||
logId++;
|
||||
const entry = { id: logId, level, source, message, time: new Date() };
|
||||
allLogs.push(entry);
|
||||
renderEntry(entry);
|
||||
logCountEl.textContent = allLogs.length + ' 条';
|
||||
}
|
||||
|
||||
function renderEntry(entry) {
|
||||
if (!matchFilter(entry)) return;
|
||||
const div = document.createElement('div');
|
||||
div.className = `log-entry level-${entry.level}`;
|
||||
div.dataset.id = entry.id;
|
||||
div.dataset.level = entry.level;
|
||||
div.dataset.source = entry.source;
|
||||
|
||||
const sourceClass = entry.source === '外部API' ? 'log-source-api' : 'log-source-frontend';
|
||||
const timeStr = entry.time.toLocaleTimeString('zh-CN', { hour12: false });
|
||||
|
||||
div.innerHTML = `<span class="log-timestamp">[${timeStr}]</span> <span class="${sourceClass}">[${entry.source}]</span> <span style="font-weight:600;">[${entry.level}]</span> ${escapeHtml(entry.message)}`;
|
||||
logContent.appendChild(div);
|
||||
|
||||
if (autoScroll.checked) {
|
||||
logContent.scrollTop = logContent.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
function matchFilter(entry) {
|
||||
const src = logSourceFilter.value;
|
||||
const lvl = logLevelFilter.value;
|
||||
const kw = logKeyword.value.trim().toLowerCase();
|
||||
if (src !== 'all' && entry.source !== src) return false;
|
||||
if (lvl !== 'all' && entry.level !== lvl) return false;
|
||||
if (kw && !entry.message.toLowerCase().includes(kw)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function rerenderAll() {
|
||||
logContent.innerHTML = '';
|
||||
allLogs.forEach(entry => renderEntry(entry));
|
||||
}
|
||||
|
||||
logSourceFilter.addEventListener('change', rerenderAll);
|
||||
logLevelFilter.addEventListener('change', rerenderAll);
|
||||
logKeyword.addEventListener('input', rerenderAll);
|
||||
|
||||
document.getElementById('btnClearLog').addEventListener('click', function() {
|
||||
allLogs = [];
|
||||
logContent.innerHTML = '';
|
||||
logCountEl.textContent = '0 条';
|
||||
});
|
||||
|
||||
document.getElementById('btnLocateError').addEventListener('click', function() {
|
||||
const errorEntries = logContent.querySelectorAll('.level-ERROR');
|
||||
if (errorEntries.length > 0) {
|
||||
errorEntries.forEach(e => e.classList.remove('highlight-error'));
|
||||
const last = errorEntries[errorEntries.length - 1];
|
||||
last.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
last.classList.add('highlight-error');
|
||||
} else {
|
||||
alert('没有发现异常日志');
|
||||
}
|
||||
});
|
||||
|
||||
const evtSource = new EventSource('/api/logs/stream');
|
||||
evtSource.onmessage = function(event) {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
addLogEntry(data.level || 'INFO', data.source || '系统', data.message || '');
|
||||
} catch (e) {
|
||||
addLogEntry('INFO', '系统', event.data);
|
||||
}
|
||||
};
|
||||
evtSource.onerror = function() {
|
||||
addLogEntry('WARN', '系统', '日志流连接断开,5秒后重连...');
|
||||
};
|
||||
|
||||
addLogEntry('INFO', '系统', '日志页面已加载,正在监听系统日志...');
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user