661 lines
17 KiB
JavaScript
661 lines
17 KiB
JavaScript
import { UserOutlined } from "@ant-design/icons";
|
|
import { Avatar, Badge, Tooltip } from "antd";
|
|
import { createContext, useEffect, useRef, useState } from "react";
|
|
import { useNavigate } from "react-router-dom";
|
|
import { Buffer } from "buffer";
|
|
|
|
/**
|
|
* constants
|
|
*/
|
|
export const Constants = {
|
|
COLORS: {
|
|
PRIMARY: "#e67e22",
|
|
SECONDARY: "#9b59b6",
|
|
ICON_INFO: "#08c",
|
|
},
|
|
TEXT_EMPTY_PLACEHOLDER: "-/-",
|
|
API_ADDRESS: "http://localhost:8080/v1",
|
|
STATIC_CONTENT_ADDRESS: "http://localhost:8080/",
|
|
WS_ADDRESS: "ws://localhost:8080/ws",
|
|
ROUTE_PATHS: {
|
|
GROUP_TASKS: "/group-tasks",
|
|
GROUP_TASKS_VIEW: "/group-tasks/",
|
|
},
|
|
GROUP_TASKS_STATUS: {
|
|
FINISHED: 1,
|
|
RUNNING: 2,
|
|
CANCELED: 3,
|
|
FAILED: 4,
|
|
INPUT_REQUIRED: 5,
|
|
},
|
|
GLOBALS: {
|
|
MIN_USERNAME_LENGTH: 2,
|
|
MAX_USERNAME_LENGTH: 20,
|
|
MIN_PASSWORD_LENGTH: 6,
|
|
MAX_PASSWORD_LENGTH: 64,
|
|
},
|
|
MAX_AVATAR_SIZE: 5 * 1024 * 1024,
|
|
ACCEPTED_FILE_TYPES: ["image/png", "image/jpeg", "image/jpg"],
|
|
GROUP_TASK_LOCKED_TIME: 3 * 1000,
|
|
};
|
|
|
|
/*
|
|
This ID is needed because the message is sent to all clients connected to the backend server when a task is locked and unlocked.
|
|
With this ID the client checks if the respective browser tab of the user is the initiator of the lock.
|
|
*/
|
|
export const GroupTasksStepsLockedAndUserUpdateInputValueRememberId =
|
|
window.crypto.randomUUID();
|
|
|
|
/**
|
|
* user session
|
|
*/
|
|
export function UseUserSession() {
|
|
const getUserSession = () => {
|
|
return getUserSessionFromLocalStorage();
|
|
};
|
|
|
|
const [userSession, setUserSession] = useState(getUserSession());
|
|
|
|
const saveUserSession = (session) => {
|
|
setUserSession(session);
|
|
|
|
if (session === undefined) {
|
|
localStorage.removeItem("session");
|
|
} else {
|
|
localStorage.setItem("session", JSON.stringify(session));
|
|
}
|
|
};
|
|
|
|
return {
|
|
setUserSession: saveUserSession,
|
|
userSession,
|
|
};
|
|
}
|
|
|
|
export function getUserSessionFromLocalStorage() {
|
|
return JSON.parse(localStorage.getItem("session"));
|
|
}
|
|
|
|
/**
|
|
* websocket
|
|
*/
|
|
let l = "loading...";
|
|
|
|
let webSocketContextPreview = {
|
|
User: {
|
|
Id: "",
|
|
Username: l,
|
|
Email: l,
|
|
Sessions: [],
|
|
},
|
|
AllUsers: [],
|
|
ConnectionBadgeStatus: "error",
|
|
ConnectedWebSocketUsersCount: 0,
|
|
CategoryGroups: [],
|
|
GroupTasks: [],
|
|
GroupTasksSteps: [],
|
|
Scanners: [],
|
|
};
|
|
|
|
export const WebSocketContext = createContext(webSocketContextPreview);
|
|
|
|
// commands received from the backend server
|
|
const ReceivedMessagesCommands = {
|
|
InitUserSocketConnection: 1,
|
|
UpdateConnectedUsers: 2,
|
|
NewGroupTaskStarted: 3,
|
|
NewGroupTaskStep: 4,
|
|
UpdateGroupTaskStep: 5,
|
|
UpdateGroupTask: 6,
|
|
ReloadingGroupTasks: 7,
|
|
GroupTasksReloaded: 8,
|
|
UpdateUserSessions: 9,
|
|
UpdateAllUsersUserAvatar: 10,
|
|
NewScanner: 11,
|
|
DeleteScanner: 12,
|
|
UpdateScannerUsedByUserId: 13,
|
|
ScanResult: 14,
|
|
UpdateScannerLastUsed: 15,
|
|
TaskLocked: 16,
|
|
TaskUnlocked: 17,
|
|
};
|
|
|
|
// commands sent to the backend server
|
|
export const SentMessagesCommands = {
|
|
StartGroupTasks: 1,
|
|
TaskFailedTryAgainRunTaskStep: 2,
|
|
TaskContinueTaskStep: 3,
|
|
ReloadGroupTasks: 4,
|
|
TaskLocking: 5,
|
|
};
|
|
|
|
export function WebSocketProvider({
|
|
children,
|
|
userSession,
|
|
setUserSession,
|
|
notificationApi,
|
|
}) {
|
|
const [isReady, setIsReady] = useState(false);
|
|
const [connectionBadgeStatus, setConnectionBadgeStatus] = useState(
|
|
webSocketContextPreview.ConnectionBadgeStatus
|
|
);
|
|
const [connectedWebSocketUsersCount, setConnectedWebSocketUsersCount] =
|
|
useState(0);
|
|
const [user, setUser] = useState(webSocketContextPreview.User);
|
|
const [allUsers, setAllUsers] = useState(webSocketContextPreview.AllUsers);
|
|
const [categoryGroups, setCategoryGroups] = useState(
|
|
webSocketContextPreview.CategoryGroups
|
|
);
|
|
// these are all group tasks that are then filtered and displayed in the respective tables for the category
|
|
const [groupTasks, setGroupTasks] = useState([]);
|
|
const [groupTasksSteps, setGroupTasksSteps] = useState([]);
|
|
const [scanners, setScanners] = useState([]);
|
|
const navigate = useNavigate();
|
|
const StartGroupTasksOpenModalRememberIdRef = useRef(null);
|
|
|
|
const ws = useRef(null);
|
|
|
|
const connect = () => {
|
|
ws.current = new WebSocket(Constants.WS_ADDRESS + "?auth=" + userSession);
|
|
|
|
ws.current.onopen = () => {
|
|
console.log("connected");
|
|
setConnectionBadgeStatus("success");
|
|
setIsReady(true);
|
|
};
|
|
|
|
ws.current.onmessage = (event) => {
|
|
const data = JSON.parse(event.data);
|
|
console.log("received message", data);
|
|
|
|
const cmd = data.Cmd;
|
|
const body = data.Body;
|
|
|
|
switch (cmd) {
|
|
case ReceivedMessagesCommands.InitUserSocketConnection:
|
|
setUser(body.User);
|
|
setAllUsers(body.AllUsers);
|
|
setCategoryGroups(body.CategoryGroups);
|
|
setGroupTasks(body.GroupTasks);
|
|
setGroupTasksSteps(body.GroupTasksSteps);
|
|
if (body.Scanners !== null) setScanners(body.Scanners);
|
|
|
|
localStorage.setItem("userId", body.User.Id);
|
|
break;
|
|
case ReceivedMessagesCommands.UpdateConnectedUsers:
|
|
setConnectedWebSocketUsersCount(body.WebSocketUsersCount);
|
|
setAllUsers((arr) => {
|
|
const newArr = [...arr];
|
|
|
|
newArr[
|
|
arr.findIndex((arr1) => arr1.Id === body.UserId)
|
|
].ConnectionStatus = body.ConnectionStatus;
|
|
|
|
return newArr;
|
|
});
|
|
break;
|
|
case ReceivedMessagesCommands.NewGroupTaskStarted:
|
|
setGroupTasks((arr) => [...arr, body]);
|
|
|
|
if (
|
|
body.RememberId === StartGroupTasksOpenModalRememberIdRef.current
|
|
) {
|
|
navigate(`${Constants.ROUTE_PATHS.GROUP_TASKS}/${body.Id}`);
|
|
}
|
|
break;
|
|
case ReceivedMessagesCommands.NewGroupTaskStep:
|
|
setGroupTasksSteps((arr) => [...arr, body]);
|
|
break;
|
|
case ReceivedMessagesCommands.UpdateGroupTaskStep:
|
|
setGroupTasksSteps((arr) => {
|
|
const newArr = [...arr];
|
|
|
|
newArr[arr.findIndex((arr1) => arr1.Id === body.Id)] = body;
|
|
|
|
return newArr;
|
|
});
|
|
break;
|
|
case ReceivedMessagesCommands.UpdateGroupTask:
|
|
setGroupTasks((arr) => {
|
|
const newArr = [...arr];
|
|
|
|
newArr[arr.findIndex((arr1) => arr1.Id === body.Id)] = body;
|
|
|
|
return newArr;
|
|
});
|
|
break;
|
|
case ReceivedMessagesCommands.ReloadingGroupTasks:
|
|
notificationApi["warning"]({
|
|
message: `Group ${body} is reloading`,
|
|
duration: 2,
|
|
});
|
|
break;
|
|
case ReceivedMessagesCommands.GroupTasksReloaded:
|
|
setCategoryGroups((arr) => {
|
|
const newArr = [...arr];
|
|
|
|
newArr[
|
|
arr.findIndex((arr1) => arr1.category === body.Category)
|
|
].groups = body.UpdatedGroups;
|
|
|
|
return newArr;
|
|
});
|
|
|
|
notificationApi["info"]({
|
|
message: `Group ${body.Category} reloaded`,
|
|
duration: 2,
|
|
});
|
|
break;
|
|
case ReceivedMessagesCommands.UpdateUserSessions:
|
|
setUser((arr) => ({ ...arr, Sessions: body }));
|
|
break;
|
|
case ReceivedMessagesCommands.UpdateAllUsersUserAvatar:
|
|
setAllUsers((arr) => {
|
|
const newArr = [...arr];
|
|
|
|
newArr[arr.findIndex((arr1) => arr1.Id === body.UserId)].Avatar =
|
|
body.Avatar;
|
|
|
|
return newArr;
|
|
});
|
|
break;
|
|
case ReceivedMessagesCommands.NewScanner:
|
|
setScanners((arr) => [...arr, body]);
|
|
break;
|
|
case ReceivedMessagesCommands.DeleteScanner:
|
|
setScanners((arr) => arr.filter((arr) => arr.Id !== body.Id));
|
|
break;
|
|
case ReceivedMessagesCommands.UpdateScannerUsedByUserId:
|
|
setScanners((arr) => {
|
|
const newArr = [...arr];
|
|
|
|
newArr[
|
|
arr.findIndex((arr1) => arr1.Id === body.ScannerId)
|
|
].UsedByUserId = body.UsedByUserId;
|
|
|
|
return newArr;
|
|
});
|
|
break;
|
|
case ReceivedMessagesCommands.ScanResult:
|
|
const decodedScanResult = Buffer.from(body, "base64").toString();
|
|
|
|
if (decodedScanResult === "" || decodedScanResult === null) {
|
|
notificationApi["error"]({
|
|
message: `Failed to decode scan result`,
|
|
description: "See in developer console",
|
|
});
|
|
|
|
console.error(
|
|
"Received scan result: ",
|
|
body,
|
|
"Decoded result: ",
|
|
decodedScanResult
|
|
);
|
|
break;
|
|
}
|
|
|
|
notificationApi["info"]({
|
|
message: `Scan Result`,
|
|
description: Buffer.from(body, "base64").toString(),
|
|
});
|
|
|
|
new Audio(
|
|
`${Constants.STATIC_CONTENT_ADDRESS}sounds/scan_result.mp3`
|
|
).play();
|
|
|
|
break;
|
|
case ReceivedMessagesCommands.UpdateScannerLastUsed:
|
|
setScanners((arr) => {
|
|
const newArr = [...arr];
|
|
|
|
newArr[
|
|
arr.findIndex((arr1) => arr1.Id === body.ScannerId)
|
|
].LastUsed = body.LastUsed;
|
|
|
|
return newArr;
|
|
});
|
|
break;
|
|
case ReceivedMessagesCommands.TaskLocked:
|
|
if (
|
|
body.rememberId ===
|
|
GroupTasksStepsLockedAndUserUpdateInputValueRememberId
|
|
)
|
|
break;
|
|
|
|
console.log("body task locked", body);
|
|
|
|
setGroupTasksSteps((arr) => {
|
|
const newArr = [...arr];
|
|
|
|
newArr[
|
|
arr.findIndex(
|
|
(arr1) =>
|
|
arr1.GroupTasksId === body.groupTaskId &&
|
|
arr1.Step === body.step
|
|
)
|
|
].LockedByUserId = body.lockedByUserId;
|
|
|
|
return newArr;
|
|
});
|
|
|
|
// update input value
|
|
// html based DOM manipulation
|
|
const foundInput = document.getElementById(body.element);
|
|
|
|
console.log("here", foundInput);
|
|
|
|
if (foundInput) {
|
|
// this timeout is needed because the previous useState for the lockedByUserId takes some milliseconds to complete
|
|
setTimeout(() => setNativeValue(foundInput, body.value), 50);
|
|
}
|
|
|
|
// update group task step as html based DOM manipulation only works if user has no other modal open
|
|
setGroupTasksSteps((arr) => {
|
|
const newArr = [...arr];
|
|
|
|
const stepIndex = arr.findIndex(
|
|
(arr1) =>
|
|
arr1.GroupTasksId === body.groupTaskId &&
|
|
arr1.Step === body.step
|
|
);
|
|
|
|
if (stepIndex === -1) return newArr;
|
|
|
|
let inputs = [];
|
|
|
|
if (newArr[stepIndex].Inputs !== "") {
|
|
inputs = JSON.parse(newArr[stepIndex].Inputs);
|
|
}
|
|
|
|
let parameterFound = false;
|
|
|
|
for (let i = 0; i < inputs.length; i++) {
|
|
if (inputs[i].parameterName === body.parameterName) {
|
|
inputs[i].value = body.value;
|
|
parameterFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!parameterFound) {
|
|
let obj = {};
|
|
|
|
obj["parameterName"] = body.parameterName;
|
|
obj["value"] = body.value;
|
|
|
|
inputs.push(obj);
|
|
}
|
|
|
|
newArr[stepIndex].Inputs = JSON.stringify(inputs);
|
|
|
|
return newArr;
|
|
});
|
|
|
|
break;
|
|
case ReceivedMessagesCommands.TaskUnlocked:
|
|
if (
|
|
body.rememberId ===
|
|
GroupTasksStepsLockedAndUserUpdateInputValueRememberId
|
|
)
|
|
break;
|
|
|
|
setGroupTasksSteps((arr) => {
|
|
const newArr = [...arr];
|
|
|
|
newArr[
|
|
arr.findIndex(
|
|
(arr1) =>
|
|
arr1.GroupTasksId === body.GroupTaskId &&
|
|
arr1.Step === body.Step
|
|
)
|
|
].LockedByUserId = "";
|
|
|
|
return newArr;
|
|
});
|
|
break;
|
|
/*case ReceivedMessagesCommands.UpdateGroupTaskStepUserInputValue:
|
|
if (
|
|
body.rememberId ===
|
|
GroupTasksStepsLockedAndUserUpdateInputValueRememberId
|
|
)
|
|
break;
|
|
|
|
// html based DOM manipulation
|
|
const foundInput = document.getElementById(body.element);
|
|
|
|
if (foundInput) {
|
|
setNativeValue(foundInput, body.value);
|
|
}
|
|
|
|
// update group task step as html based DOM manipulation only works if user has no other modal open
|
|
setGroupTasksSteps((arr) => {
|
|
const newArr = [...arr];
|
|
|
|
const stepIndex = arr.findIndex(
|
|
(arr1) =>
|
|
arr1.GroupTasksId === body.groupTaskId &&
|
|
arr1.Step === body.step
|
|
);
|
|
|
|
if (stepIndex === -1) return newArr;
|
|
|
|
let inputs = [];
|
|
|
|
if (newArr[stepIndex].Inputs !== "") {
|
|
inputs = JSON.parse(newArr[stepIndex].Inputs);
|
|
}
|
|
|
|
let parameterFound = false;
|
|
|
|
for (let i = 0; i < inputs.length; i++) {
|
|
if (inputs[i].parameterName === body.parameterName) {
|
|
inputs[i].value = body.value;
|
|
parameterFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!parameterFound) {
|
|
let obj = {};
|
|
|
|
obj["parameterName"] = body.parameterName;
|
|
obj["value"] = body.value;
|
|
|
|
inputs.push(obj);
|
|
}
|
|
|
|
newArr[stepIndex].Inputs = JSON.stringify(inputs);
|
|
|
|
return newArr;
|
|
});
|
|
break;*/
|
|
|
|
default:
|
|
console.error("unknown command", cmd);
|
|
break;
|
|
}
|
|
};
|
|
|
|
ws.current.onclose = (event) => {
|
|
setIsReady(false);
|
|
setConnectionBadgeStatus("error");
|
|
console.log("closed", event);
|
|
|
|
// custom code defined by the backend server
|
|
if (event.code === 4001 || event.code === 4002) {
|
|
//Unauthorized || SessionClosed
|
|
|
|
setUserSession();
|
|
window.location.href = "/";
|
|
return;
|
|
}
|
|
|
|
if (event.reason.code === 1005) return;
|
|
|
|
console.log("reconnecting...");
|
|
|
|
setTimeout(() => connect(), 1000);
|
|
};
|
|
};
|
|
|
|
useEffect(() => {
|
|
connect();
|
|
|
|
//return () => socket.close();
|
|
return () => ws.current.close();
|
|
}, []);
|
|
|
|
const SendSocketMessage = (cmd, body) => {
|
|
if (isReady) {
|
|
ws.current.send(JSON.stringify({ Cmd: cmd, Body: body }));
|
|
} else {
|
|
console.log("websocket not ready");
|
|
}
|
|
};
|
|
|
|
return (
|
|
<WebSocketContext.Provider
|
|
value={{
|
|
ConnectionBadgeStatus: connectionBadgeStatus,
|
|
ConnectedWebSocketUsersCount: connectedWebSocketUsersCount,
|
|
CategoryGroups: categoryGroups,
|
|
GroupTasks: groupTasks,
|
|
User: user,
|
|
AllUsers: allUsers,
|
|
Scanners: scanners,
|
|
SendSocketMessage: SendSocketMessage,
|
|
GroupTasksSteps: groupTasksSteps,
|
|
setGroupTasksSteps: setGroupTasksSteps,
|
|
StartGroupTasksOpenModalRememberIdRef:
|
|
StartGroupTasksOpenModalRememberIdRef,
|
|
}}
|
|
>
|
|
{children}
|
|
</WebSocketContext.Provider>
|
|
);
|
|
}
|
|
|
|
// https://stackoverflow.com/a/52486921
|
|
function setNativeValue(element, value) {
|
|
let lastValue = element.value;
|
|
element.value = value;
|
|
let event = new Event("input", { target: element, bubbles: true });
|
|
// React 15
|
|
event.simulated = true;
|
|
// React 16
|
|
let tracker = element._valueTracker;
|
|
if (tracker) {
|
|
tracker.setValue(lastValue);
|
|
}
|
|
element.dispatchEvent(event);
|
|
}
|
|
|
|
export function FormatDatetime(datetime) {
|
|
if (datetime === "0001-01-01T00:00:00Z") {
|
|
return Constants.TEXT_EMPTY_PLACEHOLDER;
|
|
}
|
|
|
|
return new Date(datetime).toLocaleString("de-DE");
|
|
}
|
|
|
|
export function GetDuration(startTime, endTime) {
|
|
if (endTime === "0001-01-01T00:00:00Z") {
|
|
return Constants.TEXT_EMPTY_PLACEHOLDER;
|
|
}
|
|
|
|
const diff = Math.abs(new Date(startTime) - new Date(endTime));
|
|
|
|
if (diff === 0) {
|
|
return "0ms";
|
|
}
|
|
|
|
const milliseconds = diff % 1000;
|
|
const seconds = Math.floor(diff / 1000);
|
|
const minutes = Math.floor(seconds / 60);
|
|
const hours = Math.floor(minutes / 60);
|
|
const days = Math.floor(hours / 24);
|
|
|
|
let result = "";
|
|
|
|
if (days > 0) {
|
|
result += days + "d ";
|
|
}
|
|
|
|
if (hours > 0) {
|
|
result += (hours % 24) + "h ";
|
|
}
|
|
|
|
if (minutes > 0) {
|
|
result += (minutes % 60) + "m ";
|
|
}
|
|
|
|
if (seconds > 0) {
|
|
result += (seconds % 60) + "s ";
|
|
}
|
|
|
|
if (milliseconds > 0) {
|
|
result += milliseconds + "ms";
|
|
}
|
|
|
|
return result.trim();
|
|
}
|
|
|
|
export function getConnectionStatusItem(connectionStatus) {
|
|
return connectionStatus === 0 ? (
|
|
<Badge status="error" text="Offline" />
|
|
) : (
|
|
<Badge status="success" text="Online" />
|
|
);
|
|
}
|
|
|
|
export function MyAvatar({
|
|
avatarWidth,
|
|
avatar,
|
|
tooltip,
|
|
tooltipTitle,
|
|
allUsers,
|
|
userId,
|
|
}) {
|
|
const MyDefaultAvatar = () => {
|
|
return <Avatar size={avatarWidth} icon={<UserOutlined />} />;
|
|
};
|
|
|
|
if (avatar === undefined || avatar === null) {
|
|
if (allUsers !== undefined && userId !== undefined) {
|
|
const user = allUsers.find((u) => u.Id === userId);
|
|
|
|
if (user === undefined) return <MyDefaultAvatar />;
|
|
|
|
avatar = user.Avatar;
|
|
tooltipTitle = user.Username;
|
|
} else {
|
|
return <MyDefaultAvatar />;
|
|
}
|
|
}
|
|
|
|
const MyAvat = () => {
|
|
return (
|
|
<Avatar
|
|
size={avatarWidth}
|
|
src={Constants.STATIC_CONTENT_ADDRESS + "avatars/" + avatar}
|
|
/>
|
|
);
|
|
};
|
|
|
|
if (tooltip) {
|
|
return (
|
|
<Tooltip placement="top" trigger="hover" title={tooltipTitle}>
|
|
<>
|
|
<MyAvat />
|
|
</>
|
|
</Tooltip>
|
|
);
|
|
}
|
|
|
|
return <MyAvat />;
|
|
}
|
|
|
|
export function getUserId() {
|
|
return localStorage.getItem("userId");
|
|
}
|