feat: local-cpl-sim.mjs Einstellungen done
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
// Minimal local simulator for testing SAN01 replacement only.
|
||||
// Local simulator that serves the exported build and replaces CGI placeholders from mocks/.
|
||||
// - Serves the exported "out" folder.
|
||||
// - For text files (.html, .js, .json, .css, .txt) replaces <%=SAN01%> -> "ismail" on the fly.
|
||||
// - Replaces placeholders like <%=SAV00%>, <%=SAN01%>, DANxx/DASxx etc. using mocks/device-cgi-simulator/**.
|
||||
// - Supports mapping /CPL?/path to /path in the export.
|
||||
|
||||
import http from "http";
|
||||
@@ -49,13 +49,185 @@ function isTextExt(ext) {
|
||||
return [".html", ".js", ".json", ".css", ".txt", ".svg"].includes(ext);
|
||||
}
|
||||
|
||||
function sendFileWithSAN01Replace(res, filePath) {
|
||||
function readJsonSafe(fp) {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(fp, "utf8"));
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function getRelFromRoot(filePath) {
|
||||
try {
|
||||
const r = path.relative(ROOT, filePath).split(path.sep).join("/");
|
||||
return r.startsWith("../") ? r : r;
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function tryServeSpecialMocks(res, relPath) {
|
||||
const rp = relPath.replace(/^\/+/, "");
|
||||
// Serve ready JS/JSON mocks for some routes
|
||||
if (rp === "CPL/SERVICE/systemVoltTemp.js") {
|
||||
const mockPath = path.join(
|
||||
process.cwd(),
|
||||
"mocks",
|
||||
"device-cgi-simulator",
|
||||
"SERVICE",
|
||||
"systemVoltTempMockData.js"
|
||||
);
|
||||
if (exists(mockPath)) return streamRaw(res, mockPath);
|
||||
}
|
||||
if (rp === "CPL/SERVICE/last20Messages.js") {
|
||||
const mockPath = path.join(
|
||||
process.cwd(),
|
||||
"mocks",
|
||||
"device-cgi-simulator",
|
||||
"SERVICE",
|
||||
"last20MessagesMockData.js"
|
||||
);
|
||||
if (exists(mockPath)) return streamRaw(res, mockPath);
|
||||
}
|
||||
if (rp === "CPL/meldungen/messages_all.json") {
|
||||
const mockPath = path.join(
|
||||
process.cwd(),
|
||||
"mocks",
|
||||
"device-cgi-simulator",
|
||||
"meldungen",
|
||||
"messages_all.json"
|
||||
);
|
||||
if (exists(mockPath)) return streamRaw(res, mockPath);
|
||||
}
|
||||
if (rp.startsWith("CPL/chartsData/analogInputs/")) {
|
||||
const mockPath = path.join(
|
||||
process.cwd(),
|
||||
"mocks",
|
||||
"device-cgi-simulator",
|
||||
rp.replace(/^CPL\//, "")
|
||||
);
|
||||
if (exists(mockPath)) return streamRaw(res, mockPath);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function streamRaw(res, filePath) {
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
const type = contentTypeByExt(ext);
|
||||
res.writeHead(200, { "Content-Type": type });
|
||||
fs.createReadStream(filePath).pipe(res);
|
||||
return true;
|
||||
}
|
||||
|
||||
function resolvePlaceholders(filePath, content) {
|
||||
const rel = getRelFromRoot(filePath);
|
||||
const serviceSystem =
|
||||
/CPL\/SERVICE\/(System\.(js|json)|system\.(js|json))$/i.test(rel);
|
||||
const serviceOpcua = /CPL\/SERVICE\/opcua\.(js|json)$/i.test(rel);
|
||||
const serviceDO = /CPL\/SERVICE\/digitalOutputs\.(js|json)$/i.test(rel);
|
||||
|
||||
let data = {};
|
||||
let replacer = (m) => m;
|
||||
|
||||
if (serviceSystem || /\.html$/i.test(rel)) {
|
||||
const baseDir = path.join(
|
||||
process.cwd(),
|
||||
"mocks",
|
||||
"device-cgi-simulator",
|
||||
"SERVICE"
|
||||
);
|
||||
const sysJson = exists(path.join(baseDir, "SystemMockData.json"))
|
||||
? path.join(baseDir, "SystemMockData.json")
|
||||
: path.join(baseDir, "systemMockData.json");
|
||||
data = exists(sysJson) ? readJsonSafe(sysJson) : {};
|
||||
const map = {
|
||||
SAV00: "win_appVersion",
|
||||
SAN01: "win_deviceName",
|
||||
SEM01: "win_mac1",
|
||||
SEI01: "win_ip",
|
||||
SES01: "win_subnet",
|
||||
SEG01: "win_gateway",
|
||||
SCL01: "win_cplInternalTimestamp",
|
||||
STP01: "win_ntp1",
|
||||
STP02: "win_ntp2",
|
||||
STP03: "win_ntp3",
|
||||
STT00: "win_ntpTimezone",
|
||||
STA00: "win_ntpActive",
|
||||
SOS01: "win_opcState",
|
||||
SOC01: "win_opcSessions",
|
||||
SON01: "win_opcName",
|
||||
};
|
||||
replacer = (m, key) => {
|
||||
if (Object.prototype.hasOwnProperty.call(data, key))
|
||||
return String(data[key]);
|
||||
const prop = map[key];
|
||||
if (prop && Object.prototype.hasOwnProperty.call(data, prop))
|
||||
return String(data[prop]);
|
||||
return m;
|
||||
};
|
||||
} else if (serviceOpcua) {
|
||||
const json = path.join(
|
||||
process.cwd(),
|
||||
"mocks",
|
||||
"device-cgi-simulator",
|
||||
"SERVICE",
|
||||
"opcuaMockData.json"
|
||||
);
|
||||
data = exists(json) ? readJsonSafe(json) : {};
|
||||
const map = {
|
||||
SOS01: "win_opcUaZustand",
|
||||
SOC01: "win_opcUaActiveClientCount",
|
||||
SON01: "win_opcUaNodesetName",
|
||||
};
|
||||
replacer = (m, key) => {
|
||||
if (Object.prototype.hasOwnProperty.call(data, key))
|
||||
return String(data[key]);
|
||||
const prop = map[key];
|
||||
if (prop && Object.prototype.hasOwnProperty.call(data, prop))
|
||||
return String(data[prop]);
|
||||
return m;
|
||||
};
|
||||
} else if (serviceDO) {
|
||||
const json = path.join(
|
||||
process.cwd(),
|
||||
"mocks",
|
||||
"device-cgi-simulator",
|
||||
"SERVICE",
|
||||
"digitalOutputsMockData.json"
|
||||
);
|
||||
data = exists(json) ? readJsonSafe(json) : {};
|
||||
replacer = (m, key) => {
|
||||
// DASnn -> win_da_state[n-1]
|
||||
if (/^DAS\d{2}$/i.test(key)) {
|
||||
const idx = parseInt(key.slice(3), 10) - 1;
|
||||
const arr = data.win_da_state;
|
||||
if (Array.isArray(arr) && idx >= 0 && idx < arr.length)
|
||||
return String(arr[idx]);
|
||||
}
|
||||
// DANnn -> string label (ensure proper quoting in JS array)
|
||||
if (/^DAN\d{2}$/i.test(key)) {
|
||||
const idx = parseInt(key.slice(3), 10) - 1;
|
||||
const arr = data.win_da_bezeichnung;
|
||||
if (Array.isArray(arr) && idx >= 0 && idx < arr.length)
|
||||
return JSON.stringify(arr[idx] ?? "");
|
||||
}
|
||||
return m;
|
||||
};
|
||||
} else {
|
||||
// Default: no change
|
||||
replacer = (m) => m;
|
||||
}
|
||||
|
||||
return content.replace(/<%=([A-Z0-9_]+)%>/g, replacer);
|
||||
}
|
||||
|
||||
function sendFileWithReplace(res, filePath) {
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
const type = contentTypeByExt(ext);
|
||||
if (isTextExt(ext)) {
|
||||
try {
|
||||
let content = fs.readFileSync(filePath, "utf8");
|
||||
content = content.replace(/<%=SAN01%>/g, "ismail");
|
||||
content = resolvePlaceholders(filePath, content);
|
||||
res.writeHead(200, { "Content-Type": type });
|
||||
res.end(content);
|
||||
return;
|
||||
@@ -78,19 +250,20 @@ const server = http.createServer((req, res) => {
|
||||
const pathname = decodeURIComponent(url.pathname);
|
||||
const rawQuery = req.url.includes("?") ? req.url.split("?")[1] : "";
|
||||
|
||||
// Minimal CPL? mapping: /CPL?/CPL/... -> /CPL/...
|
||||
// CPL? mapping: /CPL?/CPL/... -> /CPL/...
|
||||
if (pathname === "/CPL" && rawQuery) {
|
||||
const q = decodeURIComponent(rawQuery);
|
||||
if (q.startsWith("/")) {
|
||||
const rel = q.replace(/^\/+/, "");
|
||||
// Special mocks
|
||||
if (tryServeSpecialMocks(res, rel)) return;
|
||||
const target = path.join(ROOT, rel);
|
||||
if (exists(target) && fs.statSync(target).isFile()) {
|
||||
return sendFileWithSAN01Replace(res, target);
|
||||
return sendFileWithReplace(res, target);
|
||||
}
|
||||
if (exists(target) && fs.statSync(target).isDirectory()) {
|
||||
const indexFile = path.join(target, "index.html");
|
||||
if (exists(indexFile))
|
||||
return sendFileWithSAN01Replace(res, indexFile);
|
||||
if (exists(indexFile)) return sendFileWithReplace(res, indexFile);
|
||||
}
|
||||
return notFound(res);
|
||||
}
|
||||
@@ -105,21 +278,25 @@ const server = http.createServer((req, res) => {
|
||||
|
||||
if (pathname.endsWith("/")) {
|
||||
filePath = path.join(filePath, "index.html");
|
||||
if (exists(filePath)) return sendFileWithSAN01Replace(res, filePath);
|
||||
if (exists(filePath)) return sendFileWithReplace(res, filePath);
|
||||
}
|
||||
|
||||
// Special mocks outside CPL?
|
||||
const relStatic = pathname.replace(/^\/+/, "");
|
||||
if (tryServeSpecialMocks(res, relStatic)) return;
|
||||
|
||||
if (exists(filePath) && fs.statSync(filePath).isFile()) {
|
||||
return sendFileWithSAN01Replace(res, filePath);
|
||||
return sendFileWithReplace(res, filePath);
|
||||
}
|
||||
|
||||
const htmlVariant = filePath + ".html";
|
||||
if (exists(htmlVariant)) {
|
||||
return sendFileWithSAN01Replace(res, htmlVariant);
|
||||
return sendFileWithReplace(res, htmlVariant);
|
||||
}
|
||||
|
||||
// Fallback: out/index.html
|
||||
const fallback = path.join(ROOT, "index.html");
|
||||
if (exists(fallback)) return sendFileWithSAN01Replace(res, fallback);
|
||||
if (exists(fallback)) return sendFileWithReplace(res, fallback);
|
||||
|
||||
return notFound(res);
|
||||
} catch (err) {
|
||||
@@ -133,5 +310,5 @@ const server = http.createServer((req, res) => {
|
||||
server.listen(PORT, () => {
|
||||
console.log(`Local CPL simulator running on http://localhost:${PORT}`);
|
||||
console.log(`Serving from: ${ROOT}`);
|
||||
console.log("Replacing <%=SAN01%> -> ismail in text responses");
|
||||
console.log("Replacing CGI placeholders using mocks/ where available");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user