"use strict";
|
|
const http = require("http");
|
const fs = require("fs");
|
const path = require("path");
|
const { WebSocketServer } = require("ws");
|
|
function createDashboard({ port, dataCache, deviceManager, logger, config, uploadManager }) {
|
let server;
|
let wss;
|
let pushTimer;
|
|
const MIME = {
|
".html": "text/html; charset=utf-8",
|
".css": "text/css",
|
".js": "application/javascript",
|
};
|
|
function serveStatic(req, res) {
|
let filePath = req.url === "/" ? "/index.html" : req.url;
|
filePath = path.normalize(filePath).replace(/^(\.\.[\/\\])+/, "");
|
const fullPath = path.join(__dirname, "public", filePath);
|
if (!fullPath.startsWith(path.join(__dirname, "public"))) {
|
res.writeHead(403); res.end("Forbidden"); return;
|
}
|
fs.readFile(fullPath, (err, data) => {
|
if (err) { res.writeHead(404); res.end("Not Found"); return; }
|
const ext = path.extname(fullPath);
|
res.writeHead(200, { "Content-Type": MIME[ext] || "application/octet-stream" });
|
res.end(data);
|
});
|
}
|
|
function buildSnapshot() {
|
const devices = dataCache.getAll().map((dev) => {
|
const { rawFrame, ...rest } = dev;
|
return rest;
|
});
|
return {
|
type: "snapshot",
|
timestamp: new Date().toISOString(),
|
summary: dataCache.getSummary(),
|
devices,
|
config: {
|
pollIntervalMs: config.pollIntervalMs,
|
reconnectBaseMs: config.reconnectBaseMs,
|
reconnectMaxMs: config.reconnectMaxMs,
|
},
|
upload: uploadManager ? uploadManager.getStatus() : null,
|
};
|
}
|
|
function broadcast() {
|
if (!wss) return;
|
const payload = JSON.stringify(buildSnapshot());
|
for (const ws of wss.clients) {
|
if (ws.readyState === 1) ws.send(payload);
|
}
|
}
|
|
function start() {
|
server = http.createServer(serveStatic);
|
wss = new WebSocketServer({ server });
|
|
wss.on("connection", (ws) => {
|
logger.sys(`Dashboard 客户端已连接 (当前 ${wss.clients.size} 个)`);
|
ws.send(JSON.stringify(buildSnapshot()));
|
|
ws.on("close", () => {
|
logger.sys(`Dashboard 客户端断开 (剩余 ${wss.clients.size} 个)`);
|
});
|
});
|
|
// 数据变更防抖推送
|
let pending = false;
|
dataCache.setUpdateListener(() => {
|
if (pending) return;
|
pending = true;
|
setTimeout(() => { pending = false; broadcast(); }, 100);
|
});
|
|
// 定期推送确保时间戳更新
|
pushTimer = setInterval(() => {
|
if (pending) return;
|
broadcast();
|
}, 2000);
|
pushTimer.unref();
|
|
server.listen(port, () => {
|
logger.sys(`Dashboard 已启动: http://0.0.0.0:${port}`);
|
});
|
}
|
|
function stop() {
|
if (pushTimer) clearInterval(pushTimer);
|
if (wss) { for (const ws of wss.clients) ws.close(); }
|
if (server) server.close();
|
}
|
|
return { start, stop };
|
}
|
|
module.exports = { createDashboard };
|