102 lines
2.9 KiB
JavaScript
102 lines
2.9 KiB
JavaScript
import { appendChartPoint } from "./chart.js";
|
|
import { dom } from "./dom.js";
|
|
import { formatValue } from "./points.js";
|
|
import { state } from "./state.js";
|
|
|
|
function escapeHtml(text) {
|
|
return text
|
|
.replaceAll("&", "&")
|
|
.replaceAll("<", "<")
|
|
.replaceAll(">", ">");
|
|
}
|
|
|
|
function parseLogLine(line) {
|
|
const trimmed = line.trim();
|
|
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return JSON.parse(trimmed);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function appendLog(line) {
|
|
const atBottom = dom.logView.scrollTop + dom.logView.clientHeight >= dom.logView.scrollHeight - 10;
|
|
const div = document.createElement("div");
|
|
const parsed = parseLogLine(line);
|
|
|
|
if (!parsed) {
|
|
div.className = "log-line";
|
|
div.textContent = line;
|
|
} else {
|
|
const levelRaw = (parsed.level || "").toString();
|
|
const level = levelRaw.toLowerCase();
|
|
div.className = `log-line${level ? ` level-${level}` : ""}`;
|
|
div.innerHTML = [
|
|
`<span class="level">${escapeHtml(levelRaw || "LOG")}</span>`,
|
|
parsed.timestamp ? `<span class="muted"> ${escapeHtml(parsed.timestamp)}</span>` : "",
|
|
parsed.target ? `<span class="muted"> ${escapeHtml(parsed.target)}</span>` : "",
|
|
`<span class="message">${escapeHtml(
|
|
parsed.fields?.message || parsed.message || parsed.msg || line,
|
|
)}</span>`,
|
|
].join("");
|
|
}
|
|
|
|
dom.logView.appendChild(div);
|
|
if (atBottom) {
|
|
dom.logView.scrollTop = dom.logView.scrollHeight;
|
|
}
|
|
}
|
|
|
|
export function startLogs() {
|
|
if (state.logSource) {
|
|
state.logSource.close();
|
|
}
|
|
|
|
state.logSource = new EventSource("/api/logs/stream");
|
|
state.logSource.addEventListener("log", (event) => {
|
|
const data = JSON.parse(event.data);
|
|
(data.lines || []).forEach(appendLog);
|
|
});
|
|
state.logSource.addEventListener("error", () => {
|
|
appendLog("[log stream error]");
|
|
});
|
|
}
|
|
|
|
export function startPointSocket() {
|
|
const protocol = location.protocol === "https:" ? "wss" : "ws";
|
|
const ws = new WebSocket(`${protocol}://${location.host}/ws/public`);
|
|
state.pointSocket = ws;
|
|
|
|
ws.onmessage = (event) => {
|
|
try {
|
|
const payload = JSON.parse(event.data);
|
|
if (payload.type !== "PointNewValue" && payload.type !== "point_new_value") {
|
|
return;
|
|
}
|
|
|
|
const data = payload.data;
|
|
const entry = state.pointEls.get(data.point_id);
|
|
if (entry) {
|
|
entry.value.textContent = formatValue(data);
|
|
entry.quality.className = `badge quality-${(data.quality || "unknown").toLowerCase()}`;
|
|
entry.quality.textContent = (data.quality || "unknown").toUpperCase();
|
|
entry.time.textContent = data.timestamp || "--";
|
|
}
|
|
|
|
if (state.chartPointId === data.point_id) {
|
|
appendChartPoint(data);
|
|
}
|
|
} catch {
|
|
// ignore malformed messages
|
|
}
|
|
};
|
|
|
|
ws.onclose = () => {
|
|
window.setTimeout(startPointSocket, 2000);
|
|
};
|
|
}
|