CEC-749 Generate cert UI (#141)
* Add Create Certificate page * Tests * Update permission check * Use Azure
This commit is contained in:
@@ -37,22 +37,35 @@ const check = async (path, selector, compare) => {
|
||||
|
||||
const sleepAndCheck = async (path, selector, compare) => {
|
||||
const container = await renderRoute(path);
|
||||
await waitFor(() => { });
|
||||
await waitFor(() => {});
|
||||
expect(container.querySelector(selector).innerHTML).toEqual(compare);
|
||||
expect(container).toMatchSnapshot();
|
||||
};
|
||||
|
||||
describe("App", () => {
|
||||
const rxMakeStyles = /makeStyles-(\w+)-(\d+)/gi;
|
||||
|
||||
beforeAll(() => {
|
||||
// Stablize Table Pagination control ids
|
||||
expect.addSnapshotSerializer({
|
||||
test: function (val) {
|
||||
return val && typeof val === "string" && val.indexOf("mui-") >= 0;
|
||||
return val && typeof val === "string" && val.indexOf("mui-") > -1;
|
||||
},
|
||||
print: function (val) {
|
||||
let str = val;
|
||||
str = str.replace(/mui-\d*/g, "mui-00000");
|
||||
|
||||
return `"${str}"`;
|
||||
},
|
||||
});
|
||||
expect.addSnapshotSerializer({
|
||||
test: (val) => {
|
||||
return val && typeof val === "string" && val.search(rxMakeStyles) > -1;
|
||||
},
|
||||
print: function (val) {
|
||||
let str = val;
|
||||
str = str.replace(rxMakeStyles, "makeStyles-$1-0000");
|
||||
|
||||
return `"${str}"`;
|
||||
},
|
||||
});
|
||||
@@ -107,6 +120,10 @@ describe("App", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("Route /tools/certificates/add unauthenticated", async () => {
|
||||
await check("/tools/certificates/add", "span.MuiButton-label", "Sign In");
|
||||
});
|
||||
|
||||
it("Route /page-not-found unauthenticated", async () => {
|
||||
await check("/page-not-found", "h1", "Page Not Found");
|
||||
});
|
||||
@@ -159,4 +176,9 @@ describe("App", () => {
|
||||
setToken(TEST_AUTH_OBJECT);
|
||||
await check("/vehicle-status/FISKER123", "h6", "Vehicle FISKER123 Details");
|
||||
});
|
||||
|
||||
it("Route /tools/certificates/add authenticated", async () => {
|
||||
setToken(TEST_AUTH_OBJECT);
|
||||
await check("/tools/certificates/add", "h6", "Create Certificate");
|
||||
});
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -194,6 +194,23 @@ exports[`CANFiltersTable Render 1`] = `
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
class=""
|
||||
href="/"
|
||||
title="Delete \\"123\\""
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
aria-label="Delete 123"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
@@ -230,6 +247,23 @@ exports[`CANFiltersTable Render 1`] = `
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
class=""
|
||||
href="/"
|
||||
title="Delete \\"456-789\\""
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
aria-label="Delete 456-789"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
@@ -266,6 +300,23 @@ exports[`CANFiltersTable Render 1`] = `
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
class=""
|
||||
href="/"
|
||||
title="Delete \\"1\\""
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
aria-label="Delete 1"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -193,6 +193,23 @@ exports[`CANFiltersTab Render 1`] = `
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
class=""
|
||||
href="/"
|
||||
title="Delete \\"123\\""
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
aria-label="Delete 123"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
@@ -229,6 +246,23 @@ exports[`CANFiltersTab Render 1`] = `
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
class=""
|
||||
href="/"
|
||||
title="Delete \\"456-789\\""
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
aria-label="Delete 456-789"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
@@ -265,6 +299,23 @@ exports[`CANFiltersTab Render 1`] = `
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
class=""
|
||||
href="/"
|
||||
title="Delete \\"1\\""
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
aria-label="Delete 1"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
90
src/components/Certificates/Add/CreateForm.jsx
Normal file
90
src/components/Certificates/Add/CreateForm.jsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
FormControlLabel,
|
||||
FormLabel,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
TextField,
|
||||
} from "@material-ui/core";
|
||||
|
||||
import useStyles from "../../useStyles";
|
||||
import { CertTypes } from "../../Contexts/CertificateContext";
|
||||
|
||||
const CreateForm = ({ onCreate, busy }) => {
|
||||
const classes = useStyles();
|
||||
const vinEl = useRef(null);
|
||||
const [certType, setCertType] = useState(CertTypes.TREX);
|
||||
|
||||
const onSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (onCreate)
|
||||
onCreate({
|
||||
vin: vinEl.current.value,
|
||||
type: certType,
|
||||
});
|
||||
};
|
||||
|
||||
const onCertTypeChange = (event) => {
|
||||
setCertType(event.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classes.paper}>
|
||||
<form className={classes.form} noValidate action="{onSubmit}">
|
||||
<TextField
|
||||
id="vin"
|
||||
name="vin"
|
||||
label="VIN"
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
inputProps={{
|
||||
maxLength: "17",
|
||||
}}
|
||||
required
|
||||
fullWidth
|
||||
inputRef={vinEl}
|
||||
/>
|
||||
<FormLabel id="cert-type-group-label">Type</FormLabel>
|
||||
<RadioGroup
|
||||
row
|
||||
aria-labelledby="cert-type-group-label"
|
||||
name="cert-type"
|
||||
value={certType}
|
||||
onChange={onCertTypeChange}
|
||||
margin="normal"
|
||||
>
|
||||
<FormControlLabel
|
||||
value={CertTypes.TREX}
|
||||
control={<Radio />}
|
||||
label="TREX"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value={CertTypes.HMI}
|
||||
control={<Radio />}
|
||||
label="HMI"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value={CertTypes.Charging}
|
||||
control={<Radio />}
|
||||
label="Charging"
|
||||
/>
|
||||
</RadioGroup>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={busy}
|
||||
fullWidth
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={classes.submit}
|
||||
onClick={onSubmit}
|
||||
>
|
||||
{busy ? "Submitting..." : "Submit"}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateForm;
|
||||
50
src/components/Certificates/Add/DownloadCerts.jsx
Normal file
50
src/components/Certificates/Add/DownloadCerts.jsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { Button } from "@material-ui/core";
|
||||
import React from "react";
|
||||
|
||||
import DownloadFileLink from "../../Controls/DownloadFileLink";
|
||||
import useStyles from "../../useStyles";
|
||||
|
||||
const CertMimeType = "application/x-pem-file";
|
||||
|
||||
const DownloadCerts = ({ vin, publicCert, privateCert, onChangeView }) => {
|
||||
const classes = useStyles();
|
||||
|
||||
const onNewCert = (event) => {
|
||||
event.preventDefault();
|
||||
if (!onChangeView) return;
|
||||
onChangeView();
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<h2>Download Certifcates</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<DownloadFileLink
|
||||
data={publicCert}
|
||||
filename={`${vin}_cert.pem`}
|
||||
mimetype={CertMimeType}
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<DownloadFileLink
|
||||
data={privateCert}
|
||||
filename={`${vin}_key.pem`}
|
||||
mimetype={CertMimeType}
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
<Button
|
||||
type="submit"
|
||||
fullWidth
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={classes.submit}
|
||||
onClick={onNewCert}
|
||||
>
|
||||
Done
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DownloadCerts;
|
||||
25
src/components/Certificates/Add/DownloadCerts.test.jsx
Normal file
25
src/components/Certificates/Add/DownloadCerts.test.jsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import React from "react";
|
||||
import { render, waitFor } from "@testing-library/react";
|
||||
|
||||
import DownloadCerts from "./DownloadCerts";
|
||||
|
||||
describe("DownloadCerts", () => {
|
||||
beforeAll(() => {
|
||||
global.URL.createObjectURL = jest.fn();
|
||||
global.URL.revokeObjectURL = jest.fn();
|
||||
});
|
||||
|
||||
it("Render", async () => {
|
||||
const { container } = render(
|
||||
<DownloadCerts
|
||||
vin={"TESTVIN"}
|
||||
publicCert={"PUBLIC"}
|
||||
privateCert={"PRIVATE"}
|
||||
/>
|
||||
);
|
||||
await waitFor(() => {
|
||||
/* render */
|
||||
});
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`DownloadCerts Render 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
<h2>
|
||||
Download Certifcates
|
||||
</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
download="TESTVIN_cert.pem"
|
||||
>
|
||||
TESTVIN_cert.pem
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
download="TESTVIN_key.pem"
|
||||
>
|
||||
TESTVIN_key.pem
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<button
|
||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-6 MuiButton-containedPrimary MuiButton-fullWidth"
|
||||
tabindex="0"
|
||||
type="submit"
|
||||
>
|
||||
<span
|
||||
class="MuiButton-label"
|
||||
>
|
||||
Done
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
85
src/components/Certificates/Add/index.jsx
Normal file
85
src/components/Certificates/Add/index.jsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import {
|
||||
useCertificateContext,
|
||||
CertificateProvider,
|
||||
} from "../../Contexts/CertificateContext";
|
||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||
import { useUserContext } from "../../Contexts/UserContext";
|
||||
import { logger } from "../../../services/monitoring";
|
||||
import CreateForm from "./CreateForm";
|
||||
import DownloadCerts from "./DownloadCerts";
|
||||
|
||||
const VIEW_FORM = 0;
|
||||
const VIEW_DOWNLOAD = 1;
|
||||
|
||||
const MainForm = () => {
|
||||
const { busy, createCert } = useCertificateContext();
|
||||
const { setMessage, setTitle, setSitePath } = useStatusContext();
|
||||
const {
|
||||
token: {
|
||||
idToken: { jwtToken: token },
|
||||
},
|
||||
} = useUserContext();
|
||||
const [view, setView] = useState(VIEW_FORM);
|
||||
const [pubCert, setPubCert] = useState(null);
|
||||
const [privCert, setPrivCert] = useState(null);
|
||||
const [vin, setVIN] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
setTitle("Create Certificate");
|
||||
setSitePath([
|
||||
{
|
||||
label: "Tools",
|
||||
link: "/tools/certificates/add",
|
||||
},
|
||||
{
|
||||
label: "Create Certificate",
|
||||
},
|
||||
]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const onCreate = async (data) => {
|
||||
try {
|
||||
const result = await createCert(data, token);
|
||||
|
||||
setPubCert(result.public_key);
|
||||
setPrivCert(result.private_key);
|
||||
setVIN(data.vin);
|
||||
setMessage(`Created ${data.vin} certificate`);
|
||||
setView(VIEW_DOWNLOAD);
|
||||
} catch (e) {
|
||||
setMessage(e.message);
|
||||
logger.warn(e.stack);
|
||||
}
|
||||
};
|
||||
|
||||
const onChangeView = () => {
|
||||
setPubCert(null);
|
||||
setPrivCert(null);
|
||||
setVIN(null);
|
||||
|
||||
setView(VIEW_FORM);
|
||||
};
|
||||
|
||||
if (view === VIEW_DOWNLOAD)
|
||||
return (
|
||||
<DownloadCerts
|
||||
vin={vin}
|
||||
publicCert={pubCert}
|
||||
privateCert={privCert}
|
||||
onChangeView={onChangeView}
|
||||
/>
|
||||
);
|
||||
|
||||
return <CreateForm onCreate={onCreate} busy={busy} />;
|
||||
};
|
||||
|
||||
const CertificateCreate = () => (
|
||||
<CertificateProvider>
|
||||
<MainForm />
|
||||
</CertificateProvider>
|
||||
);
|
||||
|
||||
export default CertificateCreate;
|
||||
50
src/components/Contexts/CertificateContext.jsx
Normal file
50
src/components/Contexts/CertificateContext.jsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import React, { useContext, useState } from "react";
|
||||
|
||||
import api from "../../services/certificatesAPI";
|
||||
|
||||
const CertificateContext = React.createContext();
|
||||
|
||||
export const CertTypes = {
|
||||
TREX: "TREX",
|
||||
HMI: "HMI",
|
||||
Charging: "CHARGE",
|
||||
};
|
||||
|
||||
const validateCreate = (data) => {
|
||||
if (!data.type) throw new Error("type is required");
|
||||
if (!data.vin) throw new Error("vin is required");
|
||||
};
|
||||
|
||||
export const CertificateProvider = ({ children }) => {
|
||||
const [busy, setBusy] = useState(false);
|
||||
|
||||
const createCert = async (data, token) => {
|
||||
try {
|
||||
setBusy(true);
|
||||
|
||||
validateCreate(data);
|
||||
|
||||
const result = await api.create(data, token);
|
||||
if (result.error) {
|
||||
throw new Error(`Create certificate error. ${result.message}`);
|
||||
}
|
||||
|
||||
return result;
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<CertificateContext.Provider
|
||||
value={{
|
||||
busy,
|
||||
createCert,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</CertificateContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useCertificateContext = () => useContext(CertificateContext);
|
||||
@@ -0,0 +1,11 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`DownloadFileLink Render 1`] = `
|
||||
<div>
|
||||
<a
|
||||
download="test.txt"
|
||||
>
|
||||
test.txt
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
32
src/components/Controls/DownloadFileLink/index.jsx
Normal file
32
src/components/Controls/DownloadFileLink/index.jsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
const DownloadFileLink = ({ data, filename, mimetype }) => {
|
||||
const [link, setLink] = useState("");
|
||||
|
||||
const releaseLink = () => {
|
||||
if (link === "") return;
|
||||
URL.revokeObjectURL(link);
|
||||
};
|
||||
|
||||
const makeFile = () => {
|
||||
const file = new Blob([data], { type: mimetype ?? "text/plain" });
|
||||
|
||||
releaseLink();
|
||||
setLink(URL.createObjectURL(file));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) return;
|
||||
makeFile();
|
||||
return releaseLink;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [data, filename, mimetype]);
|
||||
|
||||
return (
|
||||
<a download={filename ?? "file.txt"} href={link}>
|
||||
{filename}
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
export default DownloadFileLink;
|
||||
21
src/components/Controls/DownloadFileLink/index.test.jsx
Normal file
21
src/components/Controls/DownloadFileLink/index.test.jsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import { render, waitFor } from "@testing-library/react";
|
||||
|
||||
import DownloadFileLink from ".";
|
||||
|
||||
describe("DownloadFileLink", () => {
|
||||
beforeAll(() => {
|
||||
global.URL.createObjectURL = jest.fn();
|
||||
global.URL.revokeObjectURL = jest.fn();
|
||||
});
|
||||
|
||||
it("Render", async () => {
|
||||
const { container } = render(
|
||||
<DownloadFileLink data={"ABCDEFGHIJK"} filename="test.txt" />
|
||||
);
|
||||
await waitFor(() => {
|
||||
/* render */
|
||||
});
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -195,6 +195,23 @@ exports[`FleetCANFiltersTable Render 1`] = `
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
class=""
|
||||
href="/"
|
||||
title="Delete \\"123-456\\""
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
aria-label="Delete 123-456"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
@@ -231,6 +248,23 @@ exports[`FleetCANFiltersTable Render 1`] = `
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
class=""
|
||||
href="/"
|
||||
title="Delete \\"1\\""
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
aria-label="Delete 1"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
@@ -267,6 +301,23 @@ exports[`FleetCANFiltersTable Render 1`] = `
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
class=""
|
||||
href="/"
|
||||
title="Delete \\"1000\\""
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
aria-label="Delete 1000"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -153,7 +153,23 @@ exports[`FleetVehiclesTable Render 1`] = `
|
||||
<td
|
||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||
>
|
||||
No actions
|
||||
<a
|
||||
class=""
|
||||
href="/"
|
||||
title="Delete \\"USWESTVIN12345678\\""
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
aria-label="Delete USWESTVIN12345678"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
@@ -171,7 +187,23 @@ exports[`FleetVehiclesTable Render 1`] = `
|
||||
<td
|
||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||
>
|
||||
No actions
|
||||
<a
|
||||
class=""
|
||||
href="/"
|
||||
title="Delete \\"USWESTVIN12345679\\""
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
aria-label="Delete USWESTVIN12345679"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
@@ -189,7 +221,23 @@ exports[`FleetVehiclesTable Render 1`] = `
|
||||
<td
|
||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||
>
|
||||
No actions
|
||||
<a
|
||||
class=""
|
||||
href="/"
|
||||
title="Delete \\"USWESTVIN12345670\\""
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
aria-label="Delete USWESTVIN12345670"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -194,6 +194,23 @@ exports[`CANFiltersTab Render 1`] = `
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
class=""
|
||||
href="/"
|
||||
title="Delete \\"123-456\\""
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
aria-label="Delete 123-456"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
@@ -230,6 +247,23 @@ exports[`CANFiltersTab Render 1`] = `
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
class=""
|
||||
href="/"
|
||||
title="Delete \\"1\\""
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
aria-label="Delete 1"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
@@ -266,6 +300,23 @@ exports[`CANFiltersTab Render 1`] = `
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
class=""
|
||||
href="/"
|
||||
title="Delete \\"1000\\""
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
aria-label="Delete 1000"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -152,7 +152,23 @@ exports[`VehiclesTab Render 1`] = `
|
||||
<td
|
||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||
>
|
||||
No actions
|
||||
<a
|
||||
class=""
|
||||
href="/"
|
||||
title="Delete \\"USWESTVIN12345678\\""
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
aria-label="Delete USWESTVIN12345678"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
@@ -170,7 +186,23 @@ exports[`VehiclesTab Render 1`] = `
|
||||
<td
|
||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||
>
|
||||
No actions
|
||||
<a
|
||||
class=""
|
||||
href="/"
|
||||
title="Delete \\"USWESTVIN12345679\\""
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
aria-label="Delete USWESTVIN12345679"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
@@ -188,7 +220,23 @@ exports[`VehiclesTab Render 1`] = `
|
||||
<td
|
||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||
>
|
||||
No actions
|
||||
<a
|
||||
class=""
|
||||
href="/"
|
||||
title="Delete \\"USWESTVIN12345670\\""
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
aria-label="Delete USWESTVIN12345670"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import React from "react";
|
||||
import { List } from "@material-ui/core";
|
||||
import HomeIcon from "@material-ui/icons/Home";
|
||||
import DirectionsCarIcon from '@material-ui/icons/DirectionsCar';
|
||||
import DirectionsCarIcon from "@material-ui/icons/DirectionsCar";
|
||||
import CommuteIcon from "@material-ui/icons/Commute";
|
||||
import CloudDownloadIcon from "@material-ui/icons/CloudDownload";
|
||||
import AssessmentIcon from "@material-ui/icons/Assessment";
|
||||
import BuildIcon from "@material-ui/icons/Build";
|
||||
|
||||
import ListItemLink from "../ListItemLink";
|
||||
import ListItemExternalLink from "../ListItemExternalLink";
|
||||
@@ -43,6 +44,19 @@ const menuData = [
|
||||
icon: <AssessmentIcon />,
|
||||
roles: [Roles.READ, Roles.CREATE],
|
||||
},
|
||||
{
|
||||
label: "Tools",
|
||||
to: "/tools/certificates/add",
|
||||
icon: <BuildIcon />,
|
||||
roles: [Roles.CERTIFICATES],
|
||||
submenus: [
|
||||
{
|
||||
label: "Certificate",
|
||||
to: "/tools/certificates/add",
|
||||
roles: [Roles.CERTIFICATES],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const MenuItem = ({ item, children }) => {
|
||||
|
||||
@@ -190,6 +190,70 @@ exports[`SideMenu Authenticated 1`] = `
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
<span>
|
||||
<li>
|
||||
<a
|
||||
aria-disabled="false"
|
||||
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
|
||||
href="/tools/certificates/add"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="MuiListItemIcon-root"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M22.7 19l-9.1-9.1c.9-2.3.4-5-1.5-6.9-2-2-5-2.4-7.4-1.3L9 6 6 9 1.6 4.7C.4 7.1.9 10.1 2.9 12.1c1.9 1.9 4.6 2.4 6.9 1.5l9.1 9.1c.4.4 1 .4 1.4 0l2.3-2.3c.5-.4.5-1.1.1-1.4z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="MuiListItemText-root"
|
||||
>
|
||||
<span
|
||||
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
|
||||
>
|
||||
Tools
|
||||
</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="/tools/certificates/add"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="MuiListItemText-root"
|
||||
>
|
||||
<span
|
||||
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
|
||||
>
|
||||
Certificate
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,8 +6,8 @@ import { MessageBar } from "../MessageBar";
|
||||
import { useUserContext } from "../Contexts/UserContext";
|
||||
import { Roles } from "../../utils/roles";
|
||||
|
||||
const CANFilterCreate = React.lazy(() => import("../CANFilter/Add"))
|
||||
const CANFilterUpdate = React.lazy(() => import("../CANFilter/Update"))
|
||||
const CANFilterCreate = React.lazy(() => import("../CANFilter/Add"));
|
||||
const CANFilterUpdate = React.lazy(() => import("../CANFilter/Update"));
|
||||
const CarsList = React.lazy(() => import("../Cars/List"));
|
||||
const CarStatus = React.lazy(() => import("../Cars/Status"));
|
||||
const CarUpdateStatus = React.lazy(() => import("../Cars/UpdateStatus"));
|
||||
@@ -15,9 +15,15 @@ const FleetsList = React.lazy(() => import("../Fleets/Table"));
|
||||
const FleetStatus = React.lazy(() => import("../Fleets/Status"));
|
||||
const FleetAddForm = React.lazy(() => import("../Fleets/Add"));
|
||||
const FleetUpdateForm = React.lazy(() => import("../Fleets/Update"));
|
||||
const FleetAddVehicleForm = React.lazy(() => import("../Fleets/Status/Vehicles/Add"));
|
||||
const FleetAddCANFilterForm = React.lazy(() => import("../Fleets/Status/CANFilters/Add"));
|
||||
const FleetUpdateCANFilterForm = React.lazy(() => import("../Fleets/Status/CANFilters/Update"));
|
||||
const FleetAddVehicleForm = React.lazy(() =>
|
||||
import("../Fleets/Status/Vehicles/Add")
|
||||
);
|
||||
const FleetAddCANFilterForm = React.lazy(() =>
|
||||
import("../Fleets/Status/CANFilters/Add")
|
||||
);
|
||||
const FleetUpdateCANFilterForm = React.lazy(() =>
|
||||
import("../Fleets/Status/CANFilters/Update")
|
||||
);
|
||||
const Home = React.lazy(() => import("../Home"));
|
||||
const Manifests = React.lazy(() => import("../Manifest/List"));
|
||||
const ManifestDeploy = React.lazy(() => import("../Manifest/Deploy"));
|
||||
@@ -26,7 +32,8 @@ const ManifestCreate = React.lazy(() => import("../Manifest/Create"));
|
||||
const PageNotFound = React.lazy(() => import("../404"));
|
||||
const SSOForm = React.lazy(() => import("../SSOForm"));
|
||||
const VehicleAddForm = React.lazy(() => import("../Cars/Add"));
|
||||
const VehicleUpdateForm = React.lazy(() => import("../Cars/Update"))
|
||||
const VehicleUpdateForm = React.lazy(() => import("../Cars/Update"));
|
||||
const CertificateCreate = React.lazy(() => import("../Certificates/Add"));
|
||||
|
||||
const SiteRoutes = () => {
|
||||
const { token, groups } = useUserContext();
|
||||
@@ -191,6 +198,14 @@ const SiteRoutes = () => {
|
||||
groups={groups}
|
||||
roles={[Roles.CREATE]}
|
||||
/>
|
||||
<AuthRoute
|
||||
path="/tools/certificates/add"
|
||||
render={() => <CertificateCreate />}
|
||||
type={TYPES.PROTECTED}
|
||||
token={token}
|
||||
groups={groups}
|
||||
roles={[Roles.CERTIFICATES]}
|
||||
/>
|
||||
<PageNotFound />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
|
||||
Reference in New Issue
Block a user