diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b6339ad..f8bf5cc 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -303,8 +303,8 @@ deploy:development:
- "eval $(ssh-agent -s)"
- ssh -V
- cat /etc/os-release
- - "echo DEPLOY_TARGET=${DEPLOY_TARGET}"
- - "echo DOCKER_HOST=${DOCKER_HOST}"
+ # - "echo DEPLOY_TARGET=${DEPLOY_TARGET}"
+ # - "echo DOCKER_HOST=${DOCKER_HOST}"
- "echo \"$DEVELOPMENT_SSH_PRIV_KEY\" | tr -d '\r' | ssh-add - "
# - "ssh -Mo StrictHostKeyChecking=no -S deploy-ctrl-socket -fnNTL /var/run/docker.sock:/var/run/docker.sock ${DEPLOY_TARGET}"
# - "ssh -S deploy-ctrl-socket -O check ${DEPLOY_TARGET}"
@@ -316,3 +316,98 @@ deploy:development:
- docker stack deploy --with-registry-auth -c docker-compose.dev.yml webui
when: manual
allow_failure: false
+
+dependencies:streamline:
+ image: node:$NODE_VERSION
+ stage: dependencies
+ only:
+ refs:
+ - streamline
+ # before_script:
+ # - npm -g config set user root
+ script:
+ - npm ci
+ cache:
+ key:
+ files:
+ - package-lock.json
+ paths:
+ - node_modules
+ artifacts:
+ expire_in: 1 hour
+ paths:
+ - node_modules/
+
+# test:
+# image: node:$NODE_VERSION
+# stage: setup
+# dependencies:
+# - dependencies:production
+# only:
+# - master
+# before_script:
+# - npm run lint
+# script:
+# - CI=true npm run test
+
+setup:streamline:
+ image: node:$NODE_VERSION
+ stage: setup
+ dependencies:
+ - dependencies:streamline
+ only:
+ - streamline
+ script:
+ - npm run build:streamline
+ artifacts:
+ expire_in: 1 hour
+ paths:
+ - build
+
+build:streamline:
+ image: docker:20.10.21
+ stage: build
+ dependencies:
+ - setup:streamline
+ variables:
+ DOCKER_DRIVER: overlay2
+ DOCKER_TLS_CERTDIR: ""
+ services:
+ - docker:20.10.21-dind
+ only:
+ - streamline
+ before_script:
+ - docker login -u ${CI_REGISTRY_USER} -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
+ script:
+ - docker build -t ${CI_REGISTRY}/brandx/bxt-app/streamline:${CI_COMMIT_SHA} .
+ - docker tag ${CI_REGISTRY}/brandx/bxt-app/streamline:${CI_COMMIT_SHA} ${CI_REGISTRY}/brandx/bxt-app/streamline:latest
+ - docker push ${CI_REGISTRY}/brandx/bxt-app/streamline:${CI_COMMIT_SHA}
+ - docker push ${CI_REGISTRY}/brandx/bxt-app/streamline:latest
+
+deploy:streamline:
+ image: docker:20.10.21
+ stage: deploy
+ variables:
+ DOCKER_DRIVER: overlay2
+ DOCKER_HOST: ssh://deploy@streamline.adaptiveagriculture.ca
+ DEPLOY_IMAGE: ${CI_REGISTRY}/brandx/bxt-app/streamline:${CI_COMMIT_SHA}
+ DEPLOY_TARGET: deploy@streamline.adaptiveagriculture.ca
+ only:
+ - streamline
+ environment:
+ name: Streamline
+ url: https://streamline.adaptiveagriculture.ca
+ before_script: &deploy_tunnel
+ - "apk --update add openssh-client"
+ - "eval $(ssh-agent -s)"
+ - "echo \"$STREAMLINE_SSH_PRIV_KEY\" | tr -d '\r' | ssh-add - > /dev/null"
+ # - "ssh -Mo StrictHostKeyChecking=no -S deploy-ctrl-socket -fnNTL /var/run/docker.sock:/var/run/docker.sock ${DEPLOY_TARGET}"
+ # - "ssh -S deploy-ctrl-socket -O check ${DEPLOY_TARGET}"
+ - mkdir -p ~/.ssh
+ - chmod 700 ~/.ssh
+ - ssh -o StrictHostKeyChecking=no ${DEPLOY_TARGET}
+ script:
+ - docker login -u ${CI_REGISTRY_USER} -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
+ - docker stack deploy --with-registry-auth -c docker-compose.streamline.yml webui
+ when: manual
+ allow_failure: false
\ No newline at end of file
diff --git a/docker-compose.streamline.yml b/docker-compose.streamline.yml
new file mode 100644
index 0000000..218ab7d
--- /dev/null
+++ b/docker-compose.streamline.yml
@@ -0,0 +1,33 @@
+version: "3.6"
+
+services:
+ webui:
+ image: ${DEPLOY_IMAGE}
+ networks:
+ - backend
+ stop_signal: SIGQUIT
+ deploy:
+ update_config:
+ order: start-first
+ healthcheck:
+ test: ["CMD", "curl", "localhost:8080/health"]
+ interval: 1m
+ timeout: 10s
+ retries: 3
+ start_period: 30s
+ labels:
+ com.datadoghq.ad.logs: '[{"source": "nginx", "service": "webui"}]'
+ traefik.docker.network: "backend"
+ traefik.enable: "true"
+ traefik.http.routers.webui.rule: "Host(`streamline.adaptiveagriculture.ca`)"
+ traefik.frontend.redirect.regex: "^https?://streamline.adaptiveagriculture.ca/(.*)"
+ traefik.frontend.redirect.replacement: "https://streamline.adaptiveagriculture.ca/$${1}"
+ traefik.backend: "www"
+ traefik.http.services.webui.loadbalancer.server.port: "80"
+ traefik.protocol: "http"
+ dns:
+ - 69.27.101.6
+
+networks:
+ backend:
+ external: true
diff --git a/nginx.conf b/nginx.conf
index a2c0d11..b390bff 100644
--- a/nginx.conf
+++ b/nginx.conf
@@ -63,6 +63,14 @@ http {
expires $cache_expires;
+ location = /update {
+ return 302 /update.html;
+ }
+
+ location = /update.html {
+ add_header Cache-Control "no-store, no-cache, must-revalidate";
+ }
+
# Redirect old /index.html requests to new file
location = /index.html {
return 301 /indexV2.html;
@@ -79,7 +87,6 @@ http {
}
}
-
# Healthcheck
server {
listen 8080;
diff --git a/package-lock.json b/package-lock.json
index 407efa0..456c54d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6374,10 +6374,11 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
@@ -6900,10 +6901,11 @@
}
},
"node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -7616,6 +7618,15 @@
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="
},
+ "node_modules/decode-uri-component": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.4.1.tgz",
+ "integrity": "sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.16"
+ }
+ },
"node_modules/decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
@@ -8370,10 +8381,11 @@
}
},
"node_modules/filelist/node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
@@ -8402,6 +8414,18 @@
"node": ">=8"
}
},
+ "node_modules/filter-obj": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-5.1.0.tgz",
+ "integrity": "sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/find-root": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
@@ -10923,6 +10947,23 @@
"node": ">=6"
}
},
+ "node_modules/query-string": {
+ "version": "9.2.1",
+ "resolved": "https://registry.npmjs.org/query-string/-/query-string-9.2.1.tgz",
+ "integrity": "sha512-3jTGGLRzlhu/1ws2zlr4Q+GVMLCQTLFOj8CMX5x44cdZG9FQE07x2mQhaNxaKVPNmIDu0mvJ/cEwtY7Pim7hqA==",
+ "license": "MIT",
+ "dependencies": {
+ "decode-uri-component": "^0.4.1",
+ "filter-obj": "^5.1.0",
+ "split-on-first": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/queue": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
@@ -12300,6 +12341,18 @@
"resolved": "https://registry.npmjs.org/splaytree-ts/-/splaytree-ts-1.0.2.tgz",
"integrity": "sha512-0kGecIZNIReCSiznK3uheYB8sbstLjCZLiwcQwbmLhgHJj2gz6OnSPkVzJQCMnmEz1BQ4gPK59ylhBoEWOhGNA=="
},
+ "node_modules/split-on-first": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-3.0.0.tgz",
+ "integrity": "sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/split-string": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
diff --git a/package.json b/package.json
index 477f4c6..3287909 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
"start": "VITE_AUTH0_CLIENT_ID=5pUCkl2SfogkWmM244UDcLEUOp8EFdHd VITE_AUTH0_AUDIENCE=api.brandxtech.ca VITE_APP_API_URL=https://api.brandxtech.ca/v1 VITE_APP_WS_URL=ws://api.brandxtech.ca/v1/live vite",
"start-local": "VITE_AUTH0_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1 VITE_AUTH0_AUDIENCE=bxt-dev.api.adaptiveagriculture.ca VITE_APP_API_URL=http://localhost:50052/v1 VITE_APP_WS_URL=ws://localhost:50052/v1/live VITE_APP_BILLING_URL=http://localhost:50053/v1 VITE_APP_RECLUSE_URL=http://localhost:50054/v1 VITE_APP_GITLAB_URL=http://localhost:50055/v1 vite",
"start-dev": "VITE_AUTH0_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1 VITE_APP_API_URL=https://bxt-dev.api.adaptiveagriculture.ca/v1 VITE_AUTH0_CLIENT_DOMAIN=brandxtech.auth0.com VITE_AUTH0_AUDIENCE=bxt-dev.api.adaptiveagriculture.ca VITE_AUTH0_DEV_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1 vite",
+ "start-streamline": "VITE_AUTH0_CLIENT_ID=HwUV0hHNdVvU96zuMBTAU8i7nFdwwgIX VITE_APP_API_URL=https://streamline.api.adaptiveagriculture.ca/v1 VITE_AUTH0_CLIENT_DOMAIN=brandxtech.auth0.com VITE_AUTH0_AUDIENCE=streamline.api.adaptiveagriculture.ca vite",
"start-staging": "VITE_LOCAL_STAGING=true VITE_AUTH0_CLIENT_ID=3ib460VvLwdeyse5iUSQfxkVdQaUmphP VITE_AUTH0_AUDIENCE=stagingapi.brandxtech.ca VITE_AUTH0_CLIENT_DOMAIN=adaptivestaging.us.auth0.com VITE_APP_API_URL=https://stagingapi.brandxtech.ca/v1 VITE_APP_WS_URL=ws://stagingapi.brandxtech.ca/v1/live VITE_APP_AUTH0_CLIENT_DOMAIN=adaptivestaging.us.auth0.com VITE_APP_AUTH0_AUDIENCE=stagingapi.brandxtech.ca vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
@@ -14,6 +15,7 @@
"build:staging": "VITE_AUTH0_CLIENT_DOMAIN=adaptivestaging.us.auth0.com VITE_AUTH0_AUDIENCE=stagingapi.brandxtech.ca VITE_AUTH0_CLIENT_ID=3ib460VvLwdeyse5iUSQfxkVdQaUmphP VITE_APP_API_URL=https://stagingapi.brandxtech.ca/v1 VITE_APP_WS_URL=wss://stagingapi.brandxtech.ca/v1/live NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_STAGING} VITE_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_CNHI_SCOPES=${CNHI_SCOPES} VITE_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_JD_SCOPES=${JD_SCOPES} VITE_JD_STATE=${JD_STATE} VITE_OPEN_WEATHERMAP=${OPEN_WEATHERMAP} vite build",
"build:development": "VITE_AUTH0_CLIENT_ID=dzJTpqIeMA4Rwk4xujtwPbAO3TY32bM1 VITE_AUTH0_AUDIENCE=bxt-dev.api.adaptiveagriculture.ca VITE_APP_API_URL=https://bxt-dev.api.adaptiveagriculture.ca/v1 NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_DEVELOPMENT} VITE_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_CNHI_SCOPES=${CNHI_SCOPES} VITE_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_JD_SCOPES=${JD_SCOPES} VITE_JD_STATE=${JD_STATE} VITE_OPEN_WEATHERMAP=${OPEN_WEATHERMAP} vite build",
"build:production": "VITE_AUTH0_CLIENT_ID=5pUCkl2SfogkWmM244UDcLEUOp8EFdHd VITE_AUTH0_CLIENT_DOMAIN=brandxtech.auth0.com VITE_AUTH0_AUDIENCE=api.brandxtech.ca VITE_APP_API_URL=https://api.brandxtech.ca/v1 NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_PRODUCTION} VITE_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_CNHI_SCOPES=${CNHI_SCOPES} VITE_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_JD_SCOPES=${JD_SCOPES} VITE_JD_STATE=${JD_STATE} VITE_OPEN_WEATHERMAP=${OPEN_WEATHERMAP} vite build",
+ "build:streamline": "VITE_AUTH0_CLIENT_ID=HwUV0hHNdVvU96zuMBTAU8i7nFdwwgIX VITE_AUTH0_CLIENT_DOMAIN=brandxtech.auth0.com VITE_AUTH0_AUDIENCE=streamline.api.adaptiveagriculture.ca VITE_APP_API_URL=https://streamline.api.adaptiveagriculture.ca/v1 NODE_OPTIONS=--max_old_space_size=4096 VITE_APP_GOOGLE_API_KEY=${GOOGLE_API_KEY} VITE_APP_STRIPE_PUBLIC_KEY=${STRIPE_PUBLIC_KEY_PRODUCTION} VITE_MAPBOX_ACCESS_TOKEN=${MAPBOX_ACCESS_TOKEN} VITE_CNHI_CLIENT_ID=${CNHI_CLIENT_ID} VITE_CNHI_AUTHORIZE_URL=${CNHI_AUTHORIZE_URL} VITE_CNHI_REDIRECT_URI=${CNHI_REDIRECT_URI} VITE_CNHI_SCOPES=${CNHI_SCOPES} VITE_CNHI_CONNECTION=${CNHI_CONNECTION} VITE_CNHI_AUDIENCE=${CNHI_AUDIENCE} VITE_APP_IMAGE4IO_USERNAME=${IMAGE4IO_USERNAME} VITE_APP_IMAGE4IO_PASSWORD=${IMAGE4IO_PASSWORD} VITE_JD_CLIENT_ID=${JD_CLIENT_ID} VITE_JD_AUTHORIZE_URL=${JD_AUTHORIZE_URL} VITE_JD_REDIRECT_URI=${JD_REDIRECT_URI} VITE_JD_SCOPES=${JD_SCOPES} VITE_JD_STATE=${JD_STATE} VITE_OPEN_WEATHERMAP=${OPEN_WEATHERMAP} vite build",
"build:offline": "npx env-cmd offline,whitelabel npm run build",
"test": "vitest"
},
diff --git a/public/Streamline/favicon-16x16.png b/public/Streamline/favicon-16x16.png
new file mode 100644
index 0000000..aa87654
Binary files /dev/null and b/public/Streamline/favicon-16x16.png differ
diff --git a/public/Streamline/favicon-32x32.png b/public/Streamline/favicon-32x32.png
new file mode 100644
index 0000000..a606ca0
Binary files /dev/null and b/public/Streamline/favicon-32x32.png differ
diff --git a/public/Streamline/favicon.ico b/public/Streamline/favicon.ico
new file mode 100644
index 0000000..54725be
Binary files /dev/null and b/public/Streamline/favicon.ico differ
diff --git a/public/Streamline/manifest.json b/public/Streamline/manifest.json
new file mode 100644
index 0000000..7f8d80f
--- /dev/null
+++ b/public/Streamline/manifest.json
@@ -0,0 +1,37 @@
+{
+ "name": "Streamline",
+ "short_name": "Streamline",
+ "icons": [
+ {
+ "src": "favicon.ico",
+ "sizes": "48x48",
+ "type": "image/x-icon"
+ },
+ {
+ "src": "android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "android-chrome-256x256.png",
+ "sizes": "256x256",
+ "type": "image/png"
+ },
+ {
+ "src": "android-chrome-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ },
+ {
+ "src": "maskable_icon.png",
+ "sizes": "196x196",
+ "type": "image/png",
+ "purpose": "any maskable"
+ }
+ ],
+ "start_url": "/",
+ "theme_color": "#272727",
+ "background_color": "#303030",
+ "display": "standalone",
+ "orientation": "any"
+}
diff --git a/public/Streamline/stream-logo.png b/public/Streamline/stream-logo.png
new file mode 100644
index 0000000..c6d994b
Binary files /dev/null and b/public/Streamline/stream-logo.png differ
diff --git a/public/update.html b/public/update.html
new file mode 100644
index 0000000..fcbeb4c
--- /dev/null
+++ b/public/update.html
@@ -0,0 +1,44 @@
+
+
+
+
+ Updating...
+
+
+
+
+ Updating App…
+ Please wait while we refresh your version.
+
+
+
+
diff --git a/src/assets/whitelabels/Streamline/stream-logo.png b/src/assets/whitelabels/Streamline/stream-logo.png
new file mode 100644
index 0000000..c6d994b
Binary files /dev/null and b/src/assets/whitelabels/Streamline/stream-logo.png differ
diff --git a/src/bin/BinComponents.tsx b/src/bin/BinComponents.tsx
index f99e349..7864c46 100644
--- a/src/bin/BinComponents.tsx
+++ b/src/bin/BinComponents.tsx
@@ -414,7 +414,7 @@ export default function BinComponents(props: Props) {
)}
- {comp.name() + " " + comp.addressDescription(devMap.get(device)?.settings.product)}
+ {comp.name() + " - " + comp.addressDescription(devMap.get(device)?.settings.product)}
{components?.get(comp.key()) !== undefined ? (
diff --git a/src/common/ResponsiveTable.tsx b/src/common/ResponsiveTable.tsx
index 1d722db..79238ea 100644
--- a/src/common/ResponsiveTable.tsx
+++ b/src/common/ResponsiveTable.tsx
@@ -59,7 +59,7 @@ const useStyles = makeStyles((theme: Theme) => {
},
stickyHeader: {
position: "sticky",
- backgroundColor: getThemeType() === "light" ? "rgb(245, 245, 245)" : "rgb(40, 40, 40)",
+ // backgroundColor: getThemeType() === "light" ? "rgb(245, 245, 245)" : "rgb(40, 40, 40)",
top: 0,
zIndex: 300 //giving a really high z-index to make sure nothing is in front of it
}
@@ -165,6 +165,7 @@ export default function ResponsiveTable(props: Props) {
const saved = localStorage.getItem(rowKey);
return saved ? JSON.parse(saved) : [];
})
+
useEffect(() => {
localStorage.setItem(rowKey, JSON.stringify(rowWidths));
}, [rowWidths])
diff --git a/src/navigation/Router.tsx b/src/navigation/Router.tsx
index 9ef6d2c..29362ad 100644
--- a/src/navigation/Router.tsx
+++ b/src/navigation/Router.tsx
@@ -53,7 +53,7 @@ export const appendToUrl = (appendage: number | string) => {
export default function Router() {
- const { isAuthenticated, loginWithRedirect, isLoading } = useAuth0();
+ const { /*isAuthenticated, loginWithRedirect,*/ isLoading } = useAuth0();
const whiteLabel = getWhitelabel();
const [{ user }] = useGlobalState();
@@ -283,12 +283,12 @@ export default function Router() {
};
if (isLoading) return null;
- if (!isAuthenticated) {
- loginWithRedirect()
- return (
- null
- )
- }
+ // if (!isAuthenticated) {
+ // loginWithRedirect()
+ // return (
+ // null
+ // )
+ // }
function ErrorFallback({ error }: { error: Error }) {
return Something went wrong: {error.stack}
;
@@ -302,6 +302,7 @@ export default function Router() {
{/* Redirects */}
} />
+ {/* } /> */}
{/* } /> */}
{/* } /> */}
} />
diff --git a/src/navigation/SideNavigator.tsx b/src/navigation/SideNavigator.tsx
index fc902fb..38a10cb 100644
--- a/src/navigation/SideNavigator.tsx
+++ b/src/navigation/SideNavigator.tsx
@@ -30,6 +30,7 @@ import {
// isBXT,
IsMiVent,
IsOmniAir,
+ IsStreamline,
} from "services/whiteLabel";
import MiningIcon from "products/ventilation/MiningIcon";
import { useAuth0 } from "@auth0/auth0-react";
@@ -156,11 +157,12 @@ export default function SideNavigator(props: Props) {
const authenticatedSideMenu = () => {
const isMiVent = IsMiVent();
const isAg = IsAdaptiveAgriculture()
+ const isStreamline = IsStreamline()
const isMiPCA = IsOmniAir()
const isAdCon = IsAdCon()
return (
- {(isAg || user.hasFeature("admin")) && (
+ {(isAg || isStreamline || user.hasFeature("admin")) && (
)}
- {(isAg || user.hasFeature("admin")) && (
+ {(isAg || isStreamline || user.hasFeature("admin")) && (
)}
- {(isAg || user.hasFeature("admin")) && (
+ {(isAg || isStreamline || user.hasFeature("admin")) && (
}
- {(isAg || user.hasFeature("admin")) && (
+ {(isAg || isStreamline || user.hasFeature("admin")) && (
}
- {(isAg || user.hasFeature("admin")) && (
+ {(isAg || isStreamline || user.hasFeature("admin")) && (
}
- {isAg &&
+ {(isAg || isStreamline) &&
}
- }
+ }
)
}
diff --git a/src/pages/Devices.tsx b/src/pages/Devices.tsx
index db23b6c..fe47b3c 100644
--- a/src/pages/Devices.tsx
+++ b/src/pages/Devices.tsx
@@ -102,6 +102,8 @@ export default function Devices() {
const [limit, setLimit] = useState(10);
const [page, setPage] = useState(0);
+ const [{ user }] = useGlobalState()
+
const [order, setOrder] = useState<"asc" | "desc">(() => {
// Load from sessionStorage on initial render
const savedOrder = sessionStorage.getItem('order');
@@ -585,6 +587,7 @@ export default function Devices() {
}
const provisionButton= () => {
+ if (!user.allowedTo("provision")) return null
return (
diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx
new file mode 100644
index 0000000..3dc7d2f
--- /dev/null
+++ b/src/pages/Login.tsx
@@ -0,0 +1,53 @@
+import { RedirectLoginOptions, useAuth0 } from "@auth0/auth0-react";
+// import { useAuth } from "hooks";
+import queryString from "query-string";
+import { useEffect } from "react";
+import { useLocation } from "react-router";
+// import Loading from "./Loading";
+import LoadingScreen from "app/LoadingScreen";
+
+// interface Props {
+// prevPath?: string;
+// }
+
+export default function Login() {
+ // const { prevPath } = props;
+ const location = useLocation();
+ const { loginWithRedirect } = useAuth0();
+
+ // const setRouteBeforeLogin = useCallback((): Promise => {
+ // return new Promise(function(resolve) {
+ // localStorage.setItem("routeBeforeLogin", prevPath ? prevPath : "/");
+ // return resolve("success");
+ // });
+ // }, [prevPath]);
+
+ useEffect(() => {
+ const options: RedirectLoginOptions = {
+ authorizationParams: {} // Initialize here
+ };
+
+ if (location && location.search) {
+ let parsed = queryString.parse(location.search);
+
+ if (parsed.signup === "true") {
+ // if (options.authorizationParams) options.authorizationParams.screen_hint = "signup"; // 'signup' tells Auth0 to show the signup tab
+ options.authorizationParams!.screen_hint = "signup"; // 'signup' tells Auth0 to show the signup tab
+ options.authorizationParams!.prompt = "login";
+ }
+
+ if (parsed.email && parsed.email !== "") {
+ // if (options.authorizationParams) options.authorizationParams.login_hint = parsed.email.toString(); // prefill email
+ options.authorizationParams!.login_hint = parsed.email.toString(); // prefill email
+ }
+ }
+
+ // setRouteBeforeLogin().finally(() => {
+ console.log(options.authorizationParams)
+ loginWithRedirect(options);
+ // });
+ }, [location, loginWithRedirect]);
+
+ // return ;
+ return ;
+}
diff --git a/src/pages/PageContainer.tsx b/src/pages/PageContainer.tsx
index 0c78ed6..524fbbf 100644
--- a/src/pages/PageContainer.tsx
+++ b/src/pages/PageContainer.tsx
@@ -1,7 +1,7 @@
import { Container, SxProps, Theme } from "@mui/material";
import { makeStyles } from "@mui/styles";
import classNames from "classnames";
-import { useMobile } from "hooks";
+// import { useMobile } from "hooks";
import { PropsWithChildren } from "react";
const useStyles = makeStyles((theme: Theme) => ({
@@ -44,11 +44,11 @@ interface Props extends PropsWithChildren{
export const PageContainer: React.FunctionComponent = (props: Props) => {
const classes = useStyles();
- const isMobile = useMobile();
+ // const isMobile = useMobile();
const { children, fullViewport, isCenterCenter, sx } = props;
// let fullViewport = false
// let isCenterCenter = false
- const spacing = props.spacing;
+ const spacing = props.spacing ? props.spacing : 2;
return (
{
setToken: React.Dispatch>
+ prevPath?: string
}
export default function AuthWrapper(props: Props) {
- const { children, setToken } = props;
- const { isLoading, loginWithRedirect, isAuthenticated, getAccessTokenSilently } = useAuth0();
-
- useEffect(() => {
- if (!isAuthenticated && !isLoading) {
- loginWithRedirect();
- }
- }, [isAuthenticated, loginWithRedirect, isLoading]);
+ const { children, setToken } = props;
+ const { isLoading, loginWithRedirect, isAuthenticated, getAccessTokenSilently } = useAuth0();
- useEffect(() => {
- if (isAuthenticated) getAccessTokenSilently().then(t => {
- if (t.length < 1) {
- loginWithRedirect()
- }
- setToken(t)
- }).catch(() => {
- // No token, go back to login
- loginWithRedirect()
- })
- }, [setToken, isAuthenticated])
- return children;
+ useEffect(() => {
+ const url = new URL(window.location.href);
+
+ const isManualLoginPage = url.pathname.includes("/login");
+
+ if (isManualLoginPage) {
+ const options: RedirectLoginOptions = {
+ authorizationParams: {} // Initialize here
+ };
+
+ let parsed = queryString.parse(url.search);
+ if (parsed.signup === "true") {
+ options.authorizationParams!.screen_hint = "signup";
+ options.authorizationParams!.prompt = "login";
+ }
+
+ if (parsed.email && parsed.email !== "") {
+ options.authorizationParams!.login_hint = parsed.email.toString(); // prefill email
+ }
+
+ loginWithRedirect(options)
+
+ } else if (!isAuthenticated && !isLoading) {
+ loginWithRedirect();
+ }
+ }, [isAuthenticated, loginWithRedirect, isLoading]);
+
+ useEffect(() => {
+ if (isAuthenticated) getAccessTokenSilently().then(t => {
+ if (t.length < 1) {
+ loginWithRedirect()
+ }
+ setToken(t)
+ }).catch(() => {
+ // No token, go back to login
+ loginWithRedirect()
+ })
+ }, [setToken, isAuthenticated])
+
+ return children;
}
\ No newline at end of file
diff --git a/src/services/whiteLabel.ts b/src/services/whiteLabel.ts
index d14ab51..0cd84cf 100644
--- a/src/services/whiteLabel.ts
+++ b/src/services/whiteLabel.ts
@@ -11,6 +11,7 @@ import AeroGrowLightLogo from "../assets/whitelabels/AeroGrow/lightLogo.png";
import MiVentLightLogo from "../assets/whitelabels/MiVent/lightLogo.png";
// import OmniAirLogo from "../assets/whitelabels/OmniAir/OmniAirLogo.png";
import MiPCALogo from "../assets/whitelabels/OmniAir/MiPCALogo.png";
+import StreamlineLogo from "../assets/whitelabels/Streamline/stream-logo.png"
// import { green, yellow } from "@mui/material/colors";
const protips: string[] = [
@@ -111,6 +112,28 @@ const BXT_WHITE_LABEL: WhiteLabel = {
homePage: "/devices"
};
+const STREAMLINE_WHITE_LABEL: WhiteLabel = {
+ name: "Streamline",
+ primaryColour: "#FFFF",
+ // primaryColour: "#0000FF",
+ secondaryColour: "yellow",
+ // secondaryColour: "#FFFF00",
+ // signatureColour: "#272727",
+ signatureColour: "#005bb0",
+ signatureAccentColour: "#fff",
+ auth0ClientId: import.meta.env.VITE_AUTH0_STREAMLINE_CLIENT_ID,
+ redirectOnLogout: false,
+ logoutRedirectTarget: "",
+ darkLogo: StreamlineLogo,
+ lightLogo: StreamlineLogo,
+ transparentLogoBG: false,
+ blacklist: [],
+ hotjarID: import.meta.env.REACT_APP_HOTJAR_ID_BXT,
+ docs: "Platform",
+ protips: protips,
+ homePage: "/devices"
+};
+
export function isBXT(): boolean {
return getName() === "Brand X Technologies";
}
@@ -157,6 +180,14 @@ export function IsAdaptiveAgriculture(): boolean {
);
}
+export function IsStreamline(): boolean {
+ return (
+ getName() === "Streamline"
+ // window.location.origin.includes("staging") ||
+ // window.location.origin.includes("localhost")
+ );
+}
+
const AEROGROW_WHITE_LABEL: WhiteLabel = {
name: "AeroGrow",
primaryColour: "green",
@@ -254,6 +285,7 @@ export function IsOmniAir(): boolean {
}
const whitelabels = new Map([
+ ["streamline", STREAMLINE_WHITE_LABEL],
["adaptiveag", ADAPTIVE_AGRICULTURE_WHITE_LABEL],
["adaptiveagriculture", ADAPTIVE_AGRICULTURE_WHITE_LABEL],
["brandxducks", BXT_WHITE_LABEL],
@@ -276,6 +308,9 @@ export function getWhitelabel(): WhiteLabel {
if (window.location.origin.includes("bxt-dev")) {
return BXT_WHITE_LABEL;
}
+ if (window.location.origin.includes("localhost")) {
+ return STREAMLINE_WHITE_LABEL;
+ }
if (window.location.origin.includes("staging") || import.meta.env.VITE_LOCAL_STAGING=='true') {
return STAGING_WHITELABEL;
}
diff --git a/src/teams/TeamActions.tsx b/src/teams/TeamActions.tsx
index 63b335e..f12f9dc 100644
--- a/src/teams/TeamActions.tsx
+++ b/src/teams/TeamActions.tsx
@@ -146,6 +146,7 @@ export default function TeamActions(props: Props) {
permissions={permissions}
isDialogOpen={openState.share}
closeDialogCallback={() => setOpenState({ ...openState, share: false })}
+ useImitation={false}
/>
({
accessIcon: {
@@ -76,7 +75,7 @@ const useStyles = makeStyles((theme: Theme) => ({
export default function UserMenu() {
- const { toggleMode } = useThemeMode()
+ // const { toggleMode } = useThemeMode()
const [{ user, team, as }, dispatch] = useGlobalState();
const { loginWithRedirect } = useAuth0();
const classes = useStyles();
@@ -163,10 +162,6 @@ export default function UserMenu() {
)}
-
- {}} className={classes.rightIcon} aria-label="Toggle Theme">
-
-
>
);
};
diff --git a/src/user/UserSettings.tsx b/src/user/UserSettings.tsx
index 32beec2..ec86924 100644
--- a/src/user/UserSettings.tsx
+++ b/src/user/UserSettings.tsx
@@ -10,10 +10,10 @@ import {
Textsms as TextIcon,
Contrast,
} from "@mui/icons-material";
-import { AppBar, Box, Button, Checkbox, CircularProgress, Collapse, Divider, FormControl, FormControlLabel, FormGroup, FormHelperText, FormLabel, Grid2, IconButton, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Radio, RadioGroup, TextField, Theme, ToggleButton, ToggleButtonGroup, Toolbar, Tooltip, Typography } from "@mui/material";
+import { AppBar, Box, Button, Checkbox, CircularProgress, Collapse, Divider, FormControl, FormControlLabel, FormGroup, FormHelperText, FormLabel, Grid2, IconButton, List, ListItem, ListItemButton, ListItemIcon, ListItemText, MenuItem, Radio, RadioGroup, TextField, Theme, ToggleButton, ToggleButtonGroup, Toolbar, Tooltip, Typography } from "@mui/material";
import ResponsiveDialog from "common/ResponsiveDialog";
import UserAvatar from "./UserAvatar";
-import { useGlobalState, useUserAPI } from "providers";
+import { useGlobalState, useSnackbar, useUserAPI } from "providers";
import { useEffect, useState } from "react";
import { makeStyles } from "@mui/styles";
import { blue } from "@mui/material/colors";
@@ -26,8 +26,11 @@ import moment from "moment";
import { IconPicker } from "common/IconPicker";
import { MuiTelInput } from "mui-tel-input";
import { useThemeMode } from "theme/AppThemeProvider";
-import ContractsIcon from "products/CommonIcons/contractIcon";
+// import ContractsIcon from "products/CommonIcons/contractIcon";
+import UnitsIcon from "@mui/icons-material/Category";
import { setThemeType } from "theme";
+import { IsAdaptiveAgriculture } from "services/whiteLabel";
+import { setDistanceUnit, setGrainUnit, setPressureUnit, setTemperatureUnit } from "utils";
const useStyles = makeStyles((theme: Theme) => {
return ({
@@ -71,14 +74,16 @@ export default function UserSettings(props: Props) {
const [sharing, setSharing] = useState(false)
const userAPI = useUserAPI();
const classes = useStyles()
+ const { success } = useSnackbar();
const [isLoading, setIsLoading] = useState(false);
const [user, setUser] = useState(globalState.user);
const [profileExpanded, setProfileExpanded] = useState(false);
const [avatarExpanded, setAvatarExpanded] = useState(false);
const [notificationsExpanded, setNotificationsExpanded] = useState(false);
+ const [unitsExpanded, setUnitsExpanded] = useState(false);
const [avatarUrl, setAvatarUrl] = useState("");
- const [themeValue, setThemeValue] = useState<"light" | "dark" | "system">(themeMode.mode)
+ // const [themeValue, setThemeValue] = useState<"light" | "dark" | "system">(themeMode.mode)
useEffect(() => {
function changeIcon(icon: string | undefined) {
@@ -99,11 +104,11 @@ export default function UserSettings(props: Props) {
.updateUser(user.id(), user.protobuf())
.then(() => {
dispatch({ key: "user", value: user });
- // success("User settings successfully updated!");
- // setTemperatureUnit(user.settings.temperatureUnit);
- // setPressureUnit(user.settings.pressureUnit);
- // setDistanceUnit(user.settings.distanceUnit);
- // setGrainUnit(user.settings.grainUnit);
+ success("User settings successfully updated!");
+ setTemperatureUnit(user.settings.temperatureUnit);
+ setPressureUnit(user.settings.pressureUnit);
+ setDistanceUnit(user.settings.distanceUnit);
+ setGrainUnit(user.settings.grainUnit);
})
.catch((error: any) => {
error("Error occurred while updating your profile");
@@ -362,6 +367,160 @@ export default function UserSettings(props: Props) {
setThemeType(theme)
};
+ const changePressureUnit = (value: any) => {
+ let updatedUser = User.clone(user);
+ let pressureUnit: pond.PressureUnit;
+ switch (value) {
+ case 1:
+ pressureUnit = pond.PressureUnit.PRESSURE_UNIT_KILOPASCALS;
+ break;
+ case 2:
+ pressureUnit = pond.PressureUnit.PRESSURE_UNIT_INCHES_OF_WATER;
+ break;
+ default:
+ pressureUnit = pond.PressureUnit.PRESSURE_UNIT_UNKNOWN;
+ break;
+ }
+ updatedUser.settings.pressureUnit = pressureUnit;
+ setUser(updatedUser);
+ };
+
+ const changeTemperatureUnit = (value: any) => {
+ let updatedUser = User.clone(user);
+ let temperatureUnit: pond.TemperatureUnit;
+ switch (value) {
+ case 1:
+ temperatureUnit = pond.TemperatureUnit.TEMPERATURE_UNIT_CELSIUS;
+ break;
+ case 2:
+ temperatureUnit = pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT;
+ break;
+ default:
+ temperatureUnit = pond.TemperatureUnit.TEMPERATURE_UNIT_UNKNOWN;
+ break;
+ }
+ updatedUser.settings.temperatureUnit = temperatureUnit;
+ setUser(updatedUser);
+ };
+
+ const changeDistanceUnit = (value: any) => {
+ let updatedUser = User.clone(user);
+ let distanceUnit: pond.DistanceUnit;
+ switch (value) {
+ case 1:
+ distanceUnit = pond.DistanceUnit.DISTANCE_UNIT_FEET;
+ break;
+ case 2:
+ distanceUnit = pond.DistanceUnit.DISTANCE_UNIT_METERS;
+ break;
+ default:
+ distanceUnit = pond.DistanceUnit.DISTANCE_UNIT_UNKNOWN;
+ break;
+ }
+ updatedUser.settings.distanceUnit = distanceUnit;
+ setUser(updatedUser);
+ };
+
+ const changeGrainUnit = (value: any) => {
+ let updatedUser = User.clone(user);
+ let grainUnit: pond.GrainUnit;
+ switch (value) {
+ case 1:
+ grainUnit = pond.GrainUnit.GRAIN_UNIT_BUSHELS;
+ break;
+ case 2:
+ grainUnit = pond.GrainUnit.GRAIN_UNIT_WEIGHT;
+ break;
+ default:
+ grainUnit = pond.GrainUnit.GRAIN_UNIT_UNKNOWN;
+ break;
+ }
+ updatedUser.settings.grainUnit = grainUnit;
+ setUser(updatedUser);
+ };
+
+ const unitPreferences = () => {
+ const { pressureUnit, temperatureUnit, distanceUnit, grainUnit } = user.settings;
+ return (
+
+
+ changePressureUnit(event.target.value)}
+ margin="normal"
+ variant="outlined"
+ InputLabelProps={{ shrink: true }}>
+
+
+
+
+
+ changeTemperatureUnit(event.target.value)}
+ margin="normal"
+ variant="outlined"
+ InputLabelProps={{ shrink: true }}>
+
+
+
+
+
+ changeDistanceUnit(event.target.value)}
+ margin="normal"
+ variant="outlined"
+ InputLabelProps={{ shrink: true }}>
+
+
+
+
+ {IsAdaptiveAgriculture() && (
+
+ changeGrainUnit(event.target.value)}
+ margin="normal"
+ variant="outlined"
+ InputLabelProps={{ shrink: true }}>
+
+
+
+
+ )}
+
+ );
+ };
+
const themeToggle = () => {
return (
- {/*
+
- setUnitsExpanded(!unitsExpanded)}>
+ setUnitsExpanded(!unitsExpanded)}>
{unitsExpanded ? : }
-
+
@@ -472,7 +631,7 @@ export default function UserSettings(props: Props) {
-
+ {/*
setMapsExpanded(!mapsExpanded)}>