Development (#94)
* CEC-371 Car ECU display (#79) * Merge Development (#53) * Use responsive iframe control for charts (#49) * Use responsive iframe control to charts * Move external Grafana link to Dashboard page * Remove unused embedded style class * Add button label * added delete button to deploy packages * Fix unit test warning Remove unused route from test * Fix styling of button * minor fixes per pr review Co-authored-by: jcw-fisker <jwatson@fiskerinc.com> Co-authored-by: John Cotten Watson <83605808+jcw-fisker@users.noreply.github.com> * Development Merge (#57) * CEC-287 Car connection status (#59) (#60) * Car connection status * Formatting * Merge Development (#64) * Add connection status to vehicles page * ConnectedIcon control * Handle Style * Development (#67) * preliminary map for vehicles * weird zoom bug * passing react tests * fixing warnings and updating snapshots * update node environment to 14 * addressing comments by changing variable types and adding styles to home page title * adding CODEOWNERS file * fixing token error * 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 Co-authored-by: jcw-fisker <jwatson@fiskerinc.com> Co-authored-by: John Cotten Watson <83605808+jcw-fisker@users.noreply.github.com> Co-authored-by: Drew Taylor <69828061+drew-fisker@users.noreply.github.com> * CEC-394 Car update log (#81) * CEC-394 Car update status control * Remove Datadog RUM Remove package update components Move control components into Controls folder Add Car update status page * Display update status log Clean up unused update package code * Remove console.logs * no vars * adding timestamp to vehicle popup * modifying vehicle data query * removing extraneous code * removing console log * Clean up SonarCloud warnings (#83) * Clean up SonarCloud warnings * Bogus security warning * Fix another warning * Fix unauthorized locations request * Fix update progress control * CEC-563 New manifest format (#88) * Add ManifestCreateContext Update create manifest page * Finish UI changes and API integration * Fixes * Fix test * Remove manifest ECU file version and type * Fixes * Add manifest ecu file type control * Fix Sonar warnings * Fix test * Update codeowners * Formatting * CEC-553 Change file type to string (#90) * CEC-553 File type uses string enum * Fix test timeout * Fix * Merge development * Increase timeout * Clean up (#95) * Clean up Mock missing methods * Smell Co-authored-by: jcw-fisker <jwatson@fiskerinc.com> Co-authored-by: John Cotten Watson <83605808+jcw-fisker@users.noreply.github.com> Co-authored-by: Drew Taylor <69828061+drew-fisker@users.noreply.github.com> Co-authored-by: Drew Taylor <dtaylor@fiskerinc.com>
This commit is contained in:
87
src/components/Controls/FileDragArea/index.jsx
Normal file
87
src/components/Controls/FileDragArea/index.jsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import clsx from "clsx";
|
||||
|
||||
import useStyles from "../../useStyles";
|
||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||
|
||||
const FileDragArea = ({
|
||||
onFileSelect,
|
||||
onDragEnter,
|
||||
onDragOver,
|
||||
onDragLeave,
|
||||
children,
|
||||
}) => {
|
||||
const { setMessage } = useStatusContext();
|
||||
const [over, setOver] = useState(false);
|
||||
const classes = useStyles();
|
||||
const inputFile = useRef();
|
||||
|
||||
const dragEnterHandler = (e) => {
|
||||
setOver(true);
|
||||
if (onDragEnter) onDragEnter(e);
|
||||
};
|
||||
|
||||
const dragOverHandler = (e) => {
|
||||
setOver(true);
|
||||
if (onDragEnter) onDragOver(e);
|
||||
};
|
||||
|
||||
const dragLeaveHandler = (e) => {
|
||||
setOver(false);
|
||||
if (onDragLeave) onDragLeave(e);
|
||||
};
|
||||
|
||||
const dropHandler = (e) => {
|
||||
try {
|
||||
const { files } = e.dataTransfer;
|
||||
if (onFileSelect) onFileSelect(files);
|
||||
setOver(false);
|
||||
} catch (err) {
|
||||
setMessage(err);
|
||||
}
|
||||
};
|
||||
|
||||
const selectHandler = (e) => {
|
||||
try {
|
||||
const { files } = e.target;
|
||||
if (onFileSelect) onFileSelect(files);
|
||||
} catch (err) {
|
||||
setMessage(err);
|
||||
}
|
||||
};
|
||||
|
||||
const onClick = (e) => {
|
||||
try {
|
||||
inputFile.current.click();
|
||||
} catch (err) {
|
||||
setMessage(err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
onDragEnter={dragEnterHandler}
|
||||
onDragOver={dragOverHandler}
|
||||
onDragLeave={dragLeaveHandler}
|
||||
onDrop={dropHandler}
|
||||
onClick={onClick}
|
||||
className={clsx(
|
||||
classes.fileDropArea,
|
||||
classes.clickable,
|
||||
over ? classes.overHighlight : null
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
<input
|
||||
type="file"
|
||||
onChange={selectHandler}
|
||||
ref={inputFile}
|
||||
className={classes.hidden}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileDragArea;
|
||||
14
src/components/Controls/ListHead/index.jsx
Normal file
14
src/components/Controls/ListHead/index.jsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from "react";
|
||||
import { TableCell, TableHead, TableRow } from "@material-ui/core";
|
||||
|
||||
const ListHead = ({ options }) => (
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{options.map((option) => (
|
||||
<TableCell key={option.label || "none"}>{option.label}</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
);
|
||||
|
||||
export default ListHead;
|
||||
79
src/components/Controls/ManifestECUFileList/index.jsx
Normal file
79
src/components/Controls/ManifestECUFileList/index.jsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableFooter,
|
||||
TableRow,
|
||||
} from "@material-ui/core";
|
||||
|
||||
import FileDragArea from "../FileDragArea";
|
||||
import ListHead from "../ListHead";
|
||||
import SubListItem from "../SubListItem";
|
||||
import { useManifestCreateContext } from "../../Contexts/ManifestCreateContext";
|
||||
import ManifestECUFileTypes from "../ManifestECUFileTypes";
|
||||
|
||||
const options = [
|
||||
{
|
||||
label: "Filename",
|
||||
field: "filename",
|
||||
readonly: true,
|
||||
},
|
||||
{
|
||||
label: "Offset",
|
||||
field: "offset",
|
||||
},
|
||||
{
|
||||
label: "Checksum",
|
||||
field: "checksum",
|
||||
},
|
||||
{
|
||||
label: "Type",
|
||||
field: "type",
|
||||
control: ManifestECUFileTypes,
|
||||
},
|
||||
{
|
||||
label: "",
|
||||
field: "filename",
|
||||
delete: true,
|
||||
},
|
||||
];
|
||||
|
||||
const ManifestECUFileList = ({ data }) => {
|
||||
const { addECUFile, deleteECUFile } = useManifestCreateContext();
|
||||
|
||||
const onAddFile = (files) => {
|
||||
addECUFile(data.data_id, files);
|
||||
};
|
||||
|
||||
const onDeletFile = (filename) => {
|
||||
deleteECUFile(data.data_id, filename);
|
||||
};
|
||||
|
||||
return (
|
||||
<Table>
|
||||
{data && data.files && data.files.length > 0 && (
|
||||
<ListHead options={options} />
|
||||
)}
|
||||
<TableBody>
|
||||
{data.files.map((file) => (
|
||||
<SubListItem
|
||||
key={file.filename}
|
||||
data={file}
|
||||
options={options}
|
||||
onDelete={onDeletFile}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TableCell colSpan={options.length}>
|
||||
<FileDragArea onFileSelect={onAddFile}>ADD FILES</FileDragArea>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
export default ManifestECUFileList;
|
||||
36
src/components/Controls/ManifestECUFileTypes/index.jsx
Normal file
36
src/components/Controls/ManifestECUFileTypes/index.jsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from "react";
|
||||
|
||||
import { Select } from "@material-ui/core";
|
||||
|
||||
const ManifestECUFileTypes = (props) => {
|
||||
const changeHandler = (e) => {
|
||||
if (!props.changeHandler) return;
|
||||
props.changeHandler(e);
|
||||
};
|
||||
|
||||
return (
|
||||
<Select
|
||||
id={props.id}
|
||||
native
|
||||
variant="outlined"
|
||||
value={props.value}
|
||||
onChange={changeHandler}
|
||||
>
|
||||
{FileTypes.map((item, index) => (
|
||||
<option key={index} value={item[0]}>
|
||||
{item[1]}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
|
||||
export default ManifestECUFileTypes;
|
||||
|
||||
const FileTypes = [
|
||||
["bootloader", "Bootloader"],
|
||||
["software", "Software"],
|
||||
["calibration", "Calibration"],
|
||||
["configuration", "Configuration"],
|
||||
["none", "None"],
|
||||
];
|
||||
87
src/components/Controls/ManifestECUList/index.jsx
Normal file
87
src/components/Controls/ManifestECUList/index.jsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Button,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableFooter,
|
||||
TableRow,
|
||||
} from "@material-ui/core";
|
||||
|
||||
import ECUDropDrop from "../ECUDropDown";
|
||||
import ListHead from "../ListHead";
|
||||
import ManifestECURow from "../ManifestECURow";
|
||||
import { useManifestCreateContext } from "../../Contexts/ManifestCreateContext";
|
||||
import PageDragPreventDefault from "../PageDragPreventDefault";
|
||||
|
||||
const options = [
|
||||
{
|
||||
label: "ID",
|
||||
field: "data_id",
|
||||
readonly: true,
|
||||
},
|
||||
{
|
||||
label: "ECU",
|
||||
field: "name",
|
||||
control: ECUDropDrop,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
label: "Version",
|
||||
field: "version",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
label: "Part Number",
|
||||
field: "part_number",
|
||||
},
|
||||
{
|
||||
label: "Serial",
|
||||
field: "serial_number",
|
||||
},
|
||||
{
|
||||
label: "Hardware",
|
||||
field: "hw_version",
|
||||
},
|
||||
{
|
||||
label: "Vendor",
|
||||
field: "vendor",
|
||||
},
|
||||
{
|
||||
label: "",
|
||||
delete: true,
|
||||
},
|
||||
];
|
||||
|
||||
const ManifestECUList = () => {
|
||||
const { ecus, addECU } = useManifestCreateContext();
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageDragPreventDefault />
|
||||
<Table>
|
||||
<ListHead options={options} />
|
||||
<TableBody>
|
||||
{ecus.map((item) => {
|
||||
return (
|
||||
<ManifestECURow
|
||||
key={item.data_id}
|
||||
data={item}
|
||||
options={options}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} align="center">
|
||||
<Button onClick={addECU}>Add ECU</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ManifestECUList;
|
||||
33
src/components/Controls/ManifestECURow/index.jsx
Normal file
33
src/components/Controls/ManifestECURow/index.jsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import React from "react";
|
||||
|
||||
import { TableCell, TableRow } from "@material-ui/core";
|
||||
|
||||
import ManifestECUFileList from "../ManifestECUFileList";
|
||||
import SubListItem from "../SubListItem";
|
||||
import { useManifestCreateContext } from "../../Contexts/ManifestCreateContext";
|
||||
|
||||
const ManifestECURow = ({ data, options }) => {
|
||||
const { deleteECU } = useManifestCreateContext();
|
||||
|
||||
const onDeleteECU = () => {
|
||||
deleteECU(data.data_id);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SubListItem
|
||||
key={data.data_id}
|
||||
data={data}
|
||||
options={options}
|
||||
onDelete={onDeleteECU}
|
||||
/>
|
||||
<TableRow>
|
||||
<TableCell colSpan={options.length}>
|
||||
<ManifestECUFileList data={data} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ManifestECURow;
|
||||
68
src/components/Controls/ManifestUploadProgress/index.jsx
Normal file
68
src/components/Controls/ManifestUploadProgress/index.jsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Button, Grid, LinearProgress, Typography } from "@material-ui/core";
|
||||
|
||||
import { useManifestCreateContext } from "../../Contexts/ManifestCreateContext";
|
||||
|
||||
const ManifestUploadProgress = (props) => {
|
||||
const { uploadProgress, uploadStatus, uploadFileIndex, uploadedFiles } =
|
||||
useManifestCreateContext();
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [completed, setCompleted] = useState(0);
|
||||
const [total, setTotal] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const x = uploadedFiles.reduce(
|
||||
(current, { file }) => current + file.size,
|
||||
0
|
||||
);
|
||||
setTotal(x);
|
||||
}, [uploadedFiles]);
|
||||
|
||||
useEffect(() => {
|
||||
if (uploadFileIndex === 0 || uploadFileIndex >= uploadedFiles.length)
|
||||
return;
|
||||
let uploaded = 0;
|
||||
uploadedFiles.forEach(({ file }, i) => {
|
||||
if (i < uploadFileIndex) uploaded += file.size;
|
||||
});
|
||||
setCompleted(uploaded);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [uploadFileIndex]);
|
||||
|
||||
useEffect(() => {
|
||||
if (total === 0 || uploadFileIndex >= uploadedFiles.length) return;
|
||||
const { file } = uploadedFiles[uploadFileIndex];
|
||||
const uploaded = completed + file.size * uploadProgress;
|
||||
const x = Math.min(99, Math.floor((uploaded / total) * 100));
|
||||
setProgress(x);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [uploadProgress, completed]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid container>
|
||||
<Grid xs={12}>
|
||||
<Typography align="center">
|
||||
{`File ${uploadFileIndex + 1} of ${
|
||||
uploadedFiles.length
|
||||
}. ${uploadStatus}`}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container alignContent="center" spacing={0}>
|
||||
<Grid xs={11}>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={progress}
|
||||
style={{ marginTop: 16 }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid xs={1} alignContent="flex-end" align="right">
|
||||
<Button onClick={props.onCancel}>Cancel</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ManifestUploadProgress;
|
||||
24
src/components/Controls/PageDragPreventDefault/index.jsx
Normal file
24
src/components/Controls/PageDragPreventDefault/index.jsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import React, { useEffect } from "react";
|
||||
|
||||
const PageDragPreventDefault = () => {
|
||||
const preventDefaults = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const dragEvents = ["dragenter", "dragover", "dragleave", "drop"];
|
||||
dragEvents.forEach((eventName) => {
|
||||
document.body.addEventListener(eventName, preventDefaults, false);
|
||||
});
|
||||
return () => {
|
||||
dragEvents.forEach((eventName) => {
|
||||
document.body.removeEventListener(eventName, preventDefaults, false);
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
export default PageDragPreventDefault;
|
||||
@@ -1,45 +0,0 @@
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
} from "@material-ui/core";
|
||||
import React from "react";
|
||||
import SubListItem from "../SubListItem";
|
||||
|
||||
const SubList = ({ data, options, onChange }) => {
|
||||
const onDelete = (id) => {
|
||||
if (!onChange) return;
|
||||
data.some((item, index) => {
|
||||
if (item.data_id !== id) return false;
|
||||
data.splice(index, 1);
|
||||
onChange(data);
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{options.map((option) => (
|
||||
<TableCell key={option.label || "none"}>{option.label}</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{data.map((item) => (
|
||||
<SubListItem
|
||||
key={item.data_id}
|
||||
data={item}
|
||||
options={options}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
export default SubList;
|
||||
@@ -16,11 +16,13 @@ const DataDisplay = ({ data, option, onDelete }) => {
|
||||
if (option.readonly) {
|
||||
return `${data[option.field]}`;
|
||||
} else if (option.delete) {
|
||||
const idField = option.field || "data_id";
|
||||
|
||||
return (
|
||||
<Link
|
||||
to="#"
|
||||
onClick={() => {
|
||||
deleteHandler(data.data_id);
|
||||
deleteHandler(data[idField]);
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
@@ -43,7 +45,7 @@ const DataDisplay = ({ data, option, onDelete }) => {
|
||||
name={option.field}
|
||||
placeholder={option.label}
|
||||
inputProps={option.inputProps}
|
||||
requried={option.required}
|
||||
requried={option.required ? "true" : "false"}
|
||||
fullWidth
|
||||
onChange={onChange}
|
||||
value={text}
|
||||
|
||||
Reference in New Issue
Block a user