Reorganize app pages (#73)
* Update layout and menus * Add breadcrumbs Add menu icons Add ECU drop down * Implement submenu Update download progress * revamped dashboard section - failing app.test.js * Clean up Co-authored-by: Drew Taylor <dtaylor@fiskerinc.com>
This commit is contained in:
BIN
src/assets/gray-marker.png
Normal file
BIN
src/assets/gray-marker.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/green-marker.png
Normal file
BIN
src/assets/green-marker.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
@@ -84,12 +84,12 @@ 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 () => {
|
it("Route /datascope unauthenticated", async () => {
|
||||||
await check("/vehicles-command", "span.MuiButton-label", "Sign In");
|
await check("/datascope", "span.MuiButton-label", "Sign In");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Route /dashboard unauthenticated", async () => {
|
it("Route /datascope/battery unauthenticated", async () => {
|
||||||
await check("/dashboard", "span.MuiButton-label", "Sign In");
|
await check("/datascope/battery", "span.MuiButton-label", "Sign In");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Route /packages unauthenticated", async () => {
|
it("Route /packages unauthenticated", async () => {
|
||||||
@@ -138,11 +138,6 @@ describe("App", () => {
|
|||||||
await check("/vehicle-status/FISKER123", "h6", "Vehicle FISKER123 Details");
|
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 () => {
|
||||||
await check("/page-not-found", "h1", "Page Not Found");
|
await check("/page-not-found", "h1", "Page Not Found");
|
||||||
});
|
});
|
||||||
@@ -157,14 +152,19 @@ describe("App", () => {
|
|||||||
await check("/carupdate-deploy/1", "h6", "Deploy Package 1.0");
|
await check("/carupdate-deploy/1", "h6", "Deploy Package 1.0");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Route /dashboard authenticated", async () => {
|
it("Route /datascope authenticated", async () => {
|
||||||
setToken(TEST_AUTH_OBJECT);
|
setToken(TEST_AUTH_OBJECT);
|
||||||
await check("/dashboard", "h6", "Dashboard");
|
await check("/datascope", "h6", "Datascope");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Route /datascope/battery authenticated", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT);
|
||||||
|
await check("/datascope/battery", "h6", "Battery");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Route /packages authenticated", async () => {
|
it("Route /packages authenticated", async () => {
|
||||||
setToken(TEST_AUTH_OBJECT);
|
setToken(TEST_AUTH_OBJECT);
|
||||||
await check("/packages", "h6", "Deploy Packages");
|
await check("/packages", "h6", "Deployments");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Route /package-status authenticated", async () => {
|
it("Route /package-status authenticated", async () => {
|
||||||
@@ -179,7 +179,6 @@ describe("App", () => {
|
|||||||
|
|
||||||
it("Route /package-create authenticated", async () => {
|
it("Route /package-create authenticated", async () => {
|
||||||
setToken(TEST_AUTH_OBJECT);
|
setToken(TEST_AUTH_OBJECT);
|
||||||
await check("/package-create", "h6", "Create Package");
|
await check("/package-create", "h6", "Create Deployments");
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@ import { logger } from "../../../services/monitoring";
|
|||||||
|
|
||||||
const MainForm = () => {
|
const MainForm = () => {
|
||||||
const { addVehicle, busy } = useVehicleContext();
|
const { addVehicle, busy } = useVehicleContext();
|
||||||
const { setMessage, setTitle } = useStatusContext();
|
const { setMessage, setTitle, setSitePath } = useStatusContext();
|
||||||
const {
|
const {
|
||||||
token: {
|
token: {
|
||||||
idToken: { jwtToken: token },
|
idToken: { jwtToken: token },
|
||||||
@@ -26,6 +26,15 @@ const MainForm = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTitle("Add Vehicle");
|
setTitle("Add Vehicle");
|
||||||
|
setSitePath([
|
||||||
|
{
|
||||||
|
label: "Vehicles",
|
||||||
|
link: "/vehicles",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Add Vehicle",
|
||||||
|
},
|
||||||
|
]);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
const onSubmit = async (event) => {
|
const onSubmit = async (event) => {
|
||||||
|
|||||||
@@ -1,189 +0,0 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
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 SearchField from "../../Controls/SearchField";
|
|
||||||
import { logger } from "../../../services/monitoring";
|
|
||||||
import ConnectedIcon from "../../Controls/ConnectedIcon";
|
|
||||||
import ECUList from "../../Controls/ECUList";
|
|
||||||
|
|
||||||
const tableColumns = [
|
|
||||||
{
|
|
||||||
id: "vin",
|
|
||||||
label: "VIN",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "model",
|
|
||||||
label: "Model",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "year",
|
|
||||||
label: "Year",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "trim",
|
|
||||||
label: "Trim",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "created_at",
|
|
||||||
label: "Created",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "updated_at",
|
|
||||||
label: "Updated",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const 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 [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("Vehicles");
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
await getVehicles(
|
|
||||||
{
|
|
||||||
limit: pageSize,
|
|
||||||
offset: pageSize * pageIndex,
|
|
||||||
order: `${orderBy} ${order}`,
|
|
||||||
search,
|
|
||||||
},
|
|
||||||
token
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
setMessage(e.message);
|
|
||||||
logger.warn(e.stack);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [pageIndex, pageSize, token, orderBy, order, search]);
|
|
||||||
|
|
||||||
const handleChangePageIndex = (event, newIndex) => {
|
|
||||||
setPageIndex(newIndex);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChangePageSize = (event) => {
|
|
||||||
setPageSize(parseInt(event.target.value, 10));
|
|
||||||
setPageIndex(0);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSearch = (search) => {
|
|
||||||
setSearch(search);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.paper} style={{ height: 700, width: "100%" }}>
|
|
||||||
<Toolbar className={classes.tableToolbar}>
|
|
||||||
<SearchField classes={classes} onSearch={handleSearch} />
|
|
||||||
</Toolbar>
|
|
||||||
<Table>
|
|
||||||
<TableHeaderSortable
|
|
||||||
classes={classes}
|
|
||||||
orderBy={orderBy}
|
|
||||||
order={order}
|
|
||||||
columnData={tableColumns}
|
|
||||||
onSortRequest={sortHandler}
|
|
||||||
/>
|
|
||||||
<TableBody>
|
|
||||||
{vehicles.map((row) => (
|
|
||||||
<TableRow key={row.vin}>
|
|
||||||
<TableCell align="center">
|
|
||||||
<ConnectedIcon
|
|
||||||
connected={row.connected}
|
|
||||||
style={{ marginRight: 5 }}
|
|
||||||
/>
|
|
||||||
<Link to={`/vehicle-status/${row.vin}`}>{row.vin}</Link>
|
|
||||||
{row.ecu_list && (
|
|
||||||
<>
|
|
||||||
<br />
|
|
||||||
<ECUList list={row.ecu_list} search={search} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</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={6}
|
|
||||||
count={totalVehicles}
|
|
||||||
rowsPerPage={pageSize}
|
|
||||||
page={pageIndex}
|
|
||||||
SelectProps={{
|
|
||||||
inputProps: { "aria-label": "rows per page" },
|
|
||||||
native: true,
|
|
||||||
}}
|
|
||||||
onPageChange={handleChangePageIndex}
|
|
||||||
onRowsPerPageChange={handleChangePageSize}
|
|
||||||
/>
|
|
||||||
</TableRow>
|
|
||||||
</TableFooter>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const VehiclesList = () => (
|
|
||||||
<VehicleProvider>
|
|
||||||
<MainForm />
|
|
||||||
</VehicleProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default VehiclesList;
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { Grid } from "@material-ui/core";
|
import { Grid } from "@material-ui/core";
|
||||||
|
import AddCircleIcon from "@material-ui/icons/AddCircle";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
import { VehicleProvider } from "../../Contexts/VehicleContext";
|
import { VehicleProvider } from "../../Contexts/VehicleContext";
|
||||||
import { useUserContext } from "../../Contexts/UserContext";
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
@@ -14,7 +16,7 @@ const MainForm = () => {
|
|||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [selected, setSelected] = useState([]);
|
const [selected, setSelected] = useState([]);
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const { setTitle } = useStatusContext();
|
const { setTitle, setSitePath } = useStatusContext();
|
||||||
const {
|
const {
|
||||||
token: {
|
token: {
|
||||||
idToken: { jwtToken: token },
|
idToken: { jwtToken: token },
|
||||||
@@ -46,7 +48,8 @@ const MainForm = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTitle("Send Command");
|
setTitle("Vehicles");
|
||||||
|
setSitePath([]);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -54,6 +57,9 @@ const MainForm = () => {
|
|||||||
<div className={classes.paper} style={{ height: 700, width: "100%" }}>
|
<div className={classes.paper} style={{ height: 700, width: "100%" }}>
|
||||||
<Grid container className={classes.root} spacing={2}>
|
<Grid container className={classes.root} spacing={2}>
|
||||||
<Grid item md={6}>
|
<Grid item md={6}>
|
||||||
|
<Link to="/vehicle-add" className={classes.labelInline}>
|
||||||
|
<AddCircleIcon fontSize="large" />
|
||||||
|
</Link>
|
||||||
<SearchField classes={classes} onSearch={handleSearch} />
|
<SearchField classes={classes} onSearch={handleSearch} />
|
||||||
<div
|
<div
|
||||||
className={classes.labelInline}
|
className={classes.labelInline}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ const MainForm = () => {
|
|||||||
const [orderBy, setOrderBy] = useState("id");
|
const [orderBy, setOrderBy] = useState("id");
|
||||||
const [order, setOrder] = useState("desc");
|
const [order, setOrder] = useState("desc");
|
||||||
const { getCarUpdates, carUpdates, totalCarUpdates } = useUpdatesContext();
|
const { getCarUpdates, carUpdates, totalCarUpdates } = useUpdatesContext();
|
||||||
const { setMessage, setTitle } = useStatusContext();
|
const { setMessage, setTitle, setSitePath } = useStatusContext();
|
||||||
const {
|
const {
|
||||||
token: {
|
token: {
|
||||||
idToken: { jwtToken: token },
|
idToken: { jwtToken: token },
|
||||||
@@ -60,7 +60,17 @@ const MainForm = () => {
|
|||||||
} = useUserContext();
|
} = useUserContext();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTitle(`Vehicle ${vin} Details`);
|
const title = `Vehicle ${vin} Details`;
|
||||||
|
setTitle(title);
|
||||||
|
setSitePath([
|
||||||
|
{
|
||||||
|
label: "Vehicles",
|
||||||
|
link: "/vehicles",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: title,
|
||||||
|
},
|
||||||
|
]);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [vin]);
|
}, [vin]);
|
||||||
|
|
||||||
|
|||||||
@@ -76,15 +76,17 @@ export const CarUpdatesProvider = ({ children }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const applyProgressStatus = (item, status) => {
|
const applyProgressStatus = (item, status) => {
|
||||||
if (status.msg === "DONE") {
|
if (status.msg === "package_download_complete") {
|
||||||
delete item.progress;
|
delete item.progress;
|
||||||
item.status = "downloaded";
|
item.status = "downloaded";
|
||||||
} else if (status.msg === "downloading" && status.total > 0) {
|
} else if (status.msg === "downloading" && status.package_total > 0) {
|
||||||
let progress = Math.floor((100 * status.bytes) / status.total);
|
let progress = Math.floor(
|
||||||
|
(100 * status.package_current) / status.package_total
|
||||||
|
);
|
||||||
if (progress > 99) progress = 0;
|
if (progress > 99) progress = 0;
|
||||||
item.progress = progress;
|
item.progress = progress;
|
||||||
item.status = `downloading ${progress}%`;
|
item.status = `downloading ${progress}%`;
|
||||||
} else if (status.error > 0) {
|
} else if (status.error > 0 || status.msg === "download_error") {
|
||||||
item.status = "download error";
|
item.status = "download error";
|
||||||
} else {
|
} else {
|
||||||
item.status = "downloading";
|
item.status = "downloading";
|
||||||
|
|||||||
@@ -117,6 +117,16 @@ export const ManifestsProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const checkExistingManifest = async (data, token) => {
|
||||||
|
const check = {
|
||||||
|
name: data.name,
|
||||||
|
version: data.version,
|
||||||
|
};
|
||||||
|
const { data: result } = await api.getManifests(check, token);
|
||||||
|
if (result.length > 0)
|
||||||
|
throw new Error(`Update ${data.name} ${data.version} already exists`);
|
||||||
|
};
|
||||||
|
|
||||||
const createManifest = async (data, token) => {
|
const createManifest = async (data, token) => {
|
||||||
let result;
|
let result;
|
||||||
|
|
||||||
@@ -125,7 +135,8 @@ export const ManifestsProvider = ({ children }) => {
|
|||||||
validateManifest(data, token);
|
validateManifest(data, token);
|
||||||
setUploadedFiles(data.files);
|
setUploadedFiles(data.files);
|
||||||
|
|
||||||
result = await api.createManifest(data, token);
|
await checkExistingManifest(data, token);
|
||||||
|
if (result !== null) result = await api.createManifest(data, token);
|
||||||
if (result.error)
|
if (result.error)
|
||||||
throw new Error(`Create manifest error. ${result.message}`);
|
throw new Error(`Create manifest error. ${result.message}`);
|
||||||
|
|
||||||
|
|||||||
@@ -5,14 +5,17 @@ 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("");
|
const [title, setTitle] = useState("");
|
||||||
|
const [sitePath, setSitePath] = useState([]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StatusContext.Provider
|
<StatusContext.Provider
|
||||||
value={{
|
value={{
|
||||||
message,
|
message,
|
||||||
setMessage,
|
|
||||||
title,
|
title,
|
||||||
|
sitePath,
|
||||||
|
setMessage,
|
||||||
setTitle,
|
setTitle,
|
||||||
|
setSitePath,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
121
src/components/Controls/ECUDropDown/index.jsx
Normal file
121
src/components/Controls/ECUDropDown/index.jsx
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Select } from "@material-ui/core";
|
||||||
|
|
||||||
|
const ECUDropDown = (props) => {
|
||||||
|
const changeHandler = (e) => {
|
||||||
|
if (!props.changeHandler) return;
|
||||||
|
props.changeHandler(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
id={props.id}
|
||||||
|
native
|
||||||
|
variant="outlined"
|
||||||
|
value={props.value}
|
||||||
|
onChange={changeHandler}
|
||||||
|
>
|
||||||
|
{ECUs.map((item, index) => (
|
||||||
|
<option key={index} value={item[0]}>
|
||||||
|
{item[1]}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ECUDropDown;
|
||||||
|
|
||||||
|
const ECUs = [
|
||||||
|
["AGS", "Active Grille Shutter"],
|
||||||
|
["ADB", "Adaptive Driving Beam"],
|
||||||
|
["ADAS", "Advanced Driver Assist System"],
|
||||||
|
["ACU", "Airbag Control Unit"],
|
||||||
|
["ACP", "Airconditioning Control Panel"],
|
||||||
|
["AMP", "Amplifier"],
|
||||||
|
["AP_FL", "Anti-Pinch Front Left"],
|
||||||
|
["AP_FR", "Anti-Pinch Front Right"],
|
||||||
|
["AP_RL", "Anti-Pinch Rear Left"],
|
||||||
|
["AP_RR", "Anti-Pinch Rear Right"],
|
||||||
|
["AL", "Atmosphere Lamp"],
|
||||||
|
["BCS", "Battery HV Current Sensor"],
|
||||||
|
["BMS", "Battery Management System"],
|
||||||
|
["BMU", "Battery Management Unit"],
|
||||||
|
["BCM", "Body Control Module"],
|
||||||
|
["CDS", "Center Display Screen"],
|
||||||
|
["CCU", "Charging Control Unit"],
|
||||||
|
["CIM", "Column Integrated Module"],
|
||||||
|
["CVM", "Coolant Valve Module"],
|
||||||
|
["CFM", "Cooling Fan Module"],
|
||||||
|
["CMRR_FL", "Corner Mid Range Radar Front Left"],
|
||||||
|
["CMRR_FR", "Corner Mid Range Radar Front Right"],
|
||||||
|
["CMRR_RL", "Corner Mid Range Radar Rear Left"],
|
||||||
|
["CMRR_RR", "Corner Mid Range Radar Rear Right"],
|
||||||
|
["DVRC", "Digital Video Recorder Camera"],
|
||||||
|
["DC-CHM", "Direct Current Charge Machine"],
|
||||||
|
["DMC", "Driver Monitor Camera"],
|
||||||
|
["DSMC", "Driver Seat Memory Controller"],
|
||||||
|
["DWSG", "Driver Window Switch Group"],
|
||||||
|
["EPS", "Electric Power Steering"],
|
||||||
|
["EAS", "Electrical Air Compressor System"],
|
||||||
|
["ECC", "Electrical Climate Controller"],
|
||||||
|
["EWP_B", "Electrical Water Pump Battery"],
|
||||||
|
["EWP_FD", "Electrical Water Pump Front Drive"],
|
||||||
|
["EWP_H", "Electrical Water Pump Heat"],
|
||||||
|
["EWP_RD", "Electrical Water Pump Rear Drive"],
|
||||||
|
["EWM", "Electrical Wiper Motor"],
|
||||||
|
["EXV_B", "Electronic Expansion Value Battery"],
|
||||||
|
["EXV_HP", "Electronic Expansion Valve HPC"],
|
||||||
|
["ESP", "Electronic Stability Program"],
|
||||||
|
["FDHA_FL", "Flush Door Handle Actuator Front Left"],
|
||||||
|
["FDHA_FR", "Flush Door Handle Actuator Front Right"],
|
||||||
|
["FDHA_RL", "Flush Door Handle Actuator Rear Left"],
|
||||||
|
["FDHA_RR", "Flush Door Handle Actuator Rear Right"],
|
||||||
|
["Lumber", "Four-Way Lumber"],
|
||||||
|
["FBM_L", "Front Beam Module Left"],
|
||||||
|
["FBM_R", "Front Beam Module Right"],
|
||||||
|
["FVC", "Front Video Camera"],
|
||||||
|
["GW", "Gateway"],
|
||||||
|
["HUD", "Head-Up Display"],
|
||||||
|
["IDS", "Instrument Display Screen"],
|
||||||
|
["ICC", "Integrated Cockpit Controller"],
|
||||||
|
["IBS", "Intelligent Battery Sensor"],
|
||||||
|
["iBooster", "Intelligent Booster"],
|
||||||
|
["KS", "Kick Sensor"],
|
||||||
|
["LSC", "Left Side Camera"],
|
||||||
|
["MRR", "Mid Range Radar"],
|
||||||
|
["MCU_F", "Motor Control Unit Front"],
|
||||||
|
["MCU_R", "Motor Control Unit Rear"],
|
||||||
|
["MDV", "Motorized Deco-Vent"],
|
||||||
|
["MFS", "Multifunction Steering"],
|
||||||
|
["MIS", "Multimedia Interactive Switch"],
|
||||||
|
["MPC", "Multipurpose Camera"],
|
||||||
|
["OMC", "Occupant Monitor Camera"],
|
||||||
|
["OHC", "Overhead Console"],
|
||||||
|
["PAS", "Parking Assistant System"],
|
||||||
|
["PCU", "Parking Control Unit"],
|
||||||
|
["PMS", "Particulate Matter Sensor"],
|
||||||
|
["PSM", "Passenger Seat Module"],
|
||||||
|
["PEPS", "Passive Entry And Passive Start"],
|
||||||
|
["PKC", "Phone Key Controller"],
|
||||||
|
["PKC_ANT_L", "Phone Key Controller Antenna Left"],
|
||||||
|
["PKC_ANT_R", "Phone Key Controller Antenna Right"],
|
||||||
|
["PWC", "Phone Wireless Charging"],
|
||||||
|
["PASC", "Power Adjust Steering Column"],
|
||||||
|
["PDU", "Power Distribution Unit"],
|
||||||
|
["PLGM", "Power Lift Gate Module"],
|
||||||
|
["RLS", "Rain Light Sensor"],
|
||||||
|
["RAC", "Rear Airconditioning Control"],
|
||||||
|
["RVC", "Rear View Camera"],
|
||||||
|
["RSC", "Right Side Camera"],
|
||||||
|
["RCM", "Roof Control Module"],
|
||||||
|
["RSM", "Roof Shade Module"],
|
||||||
|
["TBOX", "Telematics Box"],
|
||||||
|
["TPMS", "Tire Pressure Monitoring System"],
|
||||||
|
["TDS", "Touch Display Screen"],
|
||||||
|
["USB Box", "USB Box"],
|
||||||
|
["VCU", "Vehicle Control Unit"],
|
||||||
|
["VSP", "Vehicle Sound For Pedestraion"],
|
||||||
|
["WTC_B", "Water Thermal Controller Battery"],
|
||||||
|
["WTC_H", "Water Thermal Controller Heat"],
|
||||||
|
];
|
||||||
34
src/components/Controls/SiteBreadCrumbs/index.jsx
Normal file
34
src/components/Controls/SiteBreadCrumbs/index.jsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Link as RouterLink } from "react-router-dom";
|
||||||
|
import Breadcrumbs from "@material-ui/core/Breadcrumbs";
|
||||||
|
import { Link } from "@material-ui/core";
|
||||||
|
|
||||||
|
const SiteBreadcrumbs = ({ path }) => {
|
||||||
|
if (!path || path.length === 0) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Breadcrumbs
|
||||||
|
aria-label="breadcrumb"
|
||||||
|
color="inherit"
|
||||||
|
style={{ fontSize: "10px" }}
|
||||||
|
>
|
||||||
|
{path.map((item, index, items) => {
|
||||||
|
if (index < items.length) {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
key={index}
|
||||||
|
color="inherit"
|
||||||
|
to={item.link || "#"}
|
||||||
|
component={RouterLink}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})}
|
||||||
|
</Breadcrumbs>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SiteBreadcrumbs;
|
||||||
@@ -27,6 +27,15 @@ const DataDisplay = ({ data, option, onDelete }) => {
|
|||||||
<DeleteIcon />
|
<DeleteIcon />
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
} else if (option.control) {
|
||||||
|
return (
|
||||||
|
<option.control
|
||||||
|
id={option.field}
|
||||||
|
name={option.field}
|
||||||
|
value={text}
|
||||||
|
changeHandler={onChange}
|
||||||
|
></option.control>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
import React, { useEffect } from "react";
|
|
||||||
import { Button, Grid, Link } from "@material-ui/core";
|
|
||||||
import CreateIcon from "@material-ui/icons/Create";
|
|
||||||
|
|
||||||
import { useStatusContext } from "../Contexts/StatusContext";
|
|
||||||
import useStyles from "../useStyles";
|
|
||||||
import ResponsiveIFrame from "../Controls/ResponsiveIFrame";
|
|
||||||
|
|
||||||
const Dashboard = () => {
|
|
||||||
const classes = useStyles();
|
|
||||||
const { setTitle } = useStatusContext();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTitle("Dashboard");
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.paper}>
|
|
||||||
<Grid container className={classes.root} spacing={2}>
|
|
||||||
<Grid item md={6}>
|
|
||||||
<ResponsiveIFrame
|
|
||||||
classes={classes}
|
|
||||||
src="https://grafana.fiskerdps.com/d-solo/jRKKo2gnz/battery?orgId=2&refresh=30s&panelId=2"
|
|
||||||
title="Battery Time Series"
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
<Grid item md={6}>
|
|
||||||
<ResponsiveIFrame
|
|
||||||
classes={classes}
|
|
||||||
src="https://grafana.fiskerdps.com/d-solo/1VTVJ_qGk/dashboard?orgId=2&refresh=30s&panelId=12"
|
|
||||||
title="Signals Time Series"
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
<Button
|
|
||||||
style={{ marginTop: 10 }}
|
|
||||||
aria-label="create"
|
|
||||||
color="primary"
|
|
||||||
component={Link}
|
|
||||||
href="https://grafana.fiskerdps.com"
|
|
||||||
rel="noopener"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<CreateIcon fontSize="large" />
|
|
||||||
Create Charts
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Dashboard;
|
|
||||||
159
src/components/Datascope/Battery/index.jsx
Normal file
159
src/components/Datascope/Battery/index.jsx
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
Grid,
|
||||||
|
InputLabel,
|
||||||
|
MenuItem,
|
||||||
|
Paper,
|
||||||
|
Select,
|
||||||
|
TextField,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
|
||||||
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
|
import useStyles from "../../useStyles";
|
||||||
|
import ResponsiveIFrame from "../../Controls/ResponsiveIFrame";
|
||||||
|
|
||||||
|
const Battery = () => {
|
||||||
|
const classes = useStyles();
|
||||||
|
const { setTitle, setSitePath } = useStatusContext();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTitle("Battery");
|
||||||
|
setSitePath([
|
||||||
|
{
|
||||||
|
label: "Datascope",
|
||||||
|
link: "/datascope",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Battery",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [vin, setVIN] = useState("1F15K3R45N1234567");
|
||||||
|
const [cellNum, setCellNum] = useState(1);
|
||||||
|
|
||||||
|
const handleVINForm = (e) => {
|
||||||
|
if (e.target.value.length === 17) {
|
||||||
|
setVIN(e.target.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.paper}>
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
<Grid container item md={4} space={2}>
|
||||||
|
<Grid item md={12}>
|
||||||
|
<Paper className={classes.grafanaContainer}>
|
||||||
|
<form className={classes.formControl}>
|
||||||
|
<TextField
|
||||||
|
id="vin"
|
||||||
|
label="VIN"
|
||||||
|
defaultValue="1F15K3R45N1234567"
|
||||||
|
variant="outlined"
|
||||||
|
onChange={handleVINForm}
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
<FormControl variant="outlined" className={classes.formControl}>
|
||||||
|
<InputLabel id="demo-simple-select-outlined-label">
|
||||||
|
Cell
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="demo-simple-select-outlined-label"
|
||||||
|
id="demo-simple-select-outlined"
|
||||||
|
value={cellNum}
|
||||||
|
onChange={(e) => setCellNum(e.target.value)}
|
||||||
|
label="Cell"
|
||||||
|
>
|
||||||
|
{[...Array(112)].map((_, i) => (
|
||||||
|
<MenuItem key={i + 1} value={i + 1}>
|
||||||
|
{i + 1}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Paper>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item md={12}>
|
||||||
|
<Paper className={classes.grafanaContainer}>
|
||||||
|
Cell Voltage {cellNum}
|
||||||
|
<ResponsiveIFrame
|
||||||
|
classes={classes}
|
||||||
|
src={`https://grafana.fiskerdps.com/d-solo/LVI-aQGnz/diagnostics?orgId=2&var-VIN=${vin}&var-Signal=BMS_CellVolt${cellNum}&panelId=2`}
|
||||||
|
title="Cell Voltage"
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item md={12}>
|
||||||
|
<Paper className={classes.grafanaContainer}>
|
||||||
|
Cell Temperature {cellNum}
|
||||||
|
<ResponsiveIFrame
|
||||||
|
classes={classes}
|
||||||
|
src={`https://grafana.fiskerdps.com/d-solo/LVI-aQGnz/diagnostics?orgId=2&var-VIN=${vin}&var-Signal=BMS_CellT${cellNum}&panelId=2`}
|
||||||
|
title="Cell Temperature"
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item md={12}>
|
||||||
|
<Paper className={classes.grafanaContainer}>
|
||||||
|
<ResponsiveIFrame
|
||||||
|
classes={classes}
|
||||||
|
src={`https://grafana.fiskerdps.com/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN=${vin}&refresh=1m&panelId=4`}
|
||||||
|
title="Battery Temperature Time Series"
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid container item md={8} space={2}>
|
||||||
|
<Grid item md={12}>
|
||||||
|
<Paper className={classes.grafanaContainer}>
|
||||||
|
<ResponsiveIFrame
|
||||||
|
classes={classes}
|
||||||
|
src={`https://grafana.fiskerdps.com/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN=${vin}&refresh=1m&panelId=6`}
|
||||||
|
title="Battery Capacity Time Series"
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item md={12}>
|
||||||
|
<Paper className={classes.grafanaContainer}>
|
||||||
|
<ResponsiveIFrame
|
||||||
|
classes={classes}
|
||||||
|
src={`https://grafana.fiskerdps.com/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN=${vin}&panelId=12`}
|
||||||
|
title="Battery Percent Time Series"
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item md={6}>
|
||||||
|
<Paper className={classes.grafanaContainer}>
|
||||||
|
<ResponsiveIFrame
|
||||||
|
classes={classes}
|
||||||
|
src={`https://grafana.fiskerdps.com/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN=${vin}&refresh=1m&panelId=2`}
|
||||||
|
title="12V Battery Percentage Time Series"
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item md={6}>
|
||||||
|
<Paper className={classes.grafanaContainer}>
|
||||||
|
<ResponsiveIFrame
|
||||||
|
classes={classes}
|
||||||
|
src={`https://grafana.fiskerdps.com/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN=${vin}&refresh=1m&panelId=9`}
|
||||||
|
title="12V Battery Voltage Time Series"
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Battery;
|
||||||
96
src/components/Datascope/Home/index.jsx
Normal file
96
src/components/Datascope/Home/index.jsx
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { Button, Grid, Link, Paper } from "@material-ui/core";
|
||||||
|
import CreateIcon from "@material-ui/icons/Create";
|
||||||
|
|
||||||
|
import api from "../../../services/grafana";
|
||||||
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
|
import useStyles from "../../useStyles";
|
||||||
|
import ResponsiveIFrame from "../../Controls/ResponsiveIFrame";
|
||||||
|
|
||||||
|
const Datascope = () => {
|
||||||
|
const classes = useStyles();
|
||||||
|
const { setTitle, setSitePath } = useStatusContext();
|
||||||
|
const REQUEST_INTERVAL = 10000;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTitle("Datascope");
|
||||||
|
setSitePath([]);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [carsCount, setCarsCount] = useState(0);
|
||||||
|
useEffect(() => {
|
||||||
|
api
|
||||||
|
.getCarsCount()
|
||||||
|
.then((result) => setCarsCount(result))
|
||||||
|
.catch((error) => console.log(error));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [signalsCount, setSignalsCount] = useState("0");
|
||||||
|
useEffect(() => {
|
||||||
|
storeSignals();
|
||||||
|
|
||||||
|
const id = setInterval(function () {
|
||||||
|
storeSignals();
|
||||||
|
}, REQUEST_INTERVAL);
|
||||||
|
return () => {
|
||||||
|
clearInterval(id);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const storeSignals = () => {
|
||||||
|
api
|
||||||
|
.getSignalsCount()
|
||||||
|
.then((result) => {
|
||||||
|
let num = result.toLocaleString();
|
||||||
|
setSignalsCount(num);
|
||||||
|
})
|
||||||
|
.catch((error) => console.log(error));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.paper}>
|
||||||
|
<Grid container className={classes.root} spacing={2}>
|
||||||
|
<Grid item md={6}>
|
||||||
|
<Paper className={classes.grafanaContainer} style={{ height: 150 }}>
|
||||||
|
<h1 className={classes.datascopeContainerValue}>{carsCount}</h1>
|
||||||
|
<h2 className={classes.datascopeContainerText}>Cars</h2>
|
||||||
|
</Paper>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item md={6}>
|
||||||
|
<Paper className={classes.grafanaContainer} style={{ height: 150 }}>
|
||||||
|
<h1 className={classes.datascopeContainerValue}>{signalsCount}</h1>
|
||||||
|
<h2 className={classes.datascopeContainerText}>
|
||||||
|
Signals Collected
|
||||||
|
</h2>
|
||||||
|
</Paper>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item md={12}>
|
||||||
|
<Paper className={classes.grafanaContainer}>
|
||||||
|
<ResponsiveIFrame
|
||||||
|
classes={classes}
|
||||||
|
src="https://grafana.fiskerdps.com/d-solo/1VTVJ_qGk/dashboard?orgId=2&refresh=30s&panelId=12"
|
||||||
|
title="Signals Time Series"
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Button
|
||||||
|
style={{ marginTop: 10 }}
|
||||||
|
aria-label="create"
|
||||||
|
color="primary"
|
||||||
|
component={Link}
|
||||||
|
href="https://grafana.fiskerdps.com"
|
||||||
|
rel="noopener"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<CreateIcon fontSize="large" />
|
||||||
|
Go to Grafana
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Datascope;
|
||||||
@@ -4,38 +4,24 @@ import useStyles from "../useStyles";
|
|||||||
|
|
||||||
import { useUserContext } from "../Contexts/UserContext";
|
import { useUserContext } from "../Contexts/UserContext";
|
||||||
import { useStatusContext } from "../Contexts/StatusContext";
|
import { useStatusContext } from "../Contexts/StatusContext";
|
||||||
import { parsePayload } from "../../utils/jwt";
|
|
||||||
import VehicleMap from "../VehicleMap";
|
import VehicleMap from "../VehicleMap";
|
||||||
|
import { getName } from "../../utils/jwt";
|
||||||
const DEFAULT_GREETING = "Welcome";
|
|
||||||
|
|
||||||
const getGreeting = (token) => {
|
|
||||||
if (!token || !token.idToken || !token.idToken.jwtToken)
|
|
||||||
return DEFAULT_GREETING;
|
|
||||||
|
|
||||||
const payload = parsePayload(token.idToken.jwtToken);
|
|
||||||
|
|
||||||
if (!payload || !payload.given_name) return DEFAULT_GREETING;
|
|
||||||
|
|
||||||
return `Welcome ${payload.given_name}!`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const { token } = useUserContext();
|
const { token } = useUserContext();
|
||||||
const greeting = getGreeting(token);
|
const { setTitle, setSitePath } = useStatusContext();
|
||||||
const { setTitle } = useStatusContext();
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTitle("Home");
|
setTitle("Home");
|
||||||
|
setSitePath([]);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.paper}>
|
<div className={classes.paper}>
|
||||||
<Typography className={classes.homePageTitle} component="h1" variant="h5">
|
<Typography className={classes.homePageTitle} component="h1" variant="h5">
|
||||||
{greeting}
|
Welcome {getName(token)}!
|
||||||
</Typography>
|
</Typography>
|
||||||
<VehicleMap />
|
<VehicleMap />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import Drawer from "@material-ui/core/Drawer";
|
import {
|
||||||
import AppBar from "@material-ui/core/AppBar";
|
Container,
|
||||||
import Toolbar from "@material-ui/core/Toolbar";
|
Drawer,
|
||||||
import Typography from "@material-ui/core/Typography";
|
AppBar,
|
||||||
import Divider from "@material-ui/core/Divider";
|
Toolbar,
|
||||||
|
Typography,
|
||||||
|
Divider,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
|
||||||
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 { useStatusContext } from "../Contexts/StatusContext";
|
||||||
import { Button, Container } from "@material-ui/core";
|
import UserMenu from "./UserMenu";
|
||||||
|
import SiteBreadcrumbs from "../Controls/SiteBreadCrumbs";
|
||||||
import logo from "../../assets/fisker-badge.svg";
|
import logo from "../../assets/fisker-badge.svg";
|
||||||
|
|
||||||
export default function MenuDrawer({ children }) {
|
export default function MenuDrawer({ children }) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const { title } = useStatusContext();
|
const { title, sitePath } = useStatusContext();
|
||||||
const { signOut, token } = useUserContext();
|
const { token } = useUserContext();
|
||||||
|
|
||||||
const onSignOut = () => {
|
|
||||||
document.location = signOut();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
@@ -31,17 +31,14 @@ export default function MenuDrawer({ children }) {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
|
<div>
|
||||||
<Typography variant="h6" noWrap>
|
<Typography variant="h6" noWrap>
|
||||||
{title}
|
{title}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<SiteBreadcrumbs path={sitePath} className={classes.breadcrumbs} />
|
||||||
|
</div>
|
||||||
{token !== null && (
|
{token !== null && (
|
||||||
<Button
|
<UserMenu color="inherit" className={classes.rightToolbar} />
|
||||||
color="inherit"
|
|
||||||
onClick={onSignOut}
|
|
||||||
className={classes.rightToolbar}
|
|
||||||
>
|
|
||||||
Sign Out
|
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
|
|||||||
@@ -4,46 +4,89 @@ import ListItemLink from "../ListItemLink";
|
|||||||
import ListItemExternalLink from "../ListItemExternalLink";
|
import ListItemExternalLink from "../ListItemExternalLink";
|
||||||
import { useUserContext } from "../Contexts/UserContext";
|
import { useUserContext } from "../Contexts/UserContext";
|
||||||
import { Roles, hasRole } from "../../utils/roles";
|
import { Roles, hasRole } from "../../utils/roles";
|
||||||
|
import HomeIcon from "@material-ui/icons/Home";
|
||||||
|
import CommuteIcon from "@material-ui/icons/Commute";
|
||||||
|
import CloudDownloadIcon from "@material-ui/icons/CloudDownload";
|
||||||
|
import AssessmentIcon from "@material-ui/icons/Assessment";
|
||||||
|
|
||||||
const menuData = [
|
const menuData = [
|
||||||
{
|
{
|
||||||
label: "Home",
|
label: "Home",
|
||||||
to: "/home",
|
to: "/home",
|
||||||
|
icon: <HomeIcon />,
|
||||||
roles: [],
|
roles: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Dashboard",
|
label: "Deployments",
|
||||||
to: "/dashboard",
|
|
||||||
roles: [Roles.CREATE, Roles.READ],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Deploy Packages",
|
|
||||||
to: "/packages",
|
to: "/packages",
|
||||||
|
icon: <CloudDownloadIcon />,
|
||||||
roles: [Roles.CREATE, Roles.READ],
|
roles: [Roles.CREATE, Roles.READ],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Create Package",
|
label: "Vehicles",
|
||||||
to: "/package-create",
|
|
||||||
roles: [Roles.CREATE],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "View Vehicles",
|
|
||||||
to: "/vehicles",
|
to: "/vehicles",
|
||||||
|
icon: <CommuteIcon />,
|
||||||
|
roles: [Roles.CREATE],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Datascope",
|
||||||
|
to: "/datascope",
|
||||||
|
icon: <AssessmentIcon />,
|
||||||
roles: [Roles.CREATE, Roles.READ],
|
roles: [Roles.CREATE, Roles.READ],
|
||||||
|
submenus: [
|
||||||
|
{
|
||||||
|
label: "Battery",
|
||||||
|
to: "/datascope/battery",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Add Vehicle",
|
label: "Diagnostics",
|
||||||
to: "/vehicle-add",
|
url: "https://grafana.fiskerdps.com",
|
||||||
roles: [Roles.CREATE],
|
|
||||||
},
|
},
|
||||||
{
|
],
|
||||||
label: "Send Command",
|
|
||||||
to: "/vehicles-command",
|
|
||||||
roles: [Roles.CREATE],
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function SideMenu() {
|
const MenuItem = ({ item, children }) => {
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
{item.to && (
|
||||||
|
<ListItemLink primary={item.label} to={item.to} icon={item.icon} />
|
||||||
|
)}
|
||||||
|
{item.url && (
|
||||||
|
<ListItemExternalLink
|
||||||
|
primary={item.label}
|
||||||
|
url={item.url}
|
||||||
|
icon={item.icon}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ExpandableSideMenuItem = ({ item }) => {
|
||||||
|
/*
|
||||||
|
const [expanded, setExpanded] = useState(true);
|
||||||
|
const clickHandler = (e) => {
|
||||||
|
setExpanded(!expanded);
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span>
|
||||||
|
<MenuItem item={item}></MenuItem>
|
||||||
|
</span>
|
||||||
|
<ul style={{ marginLeft: 50 }}>
|
||||||
|
{item.submenus.map((subitem, index) => (
|
||||||
|
<MenuItem key={`submenu-${index}`} item={subitem} />
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SideMenu = () => {
|
||||||
const { groups } = useUserContext();
|
const { groups } = useUserContext();
|
||||||
const menu = menuData.reduce((result, item) => {
|
const menu = menuData.reduce((result, item) => {
|
||||||
if (hasRole(item.roles, groups)) {
|
if (hasRole(item.roles, groups)) {
|
||||||
@@ -55,14 +98,14 @@ export default function SideMenu() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<List>
|
<List>
|
||||||
{menu.map((item, index) => (
|
{menu.map((item, index) => {
|
||||||
<li key={index}>
|
const key = `menu-${index}`;
|
||||||
{item.to && <ListItemLink primary={item.label} to={item.to} />}
|
if (item.submenus)
|
||||||
{item.url && (
|
return <ExpandableSideMenuItem key={key} item={item} />;
|
||||||
<ListItemExternalLink primary={item.label} url={item.url} />
|
return <MenuItem key={key} item={item} />;
|
||||||
)}
|
})}
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</List>
|
</List>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default SideMenu;
|
||||||
|
|||||||
48
src/components/Layouts/UserMenu/index.jsx
Normal file
48
src/components/Layouts/UserMenu/index.jsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { Button, Fade, Menu, MenuItem } from "@material-ui/core";
|
||||||
|
|
||||||
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
|
import { getName } from "../../../utils/jwt";
|
||||||
|
|
||||||
|
const UserMenu = (props) => {
|
||||||
|
const { signOut, token } = useUserContext();
|
||||||
|
const [anchorEl, setAnchorEl] = useState(null);
|
||||||
|
const open = Boolean(anchorEl);
|
||||||
|
|
||||||
|
const handleClick = (event) => {
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSignOut = () => {
|
||||||
|
document.location = signOut();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div {...props}>
|
||||||
|
<Button
|
||||||
|
aria-controls="fade-menu"
|
||||||
|
aria-haspopup="true"
|
||||||
|
onClick={handleClick}
|
||||||
|
color="inherit"
|
||||||
|
>
|
||||||
|
{getName(token)}
|
||||||
|
</Button>
|
||||||
|
<Menu
|
||||||
|
id="fade-menu"
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
keepMounted
|
||||||
|
open={open}
|
||||||
|
onClose={handleClose}
|
||||||
|
TransitionComponent={Fade}
|
||||||
|
>
|
||||||
|
<MenuItem onClick={handleSignOut}>Sign out</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UserMenu;
|
||||||
@@ -16,6 +16,20 @@ exports[`SideMenu Authenticated 1`] = `
|
|||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="MuiListItemIcon-root"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="MuiListItemText-root"
|
class="MuiListItemText-root"
|
||||||
>
|
>
|
||||||
@@ -30,28 +44,6 @@ exports[`SideMenu Authenticated 1`] = `
|
|||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
aria-disabled="false"
|
|
||||||
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
|
|
||||||
href="/dashboard"
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="MuiListItemText-root"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
|
|
||||||
>
|
|
||||||
Dashboard
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
class="MuiTouchRipple-root"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
aria-disabled="false"
|
aria-disabled="false"
|
||||||
@@ -61,34 +53,26 @@ exports[`SideMenu Authenticated 1`] = `
|
|||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="MuiListItemText-root"
|
class="MuiListItemIcon-root"
|
||||||
>
|
>
|
||||||
<span
|
<svg
|
||||||
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
Deploy Packages
|
<path
|
||||||
</span>
|
d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM17 13l-5 5-5-5h3V9h4v4h3z"
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
class="MuiTouchRipple-root"
|
|
||||||
/>
|
/>
|
||||||
</a>
|
</svg>
|
||||||
</li>
|
</div>
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
aria-disabled="false"
|
|
||||||
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
|
|
||||||
href="/package-create"
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
class="MuiListItemText-root"
|
class="MuiListItemText-root"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
|
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
|
||||||
>
|
>
|
||||||
Create Package
|
Deployments
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
@@ -104,13 +88,90 @@ exports[`SideMenu Authenticated 1`] = `
|
|||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="MuiListItemIcon-root"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 4H5C3.34 4 2 5.34 2 7v8c0 1.66 1.34 3 3 3l-1 1v1h1l2-2.03L9 18v-5H4V5.98L13 6v2h2V7c0-1.66-1.34-3-3-3zM5 14c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm15.57-4.34c-.14-.4-.52-.66-.97-.66h-7.19c-.46 0-.83.26-.98.66L10 13.77l.01 5.51c0 .38.31.72.69.72h.62c.38 0 .68-.38.68-.76V18h8v1.24c0 .38.31.76.69.76h.61c.38 0 .69-.34.69-.72l.01-1.37v-4.14l-1.43-4.11zm-8.16.34h7.19l1.03 3h-9.25l1.03-3zM12 16c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm8 0c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="MuiListItemText-root"
|
class="MuiListItemText-root"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
|
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
|
||||||
>
|
>
|
||||||
View Vehicles
|
Vehicles
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<span>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
|
||||||
|
href="/datascope"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiListItemIcon-root"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiListItemText-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
|
||||||
|
>
|
||||||
|
Datascope
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</span>
|
||||||
|
<ul
|
||||||
|
style="margin-left: 50px;"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
|
||||||
|
href="/datascope/battery"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiListItemText-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
|
||||||
|
>
|
||||||
|
Battery
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
@@ -121,10 +182,13 @@ exports[`SideMenu Authenticated 1`] = `
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
aria-disabled="false"
|
aria-disabled="false"
|
||||||
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
|
class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary"
|
||||||
href="/vehicle-add"
|
href="https://grafana.fiskerdps.com"
|
||||||
|
rel="noopener"
|
||||||
role="button"
|
role="button"
|
||||||
|
style="text-decoration: inherit;"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
target="_blank"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="MuiListItemText-root"
|
class="MuiListItemText-root"
|
||||||
@@ -132,29 +196,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"
|
||||||
>
|
>
|
||||||
Add Vehicle
|
Diagnostics
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
class="MuiTouchRipple-root"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
@@ -163,6 +205,7 @@ exports[`SideMenu Authenticated 1`] = `
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -183,6 +226,20 @@ exports[`SideMenu Unauthenticated 1`] = `
|
|||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
class="MuiListItemIcon-root"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="MuiListItemText-root"
|
class="MuiListItemText-root"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { logger } from "../../../services/monitoring";
|
|||||||
import ECUFilesList from "../ECUFilesList";
|
import ECUFilesList from "../ECUFilesList";
|
||||||
|
|
||||||
const FileTemplate = {
|
const FileTemplate = {
|
||||||
name: "",
|
name: "AGS",
|
||||||
part_number: "",
|
part_number: "",
|
||||||
update_version: "1.0.0",
|
update_version: "1.0.0",
|
||||||
};
|
};
|
||||||
@@ -90,7 +90,7 @@ const UploadProgress = (props) => {
|
|||||||
const MainForm = () => {
|
const MainForm = () => {
|
||||||
const { createManifest, cancelUpload, busy } = useManifestsContext();
|
const { createManifest, cancelUpload, busy } = useManifestsContext();
|
||||||
const { token } = useUserContext();
|
const { token } = useUserContext();
|
||||||
const { setMessage, setTitle } = useStatusContext();
|
const { setMessage, setTitle, setSitePath } = useStatusContext();
|
||||||
const [redirect, setRedirect] = useState(null);
|
const [redirect, setRedirect] = useState(null);
|
||||||
const [fileIndex, setFileIndex] = useState(0);
|
const [fileIndex, setFileIndex] = useState(0);
|
||||||
const [ecuFiles, setECUFiles] = useState([]);
|
const [ecuFiles, setECUFiles] = useState([]);
|
||||||
@@ -113,7 +113,16 @@ const MainForm = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTitle("Create Package");
|
setTitle("Create Deployments");
|
||||||
|
setSitePath([
|
||||||
|
{
|
||||||
|
label: "Deployments",
|
||||||
|
link: "/packages",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Create Deployments",
|
||||||
|
},
|
||||||
|
]);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -236,7 +245,7 @@ const MainForm = () => {
|
|||||||
className={classes.submit}
|
className={classes.submit}
|
||||||
onClick={onSubmit}
|
onClick={onSubmit}
|
||||||
>
|
>
|
||||||
"Submit"
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
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, Grid, Typography } from "@material-ui/core";
|
import { Button, Grid, Typography } from "@material-ui/core";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ManifestsProvider,
|
ManifestsProvider,
|
||||||
useManifestsContext,
|
useManifestsContext,
|
||||||
@@ -27,7 +28,7 @@ const MainForm = () => {
|
|||||||
idToken: { jwtToken: token },
|
idToken: { jwtToken: token },
|
||||||
},
|
},
|
||||||
} = useUserContext();
|
} = useUserContext();
|
||||||
const { setMessage, setTitle } = useStatusContext();
|
const { setMessage, setTitle, setSitePath } = useStatusContext();
|
||||||
const [manifestName, setManifestName] = useState("");
|
const [manifestName, setManifestName] = useState("");
|
||||||
const [version, setVersion] = useState("");
|
const [version, setVersion] = useState("");
|
||||||
const [createDate, setCreateDate] = useState("");
|
const [createDate, setCreateDate] = useState("");
|
||||||
@@ -93,7 +94,17 @@ const MainForm = () => {
|
|||||||
}, [token]);
|
}, [token]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTitle(`Deploy ${manifestName} ${version}`);
|
const title = `Deploy ${manifestName} ${version}`;
|
||||||
|
setTitle(title);
|
||||||
|
setSitePath([
|
||||||
|
{
|
||||||
|
label: "Deployments",
|
||||||
|
link: "/packages",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: title,
|
||||||
|
},
|
||||||
|
]);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [manifestName, version]);
|
}, [manifestName, version]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import SubList from "../../Controls/SubList";
|
import SubList from "../../Controls/SubList";
|
||||||
|
import ECUDropDrop from "../../Controls/ECUDropDown";
|
||||||
|
|
||||||
const ECUFilesList = ({ data, onChange }) => {
|
const ECUFilesList = ({ data, onChange }) => {
|
||||||
const options = [
|
const options = [
|
||||||
@@ -11,6 +12,7 @@ const ECUFilesList = ({ data, onChange }) => {
|
|||||||
{
|
{
|
||||||
label: "ECU",
|
label: "ECU",
|
||||||
field: "name",
|
field: "name",
|
||||||
|
control: ECUDropDrop,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Part Number",
|
label: "Part Number",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
Toolbar,
|
Toolbar,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
|
import AddCircleIcon from "@material-ui/icons/AddCircle";
|
||||||
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 DeleteIcon from "@material-ui/icons/Delete";
|
import DeleteIcon from "@material-ui/icons/Delete";
|
||||||
@@ -64,7 +65,7 @@ const MainForm = () => {
|
|||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const { getManifests, deleteManifest, manifests, totalManifests } =
|
const { getManifests, deleteManifest, manifests, totalManifests } =
|
||||||
useManifestsContext();
|
useManifestsContext();
|
||||||
const { setMessage, setTitle } = useStatusContext();
|
const { setMessage, setTitle, setSitePath } = useStatusContext();
|
||||||
const {
|
const {
|
||||||
token: {
|
token: {
|
||||||
idToken: { jwtToken: token },
|
idToken: { jwtToken: token },
|
||||||
@@ -86,7 +87,8 @@ const MainForm = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTitle("Deploy Packages");
|
setTitle("Deployments");
|
||||||
|
setSitePath([]);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -184,6 +186,9 @@ const MainForm = () => {
|
|||||||
return (
|
return (
|
||||||
<div className={classes.paper} style={{ height: 700, width: "100%" }}>
|
<div className={classes.paper} style={{ height: 700, width: "100%" }}>
|
||||||
<Toolbar className={classes.tableToolbar}>
|
<Toolbar className={classes.tableToolbar}>
|
||||||
|
<Link to="/package-create" className={classes.labelInline}>
|
||||||
|
<AddCircleIcon fontSize="large" />
|
||||||
|
</Link>
|
||||||
<SearchField classes={classes} onSearch={handleSearch} />
|
<SearchField classes={classes} onSearch={handleSearch} />
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
<Table>
|
<Table>
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import SubList from "../../Controls/SubList";
|
|
||||||
|
|
||||||
const ReleaseNotesList = ({ data, onChange }) => {
|
|
||||||
const options = [
|
|
||||||
{
|
|
||||||
label: "ID",
|
|
||||||
field: "data_id",
|
|
||||||
readonly: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Locale",
|
|
||||||
field: "locale",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "URL",
|
|
||||||
field: "url",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "",
|
|
||||||
delete: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return <SubList data={data} options={options} onChange={onChange} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ReleaseNotesList;
|
|
||||||
@@ -39,7 +39,7 @@ const MainForm = () => {
|
|||||||
startMonitor,
|
startMonitor,
|
||||||
stopMonitor,
|
stopMonitor,
|
||||||
} = useCarUpdatesContext();
|
} = useCarUpdatesContext();
|
||||||
const { setMessage, setTitle } = useStatusContext();
|
const { setMessage, setTitle, setSitePath } = useStatusContext();
|
||||||
const {
|
const {
|
||||||
token: {
|
token: {
|
||||||
idToken: { jwtToken: token },
|
idToken: { jwtToken: token },
|
||||||
@@ -60,7 +60,18 @@ const MainForm = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!manifests || manifests.length === 0) return;
|
if (!manifests || manifests.length === 0) return;
|
||||||
setTitle(`Manifest ${manifests[0].name} ${manifests[0].version}`);
|
const title = `Manifest ${manifests[0].name} ${manifests[0].version}`;
|
||||||
|
setTitle(title);
|
||||||
|
setSitePath([
|
||||||
|
{
|
||||||
|
label: "Deployments",
|
||||||
|
link: "/packages",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: title,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [manifests]);
|
}, [manifests]);
|
||||||
|
|
||||||
|
|||||||
@@ -10,13 +10,12 @@ const SSOForm = React.lazy(() => import("../SSOForm"));
|
|||||||
const Home = React.lazy(() => import("../Home"));
|
const Home = React.lazy(() => import("../Home"));
|
||||||
const VehicleAddForm = React.lazy(() => import("../Cars/Add"));
|
const VehicleAddForm = React.lazy(() => import("../Cars/Add"));
|
||||||
const PageNotFound = React.lazy(() => import("../404"));
|
const PageNotFound = React.lazy(() => import("../404"));
|
||||||
const UpdatePackageEdit = React.lazy(() => import("../UpdatePackages/Edit"));
|
|
||||||
const CarUpdatesDeploy = React.lazy(() => import("../CarUpdates/Deploy"));
|
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 SendCommandBulk = React.lazy(() => import("../Cars/SendCommandBulk"));
|
const SendCommandBulk = React.lazy(() => import("../Cars/SendCommandBulk"));
|
||||||
const Dashboard = React.lazy(() => import("../Dashboard"));
|
const Datascope = React.lazy(() => import("../Datascope/Home"));
|
||||||
|
const BatteryDatascope = React.lazy(() => import("../Datascope/Battery"));
|
||||||
const Manifests = React.lazy(() => import("../Manifest/List"));
|
const Manifests = React.lazy(() => import("../Manifest/List"));
|
||||||
const ManifestDeploy = React.lazy(() => import("../Manifest/Deploy"));
|
const ManifestDeploy = React.lazy(() => import("../Manifest/Deploy"));
|
||||||
const ManifestStatus = React.lazy(() => import("../Manifest/Status"));
|
const ManifestStatus = React.lazy(() => import("../Manifest/Status"));
|
||||||
@@ -41,14 +40,6 @@ const SiteRoutes = () => {
|
|||||||
type={TYPES.PROTECTED}
|
type={TYPES.PROTECTED}
|
||||||
token={token}
|
token={token}
|
||||||
/>
|
/>
|
||||||
<AuthRoute
|
|
||||||
path="/update/:id"
|
|
||||||
render={() => <UpdatePackageEdit />}
|
|
||||||
type={TYPES.PROTECTED}
|
|
||||||
token={token}
|
|
||||||
groups={groups}
|
|
||||||
roles={[Roles.CREATE]}
|
|
||||||
/>
|
|
||||||
<AuthRoute
|
<AuthRoute
|
||||||
path="/carupdate-deploy/:packageid"
|
path="/carupdate-deploy/:packageid"
|
||||||
render={() => <CarUpdatesDeploy />}
|
render={() => <CarUpdatesDeploy />}
|
||||||
@@ -67,11 +58,11 @@ const SiteRoutes = () => {
|
|||||||
/>
|
/>
|
||||||
<AuthRoute
|
<AuthRoute
|
||||||
path="/vehicles"
|
path="/vehicles"
|
||||||
render={() => <VehiclesList />}
|
render={() => <SendCommandBulk />}
|
||||||
type={TYPES.PROTECTED}
|
type={TYPES.PROTECTED}
|
||||||
token={token}
|
token={token}
|
||||||
groups={groups}
|
groups={groups}
|
||||||
roles={[Roles.READ, Roles.CREATE]}
|
roles={[Roles.CREATE]}
|
||||||
/>
|
/>
|
||||||
<AuthRoute
|
<AuthRoute
|
||||||
path="/vehicle-add"
|
path="/vehicle-add"
|
||||||
@@ -90,16 +81,16 @@ const SiteRoutes = () => {
|
|||||||
roles={[Roles.READ, Roles.CREATE]}
|
roles={[Roles.READ, Roles.CREATE]}
|
||||||
/>
|
/>
|
||||||
<AuthRoute
|
<AuthRoute
|
||||||
path="/vehicles-command"
|
path="/datascope/battery"
|
||||||
render={() => <SendCommandBulk />}
|
render={() => <BatteryDatascope />}
|
||||||
type={TYPES.PROTECTED}
|
type={TYPES.PROTECTED}
|
||||||
token={token}
|
token={token}
|
||||||
groups={groups}
|
groups={groups}
|
||||||
roles={[Roles.CREATE]}
|
roles={[Roles.READ, Roles.CREATE]}
|
||||||
/>
|
/>
|
||||||
<AuthRoute
|
<AuthRoute
|
||||||
path="/dashboard"
|
path="/datascope"
|
||||||
render={() => <Dashboard />}
|
render={() => <Datascope />}
|
||||||
type={TYPES.PROTECTED}
|
type={TYPES.PROTECTED}
|
||||||
token={token}
|
token={token}
|
||||||
groups={groups}
|
groups={groups}
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
jest.mock("../../Contexts/UserContext");
|
|
||||||
jest.mock("../../Contexts/FileUploadContext");
|
|
||||||
jest.mock("../../Contexts/VehicleContext");
|
|
||||||
|
|
||||||
import { BrowserRouter } from "react-router-dom";
|
|
||||||
import { render, cleanup, waitFor } from "@testing-library/react";
|
|
||||||
import FileUploadForm from "./index";
|
|
||||||
import { setToken } from "../../Contexts/UserContext";
|
|
||||||
import { StatusProvider } from "../../Contexts/StatusContext";
|
|
||||||
import { TEST_AUTH_OBJECT } from "../../../utils/testing"
|
|
||||||
|
|
||||||
describe("File Upload Form", () => {
|
|
||||||
it("Should render", async () => {
|
|
||||||
setToken(TEST_AUTH_OBJECT);
|
|
||||||
const { container } = render(<StatusProvider><BrowserRouter><FileUploadForm /></BrowserRouter></StatusProvider>);
|
|
||||||
await waitFor(() => {});
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
cleanup();
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,250 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`File Upload Form Should render 1`] = `
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
data-testid="mocked-fileuploadprovider"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="makeStyles-paper-3"
|
|
||||||
>
|
|
||||||
<form
|
|
||||||
action="{onSubmit}"
|
|
||||||
class="makeStyles-form-5"
|
|
||||||
novalidate=""
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
|
||||||
data-shrink="false"
|
|
||||||
for="packagename"
|
|
||||||
id="packagename-label"
|
|
||||||
>
|
|
||||||
Package name
|
|
||||||
<span
|
|
||||||
aria-hidden="true"
|
|
||||||
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
|
||||||
>
|
|
||||||
|
|
||||||
*
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<div
|
|
||||||
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
aria-invalid="false"
|
|
||||||
class="MuiInputBase-input MuiOutlinedInput-input"
|
|
||||||
id="packagename"
|
|
||||||
maxlength="255"
|
|
||||||
name="packagename"
|
|
||||||
required=""
|
|
||||||
type="text"
|
|
||||||
value=""
|
|
||||||
/>
|
|
||||||
<fieldset
|
|
||||||
aria-hidden="true"
|
|
||||||
class="PrivateNotchedOutline-root-40 MuiOutlinedInput-notchedOutline"
|
|
||||||
>
|
|
||||||
<legend
|
|
||||||
class="PrivateNotchedOutline-legendLabelled-42"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Package name
|
|
||||||
*
|
|
||||||
</span>
|
|
||||||
</legend>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
|
||||||
data-shrink="false"
|
|
||||||
for="version"
|
|
||||||
id="version-label"
|
|
||||||
>
|
|
||||||
Version
|
|
||||||
<span
|
|
||||||
aria-hidden="true"
|
|
||||||
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
|
||||||
>
|
|
||||||
|
|
||||||
*
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<div
|
|
||||||
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
aria-invalid="false"
|
|
||||||
class="MuiInputBase-input MuiOutlinedInput-input"
|
|
||||||
id="version"
|
|
||||||
maxlength="255"
|
|
||||||
name="version"
|
|
||||||
required=""
|
|
||||||
type="text"
|
|
||||||
value=""
|
|
||||||
/>
|
|
||||||
<fieldset
|
|
||||||
aria-hidden="true"
|
|
||||||
class="PrivateNotchedOutline-root-40 MuiOutlinedInput-notchedOutline"
|
|
||||||
>
|
|
||||||
<legend
|
|
||||||
class="PrivateNotchedOutline-legendLabelled-42"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Version
|
|
||||||
*
|
|
||||||
</span>
|
|
||||||
</legend>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
|
||||||
data-shrink="false"
|
|
||||||
for="description"
|
|
||||||
id="description-label"
|
|
||||||
>
|
|
||||||
Description
|
|
||||||
<span
|
|
||||||
aria-hidden="true"
|
|
||||||
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
|
||||||
>
|
|
||||||
|
|
||||||
*
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<div
|
|
||||||
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl MuiInputBase-multiline MuiOutlinedInput-multiline"
|
|
||||||
>
|
|
||||||
<textarea
|
|
||||||
aria-invalid="false"
|
|
||||||
class="MuiInputBase-input MuiOutlinedInput-input MuiInputBase-inputMultiline MuiOutlinedInput-inputMultiline"
|
|
||||||
id="description"
|
|
||||||
maxlength="5120"
|
|
||||||
name="description"
|
|
||||||
placeholder="Package description"
|
|
||||||
required=""
|
|
||||||
rows="4"
|
|
||||||
/>
|
|
||||||
<fieldset
|
|
||||||
aria-hidden="true"
|
|
||||||
class="PrivateNotchedOutline-root-40 MuiOutlinedInput-notchedOutline"
|
|
||||||
>
|
|
||||||
<legend
|
|
||||||
class="PrivateNotchedOutline-legendLabelled-42"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Description
|
|
||||||
*
|
|
||||||
</span>
|
|
||||||
</legend>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
|
||||||
data-shrink="false"
|
|
||||||
for="releasenotes"
|
|
||||||
id="releasenotes-label"
|
|
||||||
>
|
|
||||||
Release Notes URL
|
|
||||||
<span
|
|
||||||
aria-hidden="true"
|
|
||||||
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
|
||||||
>
|
|
||||||
|
|
||||||
*
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<div
|
|
||||||
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
aria-invalid="false"
|
|
||||||
class="MuiInputBase-input MuiOutlinedInput-input"
|
|
||||||
id="releasenotes"
|
|
||||||
maxlength="1024"
|
|
||||||
name="releasenotes"
|
|
||||||
placeholder="Release Notes URL"
|
|
||||||
required=""
|
|
||||||
type="text"
|
|
||||||
value=""
|
|
||||||
/>
|
|
||||||
<fieldset
|
|
||||||
aria-hidden="true"
|
|
||||||
class="PrivateNotchedOutline-root-40 MuiOutlinedInput-notchedOutline"
|
|
||||||
>
|
|
||||||
<legend
|
|
||||||
class="PrivateNotchedOutline-legendLabelled-42"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Release Notes URL
|
|
||||||
*
|
|
||||||
</span>
|
|
||||||
</legend>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="MuiDropzoneArea-root"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
accept=""
|
|
||||||
autocomplete="off"
|
|
||||||
style="display: none;"
|
|
||||||
tabindex="-1"
|
|
||||||
type="file"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="MuiDropzoneArea-textContainer"
|
|
||||||
>
|
|
||||||
<p
|
|
||||||
class="MuiTypography-root MuiDropzoneArea-text MuiTypography-h5"
|
|
||||||
>
|
|
||||||
Drag and drop a file here or click
|
|
||||||
</p>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
class="MuiSvgIcon-root MuiDropzoneArea-icon"
|
|
||||||
focusable="false"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM14 13v4h-4v-4H7l5-5 5 5h-3z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-6 MuiButton-containedPrimary MuiButton-fullWidth"
|
|
||||||
tabindex="0"
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="MuiButton-label"
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="MuiTouchRipple-root"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
|
||||||
import { Redirect } from "react-router";
|
|
||||||
import { Button, TextField } from "@material-ui/core";
|
|
||||||
import { DropzoneArea } from "material-ui-dropzone";
|
|
||||||
|
|
||||||
import { useUserContext } from "../../Contexts/UserContext";
|
|
||||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
|
||||||
import {
|
|
||||||
useFileUploadContext,
|
|
||||||
FileUploadProvider,
|
|
||||||
} from "../../Contexts/FileUploadContext";
|
|
||||||
import ModalProgressBar from "../../ModalProgressBar";
|
|
||||||
import useStyles from "../../useStyles";
|
|
||||||
import { logger } from "../../../services/monitoring";
|
|
||||||
|
|
||||||
const FileUploadZone = ({ classes, token }) => {
|
|
||||||
const { setFiles } = useFileUploadContext();
|
|
||||||
const { setMessage } = useStatusContext();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<DropzoneArea
|
|
||||||
id="dropzone"
|
|
||||||
showPreviews={true}
|
|
||||||
showPreviewsInDropzone={false}
|
|
||||||
useChipsForPreview
|
|
||||||
previewGridProps={{ container: { spacing: 1, direction: "row" } }}
|
|
||||||
previewChipProps={{ classes: { root: classes.previewChip } }}
|
|
||||||
previewText="Selected files"
|
|
||||||
maxFileSize={1e9}
|
|
||||||
filesLimit={1}
|
|
||||||
showAlerts={false}
|
|
||||||
onChange={(files) => setFiles(files)}
|
|
||||||
onDelete={(files) => setFiles(files)}
|
|
||||||
onDropRejected={(files) => {
|
|
||||||
setMessage(`Rejected ${files[0].name} too large`);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<ModalProgressBar />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const MainForm = () => {
|
|
||||||
const { uploading, upload, files, cancel } = useFileUploadContext();
|
|
||||||
const { token } = useUserContext();
|
|
||||||
const { setMessage, setTitle } = useStatusContext();
|
|
||||||
const [redirect, setRedirect] = useState(null);
|
|
||||||
const classes = useStyles();
|
|
||||||
const packagenameEl = useRef(null);
|
|
||||||
const versionEl = useRef(null);
|
|
||||||
const descEl = useRef(null);
|
|
||||||
const releasenotesEl = useRef(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTitle("Create Update Package");
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onSubmit = async (event) => {
|
|
||||||
try {
|
|
||||||
event.preventDefault();
|
|
||||||
const {
|
|
||||||
idToken: { jwtToken: authToken },
|
|
||||||
} = token;
|
|
||||||
const formData = {
|
|
||||||
packagename: packagenameEl.current.value,
|
|
||||||
version: versionEl.current.value,
|
|
||||||
description: descEl.current.value,
|
|
||||||
releasenotes: releasenotesEl.current.value,
|
|
||||||
};
|
|
||||||
const result = await upload(formData, authToken, files);
|
|
||||||
|
|
||||||
if (!result || result.error) return;
|
|
||||||
|
|
||||||
cancel();
|
|
||||||
setMessage(`Package uploaded`);
|
|
||||||
setRedirect(`/carupdate-deploy/${result.id}`);
|
|
||||||
} catch (e) {
|
|
||||||
setMessage(e.message);
|
|
||||||
logger.warn(e.stack);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (redirect && redirect.length > 0) {
|
|
||||||
return <Redirect to={redirect} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.paper}>
|
|
||||||
<form className={classes.form} noValidate action="{onSubmit}">
|
|
||||||
<TextField
|
|
||||||
id="packagename"
|
|
||||||
name="packagename"
|
|
||||||
label="Package name"
|
|
||||||
variant="outlined"
|
|
||||||
margin="normal"
|
|
||||||
inputProps={{
|
|
||||||
maxLength: "255",
|
|
||||||
}}
|
|
||||||
required
|
|
||||||
fullWidth
|
|
||||||
inputRef={packagenameEl}
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
id="version"
|
|
||||||
name="version"
|
|
||||||
label="Version"
|
|
||||||
variant="outlined"
|
|
||||||
margin="normal"
|
|
||||||
inputProps={{
|
|
||||||
maxLength: "255",
|
|
||||||
}}
|
|
||||||
required
|
|
||||||
fullWidth
|
|
||||||
inputRef={versionEl}
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
id="description"
|
|
||||||
name="description"
|
|
||||||
label="Description"
|
|
||||||
variant="outlined"
|
|
||||||
margin="normal"
|
|
||||||
inputProps={{
|
|
||||||
maxLength: "5120",
|
|
||||||
}}
|
|
||||||
required
|
|
||||||
fullWidth
|
|
||||||
multiline
|
|
||||||
rows={4}
|
|
||||||
placeholder="Package description"
|
|
||||||
inputRef={descEl}
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
id="releasenotes"
|
|
||||||
name="releasenotes"
|
|
||||||
label="Release Notes URL"
|
|
||||||
variant="outlined"
|
|
||||||
margin="normal"
|
|
||||||
inputProps={{
|
|
||||||
maxLength: "1024",
|
|
||||||
}}
|
|
||||||
required
|
|
||||||
fullWidth
|
|
||||||
placeholder="Release Notes URL"
|
|
||||||
inputRef={releasenotesEl}
|
|
||||||
/>
|
|
||||||
<FileUploadZone classes={classes} />
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
disabled={uploading}
|
|
||||||
fullWidth
|
|
||||||
variant="contained"
|
|
||||||
color="primary"
|
|
||||||
className={classes.submit}
|
|
||||||
onClick={onSubmit}
|
|
||||||
>
|
|
||||||
{uploading ? "Uploading..." : "Submit"}
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function FileUploadForm() {
|
|
||||||
return (
|
|
||||||
<FileUploadProvider>
|
|
||||||
<MainForm />
|
|
||||||
</FileUploadProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import { useParams } from "react-router";
|
|
||||||
import { Button, TextField } from "@material-ui/core";
|
|
||||||
|
|
||||||
import {
|
|
||||||
UpdatesProvider,
|
|
||||||
useUpdatesContext,
|
|
||||||
} from "../../Contexts/UpdatesContext";
|
|
||||||
import { useUserContext } from "../../Contexts/UserContext";
|
|
||||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
|
||||||
import useStyles from "../../useStyles";
|
|
||||||
import { tsLocalDateTimeString } from "../../../utils/dates";
|
|
||||||
import { logger } from "../../../services/monitoring";
|
|
||||||
|
|
||||||
const MainForm = () => {
|
|
||||||
const { id } = useParams();
|
|
||||||
const { getPackages, updatePackage, packages, busy } = useUpdatesContext();
|
|
||||||
const {
|
|
||||||
token: {
|
|
||||||
idToken: { jwtToken: token },
|
|
||||||
},
|
|
||||||
} = useUserContext();
|
|
||||||
const { setMessage, setTitle } = useStatusContext();
|
|
||||||
const [packageName, setPackageName] = useState("");
|
|
||||||
const [version, setVersion] = useState("");
|
|
||||||
const [link, setLink] = useState("");
|
|
||||||
const [description, setDescription] = useState("");
|
|
||||||
const [releaseNotesLink, setReleaseNotesLink] = useState("");
|
|
||||||
const [createDate, setCreateDate] = useState("");
|
|
||||||
const classes = useStyles();
|
|
||||||
const onSubmit = async (event) => {
|
|
||||||
try {
|
|
||||||
event.preventDefault();
|
|
||||||
const data = {
|
|
||||||
id: parseInt(id),
|
|
||||||
package_name: packageName,
|
|
||||||
version,
|
|
||||||
link,
|
|
||||||
desc: description,
|
|
||||||
release_notes: releaseNotesLink,
|
|
||||||
};
|
|
||||||
await updatePackage(data, token);
|
|
||||||
setMessage(`Updated ${packageName} ${version}`);
|
|
||||||
} catch (e) {
|
|
||||||
setMessage(e.message);
|
|
||||||
logger.warn(e.stack);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const getData = async () => {
|
|
||||||
try {
|
|
||||||
getPackages({ id: parseInt(id) }, token);
|
|
||||||
} catch (e) {
|
|
||||||
setMessage(e.message);
|
|
||||||
logger.warn(e.stack);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const handleChange = (event) => {
|
|
||||||
const field = event.target.id;
|
|
||||||
const value = event.target.value;
|
|
||||||
|
|
||||||
if (field === "packagename") {
|
|
||||||
setPackageName(value);
|
|
||||||
} else if (field === "version") {
|
|
||||||
setVersion(value);
|
|
||||||
} else if (field === "link") {
|
|
||||||
setLink(value);
|
|
||||||
} else if (field === "description") {
|
|
||||||
setDescription(value);
|
|
||||||
} else if (field === "releasenotes") {
|
|
||||||
setReleaseNotesLink(value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTitle(`Edit Update Package ${id}`);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getData();
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [token]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!packages || packages.length === 0) return;
|
|
||||||
var data = packages[0];
|
|
||||||
|
|
||||||
setPackageName(data.package_name);
|
|
||||||
setVersion(data.version);
|
|
||||||
setLink(data.link);
|
|
||||||
setDescription(data.desc || "");
|
|
||||||
setReleaseNotesLink(data.release_notes || "");
|
|
||||||
setCreateDate(tsLocalDateTimeString(data.timestamp));
|
|
||||||
}, [packages]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.paper}>
|
|
||||||
<form className={classes.form} noValidate action="{onSubmit}">
|
|
||||||
<TextField
|
|
||||||
label="Create Date"
|
|
||||||
variant="filled"
|
|
||||||
margin="normal"
|
|
||||||
inputProps={{
|
|
||||||
readOnly: true,
|
|
||||||
maxLength: "1024",
|
|
||||||
}}
|
|
||||||
value={createDate}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
id="packagename"
|
|
||||||
name="packagename"
|
|
||||||
label="Package name"
|
|
||||||
variant="outlined"
|
|
||||||
margin="normal"
|
|
||||||
inputProps={{
|
|
||||||
maxLength: "255",
|
|
||||||
}}
|
|
||||||
value={packageName}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
id="version"
|
|
||||||
name="version"
|
|
||||||
label="Version"
|
|
||||||
variant="outlined"
|
|
||||||
margin="normal"
|
|
||||||
inputProps={{
|
|
||||||
maxLength: "255",
|
|
||||||
}}
|
|
||||||
value={version}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
id="link"
|
|
||||||
name="link"
|
|
||||||
label="Package link"
|
|
||||||
variant="outlined"
|
|
||||||
margin="normal"
|
|
||||||
inputProps={{
|
|
||||||
maxLength: "1024",
|
|
||||||
}}
|
|
||||||
value={link}
|
|
||||||
onChange={handleChange}
|
|
||||||
required
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
id="description"
|
|
||||||
name="description"
|
|
||||||
label="Description"
|
|
||||||
variant="outlined"
|
|
||||||
margin="normal"
|
|
||||||
inputProps={{
|
|
||||||
maxLength: "5120",
|
|
||||||
}}
|
|
||||||
value={description}
|
|
||||||
onChange={handleChange}
|
|
||||||
fullWidth
|
|
||||||
multiline
|
|
||||||
rows={4}
|
|
||||||
placeholder="Package description"
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
id="releasenotes"
|
|
||||||
name="releasenotes"
|
|
||||||
label="Release Notes URL"
|
|
||||||
variant="outlined"
|
|
||||||
margin="normal"
|
|
||||||
inputProps={{
|
|
||||||
maxLength: "1024",
|
|
||||||
}}
|
|
||||||
value={releaseNotesLink}
|
|
||||||
onChange={handleChange}
|
|
||||||
fullWidth
|
|
||||||
placeholder="Release Notes URL"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
disabled={busy}
|
|
||||||
fullWidth
|
|
||||||
variant="contained"
|
|
||||||
color="primary"
|
|
||||||
className={classes.submit}
|
|
||||||
onClick={onSubmit}
|
|
||||||
>
|
|
||||||
{busy ? "Updating..." : "Submit"}
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const UpdatePackageEditForm = () => (
|
|
||||||
<UpdatesProvider>
|
|
||||||
<MainForm />
|
|
||||||
</UpdatesProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default UpdatePackageEditForm;
|
|
||||||
@@ -1,244 +0,0 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableFooter,
|
|
||||||
TablePagination,
|
|
||||||
TableRow,
|
|
||||||
Toolbar,
|
|
||||||
Tooltip,
|
|
||||||
} from "@material-ui/core";
|
|
||||||
import SendIcon from "@material-ui/icons/Send";
|
|
||||||
import VisibilityIcon from "@material-ui/icons/Visibility";
|
|
||||||
import DeleteIcon from "@material-ui/icons/Delete";
|
|
||||||
import useStyles from "../../useStyles";
|
|
||||||
import {
|
|
||||||
UpdatesProvider,
|
|
||||||
useUpdatesContext,
|
|
||||||
} from "../../Contexts/UpdatesContext";
|
|
||||||
import { useUserContext } from "../../Contexts/UserContext";
|
|
||||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
|
||||||
import { LocalDateTimeString } from "../../../utils/dates";
|
|
||||||
import { Roles, hasRole } from "../../../utils/roles";
|
|
||||||
import TableHeaderSortable from "../../Table/HeaderSortable";
|
|
||||||
import SearchField from "../../Controls/SearchField";
|
|
||||||
import { logger } from "../../../services/monitoring";
|
|
||||||
import ECUList from "../../Controls/ECUList";
|
|
||||||
|
|
||||||
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 classes = useStyles();
|
|
||||||
const [pageSize, setPageSize] = useState(10);
|
|
||||||
const [pageIndex, setPageIndex] = useState(0);
|
|
||||||
const [orderBy, setOrderBy] = useState("id");
|
|
||||||
const [order, setOrder] = useState("desc");
|
|
||||||
const [search, setSearch] = useState("");
|
|
||||||
const { getPackages, deletePackage, packages, totalPackages } =
|
|
||||||
useUpdatesContext();
|
|
||||||
const {
|
|
||||||
token: {
|
|
||||||
idToken: { jwtToken: token },
|
|
||||||
},
|
|
||||||
groups,
|
|
||||||
} = useUserContext();
|
|
||||||
const { setMessage, setTitle } = useStatusContext();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTitle("Deploy Packages");
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getPackages(
|
|
||||||
{
|
|
||||||
limit: pageSize,
|
|
||||||
offset: pageSize * pageIndex,
|
|
||||||
order: `${orderBy} ${order}`,
|
|
||||||
search,
|
|
||||||
},
|
|
||||||
token
|
|
||||||
);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [pageIndex, pageSize, token, orderBy, order, search]);
|
|
||||||
|
|
||||||
const handleChangePageIndex = (event, newIndex) => {
|
|
||||||
setPageIndex(newIndex);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChangePageSize = (event) => {
|
|
||||||
setPageSize(parseInt(event.target.value, 10));
|
|
||||||
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 onDelete = async (package_id) => {
|
|
||||||
try {
|
|
||||||
await deletePackage(parseInt(package_id), token);
|
|
||||||
} catch (e) {
|
|
||||||
setMessage(e.message);
|
|
||||||
logger.warn(e.stack);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const Actions = (row) => {
|
|
||||||
let actions = [];
|
|
||||||
if (hasRole([Roles.CREATE, Roles.READ], groups)) {
|
|
||||||
actions.push({
|
|
||||||
tip: `Status "${row.package_name} ${row.version}"`,
|
|
||||||
link: `/carupdate-status/${row.id}`,
|
|
||||||
icon: (
|
|
||||||
<VisibilityIcon
|
|
||||||
aria-label={`Status ${row.package_name} ${row.version}`}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (hasRole([Roles.CREATE], groups)) {
|
|
||||||
actions = actions.concat([
|
|
||||||
{
|
|
||||||
tip: `Deploy "${row.package_name} ${row.version}"`,
|
|
||||||
link: `/carupdate-deploy/${row.id}`,
|
|
||||||
icon: (
|
|
||||||
<SendIcon
|
|
||||||
aria-label={`Deploy ${row.package_name} ${row.version}`}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tip: `Delete "${row.package_name} ${row.version}"`,
|
|
||||||
id: row.id,
|
|
||||||
icon: (
|
|
||||||
<DeleteIcon
|
|
||||||
aria-label={`Delete ${row.package_name} ${row.version}`}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actions.length === 0) return "No actions";
|
|
||||||
|
|
||||||
return actions.map((action) => {
|
|
||||||
if (action.link != null) {
|
|
||||||
return (
|
|
||||||
<Tooltip key={action.link} title={action.tip}>
|
|
||||||
<Link to={action.link} style={{ margin: 5 }}>
|
|
||||||
{action.icon}
|
|
||||||
</Link>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<Tooltip key={`delete-${action.id}`} title={action.tip}>
|
|
||||||
<Link to="#" onClick={() => onDelete(action.id)}>
|
|
||||||
{action.icon}
|
|
||||||
</Link>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.paper} style={{ height: 700, width: "100%" }}>
|
|
||||||
<Toolbar className={classes.tableToolbar}>
|
|
||||||
<SearchField classes={classes} onSearch={handleSearch} />
|
|
||||||
</Toolbar>
|
|
||||||
<Table>
|
|
||||||
<TableHeaderSortable
|
|
||||||
classes={classes}
|
|
||||||
orderBy={orderBy}
|
|
||||||
order={order}
|
|
||||||
columnData={tableColumns}
|
|
||||||
onSortRequest={handleSort}
|
|
||||||
/>
|
|
||||||
<TableBody>
|
|
||||||
{packages.map((row) => (
|
|
||||||
<TableRow key={row.id}>
|
|
||||||
<TableCell align="center">{row.id}</TableCell>
|
|
||||||
<TableCell align="center">
|
|
||||||
{row.package_name}
|
|
||||||
{row.ecu_list && (
|
|
||||||
<>
|
|
||||||
<br />
|
|
||||||
<ECUList list={row.ecu_list} search={search} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="center">{row.version}</TableCell>
|
|
||||||
<TableCell align="center">
|
|
||||||
{LocalDateTimeString(row.created)}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="center">{Actions(row)}</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
<TableFooter>
|
|
||||||
<TableRow>
|
|
||||||
<TablePagination
|
|
||||||
rowsPerPageOptions={[5, 10, 25, 100]}
|
|
||||||
colSpan={5}
|
|
||||||
count={totalPackages}
|
|
||||||
rowsPerPage={pageSize}
|
|
||||||
page={pageIndex}
|
|
||||||
SelectProps={{
|
|
||||||
inputProps: { "aria-label": "rows per page" },
|
|
||||||
native: true,
|
|
||||||
}}
|
|
||||||
onPageChange={handleChangePageIndex}
|
|
||||||
onRowsPerPageChange={handleChangePageSize}
|
|
||||||
/>
|
|
||||||
</TableRow>
|
|
||||||
</TableFooter>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const UpdatePackagesForm = () => (
|
|
||||||
<UpdatesProvider>
|
|
||||||
<UpdatePackagesList />
|
|
||||||
</UpdatesProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default UpdatePackagesForm;
|
|
||||||
@@ -7,8 +7,8 @@ import { Button } from "@material-ui/core";
|
|||||||
import { useUserContext } from "../Contexts/UserContext";
|
import { useUserContext } from "../Contexts/UserContext";
|
||||||
import { useVehicleContext, VehicleProvider } from "../Contexts/VehicleContext";
|
import { useVehicleContext, VehicleProvider } from "../Contexts/VehicleContext";
|
||||||
import { VehiclePopUp } from "./popup";
|
import { VehiclePopUp } from "./popup";
|
||||||
import GreenCarIcon from "../../assets/green-car.png";
|
import GreenMarkerIcon from "../../assets/green-marker.png";
|
||||||
import RedCarIcon from "../../assets/red-car.png";
|
import GrayMarkerIcon from "../../assets/gray-marker.png";
|
||||||
|
|
||||||
const Component = () => {
|
const Component = () => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
@@ -51,17 +51,17 @@ const Component = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const centerAroundMarkers = (markers) => {
|
const centerAroundMarkers = (markers) => {
|
||||||
if (markers == null) {
|
// if (markers == null) {
|
||||||
markers = []
|
// markers = []
|
||||||
}
|
// }
|
||||||
const coord = markers.reduce((coord, marker) => {
|
// const coord = markers.reduce((coord, marker) => {
|
||||||
coord[0] += marker[0] / markers.length;
|
// coord[0] += marker[0] / markers.length;
|
||||||
coord[1] += marker[1] / markers.length;
|
// coord[1] += marker[1] / markers.length;
|
||||||
return coord;
|
// return coord;
|
||||||
}, [0, 0])
|
// }, [0, 0])
|
||||||
|
|
||||||
setCenter(coord);
|
setCenter([37.0902, -95.7129]);
|
||||||
setZoom(4);
|
setZoom(4.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [connections, setConnections] = useState({});
|
const [connections, setConnections] = useState({});
|
||||||
@@ -109,17 +109,15 @@ const Component = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function getCarIcon(vin) {
|
function getCarIcon(vin) {
|
||||||
let icon = RedCarIcon;
|
let icon = GrayMarkerIcon;
|
||||||
|
|
||||||
if (connections[vin]) {
|
if (connections[vin]) {
|
||||||
icon = GreenCarIcon;
|
icon = GreenMarkerIcon;
|
||||||
} else {
|
|
||||||
icon = RedCarIcon;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new L.Icon({
|
return new L.Icon({
|
||||||
iconUrl: icon,
|
iconUrl: icon,
|
||||||
iconAnchor: [15, 0]
|
iconAnchor: [24, 42]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -202,6 +202,35 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
width: "100%",
|
width: "100%",
|
||||||
textAlign: "right",
|
textAlign: "right",
|
||||||
},
|
},
|
||||||
|
breadcrumbs: {
|
||||||
|
fontSize: "8px",
|
||||||
|
},
|
||||||
|
addButton: {
|
||||||
|
fontSize: "large",
|
||||||
|
position: "relative",
|
||||||
|
top: 100,
|
||||||
|
left: 100,
|
||||||
|
},
|
||||||
|
batteryGrid: {},
|
||||||
|
batteryForm: {
|
||||||
|
alignItems: "stretch",
|
||||||
|
flexDirection: "column",
|
||||||
|
},
|
||||||
|
grafanaContainer: {
|
||||||
|
alignContent: "stretch",
|
||||||
|
alignItems: "center",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "center",
|
||||||
|
padding: 15,
|
||||||
|
paddingBottom: 20,
|
||||||
|
},
|
||||||
|
datascopeContainerText: {
|
||||||
|
margin: 0,
|
||||||
|
},
|
||||||
|
datascopeContainerValue: {
|
||||||
|
margin: 0,
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default useStyles;
|
export default useStyles;
|
||||||
|
|||||||
19
src/services/__mocks__/grafana.js
Normal file
19
src/services/__mocks__/grafana.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
const grafanaAPI = {
|
||||||
|
getCarsCount: async () => fetch(`${API_ENDPOINT}/?query=SELECT%20countDistinct(vin)%20as%20count%0AFROM%20default.vehicle_data%20FORMAT%20JSON`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: Object.assign({ "Content-Type": "application/json" }),
|
||||||
|
})
|
||||||
|
.then(fetchRespHandler)
|
||||||
|
.then(result => result.data[0].count)
|
||||||
|
.catch(error => console.log(error)),
|
||||||
|
|
||||||
|
getSignalsCount: async () => fetch(`${API_ENDPOINT}/?query=SELECT%20count()%20as%20count%0AFROM%20default.vehicle_signal%20FORMAT%20JSON`, {
|
||||||
|
method: "GET",
|
||||||
|
headers: Object.assign({ "Content-Type": "application/json" }),
|
||||||
|
})
|
||||||
|
.then(fetchRespHandler)
|
||||||
|
.then(result => result.data[0].count)
|
||||||
|
.catch(error => console.log(error)),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default vehiclesAPI;
|
||||||
6
src/services/grafana.js
Normal file
6
src/services/grafana.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const grafanaAPI = {
|
||||||
|
getCarsCount: async () => 500,
|
||||||
|
getSignalsCount: async () => 1234567890,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default grafanaAPI;
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
const DEFAULT_GREETING = "Human";
|
||||||
|
|
||||||
export const parsePayload = (token) => {
|
export const parsePayload = (token) => {
|
||||||
if (!token) return null;
|
if (!token) return null;
|
||||||
const parts = token.split(".");
|
const parts = token.split(".");
|
||||||
@@ -12,3 +14,14 @@ export const decode = (payload) => {
|
|||||||
}
|
}
|
||||||
return atob(payload);
|
return atob(payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getName = (token) => {
|
||||||
|
if (!token || !token.idToken || !token.idToken.jwtToken)
|
||||||
|
return DEFAULT_GREETING;
|
||||||
|
|
||||||
|
const payload = parsePayload(token.idToken.jwtToken);
|
||||||
|
|
||||||
|
if (!payload || !payload.given_name) return DEFAULT_GREETING;
|
||||||
|
|
||||||
|
return payload.given_name;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user