mirror of
https://github.com/Genshin-bots/gsuid_core.git
synced 2025-06-18 21:35:08 +08:00
🎨 优化网页控制台
中实时日志
模块的显示效果
This commit is contained in:
parent
f55dc2d8df
commit
b9232bf1d7
@ -1,5 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
import json
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import datetime
|
import datetime
|
||||||
@ -131,16 +132,16 @@ def std_format_event(record):
|
|||||||
try:
|
try:
|
||||||
data = format_event(record)
|
data = format_event(record)
|
||||||
_data = (
|
_data = (
|
||||||
data.replace('<g>', '\033[37m')
|
data.replace("<g>", "<span class=\"log-keyword-success\">")
|
||||||
.replace('</g>', '\033[0m')
|
.replace("</g>", "</span>")
|
||||||
.replace('<c><u>', '\033[34m')
|
.replace("<c><u>", "<span class=\"log-keyword-id\">")
|
||||||
.replace('</u></c>', '\033[0m')
|
.replace("</u></c>", "</span>")
|
||||||
.replace('<m><b>', '\033[35m')
|
.replace("<m><b>", "<span> class=\"log-keyword-blue\">")
|
||||||
.replace('</b></m>', '\033[0m')
|
.replace("</b></m>", "</span>")
|
||||||
.replace('<c><b>', '\033[32m')
|
.replace("<c><b>", "<span log-keyword-error>")
|
||||||
.replace('</b></c>', '\033[0m')
|
.replace("</b></c>", "</span>")
|
||||||
.replace('<lvl>', '')
|
.replace("<lvl>", "")
|
||||||
.replace('</lvl>', '')
|
.replace("</lvl>", "")
|
||||||
)
|
)
|
||||||
log = _data.format_map(record)
|
log = _data.format_map(record)
|
||||||
log_history.append(log[:-5])
|
log_history.append(log[:-5])
|
||||||
@ -186,7 +187,13 @@ async def read_log():
|
|||||||
while True:
|
while True:
|
||||||
if index <= len(log_history) - 1:
|
if index <= len(log_history) - 1:
|
||||||
if log_history[index]:
|
if log_history[index]:
|
||||||
yield log_history[index]
|
log_data = {
|
||||||
|
"level": "INFO",
|
||||||
|
"message": log_history[index],
|
||||||
|
"message_type": "html",
|
||||||
|
"timestamp": datetime.datetime.now().isoformat(),
|
||||||
|
}
|
||||||
|
yield f"data: {json.dumps(log_data)}\n\n"
|
||||||
index += 1
|
index += 1
|
||||||
else:
|
else:
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
@ -315,7 +315,7 @@ def install_dependencies(dependencies: Dict, need_update: bool = False):
|
|||||||
installed_dependencies, dependencies
|
installed_dependencies, dependencies
|
||||||
)
|
)
|
||||||
if not to_update:
|
if not to_update:
|
||||||
logger.debug('[安装/更新依赖] 无需更新依赖!')
|
logger.debug('🚀 [安装/更新依赖] 无需更新依赖!')
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.debug(f'[安装/更新依赖] 需更新依赖列表如下:\n{to_update}')
|
logger.debug(f'[安装/更新依赖] 需更新依赖列表如下:\n{to_update}')
|
||||||
|
@ -510,8 +510,9 @@ async def get_image(image_id: str, background_tasks: BackgroundTasks):
|
|||||||
|
|
||||||
|
|
||||||
@app.get("/corelogs")
|
@app.get("/corelogs")
|
||||||
async def core_log():
|
@site.auth.requires('root')
|
||||||
return StreamingResponse(read_log(), media_type='text/plain')
|
async def core_log(request: Request):
|
||||||
|
return StreamingResponse(read_log(), media_type='text/event-stream')
|
||||||
|
|
||||||
|
|
||||||
@app.post('/genshinuid/api/loadData/{bot_id}/{bot_self_id}')
|
@app.post('/genshinuid/api/loadData/{bot_id}/{bot_self_id}')
|
||||||
|
216
gsuid_core/webconsole/log.py
Normal file
216
gsuid_core/webconsole/log.py
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
HIGHLIGHT = 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0'
|
||||||
|
|
||||||
|
|
||||||
|
def render_html():
|
||||||
|
return {
|
||||||
|
"type": "page",
|
||||||
|
"title": "",
|
||||||
|
"css": f"{HIGHLIGHT}/styles/atom-one-dark.min.css",
|
||||||
|
"js": [
|
||||||
|
f"{HIGHLIGHT}/highlight.min.js",
|
||||||
|
f"{HIGHLIGHT}/languages/json.min.js",
|
||||||
|
],
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"type": "custom",
|
||||||
|
"id": "log_viewer",
|
||||||
|
"html": HTML,
|
||||||
|
"onMount": ON_MOUNT_SSE,
|
||||||
|
"onUnmount": ON_UNMOUNT_SSE,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
HTML = """
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--theme-color: #ce5050;
|
||||||
|
--background-color: #1e1e1e;
|
||||||
|
--text-color: #d4d4d4;
|
||||||
|
--log-bg-color: #252526;
|
||||||
|
--border-color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
#log-container-inner {
|
||||||
|
height: 75vh; /* 可以适当增加高度 */
|
||||||
|
background-color: var(--log-bg-color);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 12px; /* 大幅减小内边距 */
|
||||||
|
overflow-y: auto;
|
||||||
|
font-family: 'Consolas', 'Monaco', 'Menlo', monospace;
|
||||||
|
/* 减小字体和行高以容纳更多行 */
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- 核心改动:日志条目的新样式 --- */
|
||||||
|
.log-entry {
|
||||||
|
padding: 3px 0; /* 减小每个条目的垂直内边距 */
|
||||||
|
margin: 0;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
white-space: normal; /* 允许自动换行 */
|
||||||
|
}
|
||||||
|
.log-entry:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 将元数据(时间、级别)变为行内元素 */
|
||||||
|
.log-time {
|
||||||
|
margin-right: 12px;
|
||||||
|
color: #888;
|
||||||
|
display: inline; /* 变为行内元素 */
|
||||||
|
}
|
||||||
|
.log-level {
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 1px 5px; /* 减小内边距 */
|
||||||
|
border-radius: 3px;
|
||||||
|
color: #fff;
|
||||||
|
margin-right: 10px;
|
||||||
|
display: inline-block; /* 允许设置padding */
|
||||||
|
font-size: 12px; /* 可以比正文小一点 */
|
||||||
|
}
|
||||||
|
.log-level.INFO { background-color: #2196F3; }
|
||||||
|
.log-level.WARN { background-color: #FFC107; color: #333; }
|
||||||
|
.log-level.ERROR { background-color: #F44336; }
|
||||||
|
|
||||||
|
/* 日志内容样式 */
|
||||||
|
.log-content {
|
||||||
|
display: inline; /* 核心:让内容跟在级别后面 */
|
||||||
|
word-break: break-all; /* 强制长单词换行 */
|
||||||
|
}
|
||||||
|
/* 对于普通文本的code标签 */
|
||||||
|
.log-content > code {
|
||||||
|
white-space: pre-wrap; /* 允许普通文本内容自动换行 */
|
||||||
|
}
|
||||||
|
/* 对于JSON的pre标签,它会自然成为块级元素,单独占行 */
|
||||||
|
.log-content > pre {
|
||||||
|
margin: 4px 0 0 0;
|
||||||
|
padding: 5px 8px;
|
||||||
|
background-color: rgba(0,0,0,0.2);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.log-content > pre > code.hljs {
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 关键词染色样式,保持不变 */
|
||||||
|
.log-keyword-error { color: #ff8a80; font-weight: bold; }
|
||||||
|
.log-keyword-success { color: #b9f6ca; font-weight: bold; }
|
||||||
|
.log-keyword-blue { color: #407dff; font-weight: bold; }
|
||||||
|
.log-keyword-id { color: #82aaff; background-color: #333a4f; padding: 1px 4px; border-radius: 3px; }
|
||||||
|
|
||||||
|
/* --- 页面头部样式,可以稍微紧凑一点 --- */
|
||||||
|
.log-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 2px solid var(--theme-color); padding-bottom: 8px; margin-bottom: 12px; }
|
||||||
|
.log-header h1 { color: var(--theme-color); margin: 0; font-size: 20px; font-family: 'Microsoft YaHei', sans-serif; }
|
||||||
|
.log-status { font-size: 14px; font-family: Arial, sans-serif;}
|
||||||
|
.log-status-indicator { width: 10px; height: 10px; margin-right: 6px; }
|
||||||
|
</style>
|
||||||
|
<div class="log-header">
|
||||||
|
<h1>实时日志</h1>
|
||||||
|
<div class="log-status">
|
||||||
|
<span id="status-indicator-inner" class="log-status-indicator disconnected"></span>
|
||||||
|
<span id="status-text-inner">未连接</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="log-container-inner"></div>
|
||||||
|
""" # noqa: E501
|
||||||
|
|
||||||
|
ON_UNMOUNT_SSE = """
|
||||||
|
// 关闭 EventSource 连接
|
||||||
|
if (window.logEventSource) {
|
||||||
|
window.logEventSource.close();
|
||||||
|
}
|
||||||
|
// 清理模拟服务器的定时器
|
||||||
|
if (window.mockServerInterval) {
|
||||||
|
clearInterval(window.mockServerInterval);
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
ON_MOUNT_SSE = """
|
||||||
|
const logContainer = dom.querySelector('#log-container-inner');
|
||||||
|
const statusIndicator = dom.querySelector('#status-indicator-inner');
|
||||||
|
const statusText = dom.querySelector('#status-text-inner');
|
||||||
|
|
||||||
|
const SSE_URL = '/corelogs';
|
||||||
|
window.logEventSource = null;
|
||||||
|
|
||||||
|
function appendLog(data) {
|
||||||
|
const { timestamp, level, message, message_type } = data; // 从data中解构出新字段 message_type
|
||||||
|
const logEntry = document.createElement('div');
|
||||||
|
logEntry.className = 'log-entry';
|
||||||
|
const time = new Date(timestamp).toLocaleTimeString();
|
||||||
|
|
||||||
|
let messageHtml;
|
||||||
|
let isJson = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
JSON.parse(message);
|
||||||
|
isJson = true;
|
||||||
|
} catch (e) {
|
||||||
|
// isJson 保持 false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================= 核心修改点在这里 =======================
|
||||||
|
if (isJson) {
|
||||||
|
// 1. 如果是JSON,优先高亮显示 (逻辑不变)
|
||||||
|
const jsonObj = JSON.parse(message);
|
||||||
|
const formattedJson = JSON.stringify(jsonObj, null, 2);
|
||||||
|
messageHtml = `<pre><code class="language-json hljs">${hljs.highlight(formattedJson, {language: 'json'}).value}</code></pre>`;
|
||||||
|
|
||||||
|
} else if (message_type === 'html') {
|
||||||
|
// 2. 如果后端标记为HTML,我们信任它,直接使用 message
|
||||||
|
// 不再进行HTML转义,这样<span>标签就能生效
|
||||||
|
messageHtml = `<code>${message}</code>`;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// 3. 否则,就是未知来源的纯文本,为了安全必须进行转义
|
||||||
|
const safeMessage = message.replace(/</g, "<").replace(/>/g, ">");
|
||||||
|
messageHtml = `<code>${safeMessage}</code>`;
|
||||||
|
}
|
||||||
|
// ======================= 修改结束 =======================
|
||||||
|
|
||||||
|
logEntry.innerHTML = `
|
||||||
|
<span class="log-time">${time}</span>
|
||||||
|
<span class="log-level ${level}">${level}</span>
|
||||||
|
<span class="log-content">${messageHtml}</span>
|
||||||
|
`;
|
||||||
|
|
||||||
|
logContainer.appendChild(logEntry);
|
||||||
|
|
||||||
|
const isScrolledToBottom = logContainer.scrollHeight - logContainer.clientHeight <= logContainer.scrollTop + 5;
|
||||||
|
if (isScrolledToBottom) {
|
||||||
|
logContainer.scrollTop = logContainer.scrollHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectSSE() {
|
||||||
|
// ... 此函数内容保持不变
|
||||||
|
console.log(`尝试连接到 SSE 端点: ${SSE_URL}`);
|
||||||
|
const evtSource = new EventSource(SSE_URL);
|
||||||
|
window.logEventSource = evtSource;
|
||||||
|
evtSource.onopen = () => {
|
||||||
|
console.log('SSE 连接已建立。');
|
||||||
|
statusIndicator.className = 'log-status-indicator connected';
|
||||||
|
statusText.textContent = '已连接';
|
||||||
|
};
|
||||||
|
evtSource.onmessage = (event) => {
|
||||||
|
try {
|
||||||
|
const logData = JSON.parse(event.data);
|
||||||
|
appendLog(logData);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("解析SSE数据失败:", e);
|
||||||
|
appendLog({ level: 'INFO', message: event.data, timestamp: new Date().toISOString() });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
evtSource.onerror = (err) => {
|
||||||
|
console.error("EventSource 发生错误: ", err);
|
||||||
|
statusIndicator.className = 'log-status-indicator disconnected';
|
||||||
|
statusText.textContent = '连接中断,自动重连中...';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
connectSSE();
|
||||||
|
""" # noqa:E501
|
@ -43,13 +43,15 @@ from fastapi_amis_admin.amis.components import (
|
|||||||
ButtonToolbar,
|
ButtonToolbar,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from gsuid_core.webconsole.log import render_html
|
||||||
from gsuid_core.logger import logger, handle_exceptions
|
from gsuid_core.logger import logger, handle_exceptions
|
||||||
from gsuid_core.utils.cookie_manager.add_ck import _deal_ck
|
from gsuid_core.utils.cookie_manager.add_ck import _deal_ck
|
||||||
from gsuid_core.version import __version__ as gscore_version
|
from gsuid_core.version import __version__ as gscore_version
|
||||||
from gsuid_core.webconsole.html import gsuid_webconsole_help
|
from gsuid_core.webconsole.html import gsuid_webconsole_help
|
||||||
from gsuid_core.utils.database.base_models import finally_url
|
from gsuid_core.utils.database.base_models import finally_url
|
||||||
from gsuid_core.webconsole.create_sv_panel import get_sv_page
|
from gsuid_core.webconsole.create_sv_panel import get_sv_page
|
||||||
from gsuid_core.webconsole.create_log_panel import create_log_page
|
|
||||||
|
# from gsuid_core.webconsole.create_log_panel import create_log_page
|
||||||
from gsuid_core.webconsole.create_task_panel import get_tasks_panel
|
from gsuid_core.webconsole.create_task_panel import get_tasks_panel
|
||||||
from gsuid_core.webconsole.create_config_panel import get_config_page
|
from gsuid_core.webconsole.create_config_panel import get_config_page
|
||||||
from gsuid_core.utils.plugins_config.gs_config import core_plugins_config
|
from gsuid_core.utils.plugins_config.gs_config import core_plugins_config
|
||||||
@ -493,7 +495,7 @@ class LogsPage(GsAdminPage):
|
|||||||
|
|
||||||
@handle_exceptions
|
@handle_exceptions
|
||||||
async def get_page(self, request: Request) -> Page:
|
async def get_page(self, request: Request) -> Page:
|
||||||
return Page.parse_obj(create_log_page())
|
return Page.parse_obj(render_html())
|
||||||
|
|
||||||
|
|
||||||
class HistoryLogsPage(GsAdminPage):
|
class HistoryLogsPage(GsAdminPage):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user