CEC-749 Generate cert UI (#141)

* Add Create Certificate page

* Tests

* Update permission check

* Use Azure
This commit is contained in:
John Wu
2022-04-18 16:50:51 -07:00
committed by GitHub
parent 81aeedc521
commit 56bef0c34d
28 changed files with 2449 additions and 289 deletions

View 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;

View 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;

View 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();
});
});

View File

@@ -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>
`;

View 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;