Merge pull request #66 from Fisker-Inc/car-location-visualizer

CEC-337: car location visualizer
This commit is contained in:
Drew Taylor
2021-07-22 11:53:11 -07:00
committed by GitHub
31 changed files with 1123 additions and 509 deletions

8
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1,8 @@
# default codeowners
* jwu@fiskerinc.com dtaylor@fiskerinc.com ggetsin@fiskerinc.com
# devops
.github rgreenberg@fiskerinc.com jwu@fiskerinc.com dtaylor@fiskerinc.com ggetsin@fiskerinc.com
Jenkinsfile rgreenberg@fiskerinc.com jwu@fiskerinc.com dtaylor@fiskerinc.com ggetsin@fiskerinc.com
k8s rgreenberg@fiskerinc.com jwu@fiskerinc.com dtaylor@fiskerinc.com ggetsin@fiskerinc.com
Dockerfile rgreenberg@fiskerinc.com jwu@fiskerinc.com dtaylor@fiskerinc.com ggetsin@fiskerinc.com

View File

@@ -9,7 +9,7 @@ jobs:
strategy: strategy:
matrix: matrix:
node-version: [12.x] node-version: [14.x]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

View File

@@ -1,4 +1,4 @@
FROM node:12-alpine as builder FROM node:14-alpine as builder
COPY package*.json ./ COPY package*.json ./
RUN npm install RUN npm install

View File

@@ -1,4 +1,4 @@
FROM node:12-alpine as builder FROM node:14-alpine as builder
COPY package*.json ./ COPY package*.json ./
RUN npm install RUN npm install

View File

@@ -6,7 +6,7 @@ Front-end web application for administrating services
Running locally Running locally
1. Install Node 12 1. Install Node 14
2. Run `npm install` 2. Run `npm install`
3. Copy .env.template to .env and edit the service urls for authentication and api services 3. Copy .env.template to .env and edit the service urls for authentication and api services
4. Run `./run.sh` from the terminal 4. Run `./run.sh` from the terminal

206
package-lock.json generated
View File

