CEC-1402 Merge to production (#148)
* Fix template function (#105) * CEC-638 Add EK test ECU (#106) * CEC-638 Add EK ECU * Update test * CEC-638 Should be EKS (#107) * Should be EKS * Update snapshot * CEC-624 Display update status info and ECU (#108) * Diplay ECU name in update status (#110) Optimize car update status progress control Remove car update status page test Replace with individual component tests * Handle case ECU is not in message (#111) * Refresh button label (#112) * Update ECU refresh button label * Update snapshot * remove * CEC-660 Fix release notes field (#113) * CEC-775 Manifest details component (#114) * CEC-775 Manifest details component * Code smells * Fix build warning * CEC-1050 New manifest format (#117) * CEC-1050 Manifest changes * Fix delete bug * Add approve update button * Code smell * Remove update approval * CEC-464 can filters forms (#118) * can filters forms and lists * unit tests * updating warnings and tests * merge develop * fixed snapshots * update jest mocks * updating tests * CEC-1050 Self download indicator (#119) * CEC-1160 Fix package warnings (#121) * CEC-1160 Last dependabot fix (#122) * CEC-1058 fleet forms (#123) * working fleets page * unit tests * snapshots * updating messages and snapshots * updating extraneous snaps * Update codeowners (#125) * CEC-1167 ota admin portal (#127) * Add test coverage script * Remove unnecessary check * CEC-1167 unit test and code coverage * included sonar job * updated the workflow * updated sonar properties * updated sonar properties * updated sonar properties * updated sonar properties * updated sonar properties * updated sonar properties * updated sonar properties * updated sonar properties Co-authored-by: jwu-fisker <jwu@fiskerinc.com> * CEC-1167 implementing ths coverage thresold (#128) * CEC-1216 Remove unused components (#129) * CEC-1216 Remove unused components * Remove import * CEC-1183/CEC-1201 fleet vehicles forms (#130) * working fleet vehicles forms * snapshots and api tests * CEC-1182 fleet filter forms (#131) * forms for fleet can filters * unit tests for fleet filters * removing warnings * updating regex * CEC-532 Display manifest file properties (#133) * CEC-532 Display update file properties * npm audit fix * CEC-1317 npm update (#134) * CEC-1320 Update for memory regions (#135) * CEC-1320 Update for memory regions * Clean up * CEC-1256/CEC-1330 data logger for vehicles/fleets and details tabs for vehicles/fleets (#136) * forms for fleet can filters * unit tests for fleet filters * removing warnings * updating regex * added fleet details page * fleet pages * smoothed out bugs * fleets done * working update, delete vehicles * finished mocks, still need snapshots and context tests * contexts done * snapshot tests * updating code smells * smells * CEC-1256/CEC-1330 fixing filters length function (#137) * fixing filters length function * adding filters testing * code smell * code smells * bug * CEC-1387 superset integration and removal of grafana (#138) * replace grafana with superset * updating snapshots * CEC-1316 azure migration (#140) * test portal azure * :doh: * runner * WIP * values * letsencrypt + docker cache * stg/prd * portal things * cleanup * split build/deploy + temp stage deploy * :doh: * try this * and prod * this works for now, can improve later * no need to specify azure anymore Co-authored-by: Drew Taylor <69828061+drew-fisker@users.noreply.github.com> * CEC-1369 Fix display of update error (#139) * CEC-1369 Fix display of update error * Update snapshot * CEC-749 Generate cert UI (#141) * Add Create Certificate page * Tests * Update permission check * Use Azure * CEC-1387 updating superset dns names (#142) * updating superset dns names * updating snapshots * Fix (#143) * CEC-749 Fix types (#144) * Merge branch 'develop' Co-authored-by: Drew Taylor <69828061+drew-fisker@users.noreply.github.com> Co-authored-by: venkats09 <97122017+venkats09@users.noreply.github.com> Co-authored-by: Rafi Greenberg <72412693+rafi-fisker@users.noreply.github.com>
This commit is contained in:
18
.env.dev
18
.env.dev
@@ -1,13 +1,5 @@
|
|||||||
REACT_APP_AUTH_SERVICE_URL=https://gw-dev.fiskerdps.com/compute_auth
|
REACT_APP_CERT_SERVICE_URL=https://dev-gw.cloud.fiskerinc.com/certificate
|
||||||
REACT_APP_UPLOAD_SERVICE_URL=https://gw-dev.fiskerdps.com/ota_update
|
REACT_APP_AUTH_SERVICE_URL=https://dev-gw.cloud.fiskerinc.com/compute_auth
|
||||||
REACT_APP_AUTH_CALLBACK_URL=https://dev-ota-admin.fiskerdps.com
|
REACT_APP_UPLOAD_SERVICE_URL=https://dev-gw.cloud.fiskerinc.com/ota_update
|
||||||
REACT_APP_GRAFANA_BASE_URL=https://dev-grafana.fiskerdps.com
|
REACT_APP_AUTH_CALLBACK_URL=https://dev-ota-admin.cloud.fiskerinc.com
|
||||||
REACT_APP_GRAFANA_HOME_CHART_PATH=/d-solo/1VTVJ_qGk/dashboard?orgId=2&refresh=30s&panelId=12
|
REACT_APP_SUPERSET_URL=http://superset-dev.fiskercloud.internal
|
||||||
REACT_APP_GRAFANA_VOLTAGE_CHART_PATH=/d-solo/LVI-aQGnz/diagnostics?orgId=2&var-VIN={vin}&var-Signal=BMS_CellVolt{cellNum}&panelId=2
|
|
||||||
REACT_APP_GRAFANA_CELLTEMP_CHART_PATH=/d-solo/LVI-aQGnz/diagnostics?orgId=2&var-VIN={vin}&var-Signal=BMS_CellT{cellNum}&panelId=2
|
|
||||||
REACT_APP_GRAFANA_BATTERYTEMP_CHART=/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN={vin}&refresh=1m&panelId=4
|
|
||||||
REACT_APP_GRAFANA_BATTERYCAP_CHART=/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN={vin}&refresh=1m&panelId=6
|
|
||||||
REACT_APP_GRAFANA_BATTERYPERCENT_CHART=/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN={vin}&panelId=12
|
|
||||||
REACT_APP_GRAFANA_BATTERY12VPERCENT_CHART=/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN={vin}&refresh=1m&panelId=2
|
|
||||||
REACT_APP_GRAFANA_BATTERY12VVOLTAGE_CHART=/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN={vin}&refresh=1m&panelId=9
|
|
||||||
REACT_APP_GRAFANA_API=https://dev-grafana.fiskerdps.com/api/datasources/proxy/2
|
|
||||||
|
|||||||
12
.env.local
12
.env.local
@@ -1,13 +1,5 @@
|
|||||||
REACT_APP_AUTH_SERVICE_URL=http://localhost/compute_auth
|
REACT_APP_AUTH_SERVICE_URL=http://localhost/compute_auth
|
||||||
|
REACT_APP_CERT_SERVICE_URL=http://localhost/certificate
|
||||||
REACT_APP_UPLOAD_SERVICE_URL=http://localhost/ota_update
|
REACT_APP_UPLOAD_SERVICE_URL=http://localhost/ota_update
|
||||||
REACT_APP_AUTH_CALLBACK_URL=http://localhost:3000
|
REACT_APP_AUTH_CALLBACK_URL=http://localhost:3000
|
||||||
REACT_APP_GRAFANA_BASE_URL=https://dev-grafana.fiskerdps.com
|
REACT_APP_SUPERSET_URL=http://superset-dev.fiskercloud.internal
|
||||||
REACT_APP_GRAFANA_HOME_CHART_PATH=/d-solo/1VTVJ_qGk/dashboard?orgId=2&refresh=30s&panelId=12
|
|
||||||
REACT_APP_GRAFANA_VOLTAGE_CHART_PATH=/d-solo/LVI-aQGnz/diagnostics?orgId=2&var-VIN={vin}&var-Signal=BMS_CellVolt{cellNum}&panelId=2
|
|
||||||
REACT_APP_GRAFANA_CELLTEMP_CHART_PATH=/d-solo/LVI-aQGnz/diagnostics?orgId=2&var-VIN={vin}&var-Signal=BMS_CellT{cellNum}&panelId=2
|
|
||||||
REACT_APP_GRAFANA_BATTERYTEMP_CHART=/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN={vin}&refresh=1m&panelId=4
|
|
||||||
REACT_APP_GRAFANA_BATTERYCAP_CHART=/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN={vin}&refresh=1m&panelId=6
|
|
||||||
REACT_APP_GRAFANA_BATTERYPERCENT_CHART=/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN={vin}&panelId=12
|
|
||||||
REACT_APP_GRAFANA_BATTERY12VPERCENT_CHART=/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN={vin}&refresh=1m&panelId=2
|
|
||||||
REACT_APP_GRAFANA_BATTERY12VVOLTAGE_CHART=/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN={vin}&refresh=1m&panelId=9
|
|
||||||
REACT_APP_GRAFANA_API=https://dev-grafana.fiskerdps.com/api/datasources/proxy/2
|
|
||||||
|
|||||||
18
.env.prd
18
.env.prd
@@ -1,13 +1,5 @@
|
|||||||
REACT_APP_AUTH_SERVICE_URL=https://gw.fiskerdps.com/compute_auth
|
REACT_APP_AUTH_SERVICE_URL=https://gw.cloud.fiskerinc.com/compute_auth
|
||||||
REACT_APP_UPLOAD_SERVICE_URL=https://gw.fiskerdps.com/ota_update
|
REACT_APP_CERT_SERVICE_URL=https://gw.cloud.fiskerinc.com/certificate
|
||||||
REACT_APP_AUTH_CALLBACK_URL=https://ota-admin.fiskerdps.com
|
REACT_APP_UPLOAD_SERVICE_URL=https://gw.cloud.fiskerinc.com/ota_update
|
||||||
REACT_APP_GRAFANA_BASE_URL=https://grafana.fiskerdps.com
|
REACT_APP_AUTH_CALLBACK_URL=https://ota-admin.cloud.fiskerinc.com
|
||||||
REACT_APP_GRAFANA_HOME_CHART_PATH=/d-solo/1VTVJ_qGk/dashboard?orgId=2&refresh=30s&panelId=12
|
REACT_APP_SUPERSET_URL=http://superset.fiskercloud.internal
|
||||||
REACT_APP_GRAFANA_VOLTAGE_CHART_PATH=/d-solo/LVI-aQGnz/diagnostics?orgId=2&var-VIN={vin}&var-Signal=BMS_CellVolt{cellNum}&panelId=2
|
|
||||||
REACT_APP_GRAFANA_CELLTEMP_CHART_PATH=/d-solo/LVI-aQGnz/diagnostics?orgId=2&var-VIN={vin}&var-Signal=BMS_CellT{cellNum}&panelId=2
|
|
||||||
REACT_APP_GRAFANA_BATTERYTEMP_CHART=/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN={vin}&refresh=1m&panelId=4
|
|
||||||
REACT_APP_GRAFANA_BATTERYCAP_CHART=/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN={vin}&refresh=1m&panelId=6
|
|
||||||
REACT_APP_GRAFANA_BATTERYPERCENT_CHART=/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN={vin}&panelId=12
|
|
||||||
REACT_APP_GRAFANA_BATTERY12VPERCENT_CHART=/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN={vin}&refresh=1m&panelId=2
|
|
||||||
REACT_APP_GRAFANA_BATTERY12VVOLTAGE_CHART=/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN={vin}&refresh=1m&panelId=9
|
|
||||||
REACT_APP_GRAFANA_API=https://grafana.fiskerdps.com/api/datasources/proxy/1
|
|
||||||
|
|||||||
18
.env.stg
18
.env.stg
@@ -1,13 +1,5 @@
|
|||||||
REACT_APP_AUTH_SERVICE_URL=https://gw-stg.fiskerdps.com/compute_auth
|
REACT_APP_AUTH_SERVICE_URL=https://stg-gw.cloud.fiskerinc.com/compute_auth
|
||||||
REACT_APP_UPLOAD_SERVICE_URL=https://gw-stg.fiskerdps.com/ota_update
|
REACT_APP_CERT_SERVICE_URL=https://stg-gw.cloud.fiskerinc.com/certificate
|
||||||
REACT_APP_AUTH_CALLBACK_URL=https://stg-ota-admin.fiskerdps.com
|
REACT_APP_UPLOAD_SERVICE_URL=https://stg-gw.cloud.fiskerinc.com/ota_update
|
||||||
REACT_APP_GRAFANA_BASE_URL=https://stg-grafana.fiskerdps.com
|
REACT_APP_AUTH_CALLBACK_URL=https://stg-ota-admin.cloud.fiskerinc.com
|
||||||
REACT_APP_GRAFANA_HOME_CHART_PATH=/d-solo/1VTVJ_qGk/dashboard?orgId=2&refresh=30s&panelId=12
|
REACT_APP_SUPERSET_URL=http://superset-stg.fiskercloud.internal
|
||||||
REACT_APP_GRAFANA_VOLTAGE_CHART_PATH=/d-solo/LVI-aQGnz/diagnostics?orgId=2&var-VIN={vin}&var-Signal=BMS_CellVolt{cellNum}&panelId=2
|
|
||||||
REACT_APP_GRAFANA_CELLTEMP_CHART_PATH=/d-solo/LVI-aQGnz/diagnostics?orgId=2&var-VIN={vin}&var-Signal=BMS_CellT{cellNum}&panelId=2
|
|
||||||
REACT_APP_GRAFANA_BATTERYTEMP_CHART=/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN={vin}&refresh=1m&panelId=4
|
|
||||||
REACT_APP_GRAFANA_BATTERYCAP_CHART=/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN={vin}&refresh=1m&panelId=6
|
|
||||||
REACT_APP_GRAFANA_BATTERYPERCENT_CHART=/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN={vin}&panelId=12
|
|
||||||
REACT_APP_GRAFANA_BATTERY12VPERCENT_CHART=/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN={vin}&refresh=1m&panelId=2
|
|
||||||
REACT_APP_GRAFANA_BATTERY12VVOLTAGE_CHART=/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN={vin}&refresh=1m&panelId=9
|
|
||||||
REACT_APP_GRAFANA_API=https://stg-grafana.fiskerdps.com/api/datasources/proxy/1
|
|
||||||
|
|||||||
@@ -1,13 +1,5 @@
|
|||||||
REACT_APP_AUTH_SERVICE_URL=http://localhost/compute_auth
|
REACT_APP_AUTH_SERVICE_URL=http://localhost/compute_auth
|
||||||
REACT_APP_UPLOAD_SERVICE_URL=http://localhost/ota_update
|
REACT_APP_UPLOAD_SERVICE_URL=http://localhost/ota_update
|
||||||
REACT_APP_AUTH_CALLBACK_URL=http://localhost:3000
|
REACT_APP_AUTH_CALLBACK_URL=http://localhost:3000
|
||||||
REACT_APP_GRAFANA_BASE_URL=https://grafana.fiskerdps.com
|
REACT_APP_SUPERSET_URL=http://superset-dev.fisker.internal
|
||||||
REACT_APP_GRAFANA_HOME_CHART_PATH=/d-solo/1VTVJ_qGk/dashboard?orgId=2&refresh=30s&panelId=12
|
REACT_APP_CERT_SERVICE_URL=http://localhost/certificate
|
||||||
REACT_APP_GRAFANA_VOLTAGE_CHART_PATH=/d-solo/LVI-aQGnz/diagnostics?orgId=2&var-VIN={vin}&var-Signal=BMS_CellVolt{cellNum}&panelId=2
|
|
||||||
REACT_APP_GRAFANA_CELLTEMP_CHART_PATH=/d-solo/LVI-aQGnz/diagnostics?orgId=2&var-VIN={vin}&var-Signal=BMS_CellT{cellNum}&panelId=2
|
|
||||||
REACT_APP_GRAFANA_BATTERYTEMP_CHART=/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN={vin}&refresh=1m&panelId=4
|
|
||||||
REACT_APP_GRAFANA_BATTERYCAP_CHART=/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN={vin}&refresh=1m&panelId=6
|
|
||||||
REACT_APP_GRAFANA_BATTERYPERCENT_CHART=/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN={vin}&panelId=12
|
|
||||||
REACT_APP_GRAFANA_BATTERY12VPERCENT_CHART=/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN={vin}&refresh=1m&panelId=2
|
|
||||||
REACT_APP_GRAFANA_BATTERY12VVOLTAGE_CHART=/d-solo/jRKKo2gnz/battery?orgId=2&var-VIN={vin}&refresh=1m&panelId=9
|
|
||||||
REACT_APP_GRAFANA_API=https://grafana.fiskerdps.com/api/datasources/proxy/1
|
|
||||||
|
|||||||
9
.github/CODEOWNERS
vendored
9
.github/CODEOWNERS
vendored
@@ -1,8 +1,7 @@
|
|||||||
# default codeowners
|
# default codeowners
|
||||||
* jwu@fiskerinc.com dtaylor@fiskerinc.com ggetsin@fiskerinc.com bbaker@fiskerinc.com
|
* @Fisker-Inc/cloud
|
||||||
|
|
||||||
# devops
|
# devops
|
||||||
.github rgreenberg@fiskerinc.com jwu@fiskerinc.com dtaylor@fiskerinc.com ggetsin@fiskerinc.com bbaker@fiskerinc.com
|
.github @Fisker-Inc/devops @Fisker-Inc/cloud
|
||||||
Jenkinsfile rgreenberg@fiskerinc.com jwu@fiskerinc.com dtaylor@fiskerinc.com ggetsin@fiskerinc.com bbaker@fiskerinc.com
|
k8s @Fisker-Inc/devops @Fisker-Inc/cloud
|
||||||
k8s rgreenberg@fiskerinc.com jwu@fiskerinc.com dtaylor@fiskerinc.com ggetsin@fiskerinc.com bbaker@fiskerinc.com
|
Dockerfile @Fisker-Inc/devops @Fisker-Inc/cloud
|
||||||
Dockerfile rgreenberg@fiskerinc.com jwu@fiskerinc.com dtaylor@fiskerinc.com ggetsin@fiskerinc.com bbaker@fiskerinc.com
|
|
||||||
|
|||||||
77
.github/workflows/deploy.yml
vendored
77
.github/workflows/deploy.yml
vendored
@@ -5,34 +5,42 @@ on:
|
|||||||
- main
|
- main
|
||||||
- "release/**"
|
- "release/**"
|
||||||
- "hotfix/**"
|
- "hotfix/**"
|
||||||
|
env:
|
||||||
|
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||||
|
SLACK_CHANNEL: "#cloud-builds"
|
||||||
|
SLACK_FOOTER: ""
|
||||||
|
SLACK_USERNAME: GitHub Actions
|
||||||
|
SLACK_ICON: "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png"
|
||||||
|
TAG: ${{ github.sha }}
|
||||||
|
PROJECT: ota-admin-portal
|
||||||
|
REGISTRY: fiskercloud.azurecr.io
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
build:
|
||||||
name: Deploy
|
runs-on: ubuntu-latest
|
||||||
runs-on: self-hosted
|
outputs:
|
||||||
env:
|
build-env: ${{ steps.set-env.outputs.build-env }}
|
||||||
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
|
|
||||||
SLACK_CHANNEL: "#cloud-builds"
|
|
||||||
SLACK_FOOTER: ""
|
|
||||||
SLACK_USERNAME: GitHub Actions
|
|
||||||
SLACK_ICON: "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png"
|
|
||||||
TAG: ${{ github.sha }}
|
|
||||||
PROJECT: ota-admin-portal
|
|
||||||
steps:
|
steps:
|
||||||
- name: Slack Notification
|
- name: Slack Notification
|
||||||
uses: rtCamp/action-slack-notify@v2
|
uses: rtCamp/action-slack-notify@v2
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Configure AWS Credentials
|
|
||||||
uses: aws-actions/configure-aws-credentials@v1
|
- name: Azure Login
|
||||||
|
uses: azure/login@v1
|
||||||
with:
|
with:
|
||||||
aws-region: us-west-2
|
creds: ${{ secrets.AZURE_CREDENTIALS }}
|
||||||
- name: Create ECR Repo
|
|
||||||
run: aws ecr create-repository --region us-west-2 --repository-name ${PROJECT} || true
|
- name: Docker login
|
||||||
- name: Login to Amazon ECR
|
uses: azure/docker-login@v1
|
||||||
id: login-ecr
|
with:
|
||||||
uses: aws-actions/amazon-ecr-login@v1
|
login-server: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||||
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
|
|
||||||
- name: Set Env
|
- name: Set Env
|
||||||
|
id: set-env
|
||||||
run: |
|
run: |
|
||||||
case ${GITHUB_REF} in
|
case ${GITHUB_REF} in
|
||||||
refs/heads/develop)
|
refs/heads/develop)
|
||||||
@@ -47,26 +55,33 @@ jobs:
|
|||||||
ENVIRONMENT=dev;;
|
ENVIRONMENT=dev;;
|
||||||
esac
|
esac
|
||||||
echo "ENVIRONMENT=${ENVIRONMENT}" >> $GITHUB_ENV
|
echo "ENVIRONMENT=${ENVIRONMENT}" >> $GITHUB_ENV
|
||||||
|
echo "::set-output name=build-env::${ENVIRONMENT}"
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
build-args: ENVIRONMENT=${{ env.ENVIRONMENT }}
|
build-args: ENVIRONMENT=${{ env.ENVIRONMENT }}
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.login-ecr.outputs.registry }}/${{ env.PROJECT }}:${{ env.TAG}}-${{ env.ENVIRONMENT }}
|
tags: ${{ env.REGISTRY }}/${{ env.PROJECT }}:${{ env.TAG }}-${{ env.ENVIRONMENT }}
|
||||||
cache-from: type=registry,ref=${{ steps.login-ecr.outputs.registry }}/${{ env.PROJECT }}:${{ env.TAG}}-${{ env.ENVIRONMENT }}
|
cache-from: type=gha
|
||||||
cache-to: type=inline
|
cache-to: type=gha,mode=max
|
||||||
- name: Notify deploy
|
|
||||||
uses: rtCamp/action-slack-notify@v2
|
deploy:
|
||||||
|
needs: build
|
||||||
|
runs-on: [self-hosted, azure]
|
||||||
|
env:
|
||||||
|
ENVIRONMENT: ${{ needs.build.outputs.build-env }}
|
||||||
|
steps:
|
||||||
|
- uses: rtCamp/action-slack-notify@v2
|
||||||
env:
|
env:
|
||||||
MSG_MINIMAL: true
|
MSG_MINIMAL: true
|
||||||
SLACK_MESSAGE: "Deploying to ${{ env.ENVIRONMENT }}... :partydeploy:"
|
SLACK_MESSAGE: "Deploying ${{ env.PROJECT }} to ${{ env.ENVIRONMENT }}... :partydeploy:"
|
||||||
|
|
||||||
- name: Deploy
|
- name: Deploy
|
||||||
id: deploy
|
|
||||||
env:
|
|
||||||
REGISTRY: ${{ steps.login-ecr.outputs.registry }}
|
|
||||||
run: |-
|
run: |-
|
||||||
helm upgrade \
|
helm upgrade \
|
||||||
--kube-context $ENVIRONMENT \
|
--kube-context $ENVIRONMENT \
|
||||||
@@ -74,17 +89,17 @@ jobs:
|
|||||||
--set image.name=$PROJECT \
|
--set image.name=$PROJECT \
|
||||||
--set image.tag=$TAG-$ENVIRONMENT \
|
--set image.tag=$TAG-$ENVIRONMENT \
|
||||||
--wait -i -f k8s/values-$ENVIRONMENT.yaml $PROJECT k8s/
|
--wait -i -f k8s/values-$ENVIRONMENT.yaml $PROJECT k8s/
|
||||||
|
|
||||||
- name: Notify if success
|
- name: Notify if success
|
||||||
if: ${{ success() }}
|
if: ${{ success() }}
|
||||||
uses: rtCamp/action-slack-notify@v2
|
uses: rtCamp/action-slack-notify@v2
|
||||||
env:
|
env:
|
||||||
MSG_MINIMAL: true
|
MSG_MINIMAL: true
|
||||||
SLACK_MESSAGE: "Successfully deployed to ${{ env.ENVIRONMENT }}! :gopher_party:"
|
SLACK_MESSAGE: "Successfully deployed ${{ env.PROJECT }} to ${{ env.ENVIRONMENT }}! :gopher_party:"
|
||||||
|
|
||||||
- name: Notify if failure
|
- name: Notify if failure
|
||||||
if: ${{ failure() }}
|
if: ${{ failure() }}
|
||||||
uses: rtCamp/action-slack-notify@v2
|
uses: rtCamp/action-slack-notify@v2
|
||||||
env:
|
env:
|
||||||
SLACK_COLOR: ${{ job.status }}
|
SLACK_COLOR: ${{ job.status }}
|
||||||
SLACK_MESSAGE: "Something failed! :this-is-fine:"
|
SLACK_MESSAGE: "Something failed! :this-is-fine:"
|
||||||
|
|||||||
11
.github/workflows/test.yml
vendored
11
.github/workflows/test.yml
vendored
@@ -8,6 +8,9 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
@@ -15,6 +18,12 @@ jobs:
|
|||||||
cache: "npm"
|
cache: "npm"
|
||||||
- run: npm install
|
- run: npm install
|
||||||
- run: npm run build --if-present
|
- run: npm run build --if-present
|
||||||
- run: npm test
|
- run: npm test -- --coverage --coverageDirectory='coverage' --watchAll=false
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
|
|
||||||
|
- name: SonarCloud Scan
|
||||||
|
uses: sonarsource/sonarcloud-github-action@master
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
|
|||||||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -1,3 +1,7 @@
|
|||||||
{
|
{
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true,
|
||||||
|
// spacing
|
||||||
|
"editor.tabSize": 2,
|
||||||
|
"editor.insertSpaces": true,
|
||||||
|
"editor.detectIndentation": false
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,25 @@
|
|||||||
apiVersion: networking.k8s.io/v1beta1
|
apiVersion: networking.k8s.io/v1
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
kubernetes.io/ingress.class: nginx
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
labels:
|
labels:
|
||||||
app: {{ .Chart.Name }}
|
app: {{ .Chart.Name }}
|
||||||
name: {{ .Chart.Name }}
|
name: {{ .Chart.Name }}
|
||||||
spec:
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
rules:
|
rules:
|
||||||
- host: {{ .Values.ingress.hostname }}
|
- host: {{ .Values.ingress.hostname }}
|
||||||
http:
|
http:
|
||||||
paths:
|
paths:
|
||||||
- backend:
|
- backend:
|
||||||
serviceName: {{ .Chart.Name }}
|
service:
|
||||||
servicePort: 80
|
name: {{ .Chart.Name }}
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
path: /
|
path: /
|
||||||
|
pathType: ImplementationSpecific
|
||||||
tls:
|
tls:
|
||||||
- hosts:
|
- hosts:
|
||||||
- {{ .Values.ingress.hostname }}
|
- {{ .Values.ingress.hostname }}
|
||||||
secretName: fiskerdps-cert
|
secretName: {{ .Chart.Name }}-tls
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
ingress:
|
ingress:
|
||||||
hostname: dev-ota-admin.fiskerdps.com
|
hostname: dev-ota-admin.cloud.fiskerinc.com
|
||||||
|
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
ingress:
|
ingress:
|
||||||
hostname: ota-admin.fiskerdps.com
|
hostname: ota-admin.cloud.fiskerinc.com
|
||||||
|
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
@@ -9,4 +9,4 @@ resources:
|
|||||||
cpu: 250m
|
cpu: 250m
|
||||||
memory: 256Mi
|
memory: 256Mi
|
||||||
|
|
||||||
replicas: 1
|
replicas: 3
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
ingress:
|
ingress:
|
||||||
hostname: stg-ota-admin.fiskerdps.com
|
hostname: stg-ota-admin.cloud.fiskerinc.com
|
||||||
|
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
|
|||||||
15632
package-lock.json
generated
15632
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
45
package.json
45
package.json
@@ -3,23 +3,26 @@
|
|||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@datadog/browser-logs": "^3.7.0",
|
"@datadog/browser-logs": "^3.11.0",
|
||||||
"@material-ui/core": "^4.12.3",
|
"@emotion/react": "^11.9.0",
|
||||||
"@material-ui/icons": "^4.11.2",
|
"@emotion/styled": "^11.8.1",
|
||||||
"@testing-library/jest-dom": "^5.15.0",
|
"@material-ui/core": "^4.12.4",
|
||||||
"@testing-library/react": "^12.1.2",
|
"@material-ui/icons": "^4.11.3",
|
||||||
|
"@testing-library/jest-dom": "^5.16.4",
|
||||||
|
"@testing-library/react": "^12.1.4",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"axios": "^0.21.4",
|
"axios": "^0.26.1",
|
||||||
"clsx": "^1.1.1",
|
"clsx": "^1.1.1",
|
||||||
"env-cmd": "^10.1.0",
|
"env-cmd": "^10.1.0",
|
||||||
"leaflet": "^1.7.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.2",
|
"react-leaflet": "^3.2.5",
|
||||||
"react-router-dom": "^5.3.0",
|
"react-router-dom": "^5.3.0",
|
||||||
"react-scripts": "4.0.3",
|
"react-router-hash-link": "^2.4.3",
|
||||||
"web-vitals": "^2.1.2"
|
"react-scripts": "5.0.0",
|
||||||
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "env-cmd -f .env.local react-scripts start",
|
"start": "env-cmd -f .env.local react-scripts start",
|
||||||
@@ -28,8 +31,9 @@
|
|||||||
"build:stg": "env-cmd -f .env.stg react-scripts build",
|
"build:stg": "env-cmd -f .env.stg react-scripts build",
|
||||||
"build:prd": "env-cmd -f .env.prd react-scripts build",
|
"build:prd": "env-cmd -f .env.prd react-scripts build",
|
||||||
"build:local": "env-cmd -f .env.local react-scripts build",
|
"build:local": "env-cmd -f .env.local react-scripts build",
|
||||||
"test": "env-cmd -f .env.local react-scripts test",
|
"test": "env-cmd -f .env.local react-scripts test --no-cache",
|
||||||
"test:debug": "react-scripts --inspect-brk test --runInBand --no-cache",
|
"test:debug": "react-scripts --inspect-brk test --runInBand --no-cache",
|
||||||
|
"test:coverage": "npm test -- --coverage --watchAll=false --no-cache",
|
||||||
"eject": "react-scripts eject"
|
"eject": "react-scripts eject"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
@@ -44,12 +48,27 @@
|
|||||||
"not op_mini all"
|
"not op_mini all"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "12.20.1"
|
"node": "14.17.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"react-test-renderer": "^17.0.2"
|
"react-test-renderer": "^17.0.2"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"globalSetup": "./testEnv.js"
|
"globalSetup": "./testEnv.js",
|
||||||
|
"collectCoverageFrom": [
|
||||||
|
"src/**/*.{js,jsx,ts,tsx}"
|
||||||
|
],
|
||||||
|
"coverageThreshold": {
|
||||||
|
"global": {
|
||||||
|
"branches": 39,
|
||||||
|
"functions": 47,
|
||||||
|
"lines": 50,
|
||||||
|
"statements": 49
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"coverageReporters": [
|
||||||
|
"html",
|
||||||
|
"lcov"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
6
sonar-project.properties
Normal file
6
sonar-project.properties
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
sonar.projectKey=Fisker-Inc_ota-admin-portal
|
||||||
|
sonar.organization=fisker-inc
|
||||||
|
sonar.sourceEncoding=UTF-8
|
||||||
|
sonar.javascript.coveragePlugin=lcov
|
||||||
|
sonar.javascript.lcov.reportPaths=coverage/lcov.info
|
||||||
|
sonar.sources=src
|
||||||
@@ -4,7 +4,6 @@ jest.mock("../Contexts/VehicleContext");
|
|||||||
jest.mock("../Contexts/ManifestCreateContext");
|
jest.mock("../Contexts/ManifestCreateContext");
|
||||||
jest.mock("../Contexts/ManifestsContext");
|
jest.mock("../Contexts/ManifestsContext");
|
||||||
jest.mock("../Contexts/UserContext");
|
jest.mock("../Contexts/UserContext");
|
||||||
jest.mock("../../services/grafanaAPI");
|
|
||||||
jest.mock("../../services/monitoring");
|
jest.mock("../../services/monitoring");
|
||||||
jest.mock("../../services/vehiclesAPI");
|
jest.mock("../../services/vehiclesAPI");
|
||||||
|
|
||||||
@@ -44,16 +43,29 @@ const sleepAndCheck = async (path, selector, compare) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe("App", () => {
|
describe("App", () => {
|
||||||
|
const rxMakeStyles = /makeStyles-(\w+)-(\d+)/gi;
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
// Stablize Table Pagination control ids
|
// Stablize Table Pagination control ids
|
||||||
expect.addSnapshotSerializer({
|
expect.addSnapshotSerializer({
|
||||||
test: function (val) {
|
test: function (val) {
|
||||||
return val && typeof val === "string" && val.indexOf("mui-") >= 0;
|
return val && typeof val === "string" && val.indexOf("mui-") > -1;
|
||||||
},
|
},
|
||||||
print: function (val) {
|
print: function (val) {
|
||||||
let str = val;
|
let str = val;
|
||||||
str = str.replace(/mui-\d*/g, "mui-00000");
|
str = str.replace(/mui-\d*/g, "mui-00000");
|
||||||
|
|
||||||
|
return `"${str}"`;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect.addSnapshotSerializer({
|
||||||
|
test: (val) => {
|
||||||
|
return val && typeof val === "string" && val.search(rxMakeStyles) > -1;
|
||||||
|
},
|
||||||
|
print: function (val) {
|
||||||
|
let str = val;
|
||||||
|
str = str.replace(rxMakeStyles, "makeStyles-$1-0000");
|
||||||
|
|
||||||
return `"${str}"`;
|
return `"${str}"`;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -72,14 +84,6 @@ describe("App", () => {
|
|||||||
await sleepAndCheck("/home", "span.MuiButton-label", "Sign In");
|
await sleepAndCheck("/home", "span.MuiButton-label", "Sign In");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Route /datascope unauthenticated", async () => {
|
|
||||||
await sleepAndCheck("/datascope", "span.MuiButton-label", "Sign In");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Route /datascope/battery unauthenticated", async () => {
|
|
||||||
await check("/datascope/battery", "span.MuiButton-label", "Sign In");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Route /packages unauthenticated", async () => {
|
it("Route /packages unauthenticated", async () => {
|
||||||
await check("/packages", "span.MuiButton-label", "Sign In");
|
await check("/packages", "span.MuiButton-label", "Sign In");
|
||||||
});
|
});
|
||||||
@@ -116,6 +120,10 @@ describe("App", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Route /tools/certificates/add unauthenticated", async () => {
|
||||||
|
await check("/tools/certificates/add", "span.MuiButton-label", "Sign In");
|
||||||
|
});
|
||||||
|
|
||||||
it("Route /page-not-found unauthenticated", async () => {
|
it("Route /page-not-found unauthenticated", async () => {
|
||||||
await check("/page-not-found", "h1", "Page Not Found");
|
await check("/page-not-found", "h1", "Page Not Found");
|
||||||
});
|
});
|
||||||
@@ -134,16 +142,6 @@ describe("App", () => {
|
|||||||
await check("/page-not-found", "h1", "Page Not Found");
|
await check("/page-not-found", "h1", "Page Not Found");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Route /datascope authenticated", async () => {
|
|
||||||
setToken(TEST_AUTH_OBJECT);
|
|
||||||
await sleepAndCheck("/datascope", "h6", "Datascope");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Route /datascope/battery authenticated", async () => {
|
|
||||||
setToken(TEST_AUTH_OBJECT);
|
|
||||||
await check("/datascope/battery", "h6", "Battery");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Route /packages authenticated", async () => {
|
it("Route /packages authenticated", async () => {
|
||||||
setToken(TEST_AUTH_OBJECT);
|
setToken(TEST_AUTH_OBJECT);
|
||||||
await check("/packages", "h6", "Deployments");
|
await check("/packages", "h6", "Deployments");
|
||||||
@@ -178,4 +176,9 @@ describe("App", () => {
|
|||||||
setToken(TEST_AUTH_OBJECT);
|
setToken(TEST_AUTH_OBJECT);
|
||||||
await check("/vehicle-status/FISKER123", "h6", "Vehicle FISKER123 Details");
|
await check("/vehicle-status/FISKER123", "h6", "Vehicle FISKER123 Details");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Route /tools/certificates/add authenticated", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT);
|
||||||
|
await check("/tools/certificates/add", "h6", "Create Certificate");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
177
src/components/CANFilter/Add/__snapshots__/index.test.jsx.snap
Normal file
177
src/components/CANFilter/Add/__snapshots__/index.test.jsx.snap
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`CANFiltersAdd Render 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-canfiltersprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-statusprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-userprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-canfiltersprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-3"
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
action="{onSubmit}"
|
||||||
|
class="makeStyles-form-5"
|
||||||
|
novalidate=""
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
||||||
|
data-shrink="false"
|
||||||
|
for="vin"
|
||||||
|
id="vin-label"
|
||||||
|
>
|
||||||
|
VIN
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="vin"
|
||||||
|
maxlength="255"
|
||||||
|
name="vin"
|
||||||
|
readonly=""
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-62 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-64"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
VIN
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
||||||
|
data-shrink="false"
|
||||||
|
for="canId"
|
||||||
|
id="canId-label"
|
||||||
|
>
|
||||||
|
CAN ID
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="canId"
|
||||||
|
maxlength="255"
|
||||||
|
name="canId"
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-62 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-64"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
CAN ID
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined"
|
||||||
|
data-shrink="false"
|
||||||
|
for="interval"
|
||||||
|
id="interval-label"
|
||||||
|
>
|
||||||
|
Interval
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="interval"
|
||||||
|
maxlength="255"
|
||||||
|
name="interval"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-62 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-64"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Interval
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-6 MuiButton-containedPrimary MuiButton-fullWidth"
|
||||||
|
tabindex="0"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiButton-label"
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
f
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
127
src/components/CANFilter/Add/index.jsx
Normal file
127
src/components/CANFilter/Add/index.jsx
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import { Redirect } from "react-router";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
import { Button, TextField } from "@material-ui/core";
|
||||||
|
|
||||||
|
import {
|
||||||
|
CANFiltersProvider,
|
||||||
|
useCANFiltersContext,
|
||||||
|
} from "../../Contexts/CANFiltersContext";
|
||||||
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
|
import useStyles from "../../useStyles";
|
||||||
|
import { logger } from "../../../services/monitoring";
|
||||||
|
|
||||||
|
|
||||||
|
const MainForm = () => {
|
||||||
|
const { addFilter, busy } = useCANFiltersContext();
|
||||||
|
const { token: { idToken: { jwtToken: token } } } = useUserContext();
|
||||||
|
const { setMessage, setTitle, setSitePath } = useStatusContext();
|
||||||
|
const [redirect, setRedirect] = useState(null);
|
||||||
|
const classes = useStyles();
|
||||||
|
const canIdEl = useRef(null);
|
||||||
|
const intervalEl = useRef(null);
|
||||||
|
const queries = new URLSearchParams(useLocation().search);
|
||||||
|
const vin = queries.get("vin") ?? ""
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTitle("Create CAN Filter");
|
||||||
|
setSitePath([
|
||||||
|
{
|
||||||
|
label: `Vehicle ${vin}`,
|
||||||
|
link: "/vehicles",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Create CAN Filter",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onSubmit = async (event) => {
|
||||||
|
try {
|
||||||
|
event.preventDefault();
|
||||||
|
const formData = {
|
||||||
|
can_id: canIdEl.current.value,
|
||||||
|
interval: parseInt(intervalEl.current.value)
|
||||||
|
};
|
||||||
|
const result = await addFilter(vin, formData, token);
|
||||||
|
if (!result || result.error) return;
|
||||||
|
|
||||||
|
setMessage(`Added filter`);
|
||||||
|
setRedirect(`/vehicle-status/${vin}#filters`);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
logger.warn(e.stack);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (redirect && redirect.length > 0) {
|
||||||
|
return <Redirect to={redirect} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.paper}>
|
||||||
|
<form className={classes.form} noValidate action="{onSubmit}">
|
||||||
|
<TextField
|
||||||
|
id="vin"
|
||||||
|
name="vin"
|
||||||
|
label="VIN"
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
inputProps={{
|
||||||
|
maxLength: "255",
|
||||||
|
readOnly: true,
|
||||||
|
}}
|
||||||
|
value={vin}
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
id="canId"
|
||||||
|
name="canId"
|
||||||
|
label="CAN ID"
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
inputProps={{
|
||||||
|
maxLength: "255",
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
inputRef={canIdEl}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
id="interval"
|
||||||
|
name="interval"
|
||||||
|
label="Interval"
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
inputProps={{
|
||||||
|
maxLength: "255",
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
inputRef={intervalEl}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={busy}
|
||||||
|
fullWidth
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
className={classes.submit}
|
||||||
|
onClick={onSubmit}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CANFilterCreate = (props) => (
|
||||||
|
<CANFiltersProvider>
|
||||||
|
<MainForm {...props} />
|
||||||
|
</CANFiltersProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default CANFilterCreate;
|
||||||
36
src/components/CANFilter/Add/index.test.jsx
Normal file
36
src/components/CANFilter/Add/index.test.jsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
jest.mock("../../Contexts/CANFiltersContext");
|
||||||
|
jest.mock("../../Contexts/StatusContext");
|
||||||
|
jest.mock("../../Contexts/UserContext");
|
||||||
|
|
||||||
|
import { render, waitFor } from "@testing-library/react";
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
|
||||||
|
import { CANFiltersProvider } from "../../Contexts/CANFiltersContext";
|
||||||
|
import { StatusProvider } from "../../Contexts/StatusContext";
|
||||||
|
import { UserProvider, setToken } from "../../Contexts/UserContext";
|
||||||
|
import { TEST_AUTH_OBJECT } from "../../../utils/testing";
|
||||||
|
import MainForm from "./index"
|
||||||
|
|
||||||
|
const renderCANFiltersAdd = async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<CANFiltersProvider>
|
||||||
|
<StatusProvider>
|
||||||
|
<UserProvider>
|
||||||
|
<BrowserRouter>
|
||||||
|
<MainForm />
|
||||||
|
</BrowserRouter>
|
||||||
|
</UserProvider>
|
||||||
|
</StatusProvider>f
|
||||||
|
</CANFiltersProvider>
|
||||||
|
);
|
||||||
|
await waitFor(() => { });
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("CANFiltersAdd", () => {
|
||||||
|
it("Render", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT);
|
||||||
|
const container = await renderCANFiltersAdd();
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
453
src/components/CANFilter/Table/__snapshots__/index.test.jsx.snap
Normal file
453
src/components/CANFilter/Table/__snapshots__/index.test.jsx.snap
Normal file
@@ -0,0 +1,453 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`CANFiltersTable Render 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-canfiltersprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-statusprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-userprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-canfiltersprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-3 makeStyles-tableSize-53"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-root-14 MuiGrid-container MuiGrid-spacing-xs-2"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-textJustifyAlign-47 MuiGrid-item MuiGrid-grid-md-4"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="makeStyles-labelInline-9"
|
||||||
|
href="/filter-add?vin=undefined"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiSvgIcon-fontSizeLarge"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-textCenterAlign-48 MuiGrid-item MuiGrid-grid-md-8"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root makeStyles-margin-28 makeStyles-fullWidth-50"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated"
|
||||||
|
data-shrink="false"
|
||||||
|
for="search"
|
||||||
|
>
|
||||||
|
Search
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiInput-root MuiInput-underline MuiInputBase-formControl MuiInput-formControl MuiInputBase-adornedEnd"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiInput-input MuiInputBase-inputAdornedEnd"
|
||||||
|
id="search"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="MuiInputAdornment-root MuiInputAdornment-positionEnd"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-label="search"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table
|
||||||
|
class="MuiTable-root"
|
||||||
|
>
|
||||||
|
<thead
|
||||||
|
class="MuiTableHead-root"
|
||||||
|
>
|
||||||
|
<tr
|
||||||
|
class="MuiTableRow-root MuiTableRow-head"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiTableSortLabel-root"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
CAN ID
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiTableSortLabel-root"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Interval (ms)
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
Actions
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody
|
||||||
|
class="MuiTableBody-root"
|
||||||
|
>
|
||||||
|
<tr
|
||||||
|
class="MuiTableRow-root"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
123
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
1000
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="/filter-update?vin=undefined&can_id=123&interval=1000"
|
||||||
|
style="margin: 5px;"
|
||||||
|
title="Update \\"123\\""
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-label="Update 123"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="/"
|
||||||
|
title="Delete \\"123\\""
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-label="Delete 123"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
class="MuiTableRow-root"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
456-789
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
2000
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="/filter-update?vin=undefined&can_id=456-789&interval=2000"
|
||||||
|
style="margin: 5px;"
|
||||||
|
title="Update \\"456-789\\""
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-label="Update 456-789"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="/"
|
||||||
|
title="Delete \\"456-789\\""
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-label="Delete 456-789"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
class="MuiTableRow-root"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
1
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
0
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="/filter-update?vin=undefined&can_id=1&interval=0"
|
||||||
|
style="margin: 5px;"
|
||||||
|
title="Update \\"1\\""
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-label="Update 1"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="/"
|
||||||
|
title="Delete \\"1\\""
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-label="Delete 1"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
<tfoot
|
||||||
|
class="MuiTableFooter-root"
|
||||||
|
>
|
||||||
|
<tr
|
||||||
|
class="MuiTableRow-root MuiTableRow-footer"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-footer MuiTablePagination-root"
|
||||||
|
colspan="8"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiToolbar-root MuiToolbar-regular MuiTablePagination-toolbar MuiToolbar-gutters"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTablePagination-spacer"
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
class="MuiTypography-root MuiTablePagination-caption MuiTypography-body2 MuiTypography-colorInherit"
|
||||||
|
>
|
||||||
|
Rows per page:
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiTablePagination-input MuiTablePagination-selectRoot"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
aria-label="rows per page"
|
||||||
|
class="MuiSelect-root MuiSelect-select MuiTablePagination-select MuiInputBase-input"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
class="MuiTablePagination-menuItem"
|
||||||
|
value="5"
|
||||||
|
>
|
||||||
|
5
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
class="MuiTablePagination-menuItem"
|
||||||
|
value="10"
|
||||||
|
>
|
||||||
|
10
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
class="MuiTablePagination-menuItem"
|
||||||
|
value="25"
|
||||||
|
>
|
||||||
|
25
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
class="MuiTablePagination-menuItem"
|
||||||
|
value="100"
|
||||||
|
>
|
||||||
|
100
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiSelect-icon MuiTablePagination-selectIcon"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M7 10l5 5 5-5z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="MuiTypography-root MuiTablePagination-caption MuiTypography-body2 MuiTypography-colorInherit"
|
||||||
|
>
|
||||||
|
1-3 of 3
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="MuiTablePagination-actions"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-label="Previous page"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit Mui-disabled Mui-disabled"
|
||||||
|
disabled=""
|
||||||
|
tabindex="-1"
|
||||||
|
title="Previous page"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M15.41 16.09l-4.58-4.59 4.58-4.59L14 5.5l-6 6 6 6z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-label="Next page"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit Mui-disabled Mui-disabled"
|
||||||
|
disabled=""
|
||||||
|
tabindex="-1"
|
||||||
|
title="Next page"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
210
src/components/CANFilter/Table/index.jsx
Normal file
210
src/components/CANFilter/Table/index.jsx
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import {
|
||||||
|
Grid,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableFooter,
|
||||||
|
TablePagination,
|
||||||
|
TableRow,
|
||||||
|
Tooltip,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
import AddCircleIcon from "@material-ui/icons/AddCircle";
|
||||||
|
import DeleteIcon from "@material-ui/icons/Delete";
|
||||||
|
import EditIcon from '@material-ui/icons/Edit';
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
import {
|
||||||
|
CANFiltersProvider,
|
||||||
|
useCANFiltersContext,
|
||||||
|
} from "../../Contexts/CANFiltersContext";
|
||||||
|
import TableHeaderSortable from "../../Table/HeaderSortable";
|
||||||
|
import {
|
||||||
|
useUserContext
|
||||||
|
} from "../../Contexts/UserContext"
|
||||||
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
|
import useStyles from "../../useStyles";
|
||||||
|
import SearchField from "../../Controls/SearchField";
|
||||||
|
import { logger } from "../../../services/monitoring";
|
||||||
|
import { Roles, hasRole } from "../../../utils/roles";
|
||||||
|
|
||||||
|
const tableColumns = [
|
||||||
|
{
|
||||||
|
id: "can_id",
|
||||||
|
label: "CAN ID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "interval",
|
||||||
|
label: "Interval (ms)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "",
|
||||||
|
label: "Actions"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const MainForm = ({ vin }) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
const [pageSize, setPageSize] = useState(10);
|
||||||
|
const [pageIndex, setPageIndex] = useState(0);
|
||||||
|
const [orderBy, setOrderBy] = useState("id");
|
||||||
|
const [order, setOrder] = useState("desc");
|
||||||
|
const { getFilters, deleteFilter, filters, totalFilters } = useCANFiltersContext();
|
||||||
|
const { setMessage } = useStatusContext();
|
||||||
|
const { token: { idToken: { jwtToken: token } }, groups } = useUserContext();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
if (!vin || !token) return;
|
||||||
|
await getFilters(
|
||||||
|
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 onDelete = async (can_id) => {
|
||||||
|
try {
|
||||||
|
await deleteFilter(vin, can_id, token);
|
||||||
|
setMessage(`Deleted ${can_id}`)
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
logger.warn(e.stack);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Actions = (row) => {
|
||||||
|
let actions = [];
|
||||||
|
if (hasRole([Roles.CREATE], groups)) {
|
||||||
|
actions.push({
|
||||||
|
tip: `Update "${row.can_id}"`,
|
||||||
|
link: `/filter-update?vin=${vin}&can_id=${row.can_id}&interval=${row.interval}`,
|
||||||
|
icon: <EditIcon aria-label={`Update ${row.can_id}`} />
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (hasRole([Roles.DELETE], groups)) {
|
||||||
|
actions.push({
|
||||||
|
tip: `Delete "${row.can_id}"`,
|
||||||
|
id: row.can_id,
|
||||||
|
icon: <DeleteIcon aria-label={`Delete ${row.can_id}`} />
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (actions.length === 0) return ["No actions"];
|
||||||
|
|
||||||
|
return actions.map((action) => {
|
||||||
|
if (action.link != null) {
|
||||||
|
return (
|
||||||
|
<Tooltip key={action.link} title={action.tip}>
|
||||||
|
<Link to={action.link} style={{ margin: 5 }}>
|
||||||
|
{action.icon}
|
||||||
|
</Link>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Tooltip key={`delete-${action.id}`} title={action.tip}>
|
||||||
|
<Link to="#" onClick={() => onDelete(action.id)}>
|
||||||
|
{action.icon}
|
||||||
|
</Link>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={clsx(classes.paper, classes.tableSize)}>
|
||||||
|
<Grid container className={classes.root} spacing={2}>
|
||||||
|
<Grid item md={4} className={classes.textJustifyAlign}>
|
||||||
|
<Link to={`/filter-add?vin=${vin}`} className={classes.labelInline}>
|
||||||
|
<AddCircleIcon fontSize="large" />
|
||||||
|
</Link>
|
||||||
|
</Grid>
|
||||||
|
<Grid item md={8} className={classes.textCenterAlign}>
|
||||||
|
<SearchField classes={classes} />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Table>
|
||||||
|
<TableHeaderSortable
|
||||||
|
classes={classes}
|
||||||
|
orderBy={orderBy}
|
||||||
|
order={order}
|
||||||
|
columnData={tableColumns}
|
||||||
|
onSortRequest={handleSort}
|
||||||
|
/>
|
||||||
|
<TableBody>
|
||||||
|
{filters.map((row) => (
|
||||||
|
<TableRow key={row.can_id}>
|
||||||
|
<TableCell align="center">{row.can_id}</TableCell>
|
||||||
|
<TableCell align="center">{row.interval}</TableCell>
|
||||||
|
<TableCell align="center">{Actions(row)}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
<TableFooter>
|
||||||
|
<TableRow>
|
||||||
|
<TablePagination
|
||||||
|
rowsPerPageOptions={[5, 10, 25, 100]}
|
||||||
|
colSpan={8}
|
||||||
|
count={totalFilters}
|
||||||
|
rowsPerPage={pageSize}
|
||||||
|
page={pageIndex}
|
||||||
|
SelectProps={{
|
||||||
|
inputProps: { "aria-label": "rows per page" },
|
||||||
|
native: true,
|
||||||
|
}}
|
||||||
|
onPageChange={handleChangePageIndex}
|
||||||
|
onRowsPerPageChange={handleChangePageSize}
|
||||||
|
/>
|
||||||
|
</TableRow>
|
||||||
|
</TableFooter>
|
||||||
|
</Table>
|
||||||
|
</div >
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CANFiltersTable = (props) => (
|
||||||
|
<CANFiltersProvider>
|
||||||
|
<MainForm {...props} />
|
||||||
|
</CANFiltersProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default CANFiltersTable;
|
||||||
39
src/components/CANFilter/Table/index.test.jsx
Normal file
39
src/components/CANFilter/Table/index.test.jsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
jest.mock("../../Contexts/CANFiltersContext");
|
||||||
|
jest.mock("../../Contexts/StatusContext");
|
||||||
|
jest.mock("../../Contexts/UserContext");
|
||||||
|
jest.mock('@material-ui/core/utils/unstable_useId', () =>
|
||||||
|
jest.fn().mockReturnValue('mui-test-id'),
|
||||||
|
);
|
||||||
|
|
||||||
|
import { render, waitFor } from "@testing-library/react";
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
|
||||||
|
import { CANFiltersProvider } from "../../Contexts/CANFiltersContext";
|
||||||
|
import { StatusProvider } from "../../Contexts/StatusContext";
|
||||||
|
import { UserProvider, setToken } from "../../Contexts/UserContext";
|
||||||
|
import { TEST_AUTH_OBJECT } from "../../../utils/testing";
|
||||||
|
import MainForm from "./index"
|
||||||
|
|
||||||
|
const renderCANFiltersTable = async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<CANFiltersProvider>
|
||||||
|
<StatusProvider>
|
||||||
|
<UserProvider>
|
||||||
|
<BrowserRouter>
|
||||||
|
<MainForm />
|
||||||
|
</BrowserRouter>
|
||||||
|
</UserProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
</CANFiltersProvider>
|
||||||
|
);
|
||||||
|
await waitFor(() => { });
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("CANFiltersTable", () => {
|
||||||
|
it("Render", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT);
|
||||||
|
const container = await renderCANFiltersTable();
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`CANFiltersUpdate Render 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-canfiltersprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-statusprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-userprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-canfiltersprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-3"
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
action="{onSubmit}"
|
||||||
|
class="makeStyles-form-5"
|
||||||
|
novalidate=""
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
||||||
|
data-shrink="false"
|
||||||
|
for="vin"
|
||||||
|
id="vin-label"
|
||||||
|
>
|
||||||
|
VIN
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="vin"
|
||||||
|
maxlength="255"
|
||||||
|
name="vin"
|
||||||
|
readonly=""
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-62 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-64"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
VIN
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
||||||
|
data-shrink="false"
|
||||||
|
for="canId"
|
||||||
|
id="canId-label"
|
||||||
|
>
|
||||||
|
CAN ID
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="canId"
|
||||||
|
maxlength="255"
|
||||||
|
name="canId"
|
||||||
|
readonly=""
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-62 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-64"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
CAN ID
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
||||||
|
data-shrink="false"
|
||||||
|
for="interval"
|
||||||
|
id="interval-label"
|
||||||
|
>
|
||||||
|
Interval
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="interval"
|
||||||
|
maxlength="255"
|
||||||
|
name="interval"
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-62 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-64"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Interval
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-6 MuiButton-containedPrimary MuiButton-fullWidth"
|
||||||
|
tabindex="0"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiButton-label"
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
131
src/components/CANFilter/Update/index.jsx
Normal file
131
src/components/CANFilter/Update/index.jsx
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import { Redirect } from "react-router";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
import { Button, TextField } from "@material-ui/core";
|
||||||
|
|
||||||
|
import {
|
||||||
|
CANFiltersProvider,
|
||||||
|
useCANFiltersContext,
|
||||||
|
} from "../../Contexts/CANFiltersContext";
|
||||||
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
|
import useStyles from "../../useStyles";
|
||||||
|
import { logger } from "../../../services/monitoring";
|
||||||
|
|
||||||
|
|
||||||
|
const MainForm = () => {
|
||||||
|
const { updateFilter, busy } = useCANFiltersContext();
|
||||||
|
const { token: { idToken: { jwtToken: token } } } = useUserContext();
|
||||||
|
const { setMessage, setTitle, setSitePath } = useStatusContext();
|
||||||
|
const [redirect, setRedirect] = useState(null);
|
||||||
|
const classes = useStyles();
|
||||||
|
const intervalEl = useRef(null);
|
||||||
|
const queries = new URLSearchParams(useLocation().search);
|
||||||
|
const vin = queries.get("vin") ?? ""
|
||||||
|
const canID = queries.get("can_id") ?? ""
|
||||||
|
const interval = queries.get("interval") ?? ""
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTitle("Update CAN Filter");
|
||||||
|
setSitePath([
|
||||||
|
{
|
||||||
|
label: `Vehicle ${vin}`,
|
||||||
|
link: "/vehicles",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Update CAN Filter",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onSubmit = async (event) => {
|
||||||
|
try {
|
||||||
|
event.preventDefault();
|
||||||
|
const formData = {
|
||||||
|
can_id: canID,
|
||||||
|
interval: parseInt(intervalEl.current.value)
|
||||||
|
};
|
||||||
|
const result = await updateFilter(vin, canID, formData, token);
|
||||||
|
if (!result || result.error) return;
|
||||||
|
|
||||||
|
setMessage(`Updated filter`);
|
||||||
|
setRedirect(`/vehicle-status/${vin}#filters`);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
logger.warn(e.stack);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (redirect && redirect.length > 0) {
|
||||||
|
return <Redirect to={redirect} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.paper}>
|
||||||
|
<form className={classes.form} noValidate action="{onSubmit}">
|
||||||
|
<TextField
|
||||||
|
id="vin"
|
||||||
|
name="vin"
|
||||||
|
label="VIN"
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
inputProps={{
|
||||||
|
maxLength: "255",
|
||||||
|
readOnly: true,
|
||||||
|
}}
|
||||||
|
value={vin}
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
id="canId"
|
||||||
|
name="canId"
|
||||||
|
label="CAN ID"
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
inputProps={{
|
||||||
|
maxLength: "255",
|
||||||
|
readOnly: true,
|
||||||
|
}}
|
||||||
|
value={canID}
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
id="interval"
|
||||||
|
name="interval"
|
||||||
|
label="Interval"
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
inputProps={{
|
||||||
|
maxLength: "255",
|
||||||
|
}}
|
||||||
|
defaultValue={interval}
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
inputRef={intervalEl}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={busy}
|
||||||
|
fullWidth
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
className={classes.submit}
|
||||||
|
onClick={onSubmit}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CANFilterUpdate = (props) => (
|
||||||
|
<CANFiltersProvider>
|
||||||
|
<MainForm {...props} />
|
||||||
|
</CANFiltersProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default CANFilterUpdate;
|
||||||
36
src/components/CANFilter/Update/index.test.jsx
Normal file
36
src/components/CANFilter/Update/index.test.jsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
jest.mock("../../Contexts/CANFiltersContext");
|
||||||
|
jest.mock("../../Contexts/StatusContext");
|
||||||
|
jest.mock("../../Contexts/UserContext");
|
||||||
|
|
||||||
|
import { render, waitFor } from "@testing-library/react";
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
|
||||||
|
import { CANFiltersProvider } from "../../Contexts/CANFiltersContext";
|
||||||
|
import { StatusProvider } from "../../Contexts/StatusContext";
|
||||||
|
import { UserProvider, setToken } from "../../Contexts/UserContext";
|
||||||
|
import { TEST_AUTH_OBJECT } from "../../../utils/testing";
|
||||||
|
import MainForm from "./index"
|
||||||
|
|
||||||
|
const renderCANFiltersUpdate = async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<CANFiltersProvider>
|
||||||
|
<StatusProvider>
|
||||||
|
<UserProvider>
|
||||||
|
<BrowserRouter>
|
||||||
|
<MainForm />
|
||||||
|
</BrowserRouter>
|
||||||
|
</UserProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
</CANFiltersProvider>
|
||||||
|
);
|
||||||
|
await waitFor(() => { });
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("CANFiltersUpdate", () => {
|
||||||
|
it("Render", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT);
|
||||||
|
const container = await renderCANFiltersUpdate();
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
730
src/components/Cars/Add/__snapshots__/index.test.jsx.snap
Normal file
730
src/components/Cars/Add/__snapshots__/index.test.jsx.snap
Normal file
@@ -0,0 +1,730 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`VehicleAddForm Render 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-vehicleprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-statusprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-userprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-vehicleprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-3"
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
action="{onSubmit}"
|
||||||
|
class="makeStyles-form-5"
|
||||||
|
novalidate=""
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
||||||
|
data-shrink="false"
|
||||||
|
for="vin"
|
||||||
|
id="vin-label"
|
||||||
|
>
|
||||||
|
VIN
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="vin"
|
||||||
|
maxlength="17"
|
||||||
|
name="vin"
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-62 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-64"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
VIN
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined MuiFormLabel-filled Mui-required Mui-required"
|
||||||
|
data-shrink="true"
|
||||||
|
for="model"
|
||||||
|
id="model-label"
|
||||||
|
>
|
||||||
|
Model
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="model"
|
||||||
|
maxlength="255"
|
||||||
|
name="model"
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
|
value="Ocean"
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-62 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-64 PrivateNotchedOutline-legendNotched-65"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Model
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined MuiFormLabel-filled Mui-required Mui-required"
|
||||||
|
data-shrink="true"
|
||||||
|
for="year"
|
||||||
|
id="year-label"
|
||||||
|
>
|
||||||
|
Year
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="year"
|
||||||
|
maxlength="4"
|
||||||
|
minlength="4"
|
||||||
|
name="year"
|
||||||
|
required=""
|
||||||
|
type="number"
|
||||||
|
value="2022"
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-62 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-64 PrivateNotchedOutline-legendNotched-65"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Year
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined MuiFormLabel-filled Mui-required Mui-required"
|
||||||
|
data-shrink="true"
|
||||||
|
for="trim"
|
||||||
|
id="trim-label"
|
||||||
|
>
|
||||||
|
Trim
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="trim"
|
||||||
|
maxlength="4"
|
||||||
|
minlength="4"
|
||||||
|
name="trim"
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
|
value="Base"
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-62 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-64 PrivateNotchedOutline-legendNotched-65"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Trim
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root"
|
||||||
|
id="demo-row-radio-buttons-group-label"
|
||||||
|
>
|
||||||
|
Log Level
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
aria-labelledby="demo-row-radio-buttons-group-label"
|
||||||
|
class="MuiFormGroup-root MuiFormGroup-row"
|
||||||
|
margin="normal"
|
||||||
|
role="radiogroup"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormControlLabel-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-66 MuiRadio-root MuiRadio-colorSecondary MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="PrivateSwitchBase-input-69"
|
||||||
|
name="log-level-group"
|
||||||
|
type="radio"
|
||||||
|
value="trace"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="PrivateRadioButtonIcon-root-70"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root PrivateRadioButtonIcon-layer-71"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.465 8.465C9.37 7.56 10.62 7 12 7C14.76 7 17 9.24 17 12C17 13.38 16.44 14.63 15.535 15.535C14.63 16.44 13.38 17 12 17C9.24 17 7 14.76 7 12C7 10.62 7.56 9.37 8.465 8.465Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
Trace
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
class="MuiFormControlLabel-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-66 MuiRadio-root MuiRadio-colorSecondary MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="PrivateSwitchBase-input-69"
|
||||||
|
name="log-level-group"
|
||||||
|
type="radio"
|
||||||
|
value="debug"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="PrivateRadioButtonIcon-root-70"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root PrivateRadioButtonIcon-layer-71"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.465 8.465C9.37 7.56 10.62 7 12 7C14.76 7 17 9.24 17 12C17 13.38 16.44 14.63 15.535 15.535C14.63 16.44 13.38 17 12 17C9.24 17 7 14.76 7 12C7 10.62 7.56 9.37 8.465 8.465Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
Debug
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
class="MuiFormControlLabel-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-66 MuiRadio-root MuiRadio-colorSecondary PrivateSwitchBase-checked-67 Mui-checked MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
checked=""
|
||||||
|
class="PrivateSwitchBase-input-69"
|
||||||
|
name="log-level-group"
|
||||||
|
type="radio"
|
||||||
|
value="info"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="PrivateRadioButtonIcon-root-70 PrivateRadioButtonIcon-checked-72"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root PrivateRadioButtonIcon-layer-71"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.465 8.465C9.37 7.56 10.62 7 12 7C14.76 7 17 9.24 17 12C17 13.38 16.44 14.63 15.535 15.535C14.63 16.44 13.38 17 12 17C9.24 17 7 14.76 7 12C7 10.62 7.56 9.37 8.465 8.465Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
Info
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
class="MuiFormControlLabel-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-66 MuiRadio-root MuiRadio-colorSecondary MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="PrivateSwitchBase-input-69"
|
||||||
|
name="log-level-group"
|
||||||
|
type="radio"
|
||||||
|
value="warn"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="PrivateRadioButtonIcon-root-70"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root PrivateRadioButtonIcon-layer-71"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.465 8.465C9.37 7.56 10.62 7 12 7C14.76 7 17 9.24 17 12C17 13.38 16.44 14.63 15.535 15.535C14.63 16.44 13.38 17 12 17C9.24 17 7 14.76 7 12C7 10.62 7.56 9.37 8.465 8.465Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
Warn
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
class="MuiFormControlLabel-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-66 MuiRadio-root MuiRadio-colorSecondary MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="PrivateSwitchBase-input-69"
|
||||||
|
name="log-level-group"
|
||||||
|
type="radio"
|
||||||
|
value="error"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="PrivateRadioButtonIcon-root-70"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root PrivateRadioButtonIcon-layer-71"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.465 8.465C9.37 7.56 10.62 7 12 7C14.76 7 17 9.24 17 12C17 13.38 16.44 14.63 15.535 15.535C14.63 16.44 13.38 17 12 17C9.24 17 7 14.76 7 12C7 10.62 7.56 9.37 8.465 8.465Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
Error
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
class="MuiFormControlLabel-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-66 MuiRadio-root MuiRadio-colorSecondary MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="PrivateSwitchBase-input-69"
|
||||||
|
name="log-level-group"
|
||||||
|
type="radio"
|
||||||
|
value="critical"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="PrivateRadioButtonIcon-root-70"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root PrivateRadioButtonIcon-layer-71"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.465 8.465C9.37 7.56 10.62 7 12 7C14.76 7 17 9.24 17 12C17 13.38 16.44 14.63 15.535 15.535C14.63 16.44 13.38 17 12 17C9.24 17 7 14.76 7 12C7 10.62 7.56 9.37 8.465 8.465Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
Critical
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root"
|
||||||
|
id="demo-row-radio-buttons-group-label"
|
||||||
|
>
|
||||||
|
CAN Bus
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiFormGroup-root"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormControlLabel-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-66 MuiCheckbox-root MuiCheckbox-colorSecondary PrivateSwitchBase-checked-67 Mui-checked MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
checked=""
|
||||||
|
class="PrivateSwitchBase-input-69"
|
||||||
|
data-indeterminate="false"
|
||||||
|
type="checkbox"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
CAN Bus Enabled
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined MuiFormLabel-filled Mui-required Mui-required"
|
||||||
|
data-shrink="true"
|
||||||
|
for="max_mem_buffer_size"
|
||||||
|
id="max_mem_buffer_size-label"
|
||||||
|
>
|
||||||
|
Max Memory Buffer Size (0 uses default size)
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="max_mem_buffer_size"
|
||||||
|
maxlength="12"
|
||||||
|
name="max_mem_buffer_size"
|
||||||
|
required=""
|
||||||
|
type="number"
|
||||||
|
value="0"
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-62 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-64 PrivateNotchedOutline-legendNotched-65"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Max Memory Buffer Size (0 uses default size)
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label
|
||||||
|
class="MuiFormControlLabel-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-66 MuiCheckbox-root MuiCheckbox-colorSecondary MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="PrivateSwitchBase-input-69"
|
||||||
|
data-indeterminate="false"
|
||||||
|
type="checkbox"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
Data Logger Enabled
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined Mui-disabled Mui-disabled MuiFormLabel-filled Mui-required Mui-required"
|
||||||
|
data-shrink="true"
|
||||||
|
for="max_disk_buffer_size"
|
||||||
|
id="max_disk_buffer_size-label"
|
||||||
|
>
|
||||||
|
Max Disk Buffer Size (0 uses default size)
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root Mui-disabled Mui-disabled MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input Mui-disabled Mui-disabled"
|
||||||
|
disabled=""
|
||||||
|
id="max_disk_buffer_size"
|
||||||
|
maxlength="12"
|
||||||
|
name="max_disk_buffer_size"
|
||||||
|
required=""
|
||||||
|
type="number"
|
||||||
|
value="0"
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-62 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-64 PrivateNotchedOutline-legendNotched-65"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Max Disk Buffer Size (0 uses default size)
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-6 MuiButton-containedPrimary MuiButton-fullWidth"
|
||||||
|
tabindex="0"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiButton-label"
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@@ -1,4 +1,15 @@
|
|||||||
import React, { useEffect, useRef } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import { Redirect } from "react-router";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
FormControlLabel,
|
||||||
|
FormGroup,
|
||||||
|
FormLabel,
|
||||||
|
Radio,
|
||||||
|
RadioGroup,
|
||||||
|
TextField
|
||||||
|
} from "@material-ui/core";
|
||||||
|
|
||||||
import useStyles from "../../useStyles";
|
import useStyles from "../../useStyles";
|
||||||
import {
|
import {
|
||||||
@@ -7,7 +18,6 @@ import {
|
|||||||
} from "../../Contexts/VehicleContext";
|
} from "../../Contexts/VehicleContext";
|
||||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
import { useUserContext } from "../../Contexts/UserContext";
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
import { Button, TextField } from "@material-ui/core";
|
|
||||||
import { logger } from "../../../services/monitoring";
|
import { logger } from "../../../services/monitoring";
|
||||||
|
|
||||||
const MainForm = () => {
|
const MainForm = () => {
|
||||||
@@ -19,10 +29,17 @@ const MainForm = () => {
|
|||||||
},
|
},
|
||||||
} = useUserContext();
|
} = useUserContext();
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
const [redirect, setRedirect] = useState(null);
|
||||||
|
|
||||||
const vinEl = useRef(null);
|
const vinEl = useRef(null);
|
||||||
const modelEl = useRef(null);
|
const modelEl = useRef(null);
|
||||||
const yearEl = useRef(null);
|
const yearEl = useRef(null);
|
||||||
const trimEl = useRef(null);
|
const trimEl = useRef(null);
|
||||||
|
const [selectedLogLevel, setSelectedLogLevel] = useState("info");
|
||||||
|
const [canbusEnabled, setCANBusEnabled] = useState(true);
|
||||||
|
const [dataLoggerEnabled, setDataLoggerEnabled] = useState(false);
|
||||||
|
const [maxMemBufferSize, setMaxMemBufferSize] = useState(0);
|
||||||
|
const [maxDiskBufferSize, setMaxDiskBufferSize] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTitle("Add Vehicle");
|
setTitle("Add Vehicle");
|
||||||
@@ -37,6 +54,27 @@ const MainForm = () => {
|
|||||||
]);
|
]);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const onLogLevelChange = (event) => {
|
||||||
|
setSelectedLogLevel(event.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCANBusChange = (event) => {
|
||||||
|
setCANBusEnabled(event.target.checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDataLoggerChange = (event) => {
|
||||||
|
setDataLoggerEnabled(event.target.checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onMaxMemBufferSizeChange = (event) => {
|
||||||
|
setMaxMemBufferSize(event.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onMaxDiskBufferSizeChange = (event) => {
|
||||||
|
setMaxDiskBufferSize(event.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
const onSubmit = async (event) => {
|
const onSubmit = async (event) => {
|
||||||
try {
|
try {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -46,18 +84,29 @@ const MainForm = () => {
|
|||||||
model: modelEl.current.value,
|
model: modelEl.current.value,
|
||||||
year: parseInt(yearEl.current.value),
|
year: parseInt(yearEl.current.value),
|
||||||
trim: trimEl.current.value,
|
trim: trimEl.current.value,
|
||||||
|
log_level: selectedLogLevel,
|
||||||
|
canbus: {
|
||||||
|
enabled: canbusEnabled,
|
||||||
|
data_logger_enabled: canbusEnabled ? dataLoggerEnabled : false,
|
||||||
|
max_mem_buffer_size: canbusEnabled ? parseInt(maxMemBufferSize) : 0,
|
||||||
|
max_disk_buffer_size: canbusEnabled && dataLoggerEnabled ? parseInt(maxDiskBufferSize) : 0
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await addVehicle(formData, token);
|
const result = await addVehicle(formData, token);
|
||||||
|
|
||||||
setMessage(`Added ${result.vin}`);
|
setMessage(`Added ${result.vin}`);
|
||||||
vinEl.current.value = "";
|
setRedirect(`/vehicles`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setMessage(e.message);
|
setMessage(e.message);
|
||||||
logger.warn(e.stack);
|
logger.warn(e.stack);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (redirect && redirect.length > 0) {
|
||||||
|
return <Redirect to={redirect} />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.paper}>
|
<div className={classes.paper}>
|
||||||
<form className={classes.form} noValidate action="{onSubmit}">
|
<form className={classes.form} noValidate action="{onSubmit}">
|
||||||
@@ -119,6 +168,70 @@ const MainForm = () => {
|
|||||||
fullWidth
|
fullWidth
|
||||||
inputRef={trimEl}
|
inputRef={trimEl}
|
||||||
/>
|
/>
|
||||||
|
<FormLabel id="demo-row-radio-buttons-group-label">Log Level</FormLabel>
|
||||||
|
<RadioGroup
|
||||||
|
row
|
||||||
|
aria-labelledby="demo-row-radio-buttons-group-label"
|
||||||
|
name="log-level-group"
|
||||||
|
value={selectedLogLevel}
|
||||||
|
onChange={onLogLevelChange}
|
||||||
|
margin="normal"
|
||||||
|
>
|
||||||
|
<FormControlLabel value="trace" control={<Radio />} label="Trace" />
|
||||||
|
<FormControlLabel value="debug" control={<Radio />} label="Debug" />
|
||||||
|
<FormControlLabel value="info" control={<Radio />} label="Info" />
|
||||||
|
<FormControlLabel value="warn" control={<Radio />} label="Warn" />
|
||||||
|
<FormControlLabel value="error" control={<Radio />} label="Error" />
|
||||||
|
<FormControlLabel value="critical" control={<Radio />} label="Critical" />
|
||||||
|
</RadioGroup>
|
||||||
|
<FormLabel id="demo-row-radio-buttons-group-label">CAN Bus</FormLabel>
|
||||||
|
<FormGroup>
|
||||||
|
<FormControlLabel control={
|
||||||
|
<Checkbox
|
||||||
|
checked={canbusEnabled}
|
||||||
|
onChange={onCANBusChange}
|
||||||
|
/>
|
||||||
|
} label="CAN Bus Enabled" />
|
||||||
|
<TextField
|
||||||
|
id="max_mem_buffer_size"
|
||||||
|
name="max_mem_buffer_size"
|
||||||
|
label='Max Memory Buffer Size (0 uses default size)'
|
||||||
|
value={maxMemBufferSize}
|
||||||
|
onChange={onMaxMemBufferSizeChange}
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
inputProps={{
|
||||||
|
maxLength: "12",
|
||||||
|
}}
|
||||||
|
type="number"
|
||||||
|
disabled={!canbusEnabled}
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<FormControlLabel control={
|
||||||
|
<Checkbox
|
||||||
|
checked={dataLoggerEnabled}
|
||||||
|
onChange={onDataLoggerChange}
|
||||||
|
disabled={!canbusEnabled}
|
||||||
|
/>
|
||||||
|
} label="Data Logger Enabled" />
|
||||||
|
</FormGroup>
|
||||||
|
<TextField
|
||||||
|
id="max_disk_buffer_size"
|
||||||
|
name="max_disk_buffer_size"
|
||||||
|
label='Max Disk Buffer Size (0 uses default size)'
|
||||||
|
value={maxDiskBufferSize}
|
||||||
|
onChange={onMaxDiskBufferSizeChange}
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
inputProps={{
|
||||||
|
maxLength: "12",
|
||||||
|
}}
|
||||||
|
type="number"
|
||||||
|
disabled={!dataLoggerEnabled}
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={busy}
|
disabled={busy}
|
||||||
|
|||||||
36
src/components/Cars/Add/index.test.jsx
Normal file
36
src/components/Cars/Add/index.test.jsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
jest.mock("../../Contexts/VehicleContext");
|
||||||
|
jest.mock("../../Contexts/StatusContext");
|
||||||
|
jest.mock("../../Contexts/UserContext");
|
||||||
|
|
||||||
|
import { render, waitFor } from "@testing-library/react";
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
|
||||||
|
import { VehicleProvider } from "../../Contexts/VehicleContext";
|
||||||
|
import { StatusProvider } from "../../Contexts/StatusContext";
|
||||||
|
import { UserProvider, setToken } from "../../Contexts/UserContext";
|
||||||
|
import { TEST_AUTH_OBJECT } from "../../../utils/testing";
|
||||||
|
import MainForm from "./index"
|
||||||
|
|
||||||
|
const renderVehicleAdd = async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<VehicleProvider>
|
||||||
|
<StatusProvider>
|
||||||
|
<UserProvider>
|
||||||
|
<BrowserRouter>
|
||||||
|
<MainForm />
|
||||||
|
</BrowserRouter>
|
||||||
|
</UserProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
</VehicleProvider>
|
||||||
|
);
|
||||||
|
await waitFor(() => { /* render */ });
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("VehicleAddForm", () => {
|
||||||
|
it("Render", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT);
|
||||||
|
const container = await renderVehicleAdd();
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
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 CarECUs = ({ 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 CarECUs;
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableFooter,
|
|
||||||
TablePagination,
|
|
||||||
TableRow,
|
|
||||||
} from "@material-ui/core";
|
|
||||||
|
|
||||||
import { LocalDateTimeString } from "../../../utils/dates";
|
|
||||||
import TableHeaderSortable from "../../Table/HeaderSortable";
|
|
||||||
import {
|
|
||||||
UpdatesProvider,
|
|
||||||
useUpdatesContext,
|
|
||||||
} from "../../Contexts/UpdatesContext";
|
|
||||||
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 } = useUpdatesContext();
|
|
||||||
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.updatepackage)
|
|
||||||
return `${row.updatepackage.package_name} ${row.updatepackage.version}`;
|
|
||||||
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">{updateName(row)}</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 CarUpdates = (props) => (
|
|
||||||
<UpdatesProvider>
|
|
||||||
<MainForm {...props} />
|
|
||||||
</UpdatesProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default CarUpdates;
|
|
||||||
391
src/components/Cars/List/__snapshots__/index.test.jsx.snap
Normal file
391
src/components/Cars/List/__snapshots__/index.test.jsx.snap
Normal file
@@ -0,0 +1,391 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`VehicleTable Render 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-vehicleprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-statusprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-userprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-vehicleprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-3 makeStyles-tableSize-53"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-root-14 MuiGrid-container MuiGrid-spacing-xs-2"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-textJustifyAlign-47 MuiGrid-item MuiGrid-grid-md-4"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="/vehicle-add"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiSvgIcon-fontSizeLarge"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-textCenterAlign-48 MuiGrid-item MuiGrid-grid-md-4"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root makeStyles-margin-28 makeStyles-fullWidth-50"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated"
|
||||||
|
data-shrink="false"
|
||||||
|
for="search"
|
||||||
|
>
|
||||||
|
Search
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiInput-root MuiInput-underline MuiInputBase-formControl MuiInput-formControl MuiInputBase-adornedEnd"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiInput-input MuiInputBase-inputAdornedEnd"
|
||||||
|
id="search"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="MuiInputAdornment-root MuiInputAdornment-positionEnd"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-label="search"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-textRightAlign-49 MuiGrid-item MuiGrid-grid-md-4"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-3 makeStyles-tableSize-53"
|
||||||
|
>
|
||||||
|
<table
|
||||||
|
class="MuiTable-root"
|
||||||
|
>
|
||||||
|
<thead
|
||||||
|
class="MuiTableHead-root"
|
||||||
|
>
|
||||||
|
<tr
|
||||||
|
class="MuiTableRow-root MuiTableRow-head"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
aria-sort="ascending"
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiTableSortLabel-root MuiTableSortLabel-active"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
VIN
|
||||||
|
<span
|
||||||
|
class="makeStyles-hiddenSortSpan-27"
|
||||||
|
>
|
||||||
|
sorted ascending
|
||||||
|
</span>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiTableSortLabel-root"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Model
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiTableSortLabel-root"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Year
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiTableSortLabel-root"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Trim
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiTableSortLabel-root"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Created
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiTableSortLabel-root"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Updated
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody
|
||||||
|
class="MuiTableBody-root"
|
||||||
|
/>
|
||||||
|
<tfoot
|
||||||
|
class="MuiTableFooter-root"
|
||||||
|
>
|
||||||
|
<tr
|
||||||
|
class="MuiTableRow-root MuiTableRow-footer"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-footer MuiTablePagination-root"
|
||||||
|
colspan="7"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiToolbar-root MuiToolbar-regular MuiTablePagination-toolbar MuiToolbar-gutters"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTablePagination-spacer"
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
class="MuiTypography-root MuiTablePagination-caption MuiTypography-body2 MuiTypography-colorInherit"
|
||||||
|
>
|
||||||
|
Rows per page:
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiTablePagination-input MuiTablePagination-selectRoot"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
aria-label="rows per page"
|
||||||
|
class="MuiSelect-root MuiSelect-select MuiTablePagination-select MuiInputBase-input"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
class="MuiTablePagination-menuItem"
|
||||||
|
value="5"
|
||||||
|
>
|
||||||
|
5
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
class="MuiTablePagination-menuItem"
|
||||||
|
value="10"
|
||||||
|
>
|
||||||
|
10
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
class="MuiTablePagination-menuItem"
|
||||||
|
value="25"
|
||||||
|
>
|
||||||
|
25
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
class="MuiTablePagination-menuItem"
|
||||||
|
value="100"
|
||||||
|
>
|
||||||
|
100
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiSelect-icon MuiTablePagination-selectIcon"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M7 10l5 5 5-5z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="MuiTypography-root MuiTablePagination-caption MuiTypography-body2 MuiTypography-colorInherit"
|
||||||
|
>
|
||||||
|
0-0 of 0
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="MuiTablePagination-actions"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-label="Previous page"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit Mui-disabled Mui-disabled"
|
||||||
|
disabled=""
|
||||||
|
tabindex="-1"
|
||||||
|
title="Previous page"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M15.41 16.09l-4.58-4.59 4.58-4.59L14 5.5l-6 6 6 6z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-label="Next page"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit Mui-disabled Mui-disabled"
|
||||||
|
disabled=""
|
||||||
|
tabindex="-1"
|
||||||
|
title="Next page"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@@ -8,14 +8,11 @@ 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 "../../Controls/SendCommand";
|
|
||||||
import SearchField from "../../Controls/SearchField";
|
import SearchField from "../../Controls/SearchField";
|
||||||
import CarSelectionTable from "../../Controls/CarSelectionTable";
|
import CarSelectionTable from "../../Controls/CarSelectionTable";
|
||||||
import { logger } from "../../../services/monitoring";
|
|
||||||
|
|
||||||
const MainForm = () => {
|
const MainForm = () => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [selected, setSelected] = useState([]);
|
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const { setTitle, setSitePath } = useStatusContext();
|
const { setTitle, setSitePath } = useStatusContext();
|
||||||
const {
|
const {
|
||||||
@@ -25,29 +22,9 @@ const MainForm = () => {
|
|||||||
} = useUserContext();
|
} = useUserContext();
|
||||||
|
|
||||||
const handleSearch = (query) => {
|
const handleSearch = (query) => {
|
||||||
setSelected([]);
|
|
||||||
setSearch(query);
|
setSearch(query);
|
||||||
};
|
};
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTitle("Vehicles");
|
setTitle("Vehicles");
|
||||||
setSitePath([]);
|
setSitePath([]);
|
||||||
@@ -61,24 +38,17 @@ const MainForm = () => {
|
|||||||
<Link to="/vehicle-add">
|
<Link to="/vehicle-add">
|
||||||
<AddCircleIcon fontSize="large" />
|
<AddCircleIcon fontSize="large" />
|
||||||
</Link>
|
</Link>
|
||||||
<div
|
|
||||||
className={classes.labelInline}
|
|
||||||
>{`${selected.length} Selected`}</div>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item md={4} className={classes.textCenterAlign}>
|
<Grid item md={4} className={classes.textCenterAlign}>
|
||||||
<SearchField classes={classes} onSearch={handleSearch} />
|
<SearchField classes={classes} onSearch={handleSearch} />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item md={4} className={classes.textRightAlign}>
|
<Grid item md={4} className={classes.textRightAlign} />
|
||||||
<SendCommand vins={selected} />
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<CarSelectionTable
|
<CarSelectionTable
|
||||||
classes={classes}
|
classes={classes}
|
||||||
token={token}
|
token={token}
|
||||||
|
multiSelect={false}
|
||||||
search={{ search }}
|
search={{ search }}
|
||||||
selected={selected}
|
|
||||||
onSelect={handleSelect}
|
|
||||||
onSelectAll={handleSelectAll}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
39
src/components/Cars/List/index.test.jsx
Normal file
39
src/components/Cars/List/index.test.jsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
jest.mock("../../Contexts/VehicleContext");
|
||||||
|
jest.mock("../../Contexts/StatusContext");
|
||||||
|
jest.mock("../../Contexts/UserContext");
|
||||||
|
jest.mock('@material-ui/core/utils/unstable_useId', () =>
|
||||||
|
jest.fn().mockReturnValue('mui-test-id'),
|
||||||
|
);
|
||||||
|
|
||||||
|
import { render, waitFor } from "@testing-library/react";
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
|
||||||
|
import { VehicleProvider } from "../../Contexts/VehicleContext";
|
||||||
|
import { StatusProvider } from "../../Contexts/StatusContext";
|
||||||
|
import { UserProvider, setToken } from "../../Contexts/UserContext";
|
||||||
|
import { TEST_AUTH_OBJECT } from "../../../utils/testing";
|
||||||
|
import MainForm from "./index"
|
||||||
|
|
||||||
|
const renderVehicleTable = async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<VehicleProvider>
|
||||||
|
<StatusProvider>
|
||||||
|
<UserProvider>
|
||||||
|
<BrowserRouter>
|
||||||
|
<MainForm />
|
||||||
|
</BrowserRouter>
|
||||||
|
</UserProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
</VehicleProvider>
|
||||||
|
);
|
||||||
|
await waitFor(() => { /* render */ });
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("VehicleTable", () => {
|
||||||
|
it("Render", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT);
|
||||||
|
const container = await renderVehicleTable();
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
21
src/components/Cars/Status/CANFiltersTab.jsx
Normal file
21
src/components/Cars/Status/CANFiltersTab.jsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useParams } from "react-router";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { Typography } from "@material-ui/core";
|
||||||
|
|
||||||
|
import CANFiltersTable from "../../CANFilter/Table";
|
||||||
|
import useStyles from "../../useStyles";
|
||||||
|
|
||||||
|
const CANFiltersTab = () => {
|
||||||
|
const { vin } = useParams();
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={clsx(classes.paper, classes.tableSize)}>
|
||||||
|
<Typography variant="h6">CAN Filters</Typography>
|
||||||
|
<CANFiltersTable vin={vin} classes={classes} />
|
||||||
|
</div >
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CANFiltersTab;
|
||||||
31
src/components/Cars/Status/CANFiltersTab.test.jsx
Normal file
31
src/components/Cars/Status/CANFiltersTab.test.jsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
jest.mock("../../Contexts/CANFiltersContext");
|
||||||
|
jest.mock("../../Contexts/StatusContext");
|
||||||
|
jest.mock("../../Contexts/UserContext");
|
||||||
|
jest.mock('@material-ui/core/utils/unstable_useId', () =>
|
||||||
|
jest.fn().mockReturnValue('mui-test-id'),
|
||||||
|
);
|
||||||
|
|
||||||
|
import { render, waitFor } from "@testing-library/react";
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
|
||||||
|
import { setToken } from "../../Contexts/UserContext";
|
||||||
|
import { TEST_AUTH_OBJECT } from "../../../utils/testing";
|
||||||
|
import CANFiltersTab from "./CANFiltersTab"
|
||||||
|
|
||||||
|
const renderCANFiltersTab = async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<BrowserRouter>
|
||||||
|
<CANFiltersTab vin="TESTVIN1234567890" />
|
||||||
|
</BrowserRouter>
|
||||||
|
);
|
||||||
|
await waitFor(() => { /* render */ });
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("CANFiltersTab", () => {
|
||||||
|
it("Render", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT);
|
||||||
|
const container = await renderCANFiltersTab();
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
73
src/components/Cars/Status/CarUpdatesTab.jsx
Normal file
73
src/components/Cars/Status/CarUpdatesTab.jsx
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useParams } from "react-router";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { Button, Grid, Typography } from "@material-ui/core";
|
||||||
|
|
||||||
|
import CarECUsTable from "../../Controls/CarECUsTable";
|
||||||
|
import CarUpdatesTable from "../../Controls/CarUpdatesTable";
|
||||||
|
import { logger } from "../../../services/monitoring";
|
||||||
|
import {
|
||||||
|
VehicleProvider,
|
||||||
|
useVehicleContext,
|
||||||
|
} from "../../Contexts/VehicleContext";
|
||||||
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
|
import useStyles from "../../useStyles";
|
||||||
|
|
||||||
|
|
||||||
|
const MainForm = () => {
|
||||||
|
const { vin } = useParams();
|
||||||
|
const classes = useStyles();
|
||||||
|
const { setMessage } = useStatusContext();
|
||||||
|
const { busy, sendCommand } = useVehicleContext();
|
||||||
|
const {
|
||||||
|
token: {
|
||||||
|
idToken: { jwtToken: token },
|
||||||
|
},
|
||||||
|
} = useUserContext();
|
||||||
|
const updateHandler = async (e) => {
|
||||||
|
try {
|
||||||
|
await sendCommand([vin], "ecu", "", token);
|
||||||
|
setMessage(`Sent command to ${vin}`);
|
||||||
|
} catch (error) {
|
||||||
|
setMessage(error.message);
|
||||||
|
logger.error(error.stack);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={clsx(classes.paper, classes.tableSize)}>
|
||||||
|
<Typography variant="h6">Car Updates</Typography>
|
||||||
|
<CarUpdatesTable vin={vin} token={token} classes={classes} />
|
||||||
|
<Grid container className={classes.root} spacing={2}>
|
||||||
|
<Grid item md={4} className={classes.textJustifyAlign}></Grid>
|
||||||
|
<Grid item md={4} className={classes.textCenterAlign}>
|
||||||
|
<Typography variant="h6" className={classes.labelInline}>
|
||||||
|
Car ECUs
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item md={4} className={classes.textRightAlign}>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={busy}
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
className={clsx(classes.formControl, classes.textField)}
|
||||||
|
onClick={updateHandler}
|
||||||
|
>
|
||||||
|
{busy ? "Sending..." : "Refresh"}
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<CarECUsTable vin={vin} token={token} classes={classes} />
|
||||||
|
</div >
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CarUpdatesTab = () => (
|
||||||
|
<VehicleProvider>
|
||||||
|
<MainForm />
|
||||||
|
</VehicleProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default CarUpdatesTab;
|
||||||
39
src/components/Cars/Status/CarUpdatesTab.test.jsx
Normal file
39
src/components/Cars/Status/CarUpdatesTab.test.jsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
jest.mock("../../Contexts/CANFiltersContext");
|
||||||
|
jest.mock("../../Contexts/StatusContext");
|
||||||
|
jest.mock("../../Contexts/UserContext");
|
||||||
|
jest.mock('@material-ui/core/utils/unstable_useId', () =>
|
||||||
|
jest.fn().mockReturnValue('mui-test-id'),
|
||||||
|
);
|
||||||
|
|
||||||
|
import { render, waitFor } from "@testing-library/react";
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
|
||||||
|
import { CANFiltersProvider } from "../../Contexts/CANFiltersContext";
|
||||||
|
import { StatusProvider } from "../../Contexts/StatusContext";
|
||||||
|
import { UserProvider, setToken } from "../../Contexts/UserContext";
|
||||||
|
import { TEST_AUTH_OBJECT } from "../../../utils/testing";
|
||||||
|
import MainForm from "./CarUpdatesTab"
|
||||||
|
|
||||||
|
const renderCarUpdatesTab = async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<CANFiltersProvider>
|
||||||
|
<StatusProvider>
|
||||||
|
<UserProvider>
|
||||||
|
<BrowserRouter>
|
||||||
|
<MainForm vin="TESTVIN1234567890" />
|
||||||
|
</BrowserRouter>
|
||||||
|
</UserProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
</CANFiltersProvider>
|
||||||
|
);
|
||||||
|
await waitFor(() => { /* render */ });
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("CarUpdatesTab", () => {
|
||||||
|
it("Render", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT);
|
||||||
|
const container = await renderCarUpdatesTab();
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`VehicleDetailsTab Render 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-vehicleprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-statusprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-userprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-vehicleprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-3 makeStyles-tableSize-53"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-root-14 MuiGrid-container MuiGrid-spacing-xs-2"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-textCenterAlign-48 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
VIN
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
Log Level
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
info
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-textCenterAlign-48 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
|
>
|
||||||
|
<b>
|
||||||
|
CANBus
|
||||||
|
</b>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
Enabled
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
true
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
Max Memory Buffer Size
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
1
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
Enabled
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
true
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
Max Disk Buffer Size
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
2
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
Filters
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
3
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-textCenterAlign-48 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="/vehicle-update?vin=undefined"
|
||||||
|
style="margin: 5px;"
|
||||||
|
title="Update \\"undefined\\""
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-label="Update \\"undefined\\""
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="/"
|
||||||
|
title="Delete \\"undefined\\""
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-label="Delete \\"undefined\\""
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
95
src/components/Cars/Status/Details/index.jsx
Normal file
95
src/components/Cars/Status/Details/index.jsx
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { Redirect } from "react-router";
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import {
|
||||||
|
Grid,
|
||||||
|
Tooltip,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
import EditIcon from "@material-ui/icons/Edit"
|
||||||
|
import DeleteIcon from "@material-ui/icons/Delete";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
import { useUserContext } from "../../../Contexts/UserContext"
|
||||||
|
import { useStatusContext } from "../../../Contexts/StatusContext";
|
||||||
|
import useStyles from "../../../useStyles";
|
||||||
|
import { logger } from "../../../../services/monitoring";
|
||||||
|
import { useVehicleContext, VehicleProvider } from "../../../Contexts/VehicleContext";
|
||||||
|
|
||||||
|
const MainForm = ({ vin }) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
const { setMessage } = useStatusContext();
|
||||||
|
const { vehicle, getVehicle, deleteVehicle } = useVehicleContext();
|
||||||
|
const [redirect, setRedirect] = useState(null);
|
||||||
|
const { token: { idToken: { jwtToken: token } } } = useUserContext();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
if (!vin || !token) return;
|
||||||
|
await getVehicle(vin, token);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
logger.warn(e.stack);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [token]);
|
||||||
|
|
||||||
|
const onDelete = async () => {
|
||||||
|
try {
|
||||||
|
await deleteVehicle(vin, token);
|
||||||
|
setMessage(`Deleted ${vin}`)
|
||||||
|
setRedirect(`/vehicles`);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
logger.warn(e.stack);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (redirect && redirect.length > 0) {
|
||||||
|
return <Redirect to={redirect} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={clsx(classes.paper, classes.tableSize)}>
|
||||||
|
<Grid container className={classes.root} spacing={2}>
|
||||||
|
<Grid item md={12} className={classes.textCenterAlign}>
|
||||||
|
<p><b>VIN</b>: {vin}</p>
|
||||||
|
{vehicle.log_level != null && (
|
||||||
|
<p><b>Log Level</b>: {vehicle.log_level}</p>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
{vehicle.canbus && (
|
||||||
|
<Grid item md={12} className={classes.textCenterAlign}>
|
||||||
|
<b>CANBus</b>
|
||||||
|
<p><b>Enabled</b>: {vehicle.canbus.enabled.toString()}</p>
|
||||||
|
<p><b>Max Memory Buffer Size</b>: {vehicle.canbus.max_mem_buffer_size ?? "Default"}</p>
|
||||||
|
<p><b>Enabled</b>: {vehicle.canbus.data_logger_enabled.toString()}</p>
|
||||||
|
<p><b>Max Disk Buffer Size</b>: {vehicle.canbus.max_disk_buffer_size ?? "Default"}</p>
|
||||||
|
<p><b>Filters</b>: {vehicle.canbus.filters ? vehicle.canbus.filters.length : 0}</p>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
<Grid item md={12} className={classes.textCenterAlign}>
|
||||||
|
<Tooltip key={`update-${vin}`} title={`Update "${vin}"`}>
|
||||||
|
<Link to={`/vehicle-update?vin=${vin}`} style={{ margin: 5 }}>
|
||||||
|
<EditIcon aria-label={`Update "${vin}"`} />
|
||||||
|
</Link>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip key={`delete-${vin}`} title={`Delete "${vin}"`}>
|
||||||
|
<Link to="#" onClick={onDelete}>
|
||||||
|
<DeleteIcon aria-label={`Delete "${vin}"`} />
|
||||||
|
</Link>
|
||||||
|
</Tooltip>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</div >
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CarDetails = (props) => (
|
||||||
|
<VehicleProvider>
|
||||||
|
<MainForm {...props} />
|
||||||
|
</VehicleProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default CarDetails;
|
||||||
36
src/components/Cars/Status/Details/index.test.jsx
Normal file
36
src/components/Cars/Status/Details/index.test.jsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
jest.mock("../../../Contexts/VehicleContext");
|
||||||
|
jest.mock("../../../Contexts/StatusContext");
|
||||||
|
jest.mock("../../../Contexts/UserContext");
|
||||||
|
|
||||||
|
import { render, waitFor } from "@testing-library/react";
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
|
||||||
|
import { VehicleProvider } from "../../../Contexts/VehicleContext";
|
||||||
|
import { StatusProvider } from "../../../Contexts/StatusContext";
|
||||||
|
import { UserProvider, setToken } from "../../../Contexts/UserContext";
|
||||||
|
import { TEST_AUTH_OBJECT } from "../../../../utils/testing";
|
||||||
|
import MainForm from "./index"
|
||||||
|
|
||||||
|
const renderVehicleDetailsTab = async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<VehicleProvider>
|
||||||
|
<StatusProvider>
|
||||||
|
<UserProvider>
|
||||||
|
<BrowserRouter>
|
||||||
|
<MainForm />
|
||||||
|
</BrowserRouter>
|
||||||
|
</UserProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
</VehicleProvider>
|
||||||
|
);
|
||||||
|
await waitFor(() => { /* render */ });
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("VehicleDetailsTab", () => {
|
||||||
|
it("Render", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT);
|
||||||
|
const container = await renderVehicleDetailsTab();
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
21
src/components/Cars/Status/DetailsTab.jsx
Normal file
21
src/components/Cars/Status/DetailsTab.jsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useParams } from "react-router";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { Typography } from "@material-ui/core";
|
||||||
|
|
||||||
|
import CarDetails from "./Details";
|
||||||
|
import useStyles from "../../useStyles";
|
||||||
|
|
||||||
|
const CarDetailsTab = () => {
|
||||||
|
const { vin } = useParams();
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={clsx(classes.paper, classes.tableSize)}>
|
||||||
|
<Typography variant="h6">Vehicle Details</Typography>
|
||||||
|
<CarDetails vin={vin} classes={classes} />
|
||||||
|
</div >
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CarDetailsTab;
|
||||||
41
src/components/Cars/Status/DetailsTab.test.jsx
Normal file
41
src/components/Cars/Status/DetailsTab.test.jsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
jest.mock("../../Contexts/VehicleContext");
|
||||||
|
jest.mock("../../Contexts/StatusContext");
|
||||||
|
jest.mock("../../Contexts/UserContext");
|
||||||
|
jest.mock('@material-ui/core/utils/unstable_useId', () =>
|
||||||
|
jest.fn().mockReturnValue('mui-test-id'),
|
||||||
|
);
|
||||||
|
|
||||||
|
import { render, waitFor } from "@testing-library/react";
|
||||||
|
import { MemoryRouter, Route } from "react-router-dom";
|
||||||
|
|
||||||
|
import { VehicleProvider } from "../../Contexts/VehicleContext";
|
||||||
|
import { StatusProvider } from "../../Contexts/StatusContext";
|
||||||
|
import { UserProvider, setToken } from "../../Contexts/UserContext";
|
||||||
|
import { TEST_AUTH_OBJECT } from "../../../utils/testing";
|
||||||
|
import MainForm from "./DetailsTab"
|
||||||
|
|
||||||
|
const renderDetailsTab = async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<VehicleProvider>
|
||||||
|
<StatusProvider>
|
||||||
|
<UserProvider>
|
||||||
|
<MemoryRouter initialEntries={['/testroute/TESTVIN1234567890']}>
|
||||||
|
<Route path="/testroute/:vin">
|
||||||
|
<MainForm vin="TESTVIN1234567890" />
|
||||||
|
</Route>
|
||||||
|
</MemoryRouter>
|
||||||
|
</UserProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
</VehicleProvider >
|
||||||
|
);
|
||||||
|
await waitFor(() => { /* render */ });
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("DetailsTab", () => {
|
||||||
|
it("Render", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT);
|
||||||
|
const container = await renderDetailsTab();
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,450 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`CANFiltersTab Render 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-3 makeStyles-tableSize-53"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
class="MuiTypography-root MuiTypography-h6"
|
||||||
|
>
|
||||||
|
CAN Filters
|
||||||
|
</h6>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-canfiltersprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-3 makeStyles-tableSize-53"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-root-14 MuiGrid-container MuiGrid-spacing-xs-2"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-textJustifyAlign-47 MuiGrid-item MuiGrid-grid-md-4"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="makeStyles-labelInline-9"
|
||||||
|
href="/filter-add?vin=undefined"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiSvgIcon-fontSizeLarge"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-textCenterAlign-48 MuiGrid-item MuiGrid-grid-md-8"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root makeStyles-margin-28 makeStyles-fullWidth-50"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated"
|
||||||
|
data-shrink="false"
|
||||||
|
for="search"
|
||||||
|
>
|
||||||
|
Search
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiInput-root MuiInput-underline MuiInputBase-formControl MuiInput-formControl MuiInputBase-adornedEnd"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiInput-input MuiInputBase-inputAdornedEnd"
|
||||||
|
id="search"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="MuiInputAdornment-root MuiInputAdornment-positionEnd"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-label="search"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table
|
||||||
|
class="MuiTable-root"
|
||||||
|
>
|
||||||
|
<thead
|
||||||
|
class="MuiTableHead-root"
|
||||||
|
>
|
||||||
|
<tr
|
||||||
|
class="MuiTableRow-root MuiTableRow-head"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiTableSortLabel-root"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
CAN ID
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiTableSortLabel-root"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Interval (ms)
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
Actions
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody
|
||||||
|
class="MuiTableBody-root"
|
||||||
|
>
|
||||||
|
<tr
|
||||||
|
class="MuiTableRow-root"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
123
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
1000
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="/filter-update?vin=undefined&can_id=123&interval=1000"
|
||||||
|
style="margin: 5px;"
|
||||||
|
title="Update \\"123\\""
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-label="Update 123"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="/"
|
||||||
|
title="Delete \\"123\\""
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-label="Delete 123"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
class="MuiTableRow-root"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
456-789
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
2000
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="/filter-update?vin=undefined&can_id=456-789&interval=2000"
|
||||||
|
style="margin: 5px;"
|
||||||
|
title="Update \\"456-789\\""
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-label="Update 456-789"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="/"
|
||||||
|
title="Delete \\"456-789\\""
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-label="Delete 456-789"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
class="MuiTableRow-root"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
1
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
0
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="/filter-update?vin=undefined&can_id=1&interval=0"
|
||||||
|
style="margin: 5px;"
|
||||||
|
title="Update \\"1\\""
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-label="Update 1"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="/"
|
||||||
|
title="Delete \\"1\\""
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-label="Delete 1"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
<tfoot
|
||||||
|
class="MuiTableFooter-root"
|
||||||
|
>
|
||||||
|
<tr
|
||||||
|
class="MuiTableRow-root MuiTableRow-footer"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-footer MuiTablePagination-root"
|
||||||
|
colspan="8"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiToolbar-root MuiToolbar-regular MuiTablePagination-toolbar MuiToolbar-gutters"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTablePagination-spacer"
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
class="MuiTypography-root MuiTablePagination-caption MuiTypography-body2 MuiTypography-colorInherit"
|
||||||
|
>
|
||||||
|
Rows per page:
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiTablePagination-input MuiTablePagination-selectRoot"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
aria-label="rows per page"
|
||||||
|
class="MuiSelect-root MuiSelect-select MuiTablePagination-select MuiInputBase-input"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
class="MuiTablePagination-menuItem"
|
||||||
|
value="5"
|
||||||
|
>
|
||||||
|
5
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
class="MuiTablePagination-menuItem"
|
||||||
|
value="10"
|
||||||
|
>
|
||||||
|
10
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
class="MuiTablePagination-menuItem"
|
||||||
|
value="25"
|
||||||
|
>
|
||||||
|
25
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
class="MuiTablePagination-menuItem"
|
||||||
|
value="100"
|
||||||
|
>
|
||||||
|
100
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiSelect-icon MuiTablePagination-selectIcon"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M7 10l5 5 5-5z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="MuiTypography-root MuiTablePagination-caption MuiTypography-body2 MuiTypography-colorInherit"
|
||||||
|
>
|
||||||
|
1-3 of 3
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="MuiTablePagination-actions"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-label="Previous page"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit Mui-disabled Mui-disabled"
|
||||||
|
disabled=""
|
||||||
|
tabindex="-1"
|
||||||
|
title="Previous page"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M15.41 16.09l-4.58-4.59 4.58-4.59L14 5.5l-6 6 6 6z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-label="Next page"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit Mui-disabled Mui-disabled"
|
||||||
|
disabled=""
|
||||||
|
tabindex="-1"
|
||||||
|
title="Next page"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@@ -0,0 +1,606 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`CarUpdatesTab Render 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-canfiltersprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-statusprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-userprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-3 makeStyles-tableSize-53"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
class="MuiTypography-root MuiTypography-h6"
|
||||||
|
>
|
||||||
|
Car Updates
|
||||||
|
</h6>
|
||||||
|
<table
|
||||||
|
class="MuiTable-root"
|
||||||
|
>
|
||||||
|
<thead
|
||||||
|
class="MuiTableHead-root"
|
||||||
|
>
|
||||||
|
<tr
|
||||||
|
class="MuiTableRow-root MuiTableRow-head"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
aria-sort="descending"
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiTableSortLabel-root MuiTableSortLabel-active"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
ID
|
||||||
|
<span
|
||||||
|
class="makeStyles-hiddenSortSpan-27"
|
||||||
|
>
|
||||||
|
sorted descending
|
||||||
|
</span>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionDesc"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiTableSortLabel-root"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Name
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiTableSortLabel-root"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Status
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiTableSortLabel-root"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Created
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiTableSortLabel-root"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Updated
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody
|
||||||
|
class="MuiTableBody-root"
|
||||||
|
/>
|
||||||
|
<tfoot
|
||||||
|
class="MuiTableFooter-root"
|
||||||
|
>
|
||||||
|
<tr
|
||||||
|
class="MuiTableRow-root MuiTableRow-footer"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-footer MuiTablePagination-root"
|
||||||
|
colspan="5"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiToolbar-root MuiToolbar-regular MuiTablePagination-toolbar MuiToolbar-gutters"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTablePagination-spacer"
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
class="MuiTypography-root MuiTablePagination-caption MuiTypography-body2 MuiTypography-colorInherit"
|
||||||
|
>
|
||||||
|
Rows per page:
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiTablePagination-input MuiTablePagination-selectRoot"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
aria-label="rows per page"
|
||||||
|
class="MuiSelect-root MuiSelect-select MuiTablePagination-select MuiInputBase-input"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
class="MuiTablePagination-menuItem"
|
||||||
|
value="5"
|
||||||
|
>
|
||||||
|
5
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
class="MuiTablePagination-menuItem"
|
||||||
|
value="10"
|
||||||
|
>
|
||||||
|
10
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
class="MuiTablePagination-menuItem"
|
||||||
|
value="25"
|
||||||
|
>
|
||||||
|
25
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
class="MuiTablePagination-menuItem"
|
||||||
|
value="100"
|
||||||
|
>
|
||||||
|
100
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiSelect-icon MuiTablePagination-selectIcon"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M7 10l5 5 5-5z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="MuiTypography-root MuiTablePagination-caption MuiTypography-body2 MuiTypography-colorInherit"
|
||||||
|
>
|
||||||
|
0-0 of 0
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="MuiTablePagination-actions"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-label="Previous page"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit Mui-disabled Mui-disabled"
|
||||||
|
disabled=""
|
||||||
|
tabindex="-1"
|
||||||
|
title="Previous page"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M15.41 16.09l-4.58-4.59 4.58-4.59L14 5.5l-6 6 6 6z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-label="Next page"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit Mui-disabled Mui-disabled"
|
||||||
|
disabled=""
|
||||||
|
tabindex="-1"
|
||||||
|
title="Next page"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-root-14 MuiGrid-container MuiGrid-spacing-xs-2"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-textJustifyAlign-47 MuiGrid-item MuiGrid-grid-md-4"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-textCenterAlign-48 MuiGrid-item MuiGrid-grid-md-4"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
class="MuiTypography-root makeStyles-labelInline-9 MuiTypography-h6"
|
||||||
|
>
|
||||||
|
Car ECUs
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-textRightAlign-49 MuiGrid-item MuiGrid-grid-md-4"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-formControl-7 makeStyles-textField-29 MuiButton-containedPrimary"
|
||||||
|
tabindex="0"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiButton-label"
|
||||||
|
>
|
||||||
|
Refresh
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-3 makeStyles-tableSize-53"
|
||||||
|
>
|
||||||
|
<table
|
||||||
|
class="MuiTable-root"
|
||||||
|
>
|
||||||
|
<thead
|
||||||
|
class="MuiTableHead-root"
|
||||||
|
>
|
||||||
|
<tr
|
||||||
|
class="MuiTableRow-root MuiTableRow-head"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
aria-sort="descending"
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiTableSortLabel-root MuiTableSortLabel-active"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
ECU
|
||||||
|
<span
|
||||||
|
class="makeStyles-hiddenSortSpan-27"
|
||||||
|
>
|
||||||
|
sorted descending
|
||||||
|
</span>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionDesc"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiTableSortLabel-root"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
SW Version
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiTableSortLabel-root"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
HW Version
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiTableSortLabel-root"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Config
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiTableSortLabel-root"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Created
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiTableSortLabel-root"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Updated
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody
|
||||||
|
class="MuiTableBody-root"
|
||||||
|
/>
|
||||||
|
<tfoot
|
||||||
|
class="MuiTableFooter-root"
|
||||||
|
>
|
||||||
|
<tr
|
||||||
|
class="MuiTableRow-root MuiTableRow-footer"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-footer MuiTablePagination-root"
|
||||||
|
colspan="10"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiToolbar-root MuiToolbar-regular MuiTablePagination-toolbar MuiToolbar-gutters"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTablePagination-spacer"
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
class="MuiTypography-root MuiTablePagination-caption MuiTypography-body2 MuiTypography-colorInherit"
|
||||||
|
>
|
||||||
|
Rows per page:
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiTablePagination-input MuiTablePagination-selectRoot"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
aria-label="rows per page"
|
||||||
|
class="MuiSelect-root MuiSelect-select MuiTablePagination-select MuiInputBase-input"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
class="MuiTablePagination-menuItem"
|
||||||
|
value="5"
|
||||||
|
>
|
||||||
|
5
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
class="MuiTablePagination-menuItem"
|
||||||
|
value="10"
|
||||||
|
>
|
||||||
|
10
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
class="MuiTablePagination-menuItem"
|
||||||
|
value="25"
|
||||||
|
>
|
||||||
|
25
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
class="MuiTablePagination-menuItem"
|
||||||
|
value="100"
|
||||||
|
>
|
||||||
|
100
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiSelect-icon MuiTablePagination-selectIcon"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M7 10l5 5 5-5z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="MuiTypography-root MuiTablePagination-caption MuiTypography-body2 MuiTypography-colorInherit"
|
||||||
|
>
|
||||||
|
0-0 of 0
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="MuiTablePagination-actions"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-label="Previous page"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit Mui-disabled Mui-disabled"
|
||||||
|
disabled=""
|
||||||
|
tabindex="-1"
|
||||||
|
title="Previous page"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M15.41 16.09l-4.58-4.59 4.58-4.59L14 5.5l-6 6 6 6z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-label="Next page"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit Mui-disabled Mui-disabled"
|
||||||
|
disabled=""
|
||||||
|
tabindex="-1"
|
||||||
|
title="Next page"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`DetailsTab Render 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-vehicleprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-statusprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-userprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-3 makeStyles-tableSize-53"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
class="MuiTypography-root MuiTypography-h6"
|
||||||
|
>
|
||||||
|
Vehicle Details
|
||||||
|
</h6>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-vehicleprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-3 makeStyles-tableSize-53"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-root-14 MuiGrid-container MuiGrid-spacing-xs-2"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-textCenterAlign-48 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
VIN
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
TESTVIN1234567890
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
Log Level
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
info
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-textCenterAlign-48 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
|
>
|
||||||
|
<b>
|
||||||
|
CANBus
|
||||||
|
</b>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
Enabled
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
true
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
Max Memory Buffer Size
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
1
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
Enabled
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
true
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
Max Disk Buffer Size
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
2
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
Filters
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
3
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-textCenterAlign-48 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="/vehicle-update?vin=TESTVIN1234567890"
|
||||||
|
style="margin: 5px;"
|
||||||
|
title="Update \\"TESTVIN1234567890\\""
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-label="Update \\"TESTVIN1234567890\\""
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="/testroute/TESTVIN1234567890"
|
||||||
|
title="Delete \\"TESTVIN1234567890\\""
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-label="Delete \\"TESTVIN1234567890\\""
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
187
src/components/Cars/Status/__snapshots__/index.test.jsx.snap
Normal file
187
src/components/Cars/Status/__snapshots__/index.test.jsx.snap
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`CarStatus Render 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-canfiltersprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-statusprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-userprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-3 makeStyles-tableSize-53"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiBox-root MuiBox-root-62 makeStyles-tableToolbar-30"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTabs-root"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTabs-scroller MuiTabs-fixed"
|
||||||
|
style="overflow: hidden;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-label="car tabs"
|
||||||
|
class="MuiTabs-flexContainer"
|
||||||
|
role="tablist"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-controls="tabpanel-0"
|
||||||
|
aria-selected="true"
|
||||||
|
class="MuiButtonBase-root MuiTab-root MuiTab-textColorInherit Mui-selected"
|
||||||
|
id="tab-0"
|
||||||
|
role="tab"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiTab-wrapper"
|
||||||
|
>
|
||||||
|
Details
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-controls="tabpanel-1"
|
||||||
|
aria-selected="false"
|
||||||
|
class="MuiButtonBase-root MuiTab-root MuiTab-textColorInherit"
|
||||||
|
id="tab-1"
|
||||||
|
role="tab"
|
||||||
|
tabindex="-1"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiTab-wrapper"
|
||||||
|
>
|
||||||
|
Car Updates
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-controls="tabpanel-2"
|
||||||
|
aria-selected="false"
|
||||||
|
class="MuiButtonBase-root MuiTab-root MuiTab-textColorInherit"
|
||||||
|
id="tab-2"
|
||||||
|
role="tab"
|
||||||
|
tabindex="-1"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiTab-wrapper"
|
||||||
|
>
|
||||||
|
CAN Filters
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
class="PrivateTabIndicator-root-63 PrivateTabIndicator-colorSecondary-65 MuiTabs-indicator"
|
||||||
|
style="left: 0px; width: 0px;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
aria-labelledby="tab-0"
|
||||||
|
id="tabpanel-0"
|
||||||
|
role="tabpanel"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiBox-root MuiBox-root-67"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-3 makeStyles-tableSize-53"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
class="MuiTypography-root MuiTypography-h6"
|
||||||
|
>
|
||||||
|
Vehicle Details
|
||||||
|
</h6>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-3 makeStyles-tableSize-53"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-root-14 MuiGrid-container MuiGrid-spacing-xs-2"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-textCenterAlign-48 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
VIN
|
||||||
|
</b>
|
||||||
|
:
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-textCenterAlign-48 MuiGrid-item MuiGrid-grid-md-12"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="/vehicle-update?vin=undefined"
|
||||||
|
style="margin: 5px;"
|
||||||
|
title="Update \\"undefined\\""
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-label="Update \\"undefined\\""
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="/"
|
||||||
|
title="Delete \\"undefined\\""
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-label="Delete \\"undefined\\""
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
aria-labelledby="tab-1"
|
||||||
|
hidden=""
|
||||||
|
id="tabpanel-1"
|
||||||
|
role="tabpanel"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
aria-labelledby="tab-2"
|
||||||
|
hidden=""
|
||||||
|
id="tabpanel-2"
|
||||||
|
role="tabpanel"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@@ -1,38 +1,30 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { useParams } from "react-router";
|
import { useParams } from "react-router";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { Button, Grid, Typography } from "@material-ui/core";
|
import { Box, Tab, Tabs } from "@material-ui/core";
|
||||||
|
|
||||||
import CarECUsTable from "../../Controls/CarECUsTable";
|
import CarDetailsTab from "./DetailsTab";
|
||||||
import CarUpdatesTable from "../../Controls/CarUpdatesTable";
|
import CarUpdatesTab from "./CarUpdatesTab";
|
||||||
import { logger } from "../../../services/monitoring";
|
import CANFiltersTab from "./CANFiltersTab";
|
||||||
import {
|
import TabPanel from "../../Controls/TabPanel";
|
||||||
VehicleProvider,
|
|
||||||
useVehicleContext,
|
|
||||||
} from "../../Contexts/VehicleContext";
|
|
||||||
import { useUserContext } from "../../Contexts/UserContext";
|
|
||||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
import useStyles from "../../useStyles";
|
import useStyles from "../../useStyles";
|
||||||
|
|
||||||
const MainForm = () => {
|
const tabHashes = ["details", "updates", "filters"];
|
||||||
|
|
||||||
|
const CarStatus = () => {
|
||||||
const { vin } = useParams();
|
const { vin } = useParams();
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const { setTitle, setSitePath, setMessage } = useStatusContext();
|
const { setTitle, setSitePath } = useStatusContext();
|
||||||
const { busy, sendCommand } = useVehicleContext();
|
const { hash } = useLocation();
|
||||||
const {
|
const [tabIndex, setTabIndex] = React.useState(0);
|
||||||
token: {
|
|
||||||
idToken: { jwtToken: token },
|
useEffect(() => {
|
||||||
},
|
const key = hash.replace("#", "");
|
||||||
} = useUserContext();
|
const index = tabHashes.findIndex((element) => element === key);
|
||||||
const updateHandler = async (e) => {
|
if (index >= 0) setTabIndex(index);
|
||||||
try {
|
}, [hash]);
|
||||||
await sendCommand([vin], "ecu", "", token);
|
|
||||||
setMessage(`Sent command to ${vin}`);
|
|
||||||
} catch (error) {
|
|
||||||
setMessage(error.message);
|
|
||||||
logger.error(error.stack);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const title = `Vehicle ${vin} Details`;
|
const title = `Vehicle ${vin} Details`;
|
||||||
@@ -49,39 +41,48 @@ const MainForm = () => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [vin]);
|
}, [vin]);
|
||||||
|
|
||||||
|
const handleTabChange = (_event, newIndex) => {
|
||||||
|
setTabIndex(newIndex);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx(classes.paper, classes.tableSize)}>
|
<div className={clsx(classes.paper, classes.tableSize)}>
|
||||||
<Typography variant="h6">Car Updates</Typography>
|
<Box
|
||||||
<CarUpdatesTable vin={vin} token={token} classes={classes} />
|
className={classes.tableToolbar}
|
||||||
<Grid container className={classes.root} spacing={2}>
|
sx={{ borderBottom: 1, borderColor: "divider" }}
|
||||||
<Grid item md={4} className={classes.textJustifyAlign}></Grid>
|
>
|
||||||
<Grid item md={4} className={classes.textCenterAlign}>
|
<Tabs
|
||||||
<Typography variant="h6" className={classes.labelInline}>
|
value={tabIndex}
|
||||||
Car ECUs
|
onChange={handleTabChange}
|
||||||
</Typography>
|
aria-label="car tabs"
|
||||||
</Grid>
|
indicatorColor="secondary"
|
||||||
<Grid item md={4} className={classes.textRightAlign}>
|
>
|
||||||
<Button
|
<Tab label="Details" {...tabProps(0)} />
|
||||||
type="submit"
|
<Tab label="Car Updates" {...tabProps(1)} />
|
||||||
disabled={busy}
|
<Tab label="CAN Filters" {...tabProps(2)} />
|
||||||
variant="contained"
|
</Tabs>
|
||||||
color="primary"
|
</Box>
|
||||||
className={clsx(classes.formControl, classes.textField)}
|
|
||||||
onClick={updateHandler}
|
<TabPanel value={tabIndex} index={0}>
|
||||||
>
|
<CarDetailsTab />
|
||||||
{busy ? "Sending..." : "Refresh"}
|
</TabPanel>
|
||||||
</Button>
|
|
||||||
</Grid>
|
<TabPanel value={tabIndex} index={1}>
|
||||||
</Grid>
|
<CarUpdatesTab />
|
||||||
<CarECUsTable vin={vin} token={token} classes={classes} />
|
</TabPanel>
|
||||||
|
|
||||||
|
<TabPanel value={tabIndex} index={2}>
|
||||||
|
<CANFiltersTab />
|
||||||
|
</TabPanel>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const CarStatus = () => (
|
function tabProps(index) {
|
||||||
<VehicleProvider>
|
return {
|
||||||
<MainForm />
|
id: `tab-${index}`,
|
||||||
</VehicleProvider>
|
"aria-controls": `tabpanel-${index}`,
|
||||||
);
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default CarStatus;
|
export default CarStatus;
|
||||||
|
|||||||
39
src/components/Cars/Status/index.test.jsx
Normal file
39
src/components/Cars/Status/index.test.jsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
jest.mock("../../Contexts/CANFiltersContext");
|
||||||
|
jest.mock("../../Contexts/StatusContext");
|
||||||
|
jest.mock("../../Contexts/UserContext");
|
||||||
|
jest.mock('@material-ui/core/utils/unstable_useId', () =>
|
||||||
|
jest.fn().mockReturnValue('mui-test-id'),
|
||||||
|
);
|
||||||
|
|
||||||
|
import { render, waitFor } from "@testing-library/react";
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
|
||||||
|
import { CANFiltersProvider } from "../../Contexts/CANFiltersContext";
|
||||||
|
import { StatusProvider } from "../../Contexts/StatusContext";
|
||||||
|
import { UserProvider, setToken } from "../../Contexts/UserContext";
|
||||||
|
import { TEST_AUTH_OBJECT } from "../../../utils/testing";
|
||||||
|
import CarStatus from "./index"
|
||||||
|
|
||||||
|
const renderCarStatus = async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<CANFiltersProvider>
|
||||||
|
<StatusProvider>
|
||||||
|
<UserProvider>
|
||||||
|
<BrowserRouter>
|
||||||
|
<CarStatus vin="TESTVIN1234567890" />
|
||||||
|
</BrowserRouter>
|
||||||
|
</UserProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
</CANFiltersProvider>
|
||||||
|
);
|
||||||
|
await waitFor(() => { /* render */ });
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("CarStatus", () => {
|
||||||
|
it("Render", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT);
|
||||||
|
const container = await renderCarStatus();
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import {
|
|
||||||
Backdrop,
|
|
||||||
Modal,
|
|
||||||
Fade,
|
|
||||||
Table,
|
|
||||||
TableHead,
|
|
||||||
TableRow,
|
|
||||||
TableCell,
|
|
||||||
TableBody,
|
|
||||||
} from "@material-ui/core";
|
|
||||||
|
|
||||||
import useStyles from "../../useStyles";
|
|
||||||
import { useUpdatesContext } from "../../Contexts/UpdatesContext";
|
|
||||||
import { useUserContext } from "../../Contexts/UserContext";
|
|
||||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
|
||||||
import { LocalDateTimeString } from "../../../utils/dates";
|
|
||||||
|
|
||||||
export default function CarStatusModal(props) {
|
|
||||||
const classes = useStyles();
|
|
||||||
const [updates, setUpdates] = useState([]);
|
|
||||||
const { setMessage } = useStatusContext();
|
|
||||||
const { getVINUpdates } = useUpdatesContext();
|
|
||||||
const {
|
|
||||||
token: {
|
|
||||||
idToken: { jwtToken: token },
|
|
||||||
},
|
|
||||||
} = useUserContext();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
if (!props.vin) return;
|
|
||||||
const result = await getVINUpdates(props.vin, token);
|
|
||||||
if (result.error) {
|
|
||||||
throw new Error(`Get VIN updates error. ${result.message}`);
|
|
||||||
} else {
|
|
||||||
setUpdates(result.data);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
setMessage(e.message);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [props.vin]);
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Modal
|
|
||||||
aria-labelledby="transition-modal-title"
|
|
||||||
aria-describedby="transition-modal-description"
|
|
||||||
className={classes.modal}
|
|
||||||
open={props.vin !== null && props.vin !== undefined}
|
|
||||||
onClose={props.handleClose}
|
|
||||||
BackdropComponent={Backdrop}
|
|
||||||
BackdropProps={{
|
|
||||||
timeout: 500,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Fade in={props.vin}>
|
|
||||||
<div className={classes.modaldialog}>
|
|
||||||
<h2 id="transition-modal-title">{props.vin} Updates</h2>
|
|
||||||
<Table>
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell align="center">Date</TableCell>
|
|
||||||
<TableCell align="center">Update</TableCell>
|
|
||||||
<TableCell align="center">Status</TableCell>
|
|
||||||
<TableCell align="center">Updated</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{updates.map((update) => (
|
|
||||||
<TableRow key={update.id}>
|
|
||||||
<TableCell align="center">
|
|
||||||
{LocalDateTimeString(update.created)}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="center">{`${update.updatepackage.package_name} ${update.updatepackage.version}`}</TableCell>
|
|
||||||
<TableCell align="center">{update.status}</TableCell>
|
|
||||||
<TableCell align="center">
|
|
||||||
{LocalDateTimeString(update.updated)}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
</Fade>
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
731
src/components/Cars/Update/__snapshots__/index.test.jsx.snap
Normal file
731
src/components/Cars/Update/__snapshots__/index.test.jsx.snap
Normal file
@@ -0,0 +1,731 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`VehicleUpdate Render 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-vehicleprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-statusprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-userprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-vehicleprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-3"
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
action="{onSubmit}"
|
||||||
|
class="makeStyles-form-5"
|
||||||
|
novalidate=""
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-disabled Mui-disabled Mui-required Mui-required"
|
||||||
|
data-shrink="false"
|
||||||
|
for="vin"
|
||||||
|
id="vin-label"
|
||||||
|
>
|
||||||
|
VIN
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root Mui-disabled Mui-disabled MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input Mui-disabled Mui-disabled"
|
||||||
|
disabled=""
|
||||||
|
id="vin"
|
||||||
|
maxlength="17"
|
||||||
|
name="vin"
|
||||||
|
readonly=""
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-62 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-64"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
VIN
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined MuiFormLabel-filled Mui-required Mui-required"
|
||||||
|
data-shrink="true"
|
||||||
|
for="model"
|
||||||
|
id="model-label"
|
||||||
|
>
|
||||||
|
Model
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="model"
|
||||||
|
maxlength="255"
|
||||||
|
name="model"
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
|
value="Ocean"
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-62 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-64 PrivateNotchedOutline-legendNotched-65"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Model
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined MuiFormLabel-filled Mui-required Mui-required"
|
||||||
|
data-shrink="true"
|
||||||
|
for="year"
|
||||||
|
id="year-label"
|
||||||
|
>
|
||||||
|
Year
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="year"
|
||||||
|
maxlength="4"
|
||||||
|
minlength="4"
|
||||||
|
name="year"
|
||||||
|
required=""
|
||||||
|
type="number"
|
||||||
|
value="2022"
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-62 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-64 PrivateNotchedOutline-legendNotched-65"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Year
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined MuiFormLabel-filled Mui-required Mui-required"
|
||||||
|
data-shrink="true"
|
||||||
|
for="trim"
|
||||||
|
id="trim-label"
|
||||||
|
>
|
||||||
|
Trim
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="trim"
|
||||||
|
maxlength="4"
|
||||||
|
minlength="4"
|
||||||
|
name="trim"
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
|
value="Base"
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-62 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-64 PrivateNotchedOutline-legendNotched-65"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Trim
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root"
|
||||||
|
id="demo-row-radio-buttons-group-label"
|
||||||
|
>
|
||||||
|
Log Level
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
aria-labelledby="demo-row-radio-buttons-group-label"
|
||||||
|
class="MuiFormGroup-root MuiFormGroup-row"
|
||||||
|
margin="normal"
|
||||||
|
role="radiogroup"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormControlLabel-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-66 MuiRadio-root MuiRadio-colorSecondary MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="PrivateSwitchBase-input-69"
|
||||||
|
name="log-level-group"
|
||||||
|
type="radio"
|
||||||
|
value="trace"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="PrivateRadioButtonIcon-root-70"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root PrivateRadioButtonIcon-layer-71"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.465 8.465C9.37 7.56 10.62 7 12 7C14.76 7 17 9.24 17 12C17 13.38 16.44 14.63 15.535 15.535C14.63 16.44 13.38 17 12 17C9.24 17 7 14.76 7 12C7 10.62 7.56 9.37 8.465 8.465Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
Trace
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
class="MuiFormControlLabel-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-66 MuiRadio-root MuiRadio-colorSecondary MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="PrivateSwitchBase-input-69"
|
||||||
|
name="log-level-group"
|
||||||
|
type="radio"
|
||||||
|
value="debug"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="PrivateRadioButtonIcon-root-70"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root PrivateRadioButtonIcon-layer-71"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.465 8.465C9.37 7.56 10.62 7 12 7C14.76 7 17 9.24 17 12C17 13.38 16.44 14.63 15.535 15.535C14.63 16.44 13.38 17 12 17C9.24 17 7 14.76 7 12C7 10.62 7.56 9.37 8.465 8.465Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
Debug
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
class="MuiFormControlLabel-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-66 MuiRadio-root MuiRadio-colorSecondary PrivateSwitchBase-checked-67 Mui-checked MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
checked=""
|
||||||
|
class="PrivateSwitchBase-input-69"
|
||||||
|
name="log-level-group"
|
||||||
|
type="radio"
|
||||||
|
value="info"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="PrivateRadioButtonIcon-root-70 PrivateRadioButtonIcon-checked-72"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root PrivateRadioButtonIcon-layer-71"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.465 8.465C9.37 7.56 10.62 7 12 7C14.76 7 17 9.24 17 12C17 13.38 16.44 14.63 15.535 15.535C14.63 16.44 13.38 17 12 17C9.24 17 7 14.76 7 12C7 10.62 7.56 9.37 8.465 8.465Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
Info
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
class="MuiFormControlLabel-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-66 MuiRadio-root MuiRadio-colorSecondary MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="PrivateSwitchBase-input-69"
|
||||||
|
name="log-level-group"
|
||||||
|
type="radio"
|
||||||
|
value="warn"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="PrivateRadioButtonIcon-root-70"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root PrivateRadioButtonIcon-layer-71"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.465 8.465C9.37 7.56 10.62 7 12 7C14.76 7 17 9.24 17 12C17 13.38 16.44 14.63 15.535 15.535C14.63 16.44 13.38 17 12 17C9.24 17 7 14.76 7 12C7 10.62 7.56 9.37 8.465 8.465Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
Warn
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
class="MuiFormControlLabel-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-66 MuiRadio-root MuiRadio-colorSecondary MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="PrivateSwitchBase-input-69"
|
||||||
|
name="log-level-group"
|
||||||
|
type="radio"
|
||||||
|
value="error"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="PrivateRadioButtonIcon-root-70"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root PrivateRadioButtonIcon-layer-71"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.465 8.465C9.37 7.56 10.62 7 12 7C14.76 7 17 9.24 17 12C17 13.38 16.44 14.63 15.535 15.535C14.63 16.44 13.38 17 12 17C9.24 17 7 14.76 7 12C7 10.62 7.56 9.37 8.465 8.465Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
Error
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
class="MuiFormControlLabel-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-66 MuiRadio-root MuiRadio-colorSecondary MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="PrivateSwitchBase-input-69"
|
||||||
|
name="log-level-group"
|
||||||
|
type="radio"
|
||||||
|
value="critical"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="PrivateRadioButtonIcon-root-70"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root PrivateRadioButtonIcon-layer-71"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.465 8.465C9.37 7.56 10.62 7 12 7C14.76 7 17 9.24 17 12C17 13.38 16.44 14.63 15.535 15.535C14.63 16.44 13.38 17 12 17C9.24 17 7 14.76 7 12C7 10.62 7.56 9.37 8.465 8.465Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
Critical
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root"
|
||||||
|
id="demo-row-radio-buttons-group-label"
|
||||||
|
>
|
||||||
|
CAN Bus
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiFormGroup-root"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormControlLabel-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-66 MuiCheckbox-root MuiCheckbox-colorSecondary PrivateSwitchBase-checked-67 Mui-checked MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
checked=""
|
||||||
|
class="PrivateSwitchBase-input-69"
|
||||||
|
data-indeterminate="false"
|
||||||
|
type="checkbox"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
CAN Bus Enabled
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined MuiFormLabel-filled Mui-required Mui-required"
|
||||||
|
data-shrink="true"
|
||||||
|
for="max_mem_buffer_size"
|
||||||
|
id="max_mem_buffer_size-label"
|
||||||
|
>
|
||||||
|
Max Memory Buffer Size (0 uses default size)
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="max_mem_buffer_size"
|
||||||
|
maxlength="12"
|
||||||
|
name="max_mem_buffer_size"
|
||||||
|
required=""
|
||||||
|
type="number"
|
||||||
|
value="1"
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-62 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-64 PrivateNotchedOutline-legendNotched-65"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Max Memory Buffer Size (0 uses default size)
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label
|
||||||
|
class="MuiFormControlLabel-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-66 MuiCheckbox-root MuiCheckbox-colorSecondary PrivateSwitchBase-checked-67 Mui-checked MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="PrivateSwitchBase-input-69"
|
||||||
|
data-indeterminate="false"
|
||||||
|
type="checkbox"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
Data Logger Enabled
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined MuiFormLabel-filled Mui-required Mui-required"
|
||||||
|
data-shrink="true"
|
||||||
|
for="max_disk_buffer_size"
|
||||||
|
id="max_disk_buffer_size-label"
|
||||||
|
>
|
||||||
|
Max Disk Buffer Size (0 uses default size)
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="max_disk_buffer_size"
|
||||||
|
maxlength="12"
|
||||||
|
name="max_disk_buffer_size"
|
||||||
|
required=""
|
||||||
|
type="number"
|
||||||
|
value="2"
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-62 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-64 PrivateNotchedOutline-legendNotched-65"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Max Disk Buffer Size (0 uses default size)
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-6 MuiButton-containedPrimary MuiButton-fullWidth"
|
||||||
|
tabindex="0"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiButton-label"
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
285
src/components/Cars/Update/index.jsx
Normal file
285
src/components/Cars/Update/index.jsx
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import { Redirect } from "react-router";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
FormControlLabel,
|
||||||
|
FormGroup,
|
||||||
|
FormLabel,
|
||||||
|
Radio,
|
||||||
|
RadioGroup,
|
||||||
|
TextField
|
||||||
|
} from "@material-ui/core";
|
||||||
|
|
||||||
|
import useStyles from "../../useStyles";
|
||||||
|
import {
|
||||||
|
useVehicleContext,
|
||||||
|
VehicleProvider
|
||||||
|
} from "../../Contexts/VehicleContext";
|
||||||
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
|
import { logger } from "../../../services/monitoring";
|
||||||
|
|
||||||
|
|
||||||
|
const MainForm = () => {
|
||||||
|
const queries = new URLSearchParams(useLocation().search);
|
||||||
|
const vin = queries.get("vin") ?? "";
|
||||||
|
|
||||||
|
const { vehicle, getVehicle, updateVehicle, busy } = useVehicleContext();
|
||||||
|
const { token: { idToken: { jwtToken: token } } } = useUserContext();
|
||||||
|
const { setMessage, setTitle, setSitePath } = useStatusContext();
|
||||||
|
const [redirect, setRedirect] = useState(null);
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
const modelEl = useRef(null);
|
||||||
|
const yearEl = useRef(null);
|
||||||
|
const trimEl = useRef(null);
|
||||||
|
const [selectedLogLevel, setSelectedLogLevel] = useState("info");
|
||||||
|
const [canbusEnabled, setCANBusEnabled] = useState(true);
|
||||||
|
const [dataLoggerEnabled, setDataLoggerEnabled] = useState(false);
|
||||||
|
const [maxMemBufferSize, setMaxMemBufferSize] = useState(0);
|
||||||
|
const [maxDiskBufferSize, setMaxDiskBufferSize] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTitle("Update Vehicle");
|
||||||
|
setSitePath([
|
||||||
|
{
|
||||||
|
label: `Vehicle ${vin}`,
|
||||||
|
link: "/vehicles",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Update Vehicle",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
if (!vin || !token) return;
|
||||||
|
await getVehicle(vin, token);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
logger.warn(e.stack);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [token]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedLogLevel(vehicle.log_level ?? selectedLogLevel);
|
||||||
|
|
||||||
|
if (vehicle.canbus) {
|
||||||
|
setCANBusEnabled(vehicle.canbus.enabled ?? canbusEnabled);
|
||||||
|
setDataLoggerEnabled(vehicle.canbus.data_logger_enabled ?? dataLoggerEnabled);
|
||||||
|
setMaxMemBufferSize(vehicle.canbus.max_mem_buffer_size ?? maxMemBufferSize);
|
||||||
|
setMaxDiskBufferSize(vehicle.canbus.max_disk_buffer_size ?? maxDiskBufferSize);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [vehicle]);
|
||||||
|
|
||||||
|
const onLogLevelChange = (event) => {
|
||||||
|
setSelectedLogLevel(event.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCANBusChange = (event) => {
|
||||||
|
setCANBusEnabled(event.target.checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDataLoggerChange = (event) => {
|
||||||
|
setDataLoggerEnabled(event.target.checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onMaxMemBufferSizeChange = (event) => {
|
||||||
|
setMaxMemBufferSize(event.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onMaxDiskBufferSizeChange = (event) => {
|
||||||
|
setMaxDiskBufferSize(event.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSubmit = async (event) => {
|
||||||
|
try {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const formData = {
|
||||||
|
vin: vin,
|
||||||
|
model: modelEl.current.value,
|
||||||
|
year: parseInt(yearEl.current.value),
|
||||||
|
trim: trimEl.current.value,
|
||||||
|
log_level: selectedLogLevel,
|
||||||
|
canbus: {
|
||||||
|
enabled: canbusEnabled,
|
||||||
|
data_logger_enabled: canbusEnabled ? dataLoggerEnabled : false,
|
||||||
|
max_mem_buffer_size: canbusEnabled ? parseInt(maxMemBufferSize) : 0,
|
||||||
|
max_disk_buffer_size: canbusEnabled && dataLoggerEnabled ? parseInt(maxDiskBufferSize) : 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await updateVehicle(vin, formData, token);
|
||||||
|
if (!result || result.error) return;
|
||||||
|
|
||||||
|
setMessage(`Updated ${result.vin}`);
|
||||||
|
setRedirect(`/vehicle-status/${result.vin}`);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
logger.warn(e.stack);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (redirect && redirect.length > 0) {
|
||||||
|
return <Redirect to={redirect} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.paper}>
|
||||||
|
<form className={classes.form} noValidate action="{onSubmit}">
|
||||||
|
<TextField
|
||||||
|
id="vin"
|
||||||
|
name="vin"
|
||||||
|
label="VIN"
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
inputProps={{
|
||||||
|
maxLength: "17",
|
||||||
|
readOnly: true,
|
||||||
|
}}
|
||||||
|
disabled
|
||||||
|
value={vin}
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
id="model"
|
||||||
|
name="model"
|
||||||
|
label="Model"
|
||||||
|
defaultValue="Ocean"
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
inputProps={{
|
||||||
|
maxLength: "255",
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
inputRef={modelEl}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
id="year"
|
||||||
|
name="year"
|
||||||
|
label="Year"
|
||||||
|
type="number"
|
||||||
|
defaultValue="2022"
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
inputProps={{
|
||||||
|
maxLength: "4",
|
||||||
|
minLength: "4",
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
inputRef={yearEl}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
id="trim"
|
||||||
|
name="trim"
|
||||||
|
label="Trim"
|
||||||
|
defaultValue="Base"
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
inputProps={{
|
||||||
|
maxLength: "4",
|
||||||
|
minLength: "4",
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
inputRef={trimEl}
|
||||||
|
/>
|
||||||
|
<FormLabel id="demo-row-radio-buttons-group-label">Log Level</FormLabel>
|
||||||
|
<RadioGroup
|
||||||
|
row
|
||||||
|
aria-labelledby="demo-row-radio-buttons-group-label"
|
||||||
|
name="log-level-group"
|
||||||
|
value={selectedLogLevel}
|
||||||
|
onChange={onLogLevelChange}
|
||||||
|
margin="normal"
|
||||||
|
>
|
||||||
|
<FormControlLabel value="trace" control={<Radio />} label="Trace" />
|
||||||
|
<FormControlLabel value="debug" control={<Radio />} label="Debug" />
|
||||||
|
<FormControlLabel value="info" control={<Radio />} label="Info" />
|
||||||
|
<FormControlLabel value="warn" control={<Radio />} label="Warn" />
|
||||||
|
<FormControlLabel value="error" control={<Radio />} label="Error" />
|
||||||
|
<FormControlLabel value="critical" control={<Radio />} label="Critical" />
|
||||||
|
</RadioGroup>
|
||||||
|
<FormLabel id="demo-row-radio-buttons-group-label">CAN Bus</FormLabel>
|
||||||
|
<FormGroup>
|
||||||
|
<FormControlLabel control={
|
||||||
|
<Checkbox
|
||||||
|
checked={canbusEnabled}
|
||||||
|
onChange={onCANBusChange}
|
||||||
|
/>
|
||||||
|
} label="CAN Bus Enabled" />
|
||||||
|
<TextField
|
||||||
|
id="max_mem_buffer_size"
|
||||||
|
name="max_mem_buffer_size"
|
||||||
|
label='Max Memory Buffer Size (0 uses default size)'
|
||||||
|
value={maxMemBufferSize}
|
||||||
|
onChange={onMaxMemBufferSizeChange}
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
inputProps={{
|
||||||
|
maxLength: "12",
|
||||||
|
}}
|
||||||
|
type="number"
|
||||||
|
disabled={!canbusEnabled}
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<FormControlLabel control={
|
||||||
|
<Checkbox
|
||||||
|
checked={dataLoggerEnabled}
|
||||||
|
onChange={onDataLoggerChange}
|
||||||
|
disabled={!canbusEnabled}
|
||||||
|
/>
|
||||||
|
} label="Data Logger Enabled" />
|
||||||
|
</FormGroup>
|
||||||
|
<TextField
|
||||||
|
id="max_disk_buffer_size"
|
||||||
|
name="max_disk_buffer_size"
|
||||||
|
label='Max Disk Buffer Size (0 uses default size)'
|
||||||
|
value={maxDiskBufferSize}
|
||||||
|
onChange={onMaxDiskBufferSizeChange}
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
inputProps={{
|
||||||
|
maxLength: "12",
|
||||||
|
}}
|
||||||
|
type="number"
|
||||||
|
disabled={!dataLoggerEnabled}
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={busy}
|
||||||
|
fullWidth
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
className={classes.submit}
|
||||||
|
onClick={onSubmit}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const VehicleUpdateForm = (props) => (
|
||||||
|
<VehicleProvider>
|
||||||
|
<MainForm {...props} />
|
||||||
|
</VehicleProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default VehicleUpdateForm;
|
||||||
36
src/components/Cars/Update/index.test.jsx
Normal file
36
src/components/Cars/Update/index.test.jsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
jest.mock("../../Contexts/VehicleContext");
|
||||||
|
jest.mock("../../Contexts/StatusContext");
|
||||||
|
jest.mock("../../Contexts/UserContext");
|
||||||
|
|
||||||
|
import { render, waitFor } from "@testing-library/react";
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
|
||||||
|
import { VehicleProvider } from "../../Contexts/VehicleContext";
|
||||||
|
import { StatusProvider } from "../../Contexts/StatusContext";
|
||||||
|
import { UserProvider, setToken } from "../../Contexts/UserContext";
|
||||||
|
import { TEST_AUTH_OBJECT } from "../../../utils/testing";
|
||||||
|
import MainForm from "./index"
|
||||||
|
|
||||||
|
const renderVehicleUpdate = async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<VehicleProvider>
|
||||||
|
<StatusProvider>
|
||||||
|
<UserProvider>
|
||||||
|
<BrowserRouter>
|
||||||
|
<MainForm />
|
||||||
|
</BrowserRouter>
|
||||||
|
</UserProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
</VehicleProvider>
|
||||||
|
);
|
||||||
|
await waitFor(() => { /* render */ });
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("VehicleUpdate", () => {
|
||||||
|
it("Render", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT);
|
||||||
|
const container = await renderVehicleUpdate();
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
90
src/components/Certificates/Add/CreateForm.jsx
Normal file
90
src/components/Certificates/Add/CreateForm.jsx
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import React, { useRef, useState } from "react";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
FormControlLabel,
|
||||||
|
FormLabel,
|
||||||
|
Radio,
|
||||||
|
RadioGroup,
|
||||||
|
TextField,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
|
||||||
|
import useStyles from "../../useStyles";
|
||||||
|
import { CertTypes } from "../../Contexts/CertificateContext";
|
||||||
|
|
||||||
|
const CreateForm = ({ onCreate, busy }) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
const vinEl = useRef(null);
|
||||||
|
const [certType, setCertType] = useState(CertTypes.TBOX);
|
||||||
|
|
||||||
|
const onSubmit = async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (onCreate)
|
||||||
|
onCreate({
|
||||||
|
vin: vinEl.current.value,
|
||||||
|
type: certType,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCertTypeChange = (event) => {
|
||||||
|
setCertType(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.paper}>
|
||||||
|
<form className={classes.form} noValidate action="{onSubmit}">
|
||||||
|
<TextField
|
||||||
|
id="vin"
|
||||||
|
name="vin"
|
||||||
|
label="VIN"
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
inputProps={{
|
||||||
|
maxLength: "17",
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
inputRef={vinEl}
|
||||||
|
/>
|
||||||
|
<FormLabel id="cert-type-group-label">Type</FormLabel>
|
||||||
|
<RadioGroup
|
||||||
|
row
|
||||||
|
aria-labelledby="cert-type-group-label"
|
||||||
|
name="cert-type"
|
||||||
|
value={certType}
|
||||||
|
onChange={onCertTypeChange}
|
||||||
|
margin="normal"
|
||||||
|
>
|
||||||
|
<FormControlLabel
|
||||||
|
value={CertTypes.TBOX}
|
||||||
|
control={<Radio />}
|
||||||
|
label="TBOX"
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
value={CertTypes.ICC}
|
||||||
|
control={<Radio />}
|
||||||
|
label="ICC"
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
value={CertTypes.Charging}
|
||||||
|
control={<Radio />}
|
||||||
|
label="Charging"
|
||||||
|
/>
|
||||||
|
</RadioGroup>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={busy}
|
||||||
|
fullWidth
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
className={classes.submit}
|
||||||
|
onClick={onSubmit}
|
||||||
|
>
|
||||||
|
{busy ? "Submitting..." : "Submit"}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateForm;
|
||||||
50
src/components/Certificates/Add/DownloadCerts.jsx
Normal file
50
src/components/Certificates/Add/DownloadCerts.jsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { Button } from "@material-ui/core";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import DownloadFileLink from "../../Controls/DownloadFileLink";
|
||||||
|
import useStyles from "../../useStyles";
|
||||||
|
|
||||||
|
const CertMimeType = "application/x-pem-file";
|
||||||
|
|
||||||
|
const DownloadCerts = ({ vin, publicCert, privateCert, onChangeView }) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
const onNewCert = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (!onChangeView) return;
|
||||||
|
onChangeView();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2>Download Certificates</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<DownloadFileLink
|
||||||
|
data={publicCert}
|
||||||
|
filename={`${vin}_cert.pem`}
|
||||||
|
mimetype={CertMimeType}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<DownloadFileLink
|
||||||
|
data={privateCert}
|
||||||
|
filename={`${vin}_key.pem`}
|
||||||
|
mimetype={CertMimeType}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
fullWidth
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
className={classes.submit}
|
||||||
|
onClick={onNewCert}
|
||||||
|
>
|
||||||
|
Done
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DownloadCerts;
|
||||||
25
src/components/Certificates/Add/DownloadCerts.test.jsx
Normal file
25
src/components/Certificates/Add/DownloadCerts.test.jsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { render, waitFor } from "@testing-library/react";
|
||||||
|
|
||||||
|
import DownloadCerts from "./DownloadCerts";
|
||||||
|
|
||||||
|
describe("DownloadCerts", () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
global.URL.createObjectURL = jest.fn();
|
||||||
|
global.URL.revokeObjectURL = jest.fn();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Render", async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<DownloadCerts
|
||||||
|
vin={"TESTVIN"}
|
||||||
|
publicCert={"PUBLIC"}
|
||||||
|
privateCert={"PRIVATE"}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await waitFor(() => {
|
||||||
|
/* render */
|
||||||
|
});
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`DownloadCerts Render 1`] = `
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<h2>
|
||||||
|
Download Certificates
|
||||||
|
</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
download="TESTVIN_cert.pem"
|
||||||
|
>
|
||||||
|
TESTVIN_cert.pem
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
download="TESTVIN_key.pem"
|
||||||
|
>
|
||||||
|
TESTVIN_key.pem
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-6 MuiButton-containedPrimary MuiButton-fullWidth"
|
||||||
|
tabindex="0"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiButton-label"
|
||||||
|
>
|
||||||
|
Done
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
85
src/components/Certificates/Add/index.jsx
Normal file
85
src/components/Certificates/Add/index.jsx
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
useCertificateContext,
|
||||||
|
CertificateProvider,
|
||||||
|
} from "../../Contexts/CertificateContext";
|
||||||
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
|
import { logger } from "../../../services/monitoring";
|
||||||
|
import CreateForm from "./CreateForm";
|
||||||
|
import DownloadCerts from "./DownloadCerts";
|
||||||
|
|
||||||
|
const VIEW_FORM = 0;
|
||||||
|
const VIEW_DOWNLOAD = 1;
|
||||||
|
|
||||||
|
const MainForm = () => {
|
||||||
|
const { busy, createCert } = useCertificateContext();
|
||||||
|
const { setMessage, setTitle, setSitePath } = useStatusContext();
|
||||||
|
const {
|
||||||
|
token: {
|
||||||
|
idToken: { jwtToken: token },
|
||||||
|
},
|
||||||
|
} = useUserContext();
|
||||||
|
const [view, setView] = useState(VIEW_FORM);
|
||||||
|
const [pubCert, setPubCert] = useState(null);
|
||||||
|
const [privCert, setPrivCert] = useState(null);
|
||||||
|
const [vin, setVIN] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTitle("Create Certificate");
|
||||||
|
setSitePath([
|
||||||
|
{
|
||||||
|
label: "Tools",
|
||||||
|
link: "/tools/certificates/add",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Create Certificate",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onCreate = async (data) => {
|
||||||
|
try {
|
||||||
|
const result = await createCert(data, token);
|
||||||
|
|
||||||
|
setPubCert(result.public_key);
|
||||||
|
setPrivCert(result.private_key);
|
||||||
|
setVIN(data.vin);
|
||||||
|
setMessage(`Created ${data.vin} certificate`);
|
||||||
|
setView(VIEW_DOWNLOAD);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
logger.warn(e.stack);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChangeView = () => {
|
||||||
|
setPubCert(null);
|
||||||
|
setPrivCert(null);
|
||||||
|
setVIN(null);
|
||||||
|
|
||||||
|
setView(VIEW_FORM);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (view === VIEW_DOWNLOAD)
|
||||||
|
return (
|
||||||
|
<DownloadCerts
|
||||||
|
vin={vin}
|
||||||
|
publicCert={pubCert}
|
||||||
|
privateCert={privCert}
|
||||||
|
onChangeView={onChangeView}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return <CreateForm onCreate={onCreate} busy={busy} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CertificateCreate = () => (
|
||||||
|
<CertificateProvider>
|
||||||
|
<MainForm />
|
||||||
|
</CertificateProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default CertificateCreate;
|
||||||
127
src/components/Contexts/CANFiltersContext.jsx
Normal file
127
src/components/Contexts/CANFiltersContext.jsx
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import React, { useContext, useState } from "react";
|
||||||
|
import api from "../../services/CANFiltersAPI";
|
||||||
|
|
||||||
|
const CANFiltersContext = React.createContext();
|
||||||
|
|
||||||
|
export const CANFiltersProvider = ({ children }) => {
|
||||||
|
const [busy, setBusy] = useState(false);
|
||||||
|
const [filters, setFilters] = useState([]);
|
||||||
|
const [totalFilters, setTotalFilters] = useState(0);
|
||||||
|
|
||||||
|
const addFilter = async (vin, filter, token) => {
|
||||||
|
try {
|
||||||
|
setBusy(true);
|
||||||
|
|
||||||
|
validateVIN(vin);
|
||||||
|
validateFilter(filter);
|
||||||
|
|
||||||
|
const result = await api.addFilter(vin, filter, token);
|
||||||
|
if (result.error) throw new Error(`Add filter error. ${result.message}`);
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFilters = async (vin, search, token) => {
|
||||||
|
try {
|
||||||
|
setBusy(true);
|
||||||
|
|
||||||
|
validateVIN(vin);
|
||||||
|
|
||||||
|
const result = await api.getFilters(vin, search, token);
|
||||||
|
if (result.error) {
|
||||||
|
setFilters([])
|
||||||
|
throw new Error(`Get filters error. ${result.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
setFilters(result.data)
|
||||||
|
if (result.total) {
|
||||||
|
setTotalFilters(result.total);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateFilter = async (vin, canID, filter, token) => {
|
||||||
|
try {
|
||||||
|
setBusy(true);
|
||||||
|
|
||||||
|
validateVIN(vin);
|
||||||
|
validateID(canID);
|
||||||
|
validateFilter(filter);
|
||||||
|
|
||||||
|
const result = await api.updateFilter(vin, canID, filter, token);
|
||||||
|
if (result.error) {
|
||||||
|
throw new Error(`Update filters error. ${result.message}`);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteFilter = async (vin, canID, token) => {
|
||||||
|
try {
|
||||||
|
setBusy(true);
|
||||||
|
|
||||||
|
validateVIN(vin);
|
||||||
|
validateID(canID);
|
||||||
|
|
||||||
|
const result = await api.deleteFilter(vin, canID, token);
|
||||||
|
if (result.error) {
|
||||||
|
throw new Error(`Delete filter error. ${result.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = filters.findIndex(element => element.can_id === canID);
|
||||||
|
if (index >= 0) filters.splice(index, 1);
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CANFiltersContext.Provider
|
||||||
|
value={{
|
||||||
|
busy,
|
||||||
|
filters,
|
||||||
|
totalFilters,
|
||||||
|
addFilter,
|
||||||
|
getFilters,
|
||||||
|
updateFilter,
|
||||||
|
deleteFilter
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</CANFiltersContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateVIN = (vin) => {
|
||||||
|
if (vin == null || vin.length !== 17) {
|
||||||
|
throw new Error("Invalid VIN");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateID = (can_id) => {
|
||||||
|
if (can_id == null || can_id === "") {
|
||||||
|
throw new Error("CAN ID required");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateFilter = (filter) => {
|
||||||
|
if (filter == null) {
|
||||||
|
throw new Error("No filter data");
|
||||||
|
}
|
||||||
|
|
||||||
|
validateID(filter.can_id)
|
||||||
|
|
||||||
|
if (filter.interval == null) {
|
||||||
|
throw new Error("Interval required");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCANFiltersContext = () => useContext(CANFiltersContext);
|
||||||
289
src/components/Contexts/CANFiltersContext.test.jsx
Normal file
289
src/components/Contexts/CANFiltersContext.test.jsx
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
jest.mock("../../services/CANFiltersAPI")
|
||||||
|
|
||||||
|
import {
|
||||||
|
render,
|
||||||
|
cleanup,
|
||||||
|
screen,
|
||||||
|
fireEvent,
|
||||||
|
waitFor,
|
||||||
|
} from "@testing-library/react";
|
||||||
|
import { CANFiltersProvider, useCANFiltersContext } from "./CANFiltersContext";
|
||||||
|
import { StatusProvider, useStatusContext } from "./StatusContext";
|
||||||
|
|
||||||
|
const checkFiltersResults = (error, busy, filters) => {
|
||||||
|
checkBaseResults(error, busy);
|
||||||
|
expect(screen.getByTestId("filters").innerHTML).toEqual(filters);
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkBaseResults = (error, busy) => {
|
||||||
|
expect(screen.getByTestId("error").innerHTML).toEqual(error);
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual(busy);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("CANFiltersContext", () => {
|
||||||
|
describe("getFilters", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const TestComp = () => {
|
||||||
|
const { busy, error, filters, getFilters } = useCANFiltersContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-testid="error">{error}</div>
|
||||||
|
<div data-testid="busy">{busy.toString()}</div>
|
||||||
|
<div data-testid="filters">{JSON.stringify(filters)}</div>
|
||||||
|
<button
|
||||||
|
data-testid="getFilters"
|
||||||
|
onClick={() => getFilters("TESTVIN1234567890")}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<CANFiltersProvider>
|
||||||
|
<TestComp />
|
||||||
|
</CANFiltersProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initial state", () => {
|
||||||
|
checkFiltersResults("", "false", "[]");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getFilters", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("getFilters"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("filters").innerHTML).not.toBe("[]")
|
||||||
|
);
|
||||||
|
checkFiltersResults("", "false", JSON.stringify(expectedFiltersData));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("addFilter", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const TestComp = () => {
|
||||||
|
const { busy, addFilter } = useCANFiltersContext();
|
||||||
|
const { message, setMessage } = useStatusContext();
|
||||||
|
const add = async (data) => {
|
||||||
|
try {
|
||||||
|
await addFilter("TESTVIN1234567890", data);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-testid="error">{message}</div>
|
||||||
|
<div data-testid="busy">{busy.toString()}</div>
|
||||||
|
<button data-testid="addFilterNull" onClick={() => add(null)} />
|
||||||
|
<button data-testid="addFilterNoCANID" onClick={() => add({})} />
|
||||||
|
<button
|
||||||
|
data-testid="addFilter"
|
||||||
|
onClick={() =>
|
||||||
|
add({ can_id: "123", interval: 1000 })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<StatusProvider>
|
||||||
|
<CANFiltersProvider>
|
||||||
|
<TestComp />
|
||||||
|
</CANFiltersProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initial state", () => {
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("addFilterNull", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("addFilterNull"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("No filter data", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("addFilterNoCANID", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("addFilterNoCANID"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("CAN ID required", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("addFilter", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("addFilter"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("updateFilter", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const TestComp = () => {
|
||||||
|
const { busy, updateFilter } = useCANFiltersContext();
|
||||||
|
const { message, setMessage } = useStatusContext();
|
||||||
|
const update = async (data) => {
|
||||||
|
try {
|
||||||
|
await updateFilter("TESTVIN1234567890", data.can_id, data);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-testid="error">{message}</div>
|
||||||
|
<div data-testid="busy">{busy.toString()}</div>
|
||||||
|
<button data-testid="updateFilterNull" onClick={() => update(null)} />
|
||||||
|
<button data-testid="updateFilterNoCANID" onClick={() => update({})} />
|
||||||
|
<button
|
||||||
|
data-testid="updateFilter"
|
||||||
|
onClick={() =>
|
||||||
|
update({ can_id: "123", interval: 1000 })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<StatusProvider>
|
||||||
|
<CANFiltersProvider>
|
||||||
|
<TestComp />
|
||||||
|
</CANFiltersProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initial state", () => {
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updateFilterNull", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("updateFilterNull"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("Cannot read property 'can_id' of null", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updateFilterNoCANID", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("updateFilterNoCANID"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("CAN ID required", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updateFilter", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("updateFilter"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("deleteFilter", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const TestComp = () => {
|
||||||
|
const { busy, deleteFilter } = useCANFiltersContext();
|
||||||
|
const { message, setMessage } = useStatusContext();
|
||||||
|
const deleteF = async (can_id) => {
|
||||||
|
try {
|
||||||
|
await deleteFilter("TESTVIN1234567890", can_id);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-testid="error">{message}</div>
|
||||||
|
<div data-testid="busy">{busy.toString()}</div>
|
||||||
|
<button data-testid="deleteFilterNull" onClick={() => deleteF(null)} />
|
||||||
|
<button data-testid="deleteFilterNonexistent" onClick={() => deleteF(-1)} />
|
||||||
|
<button
|
||||||
|
data-testid="deleteFilter"
|
||||||
|
onClick={() =>
|
||||||
|
deleteF(123)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<StatusProvider>
|
||||||
|
<CANFiltersProvider>
|
||||||
|
<TestComp />
|
||||||
|
</CANFiltersProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initial state", () => {
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteFilterNull", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("deleteFilterNull"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("CAN ID required", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteFilterNonexistent", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("deleteFilterNonexistent"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteFilter", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("deleteFilter"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedFiltersData = [
|
||||||
|
{
|
||||||
|
can_id: "123",
|
||||||
|
interval: 1000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
can_id: "456",
|
||||||
|
interval: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
can_id: "789-1000",
|
||||||
|
interval: 5
|
||||||
|
},
|
||||||
|
];
|
||||||
50
src/components/Contexts/CertificateContext.jsx
Normal file
50
src/components/Contexts/CertificateContext.jsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import React, { useContext, useState } from "react";
|
||||||
|
|
||||||
|
import api from "../../services/certificatesAPI";
|
||||||
|
|
||||||
|
const CertificateContext = React.createContext();
|
||||||
|
|
||||||
|
export const CertTypes = {
|
||||||
|
TBOX: "TBOX",
|
||||||
|
ICC: "ICC",
|
||||||
|
Charging: "Charging",
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateCreate = (data) => {
|
||||||
|
if (!data.type) throw new Error("type is required");
|
||||||
|
if (!data.vin) throw new Error("vin is required");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CertificateProvider = ({ children }) => {
|
||||||
|
const [busy, setBusy] = useState(false);
|
||||||
|
|
||||||
|
const createCert = async (data, token) => {
|
||||||
|
try {
|
||||||
|
setBusy(true);
|
||||||
|
|
||||||
|
validateCreate(data);
|
||||||
|
|
||||||
|
const result = await api.create(data, token);
|
||||||
|
if (result.error) {
|
||||||
|
throw new Error(`Create certificate error. ${result.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CertificateContext.Provider
|
||||||
|
value={{
|
||||||
|
busy,
|
||||||
|
createCert,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</CertificateContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCertificateContext = () => useContext(CertificateContext);
|
||||||
307
src/components/Contexts/FleetContext.jsx
Normal file
307
src/components/Contexts/FleetContext.jsx
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
import React, { useContext, useState } from "react";
|
||||||
|
import api from "../../services/fleetsAPI";
|
||||||
|
|
||||||
|
const FleetContext = React.createContext();
|
||||||
|
|
||||||
|
export const FleetProvider = ({ children }) => {
|
||||||
|
const [busy, setBusy] = useState(false);
|
||||||
|
|
||||||
|
const [fleet, setFleet] = useState({});
|
||||||
|
|
||||||
|
const [fleets, setFleets] = useState([]);
|
||||||
|
const [totalFleets, setTotalFleets] = useState(0);
|
||||||
|
|
||||||
|
const [fleetVehicles, setFleetVehicles] = useState([]);
|
||||||
|
const [totalFleetVehicles, setTotalFleetVehicles] = useState(0);
|
||||||
|
|
||||||
|
const [fleetCANFilters, setFleetCANFilters] = useState([]);
|
||||||
|
const [totalFleetCANFilters, setTotalFleetCANFilters] = useState(0);
|
||||||
|
|
||||||
|
const addFleet = async (f, token) => {
|
||||||
|
try {
|
||||||
|
setBusy(true);
|
||||||
|
validateFleet(f);
|
||||||
|
|
||||||
|
const result = await api.addFleet(f, token);
|
||||||
|
if (result.error) throw new Error(`Add fleet error. ${result.message}`);
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFleet = async (name, token) => {
|
||||||
|
try {
|
||||||
|
setBusy(true);
|
||||||
|
validateFleetName(name);
|
||||||
|
|
||||||
|
const result = await api.getFleet(name, token);
|
||||||
|
if (result.error) {
|
||||||
|
setFleet({});
|
||||||
|
throw new Error(`Get fleet error. ${result.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
setFleet(result);
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFleets = async (search, token) => {
|
||||||
|
try {
|
||||||
|
setBusy(true);
|
||||||
|
|
||||||
|
const result = await api.getFleets(search, token);
|
||||||
|
if (result.error) {
|
||||||
|
setFleets([])
|
||||||
|
throw new Error(`Get fleets error. ${result.message}`);
|
||||||
|
}
|
||||||
|
setFleets(result.data)
|
||||||
|
if (result.total) {
|
||||||
|
setTotalFleets(result.total);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateFleet = async (name, f, token) => {
|
||||||
|
try {
|
||||||
|
setBusy(true);
|
||||||
|
|
||||||
|
validateFleetName(name);
|
||||||
|
validateFleet(f);
|
||||||
|
|
||||||
|
const result = await api.updateFleet(name, f, token);
|
||||||
|
if (result.error) {
|
||||||
|
throw new Error(`Update fleet error. ${result.message}`);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteFleet = async (name, token) => {
|
||||||
|
try {
|
||||||
|
setBusy(true);
|
||||||
|
|
||||||
|
validateFleetName(name);
|
||||||
|
|
||||||
|
const result = await api.deleteFleet(name, token);
|
||||||
|
if (result.error) {
|
||||||
|
throw new Error(`Delete filter error. ${result.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFleetVehicles = async (name, search, token) => {
|
||||||
|
try {
|
||||||
|
setBusy(true);
|
||||||
|
|
||||||
|
const result = await api.getFleetVehicles(name, search, token);
|
||||||
|
if (result.error) {
|
||||||
|
setFleetVehicles([])
|
||||||
|
throw new Error(`Get fleet vehicles error. ${result.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
setFleetVehicles(result.data)
|
||||||
|
if (result.total) {
|
||||||
|
setTotalFleetVehicles(result.total);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addFleetVehicle = async (name, vehicle, token) => {
|
||||||
|
try {
|
||||||
|
setBusy(true);
|
||||||
|
|
||||||
|
validateFleetName(name);
|
||||||
|
validateVIN(vehicle.vin);
|
||||||
|
|
||||||
|
const result = await api.addFleetVehicle(name, vehicle, token);
|
||||||
|
if (result.error) {
|
||||||
|
throw new Error(`Add fleet vehicle error. ${result.message}`);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteFleetVehicle = async (name, vehicle, token) => {
|
||||||
|
try {
|
||||||
|
setBusy(true);
|
||||||
|
|
||||||
|
validateFleetName(name);
|
||||||
|
validateVIN(vehicle.vin);
|
||||||
|
|
||||||
|
const result = await api.deleteFleetVehicle(name, vehicle, token);
|
||||||
|
if (result.error) {
|
||||||
|
throw new Error(`Delete fleet vehicle error. ${result.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = fleetVehicles.findIndex(element => element === vehicle.vin);
|
||||||
|
if (index >= 0) fleetVehicles.splice(index, 1);
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFleetCANFilters = async (name, search, token) => {
|
||||||
|
try {
|
||||||
|
setBusy(true);
|
||||||
|
|
||||||
|
const result = await api.getFleetCANFilters(name, search, token);
|
||||||
|
if (result.error) {
|
||||||
|
setFleetCANFilters([])
|
||||||
|
throw new Error(`Get fleet filters error. ${result.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
setFleetCANFilters(result.data)
|
||||||
|
if (result.total) {
|
||||||
|
setTotalFleetCANFilters(result.total);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addFleetCANFilter = async (name, filter, token) => {
|
||||||
|
try {
|
||||||
|
setBusy(true);
|
||||||
|
|
||||||
|
validateFleetName(name);
|
||||||
|
validateFilter(filter);
|
||||||
|
|
||||||
|
const result = await api.addFleetCANFilter(name, filter, token);
|
||||||
|
if (result.error) {
|
||||||
|
throw new Error(`Add fleet CAN filter error. ${result.message}`);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateFleetCANFilter = async (name, can_id, filter, token) => {
|
||||||
|
try {
|
||||||
|
setBusy(true);
|
||||||
|
|
||||||
|
validateFleetName(name);
|
||||||
|
validateCANID(can_id);
|
||||||
|
validateFilter(filter);
|
||||||
|
|
||||||
|
const result = await api.updateFleetCANFilter(name, can_id, filter, token);
|
||||||
|
if (result.error) {
|
||||||
|
throw new Error(`Update fleet CAN filter error. ${result.message}`);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteFleetCANFilter = async (name, can_id, token) => {
|
||||||
|
try {
|
||||||
|
setBusy(true);
|
||||||
|
|
||||||
|
validateFleetName(name);
|
||||||
|
validateCANID(can_id);
|
||||||
|
|
||||||
|
const result = await api.deleteFleetCANFilter(name, can_id, token);
|
||||||
|
if (result.error) {
|
||||||
|
throw new Error(`Delete fleet vehicle error. ${result.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = fleetCANFilters.findIndex(element => element.can_id === can_id);
|
||||||
|
if (index >= 0) fleetCANFilters.splice(index, 1);
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FleetContext.Provider
|
||||||
|
value={{
|
||||||
|
busy,
|
||||||
|
|
||||||
|
fleet,
|
||||||
|
fleets,
|
||||||
|
totalFleets,
|
||||||
|
addFleet,
|
||||||
|
getFleet,
|
||||||
|
getFleets,
|
||||||
|
updateFleet,
|
||||||
|
deleteFleet,
|
||||||
|
|
||||||
|
fleetVehicles,
|
||||||
|
totalFleetVehicles,
|
||||||
|
getFleetVehicles,
|
||||||
|
addFleetVehicle,
|
||||||
|
deleteFleetVehicle,
|
||||||
|
|
||||||
|
fleetCANFilters,
|
||||||
|
totalFleetCANFilters,
|
||||||
|
getFleetCANFilters,
|
||||||
|
addFleetCANFilter,
|
||||||
|
updateFleetCANFilter,
|
||||||
|
deleteFleetCANFilter
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</FleetContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateFleet = (f) => {
|
||||||
|
if (f == null) {
|
||||||
|
throw new Error("No fleet data");
|
||||||
|
}
|
||||||
|
|
||||||
|
validateFleetName(f.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateFleetName = (name) => {
|
||||||
|
if (name == null || !/^[\w-]+$/.test(name)) {
|
||||||
|
throw new Error("Invalid name");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateVIN = (vin) => {
|
||||||
|
if (vin == null || vin.length !== 17) {
|
||||||
|
throw new Error("Invalid VIN");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateFilter = (filter) => {
|
||||||
|
if (filter == null) {
|
||||||
|
throw new Error("No CAN filter data");
|
||||||
|
}
|
||||||
|
|
||||||
|
validateCANID(filter.can_id)
|
||||||
|
|
||||||
|
if (filter.interval == null || !/^\d+$/.test(filter.interval)) {
|
||||||
|
throw new Error("Invalid interval");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateCANID = (can_id) => {
|
||||||
|
if (can_id == null || !/^\d+(-\d+)?$/.test(can_id)) {
|
||||||
|
throw new Error("Invalid CAN ID");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useFleetContext = () => useContext(FleetContext);
|
||||||
752
src/components/Contexts/FleetContext.test.jsx
Normal file
752
src/components/Contexts/FleetContext.test.jsx
Normal file
@@ -0,0 +1,752 @@
|
|||||||
|
jest.mock("../../services/fleetsAPI")
|
||||||
|
|
||||||
|
import {
|
||||||
|
render,
|
||||||
|
cleanup,
|
||||||
|
screen,
|
||||||
|
fireEvent,
|
||||||
|
waitFor,
|
||||||
|
} from "@testing-library/react";
|
||||||
|
import { FleetProvider, useFleetContext } from "./FleetContext";
|
||||||
|
import { StatusProvider, useStatusContext } from "./StatusContext";
|
||||||
|
|
||||||
|
const checkFleetResults = (error, busy, fleet) => {
|
||||||
|
checkBaseResults(error, busy);
|
||||||
|
expect(screen.getByTestId("fleet").innerHTML).toEqual(fleet);
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkFleetsResults = (error, busy, fleets) => {
|
||||||
|
checkBaseResults(error, busy);
|
||||||
|
expect(screen.getByTestId("fleets").innerHTML).toEqual(fleets);
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkFleetVehicleResults = (error, busy, vehicles) => {
|
||||||
|
checkBaseResults(error, busy);
|
||||||
|
expect(screen.getByTestId("fleet-vehicles").innerHTML).toEqual(vehicles);
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkFleetCANFilterResults = (error, busy, filters) => {
|
||||||
|
checkBaseResults(error, busy);
|
||||||
|
expect(screen.getByTestId("fleet-filters").innerHTML).toEqual(filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkBaseResults = (error, busy) => {
|
||||||
|
expect(screen.getByTestId("error").innerHTML).toEqual(error);
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual(busy);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("FleetContext", () => {
|
||||||
|
describe("getFleets", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const TestComp = () => {
|
||||||
|
const { busy, error, fleets, getFleets } = useFleetContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-testid="error">{error}</div>
|
||||||
|
<div data-testid="busy">{busy.toString()}</div>
|
||||||
|
<div data-testid="fleets">{JSON.stringify(fleets)}</div>
|
||||||
|
<button
|
||||||
|
data-testid="getFleets"
|
||||||
|
onClick={() => getFleets()}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<FleetProvider>
|
||||||
|
<TestComp />
|
||||||
|
</FleetProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initial state", () => {
|
||||||
|
checkFleetsResults("", "false", "[]");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getFleets", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("getFleets"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("fleets").innerHTML).not.toBe("[]")
|
||||||
|
);
|
||||||
|
checkFleetsResults("", "false", JSON.stringify(expectedFleetsData));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getFleet", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const TestComp = () => {
|
||||||
|
const { busy, error, fleet, getFleet } = useFleetContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-testid="error">{error}</div>
|
||||||
|
<div data-testid="busy">{busy.toString()}</div>
|
||||||
|
<div data-testid="fleet">{JSON.stringify(fleet)}</div>
|
||||||
|
<button
|
||||||
|
data-testid="getFleet"
|
||||||
|
onClick={() => getFleet("US-WEST")}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<FleetProvider>
|
||||||
|
<TestComp />
|
||||||
|
</FleetProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initial state", () => {
|
||||||
|
checkFleetResults("", "false", "{}");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getFleet", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("getFleet"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("fleet").innerHTML).not.toBe("{}")
|
||||||
|
);
|
||||||
|
checkFleetResults("", "false", JSON.stringify(expectedFleetData));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("addFleet", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const TestComp = () => {
|
||||||
|
const { busy, addFleet } = useFleetContext();
|
||||||
|
const { message, setMessage } = useStatusContext();
|
||||||
|
const add = async (fleet) => {
|
||||||
|
try {
|
||||||
|
await addFleet(fleet);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-testid="error">{message}</div>
|
||||||
|
<div data-testid="busy">{busy.toString()}</div>
|
||||||
|
<button data-testid="addFleetNull" onClick={() => add(null)} />
|
||||||
|
<button data-testid="addFleetNoName" onClick={() => add({})} />
|
||||||
|
<button
|
||||||
|
data-testid="addFleet"
|
||||||
|
onClick={() =>
|
||||||
|
add({ name: "EU-WEST", log_level: "warn", canbus: { enabled: false } })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<StatusProvider>
|
||||||
|
<FleetProvider>
|
||||||
|
<TestComp />
|
||||||
|
</FleetProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initial state", () => {
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("addFleetNull", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("addFleetNull"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("No fleet data", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("addFleetNoName", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("addFleetNoName"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("Invalid name", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("addFleet", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("addFleet"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("updateFleet", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const TestComp = () => {
|
||||||
|
const { busy, updateFleet } = useFleetContext();
|
||||||
|
const { message, setMessage } = useStatusContext();
|
||||||
|
const update = async (data) => {
|
||||||
|
try {
|
||||||
|
await updateFleet("EU-WEST", data);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-testid="error">{message}</div>
|
||||||
|
<div data-testid="busy">{busy.toString()}</div>
|
||||||
|
<button data-testid="updateFleetNull" onClick={() => update(null)} />
|
||||||
|
<button data-testid="updateFleetNoName" onClick={() => update({})} />
|
||||||
|
<button
|
||||||
|
data-testid="updateFleet"
|
||||||
|
onClick={() =>
|
||||||
|
update({ name: "EU-WEST", log_level: "warn", canbus: { enabled: false } })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<StatusProvider>
|
||||||
|
<FleetProvider>
|
||||||
|
<TestComp />
|
||||||
|
</FleetProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initial state", () => {
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updateFleetNull", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("updateFleetNull"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("No fleet data", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updateFleetNoName", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("updateFleetNoName"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("Invalid name", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updateFleet", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("updateFleet"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("deleteFleet", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const TestComp = () => {
|
||||||
|
const { busy, deleteFleet } = useFleetContext();
|
||||||
|
const { message, setMessage } = useStatusContext();
|
||||||
|
const deleteF = async (name) => {
|
||||||
|
try {
|
||||||
|
await deleteFleet(name);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-testid="error">{message}</div>
|
||||||
|
<div data-testid="busy">{busy.toString()}</div>
|
||||||
|
<button data-testid="deleteFleetNull" onClick={() => deleteF(null)} />
|
||||||
|
<button data-testid="deleteFleetNonexistent" onClick={() => deleteF("INVALID")} />
|
||||||
|
<button
|
||||||
|
data-testid="deleteFleet"
|
||||||
|
onClick={() =>
|
||||||
|
deleteF("US-WEST")
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<StatusProvider>
|
||||||
|
<FleetProvider>
|
||||||
|
<TestComp />
|
||||||
|
</FleetProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initial state", () => {
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteFleetNull", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("deleteFleetNull"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("Invalid name", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteFleetNonexistent", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("deleteFleetNonexistent"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteFleet", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("deleteFleet"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getFleetVehicles", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const TestComp = () => {
|
||||||
|
const { busy, error, fleetVehicles, getFleetVehicles } = useFleetContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-testid="error">{error}</div>
|
||||||
|
<div data-testid="busy">{busy.toString()}</div>
|
||||||
|
<div data-testid="fleet-vehicles">{JSON.stringify(fleetVehicles)}</div>
|
||||||
|
<button
|
||||||
|
data-testid="getFleetVehicles"
|
||||||
|
onClick={() => getFleetVehicles("US-WEST")}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<FleetProvider>
|
||||||
|
<TestComp />
|
||||||
|
</FleetProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initial state", () => {
|
||||||
|
checkFleetVehicleResults("", "false", "[]");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getFleetVehicles", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("getFleetVehicles"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("fleet-vehicles").innerHTML).not.toBe("[]")
|
||||||
|
);
|
||||||
|
checkFleetVehicleResults("", "false", JSON.stringify(expectedFleetVehiclesData));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("addFleetVehicle", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const TestComp = () => {
|
||||||
|
const { busy, addFleetVehicle } = useFleetContext();
|
||||||
|
const { message, setMessage } = useStatusContext();
|
||||||
|
const add = async (name, vehicle) => {
|
||||||
|
try {
|
||||||
|
await addFleetVehicle(name, vehicle);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-testid="error">{message}</div>
|
||||||
|
<div data-testid="busy">{busy.toString()}</div>
|
||||||
|
<button data-testid="addFleetVehicleNull" onClick={() => add(null)} />
|
||||||
|
<button data-testid="addFleetVehicleNoName" onClick={() => add({})} />
|
||||||
|
<button
|
||||||
|
data-testid="addFleetVehicle"
|
||||||
|
onClick={() =>
|
||||||
|
add("US-TEST", { vin: "TESTVIN1234567890" })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<StatusProvider>
|
||||||
|
<FleetProvider>
|
||||||
|
<TestComp />
|
||||||
|
</FleetProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initial state", () => {
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("addFleetVehicleNull", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("addFleetVehicleNull"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("Invalid name", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("addFleetVehicleNoName", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("addFleetVehicleNoName"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("Invalid name", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("addFleetVehicle", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("addFleetVehicle"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("deleteFleetVehicle", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const TestComp = () => {
|
||||||
|
const { busy, deleteFleetVehicle } = useFleetContext();
|
||||||
|
const { message, setMessage } = useStatusContext();
|
||||||
|
const deleteFV = async (name, vehicle) => {
|
||||||
|
try {
|
||||||
|
await deleteFleetVehicle(name, vehicle);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-testid="error">{message}</div>
|
||||||
|
<div data-testid="busy">{busy.toString()}</div>
|
||||||
|
<button data-testid="deleteFleetVehicleNull" onClick={() => deleteFV("US-WEST", null)} />
|
||||||
|
<button data-testid="deleteFleetVehicleInvalid" onClick={() => deleteFV("US-WEST", "INVALID")} />
|
||||||
|
<button
|
||||||
|
data-testid="deleteFleetVehicle"
|
||||||
|
onClick={() =>
|
||||||
|
deleteFV("US-WEST", { vin: "USWESTVIN12345678" })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<StatusProvider>
|
||||||
|
<FleetProvider>
|
||||||
|
<TestComp />
|
||||||
|
</FleetProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initial state", () => {
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteFleetVehicleNull", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("deleteFleetVehicleNull"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("Cannot read property 'vin' of null", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteFleetVehicleNonexistent", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("deleteFleetVehicleInvalid"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("Invalid VIN", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteFleetVehicle", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("deleteFleetVehicle"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getFleetCANFilters", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const TestComp = () => {
|
||||||
|
const { busy, error, fleetCANFilters, getFleetCANFilters } = useFleetContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-testid="error">{error}</div>
|
||||||
|
<div data-testid="busy">{busy.toString()}</div>
|
||||||
|
<div data-testid="fleet-filters">{JSON.stringify(fleetCANFilters)}</div>
|
||||||
|
<button
|
||||||
|
data-testid="getFleetCANFilters"
|
||||||
|
onClick={() => getFleetCANFilters("US-TEST")}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<FleetProvider>
|
||||||
|
<TestComp />
|
||||||
|
</FleetProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initial state", () => {
|
||||||
|
checkFleetCANFilterResults("", "false", "[]");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getFleetCANFilters", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("getFleetCANFilters"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("fleet-filters").innerHTML).not.toBe("[]")
|
||||||
|
);
|
||||||
|
checkFleetCANFilterResults("", "false", JSON.stringify(expectedFleetCANFiltersData));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("addFleetCANFilter", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const TestComp = () => {
|
||||||
|
const { busy, addFleetCANFilter } = useFleetContext();
|
||||||
|
const { message, setMessage } = useStatusContext();
|
||||||
|
const add = async (name, filter) => {
|
||||||
|
try {
|
||||||
|
await addFleetCANFilter(name, filter);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-testid="error">{message}</div>
|
||||||
|
<div data-testid="busy">{busy.toString()}</div>
|
||||||
|
<button data-testid="addFleetCANFilterNull" onClick={() => add(null)} />
|
||||||
|
<button data-testid="addFleetCANFilterNoName" onClick={() => add({})} />
|
||||||
|
<button
|
||||||
|
data-testid="addFleetCANFilter"
|
||||||
|
onClick={() =>
|
||||||
|
add("US-TEST", { can_id: "111", interval: 222 })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<StatusProvider>
|
||||||
|
<FleetProvider>
|
||||||
|
<TestComp />
|
||||||
|
</FleetProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initial state", () => {
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("addFleetCANFilterNull", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("addFleetCANFilterNull"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("Invalid name", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("addFleetCANFilterNoName", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("addFleetCANFilterNoName"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("Invalid name", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("addFleetCANFilter", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("addFleetCANFilter"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("deleteFleetCANFilter", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const TestComp = () => {
|
||||||
|
const { busy, deleteFleetCANFilter } = useFleetContext();
|
||||||
|
const { message, setMessage } = useStatusContext();
|
||||||
|
const deleteFF = async (name, can_id) => {
|
||||||
|
try {
|
||||||
|
await deleteFleetCANFilter(name, can_id);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-testid="error">{message}</div>
|
||||||
|
<div data-testid="busy">{busy.toString()}</div>
|
||||||
|
<button data-testid="deleteFleetCANFilterNull" onClick={() => deleteFF("US-WEST", null)} />
|
||||||
|
<button data-testid="deleteFleetCANFilterInvalid" onClick={() => deleteFF("US-WEST", "INVALID")} />
|
||||||
|
<button
|
||||||
|
data-testid="deleteFleetCANFilter"
|
||||||
|
onClick={() =>
|
||||||
|
deleteFF("US-WEST", "123-456")
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<StatusProvider>
|
||||||
|
<FleetProvider>
|
||||||
|
<TestComp />
|
||||||
|
</FleetProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initial state", () => {
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteFleetCANFilterNull", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("deleteFleetCANFilterNull"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("Invalid CAN ID", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteFleetCANFilterNonexistent", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("deleteFleetCANFilterInvalid"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("Invalid CAN ID", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteFleetCANFilter", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("deleteFleetCANFilter"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedFilters = [
|
||||||
|
{
|
||||||
|
can_id: "123-456",
|
||||||
|
interval: 789
|
||||||
|
},
|
||||||
|
{
|
||||||
|
can_id: "1",
|
||||||
|
interval: 1000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
can_id: "1000",
|
||||||
|
interval: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const expectedFleetData = {
|
||||||
|
name: "US-WEST",
|
||||||
|
log_level: "info",
|
||||||
|
canbus: { enabled: true, data_logger_enabled: true, max_mem_buffer_size: 1, max_disk_buffer_size: 2, filters: expectedFilters },
|
||||||
|
vehicles: ["USWESTVIN12345678", "USWESTVIN12345679", "USWESTVIN12345670"]
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedFleetsData = [
|
||||||
|
{
|
||||||
|
name: "US-WEST",
|
||||||
|
log_level: "info",
|
||||||
|
canbus: { enabled: true, data_logger_enabled: true, max_mem_buffer_size: 1, max_disk_buffer_size: 2, filters: expectedFilters },
|
||||||
|
vehicles: ["USWESTVIN12345678", "USWESTVIN12345679", "USWESTVIN12345670"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "US-CENTRAL",
|
||||||
|
log_level: "warn",
|
||||||
|
canbus: { enabled: false, data_logger_enabled: false, max_mem_buffer_size: 0, max_disk_buffer_size: 0 },
|
||||||
|
vehicles: ["USCENTVIN12345678", "USCENTVIN12345679", "USCENTVIN12345670"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "US-EAST",
|
||||||
|
log_level: "error",
|
||||||
|
canbus: { enabled: true },
|
||||||
|
vehicles: ["USEASTVIN12345678", "USEASTVIN12345679", "USEASTVIN12345670"]
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const expectedFleetVehiclesData = ["USWESTVIN12345678", "USWESTVIN12345679", "USWESTVIN12345670"];
|
||||||
|
|
||||||
|
const expectedFleetCANFiltersData = [
|
||||||
|
{
|
||||||
|
can_id: "123-456",
|
||||||
|
interval: 789
|
||||||
|
},
|
||||||
|
{
|
||||||
|
can_id: "1",
|
||||||
|
interval: 1000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
can_id: "1000",
|
||||||
|
interval: 1
|
||||||
|
}
|
||||||
|
];
|
||||||
@@ -22,13 +22,10 @@ const checkExistingManifest = async (data, token) => {
|
|||||||
|
|
||||||
const ECUTemplate = {
|
const ECUTemplate = {
|
||||||
name: "AGS",
|
name: "AGS",
|
||||||
part_number: "",
|
|
||||||
version: "",
|
version: "",
|
||||||
serial_number: "",
|
|
||||||
hw_version: "",
|
hw_version: "",
|
||||||
vendor: "",
|
configuration_mask: "",
|
||||||
configuration: "",
|
configuration: "",
|
||||||
fingerprint: "",
|
|
||||||
files: [],
|
files: [],
|
||||||
manifest_id: 0,
|
manifest_id: 0,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,16 +46,16 @@ export const ManifestsProvider = ({ children }) => {
|
|||||||
const deleteManifest = async (package_id, token) => {
|
const deleteManifest = async (package_id, token) => {
|
||||||
let result;
|
let result;
|
||||||
|
|
||||||
const index = manifests.findIndex((element) => {
|
|
||||||
return element.id === package_id;
|
|
||||||
});
|
|
||||||
manifests.splice(index, 1);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
result = await api.deleteManifest(package_id, token);
|
result = await api.deleteManifest(package_id, token);
|
||||||
if (result.error)
|
if (result.error)
|
||||||
throw new Error(`Delete manifest error. ${result.message}`);
|
throw new Error(`Delete manifest error. ${result.message}`);
|
||||||
|
|
||||||
|
const index = manifests.findIndex((element) => {
|
||||||
|
return element.id === package_id;
|
||||||
|
});
|
||||||
|
manifests.splice(index, 1);
|
||||||
} finally {
|
} finally {
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import api from "../../services/vehiclesAPI";
|
|||||||
const VehicleContext = React.createContext();
|
const VehicleContext = React.createContext();
|
||||||
|
|
||||||
const validateAdd = (vehicle) => {
|
const validateAdd = (vehicle) => {
|
||||||
if (vehicle === null) {
|
if (vehicle == null) {
|
||||||
throw new Error("No vehicle data");
|
throw new Error("No vehicle data");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,6 +28,7 @@ const validateAdd = (vehicle) => {
|
|||||||
|
|
||||||
export const VehicleProvider = ({ children }) => {
|
export const VehicleProvider = ({ children }) => {
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
|
const [vehicle, setVehicle] = useState({});
|
||||||
const [vehicles, setVehicles] = useState([]);
|
const [vehicles, setVehicles] = useState([]);
|
||||||
const [totalVehicles, setTotalVehicles] = useState(0);
|
const [totalVehicles, setTotalVehicles] = useState(0);
|
||||||
const [models, setModels] = useState([]);
|
const [models, setModels] = useState([]);
|
||||||
@@ -49,11 +50,11 @@ export const VehicleProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const addVehicle = async (vehicle, token) => {
|
const addVehicle = async (v, token) => {
|
||||||
try {
|
try {
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
validateAdd(vehicle);
|
validateAdd(v);
|
||||||
const result = await api.addVehicle(vehicle, token);
|
const result = await api.addVehicle(v, token);
|
||||||
if (result.error) throw new Error(`Add vehicle error. ${result.message}`);
|
if (result.error) throw new Error(`Add vehicle error. ${result.message}`);
|
||||||
return result;
|
return result;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -118,6 +119,21 @@ export const VehicleProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getVehicle = async (vin, token) => {
|
||||||
|
try {
|
||||||
|
setBusy(true);
|
||||||
|
validateVIN(vin);
|
||||||
|
|
||||||
|
const result = await api.getVehicle(vin, token);
|
||||||
|
if (result.error) throw new Error(`Get vehicle error. ${result.message}`);
|
||||||
|
|
||||||
|
setVehicle(result);
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const getVehicles = async (search, token) => {
|
const getVehicles = async (search, token) => {
|
||||||
try {
|
try {
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
@@ -159,23 +175,56 @@ export const VehicleProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateVehicle = async (vin, v, token) => {
|
||||||
|
try {
|
||||||
|
setBusy(true);
|
||||||
|
validateVIN(vin);
|
||||||
|
validateVehicle(v);
|
||||||
|
|
||||||
|
const result = await api.updateVehicle(vin, v, token);
|
||||||
|
if (result.error)
|
||||||
|
throw new Error(`Update vehicle error. ${result.message}`);
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteVehicle = async (vin, token) => {
|
||||||
|
try {
|
||||||
|
setBusy(true);
|
||||||
|
validateVIN(vin);
|
||||||
|
|
||||||
|
const result = await api.deleteVehicle(vin, token);
|
||||||
|
if (result.error)
|
||||||
|
throw new Error(`Delete vehicle error. ${result.message}`);
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VehicleContext.Provider
|
<VehicleContext.Provider
|
||||||
value={{
|
value={{
|
||||||
busy,
|
busy,
|
||||||
models,
|
models,
|
||||||
totalVehicles,
|
totalVehicles,
|
||||||
|
vehicle,
|
||||||
vehicles,
|
vehicles,
|
||||||
years,
|
years,
|
||||||
addVehicle,
|
addVehicle,
|
||||||
|
deleteVehicle,
|
||||||
getConnections,
|
getConnections,
|
||||||
getECUs,
|
getECUs,
|
||||||
getLocations,
|
getLocations,
|
||||||
getModels,
|
getModels,
|
||||||
getState,
|
getState,
|
||||||
getYears,
|
getYears,
|
||||||
|
getVehicle,
|
||||||
getVehicles,
|
getVehicles,
|
||||||
sendCommand,
|
sendCommand,
|
||||||
|
updateVehicle
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@@ -183,4 +232,19 @@ export const VehicleProvider = ({ children }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const validateVehicle = (v) => {
|
||||||
|
if (v == null) {
|
||||||
|
throw new Error("No vehicle data");
|
||||||
|
}
|
||||||
|
|
||||||
|
validateVIN(v.vin);
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateVIN = (vin) => {
|
||||||
|
if (vin == null || vin.length !== 17) {
|
||||||
|
throw new Error("Invalid VIN");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export const useVehicleContext = () => useContext(VehicleContext);
|
export const useVehicleContext = () => useContext(VehicleContext);
|
||||||
|
|||||||
@@ -10,7 +10,12 @@ import {
|
|||||||
import { VehicleProvider, useVehicleContext } from "./VehicleContext";
|
import { VehicleProvider, useVehicleContext } from "./VehicleContext";
|
||||||
import { StatusProvider, useStatusContext } from "./StatusContext";
|
import { StatusProvider, useStatusContext } from "./StatusContext";
|
||||||
|
|
||||||
const checkVehicleResults = (error, busy, vehicles) => {
|
const checkVehicleResult = (error, busy, vehicle) => {
|
||||||
|
checkBaseResults(error, busy);
|
||||||
|
expect(screen.getByTestId("vehicle").innerHTML).toEqual(vehicle);
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkVehiclesResult = (error, busy, vehicles) => {
|
||||||
checkBaseResults(error, busy);
|
checkBaseResults(error, busy);
|
||||||
expect(screen.getByTestId("vehicles").innerHTML).toEqual(vehicles);
|
expect(screen.getByTestId("vehicles").innerHTML).toEqual(vehicles);
|
||||||
};
|
};
|
||||||
@@ -50,7 +55,7 @@ describe("VehicleContext", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("Initial state", () => {
|
it("Initial state", () => {
|
||||||
checkVehicleResults("", "false", "[]");
|
checkVehiclesResult("", "false", "[]");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("getVehicles", async () => {
|
it("getVehicles", async () => {
|
||||||
@@ -58,7 +63,48 @@ describe("VehicleContext", () => {
|
|||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(screen.getByTestId("vehicles").innerHTML).not.toBe("[]")
|
expect(screen.getByTestId("vehicles").innerHTML).not.toBe("[]")
|
||||||
);
|
);
|
||||||
checkVehicleResults("", "false", JSON.stringify(expectedVehicleData));
|
checkVehiclesResult("", "false", JSON.stringify(expectedVehiclesData));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getVehicle", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const TestComp = () => {
|
||||||
|
const { busy, error, vehicle, getVehicle } = useVehicleContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-testid="error">{error}</div>
|
||||||
|
<div data-testid="busy">{busy.toString()}</div>
|
||||||
|
<div data-testid="vehicle">{JSON.stringify(vehicle)}</div>
|
||||||
|
<button
|
||||||
|
data-testid="getVehicle"
|
||||||
|
onClick={() => getVehicle("3C4PDCBG0ET127145")}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<VehicleProvider>
|
||||||
|
<TestComp />
|
||||||
|
</VehicleProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Initial state", () => {
|
||||||
|
checkVehicleResult("", "false", "{}");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getVehicle", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("getVehicle"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("vehicle").innerHTML).not.toBe("{}")
|
||||||
|
);
|
||||||
|
checkVehicleResult("", "false", JSON.stringify(expectedVehicleData));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -131,15 +177,183 @@ describe("VehicleContext", () => {
|
|||||||
checkBaseResults("", "false");
|
checkBaseResults("", "false");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("updateVehicle", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const TestComp = () => {
|
||||||
|
const { busy, updateVehicle } = useVehicleContext();
|
||||||
|
const { message, setMessage } = useStatusContext();
|
||||||
|
const update = async (data) => {
|
||||||
|
try {
|
||||||
|
await updateVehicle("3C4PDCBG0ET127145", data);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-testid="error">{message}</div>
|
||||||
|
<div data-testid="busy">{busy.toString()}</div>
|
||||||
|
<button data-testid="updateVehicleNull" onClick={() => update(null)} />
|
||||||
|
<button data-testid="updateVehicleNoVIN" onClick={() => update({})} />
|
||||||
|
<button
|
||||||
|
data-testid="updateVehicle"
|
||||||
|
onClick={() =>
|
||||||
|
update({ vin: "3C4PDCBG0ET127145", log_level: "warn", canbus: { enabled: false } })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<StatusProvider>
|
||||||
|
<VehicleProvider>
|
||||||
|
<TestComp />
|
||||||
|
</VehicleProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initial state", () => {
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updateVehicleNull", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("updateVehicleNull"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("No vehicle data", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updateVehicleNoVIN", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("updateVehicleNoVIN"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("Invalid VIN", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updateVehicle", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("updateVehicle"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("deleteVehicle", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
const TestComp = () => {
|
||||||
|
const { busy, deleteVehicle } = useVehicleContext();
|
||||||
|
const { message, setMessage } = useStatusContext();
|
||||||
|
const deleteV = async (name) => {
|
||||||
|
try {
|
||||||
|
await deleteVehicle(name);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-testid="error">{message}</div>
|
||||||
|
<div data-testid="busy">{busy.toString()}</div>
|
||||||
|
<button data-testid="deleteVehicleNull" onClick={() => deleteV(null)} />
|
||||||
|
<button data-testid="deleteVehicleNonexistent" onClick={() => deleteV("11111111111111111")} />
|
||||||
|
<button
|
||||||
|
data-testid="deleteVehicle"
|
||||||
|
onClick={() =>
|
||||||
|
deleteV("3C4PDCBG0ET127145")
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(
|
||||||
|
<StatusProvider>
|
||||||
|
<VehicleProvider>
|
||||||
|
<TestComp />
|
||||||
|
</VehicleProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("initial state", () => {
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteVehicleNull", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("deleteVehicleNull"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("Invalid VIN", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteVehicleNonexistent", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("deleteVehicleNonexistent"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteVehicle", async () => {
|
||||||
|
fireEvent.click(screen.getByTestId("deleteVehicle"));
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByTestId("busy").innerHTML).toEqual("false")
|
||||||
|
);
|
||||||
|
checkBaseResults("", "false");
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const expectedVehicleData = [
|
const expectedFilters = [
|
||||||
|
{
|
||||||
|
can_id: "123-456",
|
||||||
|
interval: 789
|
||||||
|
},
|
||||||
|
{
|
||||||
|
can_id: "1",
|
||||||
|
interval: 1000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
can_id: "1000",
|
||||||
|
interval: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const expectedVehicleData = {
|
||||||
|
vin: "3C4PDCBG0ET127145",
|
||||||
|
year: 2021,
|
||||||
|
model: "Ocean",
|
||||||
|
trim: "Basic",
|
||||||
|
ecu_list: "ECUA 2.0.0, ECUB 2.1.1",
|
||||||
|
log_level: "info",
|
||||||
|
canbus: { enabled: true, data_logger_enabled: true, max_mem_buffer_size: 1, max_disk_buffer_size: 2, filters: expectedFilters },
|
||||||
|
connected: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedVehiclesData = [
|
||||||
{
|
{
|
||||||
vin: "3C4PDCBG0ET127145",
|
vin: "3C4PDCBG0ET127145",
|
||||||
year: 2021,
|
year: 2021,
|
||||||
model: "Ocean",
|
model: "Ocean",
|
||||||
trim: "Basic",
|
trim: "Basic",
|
||||||
ecu_list: "ECUA 2.0.0, ECUB 2.1.1",
|
ecu_list: "ECUA 2.0.0, ECUB 2.1.1",
|
||||||
|
log_level: "info",
|
||||||
|
canbus: { enabled: true, data_logger_enabled: true, max_mem_buffer_size: 1, max_disk_buffer_size: 2, filters: expectedFilters },
|
||||||
connected: true,
|
connected: true,
|
||||||
},
|
},
|
||||||
{ vin: "1G1FP87S3GN100062", connected: true },
|
{ vin: "1G1FP87S3GN100062", connected: true },
|
||||||
|
|||||||
30
src/components/Contexts/__mocks__/CANFiltersContext.jsx
Normal file
30
src/components/Contexts/__mocks__/CANFiltersContext.jsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
let busy = false;
|
||||||
|
let filters = [
|
||||||
|
{
|
||||||
|
can_id: "123",
|
||||||
|
interval: 1000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
can_id: "456-789",
|
||||||
|
interval: 2000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
can_id: "1",
|
||||||
|
interval: 0
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let totalFilters = 3;
|
||||||
|
|
||||||
|
export const CANFiltersProvider = ({ children }) => {
|
||||||
|
return <div data-testid="mocked-canfiltersprovider">{children}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCANFiltersContext = () => ({
|
||||||
|
busy,
|
||||||
|
filters,
|
||||||
|
totalFilters,
|
||||||
|
addFilter: jest.fn(),
|
||||||
|
getFilters: jest.fn(),
|
||||||
|
updateFilter: jest.fn(),
|
||||||
|
deleteFilter: jest.fn(),
|
||||||
|
});
|
||||||
@@ -44,8 +44,17 @@ let carUpdateLog = {
|
|||||||
created: "2021-08-23T17:06:38.030052Z",
|
created: "2021-08-23T17:06:38.030052Z",
|
||||||
updated: "2021-08-23T17:06:38.030052Z",
|
updated: "2021-08-23T17:06:38.030052Z",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 88,
|
||||||
|
carupdate_id: 284,
|
||||||
|
status: "install_approval_await",
|
||||||
|
error_code: 0,
|
||||||
|
info: "TEST",
|
||||||
|
created: "2021-08-23T17:06:38.030052Z",
|
||||||
|
updated: "2021-08-23T17:06:38.030052Z",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
total: 2,
|
total: 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CarUpdatesProvider = ({ children }) => {
|
export const CarUpdatesProvider = ({ children }) => {
|
||||||
@@ -64,4 +73,5 @@ export const useCarUpdatesContext = () => ({
|
|||||||
getVINUpdates: jest.fn(() => carUpdates),
|
getVINUpdates: jest.fn(() => carUpdates),
|
||||||
startMonitor: jest.fn(),
|
startMonitor: jest.fn(),
|
||||||
stopMonitor: jest.fn(),
|
stopMonitor: jest.fn(),
|
||||||
|
approveUpdate: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|||||||
75
src/components/Contexts/__mocks__/FleetContext.jsx
Normal file
75
src/components/Contexts/__mocks__/FleetContext.jsx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
let busy = false;
|
||||||
|
let fleetCANFilters = [
|
||||||
|
{
|
||||||
|
can_id: "123-456",
|
||||||
|
interval: 789
|
||||||
|
},
|
||||||
|
{
|
||||||
|
can_id: "1",
|
||||||
|
interval: 1000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
can_id: "1000",
|
||||||
|
interval: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
let fleet = {
|
||||||
|
name: "US-WEST",
|
||||||
|
log_level: "info",
|
||||||
|
canbus: { enabled: true, data_logger_enabled: true, max_mem_buffer_size: 1, max_disk_buffer_size: 2, filters: fleetCANFilters },
|
||||||
|
vehicles: ["USWESTVIN12345678", "USWESTVIN12345679", "USWESTVIN12345670"],
|
||||||
|
}
|
||||||
|
let fleets = [
|
||||||
|
{
|
||||||
|
name: "US-WEST",
|
||||||
|
log_level: "info",
|
||||||
|
canbus: { enabled: true, data_logger_enabled: true, max_mem_buffer_size: 1, max_disk_buffer_size: 2, filters: fleetCANFilters },
|
||||||
|
vehicles: ["USWESTVIN12345678", "USWESTVIN12345679", "USWESTVIN12345670"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "US-CENTRAL",
|
||||||
|
log_level: "warn",
|
||||||
|
canbus: { enabled: false, data_logger_enabled: false, max_mem_buffer_size: 0, max_disk_buffer_size: 0 },
|
||||||
|
vehicles: ["USCENTVIN12345678", "USCENTVIN12345679", "USCENTVIN12345670"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "US-EAST",
|
||||||
|
log_level: "error",
|
||||||
|
canbus: { enabled: true },
|
||||||
|
vehicles: ["USEASTVIN12345678", "USEASTVIN12345679", "USEASTVIN12345670"]
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let totalFleets = 3;
|
||||||
|
let fleetVehicles = ["USWESTVIN12345678", "USWESTVIN12345679", "USWESTVIN12345670"];
|
||||||
|
let totalFleetVehicles = 3;
|
||||||
|
let totalFleetCANFilters = 3;
|
||||||
|
|
||||||
|
export const FleetProvider = ({ children }) => {
|
||||||
|
return <div data-testid="mocked-fleetprovider">{children}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useFleetContext = () => ({
|
||||||
|
busy,
|
||||||
|
|
||||||
|
fleet,
|
||||||
|
fleets,
|
||||||
|
totalFleets,
|
||||||
|
addFleet: jest.fn(),
|
||||||
|
getFleet: jest.fn(),
|
||||||
|
getFleets: jest.fn(),
|
||||||
|
updateFleet: jest.fn(),
|
||||||
|
deleteFleet: jest.fn(),
|
||||||
|
|
||||||
|
fleetVehicles,
|
||||||
|
totalFleetVehicles,
|
||||||
|
getFleetVehicles: jest.fn(),
|
||||||
|
addFleetVehicle: jest.fn(),
|
||||||
|
deleteFleetVehicle: jest.fn(),
|
||||||
|
|
||||||
|
fleetCANFilters,
|
||||||
|
totalFleetCANFilters,
|
||||||
|
getFleetCANFilters: jest.fn(),
|
||||||
|
addFleetCANFilter: jest.fn(),
|
||||||
|
updateFleetCANFilter: jest.fn(),
|
||||||
|
deleteFleetCANFilter: jest.fn(),
|
||||||
|
});
|
||||||
@@ -7,13 +7,8 @@ let ecus = [
|
|||||||
{
|
{
|
||||||
data_id: 0,
|
data_id: 0,
|
||||||
name: "AGS",
|
name: "AGS",
|
||||||
part_number: "",
|
|
||||||
version: "",
|
version: "",
|
||||||
serial_number: "",
|
|
||||||
hw_version: "",
|
hw_version: "",
|
||||||
vendor: "",
|
|
||||||
configuration: "",
|
|
||||||
fingerprint: "",
|
|
||||||
manifest_id: 0,
|
manifest_id: 0,
|
||||||
files: [
|
files: [
|
||||||
{
|
{
|
||||||
@@ -21,6 +16,8 @@ let ecus = [
|
|||||||
order: 0,
|
order: 0,
|
||||||
offset: "0",
|
offset: "0",
|
||||||
checksum: "",
|
checksum: "",
|
||||||
|
self_download: false,
|
||||||
|
mode: "D",
|
||||||
type: 1,
|
type: 1,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -20,8 +20,29 @@ let manifest = {
|
|||||||
file_id: "b0cda514c94080b4",
|
file_id: "b0cda514c94080b4",
|
||||||
filename: "LARGE.jpg",
|
filename: "LARGE.jpg",
|
||||||
url: "https://upload-dev.fiskerdps.com/92bbc448-99c8-4851-91ad-f8042e4deb49/LARGE.jpg",
|
url: "https://upload-dev.fiskerdps.com/92bbc448-99c8-4851-91ad-f8042e4deb49/LARGE.jpg",
|
||||||
|
write_region: {
|
||||||
|
offset: 100,
|
||||||
|
length: 14488498,
|
||||||
|
},
|
||||||
|
erase_region: {
|
||||||
|
offset: 0,
|
||||||
|
length: 120559274,
|
||||||
|
},
|
||||||
file_size: 14559274,
|
file_size: 14559274,
|
||||||
size: 14488498,
|
type: "ODX Data",
|
||||||
|
created: "2021-12-09T22:38:29.102813Z",
|
||||||
|
updated: "2021-12-09T22:38:29.102813Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file_id: "4B897b1DcbeCds8e9",
|
||||||
|
filename: "SMALL.jpg",
|
||||||
|
url: "https://upload-dev.fiskerdps.com/92bbc448-99c8-4851-91ad-f8042e4deb49/SMALL.jpg",
|
||||||
|
write_region: {
|
||||||
|
offset: 120559274,
|
||||||
|
length: 559274,
|
||||||
|
},
|
||||||
|
checksum: "0a06d87c",
|
||||||
|
file_size: 488498,
|
||||||
type: "ODX Data",
|
type: "ODX Data",
|
||||||
created: "2021-12-09T22:38:29.102813Z",
|
created: "2021-12-09T22:38:29.102813Z",
|
||||||
updated: "2021-12-09T22:38:29.102813Z",
|
updated: "2021-12-09T22:38:29.102813Z",
|
||||||
|
|||||||
16
src/components/Contexts/__mocks__/StatusContext.jsx
Normal file
16
src/components/Contexts/__mocks__/StatusContext.jsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
let message = ""
|
||||||
|
let title = ""
|
||||||
|
let sitePath = {}
|
||||||
|
|
||||||
|
export const StatusProvider = ({ children }) => {
|
||||||
|
return <div data-testid="mocked-statusprovider">{children}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useStatusContext = () => ({
|
||||||
|
message,
|
||||||
|
title,
|
||||||
|
sitePath,
|
||||||
|
setMessage: jest.fn(m => message = m),
|
||||||
|
setTitle: jest.fn(t => title = t),
|
||||||
|
setSitePath: jest.fn(s => sitePath = s),
|
||||||
|
});
|
||||||
@@ -1,6 +1,31 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
let busy = false;
|
let busy = false;
|
||||||
|
|
||||||
|
const filters = [
|
||||||
|
{
|
||||||
|
can_id: "123-456",
|
||||||
|
interval: 789
|
||||||
|
},
|
||||||
|
{
|
||||||
|
can_id: "1",
|
||||||
|
interval: 1000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
can_id: "1000",
|
||||||
|
interval: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
let vehicle = {
|
||||||
|
vin: "3C4PDCBG0ET127145",
|
||||||
|
year: 2021,
|
||||||
|
model: "Ocean",
|
||||||
|
trim: "Basic",
|
||||||
|
ecu_list: "ECUA 2.0.0, ECUB 2.1.1",
|
||||||
|
log_level: "info",
|
||||||
|
canbus: { enabled: true, data_logger_enabled: true, max_mem_buffer_size: 1, max_disk_buffer_size: 2, filters: filters },
|
||||||
|
};
|
||||||
let vehicles = [];
|
let vehicles = [];
|
||||||
let models = ["Ocean", "PEAR"];
|
let models = ["Ocean", "PEAR"];
|
||||||
let years = [2023, 2024];
|
let years = [2023, 2024];
|
||||||
@@ -15,10 +40,11 @@ export const useVehicleContext = () => ({
|
|||||||
busy,
|
busy,
|
||||||
models,
|
models,
|
||||||
totalVehicles,
|
totalVehicles,
|
||||||
|
vehicle,
|
||||||
vehicles,
|
vehicles,
|
||||||
years,
|
years,
|
||||||
addVehicle: jest.fn(),
|
addVehicle: jest.fn(),
|
||||||
getConnections: jest.fn((vins, token) => {
|
getConnections: jest.fn((vins, _token) => {
|
||||||
const result = {};
|
const result = {};
|
||||||
|
|
||||||
vins.forEach((vin) => {
|
vins.forEach((vin) => {
|
||||||
@@ -31,28 +57,19 @@ export const useVehicleContext = () => ({
|
|||||||
return {
|
return {
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
boot_loader_version: "BLVERSION",
|
|
||||||
config: "CONFIG",
|
config: "CONFIG",
|
||||||
created: "2021-07-14T20:09:40.98187Z",
|
created: "2021-07-14T20:09:40.98187Z",
|
||||||
ecu: "ECUA",
|
ecu: "ECUA",
|
||||||
fingerprint: "FINGERPRINT",
|
|
||||||
hw_version: "HWVERSION",
|
hw_version: "HWVERSION",
|
||||||
serial_number: "SERIAL",
|
|
||||||
sw_version: "SWVERSION",
|
sw_version: "SWVERSION",
|
||||||
updated: "2021-07-14T20:09:40.98187Z",
|
updated: "2021-07-14T20:09:40.98187Z",
|
||||||
vendor: "VENDOR",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
boot_loader_version: "BLVERSION",
|
|
||||||
config: "CONFIG",
|
config: "CONFIG",
|
||||||
created: "2021-07-14T20:09:40.98187Z",
|
created: "2021-07-14T20:09:40.98187Z",
|
||||||
ecu: "ECUB",
|
|
||||||
fingerprint: "FINGERPRINT",
|
|
||||||
hw_version: "HWVERSION",
|
hw_version: "HWVERSION",
|
||||||
serial_number: "SERIAL",
|
|
||||||
sw_version: "SWVERSION",
|
sw_version: "SWVERSION",
|
||||||
updated: "2021-07-14T20:09:40.98187Z",
|
updated: "2021-07-14T20:09:40.98187Z",
|
||||||
vendor: "VENDOR",
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
total: 2,
|
total: 2,
|
||||||
@@ -70,8 +87,9 @@ export const useVehicleContext = () => ({
|
|||||||
getYears: jest.fn(() => {
|
getYears: jest.fn(() => {
|
||||||
years = [2023, 2024];
|
years = [2023, 2024];
|
||||||
}),
|
}),
|
||||||
|
getVehicle: jest.fn(),
|
||||||
getVehicles: jest.fn(() => vehicles),
|
getVehicles: jest.fn(() => vehicles),
|
||||||
sendCommand: jest.fn((vins, command, parameters, token) => ({
|
sendCommand: jest.fn((vins, command, parameters, _token) => ({
|
||||||
vins,
|
vins,
|
||||||
command,
|
command,
|
||||||
parameters,
|
parameters,
|
||||||
|
|||||||
@@ -24,30 +24,14 @@ const tableColumns = [
|
|||||||
id: "sw_version",
|
id: "sw_version",
|
||||||
label: "SW Version",
|
label: "SW Version",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "boot_loader_version",
|
|
||||||
label: "BL Version",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "hw_version",
|
id: "hw_version",
|
||||||
label: "HW Version",
|
label: "HW Version",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "vendor",
|
|
||||||
label: "Vendor",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "config",
|
id: "config",
|
||||||
label: "Config",
|
label: "Config",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "fingerprint",
|
|
||||||
label: "Fingerprint",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "serial_number",
|
|
||||||
label: "Serial",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "created_at",
|
id: "created_at",
|
||||||
label: "Created",
|
label: "Created",
|
||||||
@@ -128,16 +112,12 @@ const CarECUsTable = ({ vin, token, classes }) => {
|
|||||||
onSortRequest={handleSort}
|
onSortRequest={handleSort}
|
||||||
/>
|
/>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{ecus.map((row) => (
|
{ecus.map((row, i) => (
|
||||||
<TableRow key={row.ecu}>
|
<TableRow key={row.ecu + i}>
|
||||||
<TableCell align="center">{row.ecu}</TableCell>
|
<TableCell align="center">{row.ecu}</TableCell>
|
||||||
<TableCell align="center">{row.sw_version}</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.hw_version}</TableCell>
|
||||||
<TableCell align="center">{row.vendor}</TableCell>
|
|
||||||
<TableCell align="center">{row.config}</TableCell>
|
<TableCell align="center">{row.config}</TableCell>
|
||||||
<TableCell align="center">{row.fingerprint}</TableCell>
|
|
||||||
<TableCell align="center">{row.serial_number}</TableCell>
|
|
||||||
<TableCell align="center">
|
<TableCell align="center">
|
||||||
{LocalDateTimeString(row.created)}
|
{LocalDateTimeString(row.created)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ const tableColumns = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const CarSelectionTable = (props) => {
|
const CarSelectionTable = (props) => {
|
||||||
const { token, classes, search, selected, onSelect, onSelectAll } = props;
|
const { token, classes, search, multiSelect, selected, onSelect, onSelectAll } = props;
|
||||||
const [pageSize, setPageSize] = useState(10);
|
const [pageSize, setPageSize] = useState(10);
|
||||||
const [pageIndex, setPageIndex] = useState(0);
|
const [pageIndex, setPageIndex] = useState(0);
|
||||||
const [orderBy, setOrderBy] = useState("vin");
|
const [orderBy, setOrderBy] = useState("vin");
|
||||||
@@ -56,7 +56,7 @@ const CarSelectionTable = (props) => {
|
|||||||
const { getVehicles, vehicles, totalVehicles } = useVehicleContext();
|
const { getVehicles, vehicles, totalVehicles } = useVehicleContext();
|
||||||
const { setMessage } = useStatusContext();
|
const { setMessage } = useStatusContext();
|
||||||
const { search: searchTerm } = search;
|
const { search: searchTerm } = search;
|
||||||
const sortHandler = (event, property) => {
|
const sortHandler = (_event, property) => {
|
||||||
if (property === orderBy) {
|
if (property === orderBy) {
|
||||||
if (order === "asc") {
|
if (order === "asc") {
|
||||||
setOrder("desc");
|
setOrder("desc");
|
||||||
@@ -69,7 +69,7 @@ const CarSelectionTable = (props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChangePageIndex = (event, newIndex) => {
|
const handleChangePageIndex = (_event, newIndex) => {
|
||||||
setPageIndex(newIndex);
|
setPageIndex(newIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -123,22 +123,22 @@ const CarSelectionTable = (props) => {
|
|||||||
order={order}
|
order={order}
|
||||||
columnData={tableColumns}
|
columnData={tableColumns}
|
||||||
onSortRequest={sortHandler}
|
onSortRequest={sortHandler}
|
||||||
multiSelect={true}
|
multiSelect={multiSelect}
|
||||||
onSelectAll={handleSelectAll}
|
onSelectAll={handleSelectAll}
|
||||||
selectCount={selected.length}
|
selectCount={selected ? selected.length : 0}
|
||||||
rowCount={vehicles.length}
|
rowCount={vehicles.length}
|
||||||
/>
|
/>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{vehicles.map((row) => {
|
{vehicles.map((row) => {
|
||||||
const isSelected = selected.indexOf(row.vin) !== -1;
|
const isSelected = selected ? selected.indexOf(row.vin) !== -1 : false;
|
||||||
return (
|
return (
|
||||||
<TableRow key={row.vin}>
|
<TableRow key={row.vin}>
|
||||||
<TableCell padding="checkbox">
|
{multiSelect && (<TableCell padding="checkbox">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={isSelected}
|
checked={isSelected}
|
||||||
onChange={(event) => handleSelect(event, row.vin)}
|
onChange={(event) => handleSelect(event, row.vin)}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>)}
|
||||||
<TableCell align="center">
|
<TableCell align="center">
|
||||||
<ConnectedIcon
|
<ConnectedIcon
|
||||||
connected={row.connected}
|
connected={row.connected}
|
||||||
@@ -195,9 +195,10 @@ CarSelectionTable.propTypes = {
|
|||||||
token: PropTypes.string.isRequired,
|
token: PropTypes.string.isRequired,
|
||||||
classes: PropTypes.object.isRequired,
|
classes: PropTypes.object.isRequired,
|
||||||
search: PropTypes.object.isRequired,
|
search: PropTypes.object.isRequired,
|
||||||
selected: PropTypes.array.isRequired,
|
multiSelect: PropTypes.bool.isRequired,
|
||||||
onSelect: PropTypes.func.isRequired,
|
selected: PropTypes.array,
|
||||||
onSelectAll: PropTypes.func.isRequired,
|
onSelect: PropTypes.func,
|
||||||
|
onSelectAll: PropTypes.func,
|
||||||
connectionStatus: PropTypes.bool,
|
connectionStatus: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { render, waitFor } from "@testing-library/react";
|
|||||||
|
|
||||||
import CarUpdateStatusProgress from "../CarUpdateStatusProgress";
|
import CarUpdateStatusProgress from "../CarUpdateStatusProgress";
|
||||||
import useStyles from "../../useStyles";
|
import useStyles from "../../useStyles";
|
||||||
|
import s from "./Statuses";
|
||||||
|
|
||||||
const TestWrapper = ({ status }) => {
|
const TestWrapper = ({ status }) => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
@@ -21,7 +22,7 @@ describe("CarUpdateStatusProgress", () => {
|
|||||||
name: "manifest_received",
|
name: "manifest_received",
|
||||||
status: {
|
status: {
|
||||||
car_update_id: 297,
|
car_update_id: 297,
|
||||||
msg: "manifest_received",
|
msg: s.ManifestReceived,
|
||||||
err: -6,
|
err: -6,
|
||||||
extra_info: "",
|
extra_info: "",
|
||||||
},
|
},
|
||||||
@@ -30,7 +31,7 @@ describe("CarUpdateStatusProgress", () => {
|
|||||||
name: "manifest_accepted",
|
name: "manifest_accepted",
|
||||||
status: {
|
status: {
|
||||||
car_update_id: 297,
|
car_update_id: 297,
|
||||||
msg: "manifest_accepted",
|
msg: s.ManifestAccepted,
|
||||||
err: -7,
|
err: -7,
|
||||||
extra_info: "",
|
extra_info: "",
|
||||||
},
|
},
|
||||||
@@ -39,7 +40,7 @@ describe("CarUpdateStatusProgress", () => {
|
|||||||
name: "download_started",
|
name: "download_started",
|
||||||
status: {
|
status: {
|
||||||
car_update_id: 297,
|
car_update_id: 297,
|
||||||
msg: "download_started",
|
msg: s.DownloadStarted,
|
||||||
err: -14,
|
err: -14,
|
||||||
extra_info: "",
|
extra_info: "",
|
||||||
},
|
},
|
||||||
@@ -53,7 +54,7 @@ describe("CarUpdateStatusProgress", () => {
|
|||||||
file_total: 1264672,
|
file_total: 1264672,
|
||||||
package_current: 0,
|
package_current: 0,
|
||||||
package_total: 2529856,
|
package_total: 2529856,
|
||||||
msg: "download_start",
|
msg: s.DownloadStarted,
|
||||||
err: 0,
|
err: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -66,7 +67,7 @@ describe("CarUpdateStatusProgress", () => {
|
|||||||
file_total: 1264672,
|
file_total: 1264672,
|
||||||
package_current: 1048576,
|
package_current: 1048576,
|
||||||
package_total: 2529856,
|
package_total: 2529856,
|
||||||
msg: "downloading",
|
msg: s.Downloading,
|
||||||
err: 0,
|
err: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -79,7 +80,7 @@ describe("CarUpdateStatusProgress", () => {
|
|||||||
file_total: 1264672,
|
file_total: 1264672,
|
||||||
package_current: 1264672,
|
package_current: 1264672,
|
||||||
package_total: 2529856,
|
package_total: 2529856,
|
||||||
msg: "download_complete",
|
msg: s.DownloadCompleted,
|
||||||
err: 0,
|
err: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -87,7 +88,7 @@ describe("CarUpdateStatusProgress", () => {
|
|||||||
name: "package_download_complete",
|
name: "package_download_complete",
|
||||||
status: {
|
status: {
|
||||||
car_update_id: 297,
|
car_update_id: 297,
|
||||||
msg: "package_download_complete",
|
msg: s.PackageDownloadCompleted,
|
||||||
err: -15,
|
err: -15,
|
||||||
extra_info: "",
|
extra_info: "",
|
||||||
},
|
},
|
||||||
@@ -101,7 +102,7 @@ describe("CarUpdateStatusProgress", () => {
|
|||||||
file_total: 100,
|
file_total: 100,
|
||||||
package_current: 0,
|
package_current: 0,
|
||||||
package_total: 1000,
|
package_total: 1000,
|
||||||
msg: "download_error",
|
msg: s.DownloadFailed,
|
||||||
err: 0,
|
err: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -112,7 +113,7 @@ describe("CarUpdateStatusProgress", () => {
|
|||||||
ecu: "TEST",
|
ecu: "TEST",
|
||||||
installed: 5,
|
installed: 5,
|
||||||
total_files: 10,
|
total_files: 10,
|
||||||
msg: "installing",
|
msg: s.Installing,
|
||||||
err: 0,
|
err: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -123,7 +124,7 @@ describe("CarUpdateStatusProgress", () => {
|
|||||||
ecu: "TEST",
|
ecu: "TEST",
|
||||||
installed: 10,
|
installed: 10,
|
||||||
total_files: 10,
|
total_files: 10,
|
||||||
msg: "install_complete",
|
msg: s.InstallSucceeded,
|
||||||
err: 0,
|
err: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -134,7 +135,7 @@ describe("CarUpdateStatusProgress", () => {
|
|||||||
ecu: "TEST",
|
ecu: "TEST",
|
||||||
installed: 5,
|
installed: 5,
|
||||||
total_files: 10,
|
total_files: 10,
|
||||||
msg: "install_error",
|
msg: s.InstallFailed,
|
||||||
err: 0,
|
err: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
38
src/components/Controls/CarUpdateStatusProgress/Statuses.js
Normal file
38
src/components/Controls/CarUpdateStatusProgress/Statuses.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
const Statuses = {
|
||||||
|
Pending: "pending",
|
||||||
|
ManifestReceived: "manifest_received",
|
||||||
|
ManifestAccepted: "manifest_accepted",
|
||||||
|
ManifestRejected: "manifest_rejected",
|
||||||
|
PreconditionAwait: "requirements_await",
|
||||||
|
PreconditionSuceeded: "requirements_succeeded",
|
||||||
|
ManifestCancelReceived: "manifest_cancel_received",
|
||||||
|
ManifestCancelAccepted: "manifest_cancel_accepted",
|
||||||
|
ManifestCancelRejected: "manifest_cancel_rejected",
|
||||||
|
ManifestValidationSucceeded: "manifest_validation_succeeded",
|
||||||
|
ManifestValidationFailed: "manifest_validation_failed",
|
||||||
|
DownloadStarted: "download_started",
|
||||||
|
Downloading: "downloading",
|
||||||
|
DownloadCompleted: "download_completed",
|
||||||
|
DownloadFailed: "download_failed",
|
||||||
|
InstallApprovalAwait: "install_approval_await",
|
||||||
|
InstallApprovalReceived: "install_approval_received",
|
||||||
|
InstallStarted: "install_started",
|
||||||
|
Installing: "installing",
|
||||||
|
InstallSucceeded: "install_succeeded",
|
||||||
|
InstallFailed: "install_failed",
|
||||||
|
RollbackStarted: "rollback_started",
|
||||||
|
RollbackSucceeded: "rollback_succeeded",
|
||||||
|
RollbackFailed: "rollback_failed",
|
||||||
|
CleanupSucceeded: "cleanup_succeeded",
|
||||||
|
CleanupFailed: "cleanup_failed",
|
||||||
|
ManifestError: "manifest_error",
|
||||||
|
ManifestRollback: "manifest_rollback",
|
||||||
|
ManifestSucceeded: "manifest_succeeded",
|
||||||
|
ManifesCanceled: "manifest_canceled",
|
||||||
|
PackageDownloadStarted: "package_download_start",
|
||||||
|
PackageDownloadCompleted: "package_download_complete",
|
||||||
|
PackageInstallStarted: "package_install_start",
|
||||||
|
PackageInstallCompleted: "package_install_complete",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Statuses;
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -4,78 +4,65 @@ import Typography from "@material-ui/core/Typography";
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
import CircularProgress from "../CircularProgress";
|
import CircularProgress from "../CircularProgress";
|
||||||
|
import s from "./Statuses";
|
||||||
|
|
||||||
|
const AwaitStatus = -1;
|
||||||
|
const ErrorStatus = -100;
|
||||||
|
|
||||||
const PHASES = [
|
const PHASES = [
|
||||||
{
|
{
|
||||||
label: "Pending",
|
label: "Pending",
|
||||||
events: ["pending"],
|
events: [s.Pending],
|
||||||
progress: () => 100,
|
progress: () => 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Recieved",
|
label: "Received",
|
||||||
events: ["manifest_accepted", "manifest_received"],
|
events: [s.ManifestAccepted, s.ManifestReceived, s.ManifestRejected],
|
||||||
progress: () => 100,
|
progress: () => 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Precondition",
|
label: "Precondition",
|
||||||
events: ["requirements_succeeded"],
|
events: [s.PreconditionAwait, s.PreconditionSuceeded],
|
||||||
progress: () => 100,
|
progress: () => 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Download",
|
label: "Download",
|
||||||
events: [
|
events: [
|
||||||
"downloading",
|
s.Downloading,
|
||||||
"download_start",
|
s.DownloadStarted,
|
||||||
"download_complete",
|
s.DownloadCompleted,
|
||||||
"download_error",
|
s.DownloadFailed,
|
||||||
"package_download_start",
|
s.PackageDownloadStarted,
|
||||||
],
|
],
|
||||||
progress: (msg, progress) =>
|
progress: (msg, progress) => {
|
||||||
[
|
if (msg === s.DownloadFailed) return ErrorStatus;
|
||||||
"package_download_start",
|
return progress;
|
||||||
"downloading",
|
},
|
||||||
"download_start",
|
|
||||||
"download_complete",
|
|
||||||
].indexOf(msg) > -1
|
|
||||||
? progress
|
|
||||||
: -100,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Approved",
|
label: "Approved",
|
||||||
events: ["package_download_complete", "install_approval_await"],
|
events: [s.PackageDownloadCompleted, s.InstallApprovalAwait],
|
||||||
progress: () => -1,
|
progress: () => AwaitStatus,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Install",
|
label: "Install",
|
||||||
events: [
|
events: [
|
||||||
"install_approval_received",
|
s.InstallApprovalReceived,
|
||||||
"install_start",
|
s.InstallStarted,
|
||||||
"installing",
|
s.Installing,
|
||||||
"install_complete",
|
s.InstallSucceeded,
|
||||||
"install_error",
|
s.InstallFailed,
|
||||||
],
|
],
|
||||||
progress: (msg, progress) =>
|
|
||||||
[
|
|
||||||
"install_approval_received",
|
|
||||||
"install_start",
|
|
||||||
"installing",
|
|
||||||
"install_complete",
|
|
||||||
].indexOf(msg) > -1
|
|
||||||
? progress
|
|
||||||
: -100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Clean up",
|
|
||||||
events: ["package_install_complete", "cleanup_failed"],
|
|
||||||
progress: (msg, progress) => {
|
progress: (msg, progress) => {
|
||||||
if (msg === "package_install_complete") return -1;
|
if (msg === s.InstallFailed) return ErrorStatus;
|
||||||
return -100;
|
if (msg === s.PackageInstallCompleted) return 100;
|
||||||
|
return progress;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Updated",
|
label: "Updated",
|
||||||
events: ["cleanup_success", "manifest_succeeded"],
|
events: [s.ManifestSucceeded],
|
||||||
progress: (msg, progress) => 100,
|
progress: (_msg, _progress) => 100,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -153,6 +153,28 @@ exports[`CarUpdateStatusTable Render 1`] = `
|
|||||||
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
/>
|
/>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr
|
||||||
|
class="MuiTableRow-root"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
8/23/2021 5:06:38 PM
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
install_approval_await
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
TEST
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
/>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot
|
<tfoot
|
||||||
class="MuiTableFooter-root"
|
class="MuiTableFooter-root"
|
||||||
@@ -223,7 +245,7 @@ exports[`CarUpdateStatusTable Render 1`] = `
|
|||||||
<p
|
<p
|
||||||
class="MuiTypography-root MuiTablePagination-caption MuiTypography-body2 MuiTypography-colorInherit"
|
class="MuiTypography-root MuiTablePagination-caption MuiTypography-body2 MuiTypography-colorInherit"
|
||||||
>
|
>
|
||||||
1-2 of 2
|
1-3 of 3
|
||||||
</p>
|
</p>
|
||||||
<div
|
<div
|
||||||
class="MuiTablePagination-actions"
|
class="MuiTablePagination-actions"
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`DownloadFileLink Render 1`] = `
|
||||||
|
<div>
|
||||||
|
<a
|
||||||
|
download="test.txt"
|
||||||
|
>
|
||||||
|
test.txt
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
32
src/components/Controls/DownloadFileLink/index.jsx
Normal file
32
src/components/Controls/DownloadFileLink/index.jsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
const DownloadFileLink = ({ data, filename, mimetype }) => {
|
||||||
|
const [link, setLink] = useState("");
|
||||||
|
|
||||||
|
const releaseLink = () => {
|
||||||
|
if (link === "") return;
|
||||||
|
URL.revokeObjectURL(link);
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeFile = () => {
|
||||||
|
const file = new Blob([data], { type: mimetype ?? "text/plain" });
|
||||||
|
|
||||||
|
releaseLink();
|
||||||
|
setLink(URL.createObjectURL(file));
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!data) return;
|
||||||
|
makeFile();
|
||||||
|
return releaseLink;
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [data, filename, mimetype]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a download={filename ?? "file.txt"} href={link}>
|
||||||
|
{filename}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DownloadFileLink;
|
||||||
21
src/components/Controls/DownloadFileLink/index.test.jsx
Normal file
21
src/components/Controls/DownloadFileLink/index.test.jsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { render, waitFor } from "@testing-library/react";
|
||||||
|
|
||||||
|
import DownloadFileLink from ".";
|
||||||
|
|
||||||
|
describe("DownloadFileLink", () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
global.URL.createObjectURL = jest.fn();
|
||||||
|
global.URL.revokeObjectURL = jest.fn();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Render", async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<DownloadFileLink data={"ABCDEFGHIJK"} filename="test.txt" />
|
||||||
|
);
|
||||||
|
await waitFor(() => {
|
||||||
|
/* render */
|
||||||
|
});
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -31,22 +31,10 @@ const options = [
|
|||||||
field: "version",
|
field: "version",
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: "Part Number",
|
|
||||||
field: "part_number",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Serial",
|
|
||||||
field: "serial_number",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: "Hardware",
|
label: "Hardware",
|
||||||
field: "hw_version",
|
field: "hw_version",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: "Vendor",
|
|
||||||
field: "vendor",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: "",
|
label: "",
|
||||||
delete: true,
|
delete: true,
|
||||||
@@ -74,7 +62,7 @@ const ManifestECUList = () => {
|
|||||||
</TableBody>
|
</TableBody>
|
||||||
<TableFooter>
|
<TableFooter>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={8} align="center">
|
<TableCell colSpan={5} align="center">
|
||||||
<Button onClick={addECU}>Add ECU</Button>
|
<Button onClick={addECU}>Add ECU</Button>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`TabPanel Render 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
aria-labelledby="tab-0"
|
||||||
|
id="tabpanel-0"
|
||||||
|
role="tabpanel"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiBox-root MuiBox-root-1"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
Test
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
24
src/components/Controls/TabPanel/index.jsx
Normal file
24
src/components/Controls/TabPanel/index.jsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Box } from "@material-ui/core";
|
||||||
|
|
||||||
|
function TabPanel(props) {
|
||||||
|
const { children, value, index, ...other } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="tabpanel"
|
||||||
|
hidden={value !== index}
|
||||||
|
id={`tabpanel-${index}`}
|
||||||
|
aria-labelledby={`tab-${index}`}
|
||||||
|
{...other}
|
||||||
|
>
|
||||||
|
{value === index && (
|
||||||
|
<Box sx={{ p: 3 }}>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</div >
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TabPanel;
|
||||||
21
src/components/Controls/TabPanel/index.test.jsx
Normal file
21
src/components/Controls/TabPanel/index.test.jsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { render, waitFor } from "@testing-library/react";
|
||||||
|
|
||||||
|
import TabPanel from "./index"
|
||||||
|
|
||||||
|
|
||||||
|
const renderTabPanel = async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<TabPanel value={0} index={0}>
|
||||||
|
<div>Test</div>
|
||||||
|
</TabPanel>
|
||||||
|
);
|
||||||
|
await waitFor(() => { });
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("TabPanel", () => {
|
||||||
|
it("Render", async () => {
|
||||||
|
const container = await renderTabPanel();
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import {
|
|
||||||
FormControl,
|
|
||||||
Grid,
|
|
||||||
InputLabel,
|
|
||||||
MenuItem,
|
|
||||||
Paper,
|
|
||||||
Select,
|
|
||||||
TextField,
|
|
||||||
} from "@material-ui/core";
|
|
||||||
|
|
||||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
|
||||||
import useStyles from "../../useStyles";
|
|
||||||
import ResponsiveIFrame from "../../Controls/ResponsiveIFrame";
|
|
||||||
import { grafanaCharts } from "../../../services/grafanaCharts";
|
|
||||||
|
|
||||||
const Battery = () => {
|
|
||||||
const classes = useStyles();
|
|
||||||
const { setTitle, setSitePath } = useStatusContext();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTitle("Battery");
|
|
||||||
setSitePath([
|
|
||||||
{
|
|
||||||
label: "Datascope",
|
|
||||||
link: "/datascope",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Battery",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const [vin, setVIN] = useState("1F15K3R45N1234567");
|
|
||||||
const [cellNum, setCellNum] = useState(1);
|
|
||||||
|
|
||||||
const handleVINForm = (e) => {
|
|
||||||
if (e.target.value.length === 17) {
|
|
||||||
setVIN(e.target.value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.paper}>
|
|
||||||
<Grid container spacing={2}>
|
|
||||||
<Grid container item md={4} space={2}>
|
|
||||||
<Grid item md={12}>
|
|
||||||
<Paper className={classes.grafanaContainer}>
|
|
||||||
<form className={classes.formControl}>
|
|
||||||
<TextField
|
|
||||||
id="vin"
|
|
||||||
label="VIN"
|
|
||||||
defaultValue="1F15K3R45N1234567"
|
|
||||||
variant="outlined"
|
|
||||||
onChange={handleVINForm}
|
|
||||||
style={{ width: "100%" }}
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
<FormControl variant="outlined" className={classes.formControl}>
|
|
||||||
<InputLabel id="demo-simple-select-outlined-label">
|
|
||||||
Cell
|
|
||||||
</InputLabel>
|
|
||||||
<Select
|
|
||||||
labelId="demo-simple-select-outlined-label"
|
|
||||||
id="demo-simple-select-outlined"
|
|
||||||
value={cellNum}
|
|
||||||
onChange={(e) => setCellNum(e.target.value)}
|
|
||||||
label="Cell"
|
|
||||||
>
|
|
||||||
{[...Array(112)].map((_, i) => (
|
|
||||||
<MenuItem key={i + 1} value={i + 1}>
|
|
||||||
{i + 1}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
</Paper>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid item md={12}>
|
|
||||||
<Paper className={classes.grafanaContainer}>
|
|
||||||
Cell Voltage {cellNum}
|
|
||||||
<ResponsiveIFrame
|
|
||||||
classes={classes}
|
|
||||||
src={grafanaCharts.CELLVOLTAGE_CHART({ vin, cellNum })}
|
|
||||||
title="Cell Voltage"
|
|
||||||
/>
|
|
||||||
</Paper>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid item md={12}>
|
|
||||||
<Paper className={classes.grafanaContainer}>
|
|
||||||
Cell Temperature {cellNum}
|
|
||||||
<ResponsiveIFrame
|
|
||||||
classes={classes}
|
|
||||||
src={grafanaCharts.CELLTEMP_CHART({ vin, cellNum })}
|
|
||||||
title="Cell Temperature"
|
|
||||||
/>
|
|
||||||
</Paper>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid item md={12}>
|
|
||||||
<Paper className={classes.grafanaContainer}>
|
|
||||||
<ResponsiveIFrame
|
|
||||||
classes={classes}
|
|
||||||
src={grafanaCharts.BATTERYTEMP_CHART({ vin })}
|
|
||||||
title="Battery Temperature Time Series"
|
|
||||||
/>
|
|
||||||
</Paper>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid container item md={8} space={2}>
|
|
||||||
<Grid item md={12}>
|
|
||||||
<Paper className={classes.grafanaContainer}>
|
|
||||||
<ResponsiveIFrame
|
|
||||||
classes={classes}
|
|
||||||
src={grafanaCharts.BATTERYCAP_CHART({ vin })}
|
|
||||||
title="Battery Capacity Time Series"
|
|
||||||
/>
|
|
||||||
</Paper>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid item md={12}>
|
|
||||||
<Paper className={classes.grafanaContainer}>
|
|
||||||
<ResponsiveIFrame
|
|
||||||
classes={classes}
|
|
||||||
src={grafanaCharts.BATTERYPERCENT_CHART({ vin })}
|
|
||||||
title="Battery Percent Time Series"
|
|
||||||
/>
|
|
||||||
</Paper>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid item md={6}>
|
|
||||||
<Paper className={classes.grafanaContainer}>
|
|
||||||
<ResponsiveIFrame
|
|
||||||
classes={classes}
|
|
||||||
src={grafanaCharts.BATTERY12VPERCENT_CHART({ vin })}
|
|
||||||
title="12V Battery Percentage Time Series"
|
|
||||||
/>
|
|
||||||
</Paper>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid item md={6}>
|
|
||||||
<Paper className={classes.grafanaContainer}>
|
|
||||||
<ResponsiveIFrame
|
|
||||||
classes={classes}
|
|
||||||
src={grafanaCharts.BATTERY12VVOLTAGE_CHART({ vin })}
|
|
||||||
title="12V Battery Voltage Time Series"
|
|
||||||
/>
|
|
||||||
</Paper>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Battery;
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import { Button, Grid, Link, Paper } from "@material-ui/core";
|
|
||||||
import CreateIcon from "@material-ui/icons/Create";
|
|
||||||
|
|
||||||
import api from "../../../services/grafanaAPI";
|
|
||||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
|
||||||
import useStyles from "../../useStyles";
|
|
||||||
import ResponsiveIFrame from "../../Controls/ResponsiveIFrame";
|
|
||||||
import { logger } from "../../../services/monitoring";
|
|
||||||
import { grafanaCharts } from "../../../services/grafanaCharts";
|
|
||||||
|
|
||||||
const Datascope = () => {
|
|
||||||
const classes = useStyles();
|
|
||||||
const { setTitle, setSitePath } = useStatusContext();
|
|
||||||
const REQUEST_INTERVAL = 10000;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTitle("Datascope");
|
|
||||||
setSitePath([]);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const [carsCount, setCarsCount] = useState(0);
|
|
||||||
useEffect(() => {
|
|
||||||
api
|
|
||||||
.getCarsCount()
|
|
||||||
.then((result) => setCarsCount(result))
|
|
||||||
.catch((error) => logger.warn(error.stack));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const [signalsCount, setSignalsCount] = useState("0");
|
|
||||||
useEffect(() => {
|
|
||||||
storeSignals();
|
|
||||||
|
|
||||||
const id = setInterval(function () {
|
|
||||||
storeSignals();
|
|
||||||
}, REQUEST_INTERVAL);
|
|
||||||
return () => {
|
|
||||||
clearInterval(id);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const storeSignals = () => {
|
|
||||||
api
|
|
||||||
.getSignalsCount()
|
|
||||||
.then((result) => {
|
|
||||||
let num = result.toLocaleString();
|
|
||||||
setSignalsCount(num);
|
|
||||||
})
|
|
||||||
.catch((error) => logger.warn(error.stack));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.paper}>
|
|
||||||
<Grid container className={classes.root} spacing={2}>
|
|
||||||
<Grid item md={6}>
|
|
||||||
<Paper className={classes.grafanaContainer} style={{ height: 150 }}>
|
|
||||||
<h1 className={classes.datascopeContainerValue}>{carsCount}</h1>
|
|
||||||
<h2 className={classes.datascopeContainerText}>Cars</h2>
|
|
||||||
</Paper>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid item md={6}>
|
|
||||||
<Paper className={classes.grafanaContainer} style={{ height: 150 }}>
|
|
||||||
<h1 className={classes.datascopeContainerValue}>{signalsCount}</h1>
|
|
||||||
<h2 className={classes.datascopeContainerText}>
|
|
||||||
Signals Collected
|
|
||||||
</h2>
|
|
||||||
</Paper>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid item md={12}>
|
|
||||||
<Paper className={classes.grafanaContainer}>
|
|
||||||
<ResponsiveIFrame
|
|
||||||
classes={classes}
|
|
||||||
src={grafanaCharts.HOME_CHART}
|
|
||||||
title="Signals Time Series"
|
|
||||||
/>
|
|
||||||
</Paper>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
<Button
|
|
||||||
style={{ marginTop: 10 }}
|
|
||||||
aria-label="create"
|
|
||||||
color="primary"
|
|
||||||
component={Link}
|
|
||||||
href={grafanaCharts.BASE}
|
|
||||||
rel="noopener"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<CreateIcon fontSize="large" />
|
|
||||||
Go to Grafana
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Datascope;
|
|
||||||
590
src/components/Fleets/Add/__snapshots__/index.test.jsx.snap
Normal file
590
src/components/Fleets/Add/__snapshots__/index.test.jsx.snap
Normal file
@@ -0,0 +1,590 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`FleetAddForm Render 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-fleetprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-statusprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-userprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-fleetprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-3"
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
action="{onSubmit}"
|
||||||
|
class="makeStyles-form-5"
|
||||||
|
novalidate=""
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
||||||
|
data-shrink="false"
|
||||||
|
for="name"
|
||||||
|
id="name-label"
|
||||||
|
>
|
||||||
|
Name
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="name"
|
||||||
|
maxlength="17"
|
||||||
|
name="name"
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-62 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-64"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Name
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root"
|
||||||
|
id="demo-row-radio-buttons-group-label"
|
||||||
|
>
|
||||||
|
Log Level
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
aria-labelledby="demo-row-radio-buttons-group-label"
|
||||||
|
class="MuiFormGroup-root MuiFormGroup-row"
|
||||||
|
margin="normal"
|
||||||
|
role="radiogroup"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormControlLabel-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-66 MuiRadio-root MuiRadio-colorSecondary MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="PrivateSwitchBase-input-69"
|
||||||
|
name="log-level-group"
|
||||||
|
type="radio"
|
||||||
|
value="trace"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="PrivateRadioButtonIcon-root-70"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root PrivateRadioButtonIcon-layer-71"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.465 8.465C9.37 7.56 10.62 7 12 7C14.76 7 17 9.24 17 12C17 13.38 16.44 14.63 15.535 15.535C14.63 16.44 13.38 17 12 17C9.24 17 7 14.76 7 12C7 10.62 7.56 9.37 8.465 8.465Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
Trace
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
class="MuiFormControlLabel-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-66 MuiRadio-root MuiRadio-colorSecondary MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="PrivateSwitchBase-input-69"
|
||||||
|
name="log-level-group"
|
||||||
|
type="radio"
|
||||||
|
value="debug"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="PrivateRadioButtonIcon-root-70"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root PrivateRadioButtonIcon-layer-71"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.465 8.465C9.37 7.56 10.62 7 12 7C14.76 7 17 9.24 17 12C17 13.38 16.44 14.63 15.535 15.535C14.63 16.44 13.38 17 12 17C9.24 17 7 14.76 7 12C7 10.62 7.56 9.37 8.465 8.465Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
Debug
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
class="MuiFormControlLabel-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-66 MuiRadio-root MuiRadio-colorSecondary PrivateSwitchBase-checked-67 Mui-checked MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
checked=""
|
||||||
|
class="PrivateSwitchBase-input-69"
|
||||||
|
name="log-level-group"
|
||||||
|
type="radio"
|
||||||
|
value="info"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="PrivateRadioButtonIcon-root-70 PrivateRadioButtonIcon-checked-72"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root PrivateRadioButtonIcon-layer-71"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.465 8.465C9.37 7.56 10.62 7 12 7C14.76 7 17 9.24 17 12C17 13.38 16.44 14.63 15.535 15.535C14.63 16.44 13.38 17 12 17C9.24 17 7 14.76 7 12C7 10.62 7.56 9.37 8.465 8.465Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
Info
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
class="MuiFormControlLabel-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-66 MuiRadio-root MuiRadio-colorSecondary MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="PrivateSwitchBase-input-69"
|
||||||
|
name="log-level-group"
|
||||||
|
type="radio"
|
||||||
|
value="warn"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="PrivateRadioButtonIcon-root-70"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root PrivateRadioButtonIcon-layer-71"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.465 8.465C9.37 7.56 10.62 7 12 7C14.76 7 17 9.24 17 12C17 13.38 16.44 14.63 15.535 15.535C14.63 16.44 13.38 17 12 17C9.24 17 7 14.76 7 12C7 10.62 7.56 9.37 8.465 8.465Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
Warn
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
class="MuiFormControlLabel-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-66 MuiRadio-root MuiRadio-colorSecondary MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="PrivateSwitchBase-input-69"
|
||||||
|
name="log-level-group"
|
||||||
|
type="radio"
|
||||||
|
value="error"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="PrivateRadioButtonIcon-root-70"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root PrivateRadioButtonIcon-layer-71"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.465 8.465C9.37 7.56 10.62 7 12 7C14.76 7 17 9.24 17 12C17 13.38 16.44 14.63 15.535 15.535C14.63 16.44 13.38 17 12 17C9.24 17 7 14.76 7 12C7 10.62 7.56 9.37 8.465 8.465Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
Error
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
class="MuiFormControlLabel-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-66 MuiRadio-root MuiRadio-colorSecondary MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="PrivateSwitchBase-input-69"
|
||||||
|
name="log-level-group"
|
||||||
|
type="radio"
|
||||||
|
value="critical"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="PrivateRadioButtonIcon-root-70"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root PrivateRadioButtonIcon-layer-71"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.465 8.465C9.37 7.56 10.62 7 12 7C14.76 7 17 9.24 17 12C17 13.38 16.44 14.63 15.535 15.535C14.63 16.44 13.38 17 12 17C9.24 17 7 14.76 7 12C7 10.62 7.56 9.37 8.465 8.465Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
Critical
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root"
|
||||||
|
id="demo-row-radio-buttons-group-label"
|
||||||
|
>
|
||||||
|
CAN Bus
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiFormGroup-root"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormControlLabel-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-66 MuiCheckbox-root MuiCheckbox-colorSecondary PrivateSwitchBase-checked-67 Mui-checked MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
checked=""
|
||||||
|
class="PrivateSwitchBase-input-69"
|
||||||
|
data-indeterminate="false"
|
||||||
|
type="checkbox"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
CAN Bus Enabled
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined MuiFormLabel-filled Mui-required Mui-required"
|
||||||
|
data-shrink="true"
|
||||||
|
for="max_mem_buffer_size"
|
||||||
|
id="max_mem_buffer_size-label"
|
||||||
|
>
|
||||||
|
Max Memory Buffer Size (0 uses default size)
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="max_mem_buffer_size"
|
||||||
|
maxlength="12"
|
||||||
|
name="max_mem_buffer_size"
|
||||||
|
required=""
|
||||||
|
type="number"
|
||||||
|
value="0"
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-62 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-64 PrivateNotchedOutline-legendNotched-65"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Max Memory Buffer Size (0 uses default size)
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label
|
||||||
|
class="MuiFormControlLabel-root"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-66 MuiCheckbox-root MuiCheckbox-colorSecondary MuiIconButton-colorSecondary"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="PrivateSwitchBase-input-69"
|
||||||
|
data-indeterminate="false"
|
||||||
|
type="checkbox"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
|
||||||
|
>
|
||||||
|
Data Logger Enabled
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-outlined Mui-disabled Mui-disabled MuiFormLabel-filled Mui-required Mui-required"
|
||||||
|
data-shrink="true"
|
||||||
|
for="max_disk_buffer_size"
|
||||||
|
id="max_disk_buffer_size-label"
|
||||||
|
>
|
||||||
|
Max Disk Buffer Size (0 uses default size)
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root Mui-disabled Mui-disabled MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input Mui-disabled Mui-disabled"
|
||||||
|
disabled=""
|
||||||
|
id="max_disk_buffer_size"
|
||||||
|
maxlength="12"
|
||||||
|
name="max_disk_buffer_size"
|
||||||
|
required=""
|
||||||
|
type="number"
|
||||||
|
value="0"
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-62 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-64 PrivateNotchedOutline-legendNotched-65"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Max Disk Buffer Size (0 uses default size)
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-6 MuiButton-containedPrimary MuiButton-fullWidth"
|
||||||
|
tabindex="0"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiButton-label"
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
206
src/components/Fleets/Add/index.jsx
Normal file
206
src/components/Fleets/Add/index.jsx
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import { Redirect } from "react-router";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
FormControlLabel,
|
||||||
|
FormGroup,
|
||||||
|
FormLabel,
|
||||||
|
Radio,
|
||||||
|
RadioGroup,
|
||||||
|
TextField
|
||||||
|
} from "@material-ui/core";
|
||||||
|
|
||||||
|
import useStyles from "../../useStyles";
|
||||||
|
import {
|
||||||
|
useFleetContext,
|
||||||
|
FleetProvider
|
||||||
|
} from "../../Contexts/FleetContext";
|
||||||
|
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||||
|
import { useUserContext } from "../../Contexts/UserContext";
|
||||||
|
import { logger } from "../../../services/monitoring";
|
||||||
|
|
||||||
|
const MainForm = () => {
|
||||||
|
const { setMessage, setTitle, setSitePath } = useStatusContext();
|
||||||
|
const { addFleet, busy } = useFleetContext();
|
||||||
|
const {
|
||||||
|
token: {
|
||||||
|
idToken: { jwtToken: token },
|
||||||
|
},
|
||||||
|
} = useUserContext();
|
||||||
|
const classes = useStyles();
|
||||||
|
const [redirect, setRedirect] = useState(null);
|
||||||
|
|
||||||
|
const nameEl = useRef(null);
|
||||||
|
const [selectedLogLevel, setSelectedLogLevel] = useState("info");
|
||||||
|
const [canbusEnabled, setCANBusEnabled] = useState(true);
|
||||||
|
const [dataLoggerEnabled, setDataLoggerEnabled] = useState(false);
|
||||||
|
const [maxMemBufferSize, setMaxMemBufferSize] = useState(0);
|
||||||
|
const [maxDiskBufferSize, setMaxDiskBufferSize] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTitle("Add Fleet");
|
||||||
|
setSitePath([
|
||||||
|
{
|
||||||
|
label: "Fleets",
|
||||||
|
link: "/fleets",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Add Fleet",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onLogLevelChange = (event) => {
|
||||||
|
setSelectedLogLevel(event.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCANBusChange = (event) => {
|
||||||
|
setCANBusEnabled(event.target.checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDataLoggerChange = (event) => {
|
||||||
|
setDataLoggerEnabled(event.target.checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onMaxMemBufferSizeChange = (event) => {
|
||||||
|
setMaxMemBufferSize(event.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onMaxDiskBufferSizeChange = (event) => {
|
||||||
|
setMaxDiskBufferSize(event.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSubmit = async (event) => {
|
||||||
|
try {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const formData = {
|
||||||
|
name: nameEl.current.value,
|
||||||
|
log_level: selectedLogLevel,
|
||||||
|
canbus: {
|
||||||
|
enabled: canbusEnabled,
|
||||||
|
data_logger_enabled: canbusEnabled ? dataLoggerEnabled : false,
|
||||||
|
max_mem_buffer_size: canbusEnabled ? parseInt(maxMemBufferSize) : 0,
|
||||||
|
max_disk_buffer_size: canbusEnabled && dataLoggerEnabled ? parseInt(maxDiskBufferSize) : 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const result = await addFleet(formData, token);
|
||||||
|
if (!result || result.error) return;
|
||||||
|
|
||||||
|
setMessage(`Added ${result.name}`);
|
||||||
|
setRedirect(`/fleets`);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
logger.warn(e.stack);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (redirect && redirect.length > 0) {
|
||||||
|
return <Redirect to={redirect} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.paper}>
|
||||||
|
<form className={classes.form} noValidate action="{onSubmit}">
|
||||||
|
<TextField
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
label="Name"
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
inputProps={{
|
||||||
|
maxLength: "17",
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
inputRef={nameEl}
|
||||||
|
/>
|
||||||
|
<FormLabel id="demo-row-radio-buttons-group-label">Log Level</FormLabel>
|
||||||
|
<RadioGroup
|
||||||
|
row
|
||||||
|
aria-labelledby="demo-row-radio-buttons-group-label"
|
||||||
|
name="log-level-group"
|
||||||
|
value={selectedLogLevel}
|
||||||
|
onChange={onLogLevelChange}
|
||||||
|
margin="normal"
|
||||||
|
>
|
||||||
|
<FormControlLabel value="trace" control={<Radio />} label="Trace" />
|
||||||
|
<FormControlLabel value="debug" control={<Radio />} label="Debug" />
|
||||||
|
<FormControlLabel value="info" control={<Radio />} label="Info" />
|
||||||
|
<FormControlLabel value="warn" control={<Radio />} label="Warn" />
|
||||||
|
<FormControlLabel value="error" control={<Radio />} label="Error" />
|
||||||
|
<FormControlLabel value="critical" control={<Radio />} label="Critical" />
|
||||||
|
</RadioGroup>
|
||||||
|
<FormLabel id="demo-row-radio-buttons-group-label">CAN Bus</FormLabel>
|
||||||
|
<FormGroup>
|
||||||
|
<FormControlLabel control={
|
||||||
|
<Checkbox
|
||||||
|
checked={canbusEnabled}
|
||||||
|
onChange={onCANBusChange}
|
||||||
|
/>
|
||||||
|
} label="CAN Bus Enabled" />
|
||||||
|
<TextField
|
||||||
|
id="max_mem_buffer_size"
|
||||||
|
name="max_mem_buffer_size"
|
||||||
|
label='Max Memory Buffer Size (0 uses default size)'
|
||||||
|
value={maxMemBufferSize}
|
||||||
|
onChange={onMaxMemBufferSizeChange}
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
inputProps={{
|
||||||
|
maxLength: "12",
|
||||||
|
}}
|
||||||
|
type="number"
|
||||||
|
disabled={!canbusEnabled}
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<FormControlLabel control={
|
||||||
|
<Checkbox
|
||||||
|
checked={dataLoggerEnabled}
|
||||||
|
onChange={onDataLoggerChange}
|
||||||
|
disabled={!canbusEnabled}
|
||||||
|
/>
|
||||||
|
} label="Data Logger Enabled" />
|
||||||
|
</FormGroup>
|
||||||
|
<TextField
|
||||||
|
id="max_disk_buffer_size"
|
||||||
|
name="max_disk_buffer_size"
|
||||||
|
label='Max Disk Buffer Size (0 uses default size)'
|
||||||
|
value={maxDiskBufferSize}
|
||||||
|
onChange={onMaxDiskBufferSizeChange}
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
inputProps={{
|
||||||
|
maxLength: "12",
|
||||||
|
}}
|
||||||
|
type="number"
|
||||||
|
disabled={!dataLoggerEnabled}
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={busy}
|
||||||
|
fullWidth
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
className={classes.submit}
|
||||||
|
onClick={onSubmit}
|
||||||
|
>
|
||||||
|
{busy ? "Submitting..." : "Submit"}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const FleetAddForm = () => (
|
||||||
|
<FleetProvider>
|
||||||
|
<MainForm />
|
||||||
|
</FleetProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default FleetAddForm;
|
||||||
36
src/components/Fleets/Add/index.test.jsx
Normal file
36
src/components/Fleets/Add/index.test.jsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
jest.mock("../../Contexts/FleetContext");
|
||||||
|
jest.mock("../../Contexts/StatusContext");
|
||||||
|
jest.mock("../../Contexts/UserContext");
|
||||||
|
|
||||||
|
import { render, waitFor } from "@testing-library/react";
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
|
||||||
|
import { FleetProvider } from "../../Contexts/FleetContext";
|
||||||
|
import { StatusProvider } from "../../Contexts/StatusContext";
|
||||||
|
import { UserProvider, setToken } from "../../Contexts/UserContext";
|
||||||
|
import { TEST_AUTH_OBJECT } from "../../../utils/testing";
|
||||||
|
import MainForm from "./index"
|
||||||
|
|
||||||
|
const renderFleetAdd = async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<FleetProvider>
|
||||||
|
<StatusProvider>
|
||||||
|
<UserProvider>
|
||||||
|
<BrowserRouter>
|
||||||
|
<MainForm />
|
||||||
|
</BrowserRouter>
|
||||||
|
</UserProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
</FleetProvider>
|
||||||
|
);
|
||||||
|
await waitFor(() => { /* render */ });
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("FleetAddForm", () => {
|
||||||
|
it("Render", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT);
|
||||||
|
const container = await renderFleetAdd();
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`FleetCANFilterAdd Render 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-fleetprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-statusprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-userprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-fleetprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-3"
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
action="{onSubmit}"
|
||||||
|
class="makeStyles-form-5"
|
||||||
|
novalidate=""
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
||||||
|
data-shrink="false"
|
||||||
|
for="name"
|
||||||
|
id="name-label"
|
||||||
|
>
|
||||||
|
Fleet Name
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="name"
|
||||||
|
maxlength="255"
|
||||||
|
name="name"
|
||||||
|
readonly=""
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-62 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-64"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Fleet Name
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined Mui-required Mui-required"
|
||||||
|
data-shrink="false"
|
||||||
|
for="canId"
|
||||||
|
id="canId-label"
|
||||||
|
>
|
||||||
|
CAN ID
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiFormLabel-asterisk MuiInputLabel-asterisk"
|
||||||
|
>
|
||||||
|
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="canId"
|
||||||
|
maxlength="255"
|
||||||
|
name="canId"
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-62 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-64"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
CAN ID
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root MuiTextField-root MuiFormControl-marginNormal MuiFormControl-fullWidth"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-outlined"
|
||||||
|
data-shrink="false"
|
||||||
|
for="interval"
|
||||||
|
id="interval-label"
|
||||||
|
>
|
||||||
|
Interval
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-fullWidth MuiInputBase-formControl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiOutlinedInput-input"
|
||||||
|
id="interval"
|
||||||
|
maxlength="255"
|
||||||
|
name="interval"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<fieldset
|
||||||
|
aria-hidden="true"
|
||||||
|
class="PrivateNotchedOutline-root-62 MuiOutlinedInput-notchedOutline"
|
||||||
|
>
|
||||||
|
<legend
|
||||||
|
class="PrivateNotchedOutline-legendLabelled-64"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Interval
|
||||||
|
</span>
|
||||||
|
</legend>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-6 MuiButton-containedPrimary MuiButton-fullWidth"
|
||||||
|
tabindex="0"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiButton-label"
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
127
src/components/Fleets/Status/CANFilters/Add/index.jsx
Normal file
127
src/components/Fleets/Status/CANFilters/Add/index.jsx
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import { Redirect, useParams } from "react-router";
|
||||||
|
import { Button, TextField } from "@material-ui/core";
|
||||||
|
|
||||||
|
import { useUserContext } from "../../../../Contexts/UserContext";
|
||||||
|
import { useStatusContext } from "../../../../Contexts/StatusContext";
|
||||||
|
import { useFleetContext, FleetProvider } from "../../../../Contexts/FleetContext";
|
||||||
|
import useStyles from "../../../../useStyles";
|
||||||
|
import { logger } from "../../../../../services/monitoring";
|
||||||
|
|
||||||
|
|
||||||
|
const MainForm = () => {
|
||||||
|
const { name } = useParams();
|
||||||
|
const { setMessage, setTitle, setSitePath } = useStatusContext();
|
||||||
|
const { addFleetCANFilter, busy } = useFleetContext();
|
||||||
|
const { token: { idToken: { jwtToken: token } } } = useUserContext();
|
||||||
|
const classes = useStyles();
|
||||||
|
const canIdEl = useRef(null);
|
||||||
|
const intervalEl = useRef(null);
|
||||||
|
const [redirect, setRedirect] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const title = "Add CAN Filter"
|
||||||
|
setTitle(title);
|
||||||
|
setSitePath([
|
||||||
|
{
|
||||||
|
label: `Fleets`,
|
||||||
|
link: "/fleets",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: `${name}`,
|
||||||
|
link: `/fleet/${name}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: title
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onSubmit = async (event) => {
|
||||||
|
try {
|
||||||
|
event.preventDefault();
|
||||||
|
const formData = {
|
||||||
|
can_id: canIdEl.current.value,
|
||||||
|
interval: parseInt(intervalEl.current.value)
|
||||||
|
};
|
||||||
|
const result = await addFleetCANFilter(name, formData, token);
|
||||||
|
if (!result || result.error) return;
|
||||||
|
|
||||||
|
setMessage(`Added CAN filter ${result.can_id}`);
|
||||||
|
setRedirect(`/fleet/${name}#filters`);
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
logger.warn(e.stack);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (redirect && redirect.length > 0) {
|
||||||
|
return <Redirect to={redirect} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.paper}>
|
||||||
|
<form className={classes.form} noValidate action="{onSubmit}">
|
||||||
|
<TextField
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
label="Fleet Name"
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
inputProps={{
|
||||||
|
maxLength: "255",
|
||||||
|
readOnly: true,
|
||||||
|
}}
|
||||||
|
value={name}
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
id="canId"
|
||||||
|
name="canId"
|
||||||
|
label="CAN ID"
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
inputProps={{
|
||||||
|
maxLength: "255",
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
inputRef={canIdEl}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
id="interval"
|
||||||
|
name="interval"
|
||||||
|
label="Interval"
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
inputProps={{
|
||||||
|
maxLength: "255",
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
inputRef={intervalEl}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={busy}
|
||||||
|
fullWidth
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
className={classes.submit}
|
||||||
|
onClick={onSubmit}
|
||||||
|
>
|
||||||
|
{busy ? "Submitting..." : "Submit"}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const FleetAddCANFilterForm = (props) => (
|
||||||
|
<FleetProvider>
|
||||||
|
<MainForm {...props} />
|
||||||
|
</FleetProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default FleetAddCANFilterForm;
|
||||||
36
src/components/Fleets/Status/CANFilters/Add/index.test.jsx
Normal file
36
src/components/Fleets/Status/CANFilters/Add/index.test.jsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
jest.mock("../../../../Contexts/FleetContext");
|
||||||
|
jest.mock("../../../../Contexts/StatusContext");
|
||||||
|
jest.mock("../../../../Contexts/UserContext");
|
||||||
|
|
||||||
|
import { render, waitFor } from "@testing-library/react";
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
|
||||||
|
import { FleetProvider } from "../../../../Contexts/FleetContext";
|
||||||
|
import { StatusProvider } from "../../../../Contexts/StatusContext";
|
||||||
|
import { UserProvider, setToken } from "../../../../Contexts/UserContext";
|
||||||
|
import { TEST_AUTH_OBJECT } from "../../../../../utils/testing";
|
||||||
|
import MainForm from "./index"
|
||||||
|
|
||||||
|
const renderFleetCANFilterAdd = async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<FleetProvider>
|
||||||
|
<StatusProvider>
|
||||||
|
<UserProvider>
|
||||||
|
<BrowserRouter>
|
||||||
|
<MainForm />
|
||||||
|
</BrowserRouter>
|
||||||
|
</UserProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
</FleetProvider>
|
||||||
|
);
|
||||||
|
await waitFor(() => { });
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("FleetCANFilterAdd", () => {
|
||||||
|
it("Render", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT);
|
||||||
|
const container = await renderFleetCANFilterAdd();
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,454 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`FleetCANFiltersTable Render 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-fleetprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-statusprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-userprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid="mocked-fleetprovider"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="makeStyles-paper-3 makeStyles-tableSize-53"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-root-14 MuiGrid-container MuiGrid-spacing-xs-2"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiGrid-root makeStyles-textJustifyAlign-47 MuiGrid-item MuiGrid-grid-md-4"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class="makeStyles-labelInline-9"
|
||||||
|
href="/fleet/undefined/filter-add"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiSvgIcon-fontSizeLarge"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
align="right"
|
||||||
|
class="MuiGrid-root makeStyles-textCenterAlign-48 MuiGrid-item MuiGrid-grid-md-8"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiFormControl-root makeStyles-margin-28 makeStyles-fullWidth-50"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="MuiFormLabel-root MuiInputLabel-root MuiInputLabel-formControl MuiInputLabel-animated"
|
||||||
|
data-shrink="false"
|
||||||
|
for="search"
|
||||||
|
>
|
||||||
|
Search
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiInput-root MuiInput-underline MuiInputBase-formControl MuiInput-formControl MuiInputBase-adornedEnd"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="MuiInputBase-input MuiInput-input MuiInputBase-inputAdornedEnd"
|
||||||
|
id="search"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="MuiInputAdornment-root MuiInputAdornment-positionEnd"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-label="search"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root"
|
||||||
|
tabindex="0"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="MuiTouchRipple-root"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table
|
||||||
|
class="MuiTable-root"
|
||||||
|
>
|
||||||
|
<thead
|
||||||
|
class="MuiTableHead-root"
|
||||||
|
>
|
||||||
|
<tr
|
||||||
|
class="MuiTableRow-root MuiTableRow-head"
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiTableSortLabel-root"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
CAN ID
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-disabled="false"
|
||||||
|
class="MuiButtonBase-root MuiTableSortLabel-root"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Interval (ms)
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiTableSortLabel-icon MuiTableSortLabel-iconDirectionAsc"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
class="MuiTableCell-root MuiTableCell-head MuiTableCell-alignCenter"
|
||||||
|
scope="col"
|
||||||
|
>
|
||||||
|
Actions
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody
|
||||||
|
class="MuiTableBody-root"
|
||||||
|
>
|
||||||
|
<tr
|
||||||
|
class="MuiTableRow-root"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
123-456
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
789
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="/fleet/undefined/filter-update?name=undefined&can_id=123-456&interval=789"
|
||||||
|
style="margin: 5px;"
|
||||||
|
title="Update \\"123-456\\""
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-label="Update 123-456"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="/"
|
||||||
|
title="Delete \\"123-456\\""
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-label="Delete 123-456"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
class="MuiTableRow-root"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
1
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
1000
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="/fleet/undefined/filter-update?name=undefined&can_id=1&interval=1000"
|
||||||
|
style="margin: 5px;"
|
||||||
|
title="Update \\"1\\""
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-label="Update 1"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="/"
|
||||||
|
title="Delete \\"1\\""
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-label="Delete 1"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
class="MuiTableRow-root"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
1000
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
1
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="/fleet/undefined/filter-update?name=undefined&can_id=1000&interval=1"
|
||||||
|
style="margin: 5px;"
|
||||||
|
title="Update \\"1000\\""
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-label="Update 1000"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34a.9959.9959 0 00-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
class=""
|
||||||
|
href="/"
|
||||||
|
title="Delete \\"1000\\""
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
aria-label="Delete 1000"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
<tfoot
|
||||||
|
class="MuiTableFooter-root"
|
||||||
|
>
|
||||||
|
<tr
|
||||||
|
class="MuiTableRow-root MuiTableRow-footer"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
class="MuiTableCell-root MuiTableCell-footer MuiTablePagination-root"
|
||||||
|
colspan="8"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiToolbar-root MuiToolbar-regular MuiTablePagination-toolbar MuiToolbar-gutters"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="MuiTablePagination-spacer"
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
class="MuiTypography-root MuiTablePagination-caption MuiTypography-body2 MuiTypography-colorInherit"
|
||||||
|
>
|
||||||
|
Rows per page:
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="MuiInputBase-root MuiTablePagination-input MuiTablePagination-selectRoot"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
aria-label="rows per page"
|
||||||
|
class="MuiSelect-root MuiSelect-select MuiTablePagination-select MuiInputBase-input"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
class="MuiTablePagination-menuItem"
|
||||||
|
value="5"
|
||||||
|
>
|
||||||
|
5
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
class="MuiTablePagination-menuItem"
|
||||||
|
value="10"
|
||||||
|
>
|
||||||
|
10
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
class="MuiTablePagination-menuItem"
|
||||||
|
value="25"
|
||||||
|
>
|
||||||
|
25
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
class="MuiTablePagination-menuItem"
|
||||||
|
value="100"
|
||||||
|
>
|
||||||
|
100
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root MuiSelect-icon MuiTablePagination-selectIcon"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M7 10l5 5 5-5z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
class="MuiTypography-root MuiTablePagination-caption MuiTypography-body2 MuiTypography-colorInherit"
|
||||||
|
>
|
||||||
|
1-3 of 3
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="MuiTablePagination-actions"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-label="Previous page"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit Mui-disabled Mui-disabled"
|
||||||
|
disabled=""
|
||||||
|
tabindex="-1"
|
||||||
|
title="Previous page"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M15.41 16.09l-4.58-4.59 4.58-4.59L14 5.5l-6 6 6 6z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
aria-label="Next page"
|
||||||
|
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-colorInherit Mui-disabled Mui-disabled"
|
||||||
|
disabled=""
|
||||||
|
tabindex="-1"
|
||||||
|
title="Next page"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="MuiIconButton-label"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
class="MuiSvgIcon-root"
|
||||||
|
focusable="false"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
205
src/components/Fleets/Status/CANFilters/Table/index.jsx
Normal file
205
src/components/Fleets/Status/CANFilters/Table/index.jsx
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import {
|
||||||
|
Grid,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableFooter,
|
||||||
|
TablePagination,
|
||||||
|
TableRow,
|
||||||
|
Tooltip,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
import AddCircleIcon from "@material-ui/icons/AddCircle";
|
||||||
|
import DeleteIcon from "@material-ui/icons/Delete";
|
||||||
|
import EditIcon from '@material-ui/icons/Edit';
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
import TableHeaderSortable from "../../../../Table/HeaderSortable";
|
||||||
|
import { useUserContext } from "../../../../Contexts/UserContext"
|
||||||
|
import { useStatusContext } from "../../../../Contexts/StatusContext";
|
||||||
|
import { FleetProvider, useFleetContext } from "../../../../Contexts/FleetContext"
|
||||||
|
import useStyles from "../../../../useStyles";
|
||||||
|
import SearchField from "../../../../Controls/SearchField";
|
||||||
|
import { logger } from "../../../../../services/monitoring";
|
||||||
|
import { Roles, hasRole } from "../../../../../utils/roles";
|
||||||
|
|
||||||
|
const tableColumns = [
|
||||||
|
{
|
||||||
|
id: "can_id",
|
||||||
|
label: "CAN ID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "interval",
|
||||||
|
label: "Interval (ms)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "",
|
||||||
|
label: "Actions"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const MainForm = ({ name }) => {
|
||||||
|
const [pageSize, setPageSize] = useState(10);
|
||||||
|
const [pageIndex, setPageIndex] = useState(0);
|
||||||
|
const [orderBy, setOrderBy] = useState("id");
|
||||||
|
const [order, setOrder] = useState("desc");
|
||||||
|
const classes = useStyles();
|
||||||
|
const { setMessage } = useStatusContext();
|
||||||
|
const { fleetCANFilters, totalFleetCANFilters, getFleetCANFilters, deleteFleetCANFilter } = useFleetContext();
|
||||||
|
const { token: { idToken: { jwtToken: token } }, groups } = useUserContext();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
if (!name || !token) return;
|
||||||
|
await getFleetCANFilters(
|
||||||
|
name,
|
||||||
|
{
|
||||||
|
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
|
||||||
|
}, [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 onDelete = async (can_id) => {
|
||||||
|
try {
|
||||||
|
await deleteFleetCANFilter(name, can_id, token);
|
||||||
|
setMessage(`Deleted ${can_id}`)
|
||||||
|
} catch (e) {
|
||||||
|
setMessage(e.message);
|
||||||
|
logger.warn(e.stack);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Actions = (row) => {
|
||||||
|
let actions = [];
|
||||||
|
if (hasRole([Roles.CREATE], groups)) {
|
||||||
|
actions.push({
|
||||||
|
tip: `Update "${row.can_id}"`,
|
||||||
|
link: `/fleet/${name}/filter-update?name=${name}&can_id=${row.can_id}&interval=${row.interval}`,
|
||||||
|
icon: <EditIcon aria-label={`Update ${row.can_id}`} />
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (hasRole([Roles.DELETE], groups)) {
|
||||||
|
actions.push({
|
||||||
|
tip: `Delete "${row.can_id}"`,
|
||||||
|
id: row.can_id,
|
||||||
|
icon: <DeleteIcon aria-label={`Delete ${row.can_id}`} />
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (actions.length === 0) return ["No actions"];
|
||||||
|
|
||||||
|
return actions.map((action) => {
|
||||||
|
if (action.link != null) {
|
||||||
|
return (
|
||||||
|
<Tooltip key={action.link} title={action.tip}>
|
||||||
|
<Link to={action.link} style={{ margin: 5 }}>
|
||||||
|
{action.icon}
|
||||||
|
</Link>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Tooltip key={`delete-${action.id}`} title={action.tip}>
|
||||||
|
<Link to="#" onClick={() => onDelete(action.id)}>
|
||||||
|
{action.icon}
|
||||||
|
</Link>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={clsx(classes.paper, classes.tableSize)}>
|
||||||
|
<Grid container className={classes.root} spacing={2}>
|
||||||
|
<Grid item md={4} className={classes.textJustifyAlign}>
|
||||||
|
<Link to={`/fleet/${name}/filter-add`} className={classes.labelInline}>
|
||||||
|
<AddCircleIcon fontSize="large" />
|
||||||
|
</Link>
|
||||||
|
</Grid>
|
||||||
|
<Grid item md={8} align="right" className={classes.textCenterAlign}>
|
||||||
|
<SearchField classes={classes} />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Table>
|
||||||
|
<TableHeaderSortable
|
||||||
|
classes={classes}
|
||||||
|
orderBy={orderBy}
|
||||||
|
order={order}
|
||||||
|
columnData={tableColumns}
|
||||||
|
onSortRequest={handleSort}
|
||||||
|
/>
|
||||||
|
<TableBody>
|
||||||
|
{fleetCANFilters.map(row => (
|
||||||
|
<TableRow key={row.can_id}>
|
||||||
|
<TableCell align="center">{row.can_id}</TableCell>
|
||||||
|
<TableCell align="center">{row.interval}</TableCell>
|
||||||
|
<TableCell align="center">{Actions(row)}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
<TableFooter>
|
||||||
|
<TableRow>
|
||||||
|
<TablePagination
|
||||||
|
rowsPerPageOptions={[5, 10, 25, 100]}
|
||||||
|
colSpan={8}
|
||||||
|
count={totalFleetCANFilters}
|
||||||
|
rowsPerPage={pageSize}
|
||||||
|
page={pageIndex}
|
||||||
|
SelectProps={{
|
||||||
|
inputProps: { "aria-label": "rows per page" },
|
||||||
|
native: true,
|
||||||
|
}}
|
||||||
|
onPageChange={handleChangePageIndex}
|
||||||
|
onRowsPerPageChange={handleChangePageSize}
|
||||||
|
/>
|
||||||
|
</TableRow>
|
||||||
|
</TableFooter>
|
||||||
|
</Table>
|
||||||
|
</div >
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const FleetCANFiltersTable = (props) => (
|
||||||
|
<FleetProvider>
|
||||||
|
<MainForm {...props} />
|
||||||
|
</FleetProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default FleetCANFiltersTable;
|
||||||
39
src/components/Fleets/Status/CANFilters/Table/index.test.jsx
Normal file
39
src/components/Fleets/Status/CANFilters/Table/index.test.jsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
jest.mock("../../../../Contexts/FleetContext");
|
||||||
|
jest.mock("../../../../Contexts/StatusContext");
|
||||||
|
jest.mock("../../../../Contexts/UserContext");
|
||||||
|
jest.mock('@material-ui/core/utils/unstable_useId', () =>
|
||||||
|
jest.fn().mockReturnValue('mui-test-id'),
|
||||||
|
);
|
||||||
|
|
||||||
|
import { render, waitFor } from "@testing-library/react";
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
|
||||||
|
import { FleetProvider } from "../../../../Contexts/FleetContext";
|
||||||
|
import { StatusProvider } from "../../../../Contexts/StatusContext";
|
||||||
|
import { UserProvider, setToken } from "../../../../Contexts/UserContext";
|
||||||
|
import { TEST_AUTH_OBJECT } from "../../../../../utils/testing";
|
||||||
|
import MainForm from "./index"
|
||||||
|
|
||||||
|
const renderFleetCANFiltersTable = async () => {
|
||||||
|
const { container } = render(
|
||||||
|
<FleetProvider>
|
||||||
|
<StatusProvider>
|
||||||
|
<UserProvider>
|
||||||
|
<BrowserRouter>
|
||||||
|
<MainForm />
|
||||||
|
</BrowserRouter>
|
||||||
|
</UserProvider>
|
||||||
|
</StatusProvider>
|
||||||
|
</FleetProvider>
|
||||||
|
);
|
||||||
|
await waitFor(() => { });
|
||||||
|
return container;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("FleetCANFiltersTable", () => {
|
||||||
|
it("Render", async () => {
|
||||||
|
setToken(TEST_AUTH_OBJECT);
|
||||||
|
const container = await renderFleetCANFiltersTable();
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user