docs: Zusatzfunktionen (Kai, 25.06.2025) in TODO.md ergänzt

This commit is contained in:
ISA
2025-06-25 11:42:42 +02:00
parent ca2a0cb00d
commit 2fcd0755a4
29 changed files with 369 additions and 122 deletions

View File

@@ -6,5 +6,5 @@ NEXT_PUBLIC_USE_MOCK_BACKEND_LOOP_START=false
NEXT_PUBLIC_EXPORT_STATIC=false
NEXT_PUBLIC_USE_CGI=false
# App-Versionsnummer
NEXT_PUBLIC_APP_VERSION=1.6.444
NEXT_PUBLIC_APP_VERSION=1.6.445
NEXT_PUBLIC_CPL_MODE=jsSimulatedProd # json (Entwicklungsumgebung) oder jsSimulatedProd (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_USE_CGI=true
# App-Versionsnummer
NEXT_PUBLIC_APP_VERSION=1.6.444
NEXT_PUBLIC_APP_VERSION=1.6.445
NEXT_PUBLIC_CPL_MODE=production

View File

@@ -1,3 +1,8 @@
## [1.6.445] 2025-06-25
- docs: README
---
## [1.6.444] 2025-06-23
- feat: digitalOutputs separate jsSimulatedProd mode

View File

