Merge branch 'main' into CEC-5443
This commit is contained in:
13
package-lock.json
generated
13
package-lock.json
generated
@@ -48,6 +48,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||||
|
"@ungap/structured-clone": "^1.2.0",
|
||||||
"eslint": "^8.32.0",
|
"eslint": "^8.32.0",
|
||||||
"eslint-plugin-react": "^7.32.1",
|
"eslint-plugin-react": "^7.32.1",
|
||||||
"react-test-renderer": "^17.0.2"
|
"react-test-renderer": "^17.0.2"
|
||||||
@@ -4921,6 +4922,12 @@
|
|||||||
"url": "https://opencollective.com/typescript-eslint"
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@ungap/structured-clone": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@webassemblyjs/ast": {
|
"node_modules/@webassemblyjs/ast": {
|
||||||
"version": "1.11.6",
|
"version": "1.11.6",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz",
|
||||||
@@ -20684,6 +20691,12 @@
|
|||||||
"eslint-visitor-keys": "^3.3.0"
|
"eslint-visitor-keys": "^3.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@ungap/structured-clone": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@webassemblyjs/ast": {
|
"@webassemblyjs/ast": {
|
||||||
"version": "1.11.6",
|
"version": "1.11.6",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz",
|
||||||
|
|||||||
@@ -79,6 +79,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||||
|
"@ungap/structured-clone": "^1.2.0",
|
||||||
"eslint": "^8.32.0",
|
"eslint": "^8.32.0",
|
||||||
"eslint-plugin-react": "^7.32.1",
|
"eslint-plugin-react": "^7.32.1",
|
||||||
"react-test-renderer": "^17.0.2"
|
"react-test-renderer": "^17.0.2"
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import App from ".";
|
|||||||
import addSnapshotSerializer from "../../utils/snapshot";
|
import addSnapshotSerializer from "../../utils/snapshot";
|
||||||
import { TEST_AUTH_OBJECT_FISKER, TEST_AUTH_OBJECT_MAGNA } from "../../utils/testing";
|
import { TEST_AUTH_OBJECT_FISKER, TEST_AUTH_OBJECT_MAGNA } from "../../utils/testing";
|
||||||
import { setToken } from "../Contexts/UserContext";
|
import { setToken } from "../Contexts/UserContext";
|
||||||
|
import structuredClone from '@ungap/structured-clone';
|
||||||
|
|
||||||
const LOADING_STATUS = "Loading...";
|
const LOADING_STATUS = "Loading...";
|
||||||
|
|
||||||
@@ -51,6 +52,10 @@ describe("App", () => {
|
|||||||
global.URL.createObjectURL = jest.fn();
|
global.URL.createObjectURL = jest.fn();
|
||||||
addSnapshotSerializer(expect);
|
addSnapshotSerializer(expect);
|
||||||
jest.setTimeout(10000);
|
jest.setTimeout(10000);
|
||||||
|
|
||||||
|
if (!("structuredClone" in global)) {
|
||||||
|
global.structuredClone = structuredClone;
|
||||||
|
}
|
||||||
}, 60000);
|
}, 60000);
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
@@ -4353,7 +4353,6 @@ exports[`App Route /package-deploy authenticated 1`] = `
|
|||||||
class="makeStyles-paper-0"
|
class="makeStyles-paper-0"
|
||||||
>
|
>
|
||||||
<form
|
<form
|
||||||
action="{onSubmit}"
|
|
||||||
class="makeStyles-form-0"
|
class="makeStyles-form-0"
|
||||||
novalidate=""
|
novalidate=""
|
||||||
>
|
>
|
||||||
@@ -4366,9 +4365,10 @@ exports[`App Route /package-deploy authenticated 1`] = `
|
|||||||
</p>
|
</p>
|
||||||
<div
|
<div
|
||||||
class="MuiGrid-root makeStyles-root-0 MuiGrid-container MuiGrid-spacing-xs-2"
|
class="MuiGrid-root makeStyles-root-0 MuiGrid-container MuiGrid-spacing-xs-2"
|
||||||
|
columns="[object Object]"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="MuiGrid-root MuiGrid-item MuiGrid-grid-md-2"
|
class="MuiGrid-root MuiGrid-item MuiGrid-grid-xs-2"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="makeStyles-labelInline-0"
|
class="makeStyles-labelInline-0"
|
||||||
@@ -4377,7 +4377,7 @@ exports[`App Route /package-deploy authenticated 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-2"
|
class="MuiGrid-root MuiGrid-item MuiGrid-grid-xs-2"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
class="MuiFormControlLabel-root"
|
class="MuiFormControlLabel-root"
|
||||||
@@ -4419,100 +4419,100 @@ exports[`App Route /package-deploy authenticated 1`] = `
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-2"
|
class="MuiGrid-root MuiGrid-item MuiGrid-grid-xs-4 MuiGrid-grid-sm-6"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="MuiFormControl-root makeStyles-margin-0 makeStyles-fullWidth-0"
|
class="MuiBox-root MuiBox-root-0"
|
||||||
>
|
>
|
||||||
<label
|
|
||||||
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated"
|
|
||||||
data-shrink="false"
|
|
||||||
for="search"
|
|
||||||
>
|
|
||||||
Search
|
|
||||||
</label>
|
|
||||||
<div
|
<div
|
||||||
class="MuiInputBase-root MuiInput-root MuiInput-underline MuiInputBase-formControl MuiInput-formControl MuiInputBase-adornedEnd"
|
class="MuiFormControl-root makeStyles-margin-0 makeStyles-fullWidth-0"
|
||||||
>
|
>
|
||||||
<input
|
<label
|
||||||
aria-invalid="false"
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated"
|
||||||
class="MuiInputBase-input MuiInput-input MuiInputBase-inputAdornedEnd"
|
data-shrink="false"
|
||||||
id="search"
|
for="search"
|
||||||
type="text"
|
|
||||||
value=""
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
class="MuiInputAdornment-root MuiInputAdornment-positionEnd"
|
|
||||||
>
|
>
|
||||||
<button
|
Search
|
||||||
aria-label="search"
|
</label>
|
||||||
class="MuiButtonBase-root MuiIconButton-root"
|
<div
|
||||||
tabindex="0"
|
class="MuiInputBase-root MuiInput-root MuiInput-underline MuiInputBase-formControl MuiInput-formControl MuiInputBase-adornedEnd"
|
||||||
type="button"
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiInput-input MuiInputBase-inputAdornedEnd"
|
||||||
|
id="search"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="MuiInputAdornment-root MuiInputAdornment-positionEnd"
|
||||||
>
|
>
|
||||||
<span
|
<button
|
||||||
class="MuiIconButton-label"
|
aria-label="search"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<svg
|
<span
|
||||||
aria-hidden="true"
|
class="MuiIconButton-label"
|
||||||
class="MuiSvgIcon-root"
|
|
||||||
focusable="false"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
>
|
||||||
<path
|
<svg
|
||||||
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"
|
aria-hidden="true"
|
||||||
/>
|
class="MuiSvgIcon-root"
|
||||||
</svg>
|
focusable="false"
|
||||||
</span>
|
viewBox="0 0 24 24"
|
||||||
<span
|
>
|
||||||
class="MuiTouchRipple-root"
|
<path
|
||||||
/>
|
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"
|
||||||
</button>
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
aria-haspopup="true"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M3 17v2h6v-2H3zM3 5v2h10V5H3zm10 16v-2h8v-2h-8v-2h-2v6h2zM7 9v2H3v2h4v2h2V9H7zm14 4v-2H11v2h10zm-6-4h2V7h4V5h-4V3h-2v6z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="MuiGrid-root makeStyles-textJustifyAlign-0 makeStyles-actionsBar-0 MuiGrid-item MuiGrid-grid-md-2"
|
class="MuiGrid-root MuiGrid-item MuiGrid-grid-xs-auto"
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
aria-haspopup="true"
|
|
||||||
class="MuiButtonBase-root MuiIconButton-root"
|
|
||||||
tabindex="0"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="MuiIconButton-label"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
class="MuiSvgIcon-root"
|
|
||||||
focusable="false"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M3 17v2h6v-2H3zM3 5v2h10V5H3zm10 16v-2h8v-2h-8v-2h-2v6h2zM7 9v2H3v2h4v2h2V9H7zm14 4v-2H11v2h10zm-6-4h2V7h4V5h-4V3h-2v6z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="MuiTouchRipple-root"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="MuiGrid-root MuiGrid-container MuiGrid-item MuiGrid-justify-content-xs-flex-end MuiGrid-grid-md-4"
|
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-textPrimary Mui-disabled Mui-disabled"
|
class="MuiButtonBase-root MuiIconButton-root Mui-disabled Mui-disabled"
|
||||||
disabled=""
|
disabled=""
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
type="submit"
|
type="button"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="MuiButton-label"
|
class="MuiIconButton-label"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
|
|||||||
@@ -6,16 +6,12 @@ import {
|
|||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import api from "../../../services/vehiclesAPI";
|
import api from "../../../services/vehiclesAPI";
|
||||||
import TaskRunner from "../../../utils/taskRunner";
|
import TaskRunner from "../../../utils/taskRunner";
|
||||||
import { AllECUsCommand } from "../../Controls/SendDiagnosticCommand";
|
import { AllECUsCommand, DIAGNOSTIC_COMMANDS } from "../../Controls/SendDiagnosticCommand";
|
||||||
import useStyles from "../../useStyles";
|
import useStyles from "../../useStyles";
|
||||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
import { useUserContext } from "../../Contexts/UserContext";
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
import unionIntersect from "../../../utils/unionIntersect";
|
import unionIntersect from "../../../utils/unionIntersect";
|
||||||
|
|
||||||
const commands = [
|
|
||||||
{ val: "remote_reset", displayname: "Remote Reset" },
|
|
||||||
];
|
|
||||||
|
|
||||||
async function getECUsByVINs(vins, token) {
|
async function getECUsByVINs(vins, token) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const taskRunner = new TaskRunner(30, vins.length);
|
const taskRunner = new TaskRunner(30, vins.length);
|
||||||
@@ -49,7 +45,7 @@ export default forwardRef(({
|
|||||||
const [ecus, setECUs] = useState([{ ecu: "TBOX" }]);
|
const [ecus, setECUs] = useState([{ ecu: "TBOX" }]);
|
||||||
const [currentECU, setCurrentECU] = useState("");
|
const [currentECU, setCurrentECU] = useState("");
|
||||||
const [validateECUs, setValidateECUs] = useState(false);
|
const [validateECUs, setValidateECUs] = useState(false);
|
||||||
const [command, setCommand] = useState(commands[0].val);
|
const [command, setCommand] = useState(DIAGNOSTIC_COMMANDS[0]);
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const { setMessage } = useStatusContext();
|
const { setMessage } = useStatusContext();
|
||||||
const { token: { idToken: { jwtToken: token } } } = useUserContext();
|
const { token: { idToken: { jwtToken: token } } } = useUserContext();
|
||||||
@@ -61,7 +57,7 @@ export default forwardRef(({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return api.sendDiagnosticCommand(ids, {
|
return api.sendDiagnosticCommand(ids, {
|
||||||
command,
|
command: command.val,
|
||||||
ecu_name: currentECU,
|
ecu_name: currentECU,
|
||||||
}, token)
|
}, token)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@@ -74,7 +70,7 @@ export default forwardRef(({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const handleSelectCommand = (e) => {
|
const handleSelectCommand = (e) => {
|
||||||
setCommand(e.target.value);
|
setCommand(DIAGNOSTIC_COMMANDS[e.target.value]);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -96,7 +92,12 @@ export default forwardRef(({
|
|||||||
<p>
|
<p>
|
||||||
Attempt to send a vehicle diagnostic command to the following VINs: {idCSV}.
|
Attempt to send a vehicle diagnostic command to the following VINs: {idCSV}.
|
||||||
</p>
|
</p>
|
||||||
<AllECUsCommand classes={classes} ecus={ecus} currentECU={currentECU} setCurrentECU={setCurrentECU} />
|
<AllECUsCommand
|
||||||
|
classes={classes}
|
||||||
|
ecus={command.allowAll ? [...ecus, { ecu: "*" }] : ecus}
|
||||||
|
currentECU={currentECU}
|
||||||
|
setCurrentECU={setCurrentECU}
|
||||||
|
/>
|
||||||
|
|
||||||
<FormControl className={classes.formControl} variant="outlined" size="small" margin="normal">
|
<FormControl className={classes.formControl} variant="outlined" size="small" margin="normal">
|
||||||
<InputLabel htmlFor="send-command" className={classes.whiteBackground}>
|
<InputLabel htmlFor="send-command" className={classes.whiteBackground}>
|
||||||
@@ -109,8 +110,8 @@ export default forwardRef(({
|
|||||||
}}
|
}}
|
||||||
onChange={handleSelectCommand}
|
onChange={handleSelectCommand}
|
||||||
>
|
>
|
||||||
{commands.map((command, index) => (
|
{DIAGNOSTIC_COMMANDS.map((command, index) => (
|
||||||
<option key={index} value={command.val}>
|
<option key={command.val} value={index}>
|
||||||
{command.displayname}
|
{command.displayname}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export default function BulkActions({
|
|||||||
{
|
{
|
||||||
id: "diagnostic",
|
id: "diagnostic",
|
||||||
name: "Send Diagnostic",
|
name: "Send Diagnostic",
|
||||||
disabled: !hasRole(groups, Permissions.CarDiagnostic, providers),
|
disabled: false, //!hasRole(groups, Permissions.CarDiagnostic, providers),
|
||||||
trigger: () => setActive("diagnostic"),
|
trigger: () => setActive("diagnostic"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ import {
|
|||||||
useVehicleContext
|
useVehicleContext
|
||||||
} from "../../Contexts/VehicleContext";
|
} from "../../Contexts/VehicleContext";
|
||||||
|
|
||||||
const commands = [
|
export const DIAGNOSTIC_COMMANDS = [
|
||||||
{ displayname: "Reset", val: "remote_reset" },
|
{ displayname: "Reset", val: "remote_reset" },
|
||||||
{ displayname: "Set CAN Network State", val: "can_network" },
|
{ displayname: "Set CAN Network State", val: "can_network" },
|
||||||
{ displayname: "Set Remote Ignition", val: "remote_ignition" },
|
{ displayname: "Set Remote Ignition", val: "remote_ignition" },
|
||||||
{ displayname: "Send Wake Up SMS", val: "sms" },
|
{ displayname: "Send Wake Up SMS", val: "sms" },
|
||||||
//{ displayname: "Update SecOC keys", val: "write_secoc_key" },
|
// { displayname: "Update SecOC keys", val: "write_secoc_key" },
|
||||||
{ displayname: "Read ECU versions", val: "read_ecu_versions" },
|
{ displayname: "Read ECU versions", val: "read_ecu_versions", allowAll: true },
|
||||||
]
|
]
|
||||||
|
|
||||||
const SendDiagnosticCommand = ({ vin, token, classes }) => {
|
const SendDiagnosticCommand = ({ vin, token, classes }) => {
|
||||||
@@ -30,7 +30,7 @@ const SendDiagnosticCommand = ({ vin, token, classes }) => {
|
|||||||
const [ecus, setEcus] = useState([]);
|
const [ecus, setEcus] = useState([]);
|
||||||
const { setMessage } = useStatusContext();
|
const { setMessage } = useStatusContext();
|
||||||
|
|
||||||
const [currentCommand, setCurrentCommand] = useState(commands[0].val);
|
const [currentCommand, setCurrentCommand] = useState(DIAGNOSTIC_COMMANDS[0].val);
|
||||||
const [currentECU, setCurrentECU] = useState("");
|
const [currentECU, setCurrentECU] = useState("");
|
||||||
const [canNetState, setCanNetState] = useState(false);
|
const [canNetState, setCanNetState] = useState(false);
|
||||||
const [ignitionState, setIgnitionState] = useState(false);
|
const [ignitionState, setIgnitionState] = useState(false);
|
||||||
@@ -154,7 +154,7 @@ const SendDiagnosticCommand = ({ vin, token, classes }) => {
|
|||||||
}}
|
}}
|
||||||
onChange={changeCommandHandler}
|
onChange={changeCommandHandler}
|
||||||
>
|
>
|
||||||
{commands.map((command, index) => (
|
{DIAGNOSTIC_COMMANDS.map((command, index) => (
|
||||||
<option key={index} value={command.val}>
|
<option key={index} value={command.val}>
|
||||||
{command.displayname}
|
{command.displayname}
|
||||||
</option>
|
</option>
|
||||||
|
|||||||
Reference in New Issue
Block a user