|
|
@@ -1,14 +1,19 @@
|
|
|
let logSource = null;
|
|
|
let currentUnit = null;
|
|
|
+let autoScroll = true;
|
|
|
|
|
|
const MAX_LOG_LINES = 2000;
|
|
|
let logLines = [];
|
|
|
|
|
|
-function colorLog(line) {
|
|
|
- let s = line
|
|
|
+function escapeHtml(s) {
|
|
|
+ return s
|
|
|
.replace(/&/g, "&")
|
|
|
.replace(/</g, "<")
|
|
|
.replace(/>/g, ">");
|
|
|
+}
|
|
|
+
|
|
|
+function colorLog(line) {
|
|
|
+ const s = escapeHtml(line);
|
|
|
|
|
|
const levels = [
|
|
|
["TRACE", "log-trace"],
|
|
|
@@ -21,8 +26,8 @@ function colorLog(line) {
|
|
|
["PANIC", "log-panic"]
|
|
|
];
|
|
|
|
|
|
- for (const [level, cls] of levels) {
|
|
|
- if (s.includes(level) || s.includes(level.toLowerCase())) {
|
|
|
+ for (const [lvl, cls] of levels) {
|
|
|
+ if (s.includes(lvl) || s.includes(lvl.toLowerCase())) {
|
|
|
return `<span class="${cls}">${s}</span>`;
|
|
|
}
|
|
|
}
|
|
|
@@ -57,6 +62,15 @@ function connectLog(unit, target) {
|
|
|
|
|
|
logSource = source;
|
|
|
|
|
|
+ el.addEventListener("scroll", () => {
|
|
|
+ const distance = el.scrollHeight - el.scrollTop - el.clientHeight;
|
|
|
+ if (distance < 20) {
|
|
|
+ autoScroll = true;
|
|
|
+ } else {
|
|
|
+ autoScroll = false;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
source.onmessage = (e) => {
|
|
|
if (source !== logSource) {
|
|
|
return;
|
|
|
@@ -65,17 +79,23 @@ function connectLog(unit, target) {
|
|
|
logLines.push(e.data);
|
|
|
|
|
|
if (logLines.length > MAX_LOG_LINES) {
|
|
|
- logLines.splice(
|
|
|
- 0,
|
|
|
- logLines.length - MAX_LOG_LINES
|
|
|
- );
|
|
|
+ logLines.shift();
|
|
|
}
|
|
|
|
|
|
- el.innerHTML = logLines
|
|
|
- .map(colorLog)
|
|
|
- .join("<br>") + "<br><br><br>";
|
|
|
+ const line = document.createElement("div");
|
|
|
+ line.innerHTML = colorLog(e.data);
|
|
|
|
|
|
- el.scrollTop = el.scrollHeight;
|
|
|
+ el.appendChild(line);
|
|
|
+
|
|
|
+ while (el.childElementCount > MAX_LOG_LINES) {
|
|
|
+ el.removeChild(el.firstElementChild);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (autoScroll) {
|
|
|
+ requestAnimationFrame(() => {
|
|
|
+ el.scrollTop = el.scrollHeight;
|
|
|
+ });
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
source.onerror = () => {
|
|
|
@@ -83,7 +103,7 @@ function connectLog(unit, target) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- console.log(
|
|
|
+ console.warn(
|
|
|
`[log-stream] error unit=${unit}, state=${source.readyState}`
|
|
|
);
|
|
|
};
|
|
|
@@ -111,22 +131,18 @@ function clearLog(target) {
|
|
|
: target;
|
|
|
|
|
|
if (el) {
|
|
|
- el.textContent = "";
|
|
|
+ el.replaceChildren();
|
|
|
+ el.scrollTop = 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function copyLog(target) {
|
|
|
- const el = typeof target === "string"
|
|
|
- ? document.getElementById(target)
|
|
|
- : target;
|
|
|
-
|
|
|
- if (!el) return;
|
|
|
-
|
|
|
- const text = el.value !== undefined ? el.value : el.innerText;
|
|
|
+ const text = logLines.join("\n");
|
|
|
|
|
|
if (navigator.clipboard) {
|
|
|
navigator.clipboard.writeText(text)
|
|
|
- .then(() => alert("复制成功"));
|
|
|
+ .then(() => alert("复制成功"))
|
|
|
+ .catch(() => alert("复制失败"));
|
|
|
} else {
|
|
|
const ta = document.createElement("textarea");
|
|
|
ta.value = text;
|