Merge Development (#44)
* CEC-244 Remote car commands, search, sortable tables (#42) * Add sortable table header * Send bulk commands page Update table page sizes All tables are sortable * Update site layout Add search to update packages * Reenable Datadog * remove dev stuff * CEC-244 Add search (#43) * Add search to car send command page Add snapshot check
This commit is contained in:
1
src/assets/fisker-badge.svg
Normal file
1
src/assets/fisker-badge.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 48 KiB |
@@ -87,6 +87,10 @@ describe("App", () => {
|
|||||||
await check("/vehicle-status/FISKER123", "span.MuiButton-label", "Sign In");
|
await check("/vehicle-status/FISKER123", "span.MuiButton-label", "Sign In");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Route /vehicles-command unauthenticated", async () => {
|
||||||
|
await check("/vehicles-command", "span.MuiButton-label", "Sign In");
|
||||||
|
});
|
||||||
|
|
||||||
it("Route / authenticated", async () => {
|
it("Route / authenticated", async () => {
|
||||||
setToken(TEST_AUTH_OBJECT);
|
setToken(TEST_AUTH_OBJECT);
|
||||||
await check("/", "h1", "Welcome John!");
|
await check("/", "h1", "Welcome John!");
|
||||||
@@ -99,37 +103,42 @@ describe("App", () => {
|
|||||||
|
|
||||||
it("Route /package-upload authenticated", async () => {
|
it("Route /package-upload authenticated", async () => {
|
||||||
setToken(TEST_AUTH_OBJECT);
|
setToken(TEST_AUTH_OBJECT);
|
||||||
await check("/package-upload", "h1", "Create Update Package");
|
await check("/package-upload", "h6", "Create Update Package");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Route /vehicle-add authenticated", async () => {
|
it("Route /vehicle-add authenticated", async () => {
|
||||||
setToken(TEST_AUTH_OBJECT);
|
setToken(TEST_AUTH_OBJECT);
|
||||||
await check("/vehicle-add", "h1", "Add Vehicle");
|
await check("/vehicle-add", "h6", "Add Vehicle");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Route /updates authenticated", async () => {
|
it("Route /updates authenticated", async () => {
|
||||||
setToken(TEST_AUTH_OBJECT);
|
setToken(TEST_AUTH_OBJECT);
|
||||||
await check("/updates", "h1", "Update Packages");
|
await check("/updates", "h6", "Deploy Packages");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Route /update authenticated", async () => {
|
it("Route /update authenticated", async () => {
|
||||||
setToken(TEST_AUTH_OBJECT);
|
setToken(TEST_AUTH_OBJECT);
|
||||||
await check("/update/1", "h1", "Edit Update Package 1");
|
await check("/update/1", "h6", "Edit Update Package 1");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Route /carupdate-status authenticated", async () => {
|
it("Route /carupdate-status authenticated", async () => {
|
||||||
setToken(TEST_AUTH_OBJECT);
|
setToken(TEST_AUTH_OBJECT);
|
||||||
await check("/carupdate-status/1", "h1", "");
|
await check("/carupdate-status/1", "h6", "");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Route /vehicles authenticated", async () => {
|
it("Route /vehicles authenticated", async () => {
|
||||||
setToken(TEST_AUTH_OBJECT);
|
setToken(TEST_AUTH_OBJECT);
|
||||||
await check("/vehicles", "h1", "Vehicles");
|
await check("/vehicles", "h6", "Vehicles");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Route /vehicle-status authenticated", async () => {
|
it("Route /vehicle-status authenticated", async () => {
|
||||||
setToken(TEST_AUTH_OBJECT);
|
setToken(TEST_AUTH_OBJECT);
|
||||||
await check("/vehicle-status/FISKER123", "h1", "FISKER123 Updates");
|
await check("/vehicle-status/FISKER123", "h6", "Vehicle FISKER123 Details");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Route /vehicles-command authenticated", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT);
|
||||||
|
await check("/vehicles-command", "h6", "Send Command");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Route /page-not-found unauthenticated", async () => {
|
it("Route /page-not-found unauthenticated", async () => {
|
||||||
@@ -143,6 +152,6 @@ describe("App", () => {
|
|||||||
|
|
||||||
it("Route /carupdate-deploy authenticated", async () => {
|
it("Route /carupdate-deploy authenticated", async () => {
|
||||||
setToken(TEST_AUTH_OBJECT);
|
setToken(TEST_AUTH_OBJECT);
|
||||||
await check("/carupdate-deploy/1", "h1", "Deploy [1]");
|
await check("/carupdate-deploy/1", "h6", "Deploy ");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,6 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useParams, Redirect } from "react-router";
|
import { useParams, Redirect } from "react-router";
|
||||||
import { Button, TextField, Typography } from "@material-ui/core";
|
import { Button, Typography } from "@material-ui/core";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
UpdatesProvider,
|
UpdatesProvider,
|
||||||
useUpdatesContext,
|
useUpdatesContext,
|
||||||
@@ -20,12 +19,10 @@ const MainForm = () => {
|
|||||||
idToken: { jwtToken: token },
|
idToken: { jwtToken: token },
|
||||||
},
|
},
|
||||||
} = useUserContext();
|
} = useUserContext();
|
||||||
const { setMessage } = useStatusContext();
|
const { setMessage, setTitle } = useStatusContext();
|
||||||
const [packageName, setPackageName] = useState("");
|
const [packageName, setPackageName] = useState("");
|
||||||
const [version, setVersion] = useState("");
|
const [version, setVersion] = useState("");
|
||||||
const [link, setLink] = useState("");
|
|
||||||
const [description, setDescription] = useState("");
|
const [description, setDescription] = useState("");
|
||||||
const [releaseNotesLink, setReleaseNotesLink] = useState("");
|
|
||||||
const [createDate, setCreateDate] = useState("");
|
const [createDate, setCreateDate] = useState("");
|
||||||
const [selectedVehicles, setSelectedVehicles] = useState([]);
|
const [selectedVehicles, setSelectedVehicles] = useState([]);
|
||||||
const [redirect, setRedirect] = useState("");
|
const [redirect, setRedirect] = useState("");
|
||||||
@@ -60,15 +57,18 @@ const MainForm = () => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [token]);
|
}, [token]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTitle(`Deploy ${packageName} ${version}`);
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, [packageName, version]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!packages || packages.length === 0) return;
|
if (!packages || packages.length === 0) return;
|
||||||
var data = packages[0];
|
var data = packages[0];
|
||||||
|
|
||||||
setPackageName(data.package_name);
|
setPackageName(data.package_name);
|
||||||
setVersion(data.version);
|
setVersion(data.version);
|
||||||
setLink(data.link);
|
|
||||||
setDescription(data.desc || "");
|
setDescription(data.desc || "");
|
||||||
setReleaseNotesLink(data.release_notes || "");
|
|
||||||
setCreateDate(tsLocalDateTimeString(data.timestamp));
|
setCreateDate(tsLocalDateTimeString(data.timestamp));
|
||||||
}, [packages]);
|
}, [packages]);
|
||||||
|
|
||||||
@@ -78,64 +78,11 @@ const MainForm = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.paper}>
|
<div className={classes.paper}>
|
||||||
<Typography component="h1" variant="h5">
|
|
||||||
Deploy {`${packageName} ${version} [${packageid}]`}
|
|
||||||
</Typography>
|
|
||||||
<form className={classes.form} noValidate action="{onSubmit}">
|
<form className={classes.form} noValidate action="{onSubmit}">
|
||||||
<TextField
|
<Typography variant="body2">
|
||||||
label="Create Date"
|
Created {createDate}. {description}
|
||||||
variant="filled"
|
</Typography>
|
||||||
margin="normal"
|
<hr style={{ marginBottom: 30, marginTop: 30 }} />
|
||||||
inputProps={{
|
|
||||||
readOnly: true,
|
|
||||||
}}
|
|
||||||
value={createDate}
|
|
||||||
size="small"
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
id="link"
|
|
||||||
name="link"
|
|
||||||
label="Package link"
|
|
||||||
variant="filled"
|
|
||||||
margin="normal"
|
|
||||||
inputProps={{
|
|
||||||
readOnly: true,
|
|
||||||
}}
|
|
||||||
value={link}
|
|
||||||
size="small"
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
id="description"
|
|
||||||
name="description"
|
|
||||||
label="Description"
|
|
||||||
variant="filled"
|
|
||||||
margin="normal"
|
|
||||||
inputProps={{
|
|
||||||
readOnly: true,
|
|
||||||
}}
|
|
||||||
value={description}
|
|
||||||
size="small"
|
|
||||||
fullWidth
|
|
||||||
multiline
|
|
||||||
rows={4}
|
|
||||||
placeholder="Package description"
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
id="releasenotes"
|
|
||||||
name="releasenotes"
|
|
||||||
label="Release Notes URL"
|
|
||||||
variant="filled"
|
|
||||||
margin="normal"
|
|
||||||
inputProps={{
|
|
||||||
readOnly: true,
|
|
||||||
}}
|
|
||||||
value={releaseNotesLink}
|
|
||||||
size="small"
|
|
||||||
fullWidth
|
|
||||||
placeholder="Release Notes URL"
|
|
||||||
/>
|
|
||||||
<CarSelection onSelection={setSelectedVehicles} />
|
<CarSelection onSelection={setSelectedVehicles} />
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
TableHead,
|
TableHead,
|
||||||
TablePagination,
|
TablePagination,
|
||||||
TableRow,
|
TableRow,
|
||||||
Typography,
|
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -26,7 +25,7 @@ import VehicleStatus from "../../Cars/StatusModal";
|
|||||||
const MainForm = () => {
|
const MainForm = () => {
|
||||||
const { packageid } = useParams();
|
const { packageid } = useParams();
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [pageSize, setPageSize] = useState(25);
|
const [pageSize, setPageSize] = useState(10);
|
||||||
const [pageIndex, setPageIndex] = useState(0);
|
const [pageIndex, setPageIndex] = useState(0);
|
||||||
const [viewVIN, setViewVIN] = useState(null);
|
const [viewVIN, setViewVIN] = useState(null);
|
||||||
const {
|
const {
|
||||||
@@ -38,7 +37,7 @@ const MainForm = () => {
|
|||||||
startMonitor,
|
startMonitor,
|
||||||
stopMonitor,
|
stopMonitor,
|
||||||
} = useUpdatesContext();
|
} = useUpdatesContext();
|
||||||
const { setMessage } = useStatusContext();
|
const { setMessage, setTitle } = useStatusContext();
|
||||||
const {
|
const {
|
||||||
token: {
|
token: {
|
||||||
idToken: { jwtToken: token },
|
idToken: { jwtToken: token },
|
||||||
@@ -54,6 +53,13 @@ const MainForm = () => {
|
|||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
}, [token]);
|
}, [token]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!packages || packages.length === 0) return;
|
||||||
|
setTitle(`Package ${packages[0].package_name} ${packages[0].version}`);
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, [packages]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try {
|
try {
|
||||||
stopMonitor();
|
stopMonitor();
|
||||||
@@ -104,11 +110,6 @@ const MainForm = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.paper} style={{ height: 700, width: "100%" }}>
|
<div className={classes.paper} style={{ height: 700, width: "100%" }}>
|
||||||
<Typography component="h1" variant="h5">
|
|
||||||
{packages &&
|
|
||||||
packages.length > 0 &&
|
|
||||||
`${packages[0].package_name} ${packages[0].version}`}
|
|
||||||
</Typography>
|
|
||||||
<TableContainer>
|
<TableContainer>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
@@ -150,7 +151,7 @@ const MainForm = () => {
|
|||||||
<TableFooter>
|
<TableFooter>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TablePagination
|
<TablePagination
|
||||||
rowsPerPageOptions={[5, 10, 25]}
|
rowsPerPageOptions={[5, 10, 25, 100]}
|
||||||
colSpan={5}
|
colSpan={5}
|
||||||
count={totalCarUpdates}
|
count={totalCarUpdates}
|
||||||
rowsPerPage={pageSize}
|
rowsPerPage={pageSize}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useRef } from "react";
|
import React, { useEffect, useRef } from "react";
|
||||||
|
|
||||||
import useStyles from "../../useStyles";
|
import useStyles from "../../useStyles";
|
||||||
import {
|
import {
|
||||||
@@ -7,11 +7,11 @@ import {
|
|||||||
} from "../../Contexts/VehicleContext";
|
} from "../../Contexts/VehicleContext";
|
||||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
import { useUserContext } from "../../Contexts/UserContext";
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
import { Button, TextField, Typography } from "@material-ui/core";
|
import { Button, TextField } from "@material-ui/core";
|
||||||
|
|
||||||
const MainForm = () => {
|
const MainForm = () => {
|
||||||
const { addVehicle, busy } = useVehicleContext();
|
const { addVehicle, busy } = useVehicleContext();
|
||||||
const { setMessage } = useStatusContext();
|
const { setMessage, setTitle } = useStatusContext();
|
||||||
const {
|
const {
|
||||||
token: {
|
token: {
|
||||||
idToken: { jwtToken: token },
|
idToken: { jwtToken: token },
|
||||||
@@ -22,6 +22,10 @@ const MainForm = () => {
|
|||||||
const modelEl = useRef(null);
|
const modelEl = useRef(null);
|
||||||
const yearEl = useRef(null);
|
const yearEl = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTitle("Add Vehicle");
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, []);
|
||||||
const onSubmit = async (event) => {
|
const onSubmit = async (event) => {
|
||||||
try {
|
try {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -43,9 +47,6 @@ const MainForm = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.paper}>
|
<div className={classes.paper}>
|
||||||
<Typography component="h1" variant="h5">
|
|
||||||
Add Vehicle
|
|
||||||
</Typography>
|
|
||||||
<form className={classes.form} noValidate action="{onSubmit}">
|
<form className={classes.form} noValidate action="{onSubmit}">
|
||||||
<TextField
|
<TextField
|
||||||
id="vin"
|
id="vin"
|
||||||
|
|||||||
@@ -6,10 +6,9 @@ import {
|
|||||||
TableCell,
|
TableCell,
|
||||||
TableContainer,
|
TableContainer,
|
||||||
TableFooter,
|
TableFooter,
|
||||||
TableHead,
|
|
||||||
TablePagination,
|
TablePagination,
|
||||||
TableRow,
|
TableRow,
|
||||||
Typography,
|
Toolbar,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -20,25 +19,77 @@ import { useUserContext } from "../../Contexts/UserContext";
|
|||||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
import useStyles from "../../useStyles";
|
import useStyles from "../../useStyles";
|
||||||
import { LocalDateTimeString } from "../../../utils/dates";
|
import { LocalDateTimeString } from "../../../utils/dates";
|
||||||
|
import TableHeaderSortable from "../../Table/HeaderSortable";
|
||||||
|
import SearchField from "../../Controls/SearchField";
|
||||||
|
|
||||||
|
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 MainForm = () => {
|
const MainForm = () => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [pageSize, setPageSize] = useState(25);
|
const [pageSize, setPageSize] = useState(10);
|
||||||
const [pageIndex, setPageIndex] = useState(0);
|
const [pageIndex, setPageIndex] = useState(0);
|
||||||
|
const [orderBy, setOrderBy] = useState("vin");
|
||||||
|
const [order, setOrder] = useState("asc");
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
const { getVehicles, vehicles, totalVehicles } = useVehicleContext();
|
const { getVehicles, vehicles, totalVehicles } = useVehicleContext();
|
||||||
const { setMessage } = useStatusContext();
|
const { setMessage, setTitle } = useStatusContext();
|
||||||
const {
|
const {
|
||||||
token: {
|
token: {
|
||||||
idToken: { jwtToken: token },
|
idToken: { jwtToken: token },
|
||||||
},
|
},
|
||||||
} = useUserContext();
|
} = useUserContext();
|
||||||
|
|
||||||
|
const sortHandler = (event, property) => {
|
||||||
|
if (property === orderBy) {
|
||||||
|
if (order === "asc") {
|
||||||
|
setOrder("desc");
|
||||||
|
} else {
|
||||||
|
setOrder("asc");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setOrderBy(property);
|
||||||
|
setOrder("asc");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTitle("Vehicles");
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try {
|
try {
|
||||||
getVehicles(
|
getVehicles(
|
||||||
{
|
{
|
||||||
limit: pageSize,
|
limit: pageSize,
|
||||||
offset: pageSize * pageIndex,
|
offset: pageSize * pageIndex,
|
||||||
|
order: `${orderBy} ${order}`,
|
||||||
|
search,
|
||||||
},
|
},
|
||||||
token
|
token
|
||||||
);
|
);
|
||||||
@@ -46,7 +97,7 @@ const MainForm = () => {
|
|||||||
setMessage(e.message);
|
setMessage(e.message);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
}, [pageIndex, pageSize, token]);
|
}, [pageIndex, pageSize, token, orderBy, order, search]);
|
||||||
|
|
||||||
const handleChangePageIndex = (event, newIndex) => {
|
const handleChangePageIndex = (event, newIndex) => {
|
||||||
setPageIndex(newIndex);
|
setPageIndex(newIndex);
|
||||||
@@ -57,30 +108,33 @@ const MainForm = () => {
|
|||||||
setPageIndex(0);
|
setPageIndex(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSearch = (search) => {
|
||||||
|
setSearch(search);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.paper} style={{ height: 700, width: "100%" }}>
|
<div className={classes.paper} style={{ height: 700, width: "100%" }}>
|
||||||
<Typography component="h1" variant="h5">
|
<Toolbar className={classes.tableToolbar}>
|
||||||
Vehicles
|
<SearchField classes={classes} onSearch={handleSearch} />
|
||||||
</Typography>
|
</Toolbar>
|
||||||
<TableContainer>
|
<TableContainer>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHeaderSortable
|
||||||
<TableRow>
|
classes={classes}
|
||||||
<TableCell align="center">VIN</TableCell>
|
orderBy={orderBy}
|
||||||
<TableCell align="center">Model</TableCell>
|
order={order}
|
||||||
<TableCell align="center">Year</TableCell>
|
columnData={tableColumns}
|
||||||
<TableCell align="center">Created</TableCell>
|
onSortRequest={sortHandler}
|
||||||
<TableCell align="center">Updated</TableCell>
|
/>
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{vehicles.map((row) => (
|
{vehicles.map((row) => (
|
||||||
<TableRow key={row.vin}>
|
<TableRow key={row.vin}>
|
||||||
<TableCell align="center">
|
<TableCell align="center" sortDirection={true}>
|
||||||
<Link to={`/vehicle-status/${row.vin}`}>{row.vin}</Link>
|
<Link to={`/vehicle-status/${row.vin}`}>{row.vin}</Link>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell align="center">{row.model}</TableCell>
|
<TableCell align="center">{row.model}</TableCell>
|
||||||
<TableCell align="center">{row.year}</TableCell>
|
<TableCell align="center">{row.year}</TableCell>
|
||||||
|
<TableCell align="center">{row.trim || ""}</TableCell>
|
||||||
<TableCell align="center">
|
<TableCell align="center">
|
||||||
{LocalDateTimeString(row.created)}
|
{LocalDateTimeString(row.created)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
@@ -93,7 +147,7 @@ const MainForm = () => {
|
|||||||
<TableFooter>
|
<TableFooter>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TablePagination
|
<TablePagination
|
||||||
rowsPerPageOptions={[5, 10, 25]}
|
rowsPerPageOptions={[5, 10, 25, 100]}
|
||||||
colSpan={6}
|
colSpan={6}
|
||||||
count={totalVehicles}
|
count={totalVehicles}
|
||||||
rowsPerPage={pageSize}
|
rowsPerPage={pageSize}
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
import React, { useState } from "react";
|
|
||||||
import PropTypes from "prop-types";
|
|
||||||
import {
|
|
||||||
FormControlLabel,
|
|
||||||
Grid,
|
|
||||||
IconButton,
|
|
||||||
TextField,
|
|
||||||
Switch,
|
|
||||||
} from "@material-ui/core";
|
|
||||||
import SendIcon from "@material-ui/icons/Send";
|
|
||||||
|
|
||||||
import { useVehicleContext } from "../../Contexts/VehicleContext";
|
|
||||||
import { useUserContext } from "../../Contexts/UserContext";
|
|
||||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
|
||||||
|
|
||||||
const LogFilter = ({ vin }) => {
|
|
||||||
const { sendLogFilter, busy } = useVehicleContext();
|
|
||||||
const {
|
|
||||||
token: {
|
|
||||||
idToken: { jwtToken: token },
|
|
||||||
},
|
|
||||||
} = useUserContext();
|
|
||||||
const { setMessage } = useStatusContext();
|
|
||||||
const [filter, setFilter] = useState("");
|
|
||||||
const [enableLog, setEnableLog] = useState(true);
|
|
||||||
const [freq, setFreq] = useState(0);
|
|
||||||
|
|
||||||
const changeFilterHandler = (e) => {
|
|
||||||
setFilter(e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const changeStartHandler = (e) => {
|
|
||||||
setEnableLog(e.target.checked);
|
|
||||||
};
|
|
||||||
|
|
||||||
const changeFreqHandler = (e) => {
|
|
||||||
setFreq(e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const clickHandler = async (e) => {
|
|
||||||
try {
|
|
||||||
const data = {
|
|
||||||
enable: enableLog,
|
|
||||||
frequency: freq,
|
|
||||||
filter,
|
|
||||||
};
|
|
||||||
await sendLogFilter(vin, data, token);
|
|
||||||
setMessage(`Sent log command to ${vin}`);
|
|
||||||
} catch (e) {
|
|
||||||
setMessage(e.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Grid
|
|
||||||
container
|
|
||||||
direction="row"
|
|
||||||
justify="flex-start"
|
|
||||||
alignItems="center"
|
|
||||||
spacing={2}
|
|
||||||
>
|
|
||||||
<Grid item>
|
|
||||||
<FormControlLabel
|
|
||||||
control={
|
|
||||||
<Switch
|
|
||||||
checked={enableLog}
|
|
||||||
onChange={changeStartHandler}
|
|
||||||
name="logStart"
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
labelPlacement="top"
|
|
||||||
label="Logging"
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid item>
|
|
||||||
<TextField
|
|
||||||
id="log-filter"
|
|
||||||
name="log-filter"
|
|
||||||
label="Filter"
|
|
||||||
variant="outlined"
|
|
||||||
margin="normal"
|
|
||||||
inputProps={{
|
|
||||||
maxLength: "17",
|
|
||||||
}}
|
|
||||||
disabled={!enableLog}
|
|
||||||
value={filter}
|
|
||||||
onChange={changeFilterHandler}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid item>
|
|
||||||
<TextField
|
|
||||||
id="log-frequency"
|
|
||||||
label="Frequency"
|
|
||||||
type="number"
|
|
||||||
disabled={!enableLog}
|
|
||||||
InputLabelProps={{
|
|
||||||
shrink: true,
|
|
||||||
}}
|
|
||||||
value={freq}
|
|
||||||
onChange={changeFreqHandler}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid item>
|
|
||||||
<IconButton
|
|
||||||
color="primary"
|
|
||||||
aria-label="send log command"
|
|
||||||
component="span"
|
|
||||||
onClick={clickHandler}
|
|
||||||
disabled={busy}
|
|
||||||
>
|
|
||||||
<SendIcon fontSize="large" />
|
|
||||||
</IconButton>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
LogFilter.propTypes = {
|
|
||||||
vin: PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default LogFilter;
|
|
||||||
@@ -9,7 +9,7 @@ import useStyles from "../../useStyles";
|
|||||||
import { useUserContext } from "../../Contexts/UserContext";
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
|
|
||||||
const SendCommand = ({ vin }) => {
|
const SendCommand = ({ vins }) => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const { sendCommand, busy } = useVehicleContext();
|
const { sendCommand, busy } = useVehicleContext();
|
||||||
const {
|
const {
|
||||||
@@ -17,25 +17,59 @@ const SendCommand = ({ vin }) => {
|
|||||||
idToken: { jwtToken: token },
|
idToken: { jwtToken: token },
|
||||||
},
|
},
|
||||||
} = useUserContext();
|
} = useUserContext();
|
||||||
|
const NoParameters = {
|
||||||
|
value: "",
|
||||||
|
label: "None",
|
||||||
|
};
|
||||||
const { setMessage } = useStatusContext();
|
const { setMessage } = useStatusContext();
|
||||||
const [command, setCommand] = useState("");
|
const [command, setCommand] = useState("");
|
||||||
|
const [parameters, setParameters] = useState([NoParameters]);
|
||||||
|
const [parameter, setParameter] = useState("");
|
||||||
|
const changeCommandHandler = (e) => {
|
||||||
|
selectCommand(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
const changeHandler = (e) => {
|
const selectCommand = (cmd) => {
|
||||||
setCommand(e.target.value);
|
const params = getParameters(cmd);
|
||||||
|
setCommand(cmd);
|
||||||
|
setParameters(params);
|
||||||
|
setParameter(params[0].value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeParametersHandler = (e) => {
|
||||||
|
setParameter(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const clickHandler = async (e) => {
|
const clickHandler = async (e) => {
|
||||||
try {
|
try {
|
||||||
await sendCommand(vin, command, token);
|
await sendCommand(vins, command, parameter, token);
|
||||||
setMessage(`Sent command to ${vin}`);
|
if (vins.length === 1) {
|
||||||
|
setMessage(`Sent command to ${vins[0]}`);
|
||||||
|
} else {
|
||||||
|
setMessage(`Sent command to ${vins.length} cars`);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setMessage(e.message);
|
setMessage(e.message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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(() => {
|
useEffect(() => {
|
||||||
if (!commands || commands.length === 0) return;
|
if (!commands || commands.length === 0) return;
|
||||||
setCommand(commands[0].value);
|
selectCommand(commands[0].value);
|
||||||
|
// eslint-disable-next-line
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -52,7 +86,7 @@ const SendCommand = ({ vin }) => {
|
|||||||
name: "send-command",
|
name: "send-command",
|
||||||
id: "send-command",
|
id: "send-command",
|
||||||
}}
|
}}
|
||||||
onChange={changeHandler}
|
onChange={changeCommandHandler}
|
||||||
>
|
>
|
||||||
{commands.map((item) => (
|
{commands.map((item) => (
|
||||||
<option key={item.value} value={item.value}>
|
<option key={item.value} value={item.value}>
|
||||||
@@ -61,12 +95,37 @@ const SendCommand = ({ vin }) => {
|
|||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
<FormControl className={classes.formControlInline} variant="outlined">
|
||||||
|
<InputLabel
|
||||||
|
htmlFor="send-parameter"
|
||||||
|
style={{ backgroundColor: "White" }}
|
||||||
|
>
|
||||||
|
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
|
<IconButton
|
||||||
color="primary"
|
color="primary"
|
||||||
aria-label="send command"
|
aria-label="send command"
|
||||||
component="span"
|
component="span"
|
||||||
onClick={clickHandler}
|
onClick={clickHandler}
|
||||||
disabled={busy}
|
disabled={busy || vins.length === 0}
|
||||||
>
|
>
|
||||||
<SendIcon fontSize="large" />
|
<SendIcon fontSize="large" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@@ -75,7 +134,7 @@ const SendCommand = ({ vin }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
SendCommand.propTypes = {
|
SendCommand.propTypes = {
|
||||||
vin: PropTypes.string.isRequired,
|
vins: PropTypes.array.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SendCommand;
|
export default SendCommand;
|
||||||
|
|||||||
215
src/components/Cars/SendCommandBulk/index.jsx
Normal file
215
src/components/Cars/SendCommandBulk/index.jsx
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import {
|
||||||
|
Checkbox,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableFooter,
|
||||||
|
TablePagination,
|
||||||
|
TableRow,
|
||||||
|
Toolbar,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
|
||||||
|
import {
|
||||||
|
useVehicleContext,
|
||||||
|
VehicleProvider,
|
||||||
|
} 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 SendCommand from "../SendCommand";
|
||||||
|
import SearchField from "../../Controls/SearchField";
|
||||||
|
|
||||||
|
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 MainForm = () => {
|
||||||
|
const classes = useStyles();
|
||||||
|
const [pageSize, setPageSize] = useState(10);
|
||||||
|
const [pageIndex, setPageIndex] = useState(0);
|
||||||
|
const [orderBy, setOrderBy] = useState("vin");
|
||||||
|
const [order, setOrder] = useState("asc");
|
||||||
|
const [selected, setSelected] = useState([]);
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
const { getVehicles, vehicles, totalVehicles } = useVehicleContext();
|
||||||
|
const { setMessage, setTitle } = useStatusContext();
|
||||||
|
const {
|
||||||
|
token: {
|
||||||
|
idToken: { jwtToken: token },
|
||||||
|
},
|
||||||
|
} = useUserContext();
|
||||||
|
|
||||||
|
const sortHandler = (event, property) => {
|
||||||
|
if (property === orderBy) {
|
||||||
|
if (order === "asc") {
|
||||||
|
setOrder("desc");
|
||||||
|
} else {
|
||||||
|
setOrder("asc");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setOrderBy(property);
|
||||||
|
setOrder("asc");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTitle("Send Command");
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
getVehicles(
|
||||||
|
{
|
||||||
|
limit: pageSize,
|
||||||
|
offset: pageSize * pageIndex,
|
||||||
|
order: `${orderBy} ${order}`,
|
||||||
|
search,
|
||||||
|
},
|
||||||
|
token
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, [pageIndex, pageSize, token, orderBy, order, search]);
|
||||||
|
|
||||||
|
const handleChangePageIndex = (event, newIndex) => {
|
||||||
|
setPageIndex(newIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangePageSize = (event) => {
|
||||||
|
setPageSize(parseInt(event.target.value, 10));
|
||||||
|
setPageIndex(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectAll = (event) => {
|
||||||
|
const newSelected = [];
|
||||||
|
if (event.target.checked) {
|
||||||
|
vehicles.forEach((car) => {
|
||||||
|
newSelected.push(car.vin);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setSelected(newSelected);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelect = (event, key) => {
|
||||||
|
let newSelected;
|
||||||
|
if (event.target.checked) {
|
||||||
|
newSelected = [...selected];
|
||||||
|
newSelected.push(key);
|
||||||
|
} else {
|
||||||
|
newSelected = selected.filter((vin) => vin !== key);
|
||||||
|
}
|
||||||
|
setSelected(newSelected);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = (search) => {
|
||||||
|
setSelected([]);
|
||||||
|
setSearch(search);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.paper} style={{ height: 700, width: "100%" }}>
|
||||||
|
<Toolbar className={classes.tableToolbar}>
|
||||||
|
<SearchField classes={classes} onSearch={handleSearch} />
|
||||||
|
</Toolbar>
|
||||||
|
<TableContainer>
|
||||||
|
<Table>
|
||||||
|
<TableHeaderSortable
|
||||||
|
classes={classes}
|
||||||
|
orderBy={orderBy}
|
||||||
|
order={order}
|
||||||
|
columnData={tableColumns}
|
||||||
|
onSortRequest={sortHandler}
|
||||||
|
multiSelect={true}
|
||||||
|
onSelectAll={handleSelectAll}
|
||||||
|
selectCount={selected.length}
|
||||||
|
rowCount={totalVehicles}
|
||||||
|
/>
|
||||||
|
<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" sortDirection={true}>
|
||||||
|
<Link to={`/vehicle-status/${row.vin}`}>{row.vin}</Link>
|
||||||
|
</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,
|
||||||
|
}}
|
||||||
|
onChangePage={handleChangePageIndex}
|
||||||
|
onChangeRowsPerPage={handleChangePageSize}
|
||||||
|
/>
|
||||||
|
</TableRow>
|
||||||
|
</TableFooter>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
<SendCommand vins={selected} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const VehiclesList = () => (
|
||||||
|
<VehicleProvider>
|
||||||
|
<MainForm />
|
||||||
|
</VehicleProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default VehiclesList;
|
||||||
@@ -7,10 +7,8 @@ import {
|
|||||||
TableCell,
|
TableCell,
|
||||||
TableContainer,
|
TableContainer,
|
||||||
TableFooter,
|
TableFooter,
|
||||||
TableHead,
|
|
||||||
TablePagination,
|
TablePagination,
|
||||||
TableRow,
|
TableRow,
|
||||||
Typography,
|
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -23,22 +21,51 @@ import { useUserContext } from "../../Contexts/UserContext";
|
|||||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
import useStyles from "../../useStyles";
|
import useStyles from "../../useStyles";
|
||||||
import { LocalDateTimeString } from "../../../utils/dates";
|
import { LocalDateTimeString } from "../../../utils/dates";
|
||||||
|
import TableHeaderSortable from "../../Table/HeaderSortable";
|
||||||
import SendCommand from "../SendCommand";
|
import SendCommand from "../SendCommand";
|
||||||
import LogFilter from "../LogFilter";
|
|
||||||
|
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 MainForm = () => {
|
||||||
const { vin } = useParams();
|
const { vin } = useParams();
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [pageSize, setPageSize] = useState(10);
|
const [pageSize, setPageSize] = useState(10);
|
||||||
const [pageIndex, setPageIndex] = useState(0);
|
const [pageIndex, setPageIndex] = useState(0);
|
||||||
|
const [orderBy, setOrderBy] = useState("id");
|
||||||
|
const [order, setOrder] = useState("desc");
|
||||||
const { getCarUpdates, carUpdates, totalCarUpdates } = useUpdatesContext();
|
const { getCarUpdates, carUpdates, totalCarUpdates } = useUpdatesContext();
|
||||||
const { setMessage } = useStatusContext();
|
const { setMessage, setTitle } = useStatusContext();
|
||||||
const {
|
const {
|
||||||
token: {
|
token: {
|
||||||
idToken: { jwtToken: token },
|
idToken: { jwtToken: token },
|
||||||
},
|
},
|
||||||
} = useUserContext();
|
} = useUserContext();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTitle(`Vehicle ${vin} Details`);
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, [vin]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try {
|
try {
|
||||||
getCarUpdates(
|
getCarUpdates(
|
||||||
@@ -46,6 +73,7 @@ const MainForm = () => {
|
|||||||
vin,
|
vin,
|
||||||
limit: pageSize,
|
limit: pageSize,
|
||||||
offset: pageSize * pageIndex,
|
offset: pageSize * pageIndex,
|
||||||
|
order: `${orderBy} ${order}`,
|
||||||
},
|
},
|
||||||
token
|
token
|
||||||
);
|
);
|
||||||
@@ -53,7 +81,7 @@ const MainForm = () => {
|
|||||||
setMessage(e.message);
|
setMessage(e.message);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
}, [pageIndex, pageSize, token]);
|
}, [pageIndex, pageSize, token, orderBy, order]);
|
||||||
|
|
||||||
const handleChangePageIndex = (event, newIndex) => {
|
const handleChangePageIndex = (event, newIndex) => {
|
||||||
setPageIndex(newIndex);
|
setPageIndex(newIndex);
|
||||||
@@ -64,22 +92,30 @@ const MainForm = () => {
|
|||||||
setPageIndex(0);
|
setPageIndex(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSort = (event, property) => {
|
||||||
|
if (property === orderBy) {
|
||||||
|
if (order === "asc") {
|
||||||
|
setOrder("desc");
|
||||||
|
} else {
|
||||||
|
setOrder("asc");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setOrderBy(property);
|
||||||
|
setOrder("asc");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.paper} style={{ height: 700, width: "100%" }}>
|
<div className={classes.paper} style={{ height: 700, width: "100%" }}>
|
||||||
<Typography component="h1" variant="h5">
|
|
||||||
{vin} Updates
|
|
||||||
</Typography>
|
|
||||||
<TableContainer>
|
<TableContainer>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHeaderSortable
|
||||||
<TableRow>
|
classes={classes}
|
||||||
<TableCell align="center">ID</TableCell>
|
orderBy={orderBy}
|
||||||
<TableCell align="center">Update</TableCell>
|
order={order}
|
||||||
<TableCell align="center">Status</TableCell>
|
columnData={tableColumns}
|
||||||
<TableCell align="center">Created</TableCell>
|
onSortRequest={handleSort}
|
||||||
<TableCell align="center">Updated</TableCell>
|
/>
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{carUpdates.map((row) => (
|
{carUpdates.map((row) => (
|
||||||
<TableRow key={row.id}>
|
<TableRow key={row.id}>
|
||||||
@@ -98,7 +134,7 @@ const MainForm = () => {
|
|||||||
<TableFooter>
|
<TableFooter>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TablePagination
|
<TablePagination
|
||||||
rowsPerPageOptions={[5, 10, 25]}
|
rowsPerPageOptions={[5, 10, 25, 100]}
|
||||||
colSpan={5}
|
colSpan={5}
|
||||||
count={totalCarUpdates}
|
count={totalCarUpdates}
|
||||||
rowsPerPage={pageSize}
|
rowsPerPage={pageSize}
|
||||||
@@ -116,10 +152,7 @@ const MainForm = () => {
|
|||||||
</TableContainer>
|
</TableContainer>
|
||||||
<Grid container className={classes.root} spacing={2}>
|
<Grid container className={classes.root} spacing={2}>
|
||||||
<Grid item lg={6} md={12}>
|
<Grid item lg={6} md={12}>
|
||||||
<SendCommand vin={vin} />
|
<SendCommand vins={[vin]} />
|
||||||
</Grid>
|
|
||||||
<Grid item lg={6} md={12} style={{ textAlign: "right" }}>
|
|
||||||
<LogFilter vin={vin} />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,12 +4,15 @@ const StatusContext = React.createContext();
|
|||||||
|
|
||||||
export const StatusProvider = ({ children }) => {
|
export const StatusProvider = ({ children }) => {
|
||||||
const [message, setMessage] = useState(null);
|
const [message, setMessage] = useState(null);
|
||||||
|
const [title, setTitle] = useState("");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StatusContext.Provider
|
<StatusContext.Provider
|
||||||
value={{
|
value={{
|
||||||
message,
|
message,
|
||||||
setMessage,
|
setMessage,
|
||||||
|
title,
|
||||||
|
setTitle,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -84,10 +84,10 @@ export const VehicleProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const sendCommand = async (vin, command, token) => {
|
const sendCommand = async (vins, command, parameters, token) => {
|
||||||
try {
|
try {
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
const result = await api.sendCommand(vin, command, token);
|
const result = await api.sendCommand(vins, command, parameters, token);
|
||||||
if (result.error)
|
if (result.error)
|
||||||
throw new Error(`Send command error. ${result.message}`);
|
throw new Error(`Send command error. ${result.message}`);
|
||||||
return result;
|
return result;
|
||||||
@@ -96,18 +96,6 @@ export const VehicleProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const sendLogFilter = async (vin, filter, token) => {
|
|
||||||
try {
|
|
||||||
setBusy(true);
|
|
||||||
const result = await api.sendLog(vin, filter, token);
|
|
||||||
if (result.error)
|
|
||||||
throw new Error(`Send log filter error. ${result.message}`);
|
|
||||||
return result;
|
|
||||||
} finally {
|
|
||||||
setBusy(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VehicleContext.Provider
|
<VehicleContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@@ -121,7 +109,6 @@ export const VehicleProvider = ({ children }) => {
|
|||||||
getModels,
|
getModels,
|
||||||
getYears,
|
getYears,
|
||||||
sendCommand,
|
sendCommand,
|
||||||
sendLogFilter,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -25,13 +25,10 @@ export const useVehicleContext = () => ({
|
|||||||
getYears: jest.fn(() => {
|
getYears: jest.fn(() => {
|
||||||
years = [2023, 2024];
|
years = [2023, 2024];
|
||||||
}),
|
}),
|
||||||
sendCommand: jest.fn((vin, command, token) => ({
|
sendCommand: jest.fn((vin, command, parameters, token) => ({
|
||||||
vin,
|
vin,
|
||||||
command,
|
command,
|
||||||
})),
|
parameters,
|
||||||
sendLogFilter: jest.fn((vin, filter, token) => ({
|
|
||||||
vin,
|
|
||||||
filter,
|
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
49
src/components/Controls/SearchField/index.jsx
Normal file
49
src/components/Controls/SearchField/index.jsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
IconButton,
|
||||||
|
Input,
|
||||||
|
InputAdornment,
|
||||||
|
InputLabel,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
import SearchIcon from "@material-ui/icons/Search";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
const SearchField = (props) => {
|
||||||
|
const { classes, onSearch } = props;
|
||||||
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
const handleChange = (e) => {
|
||||||
|
setSearchTerm(e.target.value);
|
||||||
|
};
|
||||||
|
const handleSearch = (e) => {
|
||||||
|
if (!onSearch) return;
|
||||||
|
onSearch(searchTerm);
|
||||||
|
};
|
||||||
|
const handleEnterPress = (e) => {
|
||||||
|
if (e.keyCode !== 13) return;
|
||||||
|
e.preventDefault();
|
||||||
|
handleSearch(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControl className={clsx(classes.margin, classes.textField)}>
|
||||||
|
<InputLabel htmlFor="search">Search</InputLabel>
|
||||||
|
<Input
|
||||||
|
id="search"
|
||||||
|
type="text"
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={handleChange}
|
||||||
|
onKeyDown={handleEnterPress}
|
||||||
|
endAdornment={
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton aria-label="search" onClick={handleSearch}>
|
||||||
|
<SearchIcon />
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SearchField;
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import React from "react";
|
import React, { useEffect } from "react";
|
||||||
import { Typography } from "@material-ui/core";
|
import { Typography } from "@material-ui/core";
|
||||||
import useStyles from "../useStyles";
|
import useStyles from "../useStyles";
|
||||||
|
|
||||||
import { useUserContext } from "../Contexts/UserContext";
|
import { useUserContext } from "../Contexts/UserContext";
|
||||||
|
import { useStatusContext } from "../Contexts/StatusContext";
|
||||||
import { parsePayload } from "../../utils/jwt";
|
import { parsePayload } from "../../utils/jwt";
|
||||||
|
|
||||||
const DEFAULT_GREETING = "Welcome";
|
const DEFAULT_GREETING = "Welcome";
|
||||||
@@ -22,6 +23,12 @@ const Home = () => {
|
|||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const { token } = useUserContext();
|
const { token } = useUserContext();
|
||||||
const greeting = getGreeting(token);
|
const greeting = getGreeting(token);
|
||||||
|
const { setTitle } = useStatusContext();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTitle("");
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.paper}>
|
<div className={classes.paper}>
|
||||||
|
|||||||
@@ -1,34 +1,22 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { useTheme } from "@material-ui/core/styles";
|
|
||||||
import Drawer from "@material-ui/core/Drawer";
|
import Drawer from "@material-ui/core/Drawer";
|
||||||
import AppBar from "@material-ui/core/AppBar";
|
import AppBar from "@material-ui/core/AppBar";
|
||||||
import Toolbar from "@material-ui/core/Toolbar";
|
import Toolbar from "@material-ui/core/Toolbar";
|
||||||
import Typography from "@material-ui/core/Typography";
|
import Typography from "@material-ui/core/Typography";
|
||||||
import Divider from "@material-ui/core/Divider";
|
import Divider from "@material-ui/core/Divider";
|
||||||
import IconButton from "@material-ui/core/IconButton";
|
|
||||||
import MenuIcon from "@material-ui/icons/Menu";
|
|
||||||
import ChevronLeftIcon from "@material-ui/icons/ChevronLeft";
|
|
||||||
import ChevronRightIcon from "@material-ui/icons/ChevronRight";
|
|
||||||
|
|
||||||
import SideMenu from "./SideMenu";
|
import SideMenu from "./SideMenu";
|
||||||
import useStyles from "../useStyles";
|
import useStyles from "../useStyles";
|
||||||
import { useUserContext } from "../Contexts/UserContext";
|
import { useUserContext } from "../Contexts/UserContext";
|
||||||
|
import { useStatusContext } from "../Contexts/StatusContext";
|
||||||
import { Button, Container } from "@material-ui/core";
|
import { Button, Container } from "@material-ui/core";
|
||||||
|
import logo from "../../assets/fisker-badge.svg";
|
||||||
|
|
||||||
export default function MenuDrawer({ children }) {
|
export default function MenuDrawer({ children }) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const theme = useTheme();
|
const { title } = useStatusContext();
|
||||||
const { signOut, token } = useUserContext();
|
const { signOut, token } = useUserContext();
|
||||||
const [open, setOpen] = React.useState(true);
|
|
||||||
|
|
||||||
const handleDrawerOpen = () => {
|
|
||||||
setOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDrawerClose = () => {
|
|
||||||
setOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSignOut = () => {
|
const onSignOut = () => {
|
||||||
document.location = signOut();
|
document.location = signOut();
|
||||||
@@ -39,26 +27,12 @@ export default function MenuDrawer({ children }) {
|
|||||||
<AppBar
|
<AppBar
|
||||||
position="fixed"
|
position="fixed"
|
||||||
className={clsx(classes.appBar, {
|
className={clsx(classes.appBar, {
|
||||||
[classes.appBarShift]: open && token !== null,
|
[classes.appBarShift]: token !== null,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
{token !== null && (
|
|
||||||
<IconButton
|
|
||||||
color="inherit"
|
|
||||||
aria-label="open drawer"
|
|
||||||
onClick={handleDrawerOpen}
|
|
||||||
edge="start"
|
|
||||||
className={clsx(
|
|
||||||
classes.menuButton,
|
|
||||||
open && classes.hide && token !== null
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<MenuIcon />
|
|
||||||
</IconButton>
|
|
||||||
)}
|
|
||||||
<Typography variant="h6" noWrap>
|
<Typography variant="h6" noWrap>
|
||||||
Fisker OTA Portal
|
{title}
|
||||||
</Typography>
|
</Typography>
|
||||||
{token !== null && (
|
{token !== null && (
|
||||||
<Button
|
<Button
|
||||||
@@ -76,19 +50,17 @@ export default function MenuDrawer({ children }) {
|
|||||||
className={classes.drawer}
|
className={classes.drawer}
|
||||||
variant="persistent"
|
variant="persistent"
|
||||||
anchor="left"
|
anchor="left"
|
||||||
open={open}
|
open={true}
|
||||||
classes={{
|
classes={{
|
||||||
paper: classes.drawerPaper,
|
paper: classes.drawerPaper,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className={classes.drawerHeader}>
|
<div className={classes.drawerHeader}>
|
||||||
<IconButton onClick={handleDrawerClose}>
|
<img
|
||||||
{theme.direction === "ltr" ? (
|
src={logo}
|
||||||
<ChevronLeftIcon />
|
alt="Fisker Admin Portal"
|
||||||
) : (
|
className={classes.logo}
|
||||||
<ChevronRightIcon />
|
/>
|
||||||
)}
|
|
||||||
</IconButton>
|
|
||||||
</div>
|
</div>
|
||||||
<Divider />
|
<Divider />
|
||||||
<SideMenu />
|
<SideMenu />
|
||||||
@@ -96,7 +68,7 @@ export default function MenuDrawer({ children }) {
|
|||||||
)}
|
)}
|
||||||
<main
|
<main
|
||||||
className={clsx(classes.content, {
|
className={clsx(classes.content, {
|
||||||
[classes.contentShift]: open && token !== null,
|
[classes.contentShift]: token !== null,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div className={classes.drawerHeader} />
|
<div className={classes.drawerHeader} />
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const menuData = [
|
|||||||
roles: [],
|
roles: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "View Packages",
|
label: "Deploy Packages",
|
||||||
to: "/updates",
|
to: "/updates",
|
||||||
roles: [Roles.CREATE, Roles.READ],
|
roles: [Roles.CREATE, Roles.READ],
|
||||||
},
|
},
|
||||||
@@ -30,6 +30,11 @@ const menuData = [
|
|||||||
to: "/vehicle-add",
|
to: "/vehicle-add",
|
||||||
roles: [Roles.CREATE],
|
roles: [Roles.CREATE],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "Send Command",
|
||||||
|
to: "/vehicles-command",
|
||||||
|
roles: [Roles.CREATE],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function SideMenu() {
|
export default function SideMenu() {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ exports[`SideMenu Authenticated 1`] = `
|
|||||||
<span
|
<span
|
||||||
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
|
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
|
||||||
>
|
>
|
||||||
View Packages
|
Deploy Packages
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
@@ -118,6 +118,28 @@ exports[`SideMenu Authenticated 1`] = `
|
|||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
|
||||||
|
href="/vehicles-command"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiListItemText-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
|
||||||
|
>
|
||||||
|
Send Command
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const CarUpdatesDeploy = React.lazy(() => import("../CarUpdates/Deploy"));
|
|||||||
const CarUpdatesStatus = React.lazy(() => import("../CarUpdates/Status"));
|
const CarUpdatesStatus = React.lazy(() => import("../CarUpdates/Status"));
|
||||||
const CarUpdates = React.lazy(() => import("../Cars/Status"));
|
const CarUpdates = React.lazy(() => import("../Cars/Status"));
|
||||||
const VehiclesList = React.lazy(() => import("../Cars/List"));
|
const VehiclesList = React.lazy(() => import("../Cars/List"));
|
||||||
|
const SendCommandBulk = React.lazy(() => import("../Cars/SendCommandBulk"));
|
||||||
|
|
||||||
const SiteRoutes = () => {
|
const SiteRoutes = () => {
|
||||||
const { token, groups } = useUserContext();
|
const { token, groups } = useUserContext();
|
||||||
@@ -101,6 +102,14 @@ const SiteRoutes = () => {
|
|||||||
groups={groups}
|
groups={groups}
|
||||||
roles={[Roles.READ, Roles.CREATE]}
|
roles={[Roles.READ, Roles.CREATE]}
|
||||||
/>
|
/>
|
||||||
|
<AuthRoute
|
||||||
|
path="/vehicles-command"
|
||||||
|
render={() => <SendCommandBulk />}
|
||||||
|
type={TYPES.PROTECTED}
|
||||||
|
token={token}
|
||||||
|
groups={groups}
|
||||||
|
roles={[Roles.CREATE]}
|
||||||
|
/>
|
||||||
<PageNotFound />
|
<PageNotFound />
|
||||||
</Switch>
|
</Switch>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|||||||
101
src/components/Table/HeaderSortable/index.jsx
Normal file
101
src/components/Table/HeaderSortable/index.jsx
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import React from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import {
|
||||||
|
Checkbox,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
TableSortLabel,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
|
||||||
|
const HeaderSortable = (props) => {
|
||||||
|
const {
|
||||||
|
classes,
|
||||||
|
order,
|
||||||
|
orderBy,
|
||||||
|
onSortRequest,
|
||||||
|
columnData,
|
||||||
|
multiSelect,
|
||||||
|
onSelectAll,
|
||||||
|
selectCount,
|
||||||
|
rowCount,
|
||||||
|
} = props;
|
||||||
|
const sortHandler = (property) => (event) => {
|
||||||
|
if (!onSortRequest) return;
|
||||||
|
onSortRequest(event, property);
|
||||||
|
};
|
||||||
|
const selectAllHandler = (event) => {
|
||||||
|
if (!onSelectAll) return;
|
||||||
|
onSelectAll(event);
|
||||||
|
};
|
||||||
|
const ColumnLabel = (column) => {
|
||||||
|
if (column.id) {
|
||||||
|
return (
|
||||||
|
<TableSortLabel
|
||||||
|
active={orderBy === column.id}
|
||||||
|
direction={orderBy === column.id ? order : "asc"}
|
||||||
|
onClick={sortHandler(column.id)}
|
||||||
|
>
|
||||||
|
{column.label}
|
||||||
|
{orderBy === column.id ? (
|
||||||
|
<span className={classes.hiddenSortSpan}>
|
||||||
|
{order === "desc" ? "sorted descending" : "sorted ascending"}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</TableSortLabel>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return column.label;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (multiSelect) {
|
||||||
|
const errors = [];
|
||||||
|
if (onSelectAll === undefined) errors.push("onSelectAll required");
|
||||||
|
if (selectCount === undefined) errors.push("selectCount required");
|
||||||
|
if (rowCount === undefined) errors.push("rowCount required");
|
||||||
|
if (errors.length > 0) {
|
||||||
|
throw new Error(errors.join(". "));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
{multiSelect && (
|
||||||
|
<TableCell padding="checkbox">
|
||||||
|
<Checkbox
|
||||||
|
indeterminate={selectCount > 0 && selectCount < rowCount}
|
||||||
|
checked={rowCount > 0 && selectCount === rowCount}
|
||||||
|
onChange={selectAllHandler}
|
||||||
|
inputProps={{ "aria-label": "select all desserts" }}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
|
{columnData.map((column) => (
|
||||||
|
<TableCell
|
||||||
|
key={column.id}
|
||||||
|
align={column.numeric ? "right" : "center"}
|
||||||
|
padding={column.disablePadding ? "none" : "default"}
|
||||||
|
sortDirection={orderBy === column.id ? order : false}
|
||||||
|
>
|
||||||
|
{ColumnLabel(column)}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
HeaderSortable.propTypes = {
|
||||||
|
classes: PropTypes.object.isRequired,
|
||||||
|
onSortRequest: PropTypes.func.isRequired,
|
||||||
|
order: PropTypes.oneOf(["asc", "desc"]).isRequired,
|
||||||
|
orderBy: PropTypes.string.isRequired,
|
||||||
|
columnData: PropTypes.array.isRequired,
|
||||||
|
multiSelect: PropTypes.bool,
|
||||||
|
selectCount: PropTypes.number,
|
||||||
|
totalRows: PropTypes.number,
|
||||||
|
onSelectAll: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HeaderSortable;
|
||||||
@@ -8,11 +8,6 @@ exports[`File Upload Form Should render 1`] = `
|
|||||||
<div
|
<div
|
||||||
class="makeStyles-paper-3"
|
class="makeStyles-paper-3"
|
||||||
>
|
>
|
||||||
<h1
|
|
||||||
class="MuiTypography-root MuiTypography-h5"
|
|
||||||
>
|
|
||||||
Create Update Package
|
|
||||||
</h1>
|
|
||||||
<form
|
<form
|
||||||
action="{onSubmit}"
|
action="{onSubmit}"
|
||||||
class="makeStyles-form-5"
|
class="makeStyles-form-5"
|
||||||
@@ -51,10 +46,10 @@ exports[`File Upload Form Should render 1`] = `
|
|||||||
/>
|
/>
|
||||||
<fieldset
|
<fieldset
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="PrivateNotchedOutline-root-26 MuiOutlinedInput-notchedOutline"
|
class="PrivateNotchedOutline-root-31 MuiOutlinedInput-notchedOutline"
|
||||||
>
|
>
|
||||||
<legend
|
<legend
|
||||||
class="PrivateNotchedOutline-legendLabelled-28"
|
class="PrivateNotchedOutline-legendLabelled-33"
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
Package name
|
Package name
|
||||||
@@ -97,10 +92,10 @@ exports[`File Upload Form Should render 1`] = `
|
|||||||
/>
|
/>
|
||||||
<fieldset
|
<fieldset
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="PrivateNotchedOutline-root-26 MuiOutlinedInput-notchedOutline"
|
class="PrivateNotchedOutline-root-31 MuiOutlinedInput-notchedOutline"
|
||||||
>
|
>
|
||||||
<legend
|
<legend
|
||||||
class="PrivateNotchedOutline-legendLabelled-28"
|
class="PrivateNotchedOutline-legendLabelled-33"
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
Version
|
Version
|
||||||
@@ -143,10 +138,10 @@ exports[`File Upload Form Should render 1`] = `
|
|||||||
/>
|
/>
|
||||||
<fieldset
|
<fieldset
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="PrivateNotchedOutline-root-26 MuiOutlinedInput-notchedOutline"
|
class="PrivateNotchedOutline-root-31 MuiOutlinedInput-notchedOutline"
|
||||||
>
|
>
|
||||||
<legend
|
<legend
|
||||||
class="PrivateNotchedOutline-legendLabelled-28"
|
class="PrivateNotchedOutline-legendLabelled-33"
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
Description
|
Description
|
||||||
@@ -190,10 +185,10 @@ exports[`File Upload Form Should render 1`] = `
|
|||||||
/>
|
/>
|
||||||
<fieldset
|
<fieldset
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="PrivateNotchedOutline-root-26 MuiOutlinedInput-notchedOutline"
|
class="PrivateNotchedOutline-root-31 MuiOutlinedInput-notchedOutline"
|
||||||
>
|
>
|
||||||
<legend
|
<legend
|
||||||
class="PrivateNotchedOutline-legendLabelled-28"
|
class="PrivateNotchedOutline-legendLabelled-33"
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
Release Notes URL
|
Release Notes URL
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { Button, TextField, Typography } from "@material-ui/core";
|
import { Button, TextField } from "@material-ui/core";
|
||||||
import { DropzoneArea } from "material-ui-dropzone";
|
import { DropzoneArea } from "material-ui-dropzone";
|
||||||
import { useUserContext } from "../../Contexts/UserContext";
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
@@ -42,13 +42,19 @@ const FileUploadZone = ({ classes, token }) => {
|
|||||||
const MainForm = () => {
|
const MainForm = () => {
|
||||||
const { uploading, upload, files, cancel } = useFileUploadContext();
|
const { uploading, upload, files, cancel } = useFileUploadContext();
|
||||||
const { token } = useUserContext();
|
const { token } = useUserContext();
|
||||||
const { setMessage } = useStatusContext();
|
const { setMessage, setTitle } = useStatusContext();
|
||||||
const [redirect, setRedirect] = useState(null);
|
const [redirect, setRedirect] = useState(null);
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const packagenameEl = useRef(null);
|
const packagenameEl = useRef(null);
|
||||||
const versionEl = useRef(null);
|
const versionEl = useRef(null);
|
||||||
const descEl = useRef(null);
|
const descEl = useRef(null);
|
||||||
const releasenotesEl = useRef(null);
|
const releasenotesEl = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTitle("Create Update Package");
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, []);
|
||||||
|
|
||||||
const onSubmit = async (event) => {
|
const onSubmit = async (event) => {
|
||||||
try {
|
try {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -79,9 +85,6 @@ const MainForm = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.paper}>
|
<div className={classes.paper}>
|
||||||
<Typography component="h1" variant="h5">
|
|
||||||
Create Update Package
|
|
||||||
</Typography>
|
|
||||||
<form className={classes.form} noValidate action="{onSubmit}">
|
<form className={classes.form} noValidate action="{onSubmit}">
|
||||||
<TextField
|
<TextField
|
||||||
id="packagename"
|
id="packagename"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useParams } from "react-router";
|
import { useParams } from "react-router";
|
||||||
import { Button, TextField, Typography } from "@material-ui/core";
|
import { Button, TextField } from "@material-ui/core";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
UpdatesProvider,
|
UpdatesProvider,
|
||||||
@@ -19,7 +19,7 @@ const MainForm = () => {
|
|||||||
idToken: { jwtToken: token },
|
idToken: { jwtToken: token },
|
||||||
},
|
},
|
||||||
} = useUserContext();
|
} = useUserContext();
|
||||||
const { setMessage } = useStatusContext();
|
const { setMessage, setTitle } = useStatusContext();
|
||||||
const [packageName, setPackageName] = useState("");
|
const [packageName, setPackageName] = useState("");
|
||||||
const [version, setVersion] = useState("");
|
const [version, setVersion] = useState("");
|
||||||
const [link, setLink] = useState("");
|
const [link, setLink] = useState("");
|
||||||
@@ -68,10 +68,16 @@ const MainForm = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTitle(`Edit Update Package ${id}`);
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getData();
|
getData();
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [token]);
|
}, [token]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!packages || packages.length === 0) return;
|
if (!packages || packages.length === 0) return;
|
||||||
var data = packages[0];
|
var data = packages[0];
|
||||||
@@ -86,9 +92,6 @@ const MainForm = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.paper}>
|
<div className={classes.paper}>
|
||||||
<Typography component="h1" variant="h5">
|
|
||||||
Edit Update Package {id}
|
|
||||||
</Typography>
|
|
||||||
<form className={classes.form} noValidate action="{onSubmit}">
|
<form className={classes.form} noValidate action="{onSubmit}">
|
||||||
<TextField
|
<TextField
|
||||||
label="Create Date"
|
label="Create Date"
|
||||||
|
|||||||
@@ -6,13 +6,11 @@ import {
|
|||||||
TableCell,
|
TableCell,
|
||||||
TableContainer,
|
TableContainer,
|
||||||
TableFooter,
|
TableFooter,
|
||||||
TableHead,
|
|
||||||
TablePagination,
|
TablePagination,
|
||||||
TableRow,
|
TableRow,
|
||||||
|
Toolbar,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Typography,
|
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import EditIcon from "@material-ui/icons/Edit";
|
|
||||||
import SendIcon from "@material-ui/icons/Send";
|
import SendIcon from "@material-ui/icons/Send";
|
||||||
import VisibilityIcon from "@material-ui/icons/Visibility";
|
import VisibilityIcon from "@material-ui/icons/Visibility";
|
||||||
import useStyles from "../../useStyles";
|
import useStyles from "../../useStyles";
|
||||||
@@ -21,13 +19,42 @@ import {
|
|||||||
useUpdatesContext,
|
useUpdatesContext,
|
||||||
} from "../../Contexts/UpdatesContext";
|
} from "../../Contexts/UpdatesContext";
|
||||||
import { useUserContext } from "../../Contexts/UserContext";
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
import { tsLocalDateTimeString } from "../../../utils/dates";
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
|
import { LocalDateTimeString } from "../../../utils/dates";
|
||||||
import { Roles, hasRole } from "../../../utils/roles";
|
import { Roles, hasRole } from "../../../utils/roles";
|
||||||
|
import TableHeaderSortable from "../../Table/HeaderSortable";
|
||||||
|
import SearchField from "../../Controls/SearchField";
|
||||||
|
|
||||||
|
const tableColumns = [
|
||||||
|
{
|
||||||
|
id: "id",
|
||||||
|
label: "ID",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "package_name",
|
||||||
|
label: "Name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "version",
|
||||||
|
label: "Version",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "created_at",
|
||||||
|
label: "Created",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "",
|
||||||
|
label: "Actions",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const UpdatePackagesList = () => {
|
const UpdatePackagesList = () => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [pageSize, setPageSize] = useState(25);
|
const [pageSize, setPageSize] = useState(10);
|
||||||
const [pageIndex, setPageIndex] = useState(0);
|
const [pageIndex, setPageIndex] = useState(0);
|
||||||
|
const [orderBy, setOrderBy] = useState("id");
|
||||||
|
const [order, setOrder] = useState("desc");
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
const { getPackages, packages, totalPackages } = useUpdatesContext();
|
const { getPackages, packages, totalPackages } = useUpdatesContext();
|
||||||
const {
|
const {
|
||||||
token: {
|
token: {
|
||||||
@@ -35,11 +62,25 @@ const UpdatePackagesList = () => {
|
|||||||
},
|
},
|
||||||
groups,
|
groups,
|
||||||
} = useUserContext();
|
} = useUserContext();
|
||||||
|
const { setTitle } = useStatusContext();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getPackages({ limit: pageSize, offset: pageSize * pageIndex }, token);
|
setTitle("Deploy Packages");
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
}, [pageIndex, pageSize, token]);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getPackages(
|
||||||
|
{
|
||||||
|
limit: pageSize,
|
||||||
|
offset: pageSize * pageIndex,
|
||||||
|
order: `${orderBy} ${order}`,
|
||||||
|
search,
|
||||||
|
},
|
||||||
|
token
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, [pageIndex, pageSize, token, orderBy, order, search]);
|
||||||
|
|
||||||
const handleChangePageIndex = (event, newIndex) => {
|
const handleChangePageIndex = (event, newIndex) => {
|
||||||
setPageIndex(newIndex);
|
setPageIndex(newIndex);
|
||||||
@@ -50,6 +91,23 @@ const UpdatePackagesList = () => {
|
|||||||
setPageIndex(0);
|
setPageIndex(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSort = (event, property) => {
|
||||||
|
if (property === orderBy) {
|
||||||
|
if (order === "asc") {
|
||||||
|
setOrder("desc");
|
||||||
|
} else {
|
||||||
|
setOrder("asc");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setOrderBy(property);
|
||||||
|
setOrder("asc");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearch = (search) => {
|
||||||
|
setSearch(search);
|
||||||
|
};
|
||||||
|
|
||||||
const Actions = (row) => {
|
const Actions = (row) => {
|
||||||
let actions = [];
|
let actions = [];
|
||||||
if (hasRole([Roles.CREATE, Roles.READ], groups)) {
|
if (hasRole([Roles.CREATE, Roles.READ], groups)) {
|
||||||
@@ -65,13 +123,6 @@ const UpdatePackagesList = () => {
|
|||||||
}
|
}
|
||||||
if (hasRole([Roles.CREATE], groups)) {
|
if (hasRole([Roles.CREATE], groups)) {
|
||||||
actions = actions.concat([
|
actions = actions.concat([
|
||||||
{
|
|
||||||
tip: `Edit "${row.package_name} ${row.version}"`,
|
|
||||||
link: `/update/${row.id}`,
|
|
||||||
icon: (
|
|
||||||
<EditIcon aria-label={`Edit ${row.package_name} ${row.version}`} />
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
tip: `Deploy "${row.package_name} ${row.version}"`,
|
tip: `Deploy "${row.package_name} ${row.version}"`,
|
||||||
link: `/carupdate-deploy/${row.id}`,
|
link: `/carupdate-deploy/${row.id}`,
|
||||||
@@ -97,20 +148,18 @@ const UpdatePackagesList = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.paper} style={{ height: 700, width: "100%" }}>
|
<div className={classes.paper} style={{ height: 700, width: "100%" }}>
|
||||||
<Typography component="h1" variant="h5">
|
<Toolbar className={classes.tableToolbar}>
|
||||||
Update Packages
|
<SearchField classes={classes} onSearch={handleSearch} />
|
||||||
</Typography>
|
</Toolbar>
|
||||||
<TableContainer>
|
<TableContainer>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHeaderSortable
|
||||||
<TableRow>
|
classes={classes}
|
||||||
<TableCell align="center">ID</TableCell>
|
orderBy={orderBy}
|
||||||
<TableCell align="center">Name</TableCell>
|
order={order}
|
||||||
<TableCell align="center">Version</TableCell>
|
columnData={tableColumns}
|
||||||
<TableCell align="center">Created</TableCell>
|
onSortRequest={handleSort}
|
||||||
<TableCell align="right">Actions</TableCell>
|
/>
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{packages.map((row) => (
|
{packages.map((row) => (
|
||||||
<TableRow key={row.id}>
|
<TableRow key={row.id}>
|
||||||
@@ -118,16 +167,16 @@ const UpdatePackagesList = () => {
|
|||||||
<TableCell align="center">{row.package_name}</TableCell>
|
<TableCell align="center">{row.package_name}</TableCell>
|
||||||
<TableCell align="center">{row.version}</TableCell>
|
<TableCell align="center">{row.version}</TableCell>
|
||||||
<TableCell align="center">
|
<TableCell align="center">
|
||||||
{tsLocalDateTimeString(row.timestamp)}
|
{LocalDateTimeString(row.created)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell align="right">{Actions(row)}</TableCell>
|
<TableCell align="center">{Actions(row)}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
<TableFooter>
|
<TableFooter>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TablePagination
|
<TablePagination
|
||||||
rowsPerPageOptions={[5, 10, 25]}
|
rowsPerPageOptions={[5, 10, 25, 100]}
|
||||||
colSpan={5}
|
colSpan={5}
|
||||||
count={totalPackages}
|
count={totalPackages}
|
||||||
rowsPerPage={pageSize}
|
rowsPerPage={pageSize}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
padding: theme.spacing(2, 4, 3),
|
padding: theme.spacing(2, 4, 3),
|
||||||
},
|
},
|
||||||
paper: {
|
paper: {
|
||||||
marginTop: theme.spacing(8),
|
marginTop: theme.spacing(1),
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
@@ -105,7 +105,7 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
padding: theme.spacing(0, 1),
|
padding: theme.spacing(0, 1),
|
||||||
// necessary for content to be below app bar
|
// necessary for content to be below app bar
|
||||||
...theme.mixins.toolbar,
|
...theme.mixins.toolbar,
|
||||||
justifyContent: "flex-end",
|
justifyContent: "flex-start",
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
@@ -134,6 +134,34 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
textDecorationLine: "underline",
|
textDecorationLine: "underline",
|
||||||
color: "Blue",
|
color: "Blue",
|
||||||
},
|
},
|
||||||
|
hiddenSortSpan: {
|
||||||
|
border: 0,
|
||||||
|
clip: "rect(0 0 0 0)",
|
||||||
|
height: 1,
|
||||||
|
margin: -1,
|
||||||
|
overflow: "hidden",
|
||||||
|
padding: 0,
|
||||||
|
position: "absolute",
|
||||||
|
top: 20,
|
||||||
|
width: 1,
|
||||||
|
},
|
||||||
|
margin: {
|
||||||
|
margin: theme.spacing(1),
|
||||||
|
},
|
||||||
|
textField: {
|
||||||
|
width: "25ch",
|
||||||
|
},
|
||||||
|
tableToolbar: {
|
||||||
|
textAlign: "left",
|
||||||
|
paddingLeft: theme.spacing(2),
|
||||||
|
paddingRight: theme.spacing(1),
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
logo: {
|
||||||
|
height: 60,
|
||||||
|
width: 60,
|
||||||
|
margin: "auto",
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default useStyles;
|
export default useStyles;
|
||||||
|
|||||||
@@ -25,14 +25,11 @@ const vehiclesAPI = {
|
|||||||
data: [2021, 2022],
|
data: [2021, 2022],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
sendCommand: async (vin, command, token) => {
|
sendCommand: async (vin, command, parameters, token) => {
|
||||||
return {
|
return {
|
||||||
vin, command,
|
vin, command, parameters
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
sendLog: async (vin, filter, token) => {
|
|
||||||
return Object.assign(filter, {vin});
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default vehiclesAPI;
|
export default vehiclesAPI;
|
||||||
|
|||||||
@@ -1,41 +1,68 @@
|
|||||||
|
const LockUnlockParams = [
|
||||||
|
{
|
||||||
|
value: "LOCK",
|
||||||
|
label: "Lock",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "UNLOCK",
|
||||||
|
label: "Unlock",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const OpenCloseParams = [
|
||||||
|
{
|
||||||
|
value: "OPEN",
|
||||||
|
label: "Open",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "CLOSE",
|
||||||
|
label: "Close",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const Commands = [{
|
const Commands = [{
|
||||||
value: "LFRD",
|
value: "LOG",
|
||||||
label: "Lock front right door",
|
label: "Log level",
|
||||||
|
parameters: [
|
||||||
|
{
|
||||||
|
value: "INFO",
|
||||||
|
label: "Info",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "DEBUG",
|
||||||
|
label: "Debug",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "TRACE",
|
||||||
|
label: "Trace",
|
||||||
|
},
|
||||||
|
],
|
||||||
},{
|
},{
|
||||||
value: "UFRD",
|
value: "FRONT-RIGHT",
|
||||||
label: "Unlock front right door",
|
label: "Front right door",
|
||||||
|
parameters: LockUnlockParams,
|
||||||
},{
|
},{
|
||||||
value: "LFLD",
|
value: "FRONT-LEFT",
|
||||||
label: "Lock front left door",
|
label: "Front left door",
|
||||||
|
parameters: LockUnlockParams,
|
||||||
},{
|
},{
|
||||||
value: "UFLD",
|
value: "REAR-RIGHT",
|
||||||
label: "Unlock front left door",
|
label: "Rear right door",
|
||||||
|
parameters: LockUnlockParams,
|
||||||
},{
|
},{
|
||||||
value: "LRRD",
|
value: "REAR-LEFT",
|
||||||
label: "Lock rear right door",
|
label: "Rear left door",
|
||||||
|
parameters: LockUnlockParams,
|
||||||
},{
|
},{
|
||||||
value: "URRD",
|
value: "TRUNK",
|
||||||
label: "Unlock rear right door",
|
label: "Trunk",
|
||||||
|
parameters: LockUnlockParams,
|
||||||
},{
|
},{
|
||||||
value: "LRLD",
|
value: "WINDOWS",
|
||||||
label: "Lock rear left door",
|
label: "Windows",
|
||||||
|
parameters: OpenCloseParams,
|
||||||
},{
|
},{
|
||||||
value: "URLD",
|
value: "FLASH-HEADLIGHTS",
|
||||||
label: "Unlock rear left door",
|
|
||||||
},{
|
|
||||||
value: "LTRK",
|
|
||||||
label: "Lock trunk",
|
|
||||||
},{
|
|
||||||
value: "UTRK",
|
|
||||||
label: "Unlock trunk",
|
|
||||||
},{
|
|
||||||
value: "OWIN",
|
|
||||||
label: "Open Windows",
|
|
||||||
},{
|
|
||||||
value: "CWIN",
|
|
||||||
label: "Close Windows",
|
|
||||||
},{
|
|
||||||
value: "FLASH",
|
|
||||||
label: "Flash headlights",
|
label: "Flash headlights",
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
|||||||
@@ -31,22 +31,14 @@ const vehiclesAPI = {
|
|||||||
})
|
})
|
||||||
.then(fetchRespHandler),
|
.then(fetchRespHandler),
|
||||||
|
|
||||||
sendCommand: async (vin, command, token) => fetch(`${API_ENDPOINT}/vehiclecommand`, {
|
sendCommand: async (vins, command, parameters, token) => fetch(`${API_ENDPOINT}/vehiclecommand`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: Object.assign({ "Content-Type": "application/json" }, getAuthHeaderOptions(token)),
|
headers: Object.assign({ "Content-Type": "application/json" }, getAuthHeaderOptions(token)),
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
vin, command,
|
vins, command, parameters
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.then(fetchRespHandler),
|
.then(fetchRespHandler),
|
||||||
|
|
||||||
sendLog: async (vin, filter, token) => fetch(`${API_ENDPOINT}/vehiclelog`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: Object.assign({ "Content-Type": "application/json" }, getAuthHeaderOptions(token)),
|
|
||||||
body: JSON.stringify(Object.assign(filter, {vin})),
|
|
||||||
})
|
|
||||||
.then(fetchRespHandler),
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default vehiclesAPI;
|
export default vehiclesAPI;
|
||||||
|
|||||||
Reference in New Issue
Block a user