feat: Unterstützung für JSON- und Production-Modus hinzugefügt

- API-Handler `updateDigitalOutputsHandler` überarbeitet:
  - JSON-Dateien werden jetzt korrekt im gültigen Format gespeichert (`{ key: value }`)
  - Schreibzugriff im production-Modus blockiert
  - JS-Mock-Struktur vorbereitet (noch nicht aktiv getestet)

- Verzeichnisstruktur vereinheitlicht:
  - JSON-Mocks unter `/mocks/api/SERVICE/`
  - CGI-Platzhalter unter `/public/CPL/`
  - JSMock-Ordner für CPL-Simulation vorbereitet (`/mocks/js-simulator/`)

- README.md um Betriebsmodi erweitert (`NEXT_PUBLIC_CPL_MODE` mit `json`, `jsmock`, `production`)
- `.env`-Dateien angepasst zur besseren Modussteuerung
This commit is contained in:
ISA
2025-06-18 14:06:23 +02:00
parent 4b7d93c64e
commit 3e6c973f3b
13 changed files with 129 additions and 78 deletions

View File

@@ -6,6 +6,6 @@ NEXT_PUBLIC_USE_MOCK_BACKEND_LOOP_START=false
NEXT_PUBLIC_EXPORT_STATIC=false NEXT_PUBLIC_EXPORT_STATIC=false
NEXT_PUBLIC_USE_CGI=false NEXT_PUBLIC_USE_CGI=false
# App-Versionsnummer # App-Versionsnummer
NEXT_PUBLIC_APP_VERSION=1.6.415 NEXT_PUBLIC_APP_VERSION=1.6.416
NEXT_PUBLIC_CPL_MODE=json # json (Entwicklungsumgebung) oder jsmock (CPL ->CGI-Interface-Simulator) oder production (CPL-> CGI-Interface Platzhalter) NEXT_PUBLIC_CPL_MODE=json # json (Entwicklungsumgebung) oder jsmock (CPL ->CGI-Interface-Simulator) oder production (CPL-> CGI-Interface Platzhalter)

View File

@@ -5,5 +5,5 @@ NEXT_PUBLIC_CPL_API_PATH=/CPL
NEXT_PUBLIC_EXPORT_STATIC=true NEXT_PUBLIC_EXPORT_STATIC=true
NEXT_PUBLIC_USE_CGI=true NEXT_PUBLIC_USE_CGI=true
# App-Versionsnummer # App-Versionsnummer
NEXT_PUBLIC_APP_VERSION=1.6.415 NEXT_PUBLIC_APP_VERSION=1.6.416
NEXT_PUBLIC_CPL_MODE=production NEXT_PUBLIC_CPL_MODE=production

View File

@@ -1,2 +1,2 @@
win_da_state = [1, 0, 1, 0]; win_da_state = [0, 0, 1, 0];
win_da_bezeichnung = ["Ausgang1", "Ausgang2", "Ausgang3", "Ausgang4"]; win_da_bezeichnung = ["Ausgang1", "Ausgang2", "Ausgang3", "Ausgang4"];

View File

