Merge branch 'release/0.9.0'
This commit is contained in:
@@ -15,6 +15,8 @@ REACT_APP_ROLE_GENERATE_CERTIFICATE=9af2d8c0-c26d-4d6d-bbd1-ac53cbd37ebc
|
|||||||
REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
|
REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
|
||||||
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8
|
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8
|
||||||
REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f
|
REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f
|
||||||
|
REACT_APP_ROLE_CAR_DIAGNOSTIC=2914e67f-fb85-4b78-b79d-656f4f37faa1
|
||||||
|
REACT_APP_ROLE_UPDATE_DEPLOY=e4af2c4c-6c5e-4784-9097-7c18e776d7b6
|
||||||
REACT_APP_ECCKEY_ENV=
|
REACT_APP_ECCKEY_ENV=
|
||||||
REACT_APP_HOME_MAP_DEFAULT_LOCATION={"lat":49.8327,"lng":9.8816,"zoom":4.5}
|
REACT_APP_HOME_MAP_DEFAULT_LOCATION={"lat":49.8327,"lng":9.8816,"zoom":4.5}
|
||||||
REACT_APP_ENABLE_DEBUGMASK=1
|
REACT_APP_ENABLE_DEBUGMASK=1
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ REACT_APP_ROLE_GENERATE_CERTIFICATE=9af2d8c0-c26d-4d6d-bbd1-ac53cbd37ebc
|
|||||||
REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
|
REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
|
||||||
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8
|
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8
|
||||||
REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f
|
REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f
|
||||||
|
REACT_APP_ROLE_CAR_DIAGNOSTIC=2914e67f-fb85-4b78-b79d-656f4f37faa1
|
||||||
|
REACT_APP_ROLE_UPDATE_DEPLOY=e4af2c4c-6c5e-4784-9097-7c18e776d7b6
|
||||||
REACT_APP_ECCKEY_ENV=
|
REACT_APP_ECCKEY_ENV=
|
||||||
REACT_APP_HOME_MAP_DEFAULT_LOCATION={"lat":37.0902,"lng":-95.7129,"zoom":4.5}
|
REACT_APP_HOME_MAP_DEFAULT_LOCATION={"lat":37.0902,"lng":-95.7129,"zoom":4.5}
|
||||||
REACT_APP_ENABLE_DEBUGMASK=1
|
REACT_APP_ENABLE_DEBUGMASK=1
|
||||||
|
|||||||
4
.env.dev
4
.env.dev
@@ -15,6 +15,8 @@ REACT_APP_ROLE_GENERATE_CERTIFICATE=746f34b0-9ba0-4b5d-8d84-0256a9c8e390
|
|||||||
REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
|
REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
|
||||||
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8
|
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8
|
||||||
REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f
|
REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f
|
||||||
|
REACT_APP_ROLE_CAR_DIAGNOSTIC=2914e67f-fb85-4b78-b79d-656f4f37faa1
|
||||||
|
REACT_APP_ROLE_UPDATE_DEPLOY=3590ec3f-1c05-428b-81a4-40b00baf83de
|
||||||
REACT_APP_ECCKEY_ENV=stage,prod
|
REACT_APP_ECCKEY_ENV=stage,prod
|
||||||
REACT_APP_HOME_MAP_DEFAULT_LOCATION={"lat":37.0902,"lng":-95.7129,"zoom":4.5}
|
REACT_APP_HOME_MAP_DEFAULT_LOCATION={"lat":37.0902,"lng":-95.7129,"zoom":4.5}
|
||||||
REACT_APP_ENABLE_DEBUGMASK=1
|
REACT_APP_ENABLE_DEBUGMASK=1
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ REACT_APP_ROLE_GENERATE_CERTIFICATE=746f34b0-9ba0-4b5d-8d84-0256a9c8e390
|
|||||||
REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
|
REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
|
||||||
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8
|
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8
|
||||||
REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f
|
REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f
|
||||||
|
REACT_APP_ROLE_UPDATE_DEPLOY=3590ec3f-1c05-428b-81a4-40b00baf83de
|
||||||
|
REACT_APP_ROLE_CAR_DIAGNOSTIC=2914e67f-fb85-4b78-b79d-656f4f37faa1
|
||||||
REACT_APP_ECCKEY_ENV=dev,stage,prod
|
REACT_APP_ECCKEY_ENV=dev,stage,prod
|
||||||
REACT_APP_HOME_MAP_DEFAULT_LOCATION={"lat":37.0902,"lng":-95.7129,"zoom":4.5}
|
REACT_APP_HOME_MAP_DEFAULT_LOCATION={"lat":37.0902,"lng":-95.7129,"zoom":4.5}
|
||||||
REACT_APP_ENABLE_DEBUGMASK=1
|
REACT_APP_ENABLE_DEBUGMASK=1
|
||||||
|
|||||||
4
.env.prd
4
.env.prd
@@ -15,6 +15,8 @@ REACT_APP_ROLE_GENERATE_CERTIFICATE=746f34b0-9ba0-4b5d-8d84-0256a9c8e390
|
|||||||
REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
|
REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
|
||||||
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8
|
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8
|
||||||
REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f
|
REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f
|
||||||
|
REACT_APP_ROLE_CAR_DIAGNOSTIC=2914e67f-fb85-4b78-b79d-656f4f37faa1
|
||||||
|
REACT_APP_ROLE_UPDATE_DEPLOY=e4af2c4c-6c5e-4784-9097-7c18e776d7b6
|
||||||
REACT_APP_ECCKEY_ENV=stage
|
REACT_APP_ECCKEY_ENV=stage
|
||||||
REACT_APP_HOME_MAP_DEFAULT_LOCATION={"lat":37.0902,"lng":-95.7129,"zoom":4.5}
|
REACT_APP_HOME_MAP_DEFAULT_LOCATION={"lat":37.0902,"lng":-95.7129,"zoom":4.5}
|
||||||
REACT_APP_ENABLE_DEBUGMASK=1
|
REACT_APP_ENABLE_DEBUGMASK=1
|
||||||
|
|||||||
4
.env.stg
4
.env.stg
@@ -15,6 +15,8 @@ REACT_APP_ROLE_GENERATE_CERTIFICATE=746f34b0-9ba0-4b5d-8d84-0256a9c8e390
|
|||||||
REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
|
REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
|
||||||
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8
|
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8
|
||||||
REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f
|
REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f
|
||||||
|
REACT_APP_ROLE_CAR_DIAGNOSTIC=2914e67f-fb85-4b78-b79d-656f4f37faa1
|
||||||
|
REACT_APP_ROLE_UPDATE_DEPLOY=3590ec3f-1c05-428b-81a4-40b00baf83de
|
||||||
REACT_APP_ECCKEY_ENV=prod
|
REACT_APP_ECCKEY_ENV=prod
|
||||||
REACT_APP_HOME_MAP_DEFAULT_LOCATION={"lat":37.0902,"lng":-95.7129,"zoom":4.5}
|
REACT_APP_HOME_MAP_DEFAULT_LOCATION={"lat":37.0902,"lng":-95.7129,"zoom":4.5}
|
||||||
REACT_APP_ENABLE_DEBUGMASK=1
|
REACT_APP_ENABLE_DEBUGMASK=1
|
||||||
|
|||||||
@@ -15,5 +15,7 @@ REACT_APP_ROLE_GENERATE_CERTIFICATE=746f34b0-9ba0-4b5d-8d84-0256a9c8e390
|
|||||||
REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
|
REACT_APP_ROLE_MANUFACTURE=3412e11a-a2d1-4355-be3e-ef9aa5065b69
|
||||||
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8
|
REACT_APP_ROLE_SUPPLIER_APPROVER=a6c9805e-80b2-42b2-bfbb-9df52e5504d8
|
||||||
REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f
|
REACT_APP_ROLE_MANIFEST_MIGRATION=42798c8a-9fa7-4fb4-82c0-9582cabe364f
|
||||||
|
REACT_APP_ROLE_CAR_DIAGNOSTIC=2914e67f-fb85-4b78-b79d-656f4f37faa1
|
||||||
|
REACT_APP_ROLE_UPDATE_DEPLOY=3590ec3f-1c05-428b-81a4-40b00baf83de
|
||||||
REACT_APP_ECCKEY_ENV=dev,stage,prod
|
REACT_APP_ECCKEY_ENV=dev,stage,prod
|
||||||
REACT_APP_HOME_MAP_DEFAULT_LOCATION={"lat":37.0902,"lng":-95.7129,"zoom":4.5}
|
REACT_APP_HOME_MAP_DEFAULT_LOCATION={"lat":37.0902,"lng":-95.7129,"zoom":4.5}
|
||||||
|
|||||||
11
package-lock.json
generated
11
package-lock.json
generated
@@ -41,6 +41,7 @@
|
|||||||
"react-router-dom": "^5.3.0",
|
"react-router-dom": "^5.3.0",
|
||||||
"react-router-hash-link": "^2.4.3",
|
"react-router-hash-link": "^2.4.3",
|
||||||
"react-scripts": "5.0.0",
|
"react-scripts": "5.0.0",
|
||||||
|
"semver-compare": "^1.0.0",
|
||||||
"usehooks-ts": "^2.7.1",
|
"usehooks-ts": "^2.7.1",
|
||||||
"web-vitals": "^2.1.4",
|
"web-vitals": "^2.1.4",
|
||||||
"webpack": "^5.74.0"
|
"webpack": "^5.74.0"
|
||||||
@@ -15130,6 +15131,11 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/semver-compare": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="
|
||||||
|
},
|
||||||
"node_modules/send": {
|
"node_modules/send": {
|
||||||
"version": "0.17.2",
|
"version": "0.17.2",
|
||||||
"resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz",
|
"resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz",
|
||||||
@@ -28020,6 +28026,11 @@
|
|||||||
"lru-cache": "^6.0.0"
|
"lru-cache": "^6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"semver-compare": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="
|
||||||
|
},
|
||||||
"send": {
|
"send": {
|
||||||
"version": "0.17.2",
|
"version": "0.17.2",
|
||||||
"resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz",
|
"resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz",
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
"react-router-dom": "^5.3.0",
|
"react-router-dom": "^5.3.0",
|
||||||
"react-router-hash-link": "^2.4.3",
|
"react-router-hash-link": "^2.4.3",
|
||||||
"react-scripts": "5.0.0",
|
"react-scripts": "5.0.0",
|
||||||
|
"semver-compare": "^1.0.0",
|
||||||
"usehooks-ts": "^2.7.1",
|
"usehooks-ts": "^2.7.1",
|
||||||
"web-vitals": "^2.1.4",
|
"web-vitals": "^2.1.4",
|
||||||
"webpack": "^5.74.0"
|
"webpack": "^5.74.0"
|
||||||
|
|||||||
@@ -6261,9 +6261,9 @@ exports[`App Route /packages authenticated 1`] = `
|
|||||||
class="MuiButtonBase-root MuiToggleButton-root Mui-selected MuiToggleButton-sizeMedium MuiToggleButton-standard MuiToggleButtonGroup-grouped MuiToggleButtonGroup-groupedHorizontal css-ueukts-MuiButtonBase-root-MuiToggleButton-root"
|
class="MuiButtonBase-root MuiToggleButton-root Mui-selected MuiToggleButton-sizeMedium MuiToggleButton-standard MuiToggleButtonGroup-grouped MuiToggleButtonGroup-groupedHorizontal css-ueukts-MuiButtonBase-root-MuiToggleButton-root"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
type="button"
|
type="button"
|
||||||
value="true"
|
value="software"
|
||||||
>
|
>
|
||||||
Active
|
Software
|
||||||
<span
|
<span
|
||||||
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
|
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
|
||||||
/>
|
/>
|
||||||
@@ -6273,18 +6273,67 @@ exports[`App Route /packages authenticated 1`] = `
|
|||||||
class="MuiButtonBase-root MuiToggleButton-root MuiToggleButton-sizeMedium MuiToggleButton-standard MuiToggleButtonGroup-grouped MuiToggleButtonGroup-groupedHorizontal css-ueukts-MuiButtonBase-root-MuiToggleButton-root"
|
class="MuiButtonBase-root MuiToggleButton-root MuiToggleButton-sizeMedium MuiToggleButton-standard MuiToggleButtonGroup-grouped MuiToggleButtonGroup-groupedHorizontal css-ueukts-MuiButtonBase-root-MuiToggleButton-root"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
type="button"
|
type="button"
|
||||||
value="false"
|
value="archived"
|
||||||
>
|
>
|
||||||
Archived
|
Archived
|
||||||
<span
|
<span
|
||||||
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
|
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
aria-pressed="false"
|
||||||
|
class="MuiButtonBase-root MuiToggleButton-root MuiToggleButton-sizeMedium MuiToggleButton-standard MuiToggleButtonGroup-grouped MuiToggleButtonGroup-groupedHorizontal css-ueukts-MuiButtonBase-root-MuiToggleButton-root"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
value="all"
|
||||||
|
>
|
||||||
|
All
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="MuiGrid-root makeStyles-textRightAlign-0 MuiGrid-item MuiGrid-grid-md-4"
|
class="MuiGrid-root makeStyles-textRightAlign-0 MuiGrid-item MuiGrid-grid-md-4"
|
||||||
/>
|
>
|
||||||
|
<div
|
||||||
|
aria-label="choose action"
|
||||||
|
class="MuiButtonGroup-root MuiButtonGroup-contained css-zqcytf-MuiButtonGroup-root"
|
||||||
|
role="group"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium Mui-disabled MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-sghohy-MuiButtonBase-root-MuiButton-root"
|
||||||
|
disabled=""
|
||||||
|
tabindex="-1"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Archive
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-haspopup="menu"
|
||||||
|
aria-label="select action"
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-11qr2p8-MuiButtonBase-root-MuiButton-root"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-i4bv87-MuiSvgIcon-root"
|
||||||
|
data-testid="ArrowDropDownIcon"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="m7 10 5 5 5-5z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table
|
<table
|
||||||
class="MuiTable-root"
|
class="MuiTable-root"
|
||||||
@@ -6295,6 +6344,40 @@ exports[`App Route /packages authenticated 1`] = `
|
|||||||
<tr
|
<tr
|
||||||
class="MuiTableRow-root MuiTableRow-head"
|
class="MuiTableRow-root MuiTableRow-head"
|
||||||
>
|
>
|
||||||
|
<th
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-paddingCheckbox"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-0 MuiCheckbox-root MuiCheckbox-colorSecondary MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-label="select all desserts"
|
||||||
|
class="PrivateSwitchBase-input-0"
|
||||||
|
data-indeterminate="false"
|
||||||
|
type="checkbox"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
<th
|
<th
|
||||||
aria-sort="ascending"
|
aria-sort="ascending"
|
||||||
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
@@ -6370,6 +6453,29 @@ exports[`App Route /packages authenticated 1`] = `
|
|||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</th>
|
</th>
|
||||||
|
<th
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiTableSortLabel-root"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Type
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
<th
|
<th
|
||||||
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
scope="col"
|
scope="col"
|
||||||
@@ -6403,7 +6509,7 @@ exports[`App Route /packages authenticated 1`] = `
|
|||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
Type
|
Update
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
|
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
|
||||||
@@ -6476,6 +6582,38 @@ exports[`App Route /packages authenticated 1`] = `
|
|||||||
<tr
|
<tr
|
||||||
class="MuiTableRow-root"
|
class="MuiTableRow-root"
|
||||||
>
|
>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-paddingCheckbox"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-0 MuiCheckbox-root MuiCheckbox-colorSecondary MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="PrivateSwitchBase-input-0"
|
||||||
|
data-indeterminate="false"
|
||||||
|
type="checkbox"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
<td
|
<td
|
||||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
>
|
>
|
||||||
@@ -6494,6 +6632,9 @@ exports[`App Route /packages authenticated 1`] = `
|
|||||||
<td
|
<td
|
||||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
/>
|
/>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
/>
|
||||||
<td
|
<td
|
||||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
>
|
>
|
||||||
@@ -6548,24 +6689,6 @@ exports[`App Route /packages authenticated 1`] = `
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<a
|
|
||||||
class=""
|
|
||||||
href="/package-deploy/1"
|
|
||||||
style="margin: 5px;"
|
|
||||||
title="Deploy \\"Test Manifest 1.0\\""
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
aria-label="Deploy Test Manifest 1.0"
|
|
||||||
class="MuiSvgIcon-root"
|
|
||||||
focusable="false"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -6577,7 +6700,7 @@ exports[`App Route /packages authenticated 1`] = `
|
|||||||
>
|
>
|
||||||
<td
|
<td
|
||||||
class="MuiTableCell-root MuiTableCell-footer MuiTablePagination-root"
|
class="MuiTableCell-root MuiTableCell-footer MuiTablePagination-root"
|
||||||
colspan="8"
|
colspan="10"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="MuiToolbar-root MuiToolbar-regular MuiTablePagination-toolbar MuiToolbar-gutters"
|
class="MuiToolbar-root MuiToolbar-regular MuiTablePagination-toolbar MuiToolbar-gutters"
|
||||||
@@ -6696,6 +6819,7 @@ exports[`App Route /packages authenticated 1`] = `
|
|||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
<div />
|
<div />
|
||||||
|
<div />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
@@ -11427,6 +11551,13 @@ exports[`App Route /vehicle-status authenticated 1`] = `
|
|||||||
:
|
:
|
||||||
false
|
false
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
DLT Logging Enabled
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
false
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
@@ -11440,63 +11571,7 @@ exports[`App Route /vehicle-status authenticated 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
>
|
/>
|
||||||
<label
|
|
||||||
class="MuiFormControlLabel-root"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-disabled="false"
|
|
||||||
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-0 MuiCheckbox-root MuiCheckbox-colorSecondary MuiIconButton-colorSecondary"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="MuiIconButton-label"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
class="PrivateSwitchBase-input-0"
|
|
||||||
data-indeterminate="false"
|
|
||||||
type="checkbox"
|
|
||||||
value=""
|
|
||||||
/>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
class="MuiSvgIcon-root"
|
|
||||||
focusable="false"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="MuiTouchRipple-root"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
|
||||||
>
|
|
||||||
Force Config Update
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<a
|
|
||||||
class=""
|
|
||||||
href="/vehicle-status/FISKER123"
|
|
||||||
title="Push Config Update to \\"FISKER123\\""
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
aria-label="Push Config Update to \\"FISKER123\\""
|
|
||||||
class="MuiSvgIcon-root MuiSvgIcon-fontSizeLarge css-tzssek-MuiSvgIcon-root"
|
|
||||||
data-testid="UploadIcon"
|
|
||||||
focusable="false"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M5 20h14v-2H5v2zm0-10h4v6h6v-6h4l-7-7-7 7z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
>
|
>
|
||||||
|
|||||||
84
src/components/BulkActions/index.jsx
Normal file
84
src/components/BulkActions/index.jsx
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import TransformModal from "../TransformModal";
|
||||||
|
import DropDownButton from "../Controls/DropDownButton";
|
||||||
|
import { useUserContext } from "../Contexts/UserContext";
|
||||||
|
import { useStatusContext } from "../Contexts/StatusContext";
|
||||||
|
import useAddTags from "./useAddTags";
|
||||||
|
import useUpdateConfig from "./useUpdateConfig";
|
||||||
|
|
||||||
|
const transformArrayToCSV = (arr) => arr.join(", ");
|
||||||
|
|
||||||
|
export default function BulkActions({
|
||||||
|
vins = [],
|
||||||
|
}) {
|
||||||
|
const [vinCSV, setVinCSV] = useState(transformArrayToCSV(vins));
|
||||||
|
const [active, setActive] = useState(null);
|
||||||
|
const actions = [
|
||||||
|
{
|
||||||
|
name: "Update Configs",
|
||||||
|
disabled: vins.length === 0,
|
||||||
|
trigger: () => setActive("updateConfig"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Add Tags",
|
||||||
|
disabled: vins.length === 0,
|
||||||
|
trigger: () => setActive("addTags"),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const updateConfig = useUpdateConfig();
|
||||||
|
const addTags = useAddTags();
|
||||||
|
|
||||||
|
const { setMessage } = useStatusContext();
|
||||||
|
const {
|
||||||
|
token: {
|
||||||
|
idToken: { jwtToken: token },
|
||||||
|
},
|
||||||
|
} = useUserContext();
|
||||||
|
|
||||||
|
const handleUpdateConfig = () => {
|
||||||
|
updateConfig.submit(vins, token)
|
||||||
|
.then(() => {
|
||||||
|
setMessage(`${vins.length} vehicles updated.`);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setMessage(error.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAddTags = () => {
|
||||||
|
addTags.submit(vins, token)
|
||||||
|
.then(() => setMessage(`Added ${addTags.data.tags.value.length} tags to ${vins.length} vehicles.`))
|
||||||
|
.catch((error) => setMessage(error.message));
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClose = () => setActive(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setVinCSV(transformArrayToCSV(vins));
|
||||||
|
}, [vins]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DropDownButton actions={actions} payload={[vins]} />
|
||||||
|
<TransformModal
|
||||||
|
title="Update Config"
|
||||||
|
body={`You are updating the config for the following VINs: ${vinCSV}.`}
|
||||||
|
close={handleClose}
|
||||||
|
open={active === "updateConfig"}
|
||||||
|
data={updateConfig.data}
|
||||||
|
setData={updateConfig.setData}
|
||||||
|
submit={handleUpdateConfig}
|
||||||
|
/>
|
||||||
|
<TransformModal
|
||||||
|
title="Add Tags"
|
||||||
|
body={`You are adding tags for the following VINs: ${vinCSV}.`}
|
||||||
|
close={handleClose}
|
||||||
|
open={active === "addTags"}
|
||||||
|
data={addTags.data}
|
||||||
|
setData={addTags.setData}
|
||||||
|
submit={handleAddTags}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
22
src/components/BulkActions/useAddTags.js
Normal file
22
src/components/BulkActions/useAddTags.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import vehiclesAPI from "../../services/vehiclesAPI";
|
||||||
|
|
||||||
|
export default function useAddTags() {
|
||||||
|
const [tags, setTags] = useState({
|
||||||
|
tags: {
|
||||||
|
label: "Tags",
|
||||||
|
type: "list.string",
|
||||||
|
value: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const submit = async (vins, token) => {
|
||||||
|
return vehiclesAPI.addTags(vins, tags.tags.value, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: tags,
|
||||||
|
setData: setTags,
|
||||||
|
submit,
|
||||||
|
};
|
||||||
|
}
|
||||||
40
src/components/BulkActions/useUpdateConfig.js
Normal file
40
src/components/BulkActions/useUpdateConfig.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import TaskRunner from "../../utils/taskRunner";
|
||||||
|
import vehiclesAPI from "../../services/vehiclesAPI";
|
||||||
|
|
||||||
|
export default function useUpdateConfig() {
|
||||||
|
const [config, setConfig] = useState({
|
||||||
|
force: {
|
||||||
|
label: "Force Push",
|
||||||
|
type: "boolean",
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const submit = async (vins, token) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const taskRunner = new TaskRunner(5);
|
||||||
|
|
||||||
|
const task = (vin, isLast) => {
|
||||||
|
return async () => vehiclesAPI.updateConfig(vin, config.force.value, token)
|
||||||
|
.then((response) => {
|
||||||
|
if (isLast) {
|
||||||
|
if (response.error) {
|
||||||
|
reject(response);
|
||||||
|
}
|
||||||
|
resolve(response)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => reject(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
vins.forEach((vin, index) => taskRunner.push(task(vin, index === vins.length - 1)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: config,
|
||||||
|
setData: setConfig,
|
||||||
|
submit,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -147,6 +147,13 @@ exports[`VehicleDetailsTab Render 1`] = `
|
|||||||
:
|
:
|
||||||
false
|
false
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
DLT Logging Enabled
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
false
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
@@ -160,63 +167,7 @@ exports[`VehicleDetailsTab Render 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
>
|
/>
|
||||||
<label
|
|
||||||
class="MuiFormControlLabel-root"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-disabled="false"
|
|
||||||
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-0 MuiCheckbox-root MuiCheckbox-colorSecondary MuiIconButton-colorSecondary"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="MuiIconButton-label"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
class="PrivateSwitchBase-input-0"
|
|
||||||
data-indeterminate="false"
|
|
||||||
type="checkbox"
|
|
||||||
value=""
|
|
||||||
/>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
class="MuiSvgIcon-root"
|
|
||||||
focusable="false"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="MuiTouchRipple-root"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
|
||||||
>
|
|
||||||
Force Config Update
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<a
|
|
||||||
class=""
|
|
||||||
href="/"
|
|
||||||
title="Push Config Update to \\"TESTVIN1234567890\\""
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
aria-label="Push Config Update to \\"TESTVIN1234567890\\""
|
|
||||||
class="MuiSvgIcon-root MuiSvgIcon-fontSizeLarge css-tzssek-MuiSvgIcon-root"
|
|
||||||
data-testid="UploadIcon"
|
|
||||||
focusable="false"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M5 20h14v-2H5v2zm0-10h4v6h6v-6h4l-7-7-7 7z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -115,38 +115,41 @@ const MainForm = ({ vin }) => {
|
|||||||
<b>Info Source</b>: {vehicle.info_source}
|
<b>Info Source</b>: {vehicle.info_source}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<b>Tags</b>: {vehicle.tags ? vehicle.tags.join(", ") : "none" }
|
<b>Tags</b>: {vehicle.tags ? vehicle.tags.join(", ") : "none"}
|
||||||
</p>
|
</p>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item md={12} className={classes.textCenterAlign}>
|
<Grid item md={12} className={classes.textCenterAlign}>
|
||||||
{vehicle.log_level != null && (
|
{vehicle.log_level != null && (
|
||||||
<p>
|
<p>
|
||||||
<b>Log Level</b>: {vehicle.log_level}
|
<b>Log Level</b>: {vehicle.log_level}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{vehicle.canbus && (
|
{vehicle.canbus && (
|
||||||
<>
|
<>
|
||||||
<p>
|
<p>
|
||||||
<b>CANBus Enabled</b>: {vehicle.canbus.enabled.toString()}
|
<b>CANBus Enabled</b>: {vehicle.canbus.enabled.toString()}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<b>Max Memory Buffer Size</b>: {vehicle.canbus.max_mem_buffer_size ?? "Default"}
|
<b>Max Memory Buffer Size</b>: {vehicle.canbus.max_mem_buffer_size ?? "Default"}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<b>Data Logger Enabled</b>: {vehicle.canbus.data_logger_enabled.toString()}
|
<b>Data Logger Enabled</b>: {vehicle.canbus.data_logger_enabled.toString()}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<b>Max Disk Buffer Size</b>: {vehicle.canbus.max_disk_buffer_size ?? "Default"}
|
<b>Max Disk Buffer Size</b>: {vehicle.canbus.max_disk_buffer_size ?? "Default"}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<b>Filters</b>: {vehicle.canbus.filters ? vehicle.canbus.filters.length : 0}
|
<b>Filters</b>: {vehicle.canbus.filters ? vehicle.canbus.filters.length : 0}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<b>DTC Enabled</b>: { (vehicle.canbus.dtc_enabled || false).toString() }
|
<b>DTC Enabled</b>: {(vehicle.canbus.dtc_enabled || false).toString()}
|
||||||
</p>
|
</p>
|
||||||
</>
|
<p>
|
||||||
)}
|
<b>DLT Logging Enabled</b>: {(vehicle.dlt_enabled || false).toString()}
|
||||||
</Grid>
|
</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
{showDebugMask && (
|
{showDebugMask && (
|
||||||
<Grid item md={12} className={classes.textCenterAlign}>
|
<Grid item md={12} className={classes.textCenterAlign}>
|
||||||
<p>
|
<p>
|
||||||
@@ -156,19 +159,19 @@ const MainForm = ({ vin }) => {
|
|||||||
)}
|
)}
|
||||||
<Grid item md={12} className={classes.textCenterAlign}>
|
<Grid item md={12} className={classes.textCenterAlign}>
|
||||||
<RoleWrap
|
<RoleWrap
|
||||||
groups={groups}
|
groups={groups}
|
||||||
providers={providers}
|
providers={providers}
|
||||||
rolesPerProvider={Permissions.FiskerCreate}
|
rolesPerProvider={Permissions.FiskerUpdateDeploy}
|
||||||
>
|
>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
label="Force Config Update"
|
label="Force Config Update"
|
||||||
control={
|
control={
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={forced}
|
checked={forced}
|
||||||
onChange={onForcedChange}
|
onChange={onForcedChange}
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Tooltip key={`push-config-${vin}`} title={`Push Config Update to "${vin}"`}>
|
<Tooltip key={`push-config-${vin}`} title={`Push Config Update to "${vin}"`}>
|
||||||
<Link to="#" onClick={() => setShowUploadConfigModal(true)} >
|
<Link to="#" onClick={() => setShowUploadConfigModal(true)} >
|
||||||
<UploadIcon aria-label={`Push Config Update to "${vin}"`} fontSize="large" />
|
<UploadIcon aria-label={`Push Config Update to "${vin}"`} fontSize="large" />
|
||||||
|
|||||||
@@ -2,24 +2,25 @@ jest.mock("../../../Contexts/VehicleContext");
|
|||||||
jest.mock("../../../Contexts/StatusContext");
|
jest.mock("../../../Contexts/StatusContext");
|
||||||
jest.mock("../../../Contexts/UserContext");
|
jest.mock("../../../Contexts/UserContext");
|
||||||
|
|
||||||
import { render, waitFor } from "@testing-library/react";
|
import { render, screen, waitFor } from "@testing-library/react";
|
||||||
import { BrowserRouter } from "react-router-dom";
|
import { BrowserRouter } from "react-router-dom";
|
||||||
import routeData from "react-router";
|
import routeData from "react-router";
|
||||||
|
|
||||||
import { VehicleProvider } from "../../../Contexts/VehicleContext";
|
import { VehicleProvider } from "../../../Contexts/VehicleContext";
|
||||||
import { StatusProvider } from "../../../Contexts/StatusContext";
|
import { StatusProvider } from "../../../Contexts/StatusContext";
|
||||||
import { UserProvider, setToken } from "../../../Contexts/UserContext";
|
import { UserProvider, setToken } from "../../../Contexts/UserContext";
|
||||||
import { TEST_AUTH_OBJECT_FISKER }from "../../../../utils/testing";
|
import { TEST_AUTH_OBJECT_FISKER } from "../../../../utils/testing";
|
||||||
import MainForm from "./index";
|
import MainForm from "./index";
|
||||||
import addSnapshotSerializer from "../../../../utils/snapshot";
|
import addSnapshotSerializer from "../../../../utils/snapshot";
|
||||||
|
import * as Roles from "../../../../utils/roles";
|
||||||
|
|
||||||
const renderVehicleDetailsTab = async () => {
|
const renderVehicleDetailsTab = async () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<VehicleProvider>
|
<VehicleProvider>
|
||||||
<StatusProvider>
|
<StatusProvider>
|
||||||
<UserProvider>
|
<UserProvider >
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<MainForm vin="TESTVIN1234567890"/>
|
<MainForm vin="TESTVIN1234567890" />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</UserProvider>
|
</UserProvider>
|
||||||
</StatusProvider>
|
</StatusProvider>
|
||||||
@@ -46,4 +47,23 @@ describe("VehicleDetailsTab", () => {
|
|||||||
const container = await renderVehicleDetailsTab();
|
const container = await renderVehicleDetailsTab();
|
||||||
expect(container).toMatchSnapshot();
|
expect(container).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders update config control when required permission is present.", () => {
|
||||||
|
const hasRole = jest.spyOn(Roles, 'hasRole');
|
||||||
|
hasRole.mockReturnValue(true);
|
||||||
|
render(
|
||||||
|
<VehicleProvider>
|
||||||
|
<StatusProvider>
|
||||||
|
<UserProvider>
|
||||||
|
<BrowserRouter>
|
||||||
|
<MainForm vin="TESTVIN1234567890" />
|
||||||
|
</BrowserRouter>
|
||||||
|
</UserProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
</VehicleProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByLabelText("Force Config Update")).toBeTruthy();
|
||||||
|
hasRole.mockRestore();
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
30
src/components/Cars/Status/RemoteDiagnosticCommands.jsx
Normal file
30
src/components/Cars/Status/RemoteDiagnosticCommands.jsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import useStyles from "../../useStyles";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import SendDiagnosticCommand from "../../Controls/SendDiagnosticCommand";
|
||||||
|
import { useParams } from "react-router";
|
||||||
|
|
||||||
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
|
|
||||||
|
import {VehicleProvider} from "../../Contexts/VehicleContext";
|
||||||
|
|
||||||
|
const RemoteDiagnosticCommandsTab = (props) => {
|
||||||
|
const { vin } = useParams();
|
||||||
|
const classes = useStyles();
|
||||||
|
const {
|
||||||
|
token: {
|
||||||
|
idToken: { jwtToken: token },
|
||||||
|
},
|
||||||
|
} = useUserContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={clsx(classes.paper, classes.tableSize)}>
|
||||||
|
<Typography variant="h6">Vehicle Diagnostic Commands</Typography>
|
||||||
|
<VehicleProvider>
|
||||||
|
<SendDiagnosticCommand vin={vin} token={token} classes={classes}></SendDiagnosticCommand>
|
||||||
|
</VehicleProvider>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RemoteDiagnosticCommandsTab
|
||||||
@@ -155,6 +155,13 @@ exports[`DetailsTab Render 1`] = `
|
|||||||
:
|
:
|
||||||
false
|
false
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
DLT Logging Enabled
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
false
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
@@ -168,63 +175,7 @@ exports[`DetailsTab Render 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
>
|
/>
|
||||||
<label
|
|
||||||
class="MuiFormControlLabel-root"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-disabled="false"
|
|
||||||
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-0 MuiCheckbox-root MuiCheckbox-colorSecondary MuiIconButton-colorSecondary"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="MuiIconButton-label"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
class="PrivateSwitchBase-input-0"
|
|
||||||
data-indeterminate="false"
|
|
||||||
type="checkbox"
|
|
||||||
value=""
|
|
||||||
/>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
class="MuiSvgIcon-root"
|
|
||||||
focusable="false"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="MuiTouchRipple-root"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
|
||||||
>
|
|
||||||
Force Config Update
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<a
|
|
||||||
class=""
|
|
||||||
href="/testroute/TESTVIN1234567890"
|
|
||||||
title="Push Config Update to \\"TESTVIN1234567890\\""
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
aria-label="Push Config Update to \\"TESTVIN1234567890\\""
|
|
||||||
class="MuiSvgIcon-root MuiSvgIcon-fontSizeLarge css-tzssek-MuiSvgIcon-root"
|
|
||||||
data-testid="UploadIcon"
|
|
||||||
focusable="false"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M5 20h14v-2H5v2zm0-10h4v6h6v-6h4l-7-7-7 7z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -237,6 +237,17 @@ exports[`DigitalTwinTab Render 1`] = `
|
|||||||
77.7 km/h
|
77.7 km/h
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="makeStyles-popupSection-0"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
Parked
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
Yes
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style="width: 100vh;"
|
style="width: 100vh;"
|
||||||
|
|||||||
@@ -331,63 +331,7 @@ exports[`CarStatus Render 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
>
|
/>
|
||||||
<label
|
|
||||||
class="MuiFormControlLabel-root"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-disabled="false"
|
|
||||||
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-0 MuiCheckbox-root MuiCheckbox-colorSecondary MuiIconButton-colorSecondary"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="MuiIconButton-label"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
class="PrivateSwitchBase-input-0"
|
|
||||||
data-indeterminate="false"
|
|
||||||
type="checkbox"
|
|
||||||
value=""
|
|
||||||
/>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
class="MuiSvgIcon-root"
|
|
||||||
focusable="false"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="MuiTouchRipple-root"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
|
||||||
>
|
|
||||||
Force Config Update
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<a
|
|
||||||
class=""
|
|
||||||
href="/"
|
|
||||||
title="Push Config Update to \\"TESTVIN1234567890\\""
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
aria-label="Push Config Update to \\"TESTVIN1234567890\\""
|
|
||||||
class="MuiSvgIcon-root MuiSvgIcon-fontSizeLarge css-tzssek-MuiSvgIcon-root"
|
|
||||||
data-testid="UploadIcon"
|
|
||||||
focusable="false"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M5 20h14v-2H5v2zm0-10h4v6h6v-6h4l-7-7-7 7z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import DigitalTwinTab from "./DigitalTwinTab";
|
|||||||
import ECUsTab from "./ECUsTab";
|
import ECUsTab from "./ECUsTab";
|
||||||
import FleetsTab from "./FleetsTab";
|
import FleetsTab from "./FleetsTab";
|
||||||
import RemoteCommandsTab from "./RemoteCommandsTab";
|
import RemoteCommandsTab from "./RemoteCommandsTab";
|
||||||
|
import RemoteDiagnosticCommandsTab from "./RemoteDiagnosticCommands";
|
||||||
import TRexLogsTab from "./TRexLogsTab";
|
import TRexLogsTab from "./TRexLogsTab";
|
||||||
|
|
||||||
const tabHashes = ["details", "updates", "filters"];
|
const tabHashes = ["details", "updates", "filters"];
|
||||||
@@ -63,6 +64,11 @@ const TabViews = [
|
|||||||
component: RemoteCommandsTab,
|
component: RemoteCommandsTab,
|
||||||
rolesPerProvider: Permissions.FiskerMagnaCreate,
|
rolesPerProvider: Permissions.FiskerMagnaCreate,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "Remote Diagnostic Commands",
|
||||||
|
component: RemoteDiagnosticCommandsTab,
|
||||||
|
rolesPerProvider: Permissions.CarDiagnostic,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: "Fleets",
|
label: "Fleets",
|
||||||
component: FleetsTab,
|
component: FleetsTab,
|
||||||
|
|||||||
@@ -1006,6 +1006,43 @@ exports[`VehicleUpdate Render 1`] = `
|
|||||||
DTC Enabled
|
DTC Enabled
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
<label
|
||||||
|
class="MuiFormControlLabel-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-0 MuiCheckbox-root MuiCheckbox-colorSecondary MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="PrivateSwitchBase-input-0"
|
||||||
|
data-indeterminate="false"
|
||||||
|
type="checkbox"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
DLT Logging Enabled (supported from T.Rex 1.1.127)
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
<div
|
<div
|
||||||
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ const MainForm = () => {
|
|||||||
const [maxMemBufferSize, setMaxMemBufferSize] = useState(0);
|
const [maxMemBufferSize, setMaxMemBufferSize] = useState(0);
|
||||||
const [maxDiskBufferSize, setMaxDiskBufferSize] = useState(0);
|
const [maxDiskBufferSize, setMaxDiskBufferSize] = useState(0);
|
||||||
const [dtcEnabled, setDTCEnabled] = useState(true);
|
const [dtcEnabled, setDTCEnabled] = useState(true);
|
||||||
|
const [dltEnabled, setDLTEnabled] = useState(false);
|
||||||
const debugMaskEl = useRef(null);
|
const debugMaskEl = useRef(null);
|
||||||
const tagsEl = useRef(null);
|
const tagsEl = useRef(null);
|
||||||
|
|
||||||
@@ -99,6 +100,7 @@ const MainForm = () => {
|
|||||||
setMaxDiskBufferSize(vehicle.canbus.max_disk_buffer_size ?? maxDiskBufferSize);
|
setMaxDiskBufferSize(vehicle.canbus.max_disk_buffer_size ?? maxDiskBufferSize);
|
||||||
setDTCEnabled(vehicle.canbus.dtc_enabled ?? dtcEnabled);
|
setDTCEnabled(vehicle.canbus.dtc_enabled ?? dtcEnabled);
|
||||||
}
|
}
|
||||||
|
setDLTEnabled(vehicle.dlt_enabled ?? dltEnabled);
|
||||||
|
|
||||||
if (showDebugMask) {
|
if (showDebugMask) {
|
||||||
debugMaskEl.current.value = vehicle.debug_mask ?? ""
|
debugMaskEl.current.value = vehicle.debug_mask ?? ""
|
||||||
@@ -125,6 +127,10 @@ const MainForm = () => {
|
|||||||
setDTCEnabled(event.target.checked);
|
setDTCEnabled(event.target.checked);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onDltEnabledChange = (event) => {
|
||||||
|
setDLTEnabled(event.target.checked);
|
||||||
|
}
|
||||||
|
|
||||||
const onMaxMemBufferSizeChange = (event) => {
|
const onMaxMemBufferSizeChange = (event) => {
|
||||||
setMaxMemBufferSize(event.target.value);
|
setMaxMemBufferSize(event.target.value);
|
||||||
}
|
}
|
||||||
@@ -148,7 +154,7 @@ const MainForm = () => {
|
|||||||
restraint: restraintEl.current.value,
|
restraint: restraintEl.current.value,
|
||||||
body_type: bodyTypeEl.current.value,
|
body_type: bodyTypeEl.current.value,
|
||||||
log_level: selectedLogLevel,
|
log_level: selectedLogLevel,
|
||||||
tags: tagsEl.current.value.split(",").map(function (word) {
|
tags: tagsEl.current.value.split(",").map(function(word) {
|
||||||
return word.trim();
|
return word.trim();
|
||||||
}),
|
}),
|
||||||
canbus: {
|
canbus: {
|
||||||
@@ -158,6 +164,7 @@ const MainForm = () => {
|
|||||||
max_disk_buffer_size: canbusEnabled && dataLoggerEnabled ? parseInt(maxDiskBufferSize) : 0,
|
max_disk_buffer_size: canbusEnabled && dataLoggerEnabled ? parseInt(maxDiskBufferSize) : 0,
|
||||||
dtc_enabled: dtcEnabled
|
dtc_enabled: dtcEnabled
|
||||||
},
|
},
|
||||||
|
dlt_enabled: dltEnabled,
|
||||||
debug_mask: debugMaskEl.current?.value
|
debug_mask: debugMaskEl.current?.value
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -423,6 +430,12 @@ const MainForm = () => {
|
|||||||
onChange={onDtcEnabledChange}
|
onChange={onDtcEnabledChange}
|
||||||
/>
|
/>
|
||||||
} label="DTC Enabled" />
|
} label="DTC Enabled" />
|
||||||
|
<FormControlLabel control={
|
||||||
|
<Checkbox
|
||||||
|
checked={dltEnabled}
|
||||||
|
onChange={onDltEnabledChange}
|
||||||
|
/>
|
||||||
|
} label="DLT Logging Enabled (supported from T.Rex 1.1.127)" />
|
||||||
{showDebugMask && (
|
{showDebugMask && (
|
||||||
<TextField
|
<TextField
|
||||||
id="debug_mask"
|
id="debug_mask"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useContext, useState } from "react";
|
import React, { useContext, useState } from "react";
|
||||||
import api from "../../services/fleetsAPI";
|
import api from "../../services/fleetsAPI";
|
||||||
|
import vehiclesAPI from "../../services/vehiclesAPI";
|
||||||
import { validateCANID, validateFilter, validateVIN } from "../../utils/validationSupplier";
|
import { validateCANID, validateFilter, validateVIN } from "../../utils/validationSupplier";
|
||||||
|
|
||||||
const FleetContext = React.createContext();
|
const FleetContext = React.createContext();
|
||||||
@@ -112,7 +113,22 @@ export const FleetProvider = ({ children }) => {
|
|||||||
throw new Error(`Get fleet vehicles error. ${result.message}`);
|
throw new Error(`Get fleet vehicles error. ${result.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
setFleetVehicles(result.data)
|
const connectionsResult = await vehiclesAPI.getConnections(result.data, token)
|
||||||
|
if (result.error) {
|
||||||
|
setFleetVehicles([])
|
||||||
|
throw new Error(`Get vehicles connections error. ${result.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
var cars = []
|
||||||
|
result.data.forEach((vin) => {
|
||||||
|
cars.push({
|
||||||
|
vin: vin,
|
||||||
|
connected: connectionsResult[vin] || false,
|
||||||
|
connectedHMI: connectionsResult[`2:${vin}`] || false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
setFleetVehicles(cars)
|
||||||
if (result.total) {
|
if (result.total) {
|
||||||
setTotalFleetVehicles(result.total);
|
setTotalFleetVehicles(result.total);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
jest.mock("../../services/fleetsAPI");
|
jest.mock("../../services/fleetsAPI");
|
||||||
|
jest.mock("../../services/vehiclesAPI");
|
||||||
|
|
||||||
import {
|
import {
|
||||||
render,
|
render,
|
||||||
@@ -800,9 +801,21 @@ const expectedFleetsData = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const expectedFleetVehiclesData = [
|
const expectedFleetVehiclesData = [
|
||||||
"USWESTVIN12345678",
|
{
|
||||||
"USWESTVIN12345679",
|
vin: "USWESTVIN12345678",
|
||||||
"USWESTVIN12345670",
|
connected: true,
|
||||||
|
connectedHMI: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
vin: "USWESTVIN12345679",
|
||||||
|
connected: true,
|
||||||
|
connectedHMI: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
vin: "USWESTVIN12345670",
|
||||||
|
connected: true,
|
||||||
|
connectedHMI: false,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const expectedFleetCANFiltersData = [
|
const expectedFleetCANFiltersData = [
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export const VehicleProvider = ({ children }) => {
|
|||||||
|
|
||||||
const result = await api.addTags(vins, tags, token)
|
const result = await api.addTags(vins, tags, token)
|
||||||
if (result.error)
|
if (result.error)
|
||||||
throw new Error(`Add tags error. ${result.message}`);
|
throw new Error(`Add tags error. ${result.message}`);
|
||||||
} finally {
|
} finally {
|
||||||
setBusy(false)
|
setBusy(false)
|
||||||
}
|
}
|
||||||
@@ -105,7 +105,7 @@ export const VehicleProvider = ({ children }) => {
|
|||||||
setBusy(true);
|
setBusy(true);
|
||||||
const result = await api.getLocations(token);
|
const result = await api.getLocations(token);
|
||||||
if (result.error)
|
if (result.error)
|
||||||
throw new Error(`Get locations vehicle paths error. ${result.message}`);
|
throw new Error(`Get locations error. ${result.message}`);
|
||||||
return result;
|
return result;
|
||||||
} finally {
|
} finally {
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
@@ -202,6 +202,18 @@ export const VehicleProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sendDiagnosticCommand = async (vins, command, token) => {
|
||||||
|
try {
|
||||||
|
setBusy(true);
|
||||||
|
const result = await api.sendDiagnosticCommand(vins, command, token);
|
||||||
|
if (result.error)
|
||||||
|
throw new Error(`Send diagnostic command error. ${result.message}`);
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const updateVehicle = async (vin, v, token) => {
|
const updateVehicle = async (vin, v, token) => {
|
||||||
try {
|
try {
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
@@ -313,6 +325,7 @@ export const VehicleProvider = ({ children }) => {
|
|||||||
getVehicle,
|
getVehicle,
|
||||||
getVehicles,
|
getVehicles,
|
||||||
sendCommand,
|
sendCommand,
|
||||||
|
sendDiagnosticCommand,
|
||||||
updateVehicle,
|
updateVehicle,
|
||||||
getFleets,
|
getFleets,
|
||||||
getVersionLog,
|
getVersionLog,
|
||||||
|
|||||||
@@ -62,7 +62,26 @@ export const useFleetContext = () => ({
|
|||||||
|
|
||||||
fleetVehicles,
|
fleetVehicles,
|
||||||
totalFleetVehicles,
|
totalFleetVehicles,
|
||||||
getFleetVehicles: jest.fn(),
|
getFleetVehicles: jest.fn().mockImplementation((name, search, _token) => {
|
||||||
|
const result = [
|
||||||
|
{
|
||||||
|
vin: "USWESTVIN12345678",
|
||||||
|
connected: false,
|
||||||
|
connectedHMI: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
vin: "USWESTVIN12345679",
|
||||||
|
connected: true,
|
||||||
|
connectedHMI: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
vin: "USWESTVIN12345670",
|
||||||
|
connected: false,
|
||||||
|
connectedHMI: false
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return Promise.resolve(result);
|
||||||
|
}),
|
||||||
addFleetVehicles: jest.fn(),
|
addFleetVehicles: jest.fn(),
|
||||||
deleteFleetVehicle: jest.fn(),
|
deleteFleetVehicle: jest.fn(),
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,9 @@ let vehicleState = {
|
|||||||
vehicle_speed: {
|
vehicle_speed: {
|
||||||
speed: 77.7,
|
speed: 77.7,
|
||||||
},
|
},
|
||||||
|
gear: {
|
||||||
|
in_park: true,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -109,10 +112,14 @@ export const useVehicleContext = () => ({
|
|||||||
addVehicle: jest.fn(),
|
addVehicle: jest.fn(),
|
||||||
getConnections: jest
|
getConnections: jest
|
||||||
.fn().mockImplementation((vins, _token) => {
|
.fn().mockImplementation((vins, _token) => {
|
||||||
const result = {};
|
const result = {
|
||||||
vins.forEach((vin) => {
|
"USWESTVIN12345678": true,
|
||||||
result[vin] = true;
|
"2:USWESTVIN12345678": false,
|
||||||
});
|
"USWESTVIN12345679": true,
|
||||||
|
"2:USWESTVIN12345679": false,
|
||||||
|
"USWESTVIN12345670": true,
|
||||||
|
"2:USWESTVIN12345670": false,
|
||||||
|
};
|
||||||
return Promise.resolve(result);
|
return Promise.resolve(result);
|
||||||
}),
|
}),
|
||||||
getECUs: jest.fn(() => {
|
getECUs: jest.fn(() => {
|
||||||
@@ -146,9 +153,9 @@ export const useVehicleContext = () => ({
|
|||||||
.fn()
|
.fn()
|
||||||
.mockResolvedValue({
|
.mockResolvedValue({
|
||||||
// tests only pass without mocking the data here
|
// tests only pass without mocking the data here
|
||||||
// '3FAFP13P71R199267': [],
|
|
||||||
// '3FAFP13P31R199430': [[16.891136999999986, 26.832352999999955], [56.891136999999986, 66.832352999999955], [26.891136999999986, 36.832352999999955]],
|
// '3FAFP13P31R199430': [[16.891136999999986, 26.832352999999955], [56.891136999999986, 66.832352999999955], [26.891136999999986, 36.832352999999955]],
|
||||||
// '3FAFP13P71R199060': [[36.891136999999986, 46.832352999999955], [76.891136999999986, 16.832352999999955]],
|
// '3FAFP13P71R199060': [[36.891136999999986, 46.832352999999955], [76.891136999999986, 16.832352999999955]],
|
||||||
|
// '3FAFP13P61R199390': [],
|
||||||
}),
|
}),
|
||||||
getModels: jest.fn(() => {
|
getModels: jest.fn(() => {
|
||||||
models = ["Ocean", "PEAR"];
|
models = ["Ocean", "PEAR"];
|
||||||
|
|||||||
@@ -40,6 +40,10 @@ const DropDownButton = ({ actions = [], payload = [] }) => {
|
|||||||
setOpen(false);
|
setOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!actions.length) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import React from "react";
|
|||||||
import { hasRole } from "../../../utils/roles";
|
import { hasRole } from "../../../utils/roles";
|
||||||
|
|
||||||
export const RoleWrap = (props) => {
|
export const RoleWrap = (props) => {
|
||||||
const {groups, rolesPerProvider, providers} = props;
|
const { groups, rolesPerProvider, providers } = props;
|
||||||
|
|
||||||
const eitherComponent = props["eitherComponent"] || null;
|
const eitherComponent = props["eitherComponent"] || null;
|
||||||
|
|
||||||
if (!hasRole(groups, rolesPerProvider, providers)) {
|
if (!hasRole(groups, rolesPerProvider, providers)) {
|
||||||
return eitherComponent != null ? eitherComponent : <></>;
|
return eitherComponent != null ? eitherComponent : <></>;
|
||||||
|
|||||||
135
src/components/Controls/SendDiagnosticCommand/index.jsx
Normal file
135
src/components/Controls/SendDiagnosticCommand/index.jsx
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
import { Button, FormControl, InputLabel, Select, FormControlLabel, FormGroup } from "@material-ui/core";
|
||||||
|
import Checkbox from '@mui/material/Checkbox';
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
|
import { logger } from "../../../services/monitoring";
|
||||||
|
import cmp from "semver-compare";
|
||||||
|
|
||||||
|
|
||||||
|
import {
|
||||||
|
useVehicleContext
|
||||||
|
} from "../../Contexts/VehicleContext";
|
||||||
|
|
||||||
|
const commands = ["Reset"]
|
||||||
|
const ecus = ["TBOX"]
|
||||||
|
|
||||||
|
const SendDiagnosticCommand = ({ vin, token, classes }) => {
|
||||||
|
|
||||||
|
const { getState, sendDiagnosticCommand } = useVehicleContext();
|
||||||
|
|
||||||
|
const [carState, setCarState] = useState(null);
|
||||||
|
const { setMessage } = useStatusContext();
|
||||||
|
|
||||||
|
const [currentCommand, setCurrentCommand] = useState(commands[0].toLowerCase());
|
||||||
|
const [currentECUs] = useState([ecus[0]]);
|
||||||
|
|
||||||
|
const changeCommandHandler = (e) => {
|
||||||
|
setCurrentCommand(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//Update online/offline state
|
||||||
|
useEffect(() => {
|
||||||
|
if (!vin) return;
|
||||||
|
getCarState();
|
||||||
|
const interval = setInterval(getCarState, 5000);
|
||||||
|
return () => { clearInterval(interval); }
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [vin]);
|
||||||
|
|
||||||
|
const getCarState = async () => {
|
||||||
|
try {
|
||||||
|
const result = await getState(token, vin);
|
||||||
|
setCarState(result.data);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
logger.warn(e.stack);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isOnline = () => {
|
||||||
|
return carState && carState?.online;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TREX_MIN_VER = "1.1.108";
|
||||||
|
const isTBOXResetSupported = () => {
|
||||||
|
return !carState?.trex_version ? true : cmp(carState.trex_version, TREX_MIN_VER) === 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const clickHandler = async (_) => {
|
||||||
|
try {
|
||||||
|
await sendDiagnosticCommand([vin], { body: { command: currentCommand, ecus: currentECUs } }, token);
|
||||||
|
setMessage(`Sent diagnostic command to ${vin}`);
|
||||||
|
} catch (error) {
|
||||||
|
setMessage(error.message);
|
||||||
|
logger.error(error.stack);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={clsx(classes.paper, classes.tableSize)}>
|
||||||
|
<FormControl
|
||||||
|
className={classes.formControl}
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<InputLabel htmlFor="send-command" className={classes.whiteBackground}>
|
||||||
|
Diagnostic Command
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
native
|
||||||
|
variant="outlined"
|
||||||
|
inputProps={{
|
||||||
|
name: "send-command",
|
||||||
|
id: "send-command",
|
||||||
|
}}
|
||||||
|
onChange={changeCommandHandler}
|
||||||
|
>
|
||||||
|
{commands.map((command, index) => (
|
||||||
|
<option key={index} value={command.toLowerCase()}>
|
||||||
|
{command}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormGroup>
|
||||||
|
{
|
||||||
|
ecus.map((ecu, idx) => {
|
||||||
|
return <FormControlLabel
|
||||||
|
control={<Checkbox key={idx} />}
|
||||||
|
label={ecu}
|
||||||
|
value={ecu}
|
||||||
|
checked={true} />
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</FormGroup>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
aria-label="send command"
|
||||||
|
fullWidth
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
className={classes.submit}
|
||||||
|
onClick={clickHandler}
|
||||||
|
disabled={!isOnline() || !isTBOXResetSupported()}
|
||||||
|
>
|
||||||
|
Send
|
||||||
|
</Button>
|
||||||
|
<div>
|
||||||
|
<b>
|
||||||
|
{isOnline() ? "ONLINE" : "OFFLINE"}
|
||||||
|
</b>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<b>
|
||||||
|
{!isTBOXResetSupported() ? `TBOX Reset supported from ${TREX_MIN_VER}, current version ${carState.trex_version}` : ""}
|
||||||
|
</b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div >
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SendDiagnosticCommand;
|
||||||
@@ -7,6 +7,8 @@ import useStyles from "../useStyles";
|
|||||||
const UNKNOWN = "unknown";
|
const UNKNOWN = "unknown";
|
||||||
const LOCKED = "Locked";
|
const LOCKED = "Locked";
|
||||||
const UNLOCKED = "Unlocked";
|
const UNLOCKED = "Unlocked";
|
||||||
|
const PARKED = "Yes";
|
||||||
|
const NOT_PARKED = "Not Parked";
|
||||||
|
|
||||||
const appendUnits = (value, units) => {
|
const appendUnits = (value, units) => {
|
||||||
if (value || value === 0) return `${value}${units}`;
|
if (value || value === 0) return `${value}${units}`;
|
||||||
@@ -32,7 +34,7 @@ const windowState = (value) => {
|
|||||||
|
|
||||||
const DigitalTwin = (props) => {
|
const DigitalTwin = (props) => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const { battery, doors, location, trex_version, ip, updated, windows, misc_windows, sunroof, dbc_version, door_locks, vcu0x260, charging_metrics, max_range, vehicle_speed } = props;
|
const { battery, doors, location, trex_version, ip, updated, windows, misc_windows, sunroof, dbc_version, door_locks, vcu0x260, charging_metrics, max_range, vehicle_speed, gear } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -133,6 +135,11 @@ const DigitalTwin = (props) => {
|
|||||||
{keyValueTemplate("Vehicle Speed", appendUnits(vehicle_speed?.speed, " km/h"))}
|
{keyValueTemplate("Vehicle Speed", appendUnits(vehicle_speed?.speed, " km/h"))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{gear && (
|
||||||
|
<div className={classes.popupSection}>
|
||||||
|
{keyValueTemplate("Parked", gear.in_park ? PARKED : NOT_PARKED)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -143,6 +143,48 @@ exports[`FleetDetailsTab Render 1`] = `
|
|||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="choose action"
|
||||||
|
class="MuiButtonGroup-root MuiButtonGroup-contained css-zqcytf-MuiButtonGroup-root"
|
||||||
|
role="group"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-sghohy-MuiButtonBase-root-MuiButton-root"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Update Configs
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-haspopup="menu"
|
||||||
|
aria-label="select action"
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-11qr2p8-MuiButtonBase-root-MuiButton-root"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-i4bv87-MuiSvgIcon-root"
|
||||||
|
data-testid="ArrowDropDownIcon"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="m7 10 5 5 5-5z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { FleetProvider, useFleetContext } from "../../../Contexts/FleetContext"
|
|||||||
import useStyles from "../../../useStyles";
|
import useStyles from "../../../useStyles";
|
||||||
import { logger } from "../../../../services/monitoring";
|
import { logger } from "../../../../services/monitoring";
|
||||||
import DeleteConfirmation from "../../../DeleteConfirmation";
|
import DeleteConfirmation from "../../../DeleteConfirmation";
|
||||||
|
import BulkActions from "../../../BulkActions";
|
||||||
|
|
||||||
const MainForm = ({ name }) => {
|
const MainForm = ({ name }) => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
@@ -94,6 +95,9 @@ const MainForm = ({ name }) => {
|
|||||||
</Link>
|
</Link>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid item md={12} className={classes.textCenterAlign}>
|
||||||
|
<BulkActions vins={fleet.vehicles} />
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<DeleteConfirmation message={name} open={showDeleteModal} close={() => setShowDeleteModal(false)} deleteFunction={onDelete} />
|
<DeleteConfirmation message={name} open={showDeleteModal} close={() => setShowDeleteModal(false)} deleteFunction={onDelete} />
|
||||||
</div >
|
</div >
|
||||||
|
|||||||
@@ -137,62 +137,7 @@ exports[`FleetVehiclesTable Render 1`] = `
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody
|
<tbody
|
||||||
class="MuiTableBody-root"
|
class="MuiTableBody-root"
|
||||||
>
|
/>
|
||||||
<tr
|
|
||||||
class="MuiTableRow-root"
|
|
||||||
>
|
|
||||||
<td
|
|
||||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="/vehicle-status/USWESTVIN12345678"
|
|
||||||
>
|
|
||||||
USWESTVIN12345678
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td
|
|
||||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
|
||||||
>
|
|
||||||
No actions
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr
|
|
||||||
class="MuiTableRow-root"
|
|
||||||
>
|
|
||||||
<td
|
|
||||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="/vehicle-status/USWESTVIN12345679"
|
|
||||||
>
|
|
||||||
USWESTVIN12345679
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td
|
|
||||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
|
||||||
>
|
|
||||||
No actions
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr
|
|
||||||
class="MuiTableRow-root"
|
|
||||||
>
|
|
||||||
<td
|
|
||||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="/vehicle-status/USWESTVIN12345670"
|
|
||||||
>
|
|
||||||
USWESTVIN12345670
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td
|
|
||||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
|
||||||
>
|
|
||||||
No actions
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
<tfoot
|
<tfoot
|
||||||
class="MuiTableFooter-root"
|
class="MuiTableFooter-root"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import SearchField from "../../../../Controls/SearchField";
|
|||||||
import DeleteConfirmation from "../../../../DeleteConfirmation";
|
import DeleteConfirmation from "../../../../DeleteConfirmation";
|
||||||
import TableHeaderSortable from "../../../../Table/HeaderSortable";
|
import TableHeaderSortable from "../../../../Table/HeaderSortable";
|
||||||
import { useLocalStorage } from "../../../../useLocalStorage";
|
import { useLocalStorage } from "../../../../useLocalStorage";
|
||||||
|
import ConnectedIcon from "../../../../Controls/ConnectedIcon";
|
||||||
import useStyles from "../../../../useStyles";
|
import useStyles from "../../../../useStyles";
|
||||||
|
|
||||||
const tableColumns = [
|
const tableColumns = [
|
||||||
@@ -190,13 +191,22 @@ const MainForm = ({ name }) => {
|
|||||||
onSortRequest={handleSort}
|
onSortRequest={handleSort}
|
||||||
/>
|
/>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{fleetVehicles.map((vin) => (
|
{fleetVehicles && fleetVehicles.map((car) => (
|
||||||
<TableRow key={vin}>
|
(car.vin && <TableRow key={"row" + car.vin}>
|
||||||
<TableCell align="center">
|
<TableCell key={"cell" + car.vin} align="center">
|
||||||
<Link to={`/vehicle-status/${vin}`}>{vin}</Link>
|
{(car.connected || car.connectedHMI) &&
|
||||||
|
<ConnectedIcon
|
||||||
|
key={"icon" + car.vin}
|
||||||
|
connected={car.connected}
|
||||||
|
connectedHMI={car.connectedHMI}
|
||||||
|
style={{ marginRight: 3 }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
<Link key={"link" + car.vin} to={`/vehicle-status/${car.vin}`}>{car.vin}</Link>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell align="center">{Actions(vin)}</TableCell>
|
<TableCell key={"cell2" + car.vin} align="center">{Actions(car.vin)}</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
)
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
<TableFooter>
|
<TableFooter>
|
||||||
|
|||||||
@@ -151,6 +151,48 @@ exports[`DetailsTab Render 1`] = `
|
|||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="choose action"
|
||||||
|
class="MuiButtonGroup-root MuiButtonGroup-contained css-zqcytf-MuiButtonGroup-root"
|
||||||
|
role="group"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-sghohy-MuiButtonBase-root-MuiButton-root"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Update Configs
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-haspopup="menu"
|
||||||
|
aria-label="select action"
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-11qr2p8-MuiButtonBase-root-MuiButton-root"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-i4bv87-MuiSvgIcon-root"
|
||||||
|
data-testid="ArrowDropDownIcon"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="m7 10 5 5 5-5z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -136,62 +136,7 @@ exports[`VehiclesTab Render 1`] = `
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody
|
<tbody
|
||||||
class="MuiTableBody-root"
|
class="MuiTableBody-root"
|
||||||
>
|
/>
|
||||||
<tr
|
|
||||||
class="MuiTableRow-root"
|
|
||||||
>
|
|
||||||
<td
|
|
||||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="/vehicle-status/USWESTVIN12345678"
|
|
||||||
>
|
|
||||||
USWESTVIN12345678
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td
|
|
||||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
|
||||||
>
|
|
||||||
No actions
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr
|
|
||||||
class="MuiTableRow-root"
|
|
||||||
>
|
|
||||||
<td
|
|
||||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="/vehicle-status/USWESTVIN12345679"
|
|
||||||
>
|
|
||||||
USWESTVIN12345679
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td
|
|
||||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
|
||||||
>
|
|
||||||
No actions
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr
|
|
||||||
class="MuiTableRow-root"
|
|
||||||
>
|
|
||||||
<td
|
|
||||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="/vehicle-status/USWESTVIN12345670"
|
|
||||||
>
|
|
||||||
USWESTVIN12345670
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td
|
|
||||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
|
||||||
>
|
|
||||||
No actions
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
<tfoot
|
<tfoot
|
||||||
class="MuiTableFooter-root"
|
class="MuiTableFooter-root"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -239,6 +239,48 @@ exports[`FleetStatus Render 1`] = `
|
|||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="choose action"
|
||||||
|
class="MuiButtonGroup-root MuiButtonGroup-contained css-zqcytf-MuiButtonGroup-root"
|
||||||
|
role="group"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-sghohy-MuiButtonBase-root-MuiButton-root"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Update Configs
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-haspopup="menu"
|
||||||
|
aria-label="select action"
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeSmall MuiButton-containedSizeSmall MuiButtonGroup-grouped MuiButtonGroup-groupedHorizontal MuiButtonGroup-groupedContained MuiButtonGroup-groupedContainedHorizontal MuiButtonGroup-groupedContainedPrimary css-11qr2p8-MuiButtonBase-root-MuiButton-root"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-i4bv87-MuiSvgIcon-root"
|
||||||
|
data-testid="ArrowDropDownIcon"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="m7 10 5 5 5-5z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
Checkbox,
|
||||||
Grid,
|
Grid,
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -6,7 +7,7 @@ import {
|
|||||||
TableFooter,
|
TableFooter,
|
||||||
TablePagination,
|
TablePagination,
|
||||||
TableRow,
|
TableRow,
|
||||||
Tooltip
|
Tooltip,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import DeleteIcon from "@material-ui/icons/Delete";
|
import DeleteIcon from "@material-ui/icons/Delete";
|
||||||
import SendIcon from "@material-ui/icons/Send";
|
import SendIcon from "@material-ui/icons/Send";
|
||||||
@@ -22,14 +23,15 @@ import { Link } from "react-router-dom";
|
|||||||
import EditIcon from "@material-ui/icons/Edit";
|
import EditIcon from "@material-ui/icons/Edit";
|
||||||
import { logger } from "../../../services/monitoring";
|
import { logger } from "../../../services/monitoring";
|
||||||
import { LocalDateTimeString } from "../../../utils/dates";
|
import { LocalDateTimeString } from "../../../utils/dates";
|
||||||
import { TYPE_MANIFEST_SOFTWARE } from "../../../utils/manifest_types";
|
import { TYPE_MANIFEST_AFTERSALES, TYPE_MANIFEST_CONFIG, TYPE_MANIFEST_SOFTWARE } from "../../../utils/manifest_types";
|
||||||
import { hasRole, Permissions } from "../../../utils/roles";
|
import { Permissions, hasRole } from "../../../utils/roles";
|
||||||
import {
|
import {
|
||||||
ManifestsProvider,
|
ManifestsProvider,
|
||||||
useManifestsContext
|
useManifestsContext
|
||||||
} from "../../Contexts/ManifestsContext";
|
} from "../../Contexts/ManifestsContext";
|
||||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
import { useUserContext } from "../../Contexts/UserContext";
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
|
import DropDownButton from "../../Controls/DropDownButton";
|
||||||
import ECUList from "../../Controls/ECUList";
|
import ECUList from "../../Controls/ECUList";
|
||||||
import { RoleWrap } from "../../Controls/RoleWrap";
|
import { RoleWrap } from "../../Controls/RoleWrap";
|
||||||
import SearchField from "../../Controls/SearchField";
|
import SearchField from "../../Controls/SearchField";
|
||||||
@@ -37,6 +39,8 @@ import DeleteConfirmation from "../../DeleteConfirmation";
|
|||||||
import TableHeaderSortable from "../../Table/HeaderSortable";
|
import TableHeaderSortable from "../../Table/HeaderSortable";
|
||||||
import { useLocalStorage } from "../../useLocalStorage";
|
import { useLocalStorage } from "../../useLocalStorage";
|
||||||
import useStyles from "../../useStyles";
|
import useStyles from "../../useStyles";
|
||||||
|
import { useUpdateManifest } from "../../../hooks";
|
||||||
|
import GeneralConfirmation from "../../GeneralConfirmation";
|
||||||
|
|
||||||
const tableColumns = [
|
const tableColumns = [
|
||||||
{
|
{
|
||||||
@@ -51,13 +55,17 @@ const tableColumns = [
|
|||||||
id: "version",
|
id: "version",
|
||||||
label: "Version",
|
label: "Version",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "manifest_type",
|
||||||
|
label: "Type",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "sums",
|
id: "sums",
|
||||||
label: "SUMS",
|
label: "SUMS",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "type",
|
id: "type",
|
||||||
label: "Type",
|
label: "Update",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "created_at",
|
id: "created_at",
|
||||||
@@ -73,7 +81,7 @@ const tableColumns = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const formatManifestType = (type) => {
|
const formatType = (type) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "forced":
|
case "forced":
|
||||||
return "Forced";
|
return "Forced";
|
||||||
@@ -82,6 +90,21 @@ const formatManifestType = (type) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatManifestType = (manifestType) => {
|
||||||
|
switch (manifestType) {
|
||||||
|
case 1:
|
||||||
|
return "Software";
|
||||||
|
case 2:
|
||||||
|
return "Config";
|
||||||
|
case 3:
|
||||||
|
return "Magna";
|
||||||
|
case 4:
|
||||||
|
return "Aftersales";
|
||||||
|
default:
|
||||||
|
return manifestType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const PAGE_SIZE = "MANIFEST_LIST_PAGE_SIZE";
|
const PAGE_SIZE = "MANIFEST_LIST_PAGE_SIZE";
|
||||||
|
|
||||||
const MainForm = () => {
|
const MainForm = () => {
|
||||||
@@ -91,13 +114,13 @@ const MainForm = () => {
|
|||||||
const [orderBy, setOrderBy] = useState("id");
|
const [orderBy, setOrderBy] = useState("id");
|
||||||
const [order, setOrder] = useState("asc");
|
const [order, setOrder] = useState("asc");
|
||||||
const [search, setSearch] = useLocalStorage("DEPLOYMENT_SEARCH", "");
|
const [search, setSearch] = useLocalStorage("DEPLOYMENT_SEARCH", "");
|
||||||
const [active, setActive] = useLocalStorage("DEPLOYMENT_ACTIVE", "true");
|
const [active, setActive] = useLocalStorage("DEPLOYMENT_TAB_TOGGLE", "software");
|
||||||
|
|
||||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||||
const [deleteId, setDeleteId] = useState("");
|
const [showArchiveModal, setShowArchiveModal] = useState(false);
|
||||||
const [deleteRowName, setDeleteRowName] = useState("");
|
const [archiveLabel, setArchiveLabel] = useState("Archive");
|
||||||
|
|
||||||
const { getManifests, deleteManifest, manifests, totalManifests } =
|
const { getManifests, manifests, totalManifests } =
|
||||||
useManifestsContext();
|
useManifestsContext();
|
||||||
const { setMessage, setTitle, setSitePath } = useStatusContext();
|
const { setMessage, setTitle, setSitePath } = useStatusContext();
|
||||||
const {
|
const {
|
||||||
@@ -107,6 +130,13 @@ const MainForm = () => {
|
|||||||
groups,
|
groups,
|
||||||
providers,
|
providers,
|
||||||
} = useUserContext();
|
} = useUserContext();
|
||||||
|
const {
|
||||||
|
remove,
|
||||||
|
archive,
|
||||||
|
updateManifestIds,
|
||||||
|
setUpdateManifestIds,
|
||||||
|
setMakeActive,
|
||||||
|
} = useUpdateManifest(token);
|
||||||
|
|
||||||
const sortHandler = (event, property) => {
|
const sortHandler = (event, property) => {
|
||||||
if (property === orderBy) {
|
if (property === orderBy) {
|
||||||
@@ -131,24 +161,84 @@ const MainForm = () => {
|
|||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
handleActiveChange(null, active);
|
handleActiveChange(null, active);
|
||||||
await getManifests(
|
switch (active) {
|
||||||
{
|
case "all":
|
||||||
limit: pageSize,
|
await getManifests(
|
||||||
offset: pageSize * pageIndex,
|
{
|
||||||
order: `${orderBy} ${order}`,
|
limit: pageSize,
|
||||||
manifest_type: TYPE_MANIFEST_SOFTWARE,
|
offset: pageSize * pageIndex,
|
||||||
search,
|
order: `${orderBy} ${order}`,
|
||||||
active,
|
search,
|
||||||
},
|
},
|
||||||
token
|
token
|
||||||
);
|
);
|
||||||
|
break;
|
||||||
|
case "aftersales":
|
||||||
|
await getManifests(
|
||||||
|
{
|
||||||
|
limit: pageSize,
|
||||||
|
offset: pageSize * pageIndex,
|
||||||
|
order: `${orderBy} ${order}`,
|
||||||
|
manifest_type: TYPE_MANIFEST_AFTERSALES,
|
||||||
|
search,
|
||||||
|
active: "true",
|
||||||
|
},
|
||||||
|
token
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "config":
|
||||||
|
await getManifests(
|
||||||
|
{
|
||||||
|
limit: pageSize,
|
||||||
|
offset: pageSize * pageIndex,
|
||||||
|
order: `${orderBy} ${order}`,
|
||||||
|
manifest_type: TYPE_MANIFEST_CONFIG,
|
||||||
|
search,
|
||||||
|
active: "true",
|
||||||
|
},
|
||||||
|
token
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "software":
|
||||||
|
await getManifests(
|
||||||
|
{
|
||||||
|
limit: pageSize,
|
||||||
|
offset: pageSize * pageIndex,
|
||||||
|
order: `${orderBy} ${order}`,
|
||||||
|
manifest_type: TYPE_MANIFEST_SOFTWARE,
|
||||||
|
search,
|
||||||
|
active: "true",
|
||||||
|
},
|
||||||
|
token
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "archived":
|
||||||
|
await getManifests(
|
||||||
|
{
|
||||||
|
limit: pageSize,
|
||||||
|
offset: pageSize * pageIndex,
|
||||||
|
order: `${orderBy} ${order}`,
|
||||||
|
manifest_type: TYPE_MANIFEST_SOFTWARE,
|
||||||
|
search,
|
||||||
|
active: "false",
|
||||||
|
},
|
||||||
|
token
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setMessage(e.message);
|
setMessage(e.message);
|
||||||
logger.warn(e.stack);
|
logger.warn(e.stack);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [pageIndex, pageSize, token, orderBy, order, search, active]);
|
}, [pageIndex, pageSize, token, orderBy, order, search, active, updateManifestIds]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setUpdateManifestIds([]);
|
||||||
|
}, [active, setUpdateManifestIds]);
|
||||||
|
|
||||||
const handleChangePageIndex = (_event, newIndex) => {
|
const handleChangePageIndex = (_event, newIndex) => {
|
||||||
setPageIndex(newIndex);
|
setPageIndex(newIndex);
|
||||||
@@ -166,19 +256,58 @@ const MainForm = () => {
|
|||||||
|
|
||||||
const handleActiveChange = (event, newAlignment) => {
|
const handleActiveChange = (event, newAlignment) => {
|
||||||
if (newAlignment !== null) {
|
if (newAlignment !== null) {
|
||||||
setActive(newAlignment)
|
setActive(newAlignment);
|
||||||
|
setMakeActive(newAlignment === 'archived');
|
||||||
|
setArchiveLabel(() => {
|
||||||
|
if (newAlignment === "archived") {
|
||||||
|
return "Activate";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Archive";
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const setDeletePopup = (id, row) => {
|
const handleSelectAll = () => {
|
||||||
setDeleteId(id);
|
setUpdateManifestIds((selected) => selected.length ? [] : manifests.map((manifest) => manifest.id));
|
||||||
setDeleteRowName(`${row.name} ${row.version}`);
|
};
|
||||||
|
|
||||||
|
const handleSelect = (event, manifest) => {
|
||||||
|
setUpdateManifestIds((selected) => {
|
||||||
|
if (event.target.checked && selected.find((id) => id === manifest.id)) {
|
||||||
|
return selected;
|
||||||
|
} else if (event.target.checked) {
|
||||||
|
return [...selected, manifest.id];
|
||||||
|
}
|
||||||
|
return selected.filter(({ id }) => id !== manifest.id);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const setDeletePopup = (row) => {
|
||||||
|
handleSelect({ target: { checked: true } }, row);
|
||||||
setShowDeleteModal(true);
|
setShowDeleteModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDelete = async (manifest_id) => {
|
const onArchive = async () => {
|
||||||
try {
|
try {
|
||||||
await deleteManifest(parseInt(manifest_id), token);
|
await archive()
|
||||||
|
.then(({ message }) => {
|
||||||
|
setUpdateManifestIds([]);
|
||||||
|
setMessage(message);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
logger.warn(e.stack);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDelete = async () => {
|
||||||
|
try {
|
||||||
|
await remove()
|
||||||
|
.then(({ summary }) => {
|
||||||
|
setUpdateManifestIds([]);
|
||||||
|
setMessage(summary);
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setMessage(e.message);
|
setMessage(e.message);
|
||||||
logger.warn(e.stack);
|
logger.warn(e.stack);
|
||||||
@@ -203,7 +332,7 @@ const MainForm = () => {
|
|||||||
icon: <EditIcon aria-label={`Update ${row.name} ${row.version}`} />,
|
icon: <EditIcon aria-label={`Update ${row.name} ${row.version}`} />,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (hasRole(groups, Permissions.FiskerMagnaCreate, providers)) {
|
if (hasRole(groups, Permissions.FiskerUpdateDeploy, providers)) {
|
||||||
actions.push({
|
actions.push({
|
||||||
tip: `Deploy "${row.name} ${row.version}"`,
|
tip: `Deploy "${row.name} ${row.version}"`,
|
||||||
link: `/package-deploy/${row.id}`,
|
link: `/package-deploy/${row.id}`,
|
||||||
@@ -232,7 +361,7 @@ const MainForm = () => {
|
|||||||
return (
|
return (
|
||||||
<span key={`delete-${action.id}-of-key`}>
|
<span key={`delete-${action.id}-of-key`}>
|
||||||
<Tooltip key={`delete-${action.id}`} title={action.tip}>
|
<Tooltip key={`delete-${action.id}`} title={action.tip}>
|
||||||
<Link to="#" onClick={() => setDeletePopup(action.id, row)}>
|
<Link to="#" onClick={() => setDeletePopup(row)}>
|
||||||
{action.icon}
|
{action.icon}
|
||||||
</Link>
|
</Link>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -259,12 +388,23 @@ const MainForm = () => {
|
|||||||
aria-label="Active"
|
aria-label="Active"
|
||||||
onChange={handleActiveChange}
|
onChange={handleActiveChange}
|
||||||
>
|
>
|
||||||
<ToggleButton value={"true"}>Active</ToggleButton>
|
<ToggleButton value={"software"}>Software</ToggleButton>
|
||||||
<ToggleButton value={"false"}>Archived</ToggleButton>
|
<ToggleButton value={"archived"}>Archived</ToggleButton>
|
||||||
|
<ToggleButton value={"all"}>All</ToggleButton>
|
||||||
</ToggleButtonGroup>
|
</ToggleButtonGroup>
|
||||||
</RoleWrap>
|
</RoleWrap>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item md={4} className={classes.textRightAlign}></Grid>
|
<Grid item md={4} className={classes.textRightAlign}>
|
||||||
|
<DropDownButton
|
||||||
|
actions={[
|
||||||
|
{
|
||||||
|
name: archiveLabel,
|
||||||
|
trigger: () => setShowArchiveModal(true),
|
||||||
|
disabled: !updateManifestIds.length || active === "all",
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeaderSortable
|
<TableHeaderSortable
|
||||||
@@ -273,44 +413,62 @@ const MainForm = () => {
|
|||||||
order={order}
|
order={order}
|
||||||
columnData={tableColumns}
|
columnData={tableColumns}
|
||||||
onSortRequest={sortHandler}
|
onSortRequest={sortHandler}
|
||||||
|
multiSelect
|
||||||
|
onSelectAll={handleSelectAll}
|
||||||
|
selectCount={updateManifestIds ? updateManifestIds.length : 0}
|
||||||
|
rowCount={manifests ? manifests.length : 0}
|
||||||
/>
|
/>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{manifests.map((row) => (
|
{manifests.map((row) => {
|
||||||
<TableRow key={row.id}>
|
const isSelected = updateManifestIds
|
||||||
<TableCell align="center">{row.id}</TableCell>
|
? !!updateManifestIds.find((id) => id === row.id)
|
||||||
<TableCell align="center">
|
: false;
|
||||||
{row.name}
|
return (
|
||||||
{row.ecu_list && (
|
<TableRow key={row.id}>
|
||||||
<>
|
<TableCell padding="checkbox">
|
||||||
<br />
|
<Checkbox
|
||||||
<ECUList
|
checked={isSelected}
|
||||||
list={row.ecu_list}
|
onChange={(event) => handleSelect(event, row)}
|
||||||
search={search}
|
/>
|
||||||
searchedOnly={true}
|
</TableCell>
|
||||||
/>
|
<TableCell align="center">{row.id}</TableCell>
|
||||||
</>
|
<TableCell align="center">
|
||||||
)}
|
{row.name}
|
||||||
</TableCell>
|
{row.ecu_list && (
|
||||||
<TableCell align="center">{row.version}</TableCell>
|
<>
|
||||||
<TableCell align="center">{row.sums}</TableCell>
|
<br />
|
||||||
<TableCell align="center">
|
<ECUList
|
||||||
{formatManifestType(row.type)}
|
list={row.ecu_list}
|
||||||
</TableCell>
|
search={search}
|
||||||
<TableCell align="center">
|
searchedOnly={true}
|
||||||
{LocalDateTimeString(row.created)}
|
/>
|
||||||
</TableCell>
|
</>
|
||||||
<TableCell align="center">
|
)}
|
||||||
{LocalDateTimeString(row.updated)}
|
</TableCell>
|
||||||
</TableCell>
|
<TableCell align="center">{row.version}</TableCell>
|
||||||
<TableCell align="center">{Actions(row)}</TableCell>
|
<TableCell align="center">
|
||||||
</TableRow>
|
{formatManifestType(row.manifest_type)}
|
||||||
))}
|
</TableCell>
|
||||||
|
<TableCell align="center">{row.sums}</TableCell>
|
||||||
|
<TableCell align="center">
|
||||||
|
{formatType(row.type)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center">
|
||||||
|
{LocalDateTimeString(row.created)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center">
|
||||||
|
{LocalDateTimeString(row.updated)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center">{Actions(row)}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
<TableFooter>
|
<TableFooter>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TablePagination
|
<TablePagination
|
||||||
rowsPerPageOptions={[5, 10, 25, 100]}
|
rowsPerPageOptions={[5, 10, 25, 100]}
|
||||||
colSpan={8}
|
colSpan={tableColumns.length + 1}
|
||||||
count={totalManifests}
|
count={totalManifests}
|
||||||
rowsPerPage={pageSize}
|
rowsPerPage={pageSize}
|
||||||
page={pageIndex}
|
page={pageIndex}
|
||||||
@@ -325,10 +483,17 @@ const MainForm = () => {
|
|||||||
</TableFooter>
|
</TableFooter>
|
||||||
</Table>
|
</Table>
|
||||||
<DeleteConfirmation
|
<DeleteConfirmation
|
||||||
message={deleteRowName}
|
message={`${updateManifestIds.length} records.`}
|
||||||
open={showDeleteModal}
|
open={showDeleteModal}
|
||||||
close={() => setShowDeleteModal(false)}
|
close={() => setShowDeleteModal(false)}
|
||||||
deleteFunction={() => onDelete(deleteId)}
|
deleteFunction={() => onDelete()}
|
||||||
|
/>
|
||||||
|
<GeneralConfirmation
|
||||||
|
title={`${archiveLabel} Resource?`}
|
||||||
|
message={`${archiveLabel} ${updateManifestIds.length} records.`}
|
||||||
|
open={showArchiveModal}
|
||||||
|
close={() => setShowArchiveModal(false)}
|
||||||
|
actionFunction={() => onArchive()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
35
src/components/Manifest/List/index.test.jsx
Normal file
35
src/components/Manifest/List/index.test.jsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
jest.mock("../../Contexts/ManifestsContext");
|
||||||
|
jest.mock("../../Contexts/UserContext");
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { render, screen, fireEvent } from "@testing-library/react";
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
|
||||||
|
import { UserProvider, setToken } from "../../Contexts/UserContext";
|
||||||
|
import { StatusProvider } from "../../Contexts/StatusContext";
|
||||||
|
import ManifestList from ".";
|
||||||
|
import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing";
|
||||||
|
|
||||||
|
const Page = (
|
||||||
|
<UserProvider>
|
||||||
|
<BrowserRouter>
|
||||||
|
<StatusProvider>
|
||||||
|
<ManifestList />
|
||||||
|
</StatusProvider>
|
||||||
|
</BrowserRouter>
|
||||||
|
</UserProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
describe("Manifest List Component", () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
setToken(TEST_AUTH_OBJECT_FISKER);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adjusts the active state on switch to archived tab", async () => {
|
||||||
|
render(Page);
|
||||||
|
|
||||||
|
const archiveActionEl = screen.getByText("Archive");
|
||||||
|
fireEvent.click(screen.getByText("Archived"));
|
||||||
|
expect(archiveActionEl.innerHTML).toBe("Activate");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -28,7 +28,7 @@ const TransformModal = ({
|
|||||||
|
|
||||||
const handleChange = (key, value) => {
|
const handleChange = (key, value) => {
|
||||||
setData((data) => {
|
setData((data) => {
|
||||||
const {[key]: toChange, ...rest} = data;
|
const { [key]: toChange, ...rest } = data;
|
||||||
switch (data[key].type) {
|
switch (data[key].type) {
|
||||||
case "boolean":
|
case "boolean":
|
||||||
toChange.value = !toChange.value;
|
toChange.value = !toChange.value;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import useStyles from "../useStyles";
|
|||||||
import GrayMarkerIcon from "../../assets/gray-marker.png";
|
import GrayMarkerIcon from "../../assets/gray-marker.png";
|
||||||
import GreenMarkerIcon from "../../assets/green-marker.png";
|
import GreenMarkerIcon from "../../assets/green-marker.png";
|
||||||
import { logger } from "../../services/monitoring";
|
import { logger } from "../../services/monitoring";
|
||||||
import { ValidateLocationVehiclePathsData } from "../../utils/locations";
|
import { ValidateLocationData, ValidateLocationVehiclePathsData } from "../../utils/locations";
|
||||||
import { useUserContext } from "../Contexts/UserContext";
|
import { useUserContext } from "../Contexts/UserContext";
|
||||||
import { useVehicleContext, VehicleProvider } from "../Contexts/VehicleContext";
|
import { useVehicleContext, VehicleProvider } from "../Contexts/VehicleContext";
|
||||||
import { VehiclePopUp } from "../VehicleMap/popup";
|
import { VehiclePopUp } from "../VehicleMap/popup";
|
||||||
@@ -54,23 +54,32 @@ const ComponentVehiclePathsMap = (props) => {
|
|||||||
vinsParam += props.lookbackHours
|
vinsParam += props.lookbackHours
|
||||||
|
|
||||||
return getLocationsVehiclePaths(accessToken, vinsParam)
|
return getLocationsVehiclePaths(accessToken, vinsParam)
|
||||||
.then((result) => {
|
.then(async (result) => {
|
||||||
let resultArray = Object.entries(result)
|
let resultArray = Object.entries(result)
|
||||||
const points = []
|
const points = []
|
||||||
|
|
||||||
// validate each location
|
// validate each location
|
||||||
for (let vinLocations of resultArray) {
|
for (let vinLocations of resultArray) {
|
||||||
// if there are points for the vin; skip if empty points array
|
if (vinLocations[0]) {
|
||||||
if (vinLocations[0] && vinLocations[1] && vinLocations[1][0]) {
|
let path = [];
|
||||||
let path = []
|
path[0] = vinLocations[0];
|
||||||
path[0] = vinLocations[0]
|
path[1] = [];
|
||||||
path[1] = []
|
if (vinLocations[1] && vinLocations[1][0]) {
|
||||||
for (let location of vinLocations[1]) {
|
for (let location of vinLocations[1]) {
|
||||||
if (ValidateLocationVehiclePathsData(location) !== false) {
|
if (ValidateLocationVehiclePathsData(location) !== false) {
|
||||||
path[1].push(location);
|
path[1].push(location);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
await getState(token, vinLocations[0]).then((stateResult) => {
|
||||||
|
if (stateResult.data && stateResult.data.location) {
|
||||||
|
if (ValidateLocationData(stateResult.data.location) !== false) {
|
||||||
|
path[1].push([stateResult.data.location.latitude, stateResult.data.location.longitude]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
points.push(path)
|
points.push(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1
src/hooks/index.js
Normal file
1
src/hooks/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { useUpdateManifest } from "./useUpdateManifest";
|
||||||
47
src/hooks/useUpdateManifest.js
Normal file
47
src/hooks/useUpdateManifest.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import manifestsAPI from "../services/manifestsAPI";
|
||||||
|
import TaskRunner from "../utils/taskRunner";
|
||||||
|
|
||||||
|
export const useUpdateManifest = (token) => {
|
||||||
|
const [updateManifestIds, setUpdateManifestIds] = useState([]);
|
||||||
|
const [makeActive, setMakeActive] = useState(false);
|
||||||
|
|
||||||
|
const remove = async () => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const taskRunner = new TaskRunner(5, updateManifestIds.length);
|
||||||
|
let errorCount = 0;
|
||||||
|
|
||||||
|
const task = (id) => {
|
||||||
|
return async () => manifestsAPI.deleteManifest(id, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateManifestIds.forEach((id) => taskRunner.push(task(id))
|
||||||
|
.then((response) => {
|
||||||
|
if (response.error) {
|
||||||
|
errorCount += 1;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
taskRunner.onComplete().then((responses) => resolve({
|
||||||
|
summary: `${updateManifestIds.length - errorCount} out of ${updateManifestIds.length} manifests were deleted.`,
|
||||||
|
responses,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const archive = async () => {
|
||||||
|
return manifestsAPI.archiveManifest({
|
||||||
|
ids: updateManifestIds,
|
||||||
|
active: makeActive,
|
||||||
|
}, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
updateManifestIds,
|
||||||
|
setUpdateManifestIds,
|
||||||
|
makeActive,
|
||||||
|
setMakeActive,
|
||||||
|
archive,
|
||||||
|
remove,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -5,6 +5,10 @@ const manifestsAPI = {
|
|||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
archiveManifest: async (data, token) => {
|
||||||
|
return { message: "Archived 1 update manifests" };
|
||||||
|
},
|
||||||
|
|
||||||
deleteManifest: async (manifest_id, token) => {
|
deleteManifest: async (manifest_id, token) => {
|
||||||
return { message: "OK" };
|
return { message: "OK" };
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ const vehiclesAPI = {
|
|||||||
|
|
||||||
vins.forEach((vin) => {
|
vins.forEach((vin) => {
|
||||||
result[vin] = true;
|
result[vin] = true;
|
||||||
|
result["2:" + vin] = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -133,6 +134,7 @@ const vehiclesAPI = {
|
|||||||
return {
|
return {
|
||||||
'3FAFP13P31R199430': [[16.891136999999986, 26.832352999999955], [56.891136999999986, 66.832352999999955], [26.891136999999986, 36.832352999999955]],
|
'3FAFP13P31R199430': [[16.891136999999986, 26.832352999999955], [56.891136999999986, 66.832352999999955], [26.891136999999986, 36.832352999999955]],
|
||||||
'3FAFP13P71R199060': [[36.891136999999986, 46.832352999999955], [76.891136999999986, 16.832352999999955]],
|
'3FAFP13P71R199060': [[36.891136999999986, 46.832352999999955], [76.891136999999986, 16.832352999999955]],
|
||||||
|
'3FAFP13P61R199390': [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getVehicle: async (vin) => {
|
getVehicle: async (vin) => {
|
||||||
|
|||||||
@@ -1,10 +1,22 @@
|
|||||||
import {
|
import {
|
||||||
addQueryParams, errorHandler, fetchRespHandler, getAuthHeaderOptions
|
addQueryParams, errorHandler, fetchRespHandler, getAuthHeaderOptions
|
||||||
} from "../utils/http";
|
} from "../utils/http";
|
||||||
|
|
||||||
const API_ENDPOINT = process.env.REACT_APP_OTA_SERVICE_URL;
|
const API_ENDPOINT = process.env.REACT_APP_OTA_SERVICE_URL;
|
||||||
|
|
||||||
const manifestsAPI = {
|
const manifestsAPI = {
|
||||||
|
archiveManifest: async (data, token) =>
|
||||||
|
fetch(`${API_ENDPOINT}/vehicles/archive`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: Object.assign(
|
||||||
|
{ "Content-Type": "application/json" },
|
||||||
|
getAuthHeaderOptions(token)
|
||||||
|
),
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
})
|
||||||
|
.then(fetchRespHandler)
|
||||||
|
.catch(errorHandler),
|
||||||
|
|
||||||
deleteManifest: async (manifest_id, token) =>
|
deleteManifest: async (manifest_id, token) =>
|
||||||
fetch(`${API_ENDPOINT}/manifest?id=${manifest_id}`, {
|
fetch(`${API_ENDPOINT}/manifest?id=${manifest_id}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
@@ -79,8 +91,8 @@ const manifestsAPI = {
|
|||||||
.then(fetchRespHandler)
|
.then(fetchRespHandler)
|
||||||
.catch(errorHandler),
|
.catch(errorHandler),
|
||||||
|
|
||||||
migrateManifest: async (manifest_id, token) =>
|
migrateManifest: async (manifest_id, token) =>
|
||||||
fetch(`${API_ENDPOINT}/manifestmigrate/${manifest_id}`,{
|
fetch(`${API_ENDPOINT}/manifestmigrate/${manifest_id}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: Object.assign(
|
headers: Object.assign(
|
||||||
{ "Content-Type": "application/json" },
|
{ "Content-Type": "application/json" },
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const vehiclesAPI = {
|
|||||||
|
|
||||||
addTags: async (vins, tags, token) =>
|
addTags: async (vins, tags, token) =>
|
||||||
fetch(`${API_ENDPOINT}/tags`, {
|
fetch(`${API_ENDPOINT}/tags`, {
|
||||||
method: "PUT",
|
method: "POST",
|
||||||
headers: Object.assign(
|
headers: Object.assign(
|
||||||
{ "Content-Type": "application/json" },
|
{ "Content-Type": "application/json" },
|
||||||
getAuthHeaderOptions(token),
|
getAuthHeaderOptions(token),
|
||||||
@@ -173,6 +173,21 @@ const vehiclesAPI = {
|
|||||||
.then(fetchRespHandler)
|
.then(fetchRespHandler)
|
||||||
.catch(errorHandler),
|
.catch(errorHandler),
|
||||||
|
|
||||||
|
sendDiagnosticCommand: async (vins, command, token) =>
|
||||||
|
fetch(`${API_ENDPOINT}/vehiclediagnosticcommand`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: Object.assign(
|
||||||
|
{ "Content-Type": "application/json" },
|
||||||
|
getAuthHeaderOptions(token)
|
||||||
|
),
|
||||||
|
body: JSON.stringify({
|
||||||
|
vins,
|
||||||
|
...command,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then(fetchRespHandler)
|
||||||
|
.catch(errorHandler),
|
||||||
|
|
||||||
updateVehicle: async (vin, vehicle, token) =>
|
updateVehicle: async (vin, vehicle, token) =>
|
||||||
fetch(`${API_ENDPOINT}/vehicle/${vin}`, {
|
fetch(`${API_ENDPOINT}/vehicle/${vin}`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
export const TYPE_MANIFEST_SOFTWARE = 1;
|
export const TYPE_MANIFEST_SOFTWARE = 1;
|
||||||
export const TYPE_MANIFEST_CONFIG = 2;
|
export const TYPE_MANIFEST_CONFIG = 2;
|
||||||
|
export const TYPE_MANIFEST_MAGNA = 3;
|
||||||
|
export const TYPE_MANIFEST_AFTERSALES = 4;
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ export const Roles = {
|
|||||||
DELETE: process.env.REACT_APP_ROLE_DELETE,
|
DELETE: process.env.REACT_APP_ROLE_DELETE,
|
||||||
CERTIFICATES: process.env.REACT_APP_ROLE_GENERATE_CERTIFICATE,
|
CERTIFICATES: process.env.REACT_APP_ROLE_GENERATE_CERTIFICATE,
|
||||||
APPROVESUPPLIERS: process.env.REACT_APP_ROLE_SUPPLIER_APPROVER,
|
APPROVESUPPLIERS: process.env.REACT_APP_ROLE_SUPPLIER_APPROVER,
|
||||||
|
UPDATEDEPLOY: process.env.REACT_APP_ROLE_UPDATE_DEPLOY,
|
||||||
MANUFACTURE: process.env.REACT_APP_ROLE_MANUFACTURE,
|
MANUFACTURE: process.env.REACT_APP_ROLE_MANUFACTURE,
|
||||||
MAGNAGROUP: process.env.REACT_APP_MAGNA_GROUP_ID,
|
MAGNAGROUP: process.env.REACT_APP_MAGNA_GROUP_ID,
|
||||||
MANIFEST_MIGRATION: process.env.REACT_APP_ROLE_MANIFEST_MIGRATION
|
MANIFEST_MIGRATION: process.env.REACT_APP_ROLE_MANIFEST_MIGRATION,
|
||||||
|
CAR_DIAGNOSTIC: process.env.REACT_APP_ROLE_CAR_DIAGNOSTIC
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Providers = {
|
export const Providers = {
|
||||||
@@ -81,6 +83,9 @@ export const Permissions = {
|
|||||||
[Providers.FISKER_QA]: [Roles.MANUFACTURE],
|
[Providers.FISKER_QA]: [Roles.MANUFACTURE],
|
||||||
[Providers.MAGNA]: [Roles.MAGNAGROUP],
|
[Providers.MAGNA]: [Roles.MAGNAGROUP],
|
||||||
},
|
},
|
||||||
|
FiskerUpdateDeploy: {
|
||||||
|
[Providers.FISKER]: [Roles.UPDATEDEPLOY],
|
||||||
|
},
|
||||||
Magna: {
|
Magna: {
|
||||||
[Providers.FISKER_QA]: [Roles.MANUFACTURE],
|
[Providers.FISKER_QA]: [Roles.MANUFACTURE],
|
||||||
[Providers.MAGNA]: [Roles.MAGNAGROUP],
|
[Providers.MAGNA]: [Roles.MAGNAGROUP],
|
||||||
@@ -97,5 +102,9 @@ export const Permissions = {
|
|||||||
},
|
},
|
||||||
ManifestMigration: {
|
ManifestMigration: {
|
||||||
[Providers.FISKER]: [Roles.MANIFEST_MIGRATION]
|
[Providers.FISKER]: [Roles.MANIFEST_MIGRATION]
|
||||||
|
},
|
||||||
|
CarDiagnostic: {
|
||||||
|
[Providers.FISKER]: [Roles.CAR_DIAGNOSTIC],
|
||||||
|
[Providers.FISKER_QA]: [Roles.CAR_DIAGNOSTIC],
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -68,6 +68,15 @@ describe("Roles Helper", () => {
|
|||||||
).toEqual(true);
|
).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Check FiskerUpdateDeploy permission", () => {
|
||||||
|
expect(
|
||||||
|
hasRole([Roles.UPDATEDEPLOY], Permissions.FiskerUpdateDeploy, [Providers.FISKER])
|
||||||
|
).toEqual(true);
|
||||||
|
expect(
|
||||||
|
hasRole([Roles.UPDATEDEPLOY], Permissions.FiskerUpdateDeploy, [Providers.MAGNA])
|
||||||
|
).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
it("Check Magna permission", () => {
|
it("Check Magna permission", () => {
|
||||||
expect(
|
expect(
|
||||||
hasRole([Roles.MAGNAGROUP], Permissions.Magna, [Providers.MAGNA])
|
hasRole([Roles.MAGNAGROUP], Permissions.Magna, [Providers.MAGNA])
|
||||||
|
|||||||
@@ -1,36 +1,67 @@
|
|||||||
export default class TaskRunner {
|
export default class TaskRunner {
|
||||||
constructor(concurrencyLimit = 1) {
|
constructor(concurrencyLimit = 1, total) {
|
||||||
this.queue = [];
|
this._queue = [];
|
||||||
this.running = 0;
|
this._index = 0;
|
||||||
this.concurrencyLimit = concurrencyLimit;
|
this._running = 0;
|
||||||
|
this._complete = 0;
|
||||||
|
this._concurrencyLimit = concurrencyLimit;
|
||||||
|
|
||||||
|
if (total) {
|
||||||
|
this._total = total;
|
||||||
|
this._responses = new Array(total);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._onComplete = new Promise((resolve, reject) => {
|
||||||
|
this._onCompleteResolve = resolve;
|
||||||
|
this._onCompleteReject = reject;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
execute() {
|
execute() {
|
||||||
if (this.running >= this.concurrencyLimit || this.queue.length === 0) {
|
if (this._running >= this._concurrencyLimit || this._queue.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const task = this.queue.shift();
|
const task = this._queue.shift();
|
||||||
this.running += 1;
|
this._running += 1;
|
||||||
task();
|
task(this._index);
|
||||||
|
this._index += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
async push(fn) {
|
async push(fn) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const task = async () => {
|
const task = async (index) => {
|
||||||
try {
|
try {
|
||||||
const result = await fn();
|
const response = await fn();
|
||||||
resolve(result);
|
if (this._responses) {
|
||||||
|
this._responses[index] = response;
|
||||||
|
}
|
||||||
|
resolve(response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
reject(error);
|
reject(error);
|
||||||
} finally {
|
} finally {
|
||||||
this.running -= 1;
|
this._running -= 1;
|
||||||
|
this.#progress();
|
||||||
this.execute();
|
this.execute();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.queue.push(task);
|
this._queue.push(task);
|
||||||
this.execute();
|
this.execute();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#progress() {
|
||||||
|
this._complete += 1;
|
||||||
|
if (this._complete === this._total) {
|
||||||
|
this._onCompleteResolve(this._responses);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async onComplete() {
|
||||||
|
if (!this._total) {
|
||||||
|
this._onCompleteReject(new Error("Total is required to determine onComplete."));
|
||||||
|
}
|
||||||
|
return this._onComplete;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,10 @@ const mockPromise = async (id, ms) => {
|
|||||||
await new Promise(resolve => setTimeout(resolve, ms));
|
await new Promise(resolve => setTimeout(resolve, ms));
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
const mockPromiseError = async (id, ms) => {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
return new Error(`Task ${id} had an error`);
|
||||||
|
}
|
||||||
|
|
||||||
const asyncFn1 = () => mockPromise(1, 200);
|
const asyncFn1 = () => mockPromise(1, 200);
|
||||||
const asyncFn2 = () => mockPromise(2, 100);
|
const asyncFn2 = () => mockPromise(2, 100);
|
||||||
@@ -12,19 +16,19 @@ const asyncFn3 = () => mockPromise(3, 50);
|
|||||||
describe("TaskRunner", () => {
|
describe("TaskRunner", () => {
|
||||||
it("runs task added to queue, when space available", () => {
|
it("runs task added to queue, when space available", () => {
|
||||||
const taskRunner = new TaskRunner(2);
|
const taskRunner = new TaskRunner(2);
|
||||||
expect(taskRunner.running).toEqual(0);
|
expect(taskRunner._running).toEqual(0);
|
||||||
taskRunner.push(() => mockPromise(1, 300));
|
taskRunner.push(() => mockPromise(1, 300));
|
||||||
expect(taskRunner.running).toEqual(1);
|
expect(taskRunner._running).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps task in queue when at concurrency limit", () => {
|
it("keeps task in queue when at concurrency limit", () => {
|
||||||
const taskRunner = new TaskRunner(2);
|
const taskRunner = new TaskRunner(2);
|
||||||
expect(taskRunner.running).toEqual(0);
|
expect(taskRunner._running).toEqual(0);
|
||||||
taskRunner.push(() => mockPromise(1, 100));
|
taskRunner.push(() => mockPromise(1, 100));
|
||||||
taskRunner.push(() => mockPromise(2, 25));
|
taskRunner.push(() => mockPromise(2, 25));
|
||||||
taskRunner.push(() => mockPromise(3, 10));
|
taskRunner.push(() => mockPromise(3, 10));
|
||||||
expect(taskRunner.running).toEqual(2);
|
expect(taskRunner._running).toEqual(2);
|
||||||
expect(taskRunner.queue.length).toEqual(1);
|
expect(taskRunner._queue.length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("runs queued tasks as space becomes available", async () => {
|
it("runs queued tasks as space becomes available", async () => {
|
||||||
@@ -32,9 +36,9 @@ describe("TaskRunner", () => {
|
|||||||
taskRunner.push(() => mockPromise(1, 600));
|
taskRunner.push(() => mockPromise(1, 600));
|
||||||
taskRunner.push(() => mockPromise(2, 300));
|
taskRunner.push(() => mockPromise(2, 300));
|
||||||
taskRunner.push(() => mockPromise(3, 100));
|
taskRunner.push(() => mockPromise(3, 100));
|
||||||
expect(taskRunner.queue.length).toEqual(1);
|
expect(taskRunner._queue.length).toEqual(1);
|
||||||
await new Promise(r => setTimeout(r, 301));
|
await new Promise(r => setTimeout(r, 301));
|
||||||
expect(taskRunner.queue.length).toEqual(0);
|
expect(taskRunner._queue.length).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("runs tasks in order", async () => {
|
it("runs tasks in order", async () => {
|
||||||
@@ -52,7 +56,44 @@ describe("TaskRunner", () => {
|
|||||||
.then((id) => {
|
.then((id) => {
|
||||||
actual.push(id);
|
actual.push(id);
|
||||||
});
|
});
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
expect(actual).toEqual([2, 3, 1]);
|
expect(actual).toEqual([2, 3, 1]);
|
||||||
});
|
});
|
||||||
})
|
|
||||||
|
it("resolves a promise when all tasks are complete", async () => {
|
||||||
|
const taskRunner = new TaskRunner(2, 5);
|
||||||
|
taskRunner.push(() => mockPromise(1, 600));
|
||||||
|
taskRunner.push(() => mockPromise(2, 300));
|
||||||
|
taskRunner.push(() => mockPromise(3, 200));
|
||||||
|
taskRunner.push(() => mockPromise(4, 600));
|
||||||
|
taskRunner.push(() => mockPromise(5, 100));
|
||||||
|
await taskRunner.onComplete().then((actual) => {
|
||||||
|
expect(actual).toStrictEqual([1, 2, 3, 4, 5]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resolves a promise when all tasks are complete, even if some fail", async () => {
|
||||||
|
const error = new Error(`Task 3 had an error`);
|
||||||
|
const taskRunner = new TaskRunner(2, 5);
|
||||||
|
taskRunner.push(() => mockPromise(1, 600));
|
||||||
|
taskRunner.push(() => mockPromise(2, 300));
|
||||||
|
taskRunner.push(() => mockPromiseError(3, 200));
|
||||||
|
taskRunner.push(() => mockPromise(4, 600));
|
||||||
|
taskRunner.push(() => mockPromise(5, 100));
|
||||||
|
await taskRunner.onComplete().then((actual) => {
|
||||||
|
expect(actual).toStrictEqual([1, 2, error, 4, 5]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects a promise when the total number of tasks is unknown", async () => {
|
||||||
|
const taskRunner = new TaskRunner(2);
|
||||||
|
taskRunner.push(() => mockPromise(1, 600));
|
||||||
|
taskRunner.push(() => mockPromise(2, 300));
|
||||||
|
taskRunner.push(() => mockPromise(3, 200));
|
||||||
|
taskRunner.push(() => mockPromise(4, 600));
|
||||||
|
taskRunner.push(() => mockPromise(5, 100));
|
||||||
|
await taskRunner.onComplete().catch((error) => {
|
||||||
|
expect(error.message).toBe("Total is required to determine onComplete.");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user