Merge CEC-394 Car update log (#82)

This commit is contained in:
John Wu
2021-08-26 15:03:45 -07:00
committed by GitHub
parent d1815e2ff9
commit 74eb2707a3
34 changed files with 3114 additions and 3583 deletions

View File

@@ -1,204 +0,0 @@
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import PropTypes from "prop-types";
import {
Checkbox,
Table,
TableBody,
TableCell,
TableFooter,
TablePagination,
TableRow,
} from "@material-ui/core";
import clsx from "clsx";
import { useVehicleContext } from "../../Contexts/VehicleContext";
import { useStatusContext } from "../../Contexts/StatusContext";
import { LocalDateTimeString } from "../../../utils/dates";
import TableHeaderSortable from "../../Table/HeaderSortable";
import { logger } from "../../../services/monitoring";
import ConnectedIcon from "../../Controls/ConnectedIcon";
import ECUList from "../../Controls/ECUList";
const tableColumns = [
{
id: "vin",
label: "VIN",
},
{
id: "model",
label: "Model",
},
{
id: "year",
label: "Year",
},
{
id: "trim",
label: "Trim",
},
{
id: "created_at",
label: "Created",
},
{
id: "updated_at",
label: "Updated",
},
];
const CarSelectionTable = (props) => {
const { token, classes, search, selected, onSelect, onSelectAll } = props;
const [pageSize, setPageSize] = useState(10);
const [pageIndex, setPageIndex] = useState(0);
const [orderBy, setOrderBy] = useState("vin");
const [order, setOrder] = useState("asc");
const { getVehicles, vehicles, totalVehicles } = useVehicleContext();
const { setMessage } = useStatusContext();
const { search: searchTerm } = search;
const sortHandler = (event, property) => {
if (property === orderBy) {
if (order === "asc") {
setOrder("desc");
} else {
setOrder("asc");
}
} else {
setOrderBy(property);
setOrder("asc");
}
};
const handleChangePageIndex = (event, newIndex) => {
setPageIndex(newIndex);
};
const handleChangePageSize = (event) => {
setPageSize(parseInt(event.target.value, 10));
setPageIndex(0);
};
const handleSelectAll = (event) => {
if (!onSelectAll) return;
const newSelected = [];
if (event.target.checked) {
vehicles.forEach((car) => {
newSelected.push(car.vin);
});
}
onSelectAll(newSelected);
};
const handleSelect = (event, key) => {
if (!onSelect) return;
onSelect(event, key);
};
useEffect(() => {
const options = {
limit: pageSize,
offset: pageSize * pageIndex,
order: `${orderBy} ${order}`,
};
(async () => {
try {
await getVehicles(Object.assign(options, search), token);
} catch (e) {
setMessage(e.message);
logger.warn(e.stack);
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pageIndex, pageSize, orderBy, order, search, token]);
return (
<div className={clsx(classes.paper, classes.tableSize)}>
<Table>
<TableHeaderSortable
classes={classes}
orderBy={orderBy}
order={order}
columnData={tableColumns}
onSortRequest={sortHandler}
multiSelect={true}
onSelectAll={handleSelectAll}
selectCount={selected.length}
rowCount={vehicles.length}
/>
<TableBody>
{vehicles.map((row) => {
const isSelected = selected.indexOf(row.vin) !== -1;
return (
<TableRow key={row.vin}>
<TableCell padding="checkbox">
<Checkbox
checked={isSelected}
onChange={(event) => handleSelect(event, row.vin)}
/>
</TableCell>
<TableCell align="center">
<ConnectedIcon
connected={row.connected}
style={{ marginRight: 5 }}
/>
<Link to={`/vehicle-status/${row.vin}`}>{row.vin}</Link>
{row.ecu_list && (
<>
<br />
<ECUList
list={row.ecu_list}
search={searchTerm}
searchedOnly={true}
/>
</>
)}
</TableCell>
<TableCell align="center">{row.model}</TableCell>
<TableCell align="center">{row.year}</TableCell>
<TableCell align="center">{row.trim || ""}</TableCell>
<TableCell align="center">
{LocalDateTimeString(row.created)}
</TableCell>
<TableCell align="center">
{LocalDateTimeString(row.updated)}
</TableCell>
</TableRow>
);
})}
</TableBody>
<TableFooter>
<TableRow>
<TablePagination
rowsPerPageOptions={[5, 10, 25, 100]}
colSpan={7}
count={totalVehicles}
rowsPerPage={pageSize}
page={pageIndex}
SelectProps={{
inputProps: { "aria-label": "rows per page" },
native: true,
}}
onPageChange={handleChangePageIndex}
onRowsPerPageChange={handleChangePageSize}
/>
</TableRow>
</TableFooter>
</Table>
</div>
);
};
CarSelectionTable.propTypes = {
token: PropTypes.string.isRequired,
classes: PropTypes.object.isRequired,
search: PropTypes.object.isRequired,
selected: PropTypes.array.isRequired,
onSelect: PropTypes.func.isRequired,
onSelectAll: PropTypes.func.isRequired,
connectionStatus: PropTypes.bool,
};
export default CarSelectionTable;

View File

@@ -8,9 +8,9 @@ import { VehicleProvider } from "../../Contexts/VehicleContext";
import { useUserContext } from "../../Contexts/UserContext";
import { useStatusContext } from "../../Contexts/StatusContext";
import useStyles from "../../useStyles";
import SendCommand from "../SendCommand";
import SendCommand from "../../Controls/SendCommand";
import SearchField from "../../Controls/SearchField";
import CarSelectionTable from "../CarSelectionTable";
import CarSelectionTable from "../../Controls/CarSelectionTable";
import { logger } from "../../../services/monitoring";
const MainForm = () => {

View File

@@ -1,151 +0,0 @@
import React, { useEffect, useState } from "react";
import PropTypes from "prop-types";
import { FormControl, IconButton, InputLabel, Select } from "@material-ui/core";
import SendIcon from "@material-ui/icons/Send";
import { useVehicleContext } from "../../Contexts/VehicleContext";
import commands from "../../../services/commands";
import useStyles from "../../useStyles";
import { useUserContext } from "../../Contexts/UserContext";
import { useStatusContext } from "../../Contexts/StatusContext";
import { logger } from "../../../services/monitoring";
const SendCommand = ({ vins }) => {
const classes = useStyles();
const { sendCommand, busy } = useVehicleContext();
const {
token: {
idToken: { jwtToken: token },
},
} = useUserContext();
const NoParameters = {
value: "",
label: "None",
};
const { setMessage } = useStatusContext();
const [command, setCommand] = useState("");
const [parameters, setParameters] = useState([NoParameters]);
const [parameter, setParameter] = useState("");
const changeCommandHandler = (e) => {
selectCommand(e.target.value);
};
const selectCommand = (cmd) => {
const params = getParameters(cmd);
setCommand(cmd);
setParameters(params);
setParameter(params[0].value);
};
const changeParametersHandler = (e) => {
setParameter(e.target.value);
};
const clickHandler = async (e) => {
try {
await sendCommand(vins, command, parameter, token);
if (vins.length === 1) {
setMessage(`Sent command to ${vins[0]}`);
} else {
setMessage(`Sent command to ${vins.length} cars`);
}
} catch (e) {
setMessage(e.message);
logger.error(e.stack);
}
};
const getParameters = (command) => {
for (let i = 0, len = commands.length; i < len; i += 1) {
const item = commands[i];
if (item.value === command) {
if (!item.parameters) {
break;
}
return item.parameters;
}
}
return [NoParameters];
};
useEffect(() => {
if (!commands || commands.length === 0) return;
selectCommand(commands[0].value);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div className={classes.form} style={{ marginTop: 20 }}>
<FormControl
className={classes.formControlInline}
variant="outlined"
size="small"
>
<InputLabel htmlFor="send-command" className={classes.whiteBackground}>
Command
</InputLabel>
<Select
native
value={command}
variant="outlined"
inputProps={{
name: "send-command",
id: "send-command",
}}
onChange={changeCommandHandler}
>
{commands.map((item, index) => (
<option key={index} value={item.value}>
{item.label}
</option>
))}
</Select>
</FormControl>
<FormControl
className={classes.formControlInline}
variant="outlined"
size="small"
>
<InputLabel
htmlFor="send-parameter"
className={classes.whiteBackground}
>
Parameter
</InputLabel>
<Select
native
value={parameter}
variant="outlined"
inputProps={{
name: "send-parameter",
id: "send-parameter",
}}
onChange={changeParametersHandler}
disabled={parameters.length === 0}
>
{parameters.map((item) => (
<option key={item.value} value={item.value}>
{item.label}
</option>
))}
</Select>
</FormControl>
<IconButton
color="primary"
aria-label="send command"
component="span"
onClick={clickHandler}
size="small"
disabled={busy || vins.length === 0}
>
<SendIcon fontSize="large" />
</IconButton>
</div>
);
};
SendCommand.propTypes = {
vins: PropTypes.array.isRequired,
};
export default SendCommand;

View File

@@ -3,8 +3,8 @@ import { useParams } from "react-router";
import clsx from "clsx";
import { Button, Grid, Typography } from "@material-ui/core";
import CarECUs from "../CarECUs";
import CarUpdates from "../CarUpdates";
import CarECUsTable from "../../Controls/CarECUsTable";
import CarUpdatesTable from "../../Controls/CarUpdatesTable";
import {
VehicleProvider,
useVehicleContext,
@@ -52,7 +52,7 @@ const MainForm = () => {
return (
<div className={clsx(classes.paper, classes.tableSize)}>
<Typography variant="h6">Car Updates</Typography>
<CarUpdates vin={vin} token={token} />
<CarUpdatesTable vin={vin} token={token} />
<Grid container className={classes.root} spacing={2}>
<Grid item md={4} className={classes.textJustifyAlign}></Grid>
<Grid item md={4} className={classes.textCenterAlign}>
@@ -73,7 +73,7 @@ const MainForm = () => {
</Button>
</Grid>
</Grid>
<CarECUs vin={vin} token={token} />
<CarECUsTable vin={vin} token={token} />
</div>
);
};

View File

@@ -0,0 +1,145 @@
import React, { useEffect } from "react";
import { useState } from "react";
import { useParams } from "react-router-dom";
import { Grid, TextField } from "@material-ui/core";
import CarUpdateStatusProgress from "../../Controls/CarUpdateStatusProgress";
import CarUpdateStatusTable from "../../Controls/CarUpdateStatusTable";
import {
CarUpdatesProvider,
useCarUpdatesContext,
} from "../../Contexts/CarUpdatesContext";
import { useStatusContext } from "../../Contexts/StatusContext";
import useStyles from "../../useStyles";
import { useUserContext } from "../../Contexts/UserContext";
import { logger } from "../../../services/monitoring";
import { LocalDateTimeString } from "../../../utils/dates";
const MainForm = () => {
const { vin, carupdateid } = useParams();
const [manifest, setManifest] = useState(null);
const [status, setStatus] = useState(null);
const { setTitle, setSitePath, setMessage } = useStatusContext();
const { getCarUpdates, carUpdates, startMonitor, stopMonitor } =
useCarUpdatesContext();
const {
token: {
idToken: { jwtToken: token },
},
} = useUserContext();
const classes = useStyles();
useEffect(() => {
(async () => {
try {
const result = await getCarUpdates({ id: carupdateid }, token);
if (!result.data && result.data.length === 0)
throw new Error(`error getting update ${carupdateid}`);
setManifest(result.data[0]["updatemanifest"]);
} catch (e) {
setMessage(e.message);
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (!manifest) return;
const title = `Vehicle ${vin}, Update ${manifest.name}`;
setTitle(title);
setSitePath([
{
label: "Vehicles",
link: "/vehicles",
},
{
label: `Vehicle ${vin} Details`,
link: `/vehicle-status/${vin}`,
},
{
label: title,
},
]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [manifest]);
useEffect(() => {
try {
if (carUpdates.length === 0) return;
setStatus(carUpdates[0]);
startMonitor(token);
} catch (e) {
setMessage(e.message);
logger.warn(e.stack);
}
return () => {
stopMonitor();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [carUpdates]);
return (
<div className={classes.root}>
<Grid container spacing={2}>
{manifest && (
<>
<Grid item md={4} className={classes.textJustifyAlign}>
<TextField
label="Name"
defaultValue={manifest.name}
className={classes.fullWidth}
InputProps={{
readOnly: true,
}}
/>
</Grid>
<Grid item md={4} className={classes.textCenterAlign}>
<TextField
label="Version"
defaultValue={manifest.version}
className={classes.fullWidth}
InputProps={{
readOnly: true,
}}
/>
</Grid>
<Grid item md={4} className={classes.textRightAlign}>
<TextField
label="Created"
defaultValue={LocalDateTimeString(manifest.created)}
className={classes.fullWidth}
InputProps={{
readOnly: true,
}}
/>
</Grid>
<Grid item md={12}>
<TextField
label="Description"
defaultValue={manifest.description}
className={classes.fullWidth}
InputProps={{
readOnly: true,
}}
/>
</Grid>
</>
)}
<Grid item md={12}>
<CarUpdateStatusProgress status={status} />
</Grid>
<Grid item md={12}>
<CarUpdateStatusTable carupdateid={carupdateid} token={token} />
</Grid>
</Grid>
</div>
);
};
const UpdateStatus = () => (
<CarUpdatesProvider>
<MainForm />
</CarUpdatesProvider>
);
export default UpdateStatus;