feat: OPC-UA Einstellungen in eigenen Redux Slice ausgelagert
- OPC-UA bezogene Variablen aus `variablesSlice` entfernt und in `opcuaSettingsSlice` ausgelagert
- Neue Redux Actions für:
- `setOpcUaZustand` (OPC-UA Zustand setzen)
- `setOpcUaEncryption` (Verschlüsselung setzen)
- `setOpcUaActiveClientCount` (Anzahl aktiver Clients setzen)
- `setOpcUaNodesetName` (Nodeset-Name setzen)
- `addOpcUaUser` & `removeOpcUaUser` (Benutzerverwaltung)
- `loadWindowVariables.ts` angepasst, um OPC-UA-Daten in `opcuaSettingsSlice` zu speichern
- Benutzerverwaltung optimiert:
- Manuell hinzugefügte Benutzer bleiben erhalten
- Benutzer werden nur aktualisiert, wenn sich `window.win_opcUaUsers` ändert
- Keine automatische Statusumschaltung mehr beim OPC-UA-Server-Button
Jetzt ist die OPC-UA Verwaltung sauber getrennt und stabil! 🚀
This commit is contained in:
@@ -11,7 +11,6 @@ const GeneralSettings = () => {
|
|||||||
const systemSettings = useSelector(
|
const systemSettings = useSelector(
|
||||||
(state: RootState) => state.systemSettings
|
(state: RootState) => state.systemSettings
|
||||||
);
|
);
|
||||||
console.log("Redux SystemSettings:", systemSettings);
|
|
||||||
|
|
||||||
const [name, setName] = useState(systemSettings.deviceName || "");
|
const [name, setName] = useState(systemSettings.deviceName || "");
|
||||||
const [ip, setIp] = useState(systemSettings.ip || "");
|
const [ip, setIp] = useState(systemSettings.ip || "");
|
||||||
|
|||||||
@@ -1,57 +1,62 @@
|
|||||||
|
"use client"; // /components/main/settingsPageComponents/OPCUAInterfaceSettings.tsx
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
|
import { RootState } from "../../../redux/store";
|
||||||
|
import {
|
||||||
|
setOpcUaEncryption,
|
||||||
|
toggleOpcUaServer,
|
||||||
|
setOpcUaNodesetName,
|
||||||
|
addOpcUaUser,
|
||||||
|
removeOpcUaUser,
|
||||||
|
} from "../../../redux/slices/opcuaSettingsSlice";
|
||||||
|
|
||||||
export default function OPCUAInterfaceSettings() {
|
export default function OPCUAInterfaceSettings() {
|
||||||
const [isEnabled, setIsEnabled] = useState(true);
|
const dispatch = useDispatch();
|
||||||
const [encryption, setEncryption] = useState("None");
|
const opcuaSettings = useSelector((state: RootState) => state.opcuaSettings);
|
||||||
const [users, setUsers] = useState([
|
|
||||||
{ id: 1, username: "admin", password: "admin123" },
|
|
||||||
{ id: 2, username: "user1", password: "user123" },
|
|
||||||
]);
|
|
||||||
const [newUser, setNewUser] = useState({ username: "", password: "" });
|
|
||||||
|
|
||||||
const toggleServer = () => setIsEnabled(!isEnabled);
|
// Lokale Zustände für das neue Benutzerformular
|
||||||
const updateEncryption = (event) => setEncryption(event.target.value);
|
const [newUsername, setNewUsername] = useState("");
|
||||||
const addUser = () => {
|
const [newPassword, setNewPassword] = useState("");
|
||||||
if (newUser.username && newUser.password) {
|
const [nodesetName, setNodesetName] = useState(
|
||||||
setUsers([...users, { id: users.length + 1, ...newUser }]);
|
opcuaSettings.opcUaNodesetName
|
||||||
setNewUser({ username: "", password: "" });
|
);
|
||||||
|
|
||||||
|
const handleAddUser = () => {
|
||||||
|
if (newUsername.trim() && newPassword.trim()) {
|
||||||
|
dispatch(addOpcUaUser({ username: newUsername, password: newPassword }));
|
||||||
|
setNewUsername("");
|
||||||
|
setNewPassword("");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const removeUser = (id) => setUsers(users.filter((user) => user.id !== id));
|
|
||||||
|
const handleNodesetUpdate = () => {
|
||||||
|
dispatch(setOpcUaNodesetName(nodesetName));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-3xl mx-auto p-6 bg-gray-100 shadow-md rounded-lg">
|
<div className="max-w-3xl mx-auto p-4 bg-gray-100 shadow-md rounded-lg">
|
||||||
<h2 className="text-xl font-semibold mb-4">OPCUA Server Einstellungen</h2>
|
<h2 className="text-lg font-semibold mb-4">OPCUA Server Einstellungen</h2>
|
||||||
|
|
||||||
{/* Server Aktivierung */}
|
{/* ✅ Server Aktivierung */}
|
||||||
<div className="mb-4">
|
<div className="mb-4 flex items-center">
|
||||||
<label className="flex items-center cursor-pointer">
|
<label className="mr-4 font-medium">Server Status:</label>
|
||||||
<input
|
<button
|
||||||
type="checkbox"
|
onClick={() => dispatch(toggleOpcUaServer())}
|
||||||
checked={isEnabled}
|
className={`px-4 py-2 rounded ${
|
||||||
onChange={toggleServer}
|
opcuaSettings.isEnabled ? "bg-green-500" : "bg-gray-300"
|
||||||
className="hidden"
|
} text-white`}
|
||||||
/>
|
>
|
||||||
<div
|
{opcuaSettings.isEnabled ? "Aktiviert" : "Deaktiviert"}
|
||||||
className={`w-12 h-6 rounded-full transition-all ${
|
</button>
|
||||||
isEnabled ? "bg-green-500" : "bg-gray-300"
|
|
||||||
}`}
|
|
||||||
></div>
|
|
||||||
<span className="ml-2 text-sm">
|
|
||||||
{isEnabled ? "Aktiviert" : "Deaktiviert"}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Verschlüsselung */}
|
{/* ✅ Verschlüsselung */}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block font-medium mb-1">Verschlüsselung</label>
|
||||||
Verschlüsselung
|
|
||||||
</label>
|
|
||||||
<select
|
<select
|
||||||
value={encryption}
|
value={opcuaSettings.encryption}
|
||||||
onChange={updateEncryption}
|
onChange={(e) => dispatch(setOpcUaEncryption(e.target.value))}
|
||||||
className="mt-1 block w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring focus:ring-blue-200"
|
className="w-full p-2 border border-gray-300 rounded-md"
|
||||||
>
|
>
|
||||||
<option value="None">Keine</option>
|
<option value="None">Keine</option>
|
||||||
<option value="Basic256">Basic256</option>
|
<option value="Basic256">Basic256</option>
|
||||||
@@ -59,11 +64,46 @@ export default function OPCUAInterfaceSettings() {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Benutzer & Passwörter */}
|
{/* ✅ OPCUA Zustand (nur Anzeige) */}
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="block font-medium mb-1">OPCUA Zustand</label>
|
||||||
|
<div className="p-2 border border-gray-300 rounded-md bg-white">
|
||||||
|
{opcuaSettings.opcUaZustand}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ✅ Aktive Clients (nur Anzeige) */}
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="block font-medium mb-1">Aktive Clients</label>
|
||||||
|
<div className="p-2 border border-gray-300 rounded-md bg-white">
|
||||||
|
{opcuaSettings.opcUaActiveClientCount}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ✅ Nodeset Name */}
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="block font-medium mb-1">Nodeset Name</label>
|
||||||
|
<div className="flex">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="flex-grow p-2 border border-gray-300 rounded-l-md"
|
||||||
|
value={nodesetName}
|
||||||
|
onChange={(e) => setNodesetName(e.target.value)}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleNodesetUpdate}
|
||||||
|
className="px-4 py-2 bg-blue-500 text-white rounded-r-md"
|
||||||
|
>
|
||||||
|
Übernehmen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ✅ Benutzerverwaltung */}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h3 className="text-lg font-semibold mb-2">Benutzer</h3>
|
<h3 className="text-lg font-semibold mb-2">Benutzer</h3>
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-2">
|
||||||
{users.map((user) => (
|
{opcuaSettings.users.map((user) => (
|
||||||
<li
|
<li
|
||||||
key={user.id}
|
key={user.id}
|
||||||
className="p-2 bg-white shadow-sm rounded-md flex justify-between items-center"
|
className="p-2 bg-white shadow-sm rounded-md flex justify-between items-center"
|
||||||
@@ -73,36 +113,34 @@ export default function OPCUAInterfaceSettings() {
|
|||||||
(Passwort: {user.password})
|
(Passwort: {user.password})
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => removeUser(user.id)}
|
onClick={() => dispatch(removeOpcUaUser(user.id))}
|
||||||
className="ml-4 text-red-500"
|
className="text-red-500"
|
||||||
>
|
>
|
||||||
Löschen
|
Löschen
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
<div className="mt-4">
|
|
||||||
|
{/* ✅ Neuen Benutzer hinzufügen */}
|
||||||
|
<div className="mt-4 flex space-x-2">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Benutzername"
|
placeholder="Benutzername"
|
||||||
value={newUser.username}
|
value={newUsername}
|
||||||
onChange={(e) =>
|
onChange={(e) => setNewUsername(e.target.value)}
|
||||||
setNewUser({ ...newUser, username: e.target.value })
|
className="p-2 border rounded w-1/3"
|
||||||
}
|
|
||||||
className="p-2 border rounded mr-2"
|
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="Passwort"
|
placeholder="Passwort"
|
||||||
value={newUser.password}
|
value={newPassword}
|
||||||
onChange={(e) =>
|
onChange={(e) => setNewPassword(e.target.value)}
|
||||||
setNewUser({ ...newUser, password: e.target.value })
|
className="p-2 border rounded w-1/3"
|
||||||
}
|
|
||||||
className="p-2 border rounded mr-2"
|
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={addUser}
|
onClick={handleAddUser}
|
||||||
className="bg-blue-500 text-white p-2 rounded"
|
className="bg-blue-500 text-white p-2 rounded w-1/3"
|
||||||
>
|
>
|
||||||
Hinzufügen
|
Hinzufügen
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -6,5 +6,5 @@
|
|||||||
2: Patch oder Hotfix (Bugfixes oder kleine Änderungen).
|
2: Patch oder Hotfix (Bugfixes oder kleine Änderungen).
|
||||||
|
|
||||||
*/
|
*/
|
||||||
const webVersion = "1.6.91";
|
const webVersion = "1.6.92";
|
||||||
export default webVersion;
|
export default webVersion;
|
||||||
|
|||||||
76
redux/slices/opcuaSettingsSlice.ts
Normal file
76
redux/slices/opcuaSettingsSlice.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
// redux/slices/opcuaSettingsSlice.ts
|
||||||
|
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||||
|
|
||||||
|
interface OPCUAUser {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OPCUASettingsState {
|
||||||
|
isEnabled: boolean;
|
||||||
|
encryption: string;
|
||||||
|
opcUaZustand: string;
|
||||||
|
opcUaActiveClientCount: number;
|
||||||
|
opcUaNodesetName: string;
|
||||||
|
users: OPCUAUser[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: OPCUASettingsState = {
|
||||||
|
isEnabled: true,
|
||||||
|
encryption: "None",
|
||||||
|
opcUaZustand: "Offline",
|
||||||
|
opcUaActiveClientCount: 0,
|
||||||
|
opcUaNodesetName: "DefaultNodeset",
|
||||||
|
users: [
|
||||||
|
{ id: 1, username: "admin", password: "admin123" },
|
||||||
|
{ id: 2, username: "user1", password: "user123" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const opcuaSettingsSlice = createSlice({
|
||||||
|
name: "opcuaSettings",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
toggleOpcUaServer(state) {
|
||||||
|
state.isEnabled = !state.isEnabled;
|
||||||
|
},
|
||||||
|
setOpcUaEncryption(state, action: PayloadAction<string>) {
|
||||||
|
state.encryption = action.payload;
|
||||||
|
},
|
||||||
|
setOpcUaZustand(state, action: PayloadAction<string>) {
|
||||||
|
state.opcUaZustand = action.payload;
|
||||||
|
},
|
||||||
|
setOpcUaActiveClientCount(state, action: PayloadAction<number>) {
|
||||||
|
state.opcUaActiveClientCount = action.payload;
|
||||||
|
},
|
||||||
|
setOpcUaNodesetName(state, action: PayloadAction<string>) {
|
||||||
|
state.opcUaNodesetName = action.payload;
|
||||||
|
},
|
||||||
|
addOpcUaUser(
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ username: string; password: string }>
|
||||||
|
) {
|
||||||
|
const newUser = {
|
||||||
|
id: state.users.length + 1,
|
||||||
|
...action.payload,
|
||||||
|
};
|
||||||
|
state.users.push(newUser);
|
||||||
|
},
|
||||||
|
removeOpcUaUser(state, action: PayloadAction<number>) {
|
||||||
|
state.users = state.users.filter((user) => user.id !== action.payload);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const {
|
||||||
|
toggleOpcUaServer,
|
||||||
|
setOpcUaEncryption,
|
||||||
|
setOpcUaZustand,
|
||||||
|
setOpcUaActiveClientCount,
|
||||||
|
setOpcUaNodesetName,
|
||||||
|
addOpcUaUser,
|
||||||
|
removeOpcUaUser,
|
||||||
|
} = opcuaSettingsSlice.actions;
|
||||||
|
|
||||||
|
export default opcuaSettingsSlice.reducer;
|
||||||
@@ -8,6 +8,7 @@ import digitalInputsReducer from "./slices/digitalInputsSlice";
|
|||||||
import kabelueberwachungChartReducer from "./slices/kabelueberwachungChartSlice";
|
import kabelueberwachungChartReducer from "./slices/kabelueberwachungChartSlice";
|
||||||
import dashboardReducer from "./slices/dashboardSlice";
|
import dashboardReducer from "./slices/dashboardSlice";
|
||||||
import systemSettingsReducer from "./slices/systemSettingsSlice";
|
import systemSettingsReducer from "./slices/systemSettingsSlice";
|
||||||
|
import opcuaSettingsReducer from "./slices/opcuaSettingsSlice";
|
||||||
|
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
@@ -19,6 +20,7 @@ const store = configureStore({
|
|||||||
kabelueberwachungChart: kabelueberwachungChartReducer,
|
kabelueberwachungChart: kabelueberwachungChartReducer,
|
||||||
dashboard: dashboardReducer,
|
dashboard: dashboardReducer,
|
||||||
systemSettings: systemSettingsReducer,
|
systemSettings: systemSettingsReducer,
|
||||||
|
opcuaSettings: opcuaSettingsReducer,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
|
// /utils/loadWindowVariables.ts
|
||||||
import store from "../redux/store";
|
import store from "../redux/store";
|
||||||
import { setSystemSettings } from "../redux/slices/systemSettingsSlice";
|
import { setSystemSettings } from "../redux/slices/systemSettingsSlice";
|
||||||
|
import {
|
||||||
|
toggleOpcUaServer,
|
||||||
|
setOpcUaEncryption,
|
||||||
|
setOpcUaZustand,
|
||||||
|
setOpcUaActiveClientCount,
|
||||||
|
setOpcUaNodesetName,
|
||||||
|
addOpcUaUser,
|
||||||
|
removeOpcUaUser,
|
||||||
|
} from "../redux/slices/opcuaSettingsSlice";
|
||||||
|
|
||||||
// ✅ Interface für `window`-Objekt zur TypeScript-Sicherheit
|
// ✅ Interface für `window`-Objekt zur TypeScript-Sicherheit
|
||||||
interface CustomWindow extends Window {
|
interface CustomWindow extends Window {
|
||||||
@@ -21,6 +31,16 @@ interface SystemSettings {
|
|||||||
ntpActive: boolean;
|
ntpActive: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ✅ Interface für OPC-UA Einstellungen im Redux-Store
|
||||||
|
interface OpcUaSettings {
|
||||||
|
isEnabled: boolean;
|
||||||
|
encryption: string;
|
||||||
|
opcUaZustand: string;
|
||||||
|
opcUaActiveClientCount: number;
|
||||||
|
opcUaNodesetName: string;
|
||||||
|
users: { id: number; username: string; password: string }[];
|
||||||
|
}
|
||||||
|
|
||||||
// ✅ Hauptfunktion zum Laden von `window`-Variablen
|
// ✅ Hauptfunktion zum Laden von `window`-Variablen
|
||||||
export async function loadWindowVariables(): Promise<Record<string, any>> {
|
export async function loadWindowVariables(): Promise<Record<string, any>> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -73,11 +93,13 @@ export async function loadWindowVariables(): Promise<Record<string, any>> {
|
|||||||
"win_analogeEingaenge6",
|
"win_analogeEingaenge6",
|
||||||
"win_analogeEingaenge7",
|
"win_analogeEingaenge7",
|
||||||
"win_analogeEingaenge8",
|
"win_analogeEingaenge8",
|
||||||
|
"win_da_state",
|
||||||
|
"win_da_bezeichnung",
|
||||||
"win_opcUaZustand",
|
"win_opcUaZustand",
|
||||||
"win_opcUaActiveClientCount",
|
"win_opcUaActiveClientCount",
|
||||||
"win_opcUaNodesetName",
|
"win_opcUaNodesetName",
|
||||||
"win_da_state",
|
"win_opcUaEncryption", // ✅ NEU: Verschlüsselung von OPC-UA
|
||||||
"win_da_bezeichnung",
|
"win_opcUaUsers", // ✅ NEU: OPC-UA Benutzerliste
|
||||||
];
|
];
|
||||||
|
|
||||||
const scripts: string[] = [
|
const scripts: string[] = [
|
||||||
@@ -127,6 +149,7 @@ export async function loadWindowVariables(): Promise<Record<string, any>> {
|
|||||||
|
|
||||||
// ✅ Redux mit Systemvariablen aktualisieren
|
// ✅ Redux mit Systemvariablen aktualisieren
|
||||||
loadAndStoreSystemSettings(win);
|
loadAndStoreSystemSettings(win);
|
||||||
|
loadAndStoreOpcUaSettings(win);
|
||||||
|
|
||||||
resolve(variablesObj);
|
resolve(variablesObj);
|
||||||
})
|
})
|
||||||
@@ -155,3 +178,59 @@ const loadAndStoreSystemSettings = (win: CustomWindow) => {
|
|||||||
|
|
||||||
store.dispatch(setSystemSettings(settings));
|
store.dispatch(setSystemSettings(settings));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ✅ Funktion zum Speichern von OPC-UA Variablen in Redux
|
||||||
|
const loadAndStoreOpcUaSettings = (win: CustomWindow) => {
|
||||||
|
// ✅ Aktuellen Redux-Status holen
|
||||||
|
const currentState = store.getState().opcuaSettings;
|
||||||
|
|
||||||
|
// ✅ Nur setzen, wenn sich der Zustand geändert hat
|
||||||
|
if (currentState.opcUaZustand !== win.win_opcUaZustand) {
|
||||||
|
store.dispatch(setOpcUaZustand(win.win_opcUaZustand || "Offline"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Nur setzen, wenn sich die Verschlüsselung geändert hat
|
||||||
|
if (currentState.encryption !== win.win_opcUaEncryption) {
|
||||||
|
store.dispatch(setOpcUaEncryption(win.win_opcUaEncryption || "None"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Nur setzen, wenn sich die Anzahl der aktiven Clients geändert hat
|
||||||
|
if (currentState.opcUaActiveClientCount !== win.win_opcUaActiveClientCount) {
|
||||||
|
store.dispatch(
|
||||||
|
setOpcUaActiveClientCount(win.win_opcUaActiveClientCount || 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Nur setzen, wenn sich der Nodeset-Name geändert hat
|
||||||
|
if (currentState.opcUaNodesetName !== win.win_opcUaNodesetName) {
|
||||||
|
store.dispatch(
|
||||||
|
setOpcUaNodesetName(win.win_opcUaNodesetName || "DefaultNodeset")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Benutzer synchronisieren (aber nicht überschreiben, wenn manuell hinzugefügt)
|
||||||
|
if (Array.isArray(win.win_opcUaUsers) && win.win_opcUaUsers.length > 0) {
|
||||||
|
const newUsers = win.win_opcUaUsers;
|
||||||
|
const currentUsers = currentState.users;
|
||||||
|
|
||||||
|
// ✅ Vorhandene Benutzer entfernen, die nicht mehr in `window` sind
|
||||||
|
currentUsers.forEach((user) => {
|
||||||
|
if (!newUsers.some((newUser) => newUser.username === user.username)) {
|
||||||
|
store.dispatch(removeOpcUaUser(user.id));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ✅ Nur neue Benutzer hinzufügen, falls sie nicht existieren
|
||||||
|
newUsers.forEach((user) => {
|
||||||
|
if (
|
||||||
|
!currentUsers.some(
|
||||||
|
(existingUser) => existingUser.username === user.username
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
store.dispatch(
|
||||||
|
addOpcUaUser({ username: user.username, password: user.password })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user