admin-dashboard-web/src/utils.js

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");
}