Compare commits

10 Commits

Author SHA1 Message Date
Ismail Ali
937e7b67e9 fix: Firmware-Update läuft nun exakt 5 Minuten bis 100 % Fortschritt
- Fehler behoben, bei dem das Firmware-Update nach wenigen Sekunden vorzeitig beendet wurde
- Fortschrittsanzeige über Redux-Slice `firmwareProgressSlice` korrekt umgesetzt
- Thunk `startFirmwareUpdateThunk` korrekt eingebunden und verwendet
- UI zeigt stabile 5-minütige Progressbar wie erwartet
2025-07-02 22:01:47 +02:00
Ismail Ali
b23d939481 fix: Firmware-Update läuft nun exakt 5 Minuten bis 100 % Fortschritt
- Fehler behoben, bei dem das Firmware-Update nach wenigen Sekunden vorzeitig beendet wurde
- Fortschrittsanzeige über Redux-Slice `firmwareProgressSlice` korrekt umgesetzt
- Thunk `startFirmwareUpdateThunk` korrekt eingebunden und verwendet
- UI zeigt stabile 5-minütige Progressbar wie erwartet
2025-07-02 22:01:17 +02:00
ISA
e9e929f577 fix: Toast-Benachrichtigungen wiederhergestellt durch Einbindung von ToastContainer
- <ToastContainer /> in _app.tsx hinzugefügt
- react-toastify funktioniert jetzt wie vorgesehen (z. B. Firmware-Update Feedback)
- autoClose-Zeit für bessere Sichtbarkeit ggf. angepasst
2025-07-02 14:24:21 +02:00
ISA
f50bff4819 fix: ConfirmModal-Zustand in Redux ausgelagert zur Stabilisierung
- Neuen confirmModalSlice erstellt für globale Steuerung des Bestätigungsdialogs
- Zustand wird nun nicht mehr durch Re-Renders oder Komponentenneuaufbau zurückgesetzt
- ConfirmModal in KueEinstellung.tsx vollständig an Redux angebunden
- Flackern und automatisches Schließen nach 10–15 Sekunden dauerhaft behoben
2025-07-02 14:16:08 +02:00
ISA
b3c5580538 feat: Auth-Status bei App-Start aus localStorage laden und in Redux speichern
- fetchAuthService erstellt zum Auslesen von isAdminLoggedIn aus localStorage
- getAuthThunks Thunk implementiert zur Initialisierung von authSlice
- authSlice erweitert um setIsAdminLoggedIn Reducer
- dispatch(getAuthThunks()) in _app.tsx integriert für automatische Initialisierung bei App-Start
- Flackern und falscher Admin-Status nach Reload dauerhaft behoben
2025-07-02 13:55:27 +02:00
ISA
1ec2c5cc14 refactor: Admin-Status direkt aus Redux ausgelesen und Props entfernt
- isAdminLoggedIn wird jetzt direkt aus authSlice im Redux-Store gelesen
- useAdminAuth und Prop-Weitergabe entfernt
- Flackern des Firmware-Buttons dauerhaft behoben
- Codestruktur vereinfacht und stabilisiert
2025-07-02 12:35:52 +02:00
ISA
a7d1e1e8df refactor: Admin-Status direkt aus Redux ausgelesen und Props entfernt
- isAdminLoggedIn wird jetzt direkt aus authSlice im Redux-Store gelesen
- useAdminAuth und Prop-Weitergabe entfernt
- Flackern des Firmware-Buttons dauerhaft behoben
- Codestruktur vereinfacht und stabilisiert
2025-07-02 12:35:15 +02:00
ISA
e46e23fada fix: Firmware-Update-Button stabilisiert und Flackern entfernt
- useAdminAuth aus KueEinstellung entfernt und einmalig in SettingsModalWrapper ausgelagert
- isAdminLoggedIn als Prop übergeben, um ständige Aktualisierungen zu vermeiden
- Button wird jetzt stabil angezeigt ohne console-Logs oder Intervall-Aufrufe
2025-07-02 12:03:41 +02:00
ISA
a9f6484fb0 fix: Firmware-Update-Button stabilisiert und Flackern entfernt
- useAdminAuth aus KueEinstellung entfernt und einmalig in SettingsModalWrapper ausgelagert
- isAdminLoggedIn als Prop übergeben, um ständige Aktualisierungen zu vermeiden
- Button wird jetzt stabil angezeigt ohne console-Logs oder Intervall-Aufrufe
2025-07-02 12:03:02 +02:00
ISA
1dfa1cc1ba 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)
2025-07-01 10:08:33 +02:00
24 changed files with 697 additions and 115 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.512 NEXT_PUBLIC_APP_VERSION=1.6.526
NEXT_PUBLIC_CPL_MODE=json # json (Entwicklungsumgebung) oder jsSimulatedProd (CPL ->CGI-Interface-Simulator) oder production (CPL-> CGI-Interface Platzhalter) NEXT_PUBLIC_CPL_MODE=json # 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_EXPORT_STATIC=true
NEXT_PUBLIC_USE_CGI=true NEXT_PUBLIC_USE_CGI=true
# App-Versionsnummer # App-Versionsnummer
NEXT_PUBLIC_APP_VERSION=1.6.512 NEXT_PUBLIC_APP_VERSION=1.6.526
NEXT_PUBLIC_CPL_MODE=production NEXT_PUBLIC_CPL_MODE=production

