CEC-371 Update car ECUs display (#78)

* Clean up className styles
Update car status page to show update and ECUs

* Add update ecu version button
Show all ECUs on car status page
Only show car ecus for search
This commit is contained in:
John Wu
2021-08-18 09:14:13 -07:00
committed by GitHub
parent 3e66959521
commit d1815e2ff9
20 changed files with 1169 additions and 299 deletions

View File

@@ -750,7 +750,7 @@ exports[`App Route /carupdate-deploy authenticated 1`] = `
class="MuiGrid-root MuiGrid-item MuiGrid-grid-md-10"
>
<div
class="MuiFormControl-root makeStyles-margin-1195 makeStyles-textField-1196"
class="MuiFormControl-root makeStyles-margin-1195 makeStyles-fullWidth-1218"
>
<label
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated"
@@ -4233,7 +4233,7 @@ exports[`App Route /package-deploy authenticated 1`] = `
class="MuiGrid-root makeStyles-root-1464 MuiGrid-container MuiGrid-spacing-xs-2"
>
<div
class="MuiGrid-root MuiGrid-item MuiGrid-grid-md-2"
class="MuiGrid-root MuiGrid-item MuiGrid-grid-md-4"
>
<div
class="makeStyles-labelInline-1459"
@@ -4242,10 +4242,10 @@ exports[`App Route /package-deploy authenticated 1`] = `
</div>
</div>
<div
class="MuiGrid-root makeStyles-textCenterAlign-1499 MuiGrid-item MuiGrid-grid-md-8"
class="MuiGrid-root makeStyles-textCenterAlign-1499 MuiGrid-item MuiGrid-grid-md-4"
>
<div
class="MuiFormControl-root makeStyles-margin-1478 makeStyles-textField-1479"
class="MuiFormControl-root makeStyles-margin-1478 makeStyles-fullWidth-1501"
>
<label
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated"
@@ -4296,10 +4296,10 @@ exports[`App Route /package-deploy authenticated 1`] = `
</div>
</div>
<div
class="MuiGrid-root makeStyles-textRightAlign-1500 MuiGrid-item MuiGrid-grid-md-2"
class="MuiGrid-root makeStyles-textRightAlign-1500 MuiGrid-item MuiGrid-grid-md-4"
>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-formControl-1457 MuiButton-containedPrimary Mui-disabled Mui-disabled"
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-formControl-1457 makeStyles-textField-1479 MuiButton-containedPrimary Mui-disabled Mui-disabled"
disabled=""
tabindex="-1"
type="submit"
@@ -5595,7 +5595,7 @@ exports[`App Route /packages authenticated 1`] = `
class="MuiGrid-root makeStyles-textCenterAlign-1389 MuiGrid-item MuiGrid-grid-md-4"
>
<div
class="MuiFormControl-root makeStyles-margin-1368 makeStyles-textField-1369"
class="MuiFormControl-root makeStyles-margin-1368 makeStyles-fullWidth-1391"
>
<label
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated"
@@ -7300,11 +7300,15 @@ exports[`App Route /vehicle-status authenticated 1`] = `
data-testid="mocked-vehicleprovider"
>
<div
data-testid="mocked-updatesprovider"
class="makeStyles-paper-1005 makeStyles-tableSize-1056"
>
<h6
class="MuiTypography-root MuiTypography-h6"
>
Car Updates
</h6>
<div
class="makeStyles-paper-1005"
style="height: 700px; width: 100%;"
data-testid="mocked-updatesprovider"
>
<table
class="MuiTable-root"
@@ -7568,6 +7572,529 @@ exports[`App Route /vehicle-status authenticated 1`] = `
</tfoot>
</table>
</div>
<div
class="MuiGrid-root makeStyles-root-1016 MuiGrid-container MuiGrid-spacing-xs-2"
>
<div
class="MuiGrid-root makeStyles-textJustifyAlign-1050 MuiGrid-item MuiGrid-grid-md-4"
/>
<div
class="MuiGrid-root makeStyles-textCenterAlign-1051 MuiGrid-item MuiGrid-grid-md-4"
>
<h6
class="MuiTypography-root makeStyles-labelInline-1011 MuiTypography-h6"
>
Car ECUs
</h6>
</div>
<div
class="MuiGrid-root makeStyles-textRightAlign-1052 MuiGrid-item MuiGrid-grid-md-4"
>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-formControl-1009 makeStyles-textField-1031 MuiButton-containedPrimary"
tabindex="0"
type="submit"
>
<span
class="MuiButton-label"
>
Update
</span>
<span
class="MuiTouchRipple-root"
/>
</button>
</div>
</div>
<div
class="makeStyles-paper-1005 makeStyles-tableSize-1056"
>
<table
class="MuiTable-root"
>
<thead
class="MuiTableHead-root"
>
<tr
class="MuiTableRow-root MuiTableRow-head"
>
<th
aria-sort="descending"
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root MuiTableSortLabel-active"
role="button"
tabindex="0"
>
ECU
<span
class="makeStyles-hiddenSortSpan-1029"
>
sorted descending
</span>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionDesc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
SW Version
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
BL Version
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
HW Version
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Vendor
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Config
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Fingerprint
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Serial
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Created
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
<th
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
scope="col"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiTableSortLabel-root"
role="button"
tabindex="0"
>
Updated
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
/>
</svg>
</span>
</th>
</tr>
</thead>
<tbody
class="MuiTableBody-root"
>
<tr
class="MuiTableRow-root"
>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
>
ECUA
</td>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
>
SWVERSION
</td>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
>
BLVERSION
</td>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
>
HWVERSION
</td>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
>
VENDOR
</td>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
>
CONFIG
</td>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
>
FINGERPRINT
</td>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
>
SERIAL
</td>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
>
7/14/2021 8:09:40 PM
</td>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
>
7/14/2021 8:09:40 PM
</td>
</tr>
<tr
class="MuiTableRow-root"
>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
>
ECUB
</td>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
>
SWVERSION
</td>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
>
BLVERSION
</td>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
>
HWVERSION
</td>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
>
VENDOR
</td>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
>
CONFIG
</td>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
>
FINGERPRINT
</td>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
>
SERIAL
</td>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
>
7/14/2021 8:09:40 PM
</td>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
>
7/14/2021 8:09:40 PM
</td>
</tr>
</tbody>
<tfoot
class="MuiTableFooter-root"
>
<tr
class="MuiTableRow-root MuiTableRow-footer"
>
<td
class="MuiTableCell-root MuiTableCell-footer MuiTablePagination-root"
colspan="10"
>
<div
class="MuiToolbar-root MuiToolbar-regular MuiTablePagination-toolbar MuiToolbar-gutters"
>
<div
class="MuiTablePagination-spacer"
/>
<p
class="MuiTypography-root MuiTablePagination-caption MuiTypography-body2 MuiTypography-colorInherit"
id="mui-00000"
>
Rows per page:
</p>
<div
class="MuiInputBase-root MuiTablePagination-input MuiTablePagination-selectRoot"
>
<select
aria-label="rows per page"
class="MuiSelect-root MuiSelect-select MuiTablePagination-select MuiInputBase-input"
id="mui-00000"
>
<option
class="MuiTablePagination-menuItem"
value="5"
>
5
</option>
<option
class="MuiTablePagination-menuItem"
value="10"
>
10
</option>
<option
class="MuiTablePagination-menuItem"
value="25"
>
25
</option>
<option
class="MuiTablePagination-menuItem"
value="100"
>
100
</option>
</select>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSelect-icon MuiTablePagination-selectIcon"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M7 10l5 5 5-5z"
/>
</svg>
</div>
<p
class="MuiTypography-root MuiTablePagination-caption MuiTypography-body2 MuiTypography-colorInherit"
>
1-2 of 2
</p>
<div
class="MuiTablePagination-actions"
>
<button
aria-label="Previous page"
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit Mui-disabled Mui-disabled"
disabled=""
tabindex="-1"
title="Previous page"
type="button"
>
<span
class="MuiIconButton-label"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M15.41 16.09l-4.58-4.59 4.58-4.59L14 5.5l-6 6 6 6z"
/>
</svg>
</span>
</button>
<button
aria-label="Next page"
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit Mui-disabled Mui-disabled"
disabled=""
tabindex="-1"
title="Next page"
type="button"
>
<span
class="MuiIconButton-label"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z"
/>
</svg>
</span>
</button>
</div>
</div>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</main>
@@ -7942,7 +8469,7 @@ exports[`App Route /vehicles authenticated 1`] = `
class="MuiGrid-root makeStyles-textCenterAlign-988 MuiGrid-item MuiGrid-grid-md-4"
>
<div
class="MuiFormControl-root makeStyles-margin-967 makeStyles-textField-968"
class="MuiFormControl-root makeStyles-margin-967 makeStyles-fullWidth-990"
>
<label
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated"
@@ -8038,6 +8565,11 @@ exports[`App Route /vehicles authenticated 1`] = `
>
Close window
</option>
<option
value="ecu"
>
ECU Versions
</option>
<option
value="log"
>

View File

@@ -11,6 +11,7 @@ import {
TablePagination,
TableRow,
} from "@material-ui/core";
import clsx from "clsx";
import {
UpdatesProvider,
@@ -105,7 +106,7 @@ const MainForm = () => {
};
return (
<div className={`${classes.paper} ${classes.tableSize}`}>
<div className={clsx(classes.paper, classes.tableSize)}>
<Table>
<TableHead>
<TableRow>

View File

@@ -0,0 +1,175 @@
import React, { useEffect, useState } from "react";
import {
Table,
TableBody,
TableCell,
TableFooter,
TablePagination,
TableRow,
} from "@material-ui/core";
import clsx from "clsx";
import { LocalDateTimeString } from "../../../utils/dates";
import TableHeaderSortable from "../../Table/HeaderSortable";
import { useVehicleContext } from "../../Contexts/VehicleContext";
import { useStatusContext } from "../../Contexts/StatusContext";
import useStyles from "../../useStyles";
import { logger } from "../../../services/monitoring";
const tableColumns = [
{
id: "ecu",
label: "ECU",
},
{
id: "sw_version",
label: "SW Version",
},
{
id: "boot_loader_version",
label: "BL Version",
},
{
id: "hw_version",
label: "HW Version",
},
{
id: "vendor",
label: "Vendor",
},
{
id: "config",
label: "Config",
},
{
id: "fingerprint",
label: "Fingerprint",
},
{
id: "serial_number",
label: "Serial",
},
{
id: "created_at",
label: "Created",
},
{
id: "updated_at",
label: "Updated",
},
];
const CarECUs = ({ vin, token }) => {
const [ecus, setECUs] = useState([]);
const [total, setTotal] = useState(0);
const classes = useStyles();
const [pageSize, setPageSize] = useState(10);
const [pageIndex, setPageIndex] = useState(0);
const [orderBy, setOrderBy] = useState("ecu");
const [order, setOrder] = useState("desc");
const { getECUs } = useVehicleContext();
const { setMessage } = useStatusContext();
useEffect(() => {
(async () => {
try {
if (!vin || !token) return;
const result = await getECUs(
{
vin,
limit: pageSize,
offset: pageSize * pageIndex,
order: `${orderBy} ${order}`,
},
token
);
setECUs(result.data);
if (result.total > -1) setTotal(result.total);
} catch (e) {
console.log(e);
setMessage(e.message);
logger.warn(e.stack);
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [vin, token, pageIndex, pageSize, orderBy, order]);
const handleChangePageIndex = (event, newIndex) => {
setPageIndex(newIndex);
};
const handleChangePageSize = (event) => {
setPageSize(parseInt(event.target.value, 10));
setPageIndex(0);
};
const handleSort = (event, property) => {
try {
if (property === orderBy) {
if (order === "asc") {
setOrder("desc");
} else {
setOrder("asc");
}
} else {
setOrderBy(property);
setOrder("asc");
}
} catch (e) {
logger.warn(e.stack);
}
};
return (
<div className={clsx(classes.paper, classes.tableSize)}>
<Table>
<TableHeaderSortable
classes={classes}
orderBy={orderBy}
order={order}
columnData={tableColumns}
onSortRequest={handleSort}
/>
<TableBody>
{ecus.map((row) => (
<TableRow key={row.ecu}>
<TableCell align="center">{row.ecu}</TableCell>
<TableCell align="center">{row.sw_version}</TableCell>
<TableCell align="center">{row.boot_loader_version}</TableCell>
<TableCell align="center">{row.hw_version}</TableCell>
<TableCell align="center">{row.vendor}</TableCell>
<TableCell align="center">{row.config}</TableCell>
<TableCell align="center">{row.fingerprint}</TableCell>
<TableCell align="center">{row.serial_number}</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={10}
count={total}
rowsPerPage={pageSize}
page={pageIndex}
SelectProps={{
inputProps: { "aria-label": "rows per page" },
native: true,
}}
onPageChange={handleChangePageIndex}
onRowsPerPageChange={handleChangePageSize}
/>
</TableRow>
</TableFooter>
</Table>
</div>
);
};
export default CarECUs;

View File

@@ -10,6 +10,7 @@ import {
TablePagination,
TableRow,
} from "@material-ui/core";
import clsx from "clsx";
import { useVehicleContext } from "../../Contexts/VehicleContext";
import { useStatusContext } from "../../Contexts/StatusContext";
@@ -114,7 +115,7 @@ const CarSelectionTable = (props) => {
}, [pageIndex, pageSize, orderBy, order, search, token]);
return (
<div className={`${classes.paper} ${classes.tableSize}`}>
<div className={clsx(classes.paper, classes.tableSize)}>
<Table>
<TableHeaderSortable
classes={classes}
@@ -147,7 +148,11 @@ const CarSelectionTable = (props) => {
{row.ecu_list && (
<>
<br />
<ECUList list={row.ecu_list} search={searchTerm} />
<ECUList
list={row.ecu_list}
search={searchTerm}
searchedOnly={true}
/>
</>
)}
</TableCell>

View File

@@ -0,0 +1,159 @@
import React, { useEffect, useState } from "react";
import {
Table,
TableBody,
TableCell,
TableFooter,
TablePagination,
TableRow,
} from "@material-ui/core";
import { LocalDateTimeString } from "../../../utils/dates";
import TableHeaderSortable from "../../Table/HeaderSortable";
import {
UpdatesProvider,
useUpdatesContext,
} from "../../Contexts/UpdatesContext";
import { useStatusContext } from "../../Contexts/StatusContext";
import useStyles from "../../useStyles";
import { logger } from "../../../services/monitoring";
const tableColumns = [
{
id: "id",
label: "ID",
},
{
id: "update_package_id",
label: "Name",
},
{
id: "status",
label: "Status",
},
{
id: "created_at",
label: "Created",
},
{
id: "updated_at",
label: "Updated",
},
];
const MainForm = ({ vin, token }) => {
const classes = useStyles();
const [pageSize, setPageSize] = useState(10);
const [pageIndex, setPageIndex] = useState(0);
const [orderBy, setOrderBy] = useState("id");
const [order, setOrder] = useState("desc");
const { getCarUpdates, carUpdates, totalCarUpdates } = useUpdatesContext();
const { setMessage } = useStatusContext();
useEffect(() => {
(async () => {
try {
if (!vin || !token) return;
await getCarUpdates(
{
vin,
limit: pageSize,
offset: pageSize * pageIndex,
order: `${orderBy} ${order}`,
},
token
);
} catch (e) {
setMessage(e.message);
logger.warn(e.stack);
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [vin, token, pageIndex, pageSize, orderBy, order]);
const handleChangePageIndex = (event, newIndex) => {
setPageIndex(newIndex);
};
const handleChangePageSize = (event) => {
setPageSize(parseInt(event.target.value, 10));
setPageIndex(0);
};
const handleSort = (event, property) => {
try {
if (property === orderBy) {
if (order === "asc") {
setOrder("desc");
} else {
setOrder("asc");
}
} else {
setOrderBy(property);
setOrder("asc");
}
} catch (e) {
logger.warn(e.stack);
}
};
const updateName = (row) => {
if (row.updatepackage)
return `${row.updatepackage.package_name} ${row.updatepackage.version}`;
if (row.updatemanifest)
return `${row.updatemanifest.name} ${row.updatemanifest.version}`;
return "None";
};
return (
<Table>
<TableHeaderSortable
classes={classes}
orderBy={orderBy}
order={order}
columnData={tableColumns}
onSortRequest={handleSort}
/>
<TableBody>
{carUpdates.map((row) => (
<TableRow key={row.id}>
<TableCell align="center">{row.id}</TableCell>
<TableCell align="center">{updateName(row)}</TableCell>
<TableCell align="center">{row.status}</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={5}
count={totalCarUpdates}
rowsPerPage={pageSize}
page={pageIndex}
SelectProps={{
inputProps: { "aria-label": "rows per page" },
native: true,
}}
onPageChange={handleChangePageIndex}
onRowsPerPageChange={handleChangePageSize}
/>
</TableRow>
</TableFooter>
</Table>
);
};
const CarUpdates = (props) => (
<UpdatesProvider>
<MainForm {...props} />
</UpdatesProvider>
);
export default CarUpdates;

View File

@@ -75,7 +75,7 @@ const SendCommand = ({ vins }) => {
}, []);
return (
<div className={`${classes.form}`} style={{ marginTop: 20 }}>
<div className={classes.form} style={{ marginTop: 20 }}>
<FormControl
className={classes.formControlInline}
variant="outlined"

View File

@@ -2,6 +2,7 @@ import React, { useEffect, useState } from "react";
import { Grid } from "@material-ui/core";
import AddCircleIcon from "@material-ui/icons/AddCircle";
import { Link } from "react-router-dom";
import clsx from "clsx";
import { VehicleProvider } from "../../Contexts/VehicleContext";
import { useUserContext } from "../../Contexts/UserContext";
@@ -54,7 +55,7 @@ const MainForm = () => {
}, []);
return (
<div className={`${classes.paper} ${classes.tableSize}`}>
<div className={clsx(classes.paper, classes.tableSize)}>
<Grid container className={classes.root} spacing={2}>
<Grid item md={4} className={classes.textJustifyAlign}>
<Link to="/vehicle-add">

View File

@@ -1,63 +1,38 @@
import React, { useEffect, useState } from "react";
import React, { useEffect } from "react";
import { useParams } from "react-router";
import {
Table,
TableBody,
TableCell,
TableFooter,
TablePagination,
TableRow,
} from "@material-ui/core";
import clsx from "clsx";
import { Button, Grid, Typography } from "@material-ui/core";
import CarECUs from "../CarECUs";
import CarUpdates from "../CarUpdates";
import {
UpdatesProvider,
useUpdatesContext,
} from "../../Contexts/UpdatesContext";
import { VehicleProvider } from "../../Contexts/VehicleContext";
VehicleProvider,
useVehicleContext,
} from "../../Contexts/VehicleContext";
import { useUserContext } from "../../Contexts/UserContext";
import { useStatusContext } from "../../Contexts/StatusContext";
import useStyles from "../../useStyles";
import { LocalDateTimeString } from "../../../utils/dates";
import TableHeaderSortable from "../../Table/HeaderSortable";
import { logger } from "../../../services/monitoring";
const tableColumns = [
{
id: "id",
label: "ID",
},
{
id: "update_package_id",
label: "Name",
},
{
id: "status",
label: "Status",
},
{
id: "created_at",
label: "Created",
},
{
id: "updated_at",
label: "Updated",
},
];
const MainForm = () => {
const { vin } = useParams();
const classes = useStyles();
const [pageSize, setPageSize] = useState(10);
const [pageIndex, setPageIndex] = useState(0);
const [orderBy, setOrderBy] = useState("id");
const [order, setOrder] = useState("desc");
const { getCarUpdates, carUpdates, totalCarUpdates } = useUpdatesContext();
const { setMessage, setTitle, setSitePath } = useStatusContext();
const { setTitle, setSitePath, setMessage } = useStatusContext();
const { busy, sendCommand } = useVehicleContext();
const {
token: {
idToken: { jwtToken: token },
},
} = useUserContext();
const updateHandler = async (e) => {
try {
await sendCommand([vin], "ecu", "", token);
setMessage(`Sent command to ${vin}`);
} catch (e) {
setMessage(e.message);
logger.error(e.stack);
}
};
useEffect(() => {
const title = `Vehicle ${vin} Details`;
@@ -74,113 +49,39 @@ const MainForm = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [vin]);
useEffect(() => {
(async () => {
try {
await getCarUpdates(
{
vin,
limit: pageSize,
offset: pageSize * pageIndex,
order: `${orderBy} ${order}`,
},
token
);
} catch (e) {
setMessage(e.message);
logger.warn(e.stack);
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pageIndex, pageSize, token, orderBy, order]);
const handleChangePageIndex = (event, newIndex) => {
setPageIndex(newIndex);
};
const handleChangePageSize = (event) => {
setPageSize(parseInt(event.target.value, 10));
setPageIndex(0);
};
const handleSort = (event, property) => {
try {
if (property === orderBy) {
if (order === "asc") {
setOrder("desc");
} else {
setOrder("asc");
}
} else {
setOrderBy(property);
setOrder("asc");
}
} catch (e) {
logger.warn(e.stack);
}
};
const updateName = (row) => {
if (row.updatepackage)
return `${row.updatepackage.package_name} ${row.updatepackage.version}`;
if (row.updatemanifest)
return `${row.updatemanifest.name} ${row.updatemanifest.version}`;
return "None";
};
return (
<div className={classes.paper} style={{ height: 700, width: "100%" }}>
<Table>
<TableHeaderSortable
classes={classes}
orderBy={orderBy}
order={order}
columnData={tableColumns}
onSortRequest={handleSort}
/>
<TableBody>
{carUpdates.map((row) => (
<TableRow key={row.id}>
<TableCell align="center">{row.id}</TableCell>
<TableCell align="center">{updateName(row)}</TableCell>
<TableCell align="center">{row.status}</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={5}
count={totalCarUpdates}
rowsPerPage={pageSize}
page={pageIndex}
SelectProps={{
inputProps: { "aria-label": "rows per page" },
native: true,
}}
onPageChange={handleChangePageIndex}
onRowsPerPageChange={handleChangePageSize}
/>
</TableRow>
</TableFooter>
</Table>
<div className={clsx(classes.paper, classes.tableSize)}>
<Typography variant="h6">Car Updates</Typography>
<CarUpdates 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}>
<Typography variant="h6" className={classes.labelInline}>
Car ECUs
</Typography>
</Grid>
<Grid item md={4} className={classes.textRightAlign}>
<Button
type="submit"
disabled={busy}
variant="contained"
color="primary"
className={clsx(classes.formControl, classes.textField)}
onClick={updateHandler}
>
{busy ? "Sending..." : "Update"}
</Button>
</Grid>
</Grid>
<CarECUs vin={vin} token={token} />
</div>
);
};
const CarUpdates = () => (
const CarStatus = () => (
<VehicleProvider>
<UpdatesProvider>
<MainForm />
</UpdatesProvider>
<MainForm />
</VehicleProvider>
);
export default CarUpdates;
export default CarStatus;

View File

@@ -33,21 +33,19 @@ export const VehicleProvider = ({ children }) => {
const [models, setModels] = useState([]);
const [years, setYears] = useState([]);
const getVehicles = async (search, token) => {
const addConnections = async (cars, token) => {
try {
setBusy(true);
const result = await api.getVehicles(search, token);
const vins = cars.map((car) => car.vin);
const result = await api.getConnections(vins, token);
if (result.error) {
setVehicles([]);
throw new Error(`Get vehicles error. ${result.message}`);
throw new Error(`Add connections error. ${result.message}`);
}
await addConnections(result.data, token);
setVehicles(result.data);
if (result.total) {
setTotalVehicles(result.total);
}
} finally {
setBusy(false);
cars.forEach((car) => {
car.connected = result[car.vin] || false;
});
} catch (e) {
logger.error(e.stack);
}
};
@@ -63,6 +61,41 @@ export const VehicleProvider = ({ children }) => {
}
};
const getConnections = async (vins, token) => {
try {
setBusy(true);
const result = await api.getConnections(vins, token);
if (result.error)
throw new Error(`Get connections error. ${result.message}`);
return result;
} finally {
setBusy(false);
}
};
const getECUs = async (search, token) => {
try {
setBusy(true);
const result = await api.getECUs(search, token);
if (result.error) throw new Error(`Get ECUs error. ${result.message}`);
return result;
} finally {
setBusy(false);
}
};
const getLocations = async (token) => {
try {
setBusy(true);
const result = await api.getLocations(token);
if (result.error)
throw new Error(`Get locations error. ${result.message}`);
return result;
} finally {
setBusy(false);
}
};
const getModels = async (token) => {
try {
setBusy(true);
@@ -74,6 +107,35 @@ export const VehicleProvider = ({ children }) => {
}
};
const getState = async (token, vin) => {
try {
setBusy(true);
const result = await api.getState(token, vin);
if (result.error) throw new Error(`Get state error. ${result.message}`);
return result;
} finally {
setBusy(false);
}
};
const getVehicles = async (search, token) => {
try {
setBusy(true);
const result = await api.getVehicles(search, token);
if (result.error) {
setVehicles([]);
throw new Error(`Get vehicles error. ${result.message}`);
}
await addConnections(result.data, token);
setVehicles(result.data);
if (result.total) {
setTotalVehicles(result.total);
}
} finally {
setBusy(false);
}
};
const getYears = async (token) => {
try {
setBusy(true);
@@ -97,74 +159,23 @@ export const VehicleProvider = ({ children }) => {
}
};
const addConnections = async (cars, token) => {
try {
const vins = cars.map((car) => car.vin);
const result = await api.getConnections(vins, token);
if (result.error) {
throw new Error(`Add connections error. ${result.message}`);
}
cars.forEach((car) => {
car.connected = result[car.vin] || false;
});
} catch (e) {
logger.error(e.stack);
}
};
const getConnections = async (vins, token) => {
try {
setBusy(true);
const result = await api.getConnections(vins, token);
if (result.error)
throw new Error(`Get connections error. ${result.message}`);
return result;
} finally {
setBusy(false);
}
};
const getLocations = async (token) => {
try {
setBusy(true);
const result = await api.getLocations(token);
if (result.error)
throw new Error(`Get locations error. ${result.message}`);
return result;
} finally {
setBusy(false);
}
};
const getState = async (token, vin) => {
try {
setBusy(true);
const result = await api.getState(token, vin);
if (result.error)
throw new Error(`Get state error. ${result.message}`);
return result;
} finally {
setBusy(false);
}
};
return (
<VehicleContext.Provider
value={{
busy,
vehicles,
totalVehicles,
models,
years,
getVehicles,
totalVehicles,
addVehicle,
getModels,
getYears,
sendCommand,
getConnections,
getECUs,
getLocations,
getModels,
getState,
getYears,
getVehicles,
sendCommand,
}}
>
{children}

View File

@@ -17,19 +17,7 @@ export const useVehicleContext = () => ({
totalVehicles,
models,
years,
getVehicles: jest.fn(() => vehicles),
addVehicle: jest.fn(),
getModels: jest.fn(() => {
models = ["Ocean", "PEAR"];
}),
getYears: jest.fn(() => {
years = [2023, 2024];
}),
sendCommand: jest.fn((vins, command, parameters, token) => ({
vins,
command,
parameters,
})),
getConnections: jest.fn((vins, token) => {
const result = {};
@@ -39,9 +27,54 @@ export const useVehicleContext = () => ({
return result;
}),
getLocations: jest.fn().mockResolvedValue([
{ "altitude": 5, "longitude": 10, "latitude": 15, "vin": "TESTVIN123" },
])
getECUs: jest.fn(() => {
return {
data: [
{
boot_loader_version: "BLVERSION",
config: "CONFIG",
created: "2021-07-14T20:09:40.98187Z",
ecu: "ECUA",
fingerprint: "FINGERPRINT",
hw_version: "HWVERSION",
serial_number: "SERIAL",
sw_version: "SWVERSION",
updated: "2021-07-14T20:09:40.98187Z",
vendor: "VENDOR",
},
{
boot_loader_version: "BLVERSION",
config: "CONFIG",
created: "2021-07-14T20:09:40.98187Z",
ecu: "ECUB",
fingerprint: "FINGERPRINT",
hw_version: "HWVERSION",
serial_number: "SERIAL",
sw_version: "SWVERSION",
updated: "2021-07-14T20:09:40.98187Z",
vendor: "VENDOR",
},
],
total: 2,
};
}),
getModels: jest.fn(() => {
models = ["Ocean", "PEAR"];
}),
getLocations: jest
.fn()
.mockResolvedValue([
{ altitude: 5, longitude: 10, latitude: 15, vin: "TESTVIN123" },
]),
getVehicles: jest.fn(() => vehicles),
getYears: jest.fn(() => {
years = [2023, 2024];
}),
sendCommand: jest.fn((vins, command, parameters, token) => ({
vins,
command,
parameters,
})),
});
export const setBusy = (val) => {

View File

@@ -2,16 +2,17 @@ import React from "react";
import PropTypes from "prop-types";
import { Chip } from "@material-ui/core";
const ECUList = ({ list, delimiter, search }) => {
const ECUList = ({ list, delimiter, search, searchedOnly }) => {
if (!list) return null;
if (!delimiter) delimiter = ",";
const items = list.split(delimiter);
return items.map((item, index) => {
const match = search
? item.toLowerCase().split(" ").indexOf(search.toLowerCase())
: -1;
if (searchedOnly && match === -1) return null;
return (
<Chip
key={index}
@@ -29,6 +30,7 @@ ECUList.propTypes = {
list: PropTypes.string,
delimiter: PropTypes.string,
search: PropTypes.string,
searchedOnly: PropTypes.bool,
};
export default ECUList;

View File

@@ -30,7 +30,7 @@ const SearchField = (props) => {
};
return (
<FormControl className={clsx(classes.margin, classes.textField)}>
<FormControl className={clsx(classes.margin, classes.fullWidth)}>
<InputLabel htmlFor="search">Search</InputLabel>
<Input
id="search"

View File

@@ -52,9 +52,7 @@ export default function MenuDrawer({ children }) {
paper: classes.drawerPaper,
}}
>
<div
className={`${classes.drawerHeader} ${classes.drawerHeaderLogo}`}
>
<div className={clsx(classes.drawerHeader, classes.drawerHeaderLogo)}>
<img
src={logo}
alt="Fisker Admin Portal"

View File

@@ -1,6 +1,7 @@
import React, { useEffect, useState } from "react";
import { useParams, Redirect } from "react-router";
import { Button, Grid, Typography } from "@material-ui/core";
import clsx from "clsx";
import {
ManifestsProvider,
@@ -126,21 +127,21 @@ const MainForm = () => {
<form className={classes.form} noValidate action="{onSubmit}">
<Typography variant="body2">Created {createDate}.</Typography>
<Grid container className={classes.root} spacing={2}>
<Grid item md={2}>
<Grid item md={4}>
<div
className={classes.labelInline}
>{`${selected.length} Selected`}</div>
</Grid>
<Grid item md={8} className={classes.textCenterAlign}>
<Grid item md={4} className={classes.textCenterAlign}>
<SearchField classes={classes} onSearch={handleSearch} />
</Grid>
<Grid item md={2} className={classes.textRightAlign}>
<Grid item md={4} className={classes.textRightAlign}>
<Button
type="submit"
disabled={busy || selected.length === 0}
variant="contained"
color="primary"
className={classes.formControl}
className={clsx(classes.formControl, classes.textField)}
onClick={onSubmit}
>
{busy ? "Deploying..." : "Deploy"}

View File

@@ -14,6 +14,7 @@ import AddCircleIcon from "@material-ui/icons/AddCircle";
import SendIcon from "@material-ui/icons/Send";
import VisibilityIcon from "@material-ui/icons/Visibility";
import DeleteIcon from "@material-ui/icons/Delete";
import clsx from "clsx";
import {
useManifestsContext,
@@ -184,7 +185,7 @@ const MainForm = () => {
};
return (
<div className={`${classes.paper} ${classes.tableSize}`}>
<div className={clsx(classes.paper, classes.tableSize)}>
<Grid container className={classes.root} spacing={2}>
<Grid item md={4} className={classes.textJustifyAlign}>
<Link to="/package-create" className={classes.labelInline}>
@@ -213,7 +214,11 @@ const MainForm = () => {
{row.ecu_list && (
<>
<br />
<ECUList list={row.ecu_list} search={search} />
<ECUList
list={row.ecu_list}
search={search}
searchedOnly={true}
/>
</>
)}
</TableCell>

View File

@@ -11,6 +11,7 @@ import {
TablePagination,
TableRow,
} from "@material-ui/core";
import clsx from "clsx";
import {
ManifestsProvider,
@@ -119,7 +120,7 @@ const MainForm = () => {
};
return (
<div className={`${classes.paper} ${classes.tableSize}`}>
<div className={clsx(classes.paper, classes.tableSize)}>
<Table>
<TableHead>
<TableRow>

View File

@@ -1,5 +1,7 @@
import React, { useEffect } from "react";
import { Button } from "@material-ui/core";
import clsx from "clsx";
import { useUserContext } from "../Contexts/UserContext";
import useStyles from "../useStyles";
@@ -21,7 +23,7 @@ export default function SignInForm() {
}, []);
return (
<div className={`${classes.paper} ${classes.textJustifyAlign}`}>
<div className={clsx(classes.paper, classes.textJustifyAlign)}>
<Button
type="submit"
variant="contained"