@@ -1275,9 +1275,9 @@
"integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==" "integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg=="
}, },
"@datadog/browser-core": { "@datadog/browser-core": {
"version": "2.15.0", "version": "2.17.0",
"resolved": "https://registry.npmjs.org/@datadog/browser-core/-/browser-core-2.15.0.tgz", "resolved": "https://registry.npmjs.org/@datadog/browser-core/-/browser-core-2.17.0.tgz",
"integrity": "sha512-qWTAysGYQXVpM5FOdstaqIF6B99nyQ2N/rJsi1ruPgFmU9yMM9tRdvqiJ7NZcy+OOsZWiinvFRFUMv9SOsHeUA==", "integrity": "sha512-jGIiVIzxdfQ+FnuFY+tC/j4gBFmK74xW/NIVqHIdN/hbGqA0oXOYs3Qof39ILLhXM5+zxa44RYFY6r/9JaxMIg==",
"requires": { "requires": {
"tslib": "^1.10.0" "tslib": "^1.10.0"
}, },
@@ -1290,22 +1290,14 @@
} }
}, },
"@datadog/browser-logs": { "@datadog/browser-logs": {
"version": "2.15.0", "version": "2.17.0",
"resolved": "https://registry.npmjs.org/@datadog/browser-logs/-/browser-logs-2.15.0.tgz", "resolved": "https://registry.npmjs.org/@datadog/browser-logs/-/browser-logs-2.17.0.tgz",
"integrity": "sha512-kZb0nKx1XoZibs3nkBwcP2iff23kX11yUcQj0aXHLL4yulO5I0AqEhoZXwVFfG8KP82sflX+uyssdNIUwMr1cg==", "integrity": "sha512-2S6ryflc28EErDJ+SgWo2OdkvWQ5KA5uqzzvbcnEBeFQpAV5ukAIfElHLiQrwSF4J6NkfLFA3tLt6KPGZE5F2w==",
"requires": { "requires": {
"@datadog/browser-core": "2.15.0", "@datadog/browser-core": "2.17.0",
"tslib": "^1.10.0" "tslib": "^1.10.0"
}, },
"dependencies": { "dependencies": {
"@datadog/browser-core": {
"version": "2.15.0",
"resolved": "https://registry.npmjs.org/@datadog/browser-core/-/browser-core-2.15.0.tgz",
"integrity": "sha512-qWTAysGYQXVpM5FOdstaqIF6B99nyQ2N/rJsi1ruPgFmU9yMM9tRdvqiJ7NZcy+OOsZWiinvFRFUMv9SOsHeUA==",
"requires": {
"tslib": "^1.10.0"
}
},
"tslib": { "tslib": {
"version": "1.14.1", "version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
@@ -1314,15 +1306,23 @@
} }
}, },
"@datadog/browser-rum": { "@datadog/browser-rum": {
"version": "2.15.0", "version": "2.18.0",
"resolved": "https://registry.npmjs.org/@datadog/browser-rum/-/browser-rum-2.15.0.tgz", "resolved": "https://registry.npmjs.org/@datadog/browser-rum/-/browser-rum-2.18.0.tgz",
"integrity": "sha512-E9PmGHxGQEdn8SUA7DmUu2mf/ifWGXLuGm95Hes/+dqoXbIPryFdmPCFnHaVF2nZNIA7wwW23oqe60KKo2Qjaw==", "integrity": "sha512-cfji5LdYJl1EpDhDZS67cpvgF6mC+qHSKRDxHQmqYurZW8R97wRhvrfpQWpDrKnl46MCvdQ76qDpSk9AVqK1zg==",
"requires": { "requires": {
"@datadog/browser-core": "2.15.0", "@datadog/browser-core": "2.18.0",
"@datadog/browser-rum-core": "2.15.0", "@datadog/browser-rum-core": "2.18.0",
"tslib": "^1.10.0" "tslib": "^1.10.0"
}, },
"dependencies": { "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": { "tslib": {
"version": "1.14.1", "version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
@@ -1331,14 +1331,22 @@
} }
}, },
"@datadog/browser-rum-core": { "@datadog/browser-rum-core": {
"version": "2.15.0", "version": "2.18.0",
"resolved": "https://registry.npmjs.org/@datadog/browser-rum-core/-/browser-rum-core-2.15.0.tgz", "resolved": "https://registry.npmjs.org/@datadog/browser-rum-core/-/browser-rum-core-2.18.0.tgz",
"integrity": "sha512-XXEe3JpSyvSvYXpXz/MgrVqs5Rl4Zu2eJXmHfxafAxb3i+VxyA6vc/pLnXPaKeWVcO489MpBEr6Gv7HiOEFZNA==", "integrity": "sha512-HzdoQltOdIkEEDiZj7r0kHDa/bw6WIBfKPQIv680arRXwhIrvaguwWUiJFTEXI865NA5ouSwGMpPm8Xgrb3B5g==",
"requires": { "requires": {
"@datadog/browser-core": "2.15.0", "@datadog/browser-core": "2.18.0",
"tslib": "^1.10.0" "tslib": "^1.10.0"
}, },
"dependencies": { "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": { "tslib": {
"version": "1.14.1", "version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
@@ -1768,13 +1776,13 @@
} }
}, },
"@material-ui/core": { "@material-ui/core": {
"version": "4.11.4", "version": "4.12.1",
"resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.11.4.tgz", "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.12.1.tgz",
"integrity": "sha512-oqb+lJ2Dl9HXI9orc6/aN8ZIAMkeThufA5iZELf2LQeBn2NtjVilF5D2w7e9RpntAzDb4jK5DsVhkfOvFY/8fg==", "integrity": "sha512-C6hYsjkWCTfBx9FaqxhCZCITBagh7fyCKFtHyvO3tTOcBw6NJaktdhNZ2n82jQdQdgfFvg6OOxi7OOzsAdAcBQ==",
"requires": { "requires": {
"@babel/runtime": "^7.4.4", "@babel/runtime": "^7.4.4",
"@material-ui/styles": "^4.11.4", "@material-ui/styles": "^4.11.4",
"@material-ui/system": "^4.11.3", "@material-ui/system": "^4.12.1",
"@material-ui/types": "5.1.0", "@material-ui/types": "5.1.0",
"@material-ui/utils": "^4.11.2", "@material-ui/utils": "^4.11.2",
"@types/react-transition-group": "^4.2.0", "@types/react-transition-group": "^4.2.0",
@@ -1818,9 +1826,9 @@
} }
}, },
"@material-ui/system": { "@material-ui/system": {
"version": "4.11.3", "version": "4.12.1",
"resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.11.3.tgz", "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.12.1.tgz",
"integrity": "sha512-SY7otguNGol41Mu2Sg6KbBP1ZRFIbFLHGK81y4KYbsV2yIcaEPOmsCK6zwWlp+2yTV3J/VwT6oSBARtGIVdXPw==", "integrity": "sha512-lUdzs4q9kEXZGhbN7BptyiS1rLNHe6kG9o8Y307HCvF4sQxbCgpL2qi+gUk+yI8a2DNk48gISEQxoxpgph0xIw==",
"requires": { "requires": {
"@babel/runtime": "^7.4.4", "@babel/runtime": "^7.4.4",
"@material-ui/utils": "^4.11.2", "@material-ui/utils": "^4.11.2",
@@ -1902,6 +1910,11 @@
} }
} }
}, },
"@react-leaflet/core": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-1.1.0.tgz",
"integrity": "sha512-zFxMHgfjCi7khRVB7o7H8NoJl36NaezvfcaeEurVXx22lAGHFlTHiSuLOGA4tOiHj+Ep+Lo3uwUGJ3YM9BGkHg=="
},
"@rollup/plugin-node-resolve": { "@rollup/plugin-node-resolve": {
"version": "7.1.3", "version": "7.1.3",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz",
@@ -2139,9 +2152,9 @@
"integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==" "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA=="
}, },
"@types/aria-query": { "@types/aria-query": {
"version": "4.2.1", "version": "4.2.2",
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.1.tgz", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz",
"integrity": "sha512-S6oPal772qJZHoRZLFc/XoZW2gFvwXusYUmXPXkgxJLuEk2vOt7jc4Yo6z/vtI0EBkbPBVrJJ0B+prLIKiWqHg==" "integrity": "sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig=="
}, },
"@types/babel__core": { "@types/babel__core": {
"version": "7.1.14", "version": "7.1.14",
@@ -2238,9 +2251,9 @@
} }
}, },
"@types/jest": { "@types/jest": {
"version": "26.0.23", "version": "26.0.24",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.23.tgz", "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.24.tgz",
"integrity": "sha512-ZHLmWMJ9jJ9PTiT58juykZpL7KjwJywFN3Rr2pTSkyQfydf/rk22yS7W8p5DaVUMQ2BQC7oYiU3FjbTM/mYrOA==", "integrity": "sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w==",
"requires": { "requires": {
"jest-diff": "^26.0.0", "jest-diff": "^26.0.0",
"pretty-format": "^26.0.0" "pretty-format": "^26.0.0"
@@ -2282,9 +2295,9 @@
"integrity": "sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA==" "integrity": "sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA=="
}, },
"@types/prop-types": { "@types/prop-types": {
"version": "15.7.3", "version": "15.7.4",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz",
"integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ=="
}, },
"@types/q": { "@types/q": {
"version": "1.5.4", "version": "1.5.4",
@@ -2292,9 +2305,9 @@
"integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==" "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug=="
}, },
"@types/react": { "@types/react": {
"version": "17.0.5", "version": "17.0.14",
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.5.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.14.tgz",
"integrity": "sha512-bj4biDB9ZJmGAYTWSKJly6bMr4BLUiBrx9ujiJEoP9XIDY9CTaPGxE5QWN/1WjpPLzYF7/jRNnV2nNxNe970sw==", "integrity": "sha512-0WwKHUbWuQWOce61UexYuWTGuGY/8JvtUe/dtQ6lR4sZ3UiylHotJeWpf3ArP9+DSGUoLY3wbU59VyMrJps5VQ==",
"requires": { "requires": {
"@types/prop-types": "*", "@types/prop-types": "*",
"@types/scheduler": "*", "@types/scheduler": "*",
@@ -2309,9 +2322,9 @@
} }
}, },
"@types/react-transition-group": { "@types/react-transition-group": {
"version": "4.4.1", "version": "4.4.2",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.1.tgz", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.2.tgz",
"integrity": "sha512-vIo69qKKcYoJ8wKCJjwSgCTM+z3chw3g18dkrDfVX665tMH7tmbDxEAnPdey4gTlwZz5QuHGzd+hul0OVZDqqQ==", "integrity": "sha512-KibDWL6nshuOJ0fu8ll7QnV/LVTo3PzQ9aCPnRUYPfX7eZohHwLIdNHj7pftanREzHNP4/nJa8oeM73uSiavMQ==",
"requires": { "requires": {
"@types/react": "*" "@types/react": "*"
} }
@@ -2325,9 +2338,9 @@
} }
}, },
"@types/scheduler": { "@types/scheduler": {
"version": "0.16.1", "version": "0.16.2",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
"integrity": "sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==" "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
}, },
"@types/source-list-map": { "@types/source-list-map": {
"version": "0.1.2", "version": "0.1.2",
@@ -7659,14 +7672,6 @@
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
}, },
"indefinite-observable": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/indefinite-observable/-/indefinite-observable-2.0.1.tgz",
"integrity": "sha512-G8vgmork+6H9S8lUAg1gtXEj2JxIQTo0g2PbFiYOdjkziSI0F7UYBiVwhZRuixhBCNGczAls34+5HJPyZysvxQ==",
"requires": {
"symbol-observable": "1.2.0"
}
},
"indent-string": { "indent-string": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
@@ -9149,13 +9154,12 @@
} }
}, },
"jss": { "jss": {
"version": "10.6.0", "version": "10.7.1",
"resolved": "https://registry.npmjs.org/jss/-/jss-10.6.0.tgz", "resolved": "https://registry.npmjs.org/jss/-/jss-10.7.1.tgz",
"integrity": "sha512-n7SHdCozmxnzYGXBHe0NsO0eUf9TvsHVq2MXvi4JmTn3x5raynodDVE/9VQmBdWFyyj9HpHZ2B4xNZ7MMy7lkw==", "integrity": "sha512-5QN8JSVZR6cxpZNeGfzIjqPEP+ZJwJJfZbXmeABNdxiExyO+eJJDy6WDtqTf8SDKnbL5kZllEpAP71E/Lt7PXg==",
"requires": { "requires": {
"@babel/runtime": "^7.3.1", "@babel/runtime": "^7.3.1",
"csstype": "^3.0.2", "csstype": "^3.0.2",
"indefinite-observable": "^2.0.1",
"is-in-browser": "^1.1.3", "is-in-browser": "^1.1.3",
"tiny-warning": "^1.0.2" "tiny-warning": "^1.0.2"
}, },
@@ -9168,70 +9172,70 @@
} }
}, },
"jss-plugin-camel-case": { "jss-plugin-camel-case": {
"version": "10.6.0", "version": "10.7.1",
"resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.6.0.tgz", "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.7.1.tgz",
"integrity": "sha512-JdLpA3aI/npwj3nDMKk308pvnhoSzkW3PXlbgHAzfx0yHWnPPVUjPhXFtLJzgKZge8lsfkUxvYSQ3X2OYIFU6A==", "integrity": "sha512-+ioIyWvmAfgDCWXsQcW1NMnLBvRinOVFkSYJUgewQ6TynOcSj5F1bSU23B7z0p1iqK0PPHIU62xY1iNJD33WGA==",
"requires": { "requires": {
"@babel/runtime": "^7.3.1", "@babel/runtime": "^7.3.1",
"hyphenate-style-name": "^1.0.3", "hyphenate-style-name": "^1.0.3",
"jss": "10.6.0" "jss": "10.7.1"
} }
}, },
"jss-plugin-default-unit": { "jss-plugin-default-unit": {
"version": "10.6.0", "version": "10.7.1",
"resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.6.0.tgz", "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.7.1.tgz",
"integrity": "sha512-7y4cAScMHAxvslBK2JRK37ES9UT0YfTIXWgzUWD5euvR+JR3q+o8sQKzBw7GmkQRfZijrRJKNTiSt1PBsLI9/w==", "integrity": "sha512-tW+dfYVNARBQb/ONzBwd8uyImigyzMiAEDai+AbH5rcHg5h3TtqhAkxx06iuZiT/dZUiFdSKlbe3q9jZGAPIwA==",
"requires": { "requires": {
"@babel/runtime": "^7.3.1", "@babel/runtime": "^7.3.1",
"jss": "10.6.0" "jss": "10.7.1"
} }
}, },
"jss-plugin-global": { "jss-plugin-global": {
"version": "10.6.0", "version": "10.7.1",
"resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.6.0.tgz", "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.7.1.tgz",
"integrity": "sha512-I3w7ji/UXPi3VuWrTCbHG9rVCgB4yoBQLehGDTmsnDfXQb3r1l3WIdcO8JFp9m0YMmyy2CU7UOV6oPI7/Tmu+w==", "integrity": "sha512-FbxCnu44IkK/bw8X3CwZKmcAnJqjAb9LujlAc/aP0bMSdVa3/MugKQRyeQSu00uGL44feJJDoeXXiHOakBr/Zw==",
"requires": { "requires": {
"@babel/runtime": "^7.3.1", "@babel/runtime": "^7.3.1",
"jss": "10.6.0" "jss": "10.7.1"
} }
}, },
"jss-plugin-nested": { "jss-plugin-nested": {
"version": "10.6.0", "version": "10.7.1",
"resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.6.0.tgz", "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.7.1.tgz",
"integrity": "sha512-fOFQWgd98H89E6aJSNkEh2fAXquC9aZcAVjSw4q4RoQ9gU++emg18encR4AT4OOIFl4lQwt5nEyBBRn9V1Rk8g==", "integrity": "sha512-RNbICk7FlYKaJyv9tkMl7s6FFfeLA3ubNIFKvPqaWtADK0KUaPsPXVYBkAu4x1ItgsWx67xvReMrkcKA0jSXfA==",
"requires": { "requires": {
"@babel/runtime": "^7.3.1", "@babel/runtime": "^7.3.1",
"jss": "10.6.0", "jss": "10.7.1",
"tiny-warning": "^1.0.2" "tiny-warning": "^1.0.2"
} }
}, },
"jss-plugin-props-sort": { "jss-plugin-props-sort": {
"version": "10.6.0", "version": "10.7.1",
"resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.6.0.tgz", "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.7.1.tgz",
"integrity": "sha512-oMCe7hgho2FllNc60d9VAfdtMrZPo9n1Iu6RNa+3p9n0Bkvnv/XX5San8fTPujrTBScPqv9mOE0nWVvIaohNuw==", "integrity": "sha512-eyd5FhA+J0QrpqXxO7YNF/HMSXXl4pB0EmUdY4vSJI4QG22F59vQ6AHtP6fSwhmBdQ98Qd9gjfO+RMxcE39P1A==",
"requires": { "requires": {
"@babel/runtime": "^7.3.1", "@babel/runtime": "^7.3.1",
"jss": "10.6.0" "jss": "10.7.1"
} }
}, },
"jss-plugin-rule-value-function": { "jss-plugin-rule-value-function": {
"version": "10.6.0", "version": "10.7.1",
"resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.6.0.tgz", "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.7.1.tgz",
"integrity": "sha512-TKFqhRTDHN1QrPTMYRlIQUOC2FFQb271+AbnetURKlGvRl/eWLswcgHQajwuxI464uZk91sPiTtdGi7r7XaWfA==", "integrity": "sha512-fGAAImlbaHD3fXAHI3ooX6aRESOl5iBt3LjpVjxs9II5u9tzam7pqFUmgTcrip9VpRqYHn8J3gA7kCtm8xKwHg==",
"requires": { "requires": {
"@babel/runtime": "^7.3.1", "@babel/runtime": "^7.3.1",
"jss": "10.6.0", "jss": "10.7.1",
"tiny-warning": "^1.0.2" "tiny-warning": "^1.0.2"
} }
}, },
"jss-plugin-vendor-prefixer": { "jss-plugin-vendor-prefixer": {
"version": "10.6.0", "version": "10.7.1",
"resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.6.0.tgz", "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.7.1.tgz",
"integrity": "sha512-doJ7MouBXT1lypLLctCwb4nJ6lDYqrTfVS3LtXgox42Xz0gXusXIIDboeh6UwnSmox90QpVnub7au8ybrb0krQ==", "integrity": "sha512-1UHFmBn7hZNsHXTkLLOL8abRl8vi+D1EVzWD4WmLFj55vawHZfnH1oEz6TUf5Y61XHv0smdHabdXds6BgOXe3A==",
"requires": { "requires": {
"@babel/runtime": "^7.3.1", "@babel/runtime": "^7.3.1",
"css-vendor": "^2.0.8", "css-vendor": "^2.0.8",
"jss": "10.6.0" "jss": "10.7.1"
} }
}, },
"jsx-ast-utils": { "jsx-ast-utils": {
@@ -9285,6 +9289,11 @@
"webpack-sources": "^1.1.0" "webpack-sources": "^1.1.0"
} }
}, },
"leaflet": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.7.1.tgz",
"integrity": "sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw=="
},
"leven": { "leven": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
@@ -12154,6 +12163,14 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz",
"integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==" "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA=="
}, },
"react-leaflet": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-3.2.0.tgz",
"integrity": "sha512-eHVqoRGjW8T9GxLt7jyTKP3BDQ7XQ5AD+tc/zkbaABn1dbmREDy8GojNcYjZQa3QFLQoOLQMcUC1PTtzytZpUA==",
"requires": {
"@react-leaflet/core": "^1.1.0"
}
},
"react-refresh": { "react-refresh": {
"version": "0.8.3", "version": "0.8.3",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz",
@@ -12294,9 +12311,9 @@
} }
}, },
"react-transition-group": { "react-transition-group": {
"version": "4.4.1", "version": "4.4.2",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz",
"integrity": "sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==", "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==",
"requires": { "requires": {
"@babel/runtime": "^7.5.5", "@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1", "dom-helpers": "^5.0.1",
@@ -14130,11 +14147,6 @@
} }
} }
}, },
"symbol-observable": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
},
"symbol-tree": { "symbol-tree": {
"version": "3.2.4", "version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",

View File

@@ -3,18 +3,20 @@
"version": "0.1.1", "version": "0.1.1",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@datadog/browser-logs": "^2.15.0", "@datadog/browser-logs": "^2.17.0",
"@datadog/browser-rum": "^2.15.0", "@datadog/browser-rum": "^2.17.0",
"@material-ui/core": "^4.11.4", "@material-ui/core": "^4.12.1",
"@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": "^11.2.7",
"@testing-library/user-event": "^12.8.3", "@testing-library/user-event": "^12.8.3",
"axios": "^0.21.1", "axios": "^0.21.1",
"clsx": "^1.1.1", "clsx": "^1.1.1",
"leaflet": "^1.7.1",
"material-ui-dropzone": "^3.5.0", "material-ui-dropzone": "^3.5.0",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-leaflet": "^3.2.0",
"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": "^0.2.4"
@@ -32,18 +34,11 @@
"react-app/jest" "react-app/jest"
] ]
}, },
"browserslist": { "browserslist": [
"production": [
">0.2%", ">0.2%",
"not dead", "not dead",
"not op_mini all" "not op_mini all"
], ],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"engines": { "engines": {
"node": "12.20.1" "node": "12.20.1"
}, },

View File

@@ -1,22 +1,23 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<meta <meta name="description" content="Fisker Admin Portal" />
name="description"
content="Fisker Admin Portal"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" /> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" /> <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
<title>Fisker Admin Portal</title> <title>Fisker Admin Portal</title>
</head> </head>
<body>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div> <div id="root"></div>
</body> </body>
</html> </html>

BIN
src/assets/green-car.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 B

BIN
src/assets/red-car.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

View File

@@ -6,7 +6,7 @@ jest.mock("../Contexts/ManifestsContext");
jest.mock("../Contexts/CarUpdatesContext"); jest.mock("../Contexts/CarUpdatesContext");
jest.mock("../../services/monitoring"); jest.mock("../../services/monitoring");
import { render, screen, cleanup, waitForElementToBeRemoved } from "@testing-library/react"; import { render, screen, cleanup, waitFor, waitForElementToBeRemoved } from "@testing-library/react";
import { setToken } from "../Contexts/UserContext"; import { setToken } from "../Contexts/UserContext";
import { TEST_AUTH_OBJECT } from "../../utils/testing" import { TEST_AUTH_OBJECT } from "../../utils/testing"
import App from "."; import App from ".";
@@ -28,6 +28,13 @@ const check = async (path, selector, compare) => {
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}; };
const sleepAndCheck = async (path, selector, compare) => {
const container = await renderRoute(path);
await waitFor(() => { });
expect(container.querySelector(selector).innerHTML).toEqual(compare);
expect(container).toMatchSnapshot();
};
describe("App", () => { describe("App", () => {
beforeAll(() => { beforeAll(() => {
// Stablize Table Pagination control ids // Stablize Table Pagination control ids
@@ -107,12 +114,12 @@ describe("App", () => {
it("Route / authenticated", async () => { it("Route / authenticated", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT);
await check("/", "h1", "Welcome John!"); await sleepAndCheck("/", "h1", "Welcome John!");
}); });
it("Route /home authenticated", async () => { it("Route /home authenticated", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT);
await check("/home", "h1", "Welcome John!"); await sleepAndCheck("/home", "h1", "Welcome John!");
}); });
it("Route /package-upload authenticated", async () => { it("Route /package-upload authenticated", async () => {

File diff suppressed because it is too large Load Diff

View File

@@ -150,8 +150,8 @@ const MainForm = () => {
inputProps: { "aria-label": "rows per page" }, inputProps: { "aria-label": "rows per page" },
native: true, native: true,
}} }}
onChangePage={handleChangePageIndex} onPageChange={handleChangePageIndex}
onChangeRowsPerPage={handleChangePageSize} onRowsPerPageChange={handleChangePageSize}
/> />
</TableRow> </TableRow>
</TableFooter> </TableFooter>

View File

@@ -176,8 +176,8 @@ const CarSelectionTable = (props) => {
inputProps: { "aria-label": "rows per page" }, inputProps: { "aria-label": "rows per page" },
native: true, native: true,
}} }}
onChangePage={handleChangePageIndex} onPageChange={handleChangePageIndex}
onChangeRowsPerPage={handleChangePageSize} onRowsPerPageChange={handleChangePageSize}
/> />
</TableRow> </TableRow>
</TableFooter> </TableFooter>

View File

@@ -170,8 +170,8 @@ const MainForm = () => {
inputProps: { "aria-label": "rows per page" }, inputProps: { "aria-label": "rows per page" },
native: true, native: true,
}} }}
onChangePage={handleChangePageIndex} onPageChange={handleChangePageIndex}
onChangeRowsPerPage={handleChangePageSize} onRowsPerPageChange={handleChangePageSize}
/> />
</TableRow> </TableRow>
</TableFooter> </TableFooter>

View File

@@ -147,8 +147,8 @@ const MainForm = () => {
inputProps: { "aria-label": "rows per page" }, inputProps: { "aria-label": "rows per page" },
native: true, native: true,
}} }}
onChangePage={handleChangePageIndex} onPageChange={handleChangePageIndex}
onChangeRowsPerPage={handleChangePageSize} onRowsPerPageChange={handleChangePageSize}
/> />
</TableRow> </TableRow>
</TableFooter> </TableFooter>

