{
const [orderBy, setOrderBy] = useState("id");
const [order, setOrder] = useState("desc");
const { getCarUpdates, carUpdates, totalCarUpdates } = useUpdatesContext();
- const { setMessage, setTitle } = useStatusContext();
+ const { setMessage, setTitle, setSitePath } = useStatusContext();
const {
token: {
idToken: { jwtToken: token },
@@ -60,7 +60,17 @@ const MainForm = () => {
} = useUserContext();
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
}, [vin]);
diff --git a/src/components/Contexts/CarUpdatesContext.jsx b/src/components/Contexts/CarUpdatesContext.jsx
index 33f1f04..9ee8854 100644
--- a/src/components/Contexts/CarUpdatesContext.jsx
+++ b/src/components/Contexts/CarUpdatesContext.jsx
@@ -76,15 +76,17 @@ export const CarUpdatesProvider = ({ children }) => {
};
const applyProgressStatus = (item, status) => {
- if (status.msg === "DONE") {
+ if (status.msg === "package_download_complete") {
delete item.progress;
item.status = "downloaded";
- } else if (status.msg === "downloading" && status.total > 0) {
- let progress = Math.floor((100 * status.bytes) / status.total);
+ } else if (status.msg === "downloading" && status.package_total > 0) {
+ let progress = Math.floor(
+ (100 * status.package_current) / status.package_total
+ );
if (progress > 99) progress = 0;
item.progress = progress;
item.status = `downloading ${progress}%`;
- } else if (status.error > 0) {
+ } else if (status.error > 0 || status.msg === "download_error") {
item.status = "download error";
} else {
item.status = "downloading";
diff --git a/src/components/Contexts/ManifestsContext.jsx b/src/components/Contexts/ManifestsContext.jsx
index e54600b..3018387 100644
--- a/src/components/Contexts/ManifestsContext.jsx
+++ b/src/components/Contexts/ManifestsContext.jsx
@@ -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) => {
let result;
@@ -125,7 +135,8 @@ export const ManifestsProvider = ({ children }) => {
validateManifest(data, token);
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)
throw new Error(`Create manifest error. ${result.message}`);
diff --git a/src/components/Contexts/StatusContext.jsx b/src/components/Contexts/StatusContext.jsx
index 07064c0..bf3bfcb 100644
--- a/src/components/Contexts/StatusContext.jsx
+++ b/src/components/Contexts/StatusContext.jsx
@@ -5,14 +5,17 @@ const StatusContext = React.createContext();
export const StatusProvider = ({ children }) => {
const [message, setMessage] = useState(null);
const [title, setTitle] = useState("");
+ const [sitePath, setSitePath] = useState([]);
return (
{children}
diff --git a/src/components/Controls/ECUDropDown/index.jsx b/src/components/Controls/ECUDropDown/index.jsx
new file mode 100644
index 0000000..0ab44ec
--- /dev/null
+++ b/src/components/Controls/ECUDropDown/index.jsx
@@ -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 (
+
+ );
+};
+
+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"],
+];
diff --git a/src/components/Controls/SiteBreadCrumbs/index.jsx b/src/components/Controls/SiteBreadCrumbs/index.jsx
new file mode 100644
index 0000000..dd15229
--- /dev/null
+++ b/src/components/Controls/SiteBreadCrumbs/index.jsx
@@ -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 (
+
+ {path.map((item, index, items) => {
+ if (index < items.length) {
+ return (
+
+ {item.label}
+
+ );
+ }
+ return null;
+ })}
+
+ );
+};
+
+export default SiteBreadcrumbs;
diff --git a/src/components/Controls/SubListItem/index.jsx b/src/components/Controls/SubListItem/index.jsx
index 5877189..e6a805b 100644
--- a/src/components/Controls/SubListItem/index.jsx
+++ b/src/components/Controls/SubListItem/index.jsx
@@ -27,6 +27,15 @@ const DataDisplay = ({ data, option, onDelete }) => {
);
+ } else if (option.control) {
+ return (
+
+ );
}
return (
diff --git a/src/components/Dashboard/index.jsx b/src/components/Dashboard/index.jsx
deleted file mode 100644
index 816dcdf..0000000
--- a/src/components/Dashboard/index.jsx
+++ /dev/null
@@ -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 (
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-export default Dashboard;
diff --git a/src/components/Datascope/Battery/index.jsx b/src/components/Datascope/Battery/index.jsx
new file mode 100644
index 0000000..f752e90
--- /dev/null
+++ b/src/components/Datascope/Battery/index.jsx
@@ -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 (
+
+
+
+
+
+
+
+
+ Cell
+
+
+
+
+
+
+
+
+ Cell Voltage {cellNum}
+
+
+
+
+
+
+ Cell Temperature {cellNum}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Battery;
diff --git a/src/components/Datascope/Home/index.jsx b/src/components/Datascope/Home/index.jsx
new file mode 100644
index 0000000..22c409e
--- /dev/null
+++ b/src/components/Datascope/Home/index.jsx
@@ -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 (
+
+
+
+
+ {carsCount}
+ Cars
+
+
+
+
+
+ {signalsCount}
+
+ Signals Collected
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Datascope;
diff --git a/src/components/Home/index.jsx b/src/components/Home/index.jsx
index 5b4d7c3..ff649f4 100644
--- a/src/components/Home/index.jsx
+++ b/src/components/Home/index.jsx
@@ -4,38 +4,24 @@ import useStyles from "../useStyles";
import { useUserContext } from "../Contexts/UserContext";
import { useStatusContext } from "../Contexts/StatusContext";
-import { parsePayload } from "../../utils/jwt";
import VehicleMap from "../VehicleMap";
-
-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}!`;
-};
+import { getName } from "../../utils/jwt";
const Home = () => {
const classes = useStyles();
const { token } = useUserContext();
- const greeting = getGreeting(token);
- const { setTitle } = useStatusContext();
-
+ const { setTitle, setSitePath } = useStatusContext();
useEffect(() => {
setTitle("Home");
+ setSitePath([]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
- {greeting}
+ Welcome {getName(token)}!
diff --git a/src/components/Layouts/MenuDrawer.jsx b/src/components/Layouts/MenuDrawer.jsx
index 31594e4..6a19ead 100644
--- a/src/components/Layouts/MenuDrawer.jsx
+++ b/src/components/Layouts/MenuDrawer.jsx
@@ -1,26 +1,26 @@
import React from "react";
import clsx from "clsx";
-import Drawer from "@material-ui/core/Drawer";
-import AppBar from "@material-ui/core/AppBar";
-import Toolbar from "@material-ui/core/Toolbar";
-import Typography from "@material-ui/core/Typography";
-import Divider from "@material-ui/core/Divider";
+import {
+ Container,
+ Drawer,
+ AppBar,
+ Toolbar,
+ Typography,
+ Divider,
+} from "@material-ui/core";
import SideMenu from "./SideMenu";
import useStyles from "../useStyles";
import { useUserContext } from "../Contexts/UserContext";
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";
export default function MenuDrawer({ children }) {
const classes = useStyles();
- const { title } = useStatusContext();
- const { signOut, token } = useUserContext();
-
- const onSignOut = () => {
- document.location = signOut();
- };
+ const { title, sitePath } = useStatusContext();
+ const { token } = useUserContext();
return (
@@ -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 59fcadb..44aebda 100644
--- a/src/components/Layouts/SideMenu.jsx
+++ b/src/components/Layouts/SideMenu.jsx
@@ -4,46 +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",
- roles: [Roles.CREATE, Roles.READ],
- },
- {
- label: "Deploy Packages",
+ label: "Deployments",
to: "/packages",
+ icon:
,
roles: [Roles.CREATE, Roles.READ],
},
{
- label: "Create Package",
- to: "/package-create",
- roles: [Roles.CREATE],
- },
- {
- label: "View Vehicles",
+ label: "Vehicles",
to: "/vehicles",
+ icon:
,
+ roles: [Roles.CREATE],
+ },
+ {
+ label: "Datascope",
+ to: "/datascope",
+ icon:
,
roles: [Roles.CREATE, Roles.READ],
- },
- {
- label: "Add Vehicle",
- to: "/vehicle-add",
- roles: [Roles.CREATE],
- },
- {
- label: "Send Command",
- to: "/vehicles-command",
- roles: [Roles.CREATE],
+ submenus: [
+ {
+ label: "Battery",
+ to: "/datascope/battery",
+ },
+ {
+ label: "Diagnostics",
+ url: "https://grafana.fiskerdps.com",
+ },
+ ],
},
];
-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)) {
@@ -55,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 f09857f..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
+
+
+
+
+
+
+
@@ -183,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
index d79a571..1bb6aac 100644
--- a/src/components/Manifest/Create/index.jsx
+++ b/src/components/Manifest/Create/index.jsx
@@ -20,7 +20,7 @@ import { logger } from "../../../services/monitoring";
import ECUFilesList from "../ECUFilesList";
const FileTemplate = {
- name: "",
+ name: "AGS",
part_number: "",
update_version: "1.0.0",
};
@@ -90,7 +90,7 @@ const UploadProgress = (props) => {
const MainForm = () => {
const { createManifest, cancelUpload, busy } = useManifestsContext();
const { token } = useUserContext();
- const { setMessage, setTitle } = useStatusContext();
+ const { setMessage, setTitle, setSitePath } = useStatusContext();
const [redirect, setRedirect] = useState(null);
const [fileIndex, setFileIndex] = useState(0);
const [ecuFiles, setECUFiles] = useState([]);
@@ -113,7 +113,16 @@ const MainForm = () => {
};
useEffect(() => {
- setTitle("Create Package");
+ setTitle("Create Deployments");
+ setSitePath([
+ {
+ label: "Deployments",
+ link: "/packages",
+ },
+ {
+ label: "Create Deployments",
+ },
+ ]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -236,7 +245,7 @@ const MainForm = () => {
className={classes.submit}
onClick={onSubmit}
>
- "Submit"
+ Submit
)}
diff --git a/src/components/Manifest/Deploy/index.jsx b/src/components/Manifest/Deploy/index.jsx
index 9cd0bdf..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("");
@@ -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
index ed5e528..05c7608 100644
--- a/src/components/Manifest/ECUFilesList/index.jsx
+++ b/src/components/Manifest/ECUFilesList/index.jsx
@@ -1,5 +1,6 @@
import React from "react";
import SubList from "../../Controls/SubList";
+import ECUDropDrop from "../../Controls/ECUDropDown";
const ECUFilesList = ({ data, onChange }) => {
const options = [
@@ -11,6 +12,7 @@ const ECUFilesList = ({ data, onChange }) => {
{
label: "ECU",
field: "name",
+ control: ECUDropDrop,
},
{
label: "Part Number",
diff --git a/src/components/Manifest/List/index.jsx b/src/components/Manifest/List/index.jsx
index da326e7..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 Packages");
+ setTitle("Deployments");
+ setSitePath([]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -184,6 +186,9 @@ const MainForm = () => {
return (
+
+
+
diff --git a/src/components/Manifest/ReleaseNotesList/index.jsx b/src/components/Manifest/ReleaseNotesList/index.jsx
deleted file mode 100644
index 216ca3b..0000000
--- a/src/components/Manifest/ReleaseNotesList/index.jsx
+++ /dev/null
@@ -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 ;
-};
-
-export default ReleaseNotesList;
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 4ff714c..47613a2 100644
--- a/src/components/Routes/SiteRoutes.jsx
+++ b/src/components/Routes/SiteRoutes.jsx
@@ -10,13 +10,12 @@ const SSOForm = React.lazy(() => import("../SSOForm"));
const Home = React.lazy(() => import("../Home"));
const VehicleAddForm = React.lazy(() => import("../Cars/Add"));
const PageNotFound = React.lazy(() => import("../404"));
-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"));
@@ -41,14 +40,6 @@ const SiteRoutes = () => {
type={TYPES.PROTECTED}
token={token}
/>
- }
- type={TYPES.PROTECTED}
- token={token}
- groups={groups}
- roles={[Roles.CREATE]}
- />
}
@@ -67,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]}
/>
}
+ path="/datascope/battery"
+ render={() => }
type={TYPES.PROTECTED}
token={token}
groups={groups}
- roles={[Roles.CREATE]}
+ roles={[Roles.READ, Roles.CREATE]}
/>
}
+ path="/datascope"
+ render={() => }
type={TYPES.PROTECTED}
token={token}
groups={groups}
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 e8219e4..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 d74230e..a653a66 100644
--- a/src/components/useStyles.jsx
+++ b/src/components/useStyles.jsx
@@ -202,6 +202,35 @@ const useStyles = makeStyles((theme) => ({
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/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;
+};