diff --git a/.env.development b/.env.development
index 15b2f38..7febb59 100644
--- a/.env.development
+++ b/.env.development
@@ -6,6 +6,6 @@ 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.513
+NEXT_PUBLIC_APP_VERSION=1.6.515
NEXT_PUBLIC_CPL_MODE=json # json (Entwicklungsumgebung) oder jsSimulatedProd (CPL ->CGI-Interface-Simulator) oder production (CPL-> CGI-Interface Platzhalter)
diff --git a/.env.production b/.env.production
index 7671d73..10f6e26 100644
--- a/.env.production
+++ b/.env.production
@@ -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.513
+NEXT_PUBLIC_APP_VERSION=1.6.515
NEXT_PUBLIC_CPL_MODE=production
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1c32858..4a0cf1d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,25 @@
+## [1.6.515] – 2025-07-02
+
+- feat: Firmwareupdate für alle KÜ-Module mit Fortschrittsanzeige und Abschlussmeldung
+
+- ProgressModal-Komponente implementiert, die während des Updates angezeigt wird
+- Firmwareupdate dauert 5 Minuten (Mock-Simulation)
+- Nach Abschluss erscheint automatisch ein Toast-Hinweis
+- Verbesserte Benutzerführung durch blockierendes Modal während Update
+- Logging in kueFirmwareUpdateLog.json integriert (Mock)
+
+---
+## [1.6.514] – 2025-07-02
+
+- feat: Firmwareupdate für alle KÜ-Module mit Fortschrittsanzeige und Abschlussmeldung
+
+- ProgressModal-Komponente implementiert, die während des Updates angezeigt wird
+- Firmwareupdate dauert 5 Minuten (Mock-Simulation)
+- Nach Abschluss erscheint automatisch ein Toast-Hinweis
+- Verbesserte Benutzerführung durch blockierendes Modal während Update
+- Logging in kueFirmwareUpdateLog.json integriert (Mock)
+
+---
## [1.6.513] – 2025-07-01
- feat: alle KÜs Firmware update confirm
diff --git a/components/common/ConfirmModal.tsx b/components/common/ConfirmModal.tsx
new file mode 100644
index 0000000..6fa56c9
--- /dev/null
+++ b/components/common/ConfirmModal.tsx
@@ -0,0 +1,43 @@
+// components/common/ConfirmModal.tsx
+import React from "react";
+
+interface ConfirmModalProps {
+ open: boolean;
+ title?: string;
+ message: string;
+ onConfirm: () => void;
+ onCancel: () => void;
+}
+
+export default function ConfirmModal({
+ open,
+ title,
+ message,
+ onConfirm,
+ onCancel,
+}: ConfirmModalProps) {
+ if (!open) return null;
+
+ return (
+
+
+ {title &&
{title}
}
+
{message}
+
+
+
+
+
+
+ );
+}
diff --git a/components/main/kabelueberwachung/kue705FO/handlers/firmwareUpdate.ts b/components/main/kabelueberwachung/kue705FO/handlers/firmwareUpdate.ts
index b922304..2aee2e8 100644
--- a/components/main/kabelueberwachung/kue705FO/handlers/firmwareUpdate.ts
+++ b/components/main/kabelueberwachung/kue705FO/handlers/firmwareUpdate.ts
@@ -1,24 +1,32 @@
-// /komponents/main/kabelueberwachung/kue705FO/handlers/firmwareUpdate.ts
-const firmwareUpdate = (slot: number) => {
+// @/components/main/kabelueberwachung/kue705FO/handlers/firmwareUpdate.ts
+export default async function firmwareUpdate(
+ slot: number
+): Promise<{ message: string }> {
const isDev =
typeof window !== "undefined" && window.location.hostname === "localhost";
const url = isDev
? `${window.location.origin}/api/cpl/kueSingleModuleUpdateMock?slot=${
slot + 1
}`
- : `${window.location.origin}/CPL?/kabelueberwachung.html&KSU${slot}=1`;
+ : `${window.location.origin}/CPL?Service/ae.ACP&KSU${slot}=1`;
- fetch(url, { method: "GET" })
- .then((response) => response.json())
- .then((data) => {
- alert(
- data.message || `Update an Slot ${slot + 1} erfolgreich gestartet!`
- );
- })
- .catch((error) => {
- console.error("Fehler:", error);
- alert("Fehler beim Update!");
- });
-};
+ try {
+ const response = await fetch(url, { method: "GET" });
-export default firmwareUpdate;
+ if (!response.ok) {
+ throw new Error(`Fehler: Status ${response.status}`);
+ }
+
+ const data = await response.json();
+
+ //alert(data.message || `Update an Slot ${slot + 1} erfolgreich gestartet!`);
+ const message =
+ data.message || `Update an Slot ${slot + 1} erfolgreich gestartet!`;
+ console.log(message);
+ return { message };
+ } catch (error) {
+ console.error("Fehler:", error);
+ //alert("Fehler beim Update!");
+ return { message: "Fehler beim Update!" };
+ }
+}
diff --git a/components/main/kabelueberwachung/kue705FO/modals/KueEinstellung.tsx b/components/main/kabelueberwachung/kue705FO/modals/KueEinstellung.tsx
index b5a03f2..9614604 100644
--- a/components/main/kabelueberwachung/kue705FO/modals/KueEinstellung.tsx
+++ b/components/main/kabelueberwachung/kue705FO/modals/KueEinstellung.tsx
@@ -1,16 +1,20 @@
"use client";
-
-import { useState } from "react";
+// components/main/kabelueberwachung/kue705FO/modals/KueEinstellung.tsx
+import { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import type { RootState } from "../../../../../redux/store";
import handleSave from "../handlers/handleSave";
import handleDisplayEinschalten from "../handlers/handleDisplayEinschalten";
import firmwareUpdate from "../handlers/firmwareUpdate";
import { useAdminAuth } from "../../../settingsPageComponents/hooks/useAdminAuth";
+import ProgressModal from "@/components/main/settingsPageComponents/modals/ProgressModal";
+import { toast } from "react-toastify";
+import ConfirmModal from "@/components/common/ConfirmModal";
interface Props {
slot: number;
showModal: boolean;
+ isAdminLoggedIn: boolean; // NEU
onClose?: () => void;
onModulNameChange?: (id: string) => void;
}
@@ -29,7 +33,8 @@ const memoryIntervalOptions = [
export default function KueEinstellung({
slot,
-
+ showModal,
+ isAdminLoggedIn,
onClose = () => {},
onModulNameChange,
}: Props) {
@@ -43,18 +48,15 @@ export default function KueEinstellung({
kueLoopInterval,
memoryInterval,
} = useSelector((state: RootState) => state.kueDataSlice);
+ const [showConfirmModal, setShowConfirmModal] = useState(false);
- const { isAdminLoggedIn } = useAdminAuth(true);
-
- const formCacheKey = `slot_${slot}`;
- if (typeof window !== "undefined") {
- window.__kueCache = window.__kueCache || {};
- }
- const cached =
- typeof window !== "undefined" ? window.__kueCache?.[formCacheKey] : null;
-
+ const [isUpdating, setIsUpdating] = useState(false);
+ const [progress, setProgress] = useState(0);
const [formData, setFormData] = useState(() => {
- if (cached) return cached;
+ if (typeof window !== "undefined") {
+ const cache = window.__kueCache?.[`slot_${slot}`];
+ if (cache) return cache;
+ }
return {
kueID: kueID[slot] || "",
kueName: kueName[slot] || "",
@@ -70,17 +72,12 @@ export default function KueEinstellung({
const updated = { ...formData, [key]: value };
setFormData(updated);
if (typeof window !== "undefined") {
- window.__kueCache![formCacheKey] = updated;
+ window.__kueCache = window.__kueCache || {};
+ window.__kueCache[`slot_${slot}`] = updated;
}
};
const handleSaveWrapper = async () => {
- const updatedKueID = [...kueID];
- //updatedKueID[slot] = formData.kueID;
- /* if (Object.isFrozen(kueID)) {
- console.warn("kueID ist readonly!");
- }
- */
const updatedKueName = [...kueName];
updatedKueName[slot] = formData.kueName;
@@ -100,7 +97,7 @@ export default function KueEinstellung({
updatedMemoryInterval[slot] = Number(formData.memoryInterval);
const newData = {
- kueID: updatedKueID[slot],
+ kueID: kueID[slot],
kueName: updatedKueName[slot],
limit1: updatedLimit1[slot].toString(),
delay1: updatedDelay1[slot].toString(),
@@ -108,13 +105,11 @@ export default function KueEinstellung({
loopInterval: updatedLoopInterval[slot].toString(),
memoryInterval: updatedMemoryInterval[slot].toString(),
};
-
setFormData(newData);
if (typeof window !== "undefined") {
window.__kueCache![`slot_${slot}`] = newData;
}
- // 🔧 handleSave aufrufen mit allen Daten
await handleSave({
slot,
ids: kueID,
@@ -122,7 +117,7 @@ export default function KueEinstellung({
isolationsgrenzwerte: updatedLimit1,
verzoegerung: updatedDelay1,
untereSchleifenGrenzwerte: updatedLimit2Low,
- obereSchleifenGrenzwerte: updatedLimit2Low, // ggf. anpassen, falls du später High-Werte brauchst
+ obereSchleifenGrenzwerte: updatedLimit2Low,
schleifenintervall: updatedLoopInterval,
speicherintervall: updatedMemoryInterval,
originalValues: {
@@ -245,20 +240,62 @@ export default function KueEinstellung({
{isAdminLoggedIn && (
-
+ />
+ )}
+ {isUpdating && (
+
)}
handleDisplayEinschalten(slot)}
diff --git a/components/main/kabelueberwachung/kue705FO/modals/SettingsModalWrapper.tsx b/components/main/kabelueberwachung/kue705FO/modals/SettingsModalWrapper.tsx
index 2721861..34a6864 100644
--- a/components/main/kabelueberwachung/kue705FO/modals/SettingsModalWrapper.tsx
+++ b/components/main/kabelueberwachung/kue705FO/modals/SettingsModalWrapper.tsx
@@ -4,6 +4,7 @@ import ReactModal from "react-modal";
import KueEinstellung from "./KueEinstellung";
import TdrEinstellung from "./TdrEinstellung";
import Knotenpunkte from "./Knotenpunkte";
+import { useAdminAuth } from "@/components/main/settingsPageComponents/hooks/useAdminAuth";
interface KueModalProps {
showModal: boolean;
@@ -20,6 +21,8 @@ declare global {
}
export default function KueModal({ showModal, onClose, slot }: KueModalProps) {
+ const { isAdminLoggedIn } = useAdminAuth(true);
+
const [activeTab, setActiveTab] = useState<"kue" | "tdr" | "knoten">(() => {
if (typeof window !== "undefined" && window.__lastKueTab) {
return window.__lastKueTab;
@@ -100,6 +103,7 @@ export default function KueModal({ showModal, onClose, slot }: KueModalProps) {
showModal={showModal}
onModulNameChange={(id) => console.log("Modulname geändert:", id)}
onClose={onClose}
+ isAdminLoggedIn={isAdminLoggedIn} // Neue
/>
)}
{activeTab === "tdr" && (
diff --git a/components/main/kabelueberwachung/kue705FO/modals/SuccessProgressModal.tsx b/components/main/kabelueberwachung/kue705FO/modals/SuccessProgressModal.tsx
new file mode 100644
index 0000000..610788f
--- /dev/null
+++ b/components/main/kabelueberwachung/kue705FO/modals/SuccessProgressModal.tsx
@@ -0,0 +1,55 @@
+"use client";
+// components/main/kabelueberwachung/kue705FO/modals/SuccessProgressModal.tsx
+import React, { useEffect, useState } from "react";
+
+interface Props {
+ visible: boolean;
+ duration?: number; // in Sekunden
+ onClose: () => void;
+}
+
+const SuccessProgressModal: React.FC = ({
+ visible,
+ duration = 10,
+ onClose,
+}) => {
+ const [progress, setProgress] = useState(0);
+
+ useEffect(() => {
+ if (!visible) return;
+ setProgress(0);
+ const interval = setInterval(() => {
+ setProgress((prev) => {
+ if (prev >= 100) {
+ clearInterval(interval);
+ setTimeout(onClose, 500); // Schließen nach kurzer Verzögerung
+ return 100;
+ }
+ return prev + 100 / duration;
+ });
+ }, 1000);
+
+ return () => clearInterval(interval);
+ }, [visible, duration, onClose]);
+
+ if (!visible) return null;
+
+ return (
+
+
+
+ ✅ Firmwareupdate erfolgreich abgeschlossen.
+
+
+
{Math.floor(progress)}%
+
+
+ );
+};
+
+export default SuccessProgressModal;
diff --git a/package-lock.json b/package-lock.json
index f23a9f1..b17ebdf 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "cpl-v4",
- "version": "1.6.513",
+ "version": "1.6.515",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cpl-v4",
- "version": "1.6.513",
+ "version": "1.6.515",
"dependencies": {
"@fontsource/roboto": "^5.1.0",
"@iconify-icons/ri": "^1.2.10",
diff --git a/package.json b/package.json
index b460877..86c7e20 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "cpl-v4",
- "version": "1.6.513",
+ "version": "1.6.515",
"private": true,
"scripts": {
"dev": "next dev",
diff --git a/pages/_app.tsx b/pages/_app.tsx
index 0c68e62..675abcf 100644
--- a/pages/_app.tsx
+++ b/pages/_app.tsx
@@ -23,6 +23,10 @@ import { getReferenceCurveBySlotThunk } from "@/redux/thunks/getReferenceCurveBy
import { getAllTDRReferenceChartThunk } from "@/redux/thunks/getAllTDRReferenceChartThunk";
import { getTDRChartDataByIdThunk } from "@/redux/thunks/getTDRChartDataByIdThunk";
import { getLoopChartDataThunk } from "@/redux/thunks/getLoopChartDataThunk";
+import Modal from "react-modal";
+if (typeof window !== "undefined") {
+ Modal.setAppElement("#__next"); // oder "#root", je nach App-Struktur
+}
import "@/styles/globals.css";
diff --git a/pages/api/cpl/kueSingleModuleUpdateMock.ts b/pages/api/cpl/kueSingleModuleUpdateMock.ts
index f56711f..fedfa46 100644
--- a/pages/api/cpl/kueSingleModuleUpdateMock.ts
+++ b/pages/api/cpl/kueSingleModuleUpdateMock.ts
@@ -3,17 +3,25 @@ import type { NextApiRequest, NextApiResponse } from "next";
import fs from "fs";
import path from "path";
-export default function handler(req: NextApiRequest, res: NextApiResponse) {
+// Hilfsfunktion für künstliche Verzögerung
+const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
+
+export default async function handler(
+ req: NextApiRequest,
+ res: NextApiResponse
+) {
const filePath = path.join(
process.cwd(),
"mocks/device-cgi-simulator/firmwareUpdate/singleModuleUpdateResponse.json"
);
try {
+ // ⏱️ 10 Sekunden warten
+ await delay(25000); // 5 Minuten simulieren (300.000 ms)
+
const fileContents = fs.readFileSync(filePath, "utf-8");
const responseData = JSON.parse(fileContents);
- // Optional: slot aus query übernehmen
const slot = req.query.slot ?? "X";
responseData.message = `Update erfolgreich gestartet für Slot ${slot}`;
diff --git a/redux/slices/firmwareUpdateSlice.ts b/redux/slices/firmwareUpdateSlice.ts
new file mode 100644
index 0000000..7e2eed1
--- /dev/null
+++ b/redux/slices/firmwareUpdateSlice.ts
@@ -0,0 +1,86 @@
+// redux/slices/firmwareUpdateSlice.ts
+import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
+import firmwareUpdate from "@/components/main/kabelueberwachung/kue705FO/handlers/firmwareUpdate";
+
+interface FirmwareUpdateState {
+ isUpdating: boolean;
+ progress: number;
+ status: "idle" | "loading" | "success" | "error";
+ message: string;
+}
+
+const initialState: FirmwareUpdateState = {
+ isUpdating: false,
+ progress: 0,
+ status: "idle",
+ message: "",
+};
+
+export const startFirmwareUpdateThunk = createAsyncThunk(
+ "firmware/update",
+ async (slot: number, { dispatch, rejectWithValue }) => {
+ try {
+ const totalDuration = 5000;
+ const intervalMs = 50;
+ const steps = totalDuration / intervalMs;
+ let currentStep = 0;
+
+ dispatch(setUpdating(true));
+
+ const interval = setInterval(() => {
+ currentStep++;
+ const newProgress = Math.min((currentStep / steps) * 100, 100);
+ dispatch(setProgress(newProgress));
+ if (currentStep >= steps) clearInterval(interval);
+ }, intervalMs);
+
+ const response = await firmwareUpdate(slot);
+
+ if (response.message.includes("erfolgreich")) {
+ return response.message;
+ } else {
+ return rejectWithValue("Update fehlgeschlagen");
+ }
+ } catch (err) {
+ console.error("Fehler beim Firmwareupdate:", err);
+ return rejectWithValue("Fehler beim Firmwareupdate");
+ }
+ }
+);
+
+const firmwareUpdateSlice = createSlice({
+ name: "firmwareUpdate",
+ initialState,
+ reducers: {
+ setUpdating: (state, action: PayloadAction) => {
+ state.isUpdating = action.payload;
+ },
+ setProgress: (state, action: PayloadAction) => {
+ state.progress = action.payload;
+ },
+ resetFirmwareState: () => initialState,
+ },
+ extraReducers: (builder) => {
+ builder
+ .addCase(startFirmwareUpdateThunk.pending, (state) => {
+ state.status = "loading";
+ state.message = "Update gestartet";
+ })
+ .addCase(startFirmwareUpdateThunk.fulfilled, (state, action) => {
+ state.status = "success";
+ state.message = action.payload;
+ state.isUpdating = false;
+ state.progress = 100;
+ })
+ .addCase(startFirmwareUpdateThunk.rejected, (state, action) => {
+ state.status = "error";
+ state.message = action.payload as string;
+ state.isUpdating = false;
+ state.progress = 100;
+ });
+ },
+});
+
+export const { setUpdating, setProgress, resetFirmwareState } =
+ firmwareUpdateSlice.actions;
+export default firmwareUpdateSlice.reducer;
diff --git a/redux/store.ts b/redux/store.ts
index ca1f144..3816d51 100644
--- a/redux/store.ts
+++ b/redux/store.ts
@@ -26,6 +26,7 @@ import systemVoltTempReducer from "./slices/systemVoltTempSlice";
import analogInputsHistoryReducer from "./slices/analogInputsHistorySlice";
import selectedAnalogInputReducer from "./slices/selectedAnalogInputSlice";
import messagesReducer from "./slices/messagesSlice";
+import firmwareUpdateReducer from "@/redux/slices/firmwareUpdateSlice";
const store = configureStore({
reducer: {
@@ -54,6 +55,7 @@ const store = configureStore({
analogInputsHistory: analogInputsHistoryReducer,
selectedAnalogInput: selectedAnalogInputReducer,
messages: messagesReducer,
+ firmwareUpdate: firmwareUpdateReducer,
},
});