Files
crypto_monitor/manual_trading_hub/static/logs.js
T

170 lines
4.9 KiB
JavaScript

/**
* 系统日志:三所 + 中控 PM2 stdout/stderr.
*/
(function () {
const page = document.getElementById("page-logs");
if (!page) return;
const tabsEl = document.getElementById("hub-logs-tabs");
const statusEl = document.getElementById("hub-logs-status");
const outEl = document.getElementById("hub-logs-out");
const errEl = document.getElementById("hub-logs-err");
const btnRefresh = document.getElementById("hub-logs-btn-refresh");
const btnPause = document.getElementById("hub-logs-btn-pause");
const POLL_MS = 4000;
let activeKey = "binance";
let tabsMeta = [];
let pollTimer = null;
let paused = false;
let loading = false;
let bound = false;
async function apiFetch(url) {
const r = await fetch(url, { credentials: "same-origin" });
const ct = (r.headers.get("content-type") || "").toLowerCase();
if (ct.includes("application/json")) {
const data = await r.json();
if (!r.ok) throw new Error((data && (data.msg || data.detail)) || r.statusText || "请求失败");
return data;
}
if (!r.ok) throw new Error(r.statusText || "请求失败");
return r;
}
function esc(s) {
return String(s ?? "")
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
function wasScrolledToBottom(el) {
if (!el) return true;
return el.scrollHeight - el.scrollTop - el.clientHeight < 24;
}
function setPreText(el, text, stickBottom) {
if (!el) return;
const atBottom = stickBottom || wasScrolledToBottom(el);
el.textContent = text || "(暂无日志)";
if (atBottom) el.scrollTop = el.scrollHeight;
}
function setStatus(text, isErr) {
if (!statusEl) return;
statusEl.textContent = text || "";
statusEl.classList.toggle("is-err", !!isErr);
}
function renderTabs() {
if (!tabsEl) return;
tabsEl.innerHTML = tabsMeta
.map(
(t) =>
`<button type="button" class="hub-logs-tab${t.key === activeKey ? " is-active" : ""}" role="tab" aria-selected="${t.key === activeKey}" data-key="${esc(t.key)}">${esc(t.label)}</button>`
)
.join("");
tabsEl.querySelectorAll(".hub-logs-tab").forEach((btn) => {
btn.addEventListener("click", () => {
const key = btn.getAttribute("data-key");
if (!key || key === activeKey) return;
activeKey = key;
renderTabs();
void loadLogs(true);
});
});
}
async function loadMeta() {
const meta = await apiFetch("/api/system-logs/meta");
tabsMeta = Array.isArray(meta.targets) ? meta.targets : [];
if (tabsMeta.length && !tabsMeta.some((t) => t.key === activeKey)) {
activeKey = tabsMeta[0].key;
}
renderTabs();
}
async function loadLogs(force) {
if (loading && !force) return;
loading = true;
try {
const data = await apiFetch(`/api/system-logs/${encodeURIComponent(activeKey)}?lines=200`);
setPreText(outEl, data.out || "", true);
setPreText(errEl, data.err || "", true);
const ts = data.updated_at ? new Date(data.updated_at * 1000) : new Date();
const hh = String(ts.getHours()).padStart(2, "0");
const mm = String(ts.getMinutes()).padStart(2, "0");
const ss = String(ts.getSeconds()).padStart(2, "0");
const missing = [];
if (!data.out_exists) missing.push("实时");
if (!data.err_exists) missing.push("报错");
const hint = missing.length ? ` · ${missing.join("/")}日志文件暂无` : "";
setStatus(`已更新 ${hh}:${mm}:${ss}${hint}`, false);
} catch (e) {
setStatus(e.message || "加载失败", true);
} finally {
loading = false;
}
}
function startPoll() {
stopPoll();
if (paused) return;
pollTimer = window.setInterval(() => {
void loadLogs(false);
}, POLL_MS);
}
function stopPoll() {
if (pollTimer) {
clearInterval(pollTimer);
pollTimer = null;
}
}
function bindControls() {
if (bound) return;
bound = true;
if (btnRefresh) {
btnRefresh.addEventListener("click", () => {
void loadLogs(true);
});
}
if (btnPause) {
btnPause.addEventListener("click", () => {
paused = !paused;
btnPause.textContent = paused ? "继续刷新" : "暂停刷新";
btnPause.classList.toggle("is-paused", paused);
if (paused) stopPoll();
else startPoll();
});
}
}
async function init() {
bindControls();
paused = false;
if (btnPause) {
btnPause.textContent = "暂停刷新";
btnPause.classList.remove("is-paused");
}
setStatus("加载中…", false);
try {
await loadMeta();
await loadLogs(true);
startPoll();
} catch (e) {
setStatus(e.message || "初始化失败", true);
}
}
function destroy() {
stopPoll();
setStatus("", false);
}
window.hubLogsPage = { init, destroy };
})();