@@ -57,6 +57,95 @@ router.get('/recordings/:id', requireSignIn, async (req, res) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PUT endpoint to update the name and limit of a robot.
|
||||||
|
*/
|
||||||
|
router.put('/recordings/:id', requireSignIn, async (req: AuthenticatedRequest, res) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
const { name, limit } = req.body;
|
||||||
|
|
||||||
|
// Validate input
|
||||||
|
if (!name && limit === undefined) {
|
||||||
|
return res.status(400).json({ error: 'Either "name" or "limit" must be provided.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the robot by ID
|
||||||
|
const robot = await Robot.findOne({ where: { 'recording_meta.id': id } });
|
||||||
|
|
||||||
|
if (!robot) {
|
||||||
|
return res.status(404).json({ error: 'Robot not found.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update fields if provided
|
||||||
|
if (name) {
|
||||||
|
robot.set('recording_meta', { ...robot.recording_meta, name });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the limit
|
||||||
|
if (limit !== undefined) {
|
||||||
|
const workflow = [...robot.recording.workflow]; // Create a copy of the workflow
|
||||||
|
|
||||||
|
// Ensure the workflow structure is valid before updating
|
||||||
|
if (
|
||||||
|
workflow.length > 0 &&
|
||||||
|
workflow[0]?.what?.[0]
|
||||||
|
) {
|
||||||
|
// Create a new workflow object with the updated limit
|
||||||
|
const updatedWorkflow = workflow.map((step, index) => {
|
||||||
|
if (index === 0) { // Assuming you want to update the first step
|
||||||
|
return {
|
||||||
|
...step,
|
||||||
|
what: step.what.map((action, actionIndex) => {
|
||||||
|
if (actionIndex === 0) { // Assuming the first action needs updating
|
||||||
|
return {
|
||||||
|
...action,
|
||||||
|
args: (action.args ?? []).map((arg, argIndex) => {
|
||||||
|
if (argIndex === 0) { // Assuming the first argument needs updating
|
||||||
|
return { ...arg, limit };
|
||||||
|
}
|
||||||
|
return arg;
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return action;
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return step;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Replace the workflow in the recording object
|
||||||
|
robot.set('recording', { ...robot.recording, workflow: updatedWorkflow });
|
||||||
|
} else {
|
||||||
|
return res.status(400).json({ error: 'Invalid workflow structure for updating limit.' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await robot.save();
|
||||||
|
|
||||||
|
const updatedRobot = await Robot.findOne({ where: { 'recording_meta.id': id } });
|
||||||
|
console.log('After save:', updatedRobot);
|
||||||
|
|
||||||
|
// Log the update
|
||||||
|
logger.log('info', `Robot with ID ${id} was updated successfully.`);
|
||||||
|
|
||||||
|
return res.status(200).json({ message: 'Robot updated successfully', robot });
|
||||||
|
} catch (error) {
|
||||||
|
// Safely handle the error type
|
||||||
|
if (error instanceof Error) {
|
||||||
|
logger.log('error', `Error updating robot with ID ${req.params.id}: ${error.message}`);
|
||||||
|
return res.status(500).json({ error: error.message });
|
||||||
|
} else {
|
||||||
|
logger.log('error', `Unknown error updating robot with ID ${req.params.id}`);
|
||||||
|
return res.status(500).json({ error: 'An unknown error occurred.' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DELETE endpoint for deleting a recording from the storage.
|
* DELETE endpoint for deleting a recording from the storage.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -19,6 +19,20 @@ export const getStoredRecordings = async (): Promise<string[] | null> => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updateRecording = async (id: string, data: { name?: string; limit?: number }): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.put(`${apiUrl}/storage/recordings/${id}`, data);
|
||||||
|
if (response.status === 200) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(`Couldn't update recording with id ${id}`);
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(`Error updating recording: ${error.message}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const getStoredRuns = async (): Promise<string[] | null> => {
|
export const getStoredRuns = async (): Promise<string[] | null> => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`${apiUrl}/storage/runs`);
|
const response = await axios.get(`${apiUrl}/storage/runs`);
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import TablePagination from '@mui/material/TablePagination';
|
|||||||
import TableRow from '@mui/material/TableRow';
|
import TableRow from '@mui/material/TableRow';
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { WorkflowFile } from "maxun-core";
|
import { WorkflowFile } from "maxun-core";
|
||||||
import { IconButton, Button, Box, Typography, TextField } from "@mui/material";
|
import { IconButton, Button, Box, Typography, TextField, MenuItem, Menu, ListItemIcon, ListItemText } from "@mui/material";
|
||||||
import { Schedule, DeleteForever, Edit, PlayCircle, Settings, Power } from "@mui/icons-material";
|
import { Schedule, DeleteForever, Edit, PlayCircle, Settings, Power, ContentCopy, } from "@mui/icons-material";
|
||||||
import LinkIcon from '@mui/icons-material/Link';
|
import LinkIcon from '@mui/icons-material/Link';
|
||||||
import { useGlobalInfoStore } from "../../context/globalInfo";
|
import { useGlobalInfoStore } from "../../context/globalInfo";
|
||||||
import { deleteRecordingFromStorage, getStoredRecordings } from "../../api/storage";
|
import { deleteRecordingFromStorage, getStoredRecordings } from "../../api/storage";
|
||||||
@@ -18,7 +18,7 @@ import { Add } from "@mui/icons-material";
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { stopRecording } from "../../api/recording";
|
import { stopRecording } from "../../api/recording";
|
||||||
import { GenericModal } from '../atoms/GenericModal';
|
import { GenericModal } from '../atoms/GenericModal';
|
||||||
|
import { Menu as MenuIcon } from '@mui/icons-material';
|
||||||
|
|
||||||
/** TODO:
|
/** TODO:
|
||||||
* 1. allow editing existing robot after persisting browser steps
|
* 1. allow editing existing robot after persisting browser steps
|
||||||
@@ -26,7 +26,7 @@ import { GenericModal } from '../atoms/GenericModal';
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
interface Column {
|
interface Column {
|
||||||
id: 'interpret' | 'name' | 'delete' | 'schedule' | 'integrate' | 'settings';
|
id: 'interpret' | 'name' | 'options' | 'schedule' | 'integrate' | 'settings';
|
||||||
label: string;
|
label: string;
|
||||||
minWidth?: number;
|
minWidth?: number;
|
||||||
align?: 'right';
|
align?: 'right';
|
||||||
@@ -69,8 +69,8 @@ const columns: readonly Column[] = [
|
|||||||
minWidth: 80,
|
minWidth: 80,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'delete',
|
id: 'options',
|
||||||
label: 'Delete',
|
label: 'Options',
|
||||||
minWidth: 80,
|
minWidth: 80,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -90,9 +90,10 @@ interface RecordingsTableProps {
|
|||||||
handleScheduleRecording: (id: string, fileName: string, params: string[]) => void;
|
handleScheduleRecording: (id: string, fileName: string, params: string[]) => void;
|
||||||
handleIntegrateRecording: (id: string, fileName: string, params: string[]) => void;
|
handleIntegrateRecording: (id: string, fileName: string, params: string[]) => void;
|
||||||
handleSettingsRecording: (id: string, fileName: string, params: string[]) => void;
|
handleSettingsRecording: (id: string, fileName: string, params: string[]) => void;
|
||||||
|
handleEditRobot: (id: string, name: string, params: string[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handleScheduleRecording, handleIntegrateRecording, handleSettingsRecording }: RecordingsTableProps) => {
|
export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handleScheduleRecording, handleIntegrateRecording, handleSettingsRecording, handleEditRobot }: RecordingsTableProps) => {
|
||||||
const [page, setPage] = React.useState(0);
|
const [page, setPage] = React.useState(0);
|
||||||
const [rowsPerPage, setRowsPerPage] = React.useState(10);
|
const [rowsPerPage, setRowsPerPage] = React.useState(10);
|
||||||
const [rows, setRows] = React.useState<Data[]>([]);
|
const [rows, setRows] = React.useState<Data[]>([]);
|
||||||
@@ -248,22 +249,27 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl
|
|||||||
<IntegrateButton handleIntegrate={() => handleIntegrateRecording(row.id, row.name, row.params || [])} />
|
<IntegrateButton handleIntegrate={() => handleIntegrateRecording(row.id, row.name, row.params || [])} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
);
|
);
|
||||||
case 'delete':
|
case 'options':
|
||||||
return (
|
return (
|
||||||
<TableCell key={column.id} align={column.align}>
|
<TableCell key={column.id} align={column.align}>
|
||||||
<IconButton aria-label="add" size="small" onClick={() => {
|
<OptionsButton
|
||||||
deleteRecordingFromStorage(row.id).then((result: boolean) => {
|
handleEdit={() => handleEditRobot(row.id, row.name, row.params || [])}
|
||||||
if (result) {
|
handleDelete={() => {
|
||||||
setRows([]);
|
deleteRecordingFromStorage(row.id).then((result: boolean) => {
|
||||||
notify('success', 'Recording deleted successfully');
|
if (result) {
|
||||||
fetchRecordings();
|
setRows([]);
|
||||||
}
|
notify('success', 'Recording deleted successfully');
|
||||||
})
|
fetchRecordings();
|
||||||
}}>
|
}
|
||||||
<DeleteForever />
|
})
|
||||||
</IconButton>
|
}}
|
||||||
</TableCell>
|
handleDuplicate={() => {
|
||||||
);
|
notify('info', 'Duplicating recording...');
|
||||||
|
// Implement duplication logic here
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
);
|
||||||
case 'settings':
|
case 'settings':
|
||||||
return (
|
return (
|
||||||
<TableCell key={column.id} align={column.align}>
|
<TableCell key={column.id} align={column.align}>
|
||||||
@@ -377,6 +383,60 @@ const SettingsButton = ({ handleSettings }: SettingsButtonProps) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface OptionsButtonProps {
|
||||||
|
handleEdit: () => void;
|
||||||
|
handleDelete: () => void;
|
||||||
|
handleDuplicate: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OptionsButton = ({ handleEdit, handleDelete, handleDuplicate }: OptionsButtonProps) => {
|
||||||
|
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||||
|
|
||||||
|
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<IconButton
|
||||||
|
aria-label="options"
|
||||||
|
size="small"
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<MenuIcon />
|
||||||
|
</IconButton>
|
||||||
|
<Menu
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
open={Boolean(anchorEl)}
|
||||||
|
onClose={handleClose}
|
||||||
|
>
|
||||||
|
<MenuItem onClick={() => { handleEdit(); handleClose(); }}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Edit fontSize="small" />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>Edit</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={() => { handleDelete(); handleClose(); }}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<DeleteForever fontSize="small" />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>Delete</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={() => { handleDuplicate(); handleClose(); }}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<ContentCopy fontSize="small" />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>Duplicate</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const modalStyle = {
|
const modalStyle = {
|
||||||
top: '50%',
|
top: '50%',
|
||||||
left: '50%',
|
left: '50%',
|
||||||
|
|||||||
178
src/components/molecules/RobotEdit.tsx
Normal file
178
src/components/molecules/RobotEdit.tsx
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { GenericModal } from "../atoms/GenericModal";
|
||||||
|
import { TextField, Typography, Box, Button } from "@mui/material";
|
||||||
|
import { modalStyle } from "./AddWhereCondModal";
|
||||||
|
import { useGlobalInfoStore } from '../../context/globalInfo';
|
||||||
|
import { getStoredRecording, updateRecording } from '../../api/storage';
|
||||||
|
import { WhereWhatPair } from 'maxun-core';
|
||||||
|
|
||||||
|
interface RobotMeta {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
pairs: number;
|
||||||
|
updatedAt: string;
|
||||||
|
params: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RobotWorkflow {
|
||||||
|
workflow: WhereWhatPair[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RobotEditOptions {
|
||||||
|
name: string;
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ScheduleConfig {
|
||||||
|
runEvery: number;
|
||||||
|
runEveryUnit: 'MINUTES' | 'HOURS' | 'DAYS' | 'WEEKS' | 'MONTHS';
|
||||||
|
startFrom: 'SUNDAY' | 'MONDAY' | 'TUESDAY' | 'WEDNESDAY' | 'THURSDAY' | 'FRIDAY' | 'SATURDAY';
|
||||||
|
atTimeStart?: string;
|
||||||
|
atTimeEnd?: string;
|
||||||
|
timezone: string;
|
||||||
|
lastRunAt?: Date;
|
||||||
|
nextRunAt?: Date;
|
||||||
|
cronExpression?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RobotSettings {
|
||||||
|
id: string;
|
||||||
|
userId?: number;
|
||||||
|
recording_meta: RobotMeta;
|
||||||
|
recording: RobotWorkflow;
|
||||||
|
google_sheet_email?: string | null;
|
||||||
|
google_sheet_name?: string | null;
|
||||||
|
google_sheet_id?: string | null;
|
||||||
|
google_access_token?: string | null;
|
||||||
|
google_refresh_token?: string | null;
|
||||||
|
schedule?: ScheduleConfig | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RobotSettingsProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
handleStart: (settings: RobotSettings) => void;
|
||||||
|
handleClose: () => void;
|
||||||
|
initialSettings?: RobotSettings | null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettings }: RobotSettingsProps) => {
|
||||||
|
const [robot, setRobot] = useState<RobotSettings | null>(null);
|
||||||
|
const { recordingId, notify } = useGlobalInfoStore();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen) {
|
||||||
|
getRobot();
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
const getRobot = async () => {
|
||||||
|
if (recordingId) {
|
||||||
|
const robot = await getStoredRecording(recordingId);
|
||||||
|
setRobot(robot);
|
||||||
|
} else {
|
||||||
|
notify('error', 'Could not find robot details. Please try again.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRobotNameChange = (newName: string) => {
|
||||||
|
setRobot((prev) =>
|
||||||
|
prev ? { ...prev, recording_meta: { ...prev.recording_meta, name: newName } } : prev
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLimitChange = (newLimit: number) => {
|
||||||
|
setRobot((prev) => {
|
||||||
|
if (!prev) return prev;
|
||||||
|
|
||||||
|
const updatedWorkflow = [...prev.recording.workflow];
|
||||||
|
|
||||||
|
if (
|
||||||
|
updatedWorkflow.length > 0 &&
|
||||||
|
updatedWorkflow[0]?.what &&
|
||||||
|
updatedWorkflow[0].what.length > 0 &&
|
||||||
|
updatedWorkflow[0].what[0].args &&
|
||||||
|
updatedWorkflow[0].what[0].args.length > 0 &&
|
||||||
|
updatedWorkflow[0].what[0].args[0]
|
||||||
|
) {
|
||||||
|
updatedWorkflow[0].what[0].args[0].limit = newLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...prev, recording: { ...prev.recording, workflow: updatedWorkflow } };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const handleSave = async () => {
|
||||||
|
if (!robot) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
name: robot.recording_meta.name,
|
||||||
|
limit: robot.recording.workflow[0]?.what[0]?.args?.[0]?.limit,
|
||||||
|
};
|
||||||
|
|
||||||
|
const success = await updateRecording(robot.recording_meta.id, payload);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
notify('success', 'Robot updated successfully.');
|
||||||
|
handleStart(robot); // Inform parent about the updated robot
|
||||||
|
handleClose(); // Close the modal
|
||||||
|
|
||||||
|
window.location.reload();
|
||||||
|
} else {
|
||||||
|
notify('error', 'Failed to update the robot. Please try again.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
notify('error', 'An error occurred while updating the robot.');
|
||||||
|
console.error('Error updating robot:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GenericModal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onClose={handleClose}
|
||||||
|
modalStyle={modalStyle}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
<Typography variant="h5" style={{ marginBottom: '20px' }}>Edit Robot</Typography>
|
||||||
|
<Box style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
|
{
|
||||||
|
robot && (
|
||||||
|
<>
|
||||||
|
<TextField
|
||||||
|
label="Change Robot Name"
|
||||||
|
key="Change Robot Name"
|
||||||
|
type='text'
|
||||||
|
value={robot.recording_meta.name}
|
||||||
|
onChange={(e) => handleRobotNameChange(e.target.value)}
|
||||||
|
style={{ marginBottom: '20px' }}
|
||||||
|
/>
|
||||||
|
{robot.recording.workflow?.[0]?.what?.[0]?.args?.[0]?.limit !== undefined && (
|
||||||
|
<TextField
|
||||||
|
label="Robot Limit"
|
||||||
|
type="number"
|
||||||
|
value={robot.recording.workflow[0].what[0].args[0].limit || ''}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleLimitChange(parseInt(e.target.value, 10) || 0)
|
||||||
|
}
|
||||||
|
style={{ marginBottom: '20px' }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Box mt={2} display="flex" justifyContent="flex-end" onClick={handleSave}>
|
||||||
|
<Button variant="contained" color="primary">
|
||||||
|
Save Changes
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleClose} color="primary" variant="outlined" style={{ marginLeft: '10px' }}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
</GenericModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -45,6 +45,8 @@ export interface Data {
|
|||||||
// task: string;
|
// task: string;
|
||||||
log: string;
|
log: string;
|
||||||
runId: string;
|
runId: string;
|
||||||
|
robotId: string;
|
||||||
|
robotMetaId: string;
|
||||||
interpreterSettings: RunSettings;
|
interpreterSettings: RunSettings;
|
||||||
serializableOutput: any;
|
serializableOutput: any;
|
||||||
binaryOutput: any;
|
binaryOutput: any;
|
||||||
@@ -63,8 +65,6 @@ export const RunsTable = (
|
|||||||
const [rowsPerPage, setRowsPerPage] = useState(10);
|
const [rowsPerPage, setRowsPerPage] = useState(10);
|
||||||
const [rows, setRows] = useState<Data[]>([]);
|
const [rows, setRows] = useState<Data[]>([]);
|
||||||
|
|
||||||
console.log(`rows runs: ${JSON.stringify(rows)}`);
|
|
||||||
|
|
||||||
const { notify, rerenderRuns, setRerenderRuns } = useGlobalInfoStore();
|
const { notify, rerenderRuns, setRerenderRuns } = useGlobalInfoStore();
|
||||||
|
|
||||||
const handleChangePage = (event: unknown, newPage: number) => {
|
const handleChangePage = (event: unknown, newPage: number) => {
|
||||||
@@ -105,12 +105,12 @@ export const RunsTable = (
|
|||||||
fetchRuns();
|
fetchRuns();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Group runs by recording name
|
// Group runs by robot meta id
|
||||||
const groupedRows = rows.reduce((acc, row) => {
|
const groupedRows = rows.reduce((acc, row) => {
|
||||||
if (!acc[row.name]) {
|
if (!acc[row.robotMetaId]) {
|
||||||
acc[row.name] = [];
|
acc[row.robotMetaId] = [];
|
||||||
}
|
}
|
||||||
acc[row.name].push(row);
|
acc[row.robotMetaId].push(row);
|
||||||
return acc;
|
return acc;
|
||||||
}, {} as Record<string, Data[]>);
|
}, {} as Record<string, Data[]>);
|
||||||
|
|
||||||
@@ -120,10 +120,10 @@ export const RunsTable = (
|
|||||||
All Runs
|
All Runs
|
||||||
</Typography>
|
</Typography>
|
||||||
<TableContainer component={Paper} sx={{ width: '100%', overflow: 'hidden' }}>
|
<TableContainer component={Paper} sx={{ width: '100%', overflow: 'hidden' }}>
|
||||||
{Object.entries(groupedRows).map(([name, group]) => (
|
{Object.entries(groupedRows).map(([id, data]) => (
|
||||||
<Accordion key={name}>
|
<Accordion key={id}>
|
||||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||||
<Typography variant="h6">{name}</Typography>
|
<Typography variant="h6">{data[data.length - 1].name}</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
<Table stickyHeader aria-label="sticky table">
|
<Table stickyHeader aria-label="sticky table">
|
||||||
@@ -142,17 +142,19 @@ export const RunsTable = (
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{group.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map((row) => (
|
{data
|
||||||
<CollapsibleRow
|
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
|
||||||
row={row}
|
.map((row) => (
|
||||||
handleDelete={handleDelete}
|
<CollapsibleRow
|
||||||
key={`row-${row.id}`}
|
row={row}
|
||||||
isOpen={runId === row.runId && runningRecordingName === row.name}
|
handleDelete={handleDelete}
|
||||||
currentLog={currentInterpretationLog}
|
key={`row-${row.id}`}
|
||||||
abortRunHandler={abortRunHandler}
|
isOpen={runId === row.runId && runningRecordingName === row.name}
|
||||||
runningRecordingName={runningRecordingName}
|
currentLog={currentInterpretationLog}
|
||||||
/>
|
abortRunHandler={abortRunHandler}
|
||||||
))}
|
runningRecordingName={runningRecordingName}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { RunSettings, RunSettingsModal } from "../molecules/RunSettings";
|
|||||||
import { ScheduleSettings, ScheduleSettingsModal } from "../molecules/ScheduleSettings";
|
import { ScheduleSettings, ScheduleSettingsModal } from "../molecules/ScheduleSettings";
|
||||||
import { IntegrationSettings, IntegrationSettingsModal } from "../molecules/IntegrationSettings";
|
import { IntegrationSettings, IntegrationSettingsModal } from "../molecules/IntegrationSettings";
|
||||||
import { RobotSettings, RobotSettingsModal } from "../molecules/RobotSettings";
|
import { RobotSettings, RobotSettingsModal } from "../molecules/RobotSettings";
|
||||||
|
import { RobotEditModal } from '../molecules/RobotEdit';
|
||||||
|
|
||||||
interface RecordingsProps {
|
interface RecordingsProps {
|
||||||
handleEditRecording: (id: string, fileName: string) => void;
|
handleEditRecording: (id: string, fileName: string) => void;
|
||||||
@@ -18,10 +19,12 @@ export const Recordings = ({ handleEditRecording, handleRunRecording, setRecordi
|
|||||||
const [scheduleSettingsAreOpen, setScheduleSettingsAreOpen] = useState(false);
|
const [scheduleSettingsAreOpen, setScheduleSettingsAreOpen] = useState(false);
|
||||||
const [integrateSettingsAreOpen, setIntegrateSettingsAreOpen] = useState(false);
|
const [integrateSettingsAreOpen, setIntegrateSettingsAreOpen] = useState(false);
|
||||||
const [robotSettingsAreOpen, setRobotSettingsAreOpen] = useState(false);
|
const [robotSettingsAreOpen, setRobotSettingsAreOpen] = useState(false);
|
||||||
|
const [robotEditAreOpen, setRobotEditAreOpen] = useState(false);
|
||||||
const [params, setParams] = useState<string[]>([]);
|
const [params, setParams] = useState<string[]>([]);
|
||||||
const [selectedRecordingId, setSelectedRecordingId] = useState<string>('');
|
const [selectedRecordingId, setSelectedRecordingId] = useState<string>('');
|
||||||
const handleIntegrateRecording = (id: string, settings: IntegrationSettings) => {};
|
const handleIntegrateRecording = (id: string, settings: IntegrationSettings) => {};
|
||||||
const handleSettingsRecording = (id: string, settings: RobotSettings) => {};
|
const handleSettingsRecording = (id: string, settings: RobotSettings) => {};
|
||||||
|
const handleEditRobot = (id: string, settings: RobotSettings) => {};
|
||||||
|
|
||||||
const handleSettingsAndIntegrate = (id: string, name: string, params: string[]) => {
|
const handleSettingsAndIntegrate = (id: string, name: string, params: string[]) => {
|
||||||
if (params.length === 0) {
|
if (params.length === 0) {
|
||||||
@@ -75,6 +78,19 @@ export const Recordings = ({ handleEditRecording, handleRunRecording, setRecordi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleEditRobotOption = (id: string, name: string, params: string[]) => {
|
||||||
|
if (params.length === 0) {
|
||||||
|
setRobotEditAreOpen(true);
|
||||||
|
setRecordingInfo(id, name);
|
||||||
|
setSelectedRecordingId(id);
|
||||||
|
} else {
|
||||||
|
setParams(params);
|
||||||
|
setRobotEditAreOpen(true);
|
||||||
|
setRecordingInfo(id, name);
|
||||||
|
setSelectedRecordingId(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setParams([]);
|
setParams([]);
|
||||||
setRunSettingsAreOpen(false);
|
setRunSettingsAreOpen(false);
|
||||||
@@ -103,6 +119,13 @@ export const Recordings = ({ handleEditRecording, handleRunRecording, setRecordi
|
|||||||
setSelectedRecordingId('');
|
setSelectedRecordingId('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleRobotEditClose = () => {
|
||||||
|
setParams([]);
|
||||||
|
setRobotEditAreOpen(false);
|
||||||
|
setRecordingInfo('', '');
|
||||||
|
setSelectedRecordingId('');
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<RunSettingsModal isOpen={runSettingsAreOpen}
|
<RunSettingsModal isOpen={runSettingsAreOpen}
|
||||||
@@ -123,6 +146,10 @@ export const Recordings = ({ handleEditRecording, handleRunRecording, setRecordi
|
|||||||
handleClose={handleRobotSettingsClose}
|
handleClose={handleRobotSettingsClose}
|
||||||
handleStart={(settings) => handleSettingsRecording(selectedRecordingId, settings)}
|
handleStart={(settings) => handleSettingsRecording(selectedRecordingId, settings)}
|
||||||
/>
|
/>
|
||||||
|
<RobotEditModal isOpen={robotEditAreOpen}
|
||||||
|
handleClose={handleRobotEditClose}
|
||||||
|
handleStart={(settings) => handleEditRobot(selectedRecordingId,settings)}
|
||||||
|
/>
|
||||||
<Grid container direction="column" sx={{ padding: '30px' }}>
|
<Grid container direction="column" sx={{ padding: '30px' }}>
|
||||||
<Grid item xs>
|
<Grid item xs>
|
||||||
<RecordingsTable
|
<RecordingsTable
|
||||||
@@ -131,6 +158,7 @@ export const Recordings = ({ handleEditRecording, handleRunRecording, setRecordi
|
|||||||
handleScheduleRecording={handleSettingsAndSchedule}
|
handleScheduleRecording={handleSettingsAndSchedule}
|
||||||
handleIntegrateRecording={handleSettingsAndIntegrate}
|
handleIntegrateRecording={handleSettingsAndIntegrate}
|
||||||
handleSettingsRecording={handleRobotSettings}
|
handleSettingsRecording={handleRobotSettings}
|
||||||
|
handleEditRobot={handleEditRobotOption}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
Reference in New Issue
Block a user