View File

@@ -1,3 +1,135 @@
## [1.6.526] 2025-07-02
- fix: Firmware-Update läuft nun exakt 5 Minuten bis 100% Fortschritt
- Fehler behoben, bei dem das Firmware-Update nach wenigen Sekunden vorzeitig beendet wurde
- Fortschrittsanzeige über Redux-Slice `firmwareProgressSlice` korrekt umgesetzt
- Thunk `startFirmwareUpdateThunk` korrekt eingebunden und verwendet
- UI zeigt stabile 5-minütige Progressbar wie erwartet
---
## [1.6.525] 2025-07-02
- fix: Toast-Benachrichtigungen wiederhergestellt durch Einbindung von ToastContainer
- <ToastContainer /> in _app.tsx hinzugefügt
- react-toastify funktioniert jetzt wie vorgesehen (z.B. Firmware-Update Feedback)
- autoClose-Zeit für bessere Sichtbarkeit ggf. angepasst
---
## [1.6.524] 2025-07-02
- fix: Toast-Benachrichtigungen wiederhergestellt durch Einbindung von ToastContainer
- <ToastContainer /> in _app.tsx hinzugefügt
- react-toastify funktioniert jetzt wie vorgesehen (z.B. Firmware-Update Feedback)
- autoClose-Zeit für bessere Sichtbarkeit ggf. angepasst
---
## [1.6.523] 2025-07-02
- fix: ConfirmModal-Zustand in Redux ausgelagert zur Stabilisierung
- Neuen confirmModalSlice erstellt für globale Steuerung des Bestätigungsdialogs
- Zustand wird nun nicht mehr durch Re-Renders oder Komponentenneuaufbau zurückgesetzt
- ConfirmModal in KueEinstellung.tsx vollständig an Redux angebunden
- Flackern und automatisches Schließen nach 1015 Sekunden dauerhaft behoben
---
## [1.6.522] 2025-07-02
- feat: Auth-Status bei App-Start aus localStorage laden und in Redux speichern
- fetchAuthService erstellt zum Auslesen von isAdminLoggedIn aus localStorage
- getAuthThunks Thunk implementiert zur Initialisierung von authSlice
- authSlice erweitert um setIsAdminLoggedIn Reducer
- dispatch(getAuthThunks()) in _app.tsx integriert für automatische Initialisierung bei App-Start
- Flackern und falscher Admin-Status nach Reload dauerhaft behoben
---
## [1.6.521] 2025-07-02
- refactor: Admin-Status direkt aus Redux ausgelesen und Props entfernt
- isAdminLoggedIn wird jetzt direkt aus authSlice im Redux-Store gelesen
- useAdminAuth und Prop-Weitergabe entfernt
- Flackern des Firmware-Buttons dauerhaft behoben
- Codestruktur vereinfacht und stabilisiert
---
## [1.6.520] 2025-07-02
- refactor: Admin-Status direkt aus Redux ausgelesen und Props entfernt
- isAdminLoggedIn wird jetzt direkt aus authSlice im Redux-Store gelesen
- useAdminAuth und Prop-Weitergabe entfernt
- Flackern des Firmware-Buttons dauerhaft behoben
- Codestruktur vereinfacht und stabilisiert
---
## [1.6.519] 2025-07-02
- fix: Firmware-Update-Button stabilisiert und Flackern entfernt
- useAdminAuth aus KueEinstellung entfernt und einmalig in SettingsModalWrapper ausgelagert
- isAdminLoggedIn als Prop übergeben, um ständige Aktualisierungen zu vermeiden
- Button wird jetzt stabil angezeigt ohne console-Logs oder Intervall-Aufrufe
---
## [1.6.518] 2025-07-02
- fix: Firmware-Update-Button stabilisiert und Flackern entfernt
- useAdminAuth aus KueEinstellung entfernt und einmalig in SettingsModalWrapper ausgelagert
- isAdminLoggedIn als Prop übergeben, um ständige Aktualisierungen zu vermeiden
- Button wird jetzt stabil angezeigt ohne console-Logs oder Intervall-Aufrufe
---
## [1.6.517] 2025-07-02
- fix: Firmware-Update-Button stabilisiert und Flackern entfernt
- useAdminAuth aus KueEinstellung entfernt und einmalig in SettingsModalWrapper ausgelagert
- isAdminLoggedIn als Prop übergeben, um ständige Aktualisierungen zu vermeiden
- Button wird jetzt stabil angezeigt ohne console-Logs oder Intervall-Aufrufe
---
## [1.6.516] 2025-07-02
- fix: Firmware-Update-Button stabilisiert und Flackern entfernt
- useAdminAuth aus KueEinstellung entfernt und einmalig in SettingsModalWrapper ausgelagert
- isAdminLoggedIn als Prop übergeben, um ständige Aktualisierungen zu vermeiden
- Button wird jetzt stabil angezeigt ohne console-Logs oder Intervall-Aufrufe
---
## [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
---
## [1.6.512] 2025-07-01 ## [1.6.512] 2025-07-01
- fix: hide Firmware update button if admin not loged in - fix: hide Firmware update button if admin not loged in