@@ -66,7 +66,7 @@ export default function DigitalOutputsModal({
// 💡 Modal wird nicht automatisch geschlossen — da Seite neu lädt. // 💡 Modal wird nicht automatisch geschlossen — da Seite neu lädt.
} else { } else {
// 🧪 Lokaler Entwicklungsmodus // 🧪 Lokaler Entwicklungsmodus
const res = await fetch("/api/cpl/updateDigitalOutputs", { const res = await fetch("/api/cpl/updateDigitalOutputsHandler", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ outputs: updatedOutputs }), body: JSON.stringify({ outputs: updatedOutputs }),

View File

@@ -1,14 +0,0 @@
{
"win_da_state": [
0,
0,
1,
0
],
"win_da_bezeichnung": [
"Ausgang1",
"Ausgang2",
"Ausgang3",
"Ausgang4"
]
}

View File

@@ -0,0 +1,2 @@
win_da_state = [1, 1, 1, 0];
win_da_bezeichnung = ["Ausgang1", "Ausgang2", "Ausgang3", "Ausgang4"];

View File

@@ -1,4 +1,14 @@
{ {
"win_da_state": [1, 0, 1, 0], "win_da_state": [
"win_da_bezeichnung": ["Ausgang1", "Ausgang2", "Ausgang3", "Ausgang4"] 1,
} 0,
1,
0
],
"win_da_bezeichnung": [
"Ausgang1",
"Ausgang2",
"Ausgang3",
"Ausgang4"
]
}

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "cpl-v4", "name": "cpl-v4",
"version": "1.6.415", "version": "1.6.416",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "cpl-v4", "name": "cpl-v4",
"version": "1.6.415", "version": "1.6.416",
"dependencies": { "dependencies": {
"@fontsource/roboto": "^5.1.0", "@fontsource/roboto": "^5.1.0",
"@iconify-icons/ri": "^1.2.10", "@iconify-icons/ri": "^1.2.10",

View File

@@ -1,6 +1,6 @@
{ {
"name": "cpl-v4", "name": "cpl-v4",
"version": "1.6.415", "version": "1.6.416",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",

View File

@@ -1,4 +1,4 @@
// /pages/api/cpl/digitalOutputsAPIHandler.ts // /pages/api/cpl/analogeEingaengeAPIHandler.ts
import { NextApiRequest, NextApiResponse } from "next"; import { NextApiRequest, NextApiResponse } from "next";
import path from "path"; import path from "path";
@@ -10,13 +10,16 @@ export default async function handler(
) { ) {
const filePath = path.join( const filePath = path.join(
process.cwd(), process.cwd(),
"apiMockData", "mocks",
"api",
"SERVICE", "SERVICE",
"digitaleAusgaengeMockData.js" "digitaleAusgaengeMockData.json"
); );
try { try {
const data = await fs.readFile(filePath, "utf-8"); const data = await fs.readFile(filePath, "utf-8");
res.setHeader("Content-Type", "text/javascript");
res.status(200).send(data); res.status(200).send(data);
} catch (error) { } catch (error) {
res.status(404).json({ error: "File not found" }); res.status(404).json({ error: "File not found" });

View File

@@ -1,46 +1,75 @@
// /pages/api/cpl/updateDigitalOutputsHandler.ts // /pages/api/cpl/updateDigitalOutputs.ts
import { NextApiRequest, NextApiResponse } from "next"; import fs from "fs";
import path from "path"; import path from "path";
import fs from "fs/promises";
export default async function handler( export default function handler(req, res) {
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== "POST") { if (req.method !== "POST") {
return res.status(405).json({ error: "Method not allowed" }); return res.status(405).json({ message: "Nur POST erlaubt" });
} }
const filePath = path.join( const { outputs } = req.body;
process.cwd(),
"apiMockdata",
"SERVICE",
"digitaleAusgaengeMockData.js"
);
try { if (!Array.isArray(outputs)) {
const { outputs } = req.body; return res.status(400).json({ error: "Ungültige Datenstruktur" });
}
if (!Array.isArray(outputs) || outputs.length !== 4) { const mode = process.env.NEXT_PUBLIC_CPL_MODE;
return res
.status(400) const jsonData = {
.json({ error: "Ungültiges Datenformat (4 Einträge erwartet)" }); win_da_state: outputs.map((o) => (o.status ? 1 : 0)),
win_da_bezeichnung: outputs.map((o) => o.label),
};
let filePath = "";
if (mode === "json") {
filePath = path.join(
process.cwd(),
"mocks",
"api",
"SERVICE",
"digitaleAusgaengeMockData.json"
);
try {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, JSON.stringify(jsonData, null, 2), "utf-8");
console.log("✅ Datei (JSON) geschrieben:", filePath);
res.status(200).json({ success: true });
} catch (err) {
console.error("❌ Fehler beim Schreiben der JSON-Datei:", err);
res.status(500).json({ error: "Fehler beim Schreiben der Datei" });
} }
} else if (mode === "jsmock") {
filePath = path.join(
process.cwd(),
"mocks",
"js-simulator",
"SERVICE",
"digitaleAusgaengeMockData.js"
);
const stateArray = outputs.map((o) => (o.status ? 1 : 0)).join(", "); const jsContent =
const labelArray = outputs.map((o) => `"${o.label}"`).join(", "); `win_da_state = [${jsonData.win_da_state.join(", ")}];\n` +
`win_da_bezeichnung = [${jsonData.win_da_bezeichnung
.map((s) => `"${s}"`)
.join(", ")}];\n`;
const fileContent = `win_da_state = [${stateArray}];\nwin_da_bezeichnung = [${labelArray}];\n`; try {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, jsContent, "utf-8");
await fs.writeFile(filePath, fileContent, "utf-8"); console.log("✅ Datei (JS-Mock) geschrieben:", filePath);
res.status(200).json({ success: true });
return res } catch (err) {
.status(200) console.error("❌ Fehler beim Schreiben der JS-Datei:", err);
.json({ message: "Mockdaten erfolgreich gespeichert." }); res.status(500).json({ error: "Fehler beim Schreiben der JS-Datei" });
} catch (error) { }
return res } else {
.status(500) res
.json({ error: "Speichern fehlgeschlagen", detail: error }); .status(403)
.json({ error: "In production ist Schreiben nicht erlaubt" });
} }
} }

View File

@@ -1,19 +1,44 @@
// /services/fetchDigitalOutputsService.ts // /services/fetchDigitalOutputsService.ts
export const fetchDigitalOutputsService = async () => { export const fetchDigitalOutputsService = async () => {
if (typeof window === "undefined") return []; const mode = process.env.NEXT_PUBLIC_CPL_MODE;
const scriptSrc = if (mode === "json") {
process.env.NEXT_PUBLIC_NODE_ENV === "production" const res = await fetch("/api/cpl/digitalOutputsAPIHandler");
? "/CPL?/CPL/SERVICE/da.js" if (!res.ok)
: "/api/cpl/digitalOutputsAPIHandler"; throw new Error("❌ Fehler beim Laden der digitalen Ausgänge (JSON)");
const data = await res.json();
const state = data.win_da_state;
const labels = data.win_da_bezeichnung;
if (!Array.isArray(state)) {
console.warn("⚠️ win_da_state fehlt oder ist ungültig:", state);
return [];
}
return state.slice(0, 4).map((status: number, index: number) => ({
id: index + 1,
label:
Array.isArray(labels) && labels[index]
? labels[index]
: `Ausgang ${index + 1}`,
status: status === 1,
}));
}
// jsmock oder production
const scriptUrl =
mode === "production"
? "/CPL?/CPL/SERVICE/digitalOutputs.js"
: "/CPLmockData/SERVICE/digitalOutputsMockData.js";
await new Promise<void>((resolve, reject) => { await new Promise<void>((resolve, reject) => {
const script = document.createElement("script"); const script = document.createElement("script");
script.src = scriptSrc; script.src = scriptUrl;
script.async = true; script.async = true;
script.onload = () => resolve(); script.onload = () => resolve();
script.onerror = () => reject("❌ Fehler beim Laden von da.js"); script.onerror = () => reject("❌ Fehler beim Laden der digitalOutputs.js");
document.body.appendChild(script); document.body.appendChild(script);
}); });
@@ -26,16 +51,12 @@ export const fetchDigitalOutputsService = async () => {
return []; return [];
} }
const outputs = state return state.slice(0, 4).map((status: number, index: number) => ({
.slice(0, 4) // ✅ Nur die 4 Ausgänge verwenden id: index + 1,
.map((status: number, index: number) => ({ label:
id: index + 1, Array.isArray(labels) && labels[index]
label: ? labels[index]
Array.isArray(labels) && labels[index] : `Ausgang ${index + 1}`,
? labels[index] status: status === 1,
: `Ausgang ${index + 1}`, }));
status: status === 1,
}));
return outputs;
}; };