View File

@@ -103,7 +103,7 @@ export const VehicleProvider = ({ children }) => {
const result = await api.getConnections(vins, token); const result = await api.getConnections(vins, token);
if (result.error) { if (result.error) {
throw new Error(`Get connections error. ${result.message}`); throw new Error(`Add connections error. ${result.message}`);
} }
cars.forEach((car) => { cars.forEach((car) => {
car.connected = result[car.vin] || false; car.connected = result[car.vin] || false;
@@ -125,6 +125,30 @@ export const VehicleProvider = ({ children }) => {
} }
}; };
const getLocations = async (token) => {
try {
setBusy(true);
const result = await api.getLocations(token);
if (result.error)
throw new Error(`Get locations error. ${result.message}`);
return result;
} finally {
setBusy(false);
}
};
const getState = async (token, vin) => {
try {
setBusy(true);
const result = await api.getState(token, vin);
if (result.error)
throw new Error(`Get state error. ${result.message}`);
return result;
} finally {
setBusy(false);
}
};
return ( return (
<VehicleContext.Provider <VehicleContext.Provider
value={{ value={{
@@ -139,6 +163,8 @@ export const VehicleProvider = ({ children }) => {
getYears, getYears,
sendCommand, sendCommand,
getConnections, getConnections,
getLocations,
getState,
}} }}
> >
{children} {children}

View File

@@ -39,6 +39,9 @@ export const useVehicleContext = () => ({
return result; return result;
}), }),
getLocations: jest.fn().mockResolvedValue([
{ "altitude": 5, "longitude": 10, "latitude": 15, "vin": "TESTVIN123" },
])
}); });
export const setBusy = (val) => { export const setBusy = (val) => {

View File

@@ -5,6 +5,7 @@ import useStyles from "../useStyles";
import { useUserContext } from "../Contexts/UserContext"; import { useUserContext } from "../Contexts/UserContext";
import { useStatusContext } from "../Contexts/StatusContext"; import { useStatusContext } from "../Contexts/StatusContext";
import { parsePayload } from "../../utils/jwt"; import { parsePayload } from "../../utils/jwt";
import VehicleMap from "../VehicleMap";
const DEFAULT_GREETING = "Welcome"; const DEFAULT_GREETING = "Welcome";
@@ -25,16 +26,17 @@ const Home = () => {
const greeting = getGreeting(token); const greeting = getGreeting(token);
const { setTitle } = useStatusContext(); const { setTitle } = useStatusContext();
useEffect(() => { useEffect(() => {
setTitle(""); setTitle("Home");
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
return ( return (
<div className={classes.paper}> <div className={classes.paper}>
<Typography component="h1" variant="h5"> <Typography className={classes.homePageTitle} component="h1" variant="h5">
{greeting} {greeting}
</Typography> </Typography>
<VehicleMap />
</div> </div>
); );
}; };

View File

@@ -14,7 +14,7 @@ const menuData = [
{ {
label: "Dashboard", label: "Dashboard",
to: "/dashboard", to: "/dashboard",
roles: [], roles: [Roles.CREATE, Roles.READ],
}, },
{ {
label: "Deploy Packages", label: "Deploy Packages",
@@ -45,7 +45,7 @@ const menuData = [
label: "Send Command", label: "Send Command",
to: "/vehicles-command", to: "/vehicles-command",
roles: [Roles.CREATE], roles: [Roles.CREATE],
}, }
]; ];
export default function SideMenu() { export default function SideMenu() {

View File

@@ -219,28 +219,6 @@ exports[`SideMenu Unauthenticated 1`] = `
/> />
</a> </a>
</li> </li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/dashboard"
role="button"
tabindex="0"
>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Dashboard
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
</ul> </ul>
</div> </div>
</div> </div>

View File

@@ -230,8 +230,8 @@ const MainForm = () => {
inputProps: { "aria-label": "rows per page" }, inputProps: { "aria-label": "rows per page" },
native: true, native: true,
}} }}
onChangePage={handleChangePageIndex} onPageChange={handleChangePageIndex}
onChangeRowsPerPage={handleChangePageSize} onRowsPerPageChange={handleChangePageSize}
/> />
</TableRow> </TableRow>
</TableFooter> </TableFooter>

View File

@@ -153,8 +153,8 @@ const MainForm = () => {
inputProps: { "aria-label": "rows per page" }, inputProps: { "aria-label": "rows per page" },
native: true, native: true,
}} }}
onChangePage={handleChangePageIndex} onPageChange={handleChangePageIndex}
onChangeRowsPerPage={handleChangePageSize} onRowsPerPageChange={handleChangePageSize}
/> />
</TableRow> </TableRow>
</TableFooter> </TableFooter>

View File

@@ -75,7 +75,7 @@ const HeaderSortable = (props) => {
<TableCell <TableCell
key={column.id} key={column.id}
align={column.numeric ? "right" : "center"} align={column.numeric ? "right" : "center"}
padding={column.disablePadding ? "none" : "default"} padding={column.disablePadding ? "none" : "normal"}
sortDirection={orderBy === column.id ? order : false} sortDirection={orderBy === column.id ? order : false}
> >
{ColumnLabel(column)} {ColumnLabel(column)}

View File

@@ -46,10 +46,10 @@ exports[`File Upload Form Should render 1`] = `
/> />
<fieldset <fieldset
aria-hidden="true" aria-hidden="true"
class="PrivateNotchedOutline-root-33 MuiOutlinedInput-notchedOutline" class="PrivateNotchedOutline-root-39 MuiOutlinedInput-notchedOutline"
> >
<legend <legend
class="PrivateNotchedOutline-legendLabelled-35" class="PrivateNotchedOutline-legendLabelled-41"
> >
<span> <span>
Package name Package name
@@ -92,10 +92,10 @@ exports[`File Upload Form Should render 1`] = `
/> />
<fieldset <fieldset
aria-hidden="true" aria-hidden="true"
class="PrivateNotchedOutline-root-33 MuiOutlinedInput-notchedOutline" class="PrivateNotchedOutline-root-39 MuiOutlinedInput-notchedOutline"
> >
<legend <legend
class="PrivateNotchedOutline-legendLabelled-35" class="PrivateNotchedOutline-legendLabelled-41"
> >
<span> <span>
Version Version
@@ -138,10 +138,10 @@ exports[`File Upload Form Should render 1`] = `
/> />
<fieldset <fieldset
aria-hidden="true" aria-hidden="true"
class="PrivateNotchedOutline-root-33 MuiOutlinedInput-notchedOutline" class="PrivateNotchedOutline-root-39 MuiOutlinedInput-notchedOutline"
> >
<legend <legend
class="PrivateNotchedOutline-legendLabelled-35" class="PrivateNotchedOutline-legendLabelled-41"
> >
<span> <span>
Description Description
@@ -185,10 +185,10 @@ exports[`File Upload Form Should render 1`] = `
/> />
<fieldset <fieldset
aria-hidden="true" aria-hidden="true"
class="PrivateNotchedOutline-root-33 MuiOutlinedInput-notchedOutline" class="PrivateNotchedOutline-root-39 MuiOutlinedInput-notchedOutline"
> >
<legend <legend
class="PrivateNotchedOutline-legendLabelled-35" class="PrivateNotchedOutline-legendLabelled-41"
> >
<span> <span>
Release Notes URL Release Notes URL

View File

@@ -225,8 +225,8 @@ const UpdatePackagesList = () => {
inputProps: { "aria-label": "rows per page" }, inputProps: { "aria-label": "rows per page" },
native: true, native: true,
}} }}
onChangePage={handleChangePageIndex} onPageChange={handleChangePageIndex}
onChangeRowsPerPage={handleChangePageSize} onRowsPerPageChange={handleChangePageSize}
/> />
</TableRow> </TableRow>
</TableFooter> </TableFooter>

View File

@@ -0,0 +1,208 @@
import React, { useEffect, useState } from "react";
import useStyles from "../useStyles";
import L from "leaflet";
import { MapContainer, TileLayer, Marker, Popup, useMap } from "react-leaflet";
import { Button } from "@material-ui/core";
import { useUserContext } from "../Contexts/UserContext";
import { useStatusContext } from "../Contexts/StatusContext";
import { useVehicleContext, VehicleProvider } from "../Contexts/VehicleContext";
import { VehiclePopUp } from "./popup";
import GreenCarIcon from "../../assets/green-car.png";
import RedCarIcon from "../../assets/red-car.png";
const Component = () => {
const classes = useStyles();
const { token } = useUserContext();
const { setTitle } = useStatusContext();
const { getConnections, getLocations, getState } = useVehicleContext();
const REQUEST_INTERVAL = 10000;
useEffect(() => {
setTitle("");
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const [center, setCenter] = useState([0, 0]);
const [zoom, setZoom] = useState(2);
const [markers, setMarkers] = useState([]);
useEffect(() => {
retrieveAndStoreLocations()
.then(points => {
centerAroundMarkers(points);
})
const id = setInterval(function () {
retrieveAndStoreLocations();
}, REQUEST_INTERVAL);
return () => { clearInterval(id) };
// eslint-disable-next-line
}, []);
const retrieveAndStoreLocations = () => {
return getLocations(token)
.then(result => {
if (result.data != null) {
const points = result.data.map(point => [point.latitude, point.longitude, point.vin]);
setMarkers(points);
return points
}
return []
})
.catch(error => console.log(error));
}
const centerAroundMarkers = (markers) => {
if (markers == null) {
markers = []
}
const coord = markers.reduce((coord, marker) => {
coord[0] += marker[0] / markers.length;
coord[1] += marker[1] / markers.length;
return coord;
}, [0, 0])
setCenter(coord);
setZoom(4);
}
const [connections, setConnections] = useState({});
useEffect(() => {
if (markers.length > 0) {
const vins = markers.map(marker => marker[2]);
getConnections(vins, token)
.then(connections => {
setConnections(connections);
})
}
// eslint-disable-next-line
}, [markers, token])
const [selectedVIN, setSelectedVIN] = useState(null);
const [carState, setCarState] = useState(null);
useEffect(() => {
if (selectedVIN != null) {
retrieveAndStoreCarState(selectedVIN);
const id = setInterval(function () {
retrieveAndStoreCarState(selectedVIN);
}, REQUEST_INTERVAL);
return () => { clearInterval(id) };
}
// eslint-disable-next-line
}, [selectedVIN]);
const selectCar = (e, vin) => {
e.preventDefault();
setSelectedVIN(vin);
}
const retrieveAndStoreCarState = (vin) => {
getState(token, vin)
.then(results => {
setCarState({ ...results.data, vin: vin });
});
}
const handleClose = () => {
setSelectedVIN(null);
setCarState(null);
};
function getCarIcon(vin) {
let icon = RedCarIcon;
if (connections[vin]) {
icon = GreenCarIcon;
} else {
icon = RedCarIcon;
}
return new L.Icon({
iconUrl: icon,
iconAnchor: [15, 0]
});
}
return (
<MapContainer
center={center}
zoom={zoom}
style={{
width: '100%',
height: '900px'
}}
>
<TileLayer
attribution='&copy <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<CenterFocus center={center} zoom={zoom} />
{markers.map((marker) => (
<Marker
icon={getCarIcon(marker[2])}
key={marker[2]}
position={[marker[0], marker[1]]}
title={marker[2]}
opacity={0.9}
>
<Popup>
<div align="center">
<p className={classes.markerTitle}><b>{marker[2]}</b></p>
<Button
type="submit"
variant="contained"
color="primary"
onClick={e => selectCar(e, marker[2])}
>
View Stats
</Button>
</div>
</Popup>
</Marker>
))
}
{
carState ? (
<VehiclePopUp
key={carState.vin}
vin={carState.vin}
online={carState.online}
battery={carState.battery}
doors={carState.doors}
location={carState.location}
windows={carState.windows}
className={classes.popup}
onClose={handleClose}
/>
) : null
}
</MapContainer >
);
};
const CenterFocus = ({ center, zoom }) => {
const map = useMap();
useEffect(() => {
if (center[0] === 0 && center[1] === 0) {
map.flyTo([0, 0], 2, { duration: 1.5 });
} else {
map.flyTo(center, zoom, { duration: 1.5 });
}
}, [center, zoom, map]);
return null;
}
const VehicleMap = () => (
<VehicleProvider>
<Component />
</VehicleProvider>
)
export default VehicleMap;

View File

@@ -0,0 +1,72 @@
import React from "react";
import Dialog from '@material-ui/core/Dialog';
import MuiDialogTitle from '@material-ui/core/DialogTitle';
import IconButton from '@material-ui/core/IconButton';
import CloseIcon from '@material-ui/icons/Close';
import Typography from '@material-ui/core/Typography';
import useStyles from "../useStyles";
const VehiclePopUp = (props) => {
const classes = useStyles();
const { vin, online, battery, doors, location, windows, onClose } = props;
return (
<Dialog
fullWidth
classes={{ paper: classes.popUp }}
open={true}
onClose={onClose}
>
<DialogTitle align="center" onClose={onClose}>{vin}</DialogTitle>
<div align="center" className={classes.popUpContent}>
<p><b>Connected</b>: {online.toString()}</p>
{online && (
<div>
{battery != null && (
<p><b>battery</b>: {battery.percent}%</p>
)}
{doors != null && (
<div>
<h3>Doors</h3>
{Object.entries(doors).map((value) => (<p key={value[0]}><b>{value[0]}</b>: {value[1] ? "open" : "closed"}</p>))}
</div>
)}
{windows != null && (
<div>
<h3>Windows</h3>
{Object.entries(windows).map((value) => (<p key={value[0]}><b>{value[0]}</b>: {value[1] ? "open" : "closed"}</p>))}
</div>
)}
{location != null && (
<div>
<h3>Location</h3>
{Object.entries(location).map((value) => (<p key={value[0]}><b>{value[0]}</b>: {value[1]}</p>))}
</div>
)}
</div>
)}
{(!online || (battery == null && doors == null && location == null && windows == null)) && (
<p>No vehicle data to display.</p>
)}
</div>
</Dialog >
);
};
const DialogTitle = (props) => {
const { children, onClose, ...other } = props;
const classes = useStyles();
return (
<MuiDialogTitle disableTypography className={classes.ppopUpTItle} {...other}>
<Typography variant="h6">{children}</Typography>
{onClose ? (
<IconButton aria-label="close" className={classes.closeButton} onClick={onClose}>
<CloseIcon />
</IconButton>
) : null}
</MuiDialogTitle>
);
};
export { VehiclePopUp };

View File

@@ -173,6 +173,31 @@ const useStyles = makeStyles((theme) => ({
width: "100%", width: "100%",
paddingTop: "56.25%", paddingTop: "56.25%",
}, },
closeButton: {
position: 'absolute',
right: theme.spacing(1),
top: theme.spacing(1),
color: theme.palette.grey[500],
},
homePageTitle: {
marginBottom: 25,
},
markerTitle: {
margin: 5,
},
popUp: {
minHeight: "15vh",
},
popUpTitle: {
margin: 0,
padding: theme.spacing(2),
},
popUpContent: {
"& p": {
margin: 0,
},
paddingBottom: "2vh",
},
})); }));
export default useStyles; export default useStyles;

View File

@@ -38,7 +38,10 @@ const vehiclesAPI = {
}); });
return result; return result;
} },
getLocations: jest.fn().mockResolvedValue([
{ "altitude": 5, "longitude": 10, "latitude": 15, "vin": "TESTVIN123" },
]),
}; };
export default vehiclesAPI; export default vehiclesAPI;

View File

@@ -48,6 +48,18 @@ const vehiclesAPI = {
}) })
.then(fetchRespHandler) .then(fetchRespHandler)
}, },
getLocations: async (token) => fetch(`${API_ENDPOINT}/carslocations`, {
method: "GET",
headers: Object.assign({ "Content-Type": "application/json" }, getAuthHeaderOptions(token)),
})
.then(fetchRespHandler),
getState: async (token, vin) => fetch(`${API_ENDPOINT}/carstate?vin=${vin}`, {
method: "GET",
headers: Object.assign({ "Content-Type": "application/json" }, getAuthHeaderOptions(token)),
})
.then(fetchRespHandler),
}; };
export default vehiclesAPI; export default vehiclesAPI;