优化流式传输&fix bugs
This commit is contained in:
@@ -843,6 +843,140 @@ var FilterPopover = class {
|
||||
}
|
||||
};
|
||||
|
||||
// frontend/js/pages/logs/systemLog.js
|
||||
var SystemLogTerminal = class {
|
||||
constructor(container, controlsContainer) {
|
||||
this.container = container;
|
||||
this.controlsContainer = controlsContainer;
|
||||
this.ws = null;
|
||||
this.isPaused = false;
|
||||
this.shouldAutoScroll = true;
|
||||
this.reconnectAttempts = 0;
|
||||
this.maxReconnectAttempts = 5;
|
||||
this.elements = {
|
||||
output: this.container.querySelector("#log-terminal-output"),
|
||||
statusIndicator: this.controlsContainer.querySelector("#terminal-status-indicator"),
|
||||
clearBtn: this.controlsContainer.querySelector('[data-action="clear-terminal"]'),
|
||||
pauseBtn: this.controlsContainer.querySelector('[data-action="toggle-pause-terminal"]'),
|
||||
scrollBtn: this.controlsContainer.querySelector('[data-action="toggle-scroll-terminal"]'),
|
||||
disconnectBtn: this.controlsContainer.querySelector('[data-action="disconnect-terminal"]')
|
||||
};
|
||||
this._initEventListeners();
|
||||
}
|
||||
_initEventListeners() {
|
||||
this.elements.clearBtn.addEventListener("click", () => this.clear());
|
||||
this.elements.pauseBtn.addEventListener("click", () => this.togglePause());
|
||||
this.elements.scrollBtn.addEventListener("click", () => this.toggleAutoScroll());
|
||||
this.elements.disconnectBtn.addEventListener("click", () => this.disconnect());
|
||||
}
|
||||
connect() {
|
||||
this.clear();
|
||||
this._appendMessage("info", "\u6B63\u5728\u8FDE\u63A5\u5230\u5B9E\u65F6\u65E5\u5FD7\u6D41...");
|
||||
this._updateStatus("connecting", "\u8FDE\u63A5\u4E2D...");
|
||||
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||
const wsUrl = `${protocol}//${window.location.host}/ws/system-logs`;
|
||||
this.ws = new WebSocket(wsUrl);
|
||||
this.ws.onopen = () => {
|
||||
this._appendMessage("info", "\u2713 \u5DF2\u8FDE\u63A5\u5230\u7CFB\u7EDF\u65E5\u5FD7\u6D41");
|
||||
this._updateStatus("connected", "\u5DF2\u8FDE\u63A5");
|
||||
this.reconnectAttempts = 0;
|
||||
};
|
||||
this.ws.onmessage = (event) => {
|
||||
if (this.isPaused) return;
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
const levelColors = {
|
||||
"error": "text-red-500",
|
||||
"warning": "text-yellow-400",
|
||||
"info": "text-blue-400",
|
||||
"debug": "text-zinc-400"
|
||||
};
|
||||
const color = levelColors[data.level] || "text-zinc-200";
|
||||
const timestamp = new Date(data.timestamp).toLocaleTimeString();
|
||||
const msg = `[${timestamp}] [${data.level.toUpperCase()}] ${data.message}`;
|
||||
this._appendMessage(color, msg);
|
||||
} catch (e) {
|
||||
this._appendMessage("text-zinc-200", event.data);
|
||||
}
|
||||
};
|
||||
this.ws.onerror = (error) => {
|
||||
this._appendMessage("error", `\u2717 WebSocket \u9519\u8BEF`);
|
||||
this._updateStatus("error", "\u8FDE\u63A5\u9519\u8BEF");
|
||||
};
|
||||
this.ws.onclose = () => {
|
||||
this._appendMessage("error", "\u2717 \u8FDE\u63A5\u5DF2\u65AD\u5F00");
|
||||
this._updateStatus("disconnected", "\u672A\u8FDE\u63A5");
|
||||
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||
this.reconnectAttempts++;
|
||||
setTimeout(() => {
|
||||
this._appendMessage("info", `\u5C1D\u8BD5\u91CD\u65B0\u8FDE\u63A5 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
|
||||
this.connect();
|
||||
}, 3e3);
|
||||
}
|
||||
};
|
||||
}
|
||||
disconnect() {
|
||||
if (this.ws) {
|
||||
this.ws.close();
|
||||
this.ws = null;
|
||||
}
|
||||
this.reconnectAttempts = this.maxReconnectAttempts;
|
||||
this._updateStatus("disconnected", "\u672A\u8FDE\u63A5");
|
||||
}
|
||||
clear() {
|
||||
if (this.elements.output) {
|
||||
this.elements.output.innerHTML = "";
|
||||
}
|
||||
}
|
||||
togglePause() {
|
||||
this.isPaused = !this.isPaused;
|
||||
const span = this.elements.pauseBtn.querySelector("span");
|
||||
const icon = this.elements.pauseBtn.querySelector("i");
|
||||
if (this.isPaused) {
|
||||
span.textContent = "\u7EE7\u7EED";
|
||||
icon.classList.replace("fa-pause", "fa-play");
|
||||
} else {
|
||||
span.textContent = "\u6682\u505C";
|
||||
icon.classList.replace("fa-play", "fa-pause");
|
||||
}
|
||||
}
|
||||
toggleAutoScroll() {
|
||||
this.shouldAutoScroll = !this.shouldAutoScroll;
|
||||
const span = this.elements.scrollBtn.querySelector("span");
|
||||
if (this.shouldAutoScroll) {
|
||||
span.textContent = "\u81EA\u52A8\u6EDA\u52A8";
|
||||
} else {
|
||||
span.textContent = "\u624B\u52A8\u6EDA\u52A8";
|
||||
}
|
||||
}
|
||||
_appendMessage(colorClass, text) {
|
||||
if (!this.elements.output) return;
|
||||
const p = document.createElement("p");
|
||||
p.className = colorClass;
|
||||
p.textContent = text;
|
||||
this.elements.output.appendChild(p);
|
||||
if (this.shouldAutoScroll) {
|
||||
this.elements.output.scrollTop = this.elements.output.scrollHeight;
|
||||
}
|
||||
}
|
||||
_updateStatus(status, text) {
|
||||
const indicator = this.elements.statusIndicator.querySelector("span.relative");
|
||||
const statusText = this.elements.statusIndicator.childNodes[2];
|
||||
const colors = {
|
||||
"connecting": "bg-yellow-500",
|
||||
"connected": "bg-green-500",
|
||||
"disconnected": "bg-zinc-500",
|
||||
"error": "bg-red-500"
|
||||
};
|
||||
indicator.querySelectorAll("span").forEach((span) => {
|
||||
span.className = span.className.replace(/bg-\w+-\d+/g, colors[status] || colors.disconnected);
|
||||
});
|
||||
if (statusText) {
|
||||
statusText.textContent = ` ${text}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// frontend/js/pages/logs/index.js
|
||||
var dataStore = {
|
||||
groups: /* @__PURE__ */ new Map(),
|
||||
@@ -854,7 +988,6 @@ var LogsPage = class {
|
||||
logs: [],
|
||||
pagination: { page: 1, pages: 1, total: 0, page_size: 20 },
|
||||
isLoading: true,
|
||||
// [优化] 统一将所有可筛选字段在此处初始化,便于管理
|
||||
filters: {
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
@@ -864,36 +997,109 @@ var LogsPage = class {
|
||||
error_types: /* @__PURE__ */ new Set(),
|
||||
status_codes: /* @__PURE__ */ new Set()
|
||||
},
|
||||
selectedLogIds: /* @__PURE__ */ new Set()
|
||||
selectedLogIds: /* @__PURE__ */ new Set(),
|
||||
currentView: "error"
|
||||
};
|
||||
this.elements = {
|
||||
tableBody: document.getElementById("logs-table-body"),
|
||||
selectedCount: document.querySelector(".flex-1.text-sm span.font-semibold:nth-child(1)"),
|
||||
totalCount: document.querySelector(".flex-1.text-sm span:last-child"),
|
||||
pageSizeSelect: document.querySelector('[data-component="custom-select-v2"] select'),
|
||||
pageInfo: document.querySelector(".flex.w-\\[100px\\]"),
|
||||
paginationBtns: document.querySelectorAll("[data-pagination-controls] button"),
|
||||
selectAllCheckbox: document.querySelector('thead .table-head-cell input[type="checkbox"]'),
|
||||
searchInput: document.getElementById("log-search-input"),
|
||||
errorTypeFilterBtn: document.getElementById("filter-error-type-btn"),
|
||||
errorCodeFilterBtn: document.getElementById("filter-error-code-btn")
|
||||
tabsContainer: document.querySelector("[data-sliding-tabs-container]"),
|
||||
contentContainer: document.getElementById("log-content-container"),
|
||||
errorFilters: document.getElementById("error-logs-filters"),
|
||||
systemControls: document.getElementById("system-logs-controls"),
|
||||
errorTemplate: document.getElementById("error-logs-template"),
|
||||
systemTemplate: document.getElementById("system-logs-template")
|
||||
};
|
||||
this.initialized = !!this.elements.tableBody;
|
||||
this.initialized = !!this.elements.contentContainer;
|
||||
if (this.initialized) {
|
||||
this.logList = new logList_default(this.elements.tableBody, dataStore);
|
||||
const selectContainer = document.querySelector('[data-component="custom-select-v2"]');
|
||||
if (selectContainer) {
|
||||
new CustomSelectV2(selectContainer);
|
||||
}
|
||||
this.logList = null;
|
||||
this.systemLogTerminal = null;
|
||||
this.debouncedLoadAndRender = debounce(() => this.loadAndRenderLogs(), 300);
|
||||
}
|
||||
}
|
||||
async init() {
|
||||
if (!this.initialized) return;
|
||||
this._initPermanentEventListeners();
|
||||
await this.loadGroupsOnce();
|
||||
this.state.currentView = null;
|
||||
this.switchToView("error");
|
||||
}
|
||||
_initPermanentEventListeners() {
|
||||
this.elements.tabsContainer.addEventListener("click", (event) => {
|
||||
const tabItem = event.target.closest("[data-tab-target]");
|
||||
if (!tabItem) return;
|
||||
event.preventDefault();
|
||||
const viewName = tabItem.dataset.tabTarget;
|
||||
if (viewName) {
|
||||
this.switchToView(viewName);
|
||||
}
|
||||
});
|
||||
}
|
||||
switchToView(viewName) {
|
||||
if (this.state.currentView === viewName && this.elements.contentContainer.innerHTML !== "") return;
|
||||
if (this.systemLogTerminal) {
|
||||
this.systemLogTerminal.disconnect();
|
||||
this.systemLogTerminal = null;
|
||||
}
|
||||
this.state.currentView = viewName;
|
||||
this.elements.contentContainer.innerHTML = "";
|
||||
if (viewName === "error") {
|
||||
this.elements.errorFilters.classList.remove("hidden");
|
||||
this.elements.systemControls.classList.add("hidden");
|
||||
const template = this.elements.errorTemplate.content.cloneNode(true);
|
||||
this.elements.contentContainer.appendChild(template);
|
||||
requestAnimationFrame(() => {
|
||||
this._initErrorLogView();
|
||||
});
|
||||
} else if (viewName === "system") {
|
||||
this.elements.errorFilters.classList.add("hidden");
|
||||
this.elements.systemControls.classList.remove("hidden");
|
||||
const template = this.elements.systemTemplate.content.cloneNode(true);
|
||||
this.elements.contentContainer.appendChild(template);
|
||||
requestAnimationFrame(() => {
|
||||
this._initSystemLogView();
|
||||
});
|
||||
}
|
||||
}
|
||||
_initErrorLogView() {
|
||||
this.elements.tableBody = document.getElementById("logs-table-body");
|
||||
this.elements.selectedCount = document.querySelector(".flex-1.text-sm span.font-semibold:nth-child(1)");
|
||||
this.elements.totalCount = document.querySelector(".flex-1.text-sm span:last-child");
|
||||
this.elements.pageSizeSelect = document.querySelector('[data-component="custom-select-v2"] select');
|
||||
this.elements.pageInfo = document.querySelector(".flex.w-\\[100px\\]");
|
||||
this.elements.paginationBtns = document.querySelectorAll("[data-pagination-controls] button");
|
||||
this.elements.selectAllCheckbox = document.querySelector('thead .table-head-cell input[type="checkbox"]');
|
||||
this.elements.searchInput = document.getElementById("log-search-input");
|
||||
this.elements.errorTypeFilterBtn = document.getElementById("filter-error-type-btn");
|
||||
this.elements.errorCodeFilterBtn = document.getElementById("filter-error-code-btn");
|
||||
this.logList = new logList_default(this.elements.tableBody, dataStore);
|
||||
const selectContainer = document.querySelector('[data-component="custom-select-v2"]');
|
||||
if (selectContainer) {
|
||||
new CustomSelectV2(selectContainer);
|
||||
}
|
||||
this.initFilterPopovers();
|
||||
this.initEventListeners();
|
||||
await this.loadGroupsOnce();
|
||||
await this.loadAndRenderLogs();
|
||||
this.loadAndRenderLogs();
|
||||
}
|
||||
_initSystemLogView() {
|
||||
this.systemLogTerminal = new SystemLogTerminal(
|
||||
this.elements.contentContainer,
|
||||
this.elements.systemControls
|
||||
);
|
||||
Swal.fire({
|
||||
title: "\u5B9E\u65F6\u7CFB\u7EDF\u65E5\u5FD7",
|
||||
text: "\u60A8\u5373\u5C06\u8FDE\u63A5\u5230\u5B9E\u65F6\u65E5\u5FD7\u6D41\u3002\u8FD9\u4F1A\u4E0E\u670D\u52A1\u5668\u5EFA\u7ACB\u4E00\u4E2A\u6301\u7EED\u7684\u8FDE\u63A5\u3002",
|
||||
icon: "info",
|
||||
confirmButtonText: "\u6211\u660E\u767D\u4E86\uFF0C\u5F00\u59CB\u8FDE\u63A5",
|
||||
showCancelButton: true,
|
||||
cancelButtonText: "\u53D6\u6D88",
|
||||
target: "#main-content-wrapper"
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
this.systemLogTerminal.connect();
|
||||
} else {
|
||||
const errorLogTab = Array.from(this.elements.tabsContainer.querySelectorAll('[data-tab-target="error"]'))[0];
|
||||
if (errorLogTab) errorLogTab.click();
|
||||
}
|
||||
});
|
||||
}
|
||||
initFilterPopovers() {
|
||||
const errorTypeOptions = [
|
||||
@@ -182,7 +182,7 @@ var pageModules = {
|
||||
// esbuild 看到这个 import() 语法,就会自动将 dashboard.js 及其依赖打包成一个独立的 chunk 文件
|
||||
"dashboard": () => import("./dashboard-XFUWX3IN.js"),
|
||||
"keys": () => import("./keys-HRP4JR7B.js"),
|
||||
"logs": () => import("./logs-OFCAHOEI.js")
|
||||
"logs": () => import("./logs-43KF5HY3.js")
|
||||
// 'settings': () => import('./pages/settings.js'), // 未来启用 settings 页面
|
||||
// 未来新增的页面,只需在这里添加一行映射,esbuild会自动处理
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user