@@ -31,17 +31,14 @@ export default function MenuDrawer({ children }) {
})}
>
-
- {title}
-
+
+
+ {title}
+
+
+
{token !== null && (
-
+
)}
diff --git a/src/components/Layouts/SideMenu.jsx b/src/components/Layouts/SideMenu.jsx
index 0e7587e..44aebda 100644
--- a/src/components/Layouts/SideMenu.jsx
+++ b/src/components/Layouts/SideMenu.jsx
@@ -4,51 +4,89 @@ import ListItemLink from "../ListItemLink";
import ListItemExternalLink from "../ListItemExternalLink";
import { useUserContext } from "../Contexts/UserContext";
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 = [
{
label: "Home",
to: "/home",
+ icon:
,
roles: [],
},
{
- label: "Dashboard",
- to: "/dashboard",
+ label: "Deployments",
+ to: "/packages",
+ icon:
,
roles: [Roles.CREATE, Roles.READ],
},
{
- label: "Deploy Packages",
- to: "/updates",
- roles: [Roles.CREATE, Roles.READ],
- },
- {
- label: "Create Package",
- to: "/package-upload",
- roles: [Roles.CREATE],
- },
- {
- label: "Deploy Manifest",
- to: "/manifests",
- roles: [Roles.CREATE, Roles.READ],
- },
- {
- label: "View Vehicles",
+ label: "Vehicles",
to: "/vehicles",
+ icon:
,
+ roles: [Roles.CREATE],
+ },
+ {
+ label: "Datascope",
+ to: "/datascope",
+ icon:
,
roles: [Roles.CREATE, Roles.READ],
+ submenus: [
+ {
+ label: "Battery",
+ to: "/datascope/battery",
+ },
+ {
+ label: "Diagnostics",
+ url: "https://grafana.fiskerdps.com",
+ },
+ ],
},
- {
- label: "Add Vehicle",
- to: "/vehicle-add",
- roles: [Roles.CREATE],
- },
- {
- label: "Send Command",
- to: "/vehicles-command",
- roles: [Roles.CREATE],
- }
];
-export default function SideMenu() {
+const MenuItem = ({ item, children }) => {
+ return (
+
+ {item.to && (
+
+ )}
+ {item.url && (
+
+ )}
+ {children}
+
+ );
+};
+
+const ExpandableSideMenuItem = ({ item }) => {
+ /*
+ const [expanded, setExpanded] = useState(true);
+ const clickHandler = (e) => {
+ setExpanded(!expanded);
+ };
+ */
+
+ return (
+ <>
+
+
+
+
+ {item.submenus.map((subitem, index) => (
+
+ ))}
+
+ >
+ );
+};
+
+const SideMenu = () => {
const { groups } = useUserContext();
const menu = menuData.reduce((result, item) => {
if (hasRole(item.roles, groups)) {
@@ -60,14 +98,14 @@ export default function SideMenu() {
return (
- {menu.map((item, index) => (
-
- {item.to && }
- {item.url && (
-
- )}
-
- ))}
+ {menu.map((item, index) => {
+ const key = `menu-${index}`;
+ if (item.submenus)
+ return ;
+ return ;
+ })}
);
-}
+};
+
+export default SideMenu;
diff --git a/src/components/Layouts/UserMenu/index.jsx b/src/components/Layouts/UserMenu/index.jsx
new file mode 100644
index 0000000..a4da3a4
--- /dev/null
+++ b/src/components/Layouts/UserMenu/index.jsx
@@ -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 (
+
+
+
+
+ );
+};
+
+export default UserMenu;
diff --git a/src/components/Layouts/__snapshots__/SideMenu.test.jsx.snap b/src/components/Layouts/__snapshots__/SideMenu.test.jsx.snap
index 4859802..f36fe51 100644
--- a/src/components/Layouts/__snapshots__/SideMenu.test.jsx.snap
+++ b/src/components/Layouts/__snapshots__/SideMenu.test.jsx.snap
@@ -16,6 +16,20 @@ exports[`SideMenu Authenticated 1`] = `
role="button"
tabindex="0"
>
+
+
+
+ Datascope
+
+
+
+
+
+
+
@@ -205,6 +226,20 @@ exports[`SideMenu Unauthenticated 1`] = `
role="button"
tabindex="0"
>
+
diff --git a/src/components/Manifest/Create/index.jsx b/src/components/Manifest/Create/index.jsx
new file mode 100644
index 0000000..1bb6aac
--- /dev/null
+++ b/src/components/Manifest/Create/index.jsx
@@ -0,0 +1,262 @@
+import React, { useEffect, useRef, useState } from "react";
+import { Redirect } from "react-router";
+import {
+ Button,
+ Grid,
+ LinearProgress,
+ TextField,
+ Typography,
+} from "@material-ui/core";
+import { DropzoneArea } from "material-ui-dropzone";
+
+import { useUserContext } from "../../Contexts/UserContext";
+import { useStatusContext } from "../../Contexts/StatusContext";
+import {
+ useManifestsContext,
+ ManifestsProvider,
+} from "../../Contexts/ManifestsContext";
+import useStyles from "../../useStyles";
+import { logger } from "../../../services/monitoring";
+import ECUFilesList from "../ECUFilesList";
+
+const FileTemplate = {
+ name: "AGS",
+ part_number: "",
+ update_version: "1.0.0",
+};
+
+const UploadProgress = (props) => {
+ const { uploadProgress, uploadStatus, uploadFileIndex, uploadedFiles } =
+ useManifestsContext();
+ const [progress, setProgress] = useState(0);
+ const [completed, setCompleted] = useState(0);
+ const [total, setTotal] = useState(0);
+
+ useEffect(() => {
+ const x = uploadedFiles.reduce(
+ (current, { file }) => current + file.size,
+ 0
+ );
+ setTotal(x);
+ }, [uploadedFiles]);
+
+ useEffect(() => {
+ if (uploadFileIndex === 0 || uploadFileIndex >= uploadedFiles.length)
+ return;
+ let uploaded = 0;
+ uploadedFiles.forEach(({ file }, i) => {
+ if (i < uploadFileIndex) uploaded += file.size;
+ });
+ setCompleted(uploaded);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [uploadFileIndex]);
+
+ useEffect(() => {
+ if (total === 0 || uploadFileIndex >= uploadedFiles.length) return;
+ const { file } = uploadedFiles[uploadFileIndex];
+ const uploaded = completed + file.size * uploadProgress;
+ const x = Math.min(99, Math.floor((uploaded / total) * 100));
+ setProgress(x);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [uploadProgress, completed]);
+
+ return (
+ <>
+
+
+
+ {`File ${uploadFileIndex + 1} of ${
+ uploadedFiles.length
+ }. ${uploadStatus}`}
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+const MainForm = () => {
+ const { createManifest, cancelUpload, busy } = useManifestsContext();
+ const { token } = useUserContext();
+ const { setMessage, setTitle, setSitePath } = useStatusContext();
+ const [redirect, setRedirect] = useState(null);
+ const [fileIndex, setFileIndex] = useState(0);
+ const [ecuFiles, setECUFiles] = useState([]);
+ const classes = useStyles();
+ const packagenameEl = useRef(null);
+ const versionEl = useRef(null);
+ const descEl = useRef(null);
+ const releasenotesEl = useRef(null);
+
+ const getNewFile = (file) => {
+ setFileIndex(fileIndex + 1);
+ return Object.assign(
+ { data_id: fileIndex, filename: file.name, file },
+ FileTemplate
+ );
+ };
+
+ const addFile = (file) => {
+ setECUFiles(ecuFiles.concat(getNewFile(file)));
+ };
+
+ useEffect(() => {
+ setTitle("Create Deployments");
+ setSitePath([
+ {
+ label: "Deployments",
+ link: "/packages",
+ },
+ {
+ label: "Create Deployments",
+ },
+ ]);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ const onSubmit = async (event) => {
+ try {
+ event.preventDefault();
+ const {
+ idToken: { jwtToken: authToken },
+ } = token;
+ const formData = {
+ name: packagenameEl.current.value,
+ version: versionEl.current.value,
+ description: descEl.current.value,
+ releasenotes: releasenotesEl.current.value,
+ files: ecuFiles,
+ };
+ const manifest = await createManifest(formData, authToken);
+
+ if (!manifest || manifest.error) return;
+
+ cancelUpload();
+ setMessage(`Package uploaded`);
+ setRedirect(`/package-deploy/${manifest.id}`);
+ } catch (e) {
+ setMessage(e.message);
+ logger.warn(e.stack);
+ }
+ };
+
+ if (redirect && redirect.length > 0) {
+ return
;
+ }
+
+ return (
+
+
+
+ );
+};
+
+export default function FileUploadForm() {
+ return (
+
+
+
+ );
+}
diff --git a/src/components/Manifest/Deploy/index.jsx b/src/components/Manifest/Deploy/index.jsx
index 5b5d452..f2d7bc2 100644
--- a/src/components/Manifest/Deploy/index.jsx
+++ b/src/components/Manifest/Deploy/index.jsx
@@ -1,6 +1,7 @@
import React, { useEffect, useState } from "react";
import { useParams, Redirect } from "react-router";
import { Button, Grid, Typography } from "@material-ui/core";
+
import {
ManifestsProvider,
useManifestsContext,
@@ -27,7 +28,7 @@ const MainForm = () => {
idToken: { jwtToken: token },
},
} = useUserContext();
- const { setMessage, setTitle } = useStatusContext();
+ const { setMessage, setTitle, setSitePath } = useStatusContext();
const [manifestName, setManifestName] = useState("");
const [version, setVersion] = useState("");
const [createDate, setCreateDate] = useState("");
@@ -71,7 +72,7 @@ const MainForm = () => {
setMessage(
`Deployed ${manifestName} ${version} to ${selected.length} cars`
);
- setRedirect(`/manifest-status/${manifest_id}`);
+ setRedirect(`/package-status/${manifest_id}`);
} catch (e) {
setMessage(e.message);
logger.warn(e.stack);
@@ -93,7 +94,17 @@ const MainForm = () => {
}, [token]);
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
}, [manifestName, version]);
diff --git a/src/components/Manifest/ECUFilesList/index.jsx b/src/components/Manifest/ECUFilesList/index.jsx
new file mode 100644
index 0000000..05c7608
--- /dev/null
+++ b/src/components/Manifest/ECUFilesList/index.jsx
@@ -0,0 +1,39 @@
+import React from "react";
+import SubList from "../../Controls/SubList";
+import ECUDropDrop from "../../Controls/ECUDropDown";
+
+const ECUFilesList = ({ data, onChange }) => {
+ const options = [
+ {
+ label: "ID",
+ field: "data_id",
+ readonly: true,
+ },
+ {
+ label: "ECU",
+ field: "name",
+ control: ECUDropDrop,
+ },
+ {
+ label: "Part Number",
+ field: "part_number",
+ },
+ {
+ label: "Version",
+ field: "update_version",
+ },
+ {
+ label: "File",
+ field: "filename",
+ readonly: true,
+ },
+ {
+ label: "",
+ delete: true,
+ },
+ ];
+
+ return
;
+};
+
+export default ECUFilesList;
diff --git a/src/components/Manifest/List/index.jsx b/src/components/Manifest/List/index.jsx
index 948bd3f..a63b95a 100644
--- a/src/components/Manifest/List/index.jsx
+++ b/src/components/Manifest/List/index.jsx
@@ -10,6 +10,7 @@ import {
Toolbar,
Tooltip,
} from "@material-ui/core";
+import AddCircleIcon from "@material-ui/icons/AddCircle";
import SendIcon from "@material-ui/icons/Send";
import VisibilityIcon from "@material-ui/icons/Visibility";
import DeleteIcon from "@material-ui/icons/Delete";
@@ -64,7 +65,7 @@ const MainForm = () => {
const [search, setSearch] = useState("");
const { getManifests, deleteManifest, manifests, totalManifests } =
useManifestsContext();
- const { setMessage, setTitle } = useStatusContext();
+ const { setMessage, setTitle, setSitePath } = useStatusContext();
const {
token: {
idToken: { jwtToken: token },
@@ -86,7 +87,8 @@ const MainForm = () => {
};
useEffect(() => {
- setTitle("Deploy Manifest");
+ setTitle("Deployments");
+ setSitePath([]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -137,7 +139,7 @@ const MainForm = () => {
if (hasRole([Roles.CREATE, Roles.READ], groups)) {
actions.push({
tip: `Status "${row.name} ${row.version}"`,
- link: `/manifest-status/${row.id}`,
+ link: `/package-status/${row.id}`,
icon: (
),
@@ -147,7 +149,7 @@ const MainForm = () => {
actions = actions.concat([
{
tip: `Deploy "${row.name} ${row.version}"`,
- link: `/manifest-deploy/${row.id}`,
+ link: `/package-deploy/${row.id}`,
icon:
,
},
{
@@ -184,6 +186,9 @@ const MainForm = () => {
return (
+
+
+
diff --git a/src/components/Manifest/Status/index.jsx b/src/components/Manifest/Status/index.jsx
index 0006e15..b1bc624 100644
--- a/src/components/Manifest/Status/index.jsx
+++ b/src/components/Manifest/Status/index.jsx
@@ -39,7 +39,7 @@ const MainForm = () => {
startMonitor,
stopMonitor,
} = useCarUpdatesContext();
- const { setMessage, setTitle } = useStatusContext();
+ const { setMessage, setTitle, setSitePath } = useStatusContext();
const {
token: {
idToken: { jwtToken: token },
@@ -60,7 +60,18 @@ const MainForm = () => {
useEffect(() => {
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
}, [manifests]);
diff --git a/src/components/Routes/SiteRoutes.jsx b/src/components/Routes/SiteRoutes.jsx
index eaa77f7..47613a2 100644
--- a/src/components/Routes/SiteRoutes.jsx
+++ b/src/components/Routes/SiteRoutes.jsx
@@ -8,20 +8,18 @@ import { Roles } from "../../utils/roles";
const SSOForm = React.lazy(() => import("../SSOForm"));
const Home = React.lazy(() => import("../Home"));
-const FileUploadForm = React.lazy(() => import("../UpdatePackages/Create"));
const VehicleAddForm = React.lazy(() => import("../Cars/Add"));
const PageNotFound = React.lazy(() => import("../404"));
-const UpdatePackagesForm = React.lazy(() => import("../UpdatePackages/List"));
-const UpdatePackageEdit = React.lazy(() => import("../UpdatePackages/Edit"));
const CarUpdatesDeploy = React.lazy(() => import("../CarUpdates/Deploy"));
const CarUpdatesStatus = React.lazy(() => import("../CarUpdates/Status"));
const CarUpdates = React.lazy(() => import("../Cars/Status"));
-const VehiclesList = React.lazy(() => import("../Cars/List"));
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 ManifestDeploy = React.lazy(() => import("../Manifest/Deploy"));
const ManifestStatus = React.lazy(() => import("../Manifest/Status"));
+const ManifestCreate = React.lazy(() => import("../Manifest/Create"));
const SiteRoutes = () => {
const { token, groups } = useUserContext();
@@ -42,30 +40,6 @@ const SiteRoutes = () => {
type={TYPES.PROTECTED}
token={token}
/>
- }
- type={TYPES.PROTECTED}
- token={token}
- groups={groups}
- roles={[Roles.CREATE]}
- />
- }
- type={TYPES.PROTECTED}
- token={token}
- groups={groups}
- roles={[Roles.CREATE]}
- />
- }
- type={TYPES.PROTECTED}
- token={token}
- groups={groups}
- roles={[Roles.CREATE]}
- />
}
@@ -84,11 +58,11 @@ const SiteRoutes = () => {
/>
}
+ render={() => }
type={TYPES.PROTECTED}
token={token}
groups={groups}
- roles={[Roles.READ, Roles.CREATE]}
+ roles={[Roles.CREATE]}
/>
{
roles={[Roles.READ, Roles.CREATE]}
/>
}
- type={TYPES.PROTECTED}
- token={token}
- groups={groups}
- roles={[Roles.CREATE]}
- />
- }
+ path="/datascope/battery"
+ render={() => }
type={TYPES.PROTECTED}
token={token}
groups={groups}
roles={[Roles.READ, Roles.CREATE]}
/>
}
+ type={TYPES.PROTECTED}
+ token={token}
+ groups={groups}
+ roles={[Roles.READ, Roles.CREATE]}
+ />
+ }
type={TYPES.PROTECTED}
token={token}
@@ -131,7 +105,7 @@ const SiteRoutes = () => {
roles={[Roles.READ, Roles.CREATE]}
/>
}
type={TYPES.PROTECTED}
token={token}
@@ -139,13 +113,21 @@ const SiteRoutes = () => {
roles={[Roles.CREATE]}
/>
}
type={TYPES.PROTECTED}
token={token}
groups={groups}
roles={[Roles.READ, Roles.CREATE]}
/>
+ }
+ type={TYPES.PROTECTED}
+ token={token}
+ groups={groups}
+ roles={[Roles.CREATE]}
+ />
diff --git a/src/components/UpdatePackages/Create/Create.test.js b/src/components/UpdatePackages/Create/Create.test.js
deleted file mode 100644
index cf3799c..0000000
--- a/src/components/UpdatePackages/Create/Create.test.js
+++ /dev/null
@@ -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();
- await waitFor(() => {});
- expect(container).toMatchSnapshot();
- cleanup();
- })
-})
\ No newline at end of file
diff --git a/src/components/UpdatePackages/Create/__snapshots__/Create.test.js.snap b/src/components/UpdatePackages/Create/__snapshots__/Create.test.js.snap
deleted file mode 100644
index 724c403..0000000
--- a/src/components/UpdatePackages/Create/__snapshots__/Create.test.js.snap
+++ /dev/null
@@ -1,250 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`File Upload Form Should render 1`] = `
-
-`;
diff --git a/src/components/UpdatePackages/Create/index.jsx b/src/components/UpdatePackages/Create/index.jsx
deleted file mode 100644
index 2272d2e..0000000
--- a/src/components/UpdatePackages/Create/index.jsx
+++ /dev/null
@@ -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 (
- <>
- setFiles(files)}
- onDelete={(files) => setFiles(files)}
- onDropRejected={(files) => {
- setMessage(`Rejected ${files[0].name} too large`);
- }}
- />
-
- >
- );
-};
-
-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 ;
- }
-
- return (
-
-
-
- );
-};
-
-export default function FileUploadForm() {
- return (
-
-
-
- );
-}
diff --git a/src/components/UpdatePackages/Edit/index.jsx b/src/components/UpdatePackages/Edit/index.jsx
deleted file mode 100644
index e86283f..0000000
--- a/src/components/UpdatePackages/Edit/index.jsx
+++ /dev/null
@@ -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 (
-
-
-
- );
-};
-
-const UpdatePackageEditForm = () => (
-
-
-
-);
-
-export default UpdatePackageEditForm;
diff --git a/src/components/UpdatePackages/List/index.jsx b/src/components/UpdatePackages/List/index.jsx
deleted file mode 100644
index 79df2e0..0000000
--- a/src/components/UpdatePackages/List/index.jsx
+++ /dev/null
@@ -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: (
-
- ),
- });
- }
- if (hasRole([Roles.CREATE], groups)) {
- actions = actions.concat([
- {
- tip: `Deploy "${row.package_name} ${row.version}"`,
- link: `/carupdate-deploy/${row.id}`,
- icon: (
-
- ),
- },
- {
- tip: `Delete "${row.package_name} ${row.version}"`,
- id: row.id,
- icon: (
-
- ),
- },
- ]);
- }
-
- if (actions.length === 0) return "No actions";
-
- return actions.map((action) => {
- if (action.link != null) {
- return (
-
-
- {action.icon}
-
-
- );
- } else {
- return (
-
- onDelete(action.id)}>
- {action.icon}
-
-
- );
- }
- });
- };
-
- return (
-
-
-
-
-
-
-
- {packages.map((row) => (
-
- {row.id}
-
- {row.package_name}
- {row.ecu_list && (
- <>
-
-
- >
- )}
-
- {row.version}
-
- {LocalDateTimeString(row.created)}
-
- {Actions(row)}
-
- ))}
-
-
-
-
-
-
-
-
- );
-};
-
-const UpdatePackagesForm = () => (
-
-
-
-);
-
-export default UpdatePackagesForm;
diff --git a/src/components/VehicleMap/index.jsx b/src/components/VehicleMap/index.jsx
index 617a4c5..6ea4ab3 100644
--- a/src/components/VehicleMap/index.jsx
+++ b/src/components/VehicleMap/index.jsx
@@ -7,8 +7,8 @@ import { Button } from "@material-ui/core";
import { useUserContext } from "../Contexts/UserContext";
import { useVehicleContext, VehicleProvider } from "../Contexts/VehicleContext";
import { VehiclePopUp } from "./popup";
-import GreenCarIcon from "../../assets/green-car.png";
-import RedCarIcon from "../../assets/red-car.png";
+import GreenMarkerIcon from "../../assets/green-marker.png";
+import GrayMarkerIcon from "../../assets/gray-marker.png";
const Component = () => {
const classes = useStyles();
@@ -51,17 +51,17 @@ const Component = () => {
}
const centerAroundMarkers = (markers) => {
- if (markers == null) {
- markers = []
- }
- const coord = markers.reduce((coord, marker) => {
- coord[0] += marker[0] / markers.length;
- coord[1] += marker[1] / markers.length;
- return coord;
- }, [0, 0])
+ // if (markers == null) {
+ // markers = []
+ // }
+ // const coord = markers.reduce((coord, marker) => {
+ // coord[0] += marker[0] / markers.length;
+ // coord[1] += marker[1] / markers.length;
+ // return coord;
+ // }, [0, 0])
- setCenter(coord);
- setZoom(4);
+ setCenter([37.0902, -95.7129]);
+ setZoom(4.5);
}
const [connections, setConnections] = useState({});
@@ -109,17 +109,15 @@ const Component = () => {
};
function getCarIcon(vin) {
- let icon = RedCarIcon;
+ let icon = GrayMarkerIcon;
if (connections[vin]) {
- icon = GreenCarIcon;
- } else {
- icon = RedCarIcon;
+ icon = GreenMarkerIcon;
}
return new L.Icon({
iconUrl: icon,
- iconAnchor: [15, 0]
+ iconAnchor: [24, 42]
});
}
diff --git a/src/components/useStyles.jsx b/src/components/useStyles.jsx
index cbd3068..a653a66 100644
--- a/src/components/useStyles.jsx
+++ b/src/components/useStyles.jsx
@@ -174,7 +174,7 @@ const useStyles = makeStyles((theme) => ({
paddingTop: "56.25%",
},
closeButton: {
- position: 'absolute',
+ position: "absolute",
right: theme.spacing(1),
top: theme.spacing(1),
color: theme.palette.grey[500],
@@ -198,6 +198,39 @@ const useStyles = makeStyles((theme) => ({
},
paddingBottom: "2vh",
},
+ toolbarFooter: {
+ width: "100%",
+ 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;
diff --git a/src/services/__mocks__/grafana.js b/src/services/__mocks__/grafana.js
new file mode 100644
index 0000000..4424256
--- /dev/null
+++ b/src/services/__mocks__/grafana.js
@@ -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;
diff --git a/src/services/grafana.js b/src/services/grafana.js
new file mode 100644
index 0000000..8376f2c
--- /dev/null
+++ b/src/services/grafana.js
@@ -0,0 +1,6 @@
+const grafanaAPI = {
+ getCarsCount: async () => 500,
+ getSignalsCount: async () => 1234567890,
+};
+
+export default grafanaAPI;
diff --git a/src/services/manifests.js b/src/services/manifests.js
index cc1abb1..ade3d6b 100644
--- a/src/services/manifests.js
+++ b/src/services/manifests.js
@@ -18,6 +18,13 @@ const manifestsAPI = {
})
.then(fetchRespHandler);
},
+
+ createManifest: async (data, token) => fetch(`${API_ENDPOINT}/manifest`, {
+ method: "POST",
+ headers: Object.assign({ "Content-Type": "application/json" }, getAuthHeaderOptions(token)),
+ body: JSON.stringify(data),
+ })
+ .then(fetchRespHandler),
};
export default manifestsAPI;
diff --git a/src/services/uploadFile.js b/src/services/uploadFile.js
index 9111d1b..13fadf6 100644
--- a/src/services/uploadFile.js
+++ b/src/services/uploadFile.js
@@ -1,13 +1,14 @@
import axios from 'axios';
const UPLOAD_ENDPOINT = process.env.REACT_APP_UPLOAD_SERVICE_URL || "https://gw-dev.fiskerdps.com/ota_update";
+const fileField = "file";
export const getCancelToken = () => {
const token = axios.CancelToken;
return token.source();
}
-export const uploadFile = (file, data, token, onProgress, cancelToken) => {
+export const uploadFile = (data, token, onProgress, cancelToken) => {
const form = new FormData();
let options = {
method: "POST",
@@ -17,19 +18,23 @@ export const uploadFile = (file, data, token, onProgress, cancelToken) => {
},
cancelToken,
};
+
if (onProgress) {
options = {
...options,
onUploadProgress: (event) => {
- onProgress(Math.min(99, Math.floor((event.loaded / event.total) * 100)));
+ onProgress(event.loaded / event.total);
}
}
}
+
for (let key in data) {
- form.append(key, data[key]);
+ if (key !== fileField) form.append(key, data[key]);
}
- form.append("file", file);
- return axios.post(`${UPLOAD_ENDPOINT}/update`, form, options)
+
+ form.append(fileField, data[fileField]);
+
+ return axios.post(`${UPLOAD_ENDPOINT}/manifestfile`, form, options)
.then((response) => response.data)
.catch((error) => {
if (typeof error.response.data === "string") {
diff --git a/src/utils/jwt.js b/src/utils/jwt.js
index 9ff1297..f2572e9 100644
--- a/src/utils/jwt.js
+++ b/src/utils/jwt.js
@@ -1,3 +1,5 @@
+const DEFAULT_GREETING = "Human";
+
export const parsePayload = (token) => {
if (!token) return null;
const parts = token.split(".");
@@ -12,3 +14,14 @@ export const decode = (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;
+};