@@ -18,7 +18,13 @@ import handleAdminLogin from "./handlers/handleAdminLogin";
ReactModal.setAppElement("#__next");
function SettingModal({ showModal, onClose }) {
function SettingModal({
showModal,
onClose,
}: {
showModal: boolean;
onClose: () => void;
}) {
const { isAdminLoggedIn, logoutAdmin } = useAdminAuth(showModal);
const { formValues, setFormValues } = useSystemSettings(showModal);
@@ -72,9 +78,21 @@ function SettingModal({ showModal, onClose }) {
const [ntp2, setNtp2] = useState(ntp2_Redux || "");
const [ntp3, setNtp3] = useState(ntp3_Redux || "");
const [ntpTimezone, setNtpTimezone] = useState(ntpTimezone_Redux || "");
const [active, setActive] = useState(active_Redux || "");
const [active, setActive] = useState<boolean>(
typeof active_Redux === "boolean" ? active_Redux : active_Redux === "true"
);
const [originalValues, setOriginalValues] = useState({});
const [originalValues, setOriginalValues] = useState({
name: name,
ip: ip,
subnet: subnet,
gateway: gateway,
ntp1: ntp1,
ntp2: ntp2,
ntp3: ntp3,
ntpTimezone: ntpTimezone,
active: active,
});
const currentValues = {
name,
ip,
@@ -228,12 +246,14 @@ function SettingModal({ showModal, onClose }) {
</div>
<div>
<label className="block font-medium">NTP Active:</label>
<input
type="text"
<select
className="border border-gray-300 rounded p-1 xl:p-2 w-full"
value={active}
onChange={(e) => setActive(e.target.value)}
/>
value={active ? "true" : "false"}
onChange={(e) => setActive(e.target.value === "true")}
>
<option value="true">true</option>
<option value="false">false</option>
</select>
</div>
</div>
@@ -241,13 +261,13 @@ function SettingModal({ showModal, onClose }) {
<div className="flex flex-col md:flex-row justify-between mt-4 gap-2">
<button
className="bg-littwin-blue text-white px-3 py-1 xl:px-4 xl:py-2 rounded w-full md:w-auto"
onClick={handleReboot}
onClick={() => handleReboot()}
>
Neustart CPL
</button>
<button
onClick={() =>
isAdminLoggedIn ? handleAdminLogout() : setShowLoginForm(true)
isAdminLoggedIn ? logoutAdmin() : setShowLoginForm(true)
}
className="bg-littwin-blue text-white px-3 py-1 xl:px-4 xl:py-2 rounded w-full md:w-auto"
>

View File

@@ -3,7 +3,7 @@ import { useState, useEffect } from "react";
import { useSelector } from "react-redux";
import { RootState } from "../../../../redux/store";
export function useSystemSettings(showModal: boolean) {
const settings = useSelector((state: RootState) => state.systemSettings);
const settings = useSelector((state: RootState) => state.systemSettingsSlice);
const [formValues, setFormValues] = useState(settings);
const [originalValues, setOriginalValues] = useState({});

View File

@@ -16,13 +16,24 @@ export function generateKeyAndIV() {
}
// Generiert einen verschlüsselten Token
export function generateToken(user) {
const payload = {
interface User {
username: string;
role: string;
}
interface TokenPayload {
username: string;
role: string;
exp: number;
}
export function generateToken(user: User): string {
const payload: TokenPayload = {
username: user.username,
role: user.role,
exp: Date.now() + 5 * 60 * 1000, // Ablaufzeit: 5 Minuten
};
const token = JSON.stringify(payload);
const token: string = JSON.stringify(payload);
const { key, iv } = generateKeyAndIV();
return CryptoJS.AES.encrypt(token, key, { iv }).toString();
}

View File

@@ -38,7 +38,11 @@ export default function AnalogInputsChart({
const dispatch = useDispatch<AppDispatch>();
const { data, isLoading, error } = useSelector(
(state: RootState) => state.analogInputsHistory
);
) as {
data: { [key: string]: any[] };
isLoading: boolean;
error: any;
};
useEffect(() => {
dispatch(getAnalogInputsHistoryThunk());
@@ -95,7 +99,7 @@ export default function AnalogInputsChart({
x: {
type: "time" as const,
time: {
unit: "hour",
unit: "hour" as const,
tooltipFormat: "HH:mm 'Uhr' dd.MM.",
displayFormats: {
hour: "HH:mm",

View File

@@ -1,4 +1,16 @@
"use client"; // /components/main/analogInputs/AnalogInputsTable.tsx
export type AnalogInput = {
id: number;
value: number;
label: string;
unit?: string; // Make unit optional if it may not exist
offset: number;
factor: number;
loggerInterval: number;
weighting: number;
};
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { RootState, AppDispatch } from "@/redux/store";

View File

@@ -9,7 +9,13 @@ import outputIcon from "@iconify/icons-mdi/output";
import switchIcon from "@iconify/icons-ion/switch";
import { setDigitalOutputs } from "@/redux/slices/digitalOutputsSlice";
export default function DigitalOutputsWidget({ openOutputModal }) {
interface DigitalOutputsWidgetProps {
openOutputModal: (output: any) => void;
}
export default function DigitalOutputsWidget({
openOutputModal,
}: DigitalOutputsWidgetProps) {
const dispatch = useDispatch<AppDispatch>();
const digitalOutputs = useSelector(
(state: RootState) => state.digitalOutputsSlice.outputs

View File

@@ -3,7 +3,7 @@ import { useState, useEffect } from "react";
import { useSelector } from "react-redux";
import { RootState } from "../../../../redux/store";
export function useSystemSettings(showModal: boolean) {
const settings = useSelector((state: RootState) => state.systemSettings);
const settings = useSelector((state: RootState) => state.systemSettingsSlice);
const [formValues, setFormValues] = useState(settings);
const [originalValues, setOriginalValues] = useState({});

View File

@@ -16,13 +16,22 @@ export function generateKeyAndIV() {
}
// Generiert einen verschlüsselten Token
export function generateToken(user) {
const payload = {
interface UserPayload {
username: string;
role: string;
}
interface TokenPayload extends UserPayload {
exp: number;
}
export function generateToken(user: UserPayload): string {
const payload: TokenPayload = {
username: user.username,
role: user.role,
exp: Date.now() + 5 * 60 * 1000, // Ablaufzeit: 5 Minuten
};
const token = JSON.stringify(payload);
const token: string = JSON.stringify(payload);
const { key, iv } = generateKeyAndIV();
return CryptoJS.AES.encrypt(token, key, { iv }).toString();
}

View File

@@ -4,16 +4,22 @@ import { Icon } from "@iconify/react";
import { useSelector } from "react-redux";
import { RootState } from "../../../redux/store";
const VersionInfo: React.FC = () => {
type VersionInfoProps = {
className?: string;
};
const VersionInfo: React.FC<VersionInfoProps> = ({ className = "" }) => {
const appVersion =
useSelector((state: RootState) => state.systemSettingsSlice.appVersion) ||
"Unbekannt";
const webVersion = useSelector(
(state: RootState) => state.webVersionSlice.appVersion
); // Webversion aus Redux holen
);
return (
<div className="bg-gray-50 p-4 rounded-lg shadow-sm border border-gray-200 w-full laptop:p-2">
<div
className={`bg-gray-50 rounded-lg shadow-sm border border-gray-200 w-full laptop:p-2 ${className}`}
>
<h2 className="text-lg font-semibold text-gray-700 mb-2">
Versionsinformationen
</h2>

View File

@@ -1,3 +1,12 @@
## ✅ TODOs
- [ ] TODO: In digitale Eingänge Modal Out of Service geht nicht in json , jsSimulatedProd und production. readOnly?
## 🔄 Zusatzfunktionen (prio niedrig) Kai, 25. Juni 2025
- [ ] Messwertanzeige eines Eingangs: Zeitraum einstellbar, Bezeichnung und Einheit in die Messkurve, Datum in die Messkurve
- [ ] Startseite Meldungen: Spalte Quelle wie bei Berichte (nicht "Modul")
- [ ] Startseite Spaltenreihenfolge: Pop (farbiger Punkt), Zeitstempel, Quelle, Meldung, Status
- [ ] Berichte Spaltenreihenfolge: Pop (farbiger Punkt), Zeitstempel, Quelle, Meldung, Status
- [ ] Filter für Quelle
- [ ] System: Button bei jedem Messwert für Detailansicht (inkl. Kurve im Popup und Zeitauswahl)
- [ ] Einstellungen Benutzerverwaltung: Admin kann Adminpasswort ändern
- [ ] Einstellungen OPC: Anzahl der aktuellen Clients (ggf. KAS-Variable einbauen)

View File

@@ -4,11 +4,11 @@ var win_analogInputsValues = [
4.771072, 5.665244, 0.005467, -0.007468, 0.000002, 0.000001, 0.000001,
0.070007,
];
var win_analogInputsLabels = ["AE 1", "AE 2", "AE 3", "AE 4", "AE 5", "AE 6", "AE 7", "AE 8", ];
var win_analogInputsOffset = [10.995, 0.0, 0.0, 0, 0.0, 0.0, 0.0, 0.0];
var win_analogInputsFactor = [11.996, 1.0, 1.0, 1, 1.0, 1.0, 1.0, 1.0];
var win_analogInputsLoggerIntervall = [6, 10, 10, 10, 10, 10, 10, 10];
var win_analogInputsUnits = ["mA", "V", "V", "V", "mA", "mA", "mA", "mA"];
var win_analogInputsLabels = ["AE 11", "AE 2", "AE 3", "AE 4", "AE 5", "AE 6", "AE 7", "AE 8", ];
var win_analogInputsOffset = [21.999, 0.0, 0.0, 0, 0.0, 0.0, 0.0, 0.0];
var win_analogInputsFactor = [21.999, 1.0, 1.0, 1, 1.0, 1.0, 1.0, 1.0];
var win_analogInputsLoggerIntervall = [21, 10, 10, 10, 10, 10, 10, 10];
var win_analogInputsUnits = ["V", "V", "V", "V", "mA", "mA", "mA", "mA"];
var win_analogInputsWeighting = [0, 0, 0, 0, 0, 0, 0, 0];
/*

View File

@@ -0,0 +1,63 @@
{
"win_analogInputsValues": [
"126.812080",
"5.680176",
"-0.015003",
"0.009538",
"-0.000002",
"0.000003",
"-0.000005",
"0.000000"
],
"win_analogInputsLabels": [
"'AE 1'",
"'AE 2'",
"'AE 3'",
"'AE 4'",
"'AE 5'",
"'AE 6'",
"'AE 7'",
"'AE 8'"
],
"win_analogInputsUnits": [
"'V'",
"'V'",
"'V'",
"'V'",
"'mA'",
"'mA'",
"'mA'",
"'mA'"
],
"win_analogInputsFactor": [
"21.999",
"1.000",
"1.000",
"1.000",
"1.000",
"1.000",
"1.000",
"1.000"
],
"win_analogInputsOffset": [
"21.999",
"0.000",
"0.000",
"0.000",
"0.000",
"0.000",
"0.000",
"0.000"
],
"win_analogInputsWeighting": ["0", "0", "0", "0", "0", "0", "0", "0"],
"win_analogInputsLoggerIntervall": [
"21",
"10",
"10",
"10",
"10",
"10",
"10",
"10"
]
}

4
package-lock.json generated
View File

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

View File

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

View File

@@ -44,7 +44,8 @@ function AppContent({
}): JSX.Element {
const dispatch = useAppDispatch();
const [sessionExpired, setSessionExpired] = useState(false);
const mode = "DIA0"; // oder aus Router oder Session
const type = 0; // Beispiel: 0 für "loop", 1 für "iso" (bitte ggf. anpassen)
useEffect(() => {
let intervalId: NodeJS.Timeout;
const pathname = window.location.pathname;
@@ -71,11 +72,19 @@ function AppContent({
} else if (pathname.includes("tdrRef")) {
dispatch(getAllTDRReferenceChartThunk());
} else if (pathname.includes("tdrSlot")) {
dispatch(getReferenceCurveBySlotThunk());
dispatch(getReferenceCurveBySlotThunk(1));
} else if (pathname.includes("tdrId")) {
dispatch(getTDRChartDataByIdThunk());
dispatch(getTDRChartDataByIdThunk(1));
} else if (pathname.includes("loopChart")) {
dispatch(getLoopChartDataThunk());
dispatch(
getLoopChartDataThunk({
mode,
type,
slotNumber: 1, // Beispielwert, ggf. anpassen
vonDatum: new Date().toISOString().split("T")[0], // Beispiel: heutiges Datum
bisDatum: new Date().toISOString().split("T")[0], // Beispiel: heutiges Datum
})
);
}
};

View File

@@ -19,12 +19,13 @@ export default function handler(req: NextApiRequest, res: NextApiResponse) {
}
if (mode === "jsSimulatedProd") {
const analogInputsScript = fs.readFileSync(
"mocks/device-cgi-simulator/SERVICE/analogInputsMockData.js",
"utf-8"
const filePath = path.join(
process.cwd(),
"mocks/device-cgi-simulator/SERVICE/analogInputsMockData.json"
);
res.setHeader("Content-Type", "application/javascript");
res.status(200).send(analogInputsScript);
const jsonContent = fs.readFileSync(filePath, "utf-8");
const data = JSON.parse(jsonContent);
res.status(200).json(data);
return;
}
} catch (error) {

View File

@@ -1,4 +1,5 @@
export default function handler(req, res) {
import type { NextApiRequest, NextApiResponse } from "next";
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const { slot } = req.query;
setTimeout(() => {
res

View File

@@ -1,5 +1,6 @@
// pages/api/cpl/tdrMessungStartenMockHandler.ts
export default function handler(req, res) {
import type { NextApiRequest, NextApiResponse } from "next";
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const { slot } = req.query;
setTimeout(() => {
res.status(200).json({ message: `TDR simuliert für Slot ${slot}` });

View File

@@ -2,8 +2,9 @@
import fs from "fs";
import path from "path";
import type { NextApiRequest, NextApiResponse } from "next";
export default function handler(req, res) {
export default function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== "POST") {
return res.status(405).json({ message: "Nur POST erlaubt" });
}

View File

@@ -1,12 +1,21 @@
// /pages/api/cpl/updateKueSettingsDataAPIHandler.ts
import path from "path";
import fs from "fs/promises";
import type { NextApiRequest, NextApiResponse } from "next";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
let { key, value, slot } = req.query;
export default async function handler(req, res) {
const { key, value, slot } = req.query;
if (!key || slot === undefined) {
return res.status(400).json({ error: "Missing key or slot parameter." });
if (
typeof key !== "string" ||
typeof value !== "string" ||
typeof slot !== "string"
) {
return res
.status(400)
.json({ error: "Missing or invalid key, value, or slot parameter." });
}
const mockFilePath = path.join(

View File

@@ -2,8 +2,12 @@
import path from "path";
import fs from "fs/promises";
import type { NextApiRequest, NextApiResponse } from "next";
export default async function handler(req, res) {
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const updates = req.body?.updates;
if (!Array.isArray(updates) || updates.length === 0) {
@@ -34,7 +38,7 @@ export default async function handler(req, res) {
.map((v) => (v === "" ? "0" : v))
.slice(0, 32);
values[Number(slot)] = Number(value);
values[Number(slot)] = String(Number(value));
const newLine = `var ${key} = [\n ${values.join(", ")}\n];`;
fileContent = fileContent.replace(regex, newLine);

View File

@@ -10,10 +10,10 @@ import { getKueDataThunk } from "../redux/thunks/getKueDataThunk";
function Kabelueberwachung() {
const dispatch: AppDispatch = useDispatch();
const searchParams = useSearchParams(); // URL-Parameter holen
const initialRack = parseInt(searchParams.get("rack")) || 1; // Rack-Nummer aus URL oder 1
const initialRack = parseInt(searchParams.get("rack") ?? "1") || 1; // Rack-Nummer aus URL oder 1
const [activeRack, setActiveRack] = useState(initialRack); // Nutze initialRack als Startwert
const [alarmStatus, setAlarmStatus] = useState([]); // Alarmstatus
const [alarmStatus, setAlarmStatus] = useState<boolean[]>([]); // Alarmstatus
// Redux-Variablen aus dem Store abrufen
const {
@@ -25,21 +25,23 @@ function Kabelueberwachung() {
kueResidence,
kueCableBreak,
kueGroundFault,
} = useSelector((state) => state.kueDataSlice);
} = useSelector((state: RootState) => state.kueDataSlice);
//----------------------------------------------------------------
// 🚀 **TDR-Daten bereits in Redux abrufen**
// Redux-Variablen abrufen
const tdrData = useSelector((state) => state.tdrChartSlice.data);
const loading = useSelector((state) => state.tdrChartSlice.loading);
const error = useSelector((state) => state.tdrChartSlice.error);
const tdrData = useSelector((state: RootState) => state.tdrChartSlice.data);
const loading = useSelector(
(state: RootState) => state.tdrChartSlice.loading
);
const error = useSelector((state: RootState) => state.tdrChartSlice.error);
//----------------------------------------------------------------
//----------------------------------------------------------------
// Alarmstatus basierend auf Redux-Variablen berechnen
const updateAlarmStatus = () => {
const updatedAlarmStatus = kueIso.map((_, index) => {
return (
return !!(
(kueAlarm1 && kueAlarm1[index]) ||
(kueAlarm2 && kueAlarm2[index]) ||
(kueCableBreak && kueCableBreak[index]) ||
@@ -66,7 +68,8 @@ function Kabelueberwachung() {
}));
//console.log("Alle Module:", allModules);
const racks = {
type RackKey = "rack1" | "rack2" | "rack3" | "rack4";
const racks: Record<RackKey, typeof allModules> = {
rack1: allModules.slice(0, 8),
rack2: allModules.slice(8, 16),
rack3: allModules.slice(16, 24),
@@ -92,7 +95,11 @@ function Kabelueberwachung() {
); */
// Funktion zum Wechseln des Racks
const changeRack = (rack) => {
interface ChangeRackFn {
(rack: number): void;
}
const changeRack: ChangeRackFn = (rack) => {
setActiveRack(rack);
console.log(`Aktives Rack geändert zu: ${rack}`);
};
@@ -144,8 +151,7 @@ function Kabelueberwachung() {
</button>
))}
</div>
<div className="flex flex-row space-x-8 xl:space-x-0 2xl:space-x-8 qhd:space-x-16 ml-[5%] mt-[5%]">
{racks[`rack${activeRack}`].map((slot, index) => {
{racks[`rack${activeRack}` as RackKey].map((slot, index) => {
const slotIndex = index + (activeRack - 1) * 8;
return (
<div key={index} className="flex">
@@ -156,12 +162,12 @@ function Kabelueberwachung() {
kueOnline={slot.kueOnlineStatus}
alarmStatus={slot.alarmStatus}
slotIndex={slotIndex}
tdrLocation={[]} // TODO: Replace with actual tdrLocation data if available
/>
</div>
);
})}
</div>
</div>
);
}

View File

@@ -0,0 +1,72 @@
{
"win_analogInputsValues": [
"<%=AAV01%>",
"<%=AAV02%>",
"<%=AAV03%>",
"<%=AAV04%>",
"<%=AAV05%>",
"<%=AAV06%>",
"<%=AAV07%>",
"<%=AAV08%>"
],
"win_analogInputsLabels": [
"<%=ACN01%>",
"<%=ACN02%>",
"<%=ACN03%>",
"<%=ACN04%>",
"<%=ACN05%>",
"<%=ACN06%>",
"<%=ACN07%>",
"<%=ACN08%>"
],
"win_analogInputsUnits": [
"<%=ACU01%>",
"<%=ACU02%>",
"<%=ACU03%>",
"<%=ACU04%>",
"<%=ACU05%>",
"<%=ACU06%>",
"<%=ACU07%>",
"<%=ACU08%>"
],
"win_analogInputsFactor": [
"<%=ACF01%>",
"<%=ACF02%>",
"<%=ACF03%>",
"<%=ACF04%>",
"<%=ACF05%>",
"<%=ACF06%>",
"<%=ACF07%>",
"<%=ACF08%>"
],
"win_analogInputsOffset": [
"<%=ACO01%>",
"<%=ACO02%>",
"<%=ACO03%>",
"<%=ACO04%>",
"<%=ACO05%>",
"<%=ACO06%>",
"<%=ACO07%>",
"<%=ACO08%>"
],
"win_analogInputsWeighting": [
"<%=ACS01%>",
"<%=ACS02%>",
"<%=ACS03%>",
"<%=ACS04%>",
"<%=ACS05%>",
"<%=ACS06%>",
"<%=ACS07%>",
"<%=ACS08%>"
],
"win_analogInputsLoggerIntervall": [
"<%=ACL01%>",
"<%=ACL02%>",
"<%=ACL03%>",
"<%=ACL04%>",
"<%=ACL05%>",
"<%=ACL06%>",
"<%=ACL07%>",
"<%=ACL08%>"
]
}

View File

@@ -2,61 +2,56 @@
export const fetchAnalogInputsService = async () => {
const mode = process.env.NEXT_PUBLIC_CPL_MODE;
// ✅ PRODUKTIV: lädt JavaScript vom Gerät über CGI
// ✅ PRODUKTIV: lädt JSON-Datei vom Gerät über CGI
if (mode === "production") {
console.log("🔄 Lade analoge Eingänge im Produktionsmodus...");
const scriptUrl = "/CPL?/CPL/SERVICE/analogInputs.js";
console.log(
"🔄 Lade analoge Eingänge im Produktionsmodus (JSON über CPL?)..."
);
await new Promise<void>((resolve, reject) => {
const script = document.createElement("script");
script.src = scriptUrl;
script.async = true;
script.onload = () => resolve();
script.onerror = () => reject("❌ Fehler beim Laden der analogInputs.js");
document.body.appendChild(script);
const res = await fetch("/CPL?/CPL/SERVICE/analogInputs.json", {
headers: { Accept: "application/json" },
});
const win = window as any;
if (!res.ok) {
throw new Error("❌ Fehler beim Laden der analogInputs.json");
}
return Array.from({ length: 8 }, (_, i) => ({
const data = await res.json();
return data.win_analogInputsValues.map((value: string, i: number) => ({
id: i + 1,
value: parseFloat(win.win_analogInputsValues[i]),
label: win.win_analogInputsLabels[i],
unit: win.win_analogInputsUnits[i],
offset: parseFloat(win.win_analogInputsOffset[i]),
factor: parseFloat(win.win_analogInputsFactor[i]),
loggerInterval: parseInt(win.win_analogInputsLoggerIntervall[i]),
weighting: parseInt(win.win_analogInputsWeighting[i]),
value: parseFloat(value),
label: data.win_analogInputsLabels[i].replace(/'/g, ""),
unit: data.win_analogInputsUnits[i].replace(/'/g, ""),
offset: parseFloat(data.win_analogInputsOffset[i]),
factor: parseFloat(data.win_analogInputsFactor[i]),
loggerInterval: parseInt(data.win_analogInputsLoggerIntervall[i]),
weighting: parseInt(data.win_analogInputsWeighting[i]),
}));
}
// ✅ jsSimulatedProd-MODUS (Script einbinden und aus window lesen)
else if (mode === "jsSimulatedProd") {
console.log("🔄 Lade simulierte analoge Eingänge im Produktionsmodus...");
const scriptUrl = "/api/cpl/getAnalogInputsHandler"; // gibt JavaScript zurück
console.log("🧪 Lade analoge Eingänge im Simulationsmodus (JSON)");
await new Promise<void>((resolve, reject) => {
const script = document.createElement("script");
script.src = scriptUrl;
script.async = true;
script.onload = () => resolve();
script.onerror = () =>
reject("❌ Fehler beim Laden des simulierten Scripts");
document.body.appendChild(script);
});
const res = await fetch("/api/cpl/getAnalogInputsHandler");
if (!res.ok)
throw new Error("❌ Fehler beim Laden der analogen Eingänge (Mock)");
const win = window as any;
const data = await res.json();
return Array.from({ length: 8 }, (_, i) => ({
return data.win_analogInputsValues.map((value: string, i: number) => ({
id: i + 1,
value: parseFloat(win.win_analogInputsValues[i]),
label: win.win_analogInputsLabels[i],
unit: win.win_analogInputsUnits[i],
offset: parseFloat(win.win_analogInputsOffset[i]),
factor: parseFloat(win.win_analogInputsFactor[i]),
loggerInterval: parseInt(win.win_analogInputsLoggerIntervall[i]),
weighting: parseInt(win.win_analogInputsWeighting[i]),
value: parseFloat(value),
label: data.win_analogInputsLabels[i].replace(/'/g, ""),
unit: data.win_analogInputsUnits[i].replace(/'/g, ""),
offset: parseFloat(data.win_analogInputsOffset[i]),
factor: parseFloat(data.win_analogInputsFactor[i]),
loggerInterval: parseInt(data.win_analogInputsLoggerIntervall[i]),
weighting: parseInt(data.win_analogInputsWeighting[i]),
}));
}
// ✅ JSON-MODUS (API gibt JSON-Daten zurück)
else if (mode === "json") {
console.log("🔄 Lade analoge Eingänge im JSON-Modus...");

View File

@@ -7,7 +7,10 @@ export async function checkSession(): Promise<boolean> {
if (typeof window === "undefined") return false;
return window[expectedGlobalVar] !== undefined;
return (
(window as unknown as Record<string, unknown>)[expectedGlobalVar] !==
undefined
);
} catch (error) {
return false;
}

View File

@@ -54,13 +54,13 @@ export async function loadWindowVariables(): Promise<{
const win = window as unknown as CustomWindow;
const variablesObj: Record<string, any> = requiredVars.reduce(
(acc, variable) => {
(acc: Record<string, any>, variable: string) => {
if (win[variable] !== undefined) {
acc[variable.replace("win_", "")] = win[variable];
}
return acc;
},
{}
{} as Record<string, any>
);
const kueData =