View File

@@ -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 (
<div className="fixed inset-0 bg-black bg-opacity-40 flex items-center justify-center z-50">
<div className="bg-white p-6 rounded shadow-xl w-[360px] max-w-full text-center">
{title && <h2 className="text-lg font-semibold mb-3">{title}</h2>}
<p className="mb-6 text-gray-800">{message}</p>
<div className="flex justify-center gap-4">
<button
className="bg-gray-300 hover:bg-gray-400 text-black px-4 py-2 rounded"
onClick={onCancel}
>
Abbrechen
</button>
<button
className="bg-littwin-blue hover:bg-blue-700 text-white px-4 py-2 rounded"
onClick={onConfirm}
>
Bestätigen
</button>
</div>
</div>
</div>
);
}

View File

@@ -1,24 +1,32 @@
// /komponents/main/kabelueberwachung/kue705FO/handlers/firmwareUpdate.ts // @/components/main/kabelueberwachung/kue705FO/handlers/firmwareUpdate.ts
const firmwareUpdate = (slot: number) => { export default async function firmwareUpdate(
slot: number
): Promise<{ message: string }> {
const isDev = const isDev =
typeof window !== "undefined" && window.location.hostname === "localhost"; typeof window !== "undefined" && window.location.hostname === "localhost";
const url = isDev const url = isDev
? `${window.location.origin}/api/cpl/kueSingleModuleUpdateMock?slot=${ ? `${window.location.origin}/api/cpl/kueSingleModuleUpdateMock?slot=${
slot + 1 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" }) try {
.then((response) => response.json()) const response = await fetch(url, { method: "GET" });
.then((data) => {
alert(
data.message || `Update an Slot ${slot + 1} erfolgreich gestartet!`
);
})
.catch((error) => {
console.error("Fehler:", error);
alert("Fehler beim Update!");
});
};
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!" };
}
}

View File

