Merge CEC-394 Car update log (#82)
This commit is contained in:
153
package-lock.json
generated
153
package-lock.json
generated
@@ -1275,9 +1275,9 @@
|
|||||||
"integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg=="
|
"integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg=="
|
||||||
},
|
},
|
||||||
"@datadog/browser-core": {
|
"@datadog/browser-core": {
|
||||||
"version": "2.18.0",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@datadog/browser-core/-/browser-core-2.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@datadog/browser-core/-/browser-core-3.1.2.tgz",
|
||||||
"integrity": "sha512-1RvxLK8TiuAaDrwkrlOg7wM+7FilJtNbC30h5BxoGChWEBB7QsgeYGnliQ60byZUCzhbvARVzHHNZTxUiP+fPQ==",
|
"integrity": "sha512-TVw6AEM39UgBm9wDAQJ/t1BurxfRvZY1TaKZiGOm2OQIFjQgdlbB1BXVZru/OcR3/kGA1JMQ6Nhh2cERDLpNHw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"tslib": "^1.10.0"
|
"tslib": "^1.10.0"
|
||||||
},
|
},
|
||||||
@@ -1290,11 +1290,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@datadog/browser-logs": {
|
"@datadog/browser-logs": {
|
||||||
"version": "2.18.0",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@datadog/browser-logs/-/browser-logs-2.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@datadog/browser-logs/-/browser-logs-3.1.2.tgz",
|
||||||
"integrity": "sha512-bDT5YkPNHGZmjADXtsVtwSyL+/J7MA4k2mBcHzutXK7/tKrIRiKH6ygHiBRryNHfD7/Q79tZqJzi32kSZy3AAA==",
|
"integrity": "sha512-XBtqH1RoLky4E7Gpr2eDVod4Zji1/BIOh+1T6zO+quapFWin9VRtiwpo9DjWFjSdGENbjGpz0HJfQIS5b+sLTA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@datadog/browser-core": "2.18.0",
|
"@datadog/browser-core": "3.1.2",
|
||||||
"tslib": "^1.10.0"
|
"tslib": "^1.10.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -1305,55 +1305,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@datadog/browser-rum": {
|
|
||||||
"version": "2.18.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@datadog/browser-rum/-/browser-rum-2.18.0.tgz",
|
|
||||||
"integrity": "sha512-cfji5LdYJl1EpDhDZS67cpvgF6mC+qHSKRDxHQmqYurZW8R97wRhvrfpQWpDrKnl46MCvdQ76qDpSk9AVqK1zg==",
|
|
||||||
"requires": {
|
|
||||||
"@datadog/browser-core": "2.18.0",
|
|
||||||
"@datadog/browser-rum-core": "2.18.0",
|
|
||||||
"tslib": "^1.10.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@datadog/browser-core": {
|
|
||||||
"version": "2.18.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@datadog/browser-core/-/browser-core-2.18.0.tgz",
|
|
||||||
"integrity": "sha512-1RvxLK8TiuAaDrwkrlOg7wM+7FilJtNbC30h5BxoGChWEBB7QsgeYGnliQ60byZUCzhbvARVzHHNZTxUiP+fPQ==",
|
|
||||||
"requires": {
|
|
||||||
"tslib": "^1.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tslib": {
|
|
||||||
"version": "1.14.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
|
||||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@datadog/browser-rum-core": {
|
|
||||||
"version": "2.18.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@datadog/browser-rum-core/-/browser-rum-core-2.18.0.tgz",
|
|
||||||
"integrity": "sha512-HzdoQltOdIkEEDiZj7r0kHDa/bw6WIBfKPQIv680arRXwhIrvaguwWUiJFTEXI865NA5ouSwGMpPm8Xgrb3B5g==",
|
|
||||||
"requires": {
|
|
||||||
"@datadog/browser-core": "2.18.0",
|
|
||||||
"tslib": "^1.10.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@datadog/browser-core": {
|
|
||||||
"version": "2.18.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@datadog/browser-core/-/browser-core-2.18.0.tgz",
|
|
||||||
"integrity": "sha512-1RvxLK8TiuAaDrwkrlOg7wM+7FilJtNbC30h5BxoGChWEBB7QsgeYGnliQ60byZUCzhbvARVzHHNZTxUiP+fPQ==",
|
|
||||||
"requires": {
|
|
||||||
"tslib": "^1.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tslib": {
|
|
||||||
"version": "1.14.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
|
||||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@emotion/hash": {
|
"@emotion/hash": {
|
||||||
"version": "0.8.0",
|
"version": "0.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
|
||||||
@@ -2088,9 +2039,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@testing-library/dom": {
|
"@testing-library/dom": {
|
||||||
"version": "7.31.2",
|
"version": "8.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.31.2.tgz",
|
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.1.0.tgz",
|
||||||
"integrity": "sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ==",
|
"integrity": "sha512-kmW9alndr19qd6DABzQ978zKQ+J65gU2Rzkl8hriIetPnwpesRaK4//jEQyYh8fEALmGhomD/LBQqt+o+DL95Q==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/code-frame": "^7.10.4",
|
"@babel/code-frame": "^7.10.4",
|
||||||
"@babel/runtime": "^7.12.5",
|
"@babel/runtime": "^7.12.5",
|
||||||
@@ -2099,17 +2050,55 @@
|
|||||||
"chalk": "^4.1.0",
|
"chalk": "^4.1.0",
|
||||||
"dom-accessibility-api": "^0.5.6",
|
"dom-accessibility-api": "^0.5.6",
|
||||||
"lz-string": "^1.4.4",
|
"lz-string": "^1.4.4",
|
||||||
"pretty-format": "^26.6.2"
|
"pretty-format": "^27.0.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@jest/types": {
|
||||||
|
"version": "27.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz",
|
||||||
|
"integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==",
|
||||||
|
"requires": {
|
||||||
|
"@types/istanbul-lib-coverage": "^2.0.0",
|
||||||
|
"@types/istanbul-reports": "^3.0.0",
|
||||||
|
"@types/node": "*",
|
||||||
|
"@types/yargs": "^16.0.0",
|
||||||
|
"chalk": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/yargs": {
|
||||||
|
"version": "16.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz",
|
||||||
|
"integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==",
|
||||||
|
"requires": {
|
||||||
|
"@types/yargs-parser": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"chalk": {
|
"chalk": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||||
"integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
|
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-styles": "^4.1.0",
|
"ansi-styles": "^4.1.0",
|
||||||
"supports-color": "^7.1.0"
|
"supports-color": "^7.1.0"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"pretty-format": {
|
||||||
|
"version": "27.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.6.tgz",
|
||||||
|
"integrity": "sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ==",
|
||||||
|
"requires": {
|
||||||
|
"@jest/types": "^27.0.6",
|
||||||
|
"ansi-regex": "^5.0.0",
|
||||||
|
"ansi-styles": "^5.0.0",
|
||||||
|
"react-is": "^17.0.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2130,18 +2119,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@testing-library/react": {
|
"@testing-library/react": {
|
||||||
"version": "11.2.7",
|
"version": "12.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@testing-library/react/-/react-11.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.0.0.tgz",
|
||||||
"integrity": "sha512-tzRNp7pzd5QmbtXNG/mhdcl7Awfu/Iz1RaVHY75zTdOkmHCuzMhRL83gWHSgOAcjS3CCbyfwUHMZgRJb4kAfpA==",
|
"integrity": "sha512-sh3jhFgEshFyJ/0IxGltRhwZv2kFKfJ3fN1vTZ6hhMXzz9ZbbcTgmDYM4e+zJv+oiVKKEWZPyqPAh4MQBI65gA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.12.5",
|
"@babel/runtime": "^7.12.5",
|
||||||
"@testing-library/dom": "^7.28.1"
|
"@testing-library/dom": "^8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@testing-library/user-event": {
|
"@testing-library/user-event": {
|
||||||
"version": "12.8.3",
|
"version": "13.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.2.1.tgz",
|
||||||
"integrity": "sha512-IR0iWbFkgd56Bu5ZI/ej8yQwrkCv8Qydx6RzwbKz9faXazR/+5tvYKsZQgyXJiwgpcva127YO6JcWy7YlCfofQ==",
|
"integrity": "sha512-cczlgVl+krjOb3j1625usarNEibI0IFRJrSWX9UsJ1HKYFgCQv9Nb7QAipUDXl3Xdz8NDTsiS78eAkPSxlzTlw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.12.5"
|
"@babel/runtime": "^7.12.5"
|
||||||
}
|
}
|
||||||
@@ -10492,9 +10481,9 @@
|
|||||||
"integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A="
|
"integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A="
|
||||||
},
|
},
|
||||||
"path-parse": {
|
"path-parse": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
|
||||||
},
|
},
|
||||||
"path-to-regexp": {
|
"path-to-regexp": {
|
||||||
"version": "1.8.0",
|
"version": "1.8.0",
|
||||||
@@ -14189,9 +14178,9 @@
|
|||||||
"integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA=="
|
"integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA=="
|
||||||
},
|
},
|
||||||
"tar": {
|
"tar": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.10",
|
||||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.10.tgz",
|
||||||
"integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==",
|
"integrity": "sha512-kvvfiVvjGMxeUNB6MyYv5z7vhfFRwbwCXJAeL0/lnbrttBVqcMOnpHUf0X42LrPMR8mMpgapkJMchFH4FSHzNA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"chownr": "^2.0.0",
|
"chownr": "^2.0.0",
|
||||||
"fs-minipass": "^2.0.0",
|
"fs-minipass": "^2.0.0",
|
||||||
@@ -14825,9 +14814,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"url-parse": {
|
"url-parse": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.3.tgz",
|
||||||
"integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==",
|
"integrity": "sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"querystringify": "^2.1.1",
|
"querystringify": "^2.1.1",
|
||||||
"requires-port": "^1.0.0"
|
"requires-port": "^1.0.0"
|
||||||
@@ -15232,9 +15221,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"web-vitals": {
|
"web-vitals": {
|
||||||
"version": "0.2.4",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-0.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-2.1.0.tgz",
|
||||||
"integrity": "sha512-6BjspCO9VriYy12z356nL6JBS0GYeEcA457YyRzD+dD6XYCQ75NKhcOHUMHentOE7OcVCIXXDvOm0jKFfQG2Gg=="
|
"integrity": "sha512-npEyJP8jHf3J71t1tRTEtz9FeKp8H2udWJUUq5ykfPhhstr//TUxiYhIEzLNwk4zv2ybAilMn7v7N6Mxmuitmg=="
|
||||||
},
|
},
|
||||||
"webidl-conversions": {
|
"webidl-conversions": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
|
|||||||
@@ -3,13 +3,12 @@
|
|||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@datadog/browser-logs": "^2.18.0",
|
"@datadog/browser-logs": "^3.1.2",
|
||||||
"@datadog/browser-rum": "^2.17.0",
|
|
||||||
"@material-ui/core": "^4.12.3",
|
"@material-ui/core": "^4.12.3",
|
||||||
"@material-ui/icons": "^4.11.2",
|
"@material-ui/icons": "^4.11.2",
|
||||||
"@testing-library/jest-dom": "^5.14.1",
|
"@testing-library/jest-dom": "^5.14.1",
|
||||||
"@testing-library/react": "^11.2.7",
|
"@testing-library/react": "^12.0.0",
|
||||||
"@testing-library/user-event": "^12.8.3",
|
"@testing-library/user-event": "^13.2.1",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"clsx": "^1.1.1",
|
"clsx": "^1.1.1",
|
||||||
"leaflet": "^1.7.1",
|
"leaflet": "^1.7.1",
|
||||||
@@ -19,7 +18,7 @@
|
|||||||
"react-leaflet": "^3.2.1",
|
"react-leaflet": "^3.2.1",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-scripts": "4.0.3",
|
"react-scripts": "4.0.3",
|
||||||
"web-vitals": "^0.2.4"
|
"web-vitals": "^2.1.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
jest.mock("../Contexts/FileUploadContext");
|
jest.mock("../Contexts/FileUploadContext");
|
||||||
jest.mock("../Contexts/VehicleContext");
|
jest.mock("../Contexts/VehicleContext");
|
||||||
jest.mock("../Contexts/UpdatesContext");
|
|
||||||
jest.mock("../Contexts/UserContext");
|
jest.mock("../Contexts/UserContext");
|
||||||
jest.mock("../Contexts/ManifestsContext");
|
jest.mock("../Contexts/ManifestsContext");
|
||||||
jest.mock("../Contexts/CarUpdatesContext");
|
jest.mock("../Contexts/CarUpdatesContext");
|
||||||
@@ -65,26 +64,6 @@ describe("App", () => {
|
|||||||
await sleepAndCheck("/home", "span.MuiButton-label", "Sign In");
|
await sleepAndCheck("/home", "span.MuiButton-label", "Sign In");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Route /vehicle-add unauthenticated", async () => {
|
|
||||||
await check("/vehicle-add", "span.MuiButton-label", "Sign In");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Route /carupdate-deploy unauthenticated", async () => {
|
|
||||||
await check("/carupdate-deploy/1", "span.MuiButton-label", "Sign In");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Route /carupdate-status unauthenticated", async () => {
|
|
||||||
await check("/carupdate-status/1", "span.MuiButton-label", "Sign In");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Route /vehicles unauthenticated", async () => {
|
|
||||||
await check("/vehicles", "span.MuiButton-label", "Sign In");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Route /vehicle-status unauthenticated", async () => {
|
|
||||||
await check("/vehicle-status/FISKER123", "span.MuiButton-label", "Sign In");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Route /datascope unauthenticated", async () => {
|
it("Route /datascope unauthenticated", async () => {
|
||||||
await sleepAndCheck("/datascope", "span.MuiButton-label", "Sign In");
|
await sleepAndCheck("/datascope", "span.MuiButton-label", "Sign In");
|
||||||
});
|
});
|
||||||
@@ -109,6 +88,25 @@ describe("App", () => {
|
|||||||
await check("/package-create", "span.MuiButton-label", "Sign In");
|
await check("/package-create", "span.MuiButton-label", "Sign In");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Route /vehicle-add unauthenticated", async () => {
|
||||||
|
await check("/vehicle-add", "span.MuiButton-label", "Sign In");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Route /vehicles unauthenticated", async () => {
|
||||||
|
await check("/vehicles", "span.MuiButton-label", "Sign In");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Route /vehicle-status unauthenticated", async () => {
|
||||||
|
await check("/vehicle-status/FISKER123", "span.MuiButton-label", "Sign In");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Route /vehicle-status/vin/carupdateid unauthenticated", async () => {
|
||||||
|
await check("/vehicle-status/1G1FP87S3GN100062/283", "span.MuiButton-label", "Sign In");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Route /page-not-found unauthenticated", async () => {
|
||||||
|
await check("/page-not-found", "h1", "Page Not Found");
|
||||||
|
});
|
||||||
it("Route / authenticated", async () => {
|
it("Route / authenticated", async () => {
|
||||||
setToken(TEST_AUTH_OBJECT);
|
setToken(TEST_AUTH_OBJECT);
|
||||||
await sleepAndCheck("/", "h6", "Home");
|
await sleepAndCheck("/", "h6", "Home");
|
||||||
@@ -119,40 +117,11 @@ describe("App", () => {
|
|||||||
await sleepAndCheck("/home", "h6", "Home");
|
await sleepAndCheck("/home", "h6", "Home");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Route /vehicle-add authenticated", async () => {
|
|
||||||
setToken(TEST_AUTH_OBJECT);
|
|
||||||
await check("/vehicle-add", "h6", "Add Vehicle");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Route /carupdate-status authenticated", async () => {
|
|
||||||
setToken(TEST_AUTH_OBJECT);
|
|
||||||
await check("/carupdate-status/1", "h6", "Package Package 1.0");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Route /vehicles authenticated", async () => {
|
|
||||||
setToken(TEST_AUTH_OBJECT);
|
|
||||||
await check("/vehicles", "h6", "Vehicles");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Route /vehicle-status authenticated", async () => {
|
|
||||||
setToken(TEST_AUTH_OBJECT);
|
|
||||||
await check("/vehicle-status/FISKER123", "h6", "Vehicle FISKER123 Details");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Route /page-not-found unauthenticated", async () => {
|
|
||||||
await check("/page-not-found", "h1", "Page Not Found");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Route /page-not-found authenticated", async () => {
|
it("Route /page-not-found authenticated", async () => {
|
||||||
setToken(TEST_AUTH_OBJECT);
|
setToken(TEST_AUTH_OBJECT);
|
||||||
await check("/page-not-found", "h1", "Page Not Found");
|
await check("/page-not-found", "h1", "Page Not Found");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Route /carupdate-deploy authenticated", async () => {
|
|
||||||
setToken(TEST_AUTH_OBJECT);
|
|
||||||
await check("/carupdate-deploy/1", "h6", "Deploy Package 1.0");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Route /datascope authenticated", async () => {
|
it("Route /datascope authenticated", async () => {
|
||||||
setToken(TEST_AUTH_OBJECT);
|
setToken(TEST_AUTH_OBJECT);
|
||||||
await sleepAndCheck("/datascope", "h6", "Datascope");
|
await sleepAndCheck("/datascope", "h6", "Datascope");
|
||||||
@@ -182,4 +151,24 @@ describe("App", () => {
|
|||||||
setToken(TEST_AUTH_OBJECT);
|
setToken(TEST_AUTH_OBJECT);
|
||||||
await check("/package-create", "h6", "Create Deployments");
|
await check("/package-create", "h6", "Create Deployments");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Route /vehicle-add authenticated", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT);
|
||||||
|
await check("/vehicle-add", "h6", "Add Vehicle");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Route /vehicles authenticated", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT);
|
||||||
|
await check("/vehicles", "h6", "Vehicles");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Route /vehicle-status authenticated", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT);
|
||||||
|
await check("/vehicle-status/FISKER123", "h6", "Vehicle FISKER123 Details");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Route /vehicle-status/vin/carupdateid authenticated", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT);
|
||||||
|
await sleepAndCheck("/vehicle-status/1G1FP87S3GN100062/283", "h6", "Vehicle 1G1FP87S3GN100062, Update TEST UPDATE");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,158 +0,0 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import { useParams, Redirect } from "react-router";
|
|
||||||
import { Button, Grid, Typography } from "@material-ui/core";
|
|
||||||
import {
|
|
||||||
UpdatesProvider,
|
|
||||||
useUpdatesContext,
|
|
||||||
} from "../../Contexts/UpdatesContext";
|
|
||||||
import { VehicleProvider } from "../../Contexts/VehicleContext";
|
|
||||||
import { useUserContext } from "../../Contexts/UserContext";
|
|
||||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
|
||||||
import useStyles from "../../useStyles";
|
|
||||||
import { tsLocalDateTimeString } from "../../../utils/dates";
|
|
||||||
import SearchField from "../../Controls/SearchField";
|
|
||||||
import CarSelectionTable from "../../Cars/CarSelectionTable";
|
|
||||||
import { logger } from "../../../services/monitoring";
|
|
||||||
|
|
||||||
const MainForm = () => {
|
|
||||||
const { packageid } = useParams();
|
|
||||||
const { getPackages, createCarUpdates, packages, busy } = useUpdatesContext();
|
|
||||||
const {
|
|
||||||
token: {
|
|
||||||
idToken: { jwtToken: token },
|
|
||||||
},
|
|
||||||
} = useUserContext();
|
|
||||||
const { setMessage, setTitle } = useStatusContext();
|
|
||||||
const [packageName, setPackageName] = useState("");
|
|
||||||
const [version, setVersion] = useState("");
|
|
||||||
const [description, setDescription] = useState("");
|
|
||||||
const [createDate, setCreateDate] = useState("");
|
|
||||||
const [selected, setSelected] = useState([]);
|
|
||||||
const [search, setSearch] = useState("");
|
|
||||||
const [redirect, setRedirect] = useState("");
|
|
||||||
const classes = useStyles();
|
|
||||||
|
|
||||||
const handleSearch = (search) => {
|
|
||||||
setSelected([]);
|
|
||||||
setSearch(search);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSelectAll = (cars) => {
|
|
||||||
setSelected(cars);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSelect = (event, key) => {
|
|
||||||
try {
|
|
||||||
let newSelected;
|
|
||||||
if (event.target.checked) {
|
|
||||||
newSelected = [...selected];
|
|
||||||
newSelected.push(key);
|
|
||||||
} else {
|
|
||||||
newSelected = selected.filter((vin) => vin !== key);
|
|
||||||
}
|
|
||||||
setSelected(newSelected);
|
|
||||||
} catch (e) {
|
|
||||||
logger.warn(e.stack);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSubmit = async (event) => {
|
|
||||||
try {
|
|
||||||
event.preventDefault();
|
|
||||||
const data = {
|
|
||||||
package_id: parseInt(packageid),
|
|
||||||
vins: selected,
|
|
||||||
};
|
|
||||||
await createCarUpdates(data, token);
|
|
||||||
setMessage(
|
|
||||||
`Deployed ${packageName} ${version} to ${selected.length} cars`
|
|
||||||
);
|
|
||||||
setRedirect(`/carupdate-status/${packageid}`);
|
|
||||||
} catch (e) {
|
|
||||||
setMessage(e.message);
|
|
||||||
logger.warn(e.stack);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getData = async () => {
|
|
||||||
try {
|
|
||||||
getPackages({ id: parseInt(packageid) }, token);
|
|
||||||
} catch (e) {
|
|
||||||
setMessage(e.message);
|
|
||||||
logger.warn(e.stack);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getData();
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [token]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTitle(`Deploy ${packageName} ${version}`);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [packageName, version]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!packages || packages.length === 0) return;
|
|
||||||
var data = packages[0];
|
|
||||||
|
|
||||||
setPackageName(data.package_name);
|
|
||||||
setVersion(data.version);
|
|
||||||
setDescription(data.desc || "");
|
|
||||||
setCreateDate(tsLocalDateTimeString(data.timestamp));
|
|
||||||
}, [packages]);
|
|
||||||
|
|
||||||
if (redirect.length > 0) {
|
|
||||||
return <Redirect to={redirect} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.paper}>
|
|
||||||
<form className={classes.form} noValidate action="{onSubmit}">
|
|
||||||
<Typography variant="body2">
|
|
||||||
Created {createDate}. {description || "No description"}
|
|
||||||
</Typography>
|
|
||||||
<Grid container className={classes.root} spacing={2}>
|
|
||||||
<Grid item md={10}>
|
|
||||||
<SearchField classes={classes} onSearch={handleSearch} />
|
|
||||||
<div
|
|
||||||
className={classes.labelInline}
|
|
||||||
>{`${selected.length} Selected`}</div>
|
|
||||||
</Grid>
|
|
||||||
<Grid item md={2} style={{ textAlign: "right" }}>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
disabled={busy || selected.length === 0}
|
|
||||||
fullWidth
|
|
||||||
variant="contained"
|
|
||||||
color="primary"
|
|
||||||
className={classes.formControl}
|
|
||||||
onClick={onSubmit}
|
|
||||||
>
|
|
||||||
{busy ? "Deploying..." : "Deploy"}
|
|
||||||
</Button>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
<CarSelectionTable
|
|
||||||
classes={classes}
|
|
||||||
token={token}
|
|
||||||
search={{ search }}
|
|
||||||
selected={selected}
|
|
||||||
onSelect={handleSelect}
|
|
||||||
onSelectAll={handleSelectAll}
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const UpdatePackageDeployForm = () => (
|
|
||||||
<VehicleProvider>
|
|
||||||
<UpdatesProvider>
|
|
||||||
<MainForm />
|
|
||||||
</UpdatesProvider>
|
|
||||||
</VehicleProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default UpdatePackageDeployForm;
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import { useParams } from "react-router";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import {
|
|
||||||
LinearProgress,
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableFooter,
|
|
||||||
TableHead,
|
|
||||||
TablePagination,
|
|
||||||
TableRow,
|
|
||||||
} from "@material-ui/core";
|
|
||||||
import clsx from "clsx";
|
|
||||||
|
|
||||||
import {
|
|
||||||
UpdatesProvider,
|
|
||||||
useUpdatesContext,
|
|
||||||
} from "../../Contexts/UpdatesContext";
|
|
||||||
import { useUserContext } from "../../Contexts/UserContext";
|
|
||||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
|
||||||
import useStyles from "../../useStyles";
|
|
||||||
import { LocalDateTimeString } from "../../../utils/dates";
|
|
||||||
import { logger } from "../../../services/monitoring";
|
|
||||||
|
|
||||||
const MainForm = () => {
|
|
||||||
const { packageid } = useParams();
|
|
||||||
const classes = useStyles();
|
|
||||||
const [pageSize, setPageSize] = useState(10);
|
|
||||||
const [pageIndex, setPageIndex] = useState(0);
|
|
||||||
const {
|
|
||||||
getCarUpdates,
|
|
||||||
carUpdates,
|
|
||||||
totalCarUpdates,
|
|
||||||
getPackages,
|
|
||||||
packages,
|
|
||||||
startMonitor,
|
|
||||||
stopMonitor,
|
|
||||||
} = useUpdatesContext();
|
|
||||||
const { setMessage, setTitle } = useStatusContext();
|
|
||||||
const {
|
|
||||||
token: {
|
|
||||||
idToken: { jwtToken: token },
|
|
||||||
},
|
|
||||||
} = useUserContext();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
await getPackages({ id: packageid }, token);
|
|
||||||
} catch (e) {
|
|
||||||
setMessage(e.message);
|
|
||||||
logger.warn(e.stack);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [token]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!packages || packages.length === 0) return;
|
|
||||||
setTitle(`Package ${packages[0].package_name} ${packages[0].version}`);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [packages]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
stopMonitor();
|
|
||||||
await getCarUpdates(
|
|
||||||
{
|
|
||||||
packageid,
|
|
||||||
limit: pageSize,
|
|
||||||
offset: pageSize * pageIndex,
|
|
||||||
},
|
|
||||||
token
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
setMessage(e.message);
|
|
||||||
logger.warn(e.stack);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [pageIndex, pageSize, token]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
try {
|
|
||||||
if (carUpdates.length === 0) return;
|
|
||||||
startMonitor(token);
|
|
||||||
} catch (e) {
|
|
||||||
setMessage(e.message);
|
|
||||||
logger.warn(e.stack);
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
stopMonitor();
|
|
||||||
};
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [carUpdates]);
|
|
||||||
|
|
||||||
const handleChangePageIndex = (event, newIndex) => {
|
|
||||||
setPageIndex(newIndex);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChangePageSize = (event) => {
|
|
||||||
setPageSize(parseInt(event.target.value, 10));
|
|
||||||
setPageIndex(0);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={clsx(classes.paper, classes.tableSize)}>
|
|
||||||
<Table>
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell align="center">ID</TableCell>
|
|
||||||
<TableCell align="center">Vehicle</TableCell>
|
|
||||||
<TableCell align="center">Status</TableCell>
|
|
||||||
<TableCell align="center">Created</TableCell>
|
|
||||||
<TableCell align="center">Updated</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{carUpdates.map((row) => (
|
|
||||||
<TableRow key={row.id}>
|
|
||||||
<TableCell align="center">{row.id}</TableCell>
|
|
||||||
<TableCell align="center">
|
|
||||||
<Link to={`/vehicle-status/${row.vin}`}>{row.vin}</Link>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="center">
|
|
||||||
{row.status}
|
|
||||||
{row.progress > -1 && (
|
|
||||||
<LinearProgress variant="determinate" value={row.progress} />
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="center">
|
|
||||||
{LocalDateTimeString(row.created)}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="center">
|
|
||||||
{LocalDateTimeString(row.updated)}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
<TableFooter>
|
|
||||||
<TableRow>
|
|
||||||
<TablePagination
|
|
||||||
rowsPerPageOptions={[5, 10, 25, 100]}
|
|
||||||
colSpan={5}
|
|
||||||
count={totalCarUpdates}
|
|
||||||
rowsPerPage={pageSize}
|
|
||||||
page={pageIndex}
|
|
||||||
SelectProps={{
|
|
||||||
inputProps: { "aria-label": "rows per page" },
|
|
||||||
native: true,
|
|
||||||
}}
|
|
||||||
onPageChange={handleChangePageIndex}
|
|
||||||
onRowsPerPageChange={handleChangePageSize}
|
|
||||||
/>
|
|
||||||
</TableRow>
|
|
||||||
</TableFooter>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const CarUpdatesStatus = () => (
|
|
||||||
<UpdatesProvider>
|
|
||||||
<MainForm />
|
|
||||||
</UpdatesProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default CarUpdatesStatus;
|
|
||||||
@@ -8,9 +8,9 @@ import { VehicleProvider } from "../../Contexts/VehicleContext";
|
|||||||
import { useUserContext } from "../../Contexts/UserContext";
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
import useStyles from "../../useStyles";
|
import useStyles from "../../useStyles";
|
||||||
import SendCommand from "../SendCommand";
|
import SendCommand from "../../Controls/SendCommand";
|
||||||
import SearchField from "../../Controls/SearchField";
|
import SearchField from "../../Controls/SearchField";
|
||||||
import CarSelectionTable from "../CarSelectionTable";
|
import CarSelectionTable from "../../Controls/CarSelectionTable";
|
||||||
import { logger } from "../../../services/monitoring";
|
import { logger } from "../../../services/monitoring";
|
||||||
|
|
||||||
const MainForm = () => {
|
const MainForm = () => {
|
||||||
@@ -3,8 +3,8 @@ import { useParams } from "react-router";
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { Button, Grid, Typography } from "@material-ui/core";
|
import { Button, Grid, Typography } from "@material-ui/core";
|
||||||
|
|
||||||
import CarECUs from "../CarECUs";
|
import CarECUsTable from "../../Controls/CarECUsTable";
|
||||||
import CarUpdates from "../CarUpdates";
|
import CarUpdatesTable from "../../Controls/CarUpdatesTable";
|
||||||
import {
|
import {
|
||||||
VehicleProvider,
|
VehicleProvider,
|
||||||
useVehicleContext,
|
useVehicleContext,
|
||||||
@@ -52,7 +52,7 @@ const MainForm = () => {
|
|||||||
return (
|
return (
|
||||||
<div className={clsx(classes.paper, classes.tableSize)}>
|
<div className={clsx(classes.paper, classes.tableSize)}>
|
||||||
<Typography variant="h6">Car Updates</Typography>
|
<Typography variant="h6">Car Updates</Typography>
|
||||||
<CarUpdates vin={vin} token={token} />
|
<CarUpdatesTable vin={vin} token={token} />
|
||||||
<Grid container className={classes.root} spacing={2}>
|
<Grid container className={classes.root} spacing={2}>
|
||||||
<Grid item md={4} className={classes.textJustifyAlign}></Grid>
|
<Grid item md={4} className={classes.textJustifyAlign}></Grid>
|
||||||
<Grid item md={4} className={classes.textCenterAlign}>
|
<Grid item md={4} className={classes.textCenterAlign}>
|
||||||
@@ -73,7 +73,7 @@ const MainForm = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<CarECUs vin={vin} token={token} />
|
<CarECUsTable vin={vin} token={token} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
145
src/components/Cars/UpdateStatus/index.jsx
Normal file
145
src/components/Cars/UpdateStatus/index.jsx
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { Grid, TextField } from "@material-ui/core";
|
||||||
|
|
||||||
|
import CarUpdateStatusProgress from "../../Controls/CarUpdateStatusProgress";
|
||||||
|
import CarUpdateStatusTable from "../../Controls/CarUpdateStatusTable";
|
||||||
|
import {
|
||||||
|
CarUpdatesProvider,
|
||||||
|
useCarUpdatesContext,
|
||||||
|
} from "../../Contexts/CarUpdatesContext";
|
||||||
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
|
import useStyles from "../../useStyles";
|
||||||
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
|
import { logger } from "../../../services/monitoring";
|
||||||
|
import { LocalDateTimeString } from "../../../utils/dates";
|
||||||
|
|
||||||
|
const MainForm = () => {
|
||||||
|
const { vin, carupdateid } = useParams();
|
||||||
|
const [manifest, setManifest] = useState(null);
|
||||||
|
const [status, setStatus] = useState(null);
|
||||||
|
const { setTitle, setSitePath, setMessage } = useStatusContext();
|
||||||
|
const { getCarUpdates, carUpdates, startMonitor, stopMonitor } =
|
||||||
|
useCarUpdatesContext();
|
||||||
|
const {
|
||||||
|
token: {
|
||||||
|
idToken: { jwtToken: token },
|
||||||
|
},
|
||||||
|
} = useUserContext();
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const result = await getCarUpdates({ id: carupdateid }, token);
|
||||||
|
if (!result.data && result.data.length === 0)
|
||||||
|
throw new Error(`error getting update ${carupdateid}`);
|
||||||
|
setManifest(result.data[0]["updatemanifest"]);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!manifest) return;
|
||||||
|
const title = `Vehicle ${vin}, Update ${manifest.name}`;
|
||||||
|
setTitle(title);
|
||||||
|
setSitePath([
|
||||||
|
{
|
||||||
|
label: "Vehicles",
|
||||||
|
link: "/vehicles",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: `Vehicle ${vin} Details`,
|
||||||
|
link: `/vehicle-status/${vin}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: title,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [manifest]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
if (carUpdates.length === 0) return;
|
||||||
|
setStatus(carUpdates[0]);
|
||||||
|
startMonitor(token);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
logger.warn(e.stack);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
stopMonitor();
|
||||||
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [carUpdates]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.root}>
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
{manifest && (
|
||||||
|
<>
|
||||||
|
<Grid item md={4} className={classes.textJustifyAlign}>
|
||||||
|
<TextField
|
||||||
|
label="Name"
|
||||||
|
defaultValue={manifest.name}
|
||||||
|
className={classes.fullWidth}
|
||||||
|
InputProps={{
|
||||||
|
readOnly: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item md={4} className={classes.textCenterAlign}>
|
||||||
|
<TextField
|
||||||
|
label="Version"
|
||||||
|
defaultValue={manifest.version}
|
||||||
|
className={classes.fullWidth}
|
||||||
|
InputProps={{
|
||||||
|
readOnly: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item md={4} className={classes.textRightAlign}>
|
||||||
|
<TextField
|
||||||
|
label="Created"
|
||||||
|
defaultValue={LocalDateTimeString(manifest.created)}
|
||||||
|
className={classes.fullWidth}
|
||||||
|
InputProps={{
|
||||||
|
readOnly: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item md={12}>
|
||||||
|
<TextField
|
||||||
|
label="Description"
|
||||||
|
defaultValue={manifest.description}
|
||||||
|
className={classes.fullWidth}
|
||||||
|
InputProps={{
|
||||||
|
readOnly: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Grid item md={12}>
|
||||||
|
<CarUpdateStatusProgress status={status} />
|
||||||
|
</Grid>
|
||||||
|
<Grid item md={12}>
|
||||||
|
<CarUpdateStatusTable carupdateid={carupdateid} token={token} />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const UpdateStatus = () => (
|
||||||
|
<CarUpdatesProvider>
|
||||||
|
<MainForm />
|
||||||
|
</CarUpdatesProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default UpdateStatus;
|
||||||
@@ -51,8 +51,11 @@ export const CarUpdatesProvider = ({ children }) => {
|
|||||||
result = await api.getCarUpdates(search, token);
|
result = await api.getCarUpdates(search, token);
|
||||||
if (result.error)
|
if (result.error)
|
||||||
throw new Error(`Get car updates error. ${result.message}`);
|
throw new Error(`Get car updates error. ${result.message}`);
|
||||||
result.data.forEach((item) => (item.progress = 0));
|
result.data.forEach((item) => {
|
||||||
console.log(result.data);
|
item.progress = 0;
|
||||||
|
item.msg = item.status;
|
||||||
|
applyProgressStatus(item, item);
|
||||||
|
});
|
||||||
setCarUpdates(result.data);
|
setCarUpdates(result.data);
|
||||||
if (search && search.offset === 0 && result.total) {
|
if (search && search.offset === 0 && result.total) {
|
||||||
setTotalCarUpdates(result.total);
|
setTotalCarUpdates(result.total);
|
||||||
@@ -179,6 +182,21 @@ export const CarUpdatesProvider = ({ children }) => {
|
|||||||
progressTimer = 0;
|
progressTimer = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getLog = async (query, token) => {
|
||||||
|
let result;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setBusy(true);
|
||||||
|
result = await api.getCarUpdateLog(query, token);
|
||||||
|
if (result.error)
|
||||||
|
throw new Error(`Get car update log error. ${result.message}`);
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CarUpdatesContext.Provider
|
<CarUpdatesContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@@ -187,6 +205,7 @@ export const CarUpdatesProvider = ({ children }) => {
|
|||||||
totalCarUpdates,
|
totalCarUpdates,
|
||||||
deployCarUpdates,
|
deployCarUpdates,
|
||||||
getCarUpdates,
|
getCarUpdates,
|
||||||
|
getLog,
|
||||||
getVINUpdates,
|
getVINUpdates,
|
||||||
startMonitor,
|
startMonitor,
|
||||||
stopMonitor,
|
stopMonitor,
|
||||||
|
|||||||
@@ -1,252 +0,0 @@
|
|||||||
import React, { useContext, useState } from "react";
|
|
||||||
|
|
||||||
import api from "../../services/updates";
|
|
||||||
|
|
||||||
const UpdatesContext = React.createContext();
|
|
||||||
|
|
||||||
export const UpdatesProvider = ({ children }) => {
|
|
||||||
const [busy, setBusy] = useState(false);
|
|
||||||
const [packages, setPackages] = useState([]);
|
|
||||||
const [carUpdates, setCarUpdates] = useState([]);
|
|
||||||
const [totalPackages, setTotalPackages] = useState(0);
|
|
||||||
const [totalCarUpdates, setTotalCarUpdates] = useState(0);
|
|
||||||
const [delayCount, setDelayCount] = useState(0);
|
|
||||||
let progressTimer = 0;
|
|
||||||
|
|
||||||
const getPackages = async (search, token) => {
|
|
||||||
let result;
|
|
||||||
|
|
||||||
try {
|
|
||||||
setBusy(true);
|
|
||||||
result = await api.getPackages(search, token);
|
|
||||||
if (result.error)
|
|
||||||
throw new Error(`Get packages error. ${result.message}`);
|
|
||||||
setPackages(result.data);
|
|
||||||
if (search && search.offset === 0 && result.total) {
|
|
||||||
setTotalPackages(result.total);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
setBusy(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
const updatePackage = async (data, token) => {
|
|
||||||
let result = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
setBusy(true);
|
|
||||||
validateUpdatePackage(data);
|
|
||||||
result = await api.updatePackage(data, token);
|
|
||||||
if (result.error)
|
|
||||||
throw new Error(`Update package error. ${result.message}`);
|
|
||||||
} finally {
|
|
||||||
setBusy(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
const deletePackage = async (package_id, token) => {
|
|
||||||
let result;
|
|
||||||
|
|
||||||
const index = packages.findIndex((element) => {
|
|
||||||
return element.id === package_id;
|
|
||||||
});
|
|
||||||
packages.splice(index, 1);
|
|
||||||
|
|
||||||
try {
|
|
||||||
setBusy(true);
|
|
||||||
result = await api.deletePackage(package_id, token);
|
|
||||||
if (result.error)
|
|
||||||
throw new Error(`Delete package error. ${result.message}`);
|
|
||||||
} finally {
|
|
||||||
setBusy(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
const createCarUpdates = async (data, token) => {
|
|
||||||
let result;
|
|
||||||
|
|
||||||
try {
|
|
||||||
setBusy(true);
|
|
||||||
validateCreateCarUpdates(data);
|
|
||||||
result = await api.createCarUpdates(data, token);
|
|
||||||
if (result.error)
|
|
||||||
throw new Error(`Create car updates error. ${result.message}`);
|
|
||||||
} finally {
|
|
||||||
setBusy(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCarUpdates = async (search, token) => {
|
|
||||||
let result;
|
|
||||||
|
|
||||||
try {
|
|
||||||
setBusy(true);
|
|
||||||
result = await api.getCarUpdates(search, token);
|
|
||||||
if (result.error)
|
|
||||||
throw new Error(`Get packages error. ${result.message}`);
|
|
||||||
setCarUpdates(result.data);
|
|
||||||
if (search && search.offset === 0 && result.total) {
|
|
||||||
setTotalCarUpdates(result.total);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
setBusy(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getVINUpdates = async (vin, token) => {
|
|
||||||
let result;
|
|
||||||
|
|
||||||
try {
|
|
||||||
setBusy(true);
|
|
||||||
result = await api.getVINUpdates(vin, token);
|
|
||||||
if (result.error)
|
|
||||||
throw new Error(`Get VIN updates error. ${result.message}`);
|
|
||||||
} finally {
|
|
||||||
setBusy(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
const applyProgressStatus = (item, status) => {
|
|
||||||
if (status.msg === "DONE") {
|
|
||||||
delete item.progress;
|
|
||||||
item.status = "downloaded";
|
|
||||||
} else if (status.msg === "downloading" && status.total > 0) {
|
|
||||||
let progress = Math.floor((100 * status.bytes) / status.total);
|
|
||||||
if (progress > 99) progress = 0;
|
|
||||||
item.progress = progress;
|
|
||||||
item.status = `downloading ${progress}%`;
|
|
||||||
} else if (status.error > 0) {
|
|
||||||
item.status = "download error";
|
|
||||||
} else {
|
|
||||||
item.status = "downloading";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const applyProgressStatuses = (statuses) => {
|
|
||||||
let items = JSON.parse(JSON.stringify(carUpdates));
|
|
||||||
|
|
||||||
statuses.forEach((status) => {
|
|
||||||
let item = items.find((item) => status.id === item.id);
|
|
||||||
if (!item || status.id === 0) return;
|
|
||||||
applyProgressStatus(item, status);
|
|
||||||
});
|
|
||||||
|
|
||||||
setCarUpdates(items);
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateStatusProgress = async (token) => {
|
|
||||||
stopMonitor();
|
|
||||||
|
|
||||||
if (!token || carUpdates.length === 0) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
setBusy(true);
|
|
||||||
const carupdateids = carUpdates.reduce((accum, update) => {
|
|
||||||
if (update.status !== "downloaded") accum.push(update.id);
|
|
||||||
return accum;
|
|
||||||
}, []);
|
|
||||||
if (carupdateids.length === 0) return;
|
|
||||||
|
|
||||||
const result = await api.getCarUpdateProgress(
|
|
||||||
carupdateids.join(","),
|
|
||||||
token
|
|
||||||
);
|
|
||||||
if (result.error)
|
|
||||||
throw new Error(`Get update progress error. ${result.message}`);
|
|
||||||
|
|
||||||
applyProgressStatuses(result.statuses);
|
|
||||||
} catch (e) {
|
|
||||||
} finally {
|
|
||||||
setBusy(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getDelay = () => {
|
|
||||||
if (delayCount < 3) {
|
|
||||||
setDelayCount(delayCount + 1);
|
|
||||||
return 1000;
|
|
||||||
}
|
|
||||||
for (let i = 0, len = carUpdates.length; i < len; i++) {
|
|
||||||
if (carUpdates[i].status.indexOf("downloading") > -1) return 1000;
|
|
||||||
}
|
|
||||||
return 10000;
|
|
||||||
};
|
|
||||||
|
|
||||||
const startMonitor = async (token) => {
|
|
||||||
const delay = getDelay();
|
|
||||||
stopMonitor();
|
|
||||||
progressTimer = setTimeout(() => {
|
|
||||||
updateStatusProgress(token);
|
|
||||||
}, delay);
|
|
||||||
};
|
|
||||||
|
|
||||||
const stopMonitor = async () => {
|
|
||||||
if (progressTimer === 0) return;
|
|
||||||
clearTimeout(progressTimer);
|
|
||||||
progressTimer = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<UpdatesContext.Provider
|
|
||||||
value={{
|
|
||||||
busy,
|
|
||||||
packages,
|
|
||||||
totalPackages,
|
|
||||||
carUpdates,
|
|
||||||
totalCarUpdates,
|
|
||||||
getPackages,
|
|
||||||
updatePackage,
|
|
||||||
deletePackage,
|
|
||||||
createCarUpdates,
|
|
||||||
getCarUpdates,
|
|
||||||
getVINUpdates,
|
|
||||||
startMonitor,
|
|
||||||
stopMonitor,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</UpdatesContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useUpdatesContext = () => useContext(UpdatesContext);
|
|
||||||
|
|
||||||
const validateUpdatePackage = (data) => {
|
|
||||||
if (data === null) {
|
|
||||||
throw new Error("No update data");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data.package_name) {
|
|
||||||
throw new Error("Package name required");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data.version) {
|
|
||||||
throw new Error("Version required");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const validateCreateCarUpdates = (data) => {
|
|
||||||
if (data === null) {
|
|
||||||
throw new Error("No car update data");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data.package_id || data.package_id === 0) {
|
|
||||||
throw new Error("Package id required");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data.vins || data.vins.length === 0) {
|
|
||||||
throw new Error("Cars are required");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,291 +0,0 @@
|
|||||||
jest.mock("../../services/updates");
|
|
||||||
|
|
||||||
import {
|
|
||||||
render,
|
|
||||||
cleanup,
|
|
||||||
screen,
|
|
||||||
fireEvent,
|
|
||||||
waitFor,
|
|
||||||
} from "@testing-library/react";
|
|
||||||
import { UpdatesProvider, useUpdatesContext } from "./UpdatesContext";
|
|
||||||
import { StatusProvider, useStatusContext } from "./StatusContext";
|
|
||||||
import { TEST_AUTH_OBJECT } from "../../utils/testing";
|
|
||||||
|
|
||||||
describe("UpdatesContext", () => {
|
|
||||||
describe("getPackages", () => {
|
|
||||||
const expectedData = `[{"id":1,"package_name":"Test","version":"1.0","link":"http://cloudfront.com/download","ecu_list":"ECU1 1.0.0,ECU2 1.0.2"},{"id":2,"package_name":"Test","version":"1.1","link":"http://cloudfront.com/download","ecu_list":"ECU1 1.0.1,ECU2 1.0.2"},{"id":3,"package_name":"Test","version":"1.2","link":"http://cloudfront.com/download","ecu_list":"ECU1 1.1.0,ECU2 1.1.2"}]`;
|
|
||||||
const checkState = (busy, packages, message) => {
|
|
||||||
expect(screen.getByTestId("busy").innerHTML).toEqual(busy);
|
|
||||||
expect(screen.getByTestId("packages").innerHTML).toEqual(packages);
|
|
||||||
expect(screen.getByTestId("message").innerHTML).toEqual(message);
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
const TestComp = () => {
|
|
||||||
const { busy, packages, getPackages } = useUpdatesContext();
|
|
||||||
const { message, setMessage } = useStatusContext();
|
|
||||||
const exec = async (data, token) => {
|
|
||||||
try {
|
|
||||||
await getPackages(data, token);
|
|
||||||
} catch (e) {
|
|
||||||
setMessage(e.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div data-testid="busy">{busy.toString()}</div>
|
|
||||||
<div data-testid="packages">{JSON.stringify(packages)}</div>
|
|
||||||
<div data-testid="message">{message}</div>
|
|
||||||
<button
|
|
||||||
data-testid="no-filter"
|
|
||||||
onClick={() => {
|
|
||||||
exec(null, TEST_AUTH_OBJECT);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
data-testid="with-filter"
|
|
||||||
onClick={() => {
|
|
||||||
exec({ package_name: "Test" }, TEST_AUTH_OBJECT);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
render(
|
|
||||||
<StatusProvider>
|
|
||||||
<UpdatesProvider>
|
|
||||||
<TestComp />
|
|
||||||
</UpdatesProvider>
|
|
||||||
</StatusProvider>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
cleanup();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Initial state", async () => {
|
|
||||||
checkState("false", "[]", "");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("getPackages no filter", async () => {
|
|
||||||
fireEvent.click(screen.getByTestId("no-filter"));
|
|
||||||
await waitFor(() =>
|
|
||||||
expect(screen.getByTestId("busy").innerHTML).toBe("false")
|
|
||||||
);
|
|
||||||
checkState("false", expectedData, "");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("getPackages with filter", async () => {
|
|
||||||
fireEvent.click(screen.getByTestId("with-filter"));
|
|
||||||
await waitFor(() =>
|
|
||||||
expect(screen.getByTestId("busy").innerHTML).toBe("false")
|
|
||||||
);
|
|
||||||
checkState("false", expectedData, "");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("updatePackage", () => {
|
|
||||||
let result = null;
|
|
||||||
const checkState = (busy, message, data) => {
|
|
||||||
expect(screen.getByTestId("busy").innerHTML).toEqual(busy);
|
|
||||||
expect(screen.getByTestId("message").innerHTML).toEqual(message);
|
|
||||||
expect(result).toEqual(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
const TestComp = () => {
|
|
||||||
const { busy, updatePackage } = useUpdatesContext();
|
|
||||||
const { message, setMessage } = useStatusContext();
|
|
||||||
const exec = async (data, token) => {
|
|
||||||
var r = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
r = await updatePackage(data, token);
|
|
||||||
} catch (e) {
|
|
||||||
setMessage(e.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return r;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div data-testid="busy">{busy.toString()}</div>
|
|
||||||
<div data-testid="message">{message}</div>
|
|
||||||
<button
|
|
||||||
data-testid="no-data"
|
|
||||||
onClick={async () => {
|
|
||||||
result = await exec(null, TEST_AUTH_OBJECT);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
data-testid="with-bad-data"
|
|
||||||
onClick={async () => {
|
|
||||||
result = await exec({ package_name: "Test" }, TEST_AUTH_OBJECT);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
data-testid="with-good-data"
|
|
||||||
onClick={async () => {
|
|
||||||
result = await exec(
|
|
||||||
{
|
|
||||||
package_name: "Test",
|
|
||||||
version: "1.0",
|
|
||||||
},
|
|
||||||
TEST_AUTH_OBJECT
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
render(
|
|
||||||
<StatusProvider>
|
|
||||||
<UpdatesProvider>
|
|
||||||
<TestComp />
|
|
||||||
</UpdatesProvider>
|
|
||||||
</StatusProvider>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
cleanup();
|
|
||||||
result = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Initial state", () => {
|
|
||||||
checkState("false", "", null);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("no-data", async () => {
|
|
||||||
fireEvent.click(screen.getByTestId("no-data"));
|
|
||||||
await waitFor(() =>
|
|
||||||
expect(screen.getByTestId("busy").innerHTML).toBe("false")
|
|
||||||
);
|
|
||||||
checkState("false", "No update data", null);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("with-bad-data", async () => {
|
|
||||||
fireEvent.click(screen.getByTestId("with-bad-data"));
|
|
||||||
await waitFor(() =>
|
|
||||||
expect(screen.getByTestId("busy").innerHTML).toBe("false")
|
|
||||||
);
|
|
||||||
checkState("false", "Version required", null);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("with-good-data", async () => {
|
|
||||||
fireEvent.click(screen.getByTestId("with-good-data"));
|
|
||||||
await waitFor(() =>
|
|
||||||
expect(screen.getByTestId("busy").innerHTML).toBe("false")
|
|
||||||
);
|
|
||||||
checkState("false", "", { package_name: "Test", version: "1.0" });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("createCarUpdates", () => {
|
|
||||||
let result = null;
|
|
||||||
const checkState = (busy, message, data) => {
|
|
||||||
expect(screen.getByTestId("busy").innerHTML).toEqual(busy);
|
|
||||||
expect(screen.getByTestId("message").innerHTML).toEqual(message);
|
|
||||||
expect(result).toEqual(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
const TestComp = () => {
|
|
||||||
const { busy, createCarUpdates } = useUpdatesContext();
|
|
||||||
const { message, setMessage } = useStatusContext();
|
|
||||||
const exec = async (data, token) => {
|
|
||||||
var r = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
r = await createCarUpdates(data, token);
|
|
||||||
} catch (e) {
|
|
||||||
setMessage(e.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return r;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div data-testid="busy">{busy.toString()}</div>
|
|
||||||
<div data-testid="message">{message}</div>
|
|
||||||
<button
|
|
||||||
data-testid="no-data"
|
|
||||||
onClick={async () => {
|
|
||||||
result = await exec(null, TEST_AUTH_OBJECT);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
data-testid="with-bad-data"
|
|
||||||
onClick={async () => {
|
|
||||||
result = await exec(
|
|
||||||
{ package_id: 1, vins: [] },
|
|
||||||
TEST_AUTH_OBJECT
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
data-testid="with-good-data"
|
|
||||||
onClick={async () => {
|
|
||||||
result = await exec(
|
|
||||||
{
|
|
||||||
package_id: 1,
|
|
||||||
vins: ["FISKER123", "FISKER124", "FISKER125"],
|
|
||||||
},
|
|
||||||
TEST_AUTH_OBJECT
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
render(
|
|
||||||
<StatusProvider>
|
|
||||||
<UpdatesProvider>
|
|
||||||
<TestComp />
|
|
||||||
</UpdatesProvider>
|
|
||||||
</StatusProvider>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
cleanup();
|
|
||||||
result = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Initial state", () => {
|
|
||||||
checkState("false", "", null);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("no-data", async () => {
|
|
||||||
fireEvent.click(screen.getByTestId("no-data"));
|
|
||||||
await waitFor(() =>
|
|
||||||
expect(screen.getByTestId("busy").innerHTML).toBe("false")
|
|
||||||
);
|
|
||||||
checkState("false", "No car update data", null);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("with-bad-data", async () => {
|
|
||||||
fireEvent.click(screen.getByTestId("with-bad-data"));
|
|
||||||
await waitFor(() =>
|
|
||||||
expect(screen.getByTestId("busy").innerHTML).toBe("false")
|
|
||||||
);
|
|
||||||
checkState("false", "Cars are required", null);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("with-good-data", async () => {
|
|
||||||
fireEvent.click(screen.getByTestId("with-good-data"));
|
|
||||||
await waitFor(() =>
|
|
||||||
expect(screen.getByTestId("busy").innerHTML).toBe("false")
|
|
||||||
);
|
|
||||||
checkState("false", "", {
|
|
||||||
id: 1,
|
|
||||||
package_id: 1,
|
|
||||||
vins: ["FISKER123", "FISKER124", "FISKER125"],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -11,9 +11,38 @@ let carUpdates = [
|
|||||||
status: "downloaded",
|
status: "downloaded",
|
||||||
created: "2021-07-01T22:40:07.778509Z",
|
created: "2021-07-01T22:40:07.778509Z",
|
||||||
updated: "2021-07-12T18:22:13.736755Z",
|
updated: "2021-07-12T18:22:13.736755Z",
|
||||||
|
updatemanifest: {
|
||||||
|
id: 283,
|
||||||
|
name: "TEST UPDATE",
|
||||||
|
version: "1000",
|
||||||
|
description: "UPDATE DESCRIPTION",
|
||||||
|
ecu_list: "AGS 1.0.0,AMP 1.0.0",
|
||||||
|
created: "2021-08-20T18:37:41.960397Z",
|
||||||
|
updated: "2021-08-20T18:37:50.007853Z",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
let totalCarUpdates = 1;
|
let totalCarUpdates = 1;
|
||||||
|
let carUpdateLog = {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
id: 90,
|
||||||
|
status: "package_install_complete",
|
||||||
|
error_code: 0,
|
||||||
|
created: "2021-08-23T17:06:38.410115Z",
|
||||||
|
updated: "2021-08-23T17:06:38.410115Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 89,
|
||||||
|
carupdate_id: 283,
|
||||||
|
status: "package_install_start",
|
||||||
|
error_code: 0,
|
||||||
|
created: "2021-08-23T17:06:38.030052Z",
|
||||||
|
updated: "2021-08-23T17:06:38.030052Z",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
total: 2,
|
||||||
|
};
|
||||||
|
|
||||||
export const CarUpdatesProvider = ({ children }) => {
|
export const CarUpdatesProvider = ({ children }) => {
|
||||||
return <div data-testid="mocked-carupdatesprovider">{children}</div>;
|
return <div data-testid="mocked-carupdatesprovider">{children}</div>;
|
||||||
@@ -24,7 +53,10 @@ export const useCarUpdatesContext = () => ({
|
|||||||
carUpdates,
|
carUpdates,
|
||||||
totalCarUpdates,
|
totalCarUpdates,
|
||||||
deployCarUpdates: jest.fn((data) => data),
|
deployCarUpdates: jest.fn((data) => data),
|
||||||
getCarUpdates: jest.fn(() => carUpdates),
|
getCarUpdates: jest.fn(() => ({
|
||||||
|
data: carUpdates,
|
||||||
|
})),
|
||||||
|
getLog: jest.fn(() => carUpdateLog),
|
||||||
getVINUpdates: jest.fn(() => carUpdates),
|
getVINUpdates: jest.fn(() => carUpdates),
|
||||||
startMonitor: jest.fn(),
|
startMonitor: jest.fn(),
|
||||||
stopMonitor: jest.fn(),
|
stopMonitor: jest.fn(),
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
const UpdatesContext = React.createContext();
|
|
||||||
|
|
||||||
let busy = false;
|
|
||||||
let packages = [];
|
|
||||||
const examplePackage = {
|
|
||||||
id: 0,
|
|
||||||
package_name: "Package",
|
|
||||||
version: "1.0",
|
|
||||||
desc: "Description",
|
|
||||||
release_notes: "https://www.google.com/",
|
|
||||||
timestamp: 1625615299,
|
|
||||||
created: "2021-07-01T22:40:07.778509Z",
|
|
||||||
updated: "2021-07-12T18:22:13.736755Z",
|
|
||||||
};
|
|
||||||
packages.push(examplePackage);
|
|
||||||
let totalPackages = 0;
|
|
||||||
let carUpdates = [];
|
|
||||||
let totalCarUpdates = 0;
|
|
||||||
|
|
||||||
export const UpdatesProvider = ({ children }) => {
|
|
||||||
return <div data-testid="mocked-updatesprovider">{children}</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useUpdatesContext = () => ({
|
|
||||||
busy,
|
|
||||||
packages,
|
|
||||||
totalPackages,
|
|
||||||
carUpdates,
|
|
||||||
totalCarUpdates,
|
|
||||||
getPackages: jest.fn(() => packages),
|
|
||||||
updatePackage: jest.fn((data) => data),
|
|
||||||
createCarUpdates: jest.fn((data) => data),
|
|
||||||
getCarUpdates: jest.fn(() => carUpdates),
|
|
||||||
getVINUpdates: jest.fn(() => carUpdates),
|
|
||||||
startMonitor: jest.fn(),
|
|
||||||
stopMonitor: jest.fn(),
|
|
||||||
});
|
|
||||||
174
src/components/Controls/CarECUsTable/index.jsx
Normal file
174
src/components/Controls/CarECUsTable/index.jsx
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableFooter,
|
||||||
|
TablePagination,
|
||||||
|
TableRow,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
import { LocalDateTimeString } from "../../../utils/dates";
|
||||||
|
import TableHeaderSortable from "../../Table/HeaderSortable";
|
||||||
|
import { useVehicleContext } from "../../Contexts/VehicleContext";
|
||||||
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
|
import useStyles from "../../useStyles";
|
||||||
|
import { logger } from "../../../services/monitoring";
|
||||||
|
|
||||||
|
const tableColumns = [
|
||||||
|
{
|
||||||
|
id: "ecu",
|
||||||
|
label: "ECU",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "sw_version",
|
||||||
|
label: "SW Version",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "boot_loader_version",
|
||||||
|
label: "BL Version",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "hw_version",
|
||||||
|
label: "HW Version",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "vendor",
|
||||||
|
label: "Vendor",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "config",
|
||||||
|
label: "Config",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "fingerprint",
|
||||||
|
label: "Fingerprint",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "serial_number",
|
||||||
|
label: "Serial",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "created_at",
|
||||||
|
label: "Created",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "updated_at",
|
||||||
|
label: "Updated",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const CarECUsTable = ({ vin, token }) => {
|
||||||
|
const [ecus, setECUs] = useState([]);
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
const classes = useStyles();
|
||||||
|
const [pageSize, setPageSize] = useState(10);
|
||||||
|
const [pageIndex, setPageIndex] = useState(0);
|
||||||
|
const [orderBy, setOrderBy] = useState("ecu");
|
||||||
|
const [order, setOrder] = useState("desc");
|
||||||
|
const { getECUs } = useVehicleContext();
|
||||||
|
const { setMessage } = useStatusContext();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
if (!vin || !token) return;
|
||||||
|
const result = await getECUs(
|
||||||
|
{
|
||||||
|
vin,
|
||||||
|
limit: pageSize,
|
||||||
|
offset: pageSize * pageIndex,
|
||||||
|
order: `${orderBy} ${order}`,
|
||||||
|
},
|
||||||
|
token
|
||||||
|
);
|
||||||
|
setECUs(result.data);
|
||||||
|
if (result.total > -1) setTotal(result.total);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
logger.warn(e.stack);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [vin, token, pageIndex, pageSize, orderBy, order]);
|
||||||
|
|
||||||
|
const handleChangePageIndex = (event, newIndex) => {
|
||||||
|
setPageIndex(newIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangePageSize = (event) => {
|
||||||
|
setPageSize(parseInt(event.target.value, 10));
|
||||||
|
setPageIndex(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSort = (event, property) => {
|
||||||
|
try {
|
||||||
|
if (property === orderBy) {
|
||||||
|
if (order === "asc") {
|
||||||
|
setOrder("desc");
|
||||||
|
} else {
|
||||||
|
setOrder("asc");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setOrderBy(property);
|
||||||
|
setOrder("asc");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn(e.stack);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={clsx(classes.paper, classes.tableSize)}>
|
||||||
|
<Table>
|
||||||
|
<TableHeaderSortable
|
||||||
|
classes={classes}
|
||||||
|
orderBy={orderBy}
|
||||||
|
order={order}
|
||||||
|
columnData={tableColumns}
|
||||||
|
onSortRequest={handleSort}
|
||||||
|
/>
|
||||||
|
<TableBody>
|
||||||
|
{ecus.map((row) => (
|
||||||
|
<TableRow key={row.ecu}>
|
||||||
|
<TableCell align="center">{row.ecu}</TableCell>
|
||||||
|
<TableCell align="center">{row.sw_version}</TableCell>
|
||||||
|
<TableCell align="center">{row.boot_loader_version}</TableCell>
|
||||||
|
<TableCell align="center">{row.hw_version}</TableCell>
|
||||||
|
<TableCell align="center">{row.vendor}</TableCell>
|
||||||
|
<TableCell align="center">{row.config}</TableCell>
|
||||||
|
<TableCell align="center">{row.fingerprint}</TableCell>
|
||||||
|
<TableCell align="center">{row.serial_number}</TableCell>
|
||||||
|
<TableCell align="center">
|
||||||
|
{LocalDateTimeString(row.created)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center">
|
||||||
|
{LocalDateTimeString(row.updated)}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
<TableFooter>
|
||||||
|
<TableRow>
|
||||||
|
<TablePagination
|
||||||
|
rowsPerPageOptions={[5, 10, 25, 100]}
|
||||||
|
colSpan={10}
|
||||||
|
count={total}
|
||||||
|
rowsPerPage={pageSize}
|
||||||
|
page={pageIndex}
|
||||||
|
SelectProps={{
|
||||||
|
inputProps: { "aria-label": "rows per page" },
|
||||||
|
native: true,
|
||||||
|
}}
|
||||||
|
onPageChange={handleChangePageIndex}
|
||||||
|
onRowsPerPageChange={handleChangePageSize}
|
||||||
|
/>
|
||||||
|
</TableRow>
|
||||||
|
</TableFooter>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CarECUsTable;
|
||||||
128
src/components/Controls/CarUpdateStatusProgress/index.jsx
Normal file
128
src/components/Controls/CarUpdateStatusProgress/index.jsx
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import { CheckCircle, RadioButtonUnchecked, Error } from "@material-ui/icons";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
import CircularProgress from "../CircularProgress";
|
||||||
|
import useStyles from "../../useStyles";
|
||||||
|
|
||||||
|
const Progress = ({ value }) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
if (value === 100)
|
||||||
|
return (
|
||||||
|
<CheckCircle
|
||||||
|
className={clsx(classes.progressIcon, classes.progressSuccess)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
if (value >= 0) return <CircularProgress value={value} />;
|
||||||
|
if (value < -1)
|
||||||
|
return (
|
||||||
|
<Error className={clsx(classes.progressIcon, classes.progressError)} />
|
||||||
|
);
|
||||||
|
|
||||||
|
return <RadioButtonUnchecked className={classes.progressIcon} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CarUpdateStatus = ({ status }) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
const [received, setReceived] = useState(-1);
|
||||||
|
const [approval, setApproval] = useState(-1);
|
||||||
|
const [precondition, setPrecondition] = useState(-1);
|
||||||
|
const [download, setDownload] = useState(-1);
|
||||||
|
const [install, setInstall] = useState(-1);
|
||||||
|
const [cleanup, setCleanup] = useState(-1);
|
||||||
|
const [updated, setUpdated] = useState(-1);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
/* eslint-disable no-fallthrough, default-case */
|
||||||
|
if (!status) return;
|
||||||
|
// update previous steps
|
||||||
|
switch (status.msg) {
|
||||||
|
case "cleanup_success":
|
||||||
|
setUpdated(100);
|
||||||
|
case "package_install_complete":
|
||||||
|
setInstall(100);
|
||||||
|
case "install_start":
|
||||||
|
case "installing":
|
||||||
|
case "install_complete":
|
||||||
|
case "install_error":
|
||||||
|
case "package_download_complete":
|
||||||
|
setDownload(100);
|
||||||
|
case "download_start":
|
||||||
|
case "downloading":
|
||||||
|
case "download_complete":
|
||||||
|
case "download_error":
|
||||||
|
case "install_approval_received":
|
||||||
|
setApproval(100);
|
||||||
|
case "requirements_succeeded":
|
||||||
|
setPrecondition(100);
|
||||||
|
case "manifest_received":
|
||||||
|
setReceived(100);
|
||||||
|
}
|
||||||
|
// update progress and errors
|
||||||
|
switch (status.msg) {
|
||||||
|
case "installing":
|
||||||
|
setInstall(status.progress);
|
||||||
|
break;
|
||||||
|
case "install_error":
|
||||||
|
setInstall(-100);
|
||||||
|
break;
|
||||||
|
case "downloading":
|
||||||
|
setDownload(status.progress);
|
||||||
|
break;
|
||||||
|
case "download_error":
|
||||||
|
setDownload(-100);
|
||||||
|
break;
|
||||||
|
case "cleanup_failed":
|
||||||
|
setCleanup(-100);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}, [status]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className={classes.textCenterAlign}>
|
||||||
|
<Progress value={100} />
|
||||||
|
<Typography>Pending</Typography>
|
||||||
|
</div>
|
||||||
|
<div className={classes.textCenterAlign}>
|
||||||
|
<Progress value={received} />
|
||||||
|
<Typography>Recieved</Typography>
|
||||||
|
</div>
|
||||||
|
<div className={classes.textCenterAlign}>
|
||||||
|
<Progress value={approval} />
|
||||||
|
<Typography>Approved</Typography>
|
||||||
|
</div>
|
||||||
|
<div className={classes.textCenterAlign}>
|
||||||
|
<Progress value={precondition} />
|
||||||
|
<Typography>Precondition</Typography>
|
||||||
|
</div>
|
||||||
|
<div className={classes.textCenterAlign}>
|
||||||
|
<Progress value={download} />
|
||||||
|
<Typography>Download</Typography>
|
||||||
|
</div>
|
||||||
|
<div className={classes.textCenterAlign}>
|
||||||
|
<Progress value={install} />
|
||||||
|
<Typography>Install</Typography>
|
||||||
|
</div>
|
||||||
|
<div className={classes.textCenterAlign}>
|
||||||
|
<Progress value={cleanup} />
|
||||||
|
<Typography>Clean up</Typography>
|
||||||
|
</div>
|
||||||
|
<div className={classes.textCenterAlign}>
|
||||||
|
<Progress value={updated} />
|
||||||
|
<Typography>Updated</Typography>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CarUpdateStatus;
|
||||||
134
src/components/Controls/CarUpdateStatusTable/index.jsx
Normal file
134
src/components/Controls/CarUpdateStatusTable/index.jsx
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableFooter,
|
||||||
|
TablePagination,
|
||||||
|
TableRow,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
|
||||||
|
import { LocalDateTimeString } from "../../../utils/dates";
|
||||||
|
import { logger } from "../../../services/monitoring";
|
||||||
|
import TableHeaderSortable from "../../Table/HeaderSortable";
|
||||||
|
import { useCarUpdatesContext } from "../../Contexts/CarUpdatesContext";
|
||||||
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
|
import useStyles from "../../useStyles";
|
||||||
|
|
||||||
|
const tableColumns = [
|
||||||
|
{
|
||||||
|
id: "created_at",
|
||||||
|
label: "Date",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "status",
|
||||||
|
label: "Status",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "error_code",
|
||||||
|
label: "Error",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const CarUpdateStatusTable = ({ carupdateid, token }) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
const [log, setLog] = useState([]);
|
||||||
|
const [logTotal, setLogTotal] = useState(0);
|
||||||
|
const [pageSize, setPageSize] = useState(10);
|
||||||
|
const [pageIndex, setPageIndex] = useState(0);
|
||||||
|
const [orderBy, setOrderBy] = useState("id");
|
||||||
|
const [order, setOrder] = useState("desc");
|
||||||
|
const { getLog } = useCarUpdatesContext();
|
||||||
|
const { setMessage } = useStatusContext();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
if (!carupdateid || !token) return;
|
||||||
|
const result = await getLog(
|
||||||
|
{
|
||||||
|
carupdateid,
|
||||||
|
limit: pageSize,
|
||||||
|
offset: pageSize * pageIndex,
|
||||||
|
order: `${orderBy} ${order}`,
|
||||||
|
},
|
||||||
|
token
|
||||||
|
);
|
||||||
|
setLog(result.data);
|
||||||
|
if (pageIndex === 0 && result.total) setLogTotal(result.total);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
logger.warn(e.stack);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [carupdateid, token, pageIndex, pageSize, orderBy, order]);
|
||||||
|
|
||||||
|
const handleChangePageIndex = (event, newIndex) => {
|
||||||
|
setPageIndex(newIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangePageSize = (event) => {
|
||||||
|
setPageSize(parseInt(event.target.value, 10));
|
||||||
|
setPageIndex(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSort = (event, property) => {
|
||||||
|
try {
|
||||||
|
if (property === orderBy) {
|
||||||
|
if (order === "asc") {
|
||||||
|
setOrder("desc");
|
||||||
|
} else {
|
||||||
|
setOrder("asc");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setOrderBy(property);
|
||||||
|
setOrder("asc");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn(e.stack);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table>
|
||||||
|
<TableHeaderSortable
|
||||||
|
classes={classes}
|
||||||
|
orderBy={orderBy}
|
||||||
|
order={order}
|
||||||
|
columnData={tableColumns}
|
||||||
|
onSortRequest={handleSort}
|
||||||
|
/>
|
||||||
|
<TableBody>
|
||||||
|
{log.map((row) => (
|
||||||
|
<TableRow key={row.id}>
|
||||||
|
<TableCell align="center">
|
||||||
|
{LocalDateTimeString(row.created)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center">{row.status}</TableCell>
|
||||||
|
<TableCell align="center">{row.error}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
<TableFooter>
|
||||||
|
<TableRow>
|
||||||
|
<TablePagination
|
||||||
|
rowsPerPageOptions={[5, 10, 25, 100]}
|
||||||
|
colSpan={5}
|
||||||
|
count={logTotal}
|
||||||
|
rowsPerPage={pageSize}
|
||||||
|
page={pageIndex}
|
||||||
|
SelectProps={{
|
||||||
|
inputProps: { "aria-label": "rows per page" },
|
||||||
|
native: true,
|
||||||
|
}}
|
||||||
|
onPageChange={handleChangePageIndex}
|
||||||
|
onRowsPerPageChange={handleChangePageSize}
|
||||||
|
/>
|
||||||
|
</TableRow>
|
||||||
|
</TableFooter>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CarUpdateStatusTable;
|
||||||
162
src/components/Controls/CarUpdatesTable/index.jsx
Normal file
162
src/components/Controls/CarUpdatesTable/index.jsx
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableFooter,
|
||||||
|
TablePagination,
|
||||||
|
TableRow,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
import { LocalDateTimeString } from "../../../utils/dates";
|
||||||
|
import TableHeaderSortable from "../../Table/HeaderSortable";
|
||||||
|
import {
|
||||||
|
CarUpdatesProvider,
|
||||||
|
useCarUpdatesContext,
|
||||||
|
} from "../../Contexts/CarUpdatesContext";
|
||||||
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
|
import useStyles from "../../useStyles";
|
||||||
|
import { logger } from "../../../services/monitoring";
|
||||||
|
|
||||||
|
const tableColumns = [
|
||||||
|
{
|
||||||
|
id: "id",
|
||||||
|
label: "ID",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "update_package_id",
|
||||||
|
label: "Name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "status",
|
||||||
|
label: "Status",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "created_at",
|
||||||
|
label: "Created",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "updated_at",
|
||||||
|
label: "Updated",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const MainForm = ({ vin, token }) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
const [pageSize, setPageSize] = useState(10);
|
||||||
|
const [pageIndex, setPageIndex] = useState(0);
|
||||||
|
const [orderBy, setOrderBy] = useState("id");
|
||||||
|
const [order, setOrder] = useState("desc");
|
||||||
|
const { getCarUpdates, carUpdates, totalCarUpdates } = useCarUpdatesContext();
|
||||||
|
const { setMessage } = useStatusContext();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
if (!vin || !token) return;
|
||||||
|
await getCarUpdates(
|
||||||
|
{
|
||||||
|
vin,
|
||||||
|
limit: pageSize,
|
||||||
|
offset: pageSize * pageIndex,
|
||||||
|
order: `${orderBy} ${order}`,
|
||||||
|
},
|
||||||
|
token
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
logger.warn(e.stack);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [vin, token, pageIndex, pageSize, orderBy, order]);
|
||||||
|
|
||||||
|
const handleChangePageIndex = (event, newIndex) => {
|
||||||
|
setPageIndex(newIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangePageSize = (event) => {
|
||||||
|
setPageSize(parseInt(event.target.value, 10));
|
||||||
|
setPageIndex(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSort = (event, property) => {
|
||||||
|
try {
|
||||||
|
if (property === orderBy) {
|
||||||
|
if (order === "asc") {
|
||||||
|
setOrder("desc");
|
||||||
|
} else {
|
||||||
|
setOrder("asc");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setOrderBy(property);
|
||||||
|
setOrder("asc");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn(e.stack);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateName = (row) => {
|
||||||
|
if (row.updatemanifest)
|
||||||
|
return `${row.updatemanifest.name} ${row.updatemanifest.version}`;
|
||||||
|
return "None";
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table>
|
||||||
|
<TableHeaderSortable
|
||||||
|
classes={classes}
|
||||||
|
orderBy={orderBy}
|
||||||
|
order={order}
|
||||||
|
columnData={tableColumns}
|
||||||
|
onSortRequest={handleSort}
|
||||||
|
/>
|
||||||
|
<TableBody>
|
||||||
|
{carUpdates.map((row) => (
|
||||||
|
<TableRow key={row.id}>
|
||||||
|
<TableCell align="center">{row.id}</TableCell>
|
||||||
|
<TableCell align="center">
|
||||||
|
<Link to={`/vehicle-status/${row.vin}/${row.id}`}>
|
||||||
|
{updateName(row)}
|
||||||
|
</Link>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center">{row.status}</TableCell>
|
||||||
|
<TableCell align="center">
|
||||||
|
{LocalDateTimeString(row.created)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center">
|
||||||
|
{LocalDateTimeString(row.updated)}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
<TableFooter>
|
||||||
|
<TableRow>
|
||||||
|
<TablePagination
|
||||||
|
rowsPerPageOptions={[5, 10, 25, 100]}
|
||||||
|
colSpan={5}
|
||||||
|
count={totalCarUpdates}
|
||||||
|
rowsPerPage={pageSize}
|
||||||
|
page={pageIndex}
|
||||||
|
SelectProps={{
|
||||||
|
inputProps: { "aria-label": "rows per page" },
|
||||||
|
native: true,
|
||||||
|
}}
|
||||||
|
onPageChange={handleChangePageIndex}
|
||||||
|
onRowsPerPageChange={handleChangePageSize}
|
||||||
|
/>
|
||||||
|
</TableRow>
|
||||||
|
</TableFooter>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CarUpdatesTable = (props) => (
|
||||||
|
<CarUpdatesProvider>
|
||||||
|
<MainForm {...props} />
|
||||||
|
</CarUpdatesProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default CarUpdatesTable;
|
||||||
43
src/components/Controls/CircularProgress/index.jsx
Normal file
43
src/components/Controls/CircularProgress/index.jsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import React from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import CircularProgress from "@material-ui/core/CircularProgress";
|
||||||
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
import Box from "@material-ui/core/Box";
|
||||||
|
|
||||||
|
const CircularProgressWithLabel = (props) => {
|
||||||
|
return (
|
||||||
|
<Box position="relative" display="inline-flex">
|
||||||
|
<CircularProgress
|
||||||
|
variant="determinate"
|
||||||
|
{...props}
|
||||||
|
style={{ color: "green" }}
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
top={0}
|
||||||
|
left={0}
|
||||||
|
bottom={0}
|
||||||
|
right={0}
|
||||||
|
position="absolute"
|
||||||
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
component="div"
|
||||||
|
color="textSecondary"
|
||||||
|
>{`${Math.round(props.value)}%`}</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
CircularProgressWithLabel.propTypes = {
|
||||||
|
/**
|
||||||
|
* The value of the progress indicator for the determinate variant.
|
||||||
|
* Value between 0 and 100.
|
||||||
|
*/
|
||||||
|
value: PropTypes.number.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CircularProgressWithLabel;
|
||||||
@@ -6,6 +6,7 @@ import api from "../../../services/grafana";
|
|||||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
import useStyles from "../../useStyles";
|
import useStyles from "../../useStyles";
|
||||||
import ResponsiveIFrame from "../../Controls/ResponsiveIFrame";
|
import ResponsiveIFrame from "../../Controls/ResponsiveIFrame";
|
||||||
|
import { logger } from "../../../services/monitoring";
|
||||||
|
|
||||||
const Datascope = () => {
|
const Datascope = () => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
@@ -23,7 +24,7 @@ const Datascope = () => {
|
|||||||
api
|
api
|
||||||
.getCarsCount()
|
.getCarsCount()
|
||||||
.then((result) => setCarsCount(result))
|
.then((result) => setCarsCount(result))
|
||||||
.catch((error) => console.log(error));
|
.catch((error) => logger.warn(error.stack));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const [signalsCount, setSignalsCount] = useState("0");
|
const [signalsCount, setSignalsCount] = useState("0");
|
||||||
@@ -45,7 +46,7 @@ const Datascope = () => {
|
|||||||
let num = result.toLocaleString();
|
let num = result.toLocaleString();
|
||||||
setSignalsCount(num);
|
setSignalsCount(num);
|
||||||
})
|
})
|
||||||
.catch((error) => console.log(error));
|
.catch((error) => logger.warn(error.stack));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import React, { Component } from "react";
|
|||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { Typography } from "@material-ui/core";
|
import { Typography } from "@material-ui/core";
|
||||||
|
|
||||||
|
import { logger } from "../services/monitoring";
|
||||||
|
|
||||||
const reload = () => {
|
const reload = () => {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
};
|
};
|
||||||
@@ -22,6 +24,10 @@ export default class ErrorBoundary extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.hasError) {
|
if (this.state.hasError) {
|
||||||
|
try {
|
||||||
|
logger.error(this.state.error.stack);
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
if (this.state.error && this.state.error.name === "ChunkLoadError") {
|
if (this.state.error && this.state.error.name === "ChunkLoadError") {
|
||||||
reload();
|
reload();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { useUserContext } from "../../Contexts/UserContext";
|
|||||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
import useStyles from "../../useStyles";
|
import useStyles from "../../useStyles";
|
||||||
import SearchField from "../../Controls/SearchField";
|
import SearchField from "../../Controls/SearchField";
|
||||||
import CarSelectionTable from "../../Cars/CarSelectionTable";
|
import CarSelectionTable from "../../Controls/CarSelectionTable";
|
||||||
import { logger } from "../../../services/monitoring";
|
import { logger } from "../../../services/monitoring";
|
||||||
import { LocalDateTimeString } from "../../../utils/dates";
|
import { LocalDateTimeString } from "../../../utils/dates";
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ const MainForm = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!manifests || manifests.length === 0) return;
|
if (!manifests || manifests.length === 0) return;
|
||||||
var data = manifests[0];
|
const data = manifests[0];
|
||||||
|
|
||||||
setManifestName(data.name);
|
setManifestName(data.name);
|
||||||
setVersion(data.version);
|
setVersion(data.version);
|
||||||
|
|||||||
@@ -68,6 +68,10 @@ const MainForm = () => {
|
|||||||
label: "Deployments",
|
label: "Deployments",
|
||||||
link: "/packages",
|
link: "/packages",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: `Deploy ${manifests[0].name} ${manifests[0].version}`,
|
||||||
|
link: `/package-deploy/${manifests[0].id}`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: title,
|
label: title,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,20 +6,19 @@ import { MessageBar } from "../MessageBar";
|
|||||||
import { useUserContext } from "../Contexts/UserContext";
|
import { useUserContext } from "../Contexts/UserContext";
|
||||||
import { Roles } from "../../utils/roles";
|
import { Roles } from "../../utils/roles";
|
||||||
|
|
||||||
const SSOForm = React.lazy(() => import("../SSOForm"));
|
|
||||||
const Home = React.lazy(() => import("../Home"));
|
|
||||||
const VehicleAddForm = React.lazy(() => import("../Cars/Add"));
|
|
||||||
const PageNotFound = React.lazy(() => import("../404"));
|
|
||||||
const CarUpdatesDeploy = React.lazy(() => import("../CarUpdates/Deploy"));
|
|
||||||
const CarUpdatesStatus = React.lazy(() => import("../CarUpdates/Status"));
|
|
||||||
const CarUpdates = React.lazy(() => import("../Cars/Status"));
|
|
||||||
const SendCommandBulk = React.lazy(() => import("../Cars/SendCommandBulk"));
|
|
||||||
const Datascope = React.lazy(() => import("../Datascope/Home"));
|
|
||||||
const BatteryDatascope = React.lazy(() => import("../Datascope/Battery"));
|
const BatteryDatascope = React.lazy(() => import("../Datascope/Battery"));
|
||||||
|
const CarsList = React.lazy(() => import("../Cars/List"));
|
||||||
|
const CarStatus = React.lazy(() => import("../Cars/Status"));
|
||||||
|
const CarUpdateStatus = React.lazy(() => import("../Cars/UpdateStatus"));
|
||||||
|
const Datascope = React.lazy(() => import("../Datascope/Home"));
|
||||||
|
const Home = React.lazy(() => import("../Home"));
|
||||||
const Manifests = React.lazy(() => import("../Manifest/List"));
|
const Manifests = React.lazy(() => import("../Manifest/List"));
|
||||||
const ManifestDeploy = React.lazy(() => import("../Manifest/Deploy"));
|
const ManifestDeploy = React.lazy(() => import("../Manifest/Deploy"));
|
||||||
const ManifestStatus = React.lazy(() => import("../Manifest/Status"));
|
const ManifestStatus = React.lazy(() => import("../Manifest/Status"));
|
||||||
const ManifestCreate = React.lazy(() => import("../Manifest/Create"));
|
const ManifestCreate = React.lazy(() => import("../Manifest/Create"));
|
||||||
|
const PageNotFound = React.lazy(() => import("../404"));
|
||||||
|
const SSOForm = React.lazy(() => import("../SSOForm"));
|
||||||
|
const VehicleAddForm = React.lazy(() => import("../Cars/Add"));
|
||||||
|
|
||||||
const SiteRoutes = () => {
|
const SiteRoutes = () => {
|
||||||
const { token, groups } = useUserContext();
|
const { token, groups } = useUserContext();
|
||||||
@@ -34,52 +33,6 @@ const SiteRoutes = () => {
|
|||||||
type={TYPES.GUEST}
|
type={TYPES.GUEST}
|
||||||
token={token}
|
token={token}
|
||||||
/>
|
/>
|
||||||
<AuthRoute
|
|
||||||
path="/home"
|
|
||||||
render={() => <Home />}
|
|
||||||
type={TYPES.PROTECTED}
|
|
||||||
token={token}
|
|
||||||
/>
|
|
||||||
<AuthRoute
|
|
||||||
path="/carupdate-deploy/:packageid"
|
|
||||||
render={() => <CarUpdatesDeploy />}
|
|
||||||
type={TYPES.PROTECTED}
|
|
||||||
token={token}
|
|
||||||
groups={groups}
|
|
||||||
roles={[Roles.CREATE]}
|
|
||||||
/>
|
|
||||||
<AuthRoute
|
|
||||||
path="/carupdate-status/:packageid"
|
|
||||||
render={() => <CarUpdatesStatus />}
|
|
||||||
type={TYPES.PROTECTED}
|
|
||||||
token={token}
|
|
||||||
groups={groups}
|
|
||||||
roles={[Roles.READ, Roles.CREATE]}
|
|
||||||
/>
|
|
||||||
<AuthRoute
|
|
||||||
path="/vehicles"
|
|
||||||
render={() => <SendCommandBulk />}
|
|
||||||
type={TYPES.PROTECTED}
|
|
||||||
token={token}
|
|
||||||
groups={groups}
|
|
||||||
roles={[Roles.CREATE]}
|
|
||||||
/>
|
|
||||||
<AuthRoute
|
|
||||||
path="/vehicle-add"
|
|
||||||
render={() => <VehicleAddForm />}
|
|
||||||
type={TYPES.PROTECTED}
|
|
||||||
token={token}
|
|
||||||
groups={groups}
|
|
||||||
roles={[Roles.CREATE]}
|
|
||||||
/>
|
|
||||||
<AuthRoute
|
|
||||||
path="/vehicle-status/:vin"
|
|
||||||
render={() => <CarUpdates />}
|
|
||||||
type={TYPES.PROTECTED}
|
|
||||||
token={token}
|
|
||||||
groups={groups}
|
|
||||||
roles={[Roles.READ, Roles.CREATE]}
|
|
||||||
/>
|
|
||||||
<AuthRoute
|
<AuthRoute
|
||||||
path="/datascope/battery"
|
path="/datascope/battery"
|
||||||
render={() => <BatteryDatascope />}
|
render={() => <BatteryDatascope />}
|
||||||
@@ -96,6 +49,12 @@ const SiteRoutes = () => {
|
|||||||
groups={groups}
|
groups={groups}
|
||||||
roles={[Roles.READ, Roles.CREATE]}
|
roles={[Roles.READ, Roles.CREATE]}
|
||||||
/>
|
/>
|
||||||
|
<AuthRoute
|
||||||
|
path="/home"
|
||||||
|
render={() => <Home />}
|
||||||
|
type={TYPES.PROTECTED}
|
||||||
|
token={token}
|
||||||
|
/>
|
||||||
<AuthRoute
|
<AuthRoute
|
||||||
path="/packages"
|
path="/packages"
|
||||||
render={() => <Manifests />}
|
render={() => <Manifests />}
|
||||||
@@ -104,6 +63,14 @@ const SiteRoutes = () => {
|
|||||||
groups={groups}
|
groups={groups}
|
||||||
roles={[Roles.READ, Roles.CREATE]}
|
roles={[Roles.READ, Roles.CREATE]}
|
||||||
/>
|
/>
|
||||||
|
<AuthRoute
|
||||||
|
path="/package-create"
|
||||||
|
render={() => <ManifestCreate />}
|
||||||
|
type={TYPES.PROTECTED}
|
||||||
|
token={token}
|
||||||
|
groups={groups}
|
||||||
|
roles={[Roles.CREATE]}
|
||||||
|
/>
|
||||||
<AuthRoute
|
<AuthRoute
|
||||||
path="/package-deploy/:manifest_id"
|
path="/package-deploy/:manifest_id"
|
||||||
render={() => <ManifestDeploy />}
|
render={() => <ManifestDeploy />}
|
||||||
@@ -121,13 +88,37 @@ const SiteRoutes = () => {
|
|||||||
roles={[Roles.READ, Roles.CREATE]}
|
roles={[Roles.READ, Roles.CREATE]}
|
||||||
/>
|
/>
|
||||||
<AuthRoute
|
<AuthRoute
|
||||||
path="/package-create"
|
path="/vehicles"
|
||||||
render={() => <ManifestCreate />}
|
render={() => <CarsList />}
|
||||||
type={TYPES.PROTECTED}
|
type={TYPES.PROTECTED}
|
||||||
token={token}
|
token={token}
|
||||||
groups={groups}
|
groups={groups}
|
||||||
roles={[Roles.CREATE]}
|
roles={[Roles.CREATE]}
|
||||||
/>
|
/>
|
||||||
|
<AuthRoute
|
||||||
|
path="/vehicle-add"
|
||||||
|
render={() => <VehicleAddForm />}
|
||||||
|
type={TYPES.PROTECTED}
|
||||||
|
token={token}
|
||||||
|
groups={groups}
|
||||||
|
roles={[Roles.CREATE]}
|
||||||
|
/>
|
||||||
|
<AuthRoute
|
||||||
|
path="/vehicle-status/:vin/:carupdateid"
|
||||||
|
render={() => <CarUpdateStatus />}
|
||||||
|
type={TYPES.PROTECTED}
|
||||||
|
token={token}
|
||||||
|
groups={groups}
|
||||||
|
roles={[Roles.READ, Roles.CREATE]}
|
||||||
|
/>
|
||||||
|
<AuthRoute
|
||||||
|
path="/vehicle-status/:vin"
|
||||||
|
render={() => <CarStatus />}
|
||||||
|
type={TYPES.PROTECTED}
|
||||||
|
token={token}
|
||||||
|
groups={groups}
|
||||||
|
roles={[Roles.READ, Roles.CREATE]}
|
||||||
|
/>
|
||||||
<PageNotFound />
|
<PageNotFound />
|
||||||
</Switch>
|
</Switch>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|||||||
@@ -9,13 +9,14 @@ import { useVehicleContext, VehicleProvider } from "../Contexts/VehicleContext";
|
|||||||
import { VehiclePopUp } from "./popup";
|
import { VehiclePopUp } from "./popup";
|
||||||
import GreenMarkerIcon from "../../assets/green-marker.png";
|
import GreenMarkerIcon from "../../assets/green-marker.png";
|
||||||
import GrayMarkerIcon from "../../assets/gray-marker.png";
|
import GrayMarkerIcon from "../../assets/gray-marker.png";
|
||||||
|
import { logger } from "../../services/monitoring";
|
||||||
|
|
||||||
const Component = () => {
|
const Component = () => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const {
|
const {
|
||||||
token: {
|
token: {
|
||||||
idToken: { jwtToken: token },
|
idToken: { jwtToken: token },
|
||||||
}
|
},
|
||||||
} = useUserContext();
|
} = useUserContext();
|
||||||
const { getConnections, getLocations, getState } = useVehicleContext();
|
const { getConnections, getLocations, getState } = useVehicleContext();
|
||||||
|
|
||||||
@@ -26,29 +27,34 @@ const Component = () => {
|
|||||||
const [markers, setMarkers] = useState([]);
|
const [markers, setMarkers] = useState([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
retrieveAndStoreLocations()
|
retrieveAndStoreLocations().then((points) => {
|
||||||
.then(points => {
|
|
||||||
centerAroundMarkers(points);
|
centerAroundMarkers(points);
|
||||||
})
|
});
|
||||||
const id = setInterval(function () {
|
const id = setInterval(function () {
|
||||||
retrieveAndStoreLocations();
|
retrieveAndStoreLocations();
|
||||||
}, REQUEST_INTERVAL);
|
}, REQUEST_INTERVAL);
|
||||||
return () => { clearInterval(id) };
|
return () => {
|
||||||
|
clearInterval(id);
|
||||||
|
};
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const retrieveAndStoreLocations = () => {
|
const retrieveAndStoreLocations = () => {
|
||||||
return getLocations(token)
|
return getLocations(token)
|
||||||
.then(result => {
|
.then((result) => {
|
||||||
if (result.data != null) {
|
if (result.data != null) {
|
||||||
const points = result.data.map(point => [point.latitude, point.longitude, point.vin]);
|
const points = result.data.map((point) => [
|
||||||
|
point.latitude,
|
||||||
|
point.longitude,
|
||||||
|
point.vin,
|
||||||
|
]);
|
||||||
setMarkers(points);
|
setMarkers(points);
|
||||||
return points
|
return points;
|
||||||
}
|
}
|
||||||
return []
|
return [];
|
||||||
})
|
})
|
||||||
.catch(error => console.log(error));
|
.catch((error) => logger.warn(error.stack));
|
||||||
}
|
};
|
||||||
|
|
||||||
const centerAroundMarkers = (markers) => {
|
const centerAroundMarkers = (markers) => {
|
||||||
// if (markers == null) {
|
// if (markers == null) {
|
||||||
@@ -62,20 +68,19 @@ const Component = () => {
|
|||||||
|
|
||||||
setCenter([37.0902, -95.7129]);
|
setCenter([37.0902, -95.7129]);
|
||||||
setZoom(4.5);
|
setZoom(4.5);
|
||||||
}
|
};
|
||||||
|
|
||||||
const [connections, setConnections] = useState({});
|
const [connections, setConnections] = useState({});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (markers.length > 0) {
|
if (markers.length > 0) {
|
||||||
const vins = markers.map(marker => marker[2]);
|
const vins = markers.map((marker) => marker[2]);
|
||||||
getConnections(vins, token)
|
getConnections(vins, token).then((connections) => {
|
||||||
.then(connections => {
|
|
||||||
setConnections(connections);
|
setConnections(connections);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
}, [markers, token])
|
}, [markers, token]);
|
||||||
|
|
||||||
const [selectedVIN, setSelectedVIN] = useState(null);
|
const [selectedVIN, setSelectedVIN] = useState(null);
|
||||||
const [carState, setCarState] = useState(null);
|
const [carState, setCarState] = useState(null);
|
||||||
@@ -86,7 +91,9 @@ const Component = () => {
|
|||||||
const id = setInterval(function () {
|
const id = setInterval(function () {
|
||||||
retrieveAndStoreCarState(selectedVIN);
|
retrieveAndStoreCarState(selectedVIN);
|
||||||
}, REQUEST_INTERVAL);
|
}, REQUEST_INTERVAL);
|
||||||
return () => { clearInterval(id) };
|
return () => {
|
||||||
|
clearInterval(id);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
}, [selectedVIN]);
|
}, [selectedVIN]);
|
||||||
@@ -94,14 +101,13 @@ const Component = () => {
|
|||||||
const selectCar = (e, vin) => {
|
const selectCar = (e, vin) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setSelectedVIN(vin);
|
setSelectedVIN(vin);
|
||||||
}
|
};
|
||||||
|
|
||||||
const retrieveAndStoreCarState = (vin) => {
|
const retrieveAndStoreCarState = (vin) => {
|
||||||
getState(token, vin)
|
getState(token, vin).then((results) => {
|
||||||
.then(results => {
|
|
||||||
setCarState({ ...results.data, vin: vin });
|
setCarState({ ...results.data, vin: vin });
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setSelectedVIN(null);
|
setSelectedVIN(null);
|
||||||
@@ -117,7 +123,7 @@ const Component = () => {
|
|||||||
|
|
||||||
return new L.Icon({
|
return new L.Icon({
|
||||||
iconUrl: icon,
|
iconUrl: icon,
|
||||||
iconAnchor: [24, 42]
|
iconAnchor: [24, 42],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,8 +132,8 @@ const Component = () => {
|
|||||||
center={center}
|
center={center}
|
||||||
zoom={zoom}
|
zoom={zoom}
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
height: '900px'
|
height: "900px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TileLayer
|
<TileLayer
|
||||||
@@ -146,23 +152,23 @@ const Component = () => {
|
|||||||
>
|
>
|
||||||
<Popup>
|
<Popup>
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<p className={classes.markerTitle}><b>{marker[2]}</b></p>
|
<p className={classes.markerTitle}>
|
||||||
|
<b>{marker[2]}</b>
|
||||||
|
</p>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={e => selectCar(e, marker[2])}
|
onClick={(e) => selectCar(e, marker[2])}
|
||||||
>
|
>
|
||||||
View Stats
|
View Stats
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Popup>
|
</Popup>
|
||||||
</Marker>
|
</Marker>
|
||||||
))
|
))}
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{carState ? (
|
||||||
carState ? (
|
|
||||||
<VehiclePopUp
|
<VehiclePopUp
|
||||||
key={carState.vin}
|
key={carState.vin}
|
||||||
vin={carState.vin}
|
vin={carState.vin}
|
||||||
@@ -174,9 +180,8 @@ const Component = () => {
|
|||||||
className={classes.popup}
|
className={classes.popup}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
/>
|
/>
|
||||||
) : null
|
) : null}
|
||||||
}
|
</MapContainer>
|
||||||
</MapContainer >
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -192,12 +197,12 @@ const CenterFocus = ({ center, zoom }) => {
|
|||||||
}, [center, zoom, map]);
|
}, [center, zoom, map]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
};
|
||||||
|
|
||||||
const VehicleMap = () => (
|
const VehicleMap = () => (
|
||||||
<VehicleProvider>
|
<VehicleProvider>
|
||||||
<Component />
|
<Component />
|
||||||
</VehicleProvider>
|
</VehicleProvider>
|
||||||
)
|
);
|
||||||
|
|
||||||
export default VehicleMap;
|
export default VehicleMap;
|
||||||
|
|||||||
@@ -262,6 +262,9 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
},
|
},
|
||||||
tableSize: { height: 700, width: "100%" },
|
tableSize: { height: 700, width: "100%" },
|
||||||
whiteBackground: { backgroundColor: "White" },
|
whiteBackground: { backgroundColor: "White" },
|
||||||
|
progressIcon: { width: 40, height: 40 },
|
||||||
|
progressSuccess: { color: "green" },
|
||||||
|
progressError: { color: "red" },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default useStyles;
|
export default useStyles;
|
||||||
|
|||||||
@@ -7,42 +7,6 @@ const updatesAPI = {
|
|||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
getPackages: async (search, token) => {
|
|
||||||
return {
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
package_name: "Test",
|
|
||||||
version: "1.0",
|
|
||||||
link: "http://cloudfront.com/download",
|
|
||||||
ecu_list: "ECU1 1.0.0,ECU2 1.0.2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
package_name: "Test",
|
|
||||||
version: "1.1",
|
|
||||||
link: "http://cloudfront.com/download",
|
|
||||||
ecu_list: "ECU1 1.0.1,ECU2 1.0.2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
package_name: "Test",
|
|
||||||
version: "1.2",
|
|
||||||
link: "http://cloudfront.com/download",
|
|
||||||
ecu_list: "ECU1 1.1.0,ECU2 1.1.2",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updatePackage: async (data, token) => {
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
|
|
||||||
deployPackage: async (data, token) => {
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
|
|
||||||
getCarUpdates: async (filter, token) => {
|
getCarUpdates: async (filter, token) => {
|
||||||
return { data: [] };
|
return { data: [] };
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { fetchRespHandler } from "../utils/http"
|
import { fetchRespHandler } from "../utils/http"
|
||||||
|
import { logger } from "../services/monitoring";
|
||||||
|
|
||||||
const API_ENDPOINT = "https://grafana.fiskerdps.com/api/datasources/proxy/2"
|
const API_ENDPOINT = "https://grafana.fiskerdps.com/api/datasources/proxy/2"
|
||||||
|
|
||||||
@@ -9,7 +10,7 @@ const grafanaAPI = {
|
|||||||
})
|
})
|
||||||
.then(fetchRespHandler)
|
.then(fetchRespHandler)
|
||||||
.then(result => result.data[0].count)
|
.then(result => result.data[0].count)
|
||||||
.catch(error => console.log(error)),
|
.catch(error => logger.warn(error.stack)),
|
||||||
|
|
||||||
getSignalsCount: async () => fetch(`${API_ENDPOINT}/?query=SELECT%20count()%20as%20count%0AFROM%20default.vehicle_signal%20FORMAT%20JSON`, {
|
getSignalsCount: async () => fetch(`${API_ENDPOINT}/?query=SELECT%20count()%20as%20count%0AFROM%20default.vehicle_signal%20FORMAT%20JSON`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -17,7 +18,7 @@ const grafanaAPI = {
|
|||||||
})
|
})
|
||||||
.then(fetchRespHandler)
|
.then(fetchRespHandler)
|
||||||
.then(result => result.data[0].count)
|
.then(result => result.data[0].count)
|
||||||
.catch(error => console.log(error)),
|
.catch(error => logger.warn(error.stack)),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default grafanaAPI;
|
export default grafanaAPI;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const manifestsAPI = {
|
|||||||
.then(fetchRespHandler),
|
.then(fetchRespHandler),
|
||||||
|
|
||||||
getManifests: async (search, token) => {
|
getManifests: async (search, token) => {
|
||||||
var u = addQueryParams(`${API_ENDPOINT}/manifests`, search);
|
const u = addQueryParams(`${API_ENDPOINT}/manifests`, search);
|
||||||
return fetch(u, {
|
return fetch(u, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: Object.assign({ "Content-Type": "application/json" }, getAuthHeaderOptions(token)),
|
headers: Object.assign({ "Content-Type": "application/json" }, getAuthHeaderOptions(token)),
|
||||||
|
|||||||
@@ -1,22 +1,9 @@
|
|||||||
import { datadogRum } from '@datadog/browser-rum';
|
|
||||||
import { datadogLogs } from "@datadog/browser-logs";
|
import { datadogLogs } from "@datadog/browser-logs";
|
||||||
|
|
||||||
const applicationId = '8ecd160c-ad5c-4e06-8d88-3a6b89833246';
|
|
||||||
const clientToken = 'pubeb25449bb91773fc993855c7378e375a';
|
const clientToken = 'pubeb25449bb91773fc993855c7378e375a';
|
||||||
const site = 'datadoghq.com';
|
const site = 'datadoghq.com';
|
||||||
const service = 'ota-portal';
|
const service = 'ota-portal';
|
||||||
|
|
||||||
datadogRum.init({
|
|
||||||
applicationId,
|
|
||||||
clientToken,
|
|
||||||
site,
|
|
||||||
service,
|
|
||||||
// Specify a version number to identify the deployed version of your application in Datadog
|
|
||||||
// version: '1.0.0',
|
|
||||||
sampleRate: 100,
|
|
||||||
trackInteractions: true
|
|
||||||
});
|
|
||||||
|
|
||||||
datadogLogs.init({
|
datadogLogs.init({
|
||||||
clientToken,
|
clientToken,
|
||||||
site,
|
site,
|
||||||
|
|||||||
@@ -10,41 +10,8 @@ const updatesAPI = {
|
|||||||
})
|
})
|
||||||
.then(fetchRespHandler),
|
.then(fetchRespHandler),
|
||||||
|
|
||||||
deletePackage: async (package_id, token) => fetch(`${API_ENDPOINT}/update?id=${package_id}`, {
|
getCarUpdateLog: async (query, token) => {
|
||||||
method: "DELETE",
|
const u = addQueryParams(`${API_ENDPOINT}/carupdateslog`, query);
|
||||||
headers: Object.assign({ "Content-Type": "application/json" }, getAuthHeaderOptions(token)),
|
|
||||||
})
|
|
||||||
.then(fetchRespHandler),
|
|
||||||
|
|
||||||
getPackages: async (search, token) => {
|
|
||||||
var u = addQueryParams(`${API_ENDPOINT}/updates`, search);
|
|
||||||
return fetch(u, {
|
|
||||||
method: "GET",
|
|
||||||
headers: Object.assign({ "Content-Type": "application/json" }, getAuthHeaderOptions(token)),
|
|
||||||
|
|
||||||
})
|
|
||||||
.then(fetchRespHandler);
|
|
||||||
},
|
|
||||||
|
|
||||||
updatePackage: async (update, token) => fetch(`${API_ENDPOINT}/update`, {
|
|
||||||
method: "PUT",
|
|
||||||
headers: Object.assign({ "Content-Type": "application/json" }, getAuthHeaderOptions(token)),
|
|
||||||
body: JSON.stringify(update),
|
|
||||||
})
|
|
||||||
.then(fetchRespHandler),
|
|
||||||
|
|
||||||
|
|
||||||
getCarUpdates: async (search, token) => {
|
|
||||||
var u = addQueryParams(`${API_ENDPOINT}/carupdates`, search);
|
|
||||||
return fetch(u, {
|
|
||||||
method: "GET",
|
|
||||||
headers: Object.assign({ "Content-Type": "application/json" }, getAuthHeaderOptions(token)),
|
|
||||||
})
|
|
||||||
.then(fetchRespHandler);
|
|
||||||
},
|
|
||||||
|
|
||||||
getVINUpdates: async (vin, token) => {
|
|
||||||
var u = addQueryParams(`${API_ENDPOINT}/carupdates`, { vin });
|
|
||||||
return fetch(u, {
|
return fetch(u, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: Object.assign({ "Content-Type": "application/json" }, getAuthHeaderOptions(token)),
|
headers: Object.assign({ "Content-Type": "application/json" }, getAuthHeaderOptions(token)),
|
||||||
@@ -53,7 +20,25 @@ const updatesAPI = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
getCarUpdateProgress: async (carupdateids, token) => {
|
getCarUpdateProgress: async (carupdateids, token) => {
|
||||||
var u = `${API_ENDPOINT}/carupdatesstatuses?carupdateids=${carupdateids}`;
|
const u = `${API_ENDPOINT}/carupdatesstatuses?carupdateids=${carupdateids}`;
|
||||||
|
return fetch(u, {
|
||||||
|
method: "GET",
|
||||||
|
headers: Object.assign({ "Content-Type": "application/json" }, getAuthHeaderOptions(token)),
|
||||||
|
})
|
||||||
|
.then(fetchRespHandler);
|
||||||
|
},
|
||||||
|
|
||||||
|
getCarUpdates: async (search, token) => {
|
||||||
|
const u = addQueryParams(`${API_ENDPOINT}/carupdates`, search);
|
||||||
|
return fetch(u, {
|
||||||
|
method: "GET",
|
||||||
|
headers: Object.assign({ "Content-Type": "application/json" }, getAuthHeaderOptions(token)),
|
||||||
|
})
|
||||||
|
.then(fetchRespHandler);
|
||||||
|
},
|
||||||
|
|
||||||
|
getVINUpdates: async (vin, token) => {
|
||||||
|
const u = addQueryParams(`${API_ENDPOINT}/carupdates`, { vin });
|
||||||
return fetch(u, {
|
return fetch(u, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: Object.assign({ "Content-Type": "application/json" }, getAuthHeaderOptions(token)),
|
headers: Object.assign({ "Content-Type": "application/json" }, getAuthHeaderOptions(token)),
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const fetchRespHandler = (response) => {
|
|||||||
export const addQueryParams = (url, params) => {
|
export const addQueryParams = (url, params) => {
|
||||||
if (!params) return url;
|
if (!params) return url;
|
||||||
|
|
||||||
var u = new URL(url);
|
const u = new URL(url);
|
||||||
|
|
||||||
Object.keys(params).forEach(key => u.searchParams.append(key, params[key]))
|
Object.keys(params).forEach(key => u.searchParams.append(key, params[key]))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user