robots table

main
alex 2023-10-14 20:41:59 +02:00
parent 4acbe50f8b
commit 28c823bc41
5 changed files with 213 additions and 24 deletions

7
commit_and_push.sh Executable file
View File

@ -0,0 +1,7 @@
git add *
read -p "Commit message: " commit_message
git commit -m "$commit_message"
git push -u origin main

View File

@ -178,13 +178,31 @@
"firmwareVersion": "Firmware Version",
"createdAt": "Erstellt am",
"actions": "Maßnahmen"
},
"status": {
"idle": "Inaktiv",
"processing": "Verarbeitung",
"connecting": "Verbinden",
"error": "Fehler",
"offline": "Offline"
},
"popconfirmEdit": {
"title": "Sind Sie sicher, dass Sie diesen Roboter bearbeiten wollen?",
"errorNotification": {
"message": "Roboter konnte nicht bearbeitet werden",
"description": "Name bereits vergeben"
}
},
"popconfirmDisconnect": {
"title": "Sind Sie sicher, dass Sie diesen Roboter trennen wollen?",
"description": "Der Roboter wird getrennt und kann nicht mehr für Aufträge verwendet werden"
}
},
"unauthorizedRobots": {
"header": "Nicht autorisierte Roboter",
"popconfirmDeny": {
"title": "Sind Sie sicher, dass Sie diesen Roboter ablehnen wollen?",
"description": "Der Roboter wird getrennt und muss sich ernuet verbinden"
"description": "Der Roboter wird getrennt und muss sich erneut verbinden"
},
"popconfirmAuthorize": {
"title": "Sind Sie sicher, dass Sie diesen Roboter autorisieren wollen?",

View File

@ -178,6 +178,24 @@
"firmwareVersion": "Firmware version",
"createdAt": "Created At",
"actions": "Actions"
},
"status": {
"idle": "Idle",
"processing": "Processing",
"connecting": "Connecting",
"error": "Error",
"offline": "Offline"
},
"popconfirmEdit": {
"title": "Are you sure you want to edit this robot?",
"errorNotification": {
"message": "Robot could not be edited",
"description": "Name already taken"
}
},
"popconfirmDisconnect": {
"title": "Are you sure you want to disconnect this robot?",
"description": "The robot will be disconnected and cannot be longer used for jobs"
}
},
"unauthorizedRobots": {

View File

@ -1,4 +1,12 @@
import { Badge, Popconfirm, Space, Table, Typography } from "antd";
import {
Badge,
Input,
Popconfirm,
Space,
Table,
Typography,
notification,
} from "antd";
import { useTranslation } from "react-i18next";
import { useRoboticsRobotContext } from "../../../Contexts/RoboticsRobot";
import { useEffect, useRef, useState } from "react";
@ -16,6 +24,8 @@ const ReceivedSSECommands = {
AddUnauthorizedRobot: 2,
AddRobot: 3,
RemoveUnauthorizedRobot: 4,
RemoveRobot: 5,
RobotUpdated: 6,
};
function getRobotTypeString(type) {
@ -32,27 +42,46 @@ function getRobotTypeString(type) {
export default function Robots() {
const robotsContext = useRoboticsRobotContext();
const { t } = useTranslation();
const [notificationApi, notificationContextHolder] =
notification.useNotification();
const [robotsPaginationPage, setRobotsPaginationPage] = useState(1);
const [
unauthorizedRobotsPaginationPage,
setUnauthorizedRobotsPaginationPage,
] = useState(1);
const [selectedRobotName, setSelectedRobotName] = useState("");
const sseEventSource = useRef(null);
const getRobotStatusBadge = (status) => {
switch (status) {
case 1:
return <Badge status="success" text={"Idle"} />;
return (
<Badge status="success" text={t("robotics.robots.status.idle")} />
);
case 2:
return <Badge status="processing" text={"Running"} />;
return (
<Badge
status="processing"
text={t("robotics.robots.status.processing")}
/>
);
case 3:
return <Badge status="warning" text={"Connecting"} />;
return (
<Badge
status="warning"
text={t("robotics.robots.status.connecting")}
/>
);
case 4:
return <Badge status="warning" text={"Error"} />;
return (
<Badge status="warning" text={t("robotics.robots.status.error")} />
);
case 5:
return <Badge status="default" text={"Offline"} />;
return (
<Badge status="default" text={t("robotics.robots.status.offline")} />
);
default:
return "Unknown";
}
@ -116,8 +145,78 @@ export default function Robots() {
key: "actions",
render: (_, record) => (
<Space size="middle">
<Link to="#">{t("common.text.edit")}</Link>
<Link to="#">{t("common.text.disconnect")}</Link>
<Popconfirm
placement="left"
title={t("robotics.robots.popconfirmEdit.title")}
description={
<Input
placeholder="Name"
value={selectedRobotName}
onChange={(e) => setSelectedRobotName(e.target.value)}
minLength={Constants.GLOBALS.MIN_ROBOTICS_ROBOT_NAME_LENGTH}
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,
name: 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"
),
});
}
})
}
>
<Link to="#" onClick={() => setSelectedRobotName(record.name)}>
{t("common.text.edit")}
</Link>
</Popconfirm>
<Popconfirm
placement="left"
title={t("robotics.robots.popconfirmDisconnect.title")}
description={t(
"robotics.robots.popconfirmDisconnect.description"
)}
okText={t("common.button.confirm")}
cancelText={t("common.button.cancel")}
onConfirm={() =>
myFetch(
`/robot/${record.id}`,
"DELETE",
null,
{},
myFetchContentType.JSON,
Constants.ROBOTICS_API_ADDRESS
)
}
>
<Link to="#">{t("common.text.disconnect")}</Link>
</Popconfirm>
</Space>
),
},
@ -129,21 +228,27 @@ export default function Robots() {
const getRobotsTableItems = (robots) => {
let items = [];
robots.forEach((robot) => {
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),
currentJob: robot.CurrentJob,
currentJob:
robot.CurrentJobId === ""
? Constants.TEXT_EMPTY_PLACEHOLDER
: robot.CurrentJobId,
jobsWaiting: robot.JobsWaitingCount,
address: robot.Address,
firmwareVersion: robot.FirmwareVersion,
connectedAt: FormatDatetime(robot.ConnectedAt),
createdAt: FormatDatetime(robot.CreatedAt),
actions: robot.Actions,
});
});
})
);
return items;
};
@ -170,6 +275,11 @@ export default function Robots() {
dataIndex: "connectedAt",
key: "connectedAt",
},
{
title: t("robotics.robots.column.firmwareVersion"),
dataIndex: "firmwareVersion",
key: "firmwareVersion",
},
{
title: t("robotics.robots.column.actions"),
dataIndex: "actions",
@ -192,9 +302,7 @@ export default function Robots() {
{},
myFetchContentType.JSON,
Constants.ROBOTICS_API_ADDRESS
).then((data) => {
console.log("data", data);
})
)
}
>
<Link to="#">{t("common.text.deny")}</Link>
@ -216,9 +324,7 @@ export default function Robots() {
{},
myFetchContentType.JSON,
Constants.ROBOTICS_API_ADDRESS
).then((data) => {
console.log("data", data);
})
)
}
>
<Link to="#">{t("common.text.authorize")}</Link>
@ -241,6 +347,7 @@ export default function Robots() {
type: getRobotTypeString(robot.Type),
address: robot.Address,
connectedAt: FormatDatetime(robot.ConnectedAt),
firmwareVersion: robot.FirmwareVersion,
actions: robot.Actions,
});
});
@ -293,12 +400,8 @@ export default function Robots() {
robotsContext.setRobots((arr) => {
const newArr = [...arr];
console.log("arr", arr);
const index = arr.findIndex((x) => x.Id === body.RobotId);
console.log("index", index);
if (index !== -1) {
newArr[index].Status = body.Status;
}
@ -335,6 +438,19 @@ export default function Robots() {
return newArr;
});
// remove from unauthorized robots
robotsContext.setUnauthorizedRobots((arr) => {
const newArr = [...arr];
const index = arr.findIndex((x) => x.Id === body.Id);
if (index !== -1) {
newArr.splice(index, 1);
}
return newArr;
});
break;
case ReceivedSSECommands.RemoveUnauthorizedRobot:
robotsContext.setUnauthorizedRobots((arr) => {
@ -349,6 +465,32 @@ export default function Robots() {
return newArr;
});
break;
case ReceivedSSECommands.RemoveRobot:
robotsContext.setRobots((arr) => {
const newArr = [...arr];
const index = arr.findIndex((x) => x.Id === body);
if (index !== -1) {
newArr.splice(index, 1);
}
return newArr;
});
break;
case ReceivedSSECommands.RobotUpdated:
robotsContext.setRobots((arr) => {
const newArr = [...arr];
const index = arr.findIndex((x) => x.Id === body.RobotId);
if (index !== -1) {
newArr[index].Name = body.Name;
}
return newArr;
});
break;
default:
break;
}
@ -365,6 +507,8 @@ export default function Robots() {
return (
<>
{notificationContextHolder}
<Typography.Title level={4}>
{t("robotics.robots.header")} ({robotsContext.robots.length})
</Typography.Title>

View File

@ -102,6 +102,8 @@ export const Constants = {
MAX_LOG_MANAGER_DISPLAY_NAME_LENGTH: 16,
MIN_LOG_MANAGER_ADDRESS_LENGTH: 3,
MAX_LOG_MANAGER_ADDRESS_LENGTH: 100,
MIN_ROBOTICS_ROBOT_NAME_LENGTH: 2,
MAX_ROBOTICS_ROBOT_NAME_LENGTH: 30,
},
MAX_AVATAR_SIZE: 5 * 1024 * 1024,
ACCEPTED_AVATAR_FILE_TYPES: [
@ -1390,7 +1392,7 @@ export function myFetch(
window.location.href = "/";
}
return;
throw response.status;
}
// check if response is json