import { appendChartPoint } from "./chart.js";
import { dom } from "./dom.js";
import { prependEvent } from "./events.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 = [
`${escapeHtml(levelRaw || "LOG")}`,
parsed.timestamp ? ` ${escapeHtml(parsed.timestamp)}` : "",
parsed.target ? ` ${escapeHtml(parsed.target)}` : "",
`${escapeHtml(
parsed.fields?.message || parsed.message || parsed.msg || line,
)}`,
].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") {
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);
}
return;
}
if (payload.type === "EventCreated" || payload.type === "event_created") {
prependEvent(payload.data);
}
} catch {
// ignore malformed messages
}
};
ws.onclose = () => {
window.setTimeout(startPointSocket, 2000);
};
}