@@ -1,12 +1,19 @@
"use client"; "use client";
// components/main/kabelueberwachung/kue705FO/modals/KueEinstellung.tsx
import { useState } from "react"; import { useState } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import type { RootState } from "../../../../../redux/store"; import type { RootState, AppDispatch } from "../../../../../redux/store";
import handleSave from "../handlers/handleSave"; import handleSave from "../handlers/handleSave";
import handleDisplayEinschalten from "../handlers/handleDisplayEinschalten"; import handleDisplayEinschalten from "../handlers/handleDisplayEinschalten";
import firmwareUpdate from "../handlers/firmwareUpdate"; 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";
import {
openConfirmModal,
closeConfirmModal,
} from "@/redux/slices/confirmModalSlice";
import { startFirmwareUpdateThunk } from "@/redux/thunks/startFirmwareUpdateThunk";
interface Props { interface Props {
slot: number; slot: number;
@@ -29,11 +36,10 @@ const memoryIntervalOptions = [
export default function KueEinstellung({ export default function KueEinstellung({
slot, slot,
onClose = () => {}, onClose = () => {},
onModulNameChange, onModulNameChange,
}: Props) { }: Props) {
const dispatch = useDispatch(); const dispatch = useDispatch<AppDispatch>();
const { const {
kueID, kueID,
kueName, kueName,
@@ -43,18 +49,27 @@ export default function KueEinstellung({
kueLoopInterval, kueLoopInterval,
memoryInterval, memoryInterval,
} = useSelector((state: RootState) => state.kueDataSlice); } = useSelector((state: RootState) => state.kueDataSlice);
const reduxAdmin = useSelector(
(state: RootState) => state.authSlice.isAdminLoggedIn
);
const [isAdminLoggedIn] = useState(() => reduxAdmin);
const { isAdminLoggedIn } = useAdminAuth(true); const showConfirmModal = useSelector(
(state: RootState) => state.confirmModal.open
);
const formCacheKey = `slot_${slot}`; const isUpdating = useSelector(
if (typeof window !== "undefined") { (state: RootState) => state.firmwareProgress.isUpdating
window.__kueCache = window.__kueCache || {}; );
} const progress = useSelector(
const cached = (state: RootState) => state.firmwareProgress.progress
typeof window !== "undefined" ? window.__kueCache?.[formCacheKey] : null; );
const [formData, setFormData] = useState(() => { const [formData, setFormData] = useState(() => {
if (cached) return cached; if (typeof window !== "undefined") {
const cache = window.__kueCache?.[`slot_${slot}`];
if (cache) return cache;
}
return { return {
kueID: kueID[slot] || "", kueID: kueID[slot] || "",
kueName: kueName[slot] || "", kueName: kueName[slot] || "",
@@ -70,17 +85,12 @@ export default function KueEinstellung({
const updated = { ...formData, [key]: value }; const updated = { ...formData, [key]: value };
setFormData(updated); setFormData(updated);
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
window.__kueCache![formCacheKey] = updated; window.__kueCache = window.__kueCache || {};
window.__kueCache[`slot_${slot}`] = updated;
} }
}; };
const handleSaveWrapper = async () => { const handleSaveWrapper = async () => {
const updatedKueID = [...kueID];
//updatedKueID[slot] = formData.kueID;
/* if (Object.isFrozen(kueID)) {
console.warn("kueID ist readonly!");
}
*/
const updatedKueName = [...kueName]; const updatedKueName = [...kueName];
updatedKueName[slot] = formData.kueName; updatedKueName[slot] = formData.kueName;
@@ -100,7 +110,7 @@ export default function KueEinstellung({
updatedMemoryInterval[slot] = Number(formData.memoryInterval); updatedMemoryInterval[slot] = Number(formData.memoryInterval);
const newData = { const newData = {
kueID: updatedKueID[slot], kueID: kueID[slot],
kueName: updatedKueName[slot], kueName: updatedKueName[slot],
limit1: updatedLimit1[slot].toString(), limit1: updatedLimit1[slot].toString(),
delay1: updatedDelay1[slot].toString(), delay1: updatedDelay1[slot].toString(),
@@ -108,13 +118,11 @@ export default function KueEinstellung({
loopInterval: updatedLoopInterval[slot].toString(), loopInterval: updatedLoopInterval[slot].toString(),
memoryInterval: updatedMemoryInterval[slot].toString(), memoryInterval: updatedMemoryInterval[slot].toString(),
}; };
setFormData(newData); setFormData(newData);
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
window.__kueCache![`slot_${slot}`] = newData; window.__kueCache![`slot_${slot}`] = newData;
} }
// 🔧 handleSave aufrufen mit allen Daten
await handleSave({ await handleSave({
slot, slot,
ids: kueID, ids: kueID,
@@ -122,7 +130,7 @@ export default function KueEinstellung({
isolationsgrenzwerte: updatedLimit1, isolationsgrenzwerte: updatedLimit1,
verzoegerung: updatedDelay1, verzoegerung: updatedDelay1,
untereSchleifenGrenzwerte: updatedLimit2Low, untereSchleifenGrenzwerte: updatedLimit2Low,
obereSchleifenGrenzwerte: updatedLimit2Low, // ggf. anpassen, falls du später High-Werte brauchst obereSchleifenGrenzwerte: updatedLimit2Low,
schleifenintervall: updatedLoopInterval, schleifenintervall: updatedLoopInterval,
speicherintervall: updatedMemoryInterval, speicherintervall: updatedMemoryInterval,
originalValues: { originalValues: {
@@ -245,20 +253,37 @@ export default function KueEinstellung({
</div> </div>
<div className="flex justify-end gap-2 p-0 rounded"> <div className="flex justify-end gap-2 p-0 rounded">
{isAdminLoggedIn && ( {isAdminLoggedIn && (
<button <>
onClick={() => { <button
if ( onClick={() => dispatch(openConfirmModal())}
window.confirm( className="bg-littwin-blue text-white px-4 py-2 rounded flex items-center"
"Warnung: Das Firmware-Update kann einige Minuten dauern und das Gerät neu starten.\nMöchten Sie wirklich fortfahren?" >
) Firmware Update
) { </button>
firmwareUpdate(slot); </>
)}
{showConfirmModal && (
<ConfirmModal
open={showConfirmModal}
title="Firmware-Update starten?"
message="⚠️ Das Firmware-Update kann einige Minuten dauern. Möchten Sie wirklich fortfahren?"
onCancel={() => dispatch(closeConfirmModal())}
onConfirm={async () => {
dispatch(closeConfirmModal());
toast.info("Firmware-Update gestartet. Bitte warten...");
dispatch(startFirmwareUpdateThunk(slot)); // Start Redux-Prozess
try {
await firmwareUpdate(slot);
} catch (err) {
console.error("Firmware-Update-Fehler:", err);
toast.error("❌ Fehler beim Firmwareupdate");
} }
}} }}
className="bg-littwin-blue text-white px-4 py-2 rounded flex items-center" />
> )}
Firmware Update {isUpdating && (
</button> <ProgressModal visible={isUpdating} progress={progress} />
)} )}
<button <button
onClick={() => handleDisplayEinschalten(slot)} onClick={() => handleDisplayEinschalten(slot)}

View File

@@ -38,7 +38,9 @@ export default function KueModal({ showModal, onClose, slot }: KueModalProps) {
window.kabelModalOpen = showModal; window.kabelModalOpen = showModal;
} }
}, [showModal]); }, [showModal]);
//-----------------------------------------------------
//------------------------------------------------------
return ( return (
<ReactModal <ReactModal
isOpen={showModal} isOpen={showModal}

View File

@@ -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<Props> = ({
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 (
<div className="fixed inset-0 z-50 bg-black bg-opacity-40 flex items-center justify-center">
<div className="bg-white p-6 rounded-lg shadow-md text-center w-72">
<h2 className="text-lg font-bold text-green-600 mb-4">
Firmwareupdate erfolgreich abgeschlossen.
</h2>
<div className="w-full bg-gray-200 rounded h-3 overflow-hidden">
<div
className="h-3 bg-green-500 transition-all duration-100"
style={{ width: `${progress}%` }}
></div>
</div>
<p className="text-sm mt-2">{Math.floor(progress)}%</p>
</div>
</div>
);
};
export default SuccessProgressModal;

View File

@@ -13,12 +13,17 @@ import { getSystemSettingsThunk } from "../../../redux/thunks/getSystemSettingsT
import handleGeneralSubmit from "./handlers/handleGeneralSubmit"; import handleGeneralSubmit from "./handlers/handleGeneralSubmit";
import handleKueFirmwareUpdate from "@/components/main/settingsPageComponents/handlers/handleKueFirmwareUpdate"; import handleKueFirmwareUpdate from "@/components/main/settingsPageComponents/handlers/handleKueFirmwareUpdate";
import { useAdminAuth } from "@/components/main/settingsPageComponents/hooks/useAdminAuth"; import { useAdminAuth } from "@/components/main/settingsPageComponents/hooks/useAdminAuth";
import ProgressModal from "@/components/main/settingsPageComponents/modals/ProgressModal";
import "react-toastify/dist/ReactToastify.css";
const GeneralSettings: React.FC = () => { const GeneralSettings: React.FC = () => {
const dispatch = useDispatch<AppDispatch>(); const dispatch = useDispatch<AppDispatch>();
const systemSettings = useSelector( const systemSettings = useSelector(
(state: RootState) => state.systemSettingsSlice (state: RootState) => state.systemSettingsSlice
); );
const [isUpdating, setIsUpdating] = useState(false);
const [progress, setProgress] = useState(0);
const { isAdminLoggedIn } = useAdminAuth(true); const { isAdminLoggedIn } = useAdminAuth(true);
@@ -143,50 +148,6 @@ const GeneralSettings: React.FC = () => {
/> />
</div> </div>
{/* Admin Login */}
{/*
<div className="col-span-2 flex flex-col gap-1">
{isAdminLoggedIn ? (
<button
type="button"
className="bg-littwin-blue text-white px-4 py-2 h-8 text-xs rounded whitespace-nowrap"
onClick={logoutAdmin}
>
Admin abmelden
</button>
) : (
<>
<div className="flex flex-row gap-3">
<input
type="text"
placeholder="Benutzername"
className="border border-gray-300 rounded h-8 p-1 w-full text-xs"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<input
type="password"
placeholder="Passwort"
className="border border-gray-300 rounded h-8 p-1 w-full text-xs"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button
type="button"
className="bg-littwin-blue text-white px-4 py-2 h-8 text-xs rounded whitespace-nowrap"
onClick={handleLogin}
>
Admin anmelden
</button>
</div>
</>
)}
</div>
*/}
{/* Feedback */}
{/* You can add feedback here if needed */}
{/* Buttons */} {/* Buttons */}
<div className="col-span-2 flex flex-wrap md:justify-between gap-1 mt-2"> <div className="col-span-2 flex flex-wrap md:justify-between gap-1 mt-2">
<button <button
@@ -205,7 +166,45 @@ const GeneralSettings: React.FC = () => {
"⚠️ Wollen Sie wirklich ein Firmwareupdate für alle KÜ-Module starten?" "⚠️ Wollen Sie wirklich ein Firmwareupdate für alle KÜ-Module starten?"
); );
if (confirmed) { if (confirmed) {
handleKueFirmwareUpdate(); setIsUpdating(true);
setProgress(0);
const updateDuration = 300; // Sekunden (5 Minuten)
const intervalMs = 1000;
let elapsed = 0;
const interval = setInterval(() => {
elapsed++;
const newProgress = Math.min(
(elapsed / updateDuration) * 100,
100
);
setProgress(newProgress);
if (elapsed >= updateDuration) {
clearInterval(interval);
setIsUpdating(false);
}
}, intervalMs);
handleKueFirmwareUpdate()
.then(() => {
clearInterval(interval);
setProgress(100);
setTimeout(() => {
setIsUpdating(false);
setProgress(100);
setTimeout(() => {
alert("✅ Firmwareupdate erfolgreich abgeschlossen.");
}, 300); // Nach Modal-Schließung
}, 500);
})
.catch((error) => {
console.error("Update-Fehler:", error);
clearInterval(interval);
setIsUpdating(false);
});
} }
}} }}
> >
@@ -213,6 +212,8 @@ const GeneralSettings: React.FC = () => {
</button> </button>
)} )}
<ProgressModal visible={isUpdating} progress={progress} />
<button <button
type="button" type="button"
className="bg-littwin-blue text-white px-4 py-2 h-8 text-xs rounded whitespace-nowrap" className="bg-littwin-blue text-white px-4 py-2 h-8 text-xs rounded whitespace-nowrap"

View File

@@ -11,7 +11,7 @@ const handleKueFirmwareUpdate = async () => {
const result = await res.text(); const result = await res.text();
console.log("Firmwareupdate gesendet:", result); console.log("Firmwareupdate gesendet:", result);
alert("Firmwareupdate wurde an alle KÜ-Module gesendet."); // alert("Firmwareupdate wurde an alle KÜ-Module gesendet.");
} catch (error) { } catch (error) {
console.error("Fehler beim Firmwareupdate:", error); console.error("Fehler beim Firmwareupdate:", error);
alert("Fehler beim Firmwareupdate."); alert("Fehler beim Firmwareupdate.");

View File

@@ -0,0 +1,28 @@
"use client";
import React from "react";
type Props = {
visible: boolean;
progress: number;
};
const ProgressModal: React.FC<Props> = ({ visible, progress }) => {
if (!visible) return null;
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white p-6 rounded shadow-md text-center w-80">
<h2 className="text-lg font-bold mb-4">Firmwareupdate läuft...</h2>
<div className="w-full bg-gray-200 rounded-full h-4">
<div
className="bg-blue-500 h-4 rounded-full transition-all duration-100"
style={{ width: `${progress}%` }}
></div>
</div>
<p className="mt-4 text-sm">{Math.round(progress)}% abgeschlossen</p>
</div>
</div>
);
};
export default ProgressModal;

View File

@@ -1,4 +1,69 @@
[ [
{
"timestamp": "2025-07-01T08:03:33.189Z",
"command": "&KSU99=1",
"message": "Firmwareupdate an alle KÜ-Module ausgelöst (Mock)"
},
{
"timestamp": "2025-07-01T07:55:54.961Z",
"command": "&KSU99=1",
"message": "Firmwareupdate an alle KÜ-Module ausgelöst (Mock)"
},
{
"timestamp": "2025-07-01T07:53:19.780Z",
"command": "&KSU99=1",
"message": "Firmwareupdate an alle KÜ-Module ausgelöst (Mock)"
},
{
"timestamp": "2025-07-01T07:49:25.419Z",
"command": "&KSU99=1",
"message": "Firmwareupdate an alle KÜ-Module ausgelöst (Mock)"
},
{
"timestamp": "2025-07-01T07:46:30.008Z",
"command": "&KSU99=1",
"message": "Firmwareupdate an alle KÜ-Module ausgelöst (Mock)"
},
{
"timestamp": "2025-07-01T07:45:57.366Z",
"command": "&KSU99=1",
"message": "Firmwareupdate an alle KÜ-Module ausgelöst (Mock)"
},
{
"timestamp": "2025-07-01T07:39:41.157Z",
"command": "&KSU99=1",
"message": "Firmwareupdate an alle KÜ-Module ausgelöst (Mock)"
},
{
"timestamp": "2025-07-01T07:39:05.316Z",
"command": "&KSU99=1",
"message": "Firmwareupdate an alle KÜ-Module ausgelöst (Mock)"
},
{
"timestamp": "2025-07-01T07:37:55.313Z",
"command": "&KSU99=1",
"message": "Firmwareupdate an alle KÜ-Module ausgelöst (Mock)"
},
{
"timestamp": "2025-07-01T07:37:20.818Z",
"command": "&KSU99=1",
"message": "Firmwareupdate an alle KÜ-Module ausgelöst (Mock)"
},
{
"timestamp": "2025-07-01T07:26:45.785Z",
"command": "&KSU99=1",
"message": "Firmwareupdate an alle KÜ-Module ausgelöst (Mock)"
},
{
"timestamp": "2025-07-01T06:19:21.460Z",
"command": "&KSU99=1",
"message": "Firmwareupdate an alle KÜ-Module ausgelöst (Mock)"
},
{
"timestamp": "2025-07-01T06:06:58.694Z",
"command": "&KSU99=1",
"message": "Firmwareupdate an alle KÜ-Module ausgelöst (Mock)"
},
{ {
"timestamp": "2025-07-01T05:48:51.161Z", "timestamp": "2025-07-01T05:48:51.161Z",
"command": "&KSU99=1", "command": "&KSU99=1",

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "cpl-v4", "name": "cpl-v4",
"version": "1.6.512", "version": "1.6.526",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "cpl-v4", "name": "cpl-v4",
"version": "1.6.512", "version": "1.6.526",
"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.512", "version": "1.6.526",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",

View File

@@ -23,6 +23,14 @@ import { getReferenceCurveBySlotThunk } from "@/redux/thunks/getReferenceCurveBy
import { getAllTDRReferenceChartThunk } from "@/redux/thunks/getAllTDRReferenceChartThunk"; import { getAllTDRReferenceChartThunk } from "@/redux/thunks/getAllTDRReferenceChartThunk";
import { getTDRChartDataByIdThunk } from "@/redux/thunks/getTDRChartDataByIdThunk"; import { getTDRChartDataByIdThunk } from "@/redux/thunks/getTDRChartDataByIdThunk";
import { getLoopChartDataThunk } from "@/redux/thunks/getLoopChartDataThunk"; import { getLoopChartDataThunk } from "@/redux/thunks/getLoopChartDataThunk";
import { getAuthThunks } from "@/redux/thunks/getAuthThunks";
import Modal from "react-modal";
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
if (typeof window !== "undefined") {
Modal.setAppElement("#__next"); // oder "#root", je nach App-Struktur
}
import "@/styles/globals.css"; import "@/styles/globals.css";
@@ -50,6 +58,7 @@ function AppContent({
const pathname = window.location.pathname; const pathname = window.location.pathname;
const loadAndDispatch = () => { const loadAndDispatch = () => {
dispatch(getAuthThunks());
if (pathname.includes("kabelueberwachung")) { if (pathname.includes("kabelueberwachung")) {
dispatch(getKueDataThunk()); dispatch(getKueDataThunk());
} else if (pathname.includes("digitalOutputs")) { } else if (pathname.includes("digitalOutputs")) {
@@ -107,6 +116,7 @@ function AppContent({
</div> </div>
)} )}
<Component {...pageProps} /> <Component {...pageProps} />
<ToastContainer position="top-right" autoClose={3000} />
</main> </main>
</div> </div>
<Footer /> <Footer />

View File

@@ -1,8 +1,15 @@
// pages/api/cpl/kueFirmwareUpdateMock.ts
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from "next";
import fs from "fs"; import fs from "fs";
import path from "path"; 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
) {
try { try {
const logFilePath = path.join( const logFilePath = path.join(
process.cwd(), process.cwd(),
@@ -22,22 +29,22 @@ export default function handler(req: NextApiRequest, res: NextApiResponse) {
existingLog = JSON.parse(fileContent); existingLog = JSON.parse(fileContent);
} }
// Letzten 50 Einträge speichern
const updatedLog = [logEntry, ...existingLog].slice(0, 50); const updatedLog = [logEntry, ...existingLog].slice(0, 50);
fs.writeFileSync(logFilePath, JSON.stringify(updatedLog, null, 2), "utf-8"); fs.writeFileSync(logFilePath, JSON.stringify(updatedLog, null, 2), "utf-8");
console.log("🕒 Starte Firmware-Mock-Wartezeit (5 Minuten)...");
await delay(10000); // 5 Minuten simulieren (300.000 ms)
console.log("✅ Firmwareupdate-Mock abgeschlossen.");
res.status(200).json({ res.status(200).json({
status: "success", status: "success",
log: logEntry, log: logEntry,
}); });
} catch (error) { } catch (error) {
console.error("Fehler beim Firmwareupdate-Mock:", error); console.error("Fehler beim Firmwareupdate-Mock:", error);
res res.status(500).json({
.status(500) status: "error",
.json({ message: "Fehler beim Speichern des Firmwareupdates",
status: "error", });
message: "Fehler beim Speichern des Firmwareupdates",
});
} }
} }

View File

@@ -3,17 +3,25 @@ import type { NextApiRequest, NextApiResponse } from "next";
import fs from "fs"; import fs from "fs";
import path from "path"; 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( const filePath = path.join(
process.cwd(), process.cwd(),
"mocks/device-cgi-simulator/firmwareUpdate/singleModuleUpdateResponse.json" "mocks/device-cgi-simulator/firmwareUpdate/singleModuleUpdateResponse.json"
); );
try { try {
// ⏱️ 10 Sekunden warten
await delay(25000); // 5 Minuten simulieren (300.000 ms)
const fileContents = fs.readFileSync(filePath, "utf-8"); const fileContents = fs.readFileSync(filePath, "utf-8");
const responseData = JSON.parse(fileContents); const responseData = JSON.parse(fileContents);
// Optional: slot aus query übernehmen
const slot = req.query.slot ?? "X"; const slot = req.query.slot ?? "X";
responseData.message = `Update erfolgreich gestartet für Slot ${slot}`; responseData.message = `Update erfolgreich gestartet für Slot ${slot}`;

View File

@@ -0,0 +1,29 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
interface ConfirmModalState {
open: boolean;
}
const initialState: ConfirmModalState = {
open: false,
};
export const confirmModalSlice = createSlice({
name: "confirmModal",
initialState,
reducers: {
openConfirmModal: (state) => {
state.open = true;
},
closeConfirmModal: (state) => {
state.open = false;
},
setConfirmModal: (state, action: PayloadAction<boolean>) => {
state.open = action.payload;
},
},
});
export const { openConfirmModal, closeConfirmModal, setConfirmModal } =
confirmModalSlice.actions;
export default confirmModalSlice.reducer;

View File

@@ -0,0 +1,28 @@
// redux/slices/firmwareProgressSlice.ts
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
interface State {
progress: number;
isUpdating: boolean;
}
const initialState: State = {
progress: 0,
isUpdating: false,
};
export const firmwareProgressSlice = createSlice({
name: "firmwareProgress",
initialState,
reducers: {
setProgress: (state, action: PayloadAction<number>) => {
state.progress = action.payload;
},
setIsUpdating: (state, action: PayloadAction<boolean>) => {
state.isUpdating = action.payload;
},
},
});
export const { setProgress, setIsUpdating } = firmwareProgressSlice.actions;
export default firmwareProgressSlice.reducer;

View File

@@ -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<boolean>) => {
state.isUpdating = action.payload;
},
setProgress: (state, action: PayloadAction<number>) => {
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;

View File

@@ -26,6 +26,9 @@ import systemVoltTempReducer from "./slices/systemVoltTempSlice";
import analogInputsHistoryReducer from "./slices/analogInputsHistorySlice"; import analogInputsHistoryReducer from "./slices/analogInputsHistorySlice";
import selectedAnalogInputReducer from "./slices/selectedAnalogInputSlice"; import selectedAnalogInputReducer from "./slices/selectedAnalogInputSlice";
import messagesReducer from "./slices/messagesSlice"; import messagesReducer from "./slices/messagesSlice";
import firmwareUpdateReducer from "@/redux/slices/firmwareUpdateSlice";
import confirmModalReducer from "./slices/confirmModalSlice";
import firmwareProgressReducer from "./slices/firmwareProgressSlice";
const store = configureStore({ const store = configureStore({
reducer: { reducer: {
@@ -54,6 +57,9 @@ const store = configureStore({
analogInputsHistory: analogInputsHistoryReducer, analogInputsHistory: analogInputsHistoryReducer,
selectedAnalogInput: selectedAnalogInputReducer, selectedAnalogInput: selectedAnalogInputReducer,
messages: messagesReducer, messages: messagesReducer,
firmwareUpdate: firmwareUpdateReducer,
confirmModal: confirmModalReducer,
firmwareProgress: firmwareProgressReducer,
}, },
}); });

View File

@@ -0,0 +1,12 @@
// redux/thunks/getAuthThunks.ts
import { createAsyncThunk } from "@reduxjs/toolkit";
import { fetchAuthService } from "@/services/fetchAuthService";
import { setAdminLoggedIn } from "@/redux/slices/authSlice";
export const getAuthThunks = createAsyncThunk(
"auth/getAuthThunks",
async (_, { dispatch }) => {
const { isAdminLoggedIn } = fetchAuthService();
dispatch(setAdminLoggedIn(isAdminLoggedIn)); // boolean, nicht string!
}
);

View File

@@ -0,0 +1,31 @@
// redux/thunks/startFirmwareUpdateThunk.ts
import { AppDispatch } from "../store";
import {
setProgress,
setIsUpdating,
} from "@/redux/slices/firmwareProgressSlice";
export const startFirmwareUpdateThunk =
(slot: number) => async (dispatch: AppDispatch) => {
dispatch(setIsUpdating(true));
dispatch(setProgress(0));
const totalDuration = 5 * 60 * 1000;
const intervalMs = 1000;
const steps = totalDuration / intervalMs;
let currentStep = 0;
const interval = setInterval(() => {
currentStep++;
const percent = Math.min((currentStep / steps) * 100, 100);
dispatch(setProgress(percent));
if (currentStep >= steps) {
clearInterval(interval);
dispatch(setProgress(100));
setTimeout(() => {
dispatch(setIsUpdating(false));
}, 500);
}
}, intervalMs);
};

View File

@@ -0,0 +1,6 @@
// services/fetchAuthService.ts
export const fetchAuthService = () => {
const isAdminLoggedInRaw = localStorage.getItem("isAdminLoggedIn");
const isAdminLoggedIn = isAdminLoggedInRaw === "true"; // < explizit Boolean
return { isAdminLoggedIn };
};