import { Badge, Button, Input, Popconfirm, Popover, Space, Table, Tooltip, Typography, notification, } from "antd"; import { useTranslation } from "react-i18next"; import { useEffect, useRef, useState } from "react"; import { Constants, FormatDatetime, hasOnePermission, hasPermission, myFetch, myFetchContentType, } from "../../../utils"; import MyPagination from "../../../Components/MyPagination"; import { Link } from "react-router-dom"; import { FileTextOutlined, LoadingOutlined, PlusOutlined, } from "@ant-design/icons"; import { MyCopyIcon, MyShowHiddenIcon } from "../../../Components/MyIcon"; import { useAppContext } from "../../../Contexts/AppContext"; const ReceivedSSECommands = { UpdateRobotStatus: 1, AddUnauthorizedRobot: 2, AddRobot: 3, RemoveUnauthorizedRobot: 4, RemoveRobot: 5, RobotUpdated: 6, UpdateRobotCurrentJob: 7, UpdateRobotJobsWaitingCount: 8, PermitJoinUpdated: 9, }; function getRobotTypeString(type) { switch (type) { case 1: return "Rex"; case 2: return "Yeet"; default: return "Unknown"; } } export default function Robots() { const { t } = useTranslation(); const appContext = useAppContext(); const [notificationApi, notificationContextHolder] = notification.useNotification(); const [robots, setRobots] = useState([]); const [robotsTotalPages, setRobotsTotalPages] = useState(0); const [robotsPaginationPage, setRobotsPaginationPage] = useState(1); const robotsPaginationPageRef = useRef(1); const [unauthorizedRobots, setUnauthorizedRobots] = useState([]); const [unauthorizedRobotsTotalPages, setUnauthorizedRobotsTotalPages] = useState(0); const [ unauthorizedRobotsPaginationPage, setUnauthorizedRobotsPaginationPage, ] = useState(1); const unauthorizedRobotsPaginationPageRef = useRef(1); const [selectedRobotName, setSelectedRobotName] = useState(""); const [permitJoinEnabled, setPermitJoinEnabled] = useState(false); const [showAddress, setShowAddress] = useState(false); const sseEventSource = useRef(null); const getRobotStatusBadge = (status) => { switch (status) { case 1: return ( ); case 2: return ( ); case 3: return ( ); case 4: return ( ); case 5: return ( ); default: return "Unknown"; } }; const getRobotsTableContent = () => { let items = [ { title: t("robotics.robots.column.id"), dataIndex: "id", key: "id", }, { title: t("robotics.robots.column.type"), dataIndex: "type", key: "type", }, { title: t("robotics.robots.column.name"), dataIndex: "name", key: "name", }, { title: t("robotics.robots.column.status"), dataIndex: "status", key: "status", }, { title: t("robotics.robots.column.currentJob"), dataIndex: "currentJobName", key: "currentJobName", }, { title: t("robotics.robots.column.jobsWaiting"), dataIndex: "jobsWaitingCount", key: "jobsWaitingCount", render: (parameter, record) => ( <> {parameter > 0 ? ( {record._jobsWaitingNameList.map((jobName, index) => (
  • {jobName}
  • ))} ) } > {parameter}
    ) : ( parameter )} ), }, ]; if ( hasPermission( appContext.userPermissions, Constants.PERMISSIONS.ROBOTICS.ROBOTS.VIEW_ROBOTS_ADDRESSES ) ) { items.push({ title: t("robotics.robots.column.address"), dataIndex: "address", key: "address", render: (text) => ( {showAddress ? text : "XXXXXX"} ), }); } items.push( { title: t("robotics.robots.column.connectedAt"), dataIndex: "connectedAt", key: "connectedAt", }, { title: t("robotics.robots.column.firmwareVersion"), dataIndex: "firmwareVersion", key: "firmwareVersion", }, { title: t("robotics.robots.column.createdAt"), dataIndex: "createdAt", key: "createdAt", } ); if ( hasOnePermission( appContext.userPermissions, Constants.PERMISSIONS.ROBOTICS.ROBOTS.EDIT_ROBOT_NAME, Constants.PERMISSIONS.ROBOTICS.ROBOTS.DISCONNECT_ROBOT ) ) { items.push({ title: t("robotics.robots.column.actions"), dataIndex: "actions", key: "actions", render: (_, record) => ( {hasPermission( appContext.userPermissions, Constants.PERMISSIONS.ROBOTICS.ROBOTS.EDIT_ROBOT_NAME ) && ( setSelectedRobotName(e.target.value)} maxLength={Constants.GLOBALS.MAX_ROBOTICS_ROBOT_NAME_LENGTH} /> } okButtonProps={{ disabled: selectedRobotName.length < Constants.GLOBALS.MIN_ROBOTICS_ROBOT_NAME_LENGTH || selectedRobotName.length > Constants.GLOBALS.MAX_ROBOTICS_ROBOT_NAME_LENGTH, }} okText={t("common.button.confirm")} cancelText={t("common.button.cancel")} onConfirm={() => myFetch( `/robot`, "PATCH", { robotId: record.id, robotName: selectedRobotName, }, {}, myFetchContentType.JSON, Constants.ROBOTICS_API_ADDRESS ).catch((err) => { if (err === 422) { notificationApi["error"]({ message: t( "robotics.robots.popconfirmEdit.errorNotification.message" ), description: t( "robotics.robots.popconfirmEdit.errorNotification.description" ), }); } }) } > setSelectedRobotName(record.name)}> {t("common.text.edit")} )} {hasPermission( appContext.userPermissions, Constants.PERMISSIONS.ROBOTICS.ROBOTS.DISCONNECT_ROBOT ) && ( myFetch( `/robot/${record.id}`, "DELETE", null, {}, myFetchContentType.JSON, Constants.ROBOTICS_API_ADDRESS ) } > {t("common.text.disconnect")} )} ), }); } return items; }; const getRobotsTableItems = (robots) => { let items = []; robots.sort((a, b) => a.Status - b.Status); robots.forEach((robot) => items.push({ key: robot.Id, id: robot.Id, type: getRobotTypeString(robot.Type), name: robot.Name, status: getRobotStatusBadge(robot.Status), currentJobName: robot.CurrentJobName === "" ? Constants.TEXT_EMPTY_PLACEHOLDER : robot.CurrentJobName, jobsWaitingCount: robot.JobsWaitingCount, _jobsWaitingNameList: robot.JobsWaitingNameList, address: robot.Address, firmwareVersion: robot.FirmwareVersion, connectedAt: FormatDatetime(robot.ConnectedAt), createdAt: FormatDatetime(robot.CreatedAt), actions: robot.Actions, }) ); return items; }; const getUnauthorizedTableContent = () => { let items = [ { title: t("robotics.robots.column.id"), dataIndex: "id", key: "id", }, { title: t("robotics.robots.column.type"), dataIndex: "type", key: "type", }, { title: t("robotics.robots.column.address"), dataIndex: "address", key: "address", }, { title: t("robotics.robots.column.connectedAt"), dataIndex: "connectedAt", key: "connectedAt", }, { title: t("robotics.robots.column.firmwareVersion"), dataIndex: "firmwareVersion", key: "firmwareVersion", }, ]; if ( hasPermission( appContext.userPermissions, Constants.PERMISSIONS.ROBOTICS.ROBOTS.AUTHORIZE_DENY_UNAUTHORIZED_ROBOTS ) ) { items.push({ title: t("robotics.robots.column.actions"), dataIndex: "actions", key: "actions", render: (_, record) => ( myFetch( `/robot/deny/${record.id}`, "DELETE", null, {}, myFetchContentType.JSON, Constants.ROBOTICS_API_ADDRESS ) } > {t("common.text.deny")} myFetch( `/robot/authorize/${record.id}`, "POST", null, {}, myFetchContentType.JSON, Constants.ROBOTICS_API_ADDRESS ) } > {t("common.text.authorize")} ), }); } return items; }; const getUnauthorizedTableItems = (unauthorizedRobots) => { let items = []; unauthorizedRobots.forEach((robot) => { items.push({ key: robot.Id, id: robot.Id, type: getRobotTypeString(robot.Type), address: robot.Address, connectedAt: FormatDatetime(robot.ConnectedAt), firmwareVersion: robot.FirmwareVersion, actions: robot.Actions, }); }); return items; }; // type = 0 => robots, type = 1 => unauthorizedRobots const fetchRobots = (type, page = 1) => { myFetch( `/${type === 1 ? "u" : ""}robots?page=${page}`, "GET", null, {}, myFetchContentType.JSON, Constants.ROBOTICS_API_ADDRESS ).then((data) => { if (type === 1) { setUnauthorizedRobots( data.UnauthorizedRobots === null ? [] : data.UnauthorizedRobots ); setUnauthorizedRobotsTotalPages(data.TotalPages); } else { setRobots(data.Robots === null ? [] : data.Robots); setRobotsTotalPages(data.TotalPages); } }); }; useEffect(() => fetchRobots(0, robotsPaginationPage), [robotsPaginationPage]); useEffect( () => fetchRobots(1, unauthorizedRobotsPaginationPage), [unauthorizedRobotsPaginationPage] ); useEffect(() => { myFetch( "/permitjoin", "GET", null, {}, myFetchContentType.JSON, Constants.ROBOTICS_API_ADDRESS ).then((data) => setPermitJoinEnabled(data.Enabled)); sseEventSource.current = new EventSource( `${Constants.ROBOTICS_API_ADDRESS}/sse` ); sseEventSource.current.onmessage = (event) => { const data = JSON.parse(event.data); const cmd = data.Cmd; const body = data.Body; console.log("sse message", data); switch (cmd) { case ReceivedSSECommands.UpdateRobotStatus: setRobots((arr) => { const newArr = [...arr]; const index = arr.findIndex((x) => x.Id === body.RobotId); if (index !== -1) { newArr[index].Status = body.Status; } return newArr; }); break; case ReceivedSSECommands.AddUnauthorizedRobot: console.log("a", unauthorizedRobotsPaginationPageRef.current); if (unauthorizedRobotsPaginationPageRef.current === 1) { setUnauthorizedRobots((arr) => { const newArr = [...arr]; const index = arr.findIndex( (x) => x.Id === body.UnauthorizedRobot.Id ); if (index !== -1) { newArr[index] = body.UnauthorizedRobot; } else { if ( newArr.length === Constants.GLOBALS.ROBOTICS_UNAUTHORIZED_PAGINATION_LIMIT ) { newArr.pop(); } newArr.unshift(body.UnauthorizedRobot); } return newArr; }); } setUnauthorizedRobotsTotalPages(body.TotalPages); break; case ReceivedSSECommands.AddRobot: if (robotsPaginationPageRef.current === 1) { setRobots((arr) => { const newArr = [...arr]; const index = arr.findIndex((x) => x.Id === body.Robot.Id); if (index !== -1) { newArr[index] = body.Robot; } else { if ( newArr.length === Constants.GLOBALS.ROBOTICS_ROBOTS_PAGINATION_LIMIT ) { newArr.pop(); } newArr.unshift(body.Robot); } return newArr; }); } setRobotsTotalPages(body.TotalPages); // remove from unauthorized robots setUnauthorizedRobots((arr) => { const newArr = [...arr]; const index = arr.findIndex((x) => x.Id === body.Robot.Id); if (index !== -1) { newArr.splice(index, 1); } return newArr; }); setUnauthorizedRobotsTotalPages(body.UnauthorizedRobotsTotalPages); // if user is on the last page and the last item is removed, we need to go back one page if ( body.UnauthorizedRobotsTotalPages > 0 && unauthorizedRobotsPaginationPageRef.current > body.UnauthorizedRobotsTotalPages ) { unauthorizedRobotsPaginationPageRef.current--; setUnauthorizedRobotsPaginationPage( unauthorizedRobotsPaginationPageRef.current ); } break; case ReceivedSSECommands.RemoveUnauthorizedRobot: setUnauthorizedRobots((arr) => { const newArr = [...arr]; const index = arr.findIndex( (x) => x.Id === body.UnauthorizedRobotId ); if (index !== -1) { newArr.splice(index, 1); } return newArr; }); setUnauthorizedRobotsTotalPages(body.TotalPages); // if user is on the last page and the last item is removed, we need to go back one page if ( body.TotalPages > 0 && unauthorizedRobotsPaginationPageRef.current > body.TotalPages ) { unauthorizedRobotsPaginationPageRef.current--; setUnauthorizedRobotsPaginationPage( unauthorizedRobotsPaginationPageRef.current ); } break; case ReceivedSSECommands.RemoveRobot: setRobots((arr) => { const newArr = [...arr]; const index = arr.findIndex((x) => x.Id === body.RobotId); if (index !== -1) { newArr.splice(index, 1); } return newArr; }); setRobotsTotalPages(body.TotalPages); // if user is on the last page and the last item is removed, we need to go back one page if ( body.TotalPages > 0 && robotsPaginationPageRef.current > body.TotalPages ) { robotsPaginationPageRef.current--; setRobotsPaginationPage(robotsPaginationPageRef.current); } break; case ReceivedSSECommands.RobotUpdated: setRobots((arr) => { const newArr = [...arr]; const index = arr.findIndex((x) => x.Id === body.RobotId); if (index !== -1) { newArr[index].Name = body.RobotName; } return newArr; }); break; case ReceivedSSECommands.UpdateRobotCurrentJob: setRobots((arr) => { const newArr = [...arr]; const index = arr.findIndex((x) => x.Id === body.RobotId); if (index !== -1) { newArr[index].CurrentJobName = body.JobName; newArr[index].JobsWaitingNameList = body.JobsWaitingNameList; } return newArr; }); break; case ReceivedSSECommands.UpdateRobotJobsWaitingCount: setRobots((arr) => { const newArr = [...arr]; const index = arr.findIndex((x) => x.Id === body.RobotId); if (index !== -1) { newArr[index].JobsWaitingCount = body.JobsWaitingCount; newArr[index].JobsWaitingNameList = body.JobsWaitingNameList; } return newArr; }); break; case ReceivedSSECommands.PermitJoinUpdated: setPermitJoinEnabled(body); break; default: break; } }; sseEventSource.current.onerror = (event) => console.log("sse error", event); sseEventSource.current.onopen = (event) => console.log("sse open", event); sseEventSource.current.onclose = (event) => console.log("sse close", event); return () => sseEventSource.current.close(); }, []); return ( <> {notificationContextHolder}
    {t("robotics.robots.header")}{" "}
    { setRobotsPaginationPage(page); robotsPaginationPageRef.current = page; }} totalPages={robotsTotalPages} /> {t("robotics.unauthorizedRobots.header")}
    { setUnauthorizedRobotsPaginationPage(page); unauthorizedRobotsPaginationPageRef.current = page; }} totalPages={unauthorizedRobotsTotalPages} /> ); }