refactor(web): remove realtime log stream, compact event list to single line
- Remove SSE log stream (EventSource /api/logs/stream) and logView panel - System events panel now occupies the full bottom-middle panel - Each event renders as a single flex row: level badge, type, message, timestamp - Remove logSource from state, logView from dom, startLogs from app bootstrap Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d88d8375fd
commit
a405623ec1
|
|
@ -1,18 +1,7 @@
|
|||
<section class="panel bottom-middle">
|
||||
<div class="stack-panel">
|
||||
<div class="stack-section event-section">
|
||||
<div class="panel-head">
|
||||
<h2>系统事件</h2>
|
||||
<button type="button" class="secondary" id="refreshEventBtn">刷新</button>
|
||||
</div>
|
||||
<div class="list event-list" id="eventList"></div>
|
||||
</div>
|
||||
|
||||
<div class="stack-section stack-section-bordered log-section">
|
||||
<div class="panel-head">
|
||||
<h2>实时日志</h2>
|
||||
</div>
|
||||
<div class="log" id="logView"></div>
|
||||
</div>
|
||||
<div class="panel-head">
|
||||
<h2>系统事件</h2>
|
||||
<button type="button" class="secondary" id="refreshEventBtn">刷新</button>
|
||||
</div>
|
||||
<div class="list event-list" id="eventList"></div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import {
|
|||
resetEquipmentForm,
|
||||
saveEquipment,
|
||||
} from "./equipment.js";
|
||||
import { startLogs, startPointSocket } from "./logs.js";
|
||||
import { startPointSocket } from "./logs.js";
|
||||
import {
|
||||
clearBatchBinding,
|
||||
browseAndLoadTree,
|
||||
|
|
@ -136,7 +136,6 @@ async function bootstrap() {
|
|||
updateSelectedPointSummary();
|
||||
updatePointFilterSummary();
|
||||
renderChart();
|
||||
startLogs();
|
||||
startPointSocket();
|
||||
|
||||
await withStatus(loadUnits());
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ export const dom = {
|
|||
pointSourceSelect: byId("pointSourceSelect"),
|
||||
pointSourceNodeCount: byId("pointSourceNodeCount"),
|
||||
openPointModalBtn: byId("openPointModal"),
|
||||
logView: byId("logView"),
|
||||
chartCanvas: byId("chartCanvas"),
|
||||
chartTitle: byId("chartTitle"),
|
||||
chartSummary: byId("chartSummary"),
|
||||
|
|
|
|||
|
|
@ -19,15 +19,8 @@ export function renderEvents() {
|
|||
|
||||
state.events.forEach((item) => {
|
||||
const row = document.createElement("div");
|
||||
row.className = "list-item event-card";
|
||||
row.innerHTML = `
|
||||
<div class="row">
|
||||
<strong>${item.event_type}</strong>
|
||||
<span class="badge">${(item.level || "info").toUpperCase()}</span>
|
||||
</div>
|
||||
<div>${item.message}</div>
|
||||
<div class="muted">${formatTime(item.created_at)}</div>
|
||||
`;
|
||||
row.className = "event-card";
|
||||
row.innerHTML = `<span class="badge">${(item.level || "info").toUpperCase()}</span><strong class="event-type">${item.event_type}</strong><span class="event-message">${item.message}</span><span class="muted event-time">${formatTime(item.created_at)}</span>`;
|
||||
dom.eventList.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,73 +1,9 @@
|
|||
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";
|
||||
import { renderUnits } from "./units.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`);
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ export const state = {
|
|||
chartPointId: null,
|
||||
chartPointName: "",
|
||||
chartData: [],
|
||||
logSource: null,
|
||||
pointSocket: null,
|
||||
apiDocLoaded: false,
|
||||
runtimes: new Map(), // unit_id -> UnitRuntime
|
||||
|
|
|
|||
|
|
@ -660,8 +660,7 @@ button.danger:hover { background: var(--danger-hover); }
|
|||
max-height: 50vh;
|
||||
}
|
||||
|
||||
.unit-list,
|
||||
.event-list {
|
||||
.unit-list {
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
|
|
@ -670,20 +669,32 @@ button.danger:hover { background: var(--danger-hover); }
|
|||
}
|
||||
|
||||
.event-card {
|
||||
cursor: default;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
padding: 3px 8px;
|
||||
font-size: 12px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.event-card:hover {
|
||||
background: var(--surface);
|
||||
border-color: var(--border);
|
||||
background: var(--surface-hover, var(--surface));
|
||||
}
|
||||
|
||||
.event-section {
|
||||
flex-basis: 42%;
|
||||
.event-type {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.log-section {
|
||||
flex-basis: 58%;
|
||||
.event-message {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.event-time {
|
||||
flex-shrink: 0;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.equipment-select-row {
|
||||
|
|
|
|||
Loading…
Reference in New Issue