diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index d1e373003a..2a57d6f388 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -137,7 +137,7 @@ If you wish to delete all the apps created in development and reset the environm ### Backend -For the backend we run [Redis](https://redis.io/), [CouchDB](https://couchdb.apache.org/), [MinIO](https://min.io/) and [Envoy](https://www.envoyproxy.io/) in Docker compose. This means that to develop Budibase you will need Docker and Docker compose installed. The backend services are then ran separately as Node services with nodemon so that they can be debugged outside of Docker. +For the backend we run [Redis](https://redis.io/), [CouchDB](https://couchdb.apache.org/), [MinIO](https://min.io/) and [NGINX](https://www.nginx.com/) in Docker compose. This means that to develop Budibase you will need Docker and Docker compose installed. The backend services are then ran separately as Node services with nodemon so that they can be debugged outside of Docker. ### Data Storage diff --git a/.github/workflows/deploy-preprod.yml b/.github/workflows/deploy-preprod.yml index 5b3282313c..10fadc36c5 100644 --- a/.github/workflows/deploy-preprod.yml +++ b/.github/workflows/deploy-preprod.yml @@ -38,7 +38,7 @@ jobs: wc -l values.preprod.yaml - name: Deploy to Preprod Environment - uses: deliverybot/helm@v1 + uses: glopezep/helm@v1.7.1 with: release: budibase-preprod namespace: budibase diff --git a/.github/workflows/release-selfhost.yml b/.github/workflows/release-selfhost.yml index e842da9d7c..f51648074c 100644 --- a/.github/workflows/release-selfhost.yml +++ b/.github/workflows/release-selfhost.yml @@ -25,14 +25,17 @@ jobs: # Pull apps and worker images docker pull budibase/apps:$release_tag docker pull budibase/worker:$release_tag + docker pull budibase/proxy:$release_tag # Tag apps and worker images docker tag budibase/apps:$release_tag budibase/apps:$SELFHOST_TAG docker tag budibase/worker:$release_tag budibase/worker:$SELFHOST_TAG + docker tag budibase/proxy:$release_tag budibase/proxy:$SELFHOST_TAG # Push images docker push budibase/apps:$SELFHOST_TAG docker push budibase/worker:$SELFHOST_TAG + docker push budibase/proxy:$SELFHOST_TAG env: DOCKER_USER: ${{ secrets.DOCKER_USERNAME }} DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }} @@ -73,4 +76,4 @@ jobs: files: | packages/cli/build/cli-win.exe packages/cli/build/cli-linux - packages/cli/build/cli-macos \ No newline at end of file + packages/cli/build/cli-macos diff --git a/.gitignore b/.gitignore index 969a38b46e..69eba842ad 100644 --- a/.gitignore +++ b/.gitignore @@ -64,7 +64,7 @@ typings/ # dotenv environment variables file .env !hosting/.env -hosting/.generated-envoy.dev.yaml +hosting/.generated-nginx.dev.conf # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/charts/budibase/Chart.yaml b/charts/budibase/Chart.yaml index 8c9d44f201..766657769b 100644 --- a/charts/budibase/Chart.yaml +++ b/charts/budibase/Chart.yaml @@ -11,8 +11,8 @@ sources: - https://github.com/Budibase/budibase - https://budibase.com type: application -version: 0.2.5 -appVersion: 1.0.25 +version: 0.2.6 +appVersion: 1.0.48 dependencies: - name: couchdb version: 3.3.4 diff --git a/charts/budibase/templates/proxy-service-deployment.yaml b/charts/budibase/templates/proxy-service-deployment.yaml index 2e453d1c5b..9ea7df1608 100644 --- a/charts/budibase/templates/proxy-service-deployment.yaml +++ b/charts/budibase/templates/proxy-service-deployment.yaml @@ -25,7 +25,7 @@ spec: app.kubernetes.io/name: budibase-proxy spec: containers: - - image: budibase/proxy + - image: budibase/proxy:k8s imagePullPolicy: Always name: proxy-service ports: diff --git a/charts/budibase/templates/worker-service-deployment.yaml b/charts/budibase/templates/worker-service-deployment.yaml index 8b6f5564ad..b6c757cb9f 100644 --- a/charts/budibase/templates/worker-service-deployment.yaml +++ b/charts/budibase/templates/worker-service-deployment.yaml @@ -111,6 +111,10 @@ spec: value: {{ .Values.globals.smtp.from | quote }} - name: APPS_URL value: http://app-service:{{ .Values.services.apps.port }} + - name: GOOGLE_CLIENT_ID + value: {{ .Values.globals.google.clientId | quote }} + - name: GOOGLE_CLIENT_SECRET + value: {{ .Values.globals.google.secret | quote }} image: budibase/worker:{{ .Values.globals.appVersion }} imagePullPolicy: Always name: bbworker diff --git a/hosting/.env b/hosting/.env deleted file mode 120000 index bb1b54ad77..0000000000 --- a/hosting/.env +++ /dev/null @@ -1 +0,0 @@ -hosting.properties \ No newline at end of file diff --git a/hosting/.env b/hosting/.env new file mode 100644 index 0000000000..39df76d01e --- /dev/null +++ b/hosting/.env @@ -0,0 +1,21 @@ +# Use the main port in the builder for your self hosting URL, e.g. localhost:10000 +MAIN_PORT=10000 + +# This section contains all secrets pertaining to the system +# These should be updated +JWT_SECRET=testsecret +MINIO_ACCESS_KEY=budibase +MINIO_SECRET_KEY=budibase +COUCH_DB_PASSWORD=budibase +COUCH_DB_USER=budibase +REDIS_PASSWORD=budibase +INTERNAL_API_KEY=budibase + +# This section contains variables that do not need to be altered under normal circumstances +APP_PORT=4002 +WORKER_PORT=4003 +MINIO_PORT=4004 +COUCH_DB_PORT=4005 +REDIS_PORT=6379 +WATCHTOWER_PORT=6161 +BUDIBASE_ENVIRONMENT=PRODUCTION \ No newline at end of file diff --git a/hosting/digitalocean/files/var/lib/cloud/scripts/per-instance/001_onboot b/hosting/digitalocean/files/var/lib/cloud/scripts/per-instance/001_onboot index e5a883ac81..ffa63ad670 100755 --- a/hosting/digitalocean/files/var/lib/cloud/scripts/per-instance/001_onboot +++ b/hosting/digitalocean/files/var/lib/cloud/scripts/per-instance/001_onboot @@ -3,9 +3,8 @@ # go into the app dir cd /root -# fetch envoy and docker-compose files +# fetch nginx and docker-compose files wget https://raw.githubusercontent.com/Budibase/budibase/master/hosting/docker-compose.yaml -wget https://raw.githubusercontent.com/Budibase/budibase/master/hosting/envoy.yaml wget https://raw.githubusercontent.com/Budibase/budibase/master/hosting/hosting.properties # Create .env file from hosting.properties using bash and then remove it diff --git a/hosting/docker-compose.dev.yaml b/hosting/docker-compose.dev.yaml index eaced64e06..df403c0a22 100644 --- a/hosting/docker-compose.dev.yaml +++ b/hosting/docker-compose.dev.yaml @@ -22,18 +22,21 @@ services: retries: 3 proxy-service: - container_name: budi-envoy-dev + container_name: budi-nginx-dev restart: always - image: envoyproxy/envoy:v1.16-latest + image: nginx:latest volumes: - - ./.generated-envoy.dev.yaml:/etc/envoy/envoy.yaml + - ./.generated-nginx.dev.conf:/etc/nginx/nginx.conf ports: - "${MAIN_PORT}:10000" depends_on: - minio-service - couchdb-service + extra_hosts: + - "host.docker.internal:host-gateway" couchdb-service: + # platform: linux/amd64 container_name: budi-couchdb-dev restart: always image: ibmcom/couchdb3 diff --git a/hosting/docker-compose.yaml b/hosting/docker-compose.yaml index c94d1520a1..17ed12a13d 100644 --- a/hosting/docker-compose.yaml +++ b/hosting/docker-compose.yaml @@ -80,9 +80,8 @@ services: proxy-service: restart: always - image: envoyproxy/envoy:v1.16-latest - volumes: - - ./envoy.yaml:/etc/envoy/envoy.yaml + container_name: bbproxy + image: budibase/proxy ports: - "${MAIN_PORT}:10000" depends_on: @@ -125,7 +124,7 @@ services: - "${WATCHTOWER_PORT}:8080" volumes: - /var/run/docker.sock:/var/run/docker.sock - command: --debug --http-api-update bbapps bbworker + command: --debug --http-api-update bbapps bbworker bbproxy environment: - WATCHTOWER_HTTP_API=true - WATCHTOWER_HTTP_API_TOKEN=budibase diff --git a/hosting/envoy.dev.yaml.hbs b/hosting/envoy.dev.yaml.hbs deleted file mode 100644 index 59363fab5e..0000000000 --- a/hosting/envoy.dev.yaml.hbs +++ /dev/null @@ -1,149 +0,0 @@ -static_resources: - listeners: - - name: main_listener - address: - socket_address: { address: 0.0.0.0, port_value: 10000 } - filter_chains: - - filters: - - name: envoy.filters.network.http_connection_manager - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager - stat_prefix: ingress - codec_type: auto - route_config: - name: local_route - virtual_hosts: - - name: local_services - domains: ["*"] - routes: - # special case to redirect specifically the route path - # to the builder, if this were a prefix then it would break minio - - match: { path: "/" } - redirect: { path_redirect: "/builder/" } - - - match: { prefix: "/db/" } - route: - cluster: couchdb-service - prefix_rewrite: "/" - - - match: { prefix: "/api/system/" } - route: - cluster: worker-dev - - - match: { prefix: "/api/admin/" } - route: - cluster: worker-dev - - - match: { prefix: "/api/global/" } - route: - cluster: worker-dev - - - match: { prefix: "/api/" } - route: - cluster: server-dev - timeout: 120s - - - match: { prefix: "/app_" } - route: - cluster: server-dev - - - match: { prefix: "/app/" } - route: - cluster: server-dev - prefix_rewrite: "/" - - # the below three cases are needed to make sure - # all traffic prefixed for the builder is passed through - # correctly. - - match: { path: "/" } - route: - cluster: builder-dev - - - match: { prefix: "/builder/" } - route: - cluster: builder-dev - - - match: { prefix: "/builder" } - route: - cluster: builder-dev - prefix_rewrite: "/builder/" - - # minio is on the default route because this works - # best, minio + AWS SDK doesn't handle path proxy - - match: { prefix: "/" } - route: - cluster: minio-service - - http_filters: - - name: envoy.filters.http.router - - clusters: - - name: minio-service - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: minio-service - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: minio-service - port_value: 9000 - - - name: couchdb-service - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: couchdb-service - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: couchdb-service - port_value: 5984 - - - name: server-dev - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: server-dev - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: {{ address }} - port_value: 4001 - - - name: builder-dev - connect_timeout: 15s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: builder-dev - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: {{ address }} - port_value: 3000 - - - name: worker-dev - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: worker-dev - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: {{ address }} - port_value: 4002 diff --git a/hosting/hosting.properties b/hosting/hosting.properties deleted file mode 100644 index c8e2f5c606..0000000000 --- a/hosting/hosting.properties +++ /dev/null @@ -1,21 +0,0 @@ -# Use the main port in the builder for your self hosting URL, e.g. localhost:10000 -MAIN_PORT=10000 - -# This section contains all secrets pertaining to the system -# These should be updated -JWT_SECRET=testsecret -MINIO_ACCESS_KEY=budibase -MINIO_SECRET_KEY=budibase -COUCH_DB_PASSWORD=budibase -COUCH_DB_USER=budibase -REDIS_PASSWORD=budibase -INTERNAL_API_KEY=budibase - -# This section contains variables that do not need to be altered under normal circumstances -APP_PORT=4002 -WORKER_PORT=4003 -MINIO_PORT=4004 -COUCH_DB_PORT=4005 -REDIS_PORT=6379 -WATCHTOWER_PORT=6161 -BUDIBASE_ENVIRONMENT=PRODUCTION diff --git a/hosting/kubernetes/envoy/Dockerfile b/hosting/kubernetes/envoy/Dockerfile deleted file mode 100644 index 96334fa723..0000000000 --- a/hosting/kubernetes/envoy/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM envoyproxy/envoy:v1.16-latest -COPY envoy.yaml /etc/envoy/envoy.yaml -RUN chmod go+r /etc/envoy/envoy.yaml - diff --git a/hosting/kubernetes/envoy/envoy.yaml b/hosting/kubernetes/envoy/envoy.yaml deleted file mode 100644 index bab1f25c02..0000000000 --- a/hosting/kubernetes/envoy/envoy.yaml +++ /dev/null @@ -1,146 +0,0 @@ -static_resources: - listeners: - - name: main_listener - address: - socket_address: { address: 0.0.0.0, port_value: 10000 } - filter_chains: - - filters: - - name: envoy.filters.network.http_connection_manager - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager - stat_prefix: ingress - codec_type: auto - route_config: - name: local_route - virtual_hosts: - - name: local_services - domains: ["*"] - routes: - - match: { prefix: "/app/" } - route: - cluster: app-service - prefix_rewrite: "/" - - - match: { prefix: "/builder/" } - route: - cluster: app-service - - - match: { prefix: "/builder" } - route: - cluster: app-service - - - match: { prefix: "/app_" } - route: - cluster: app-service - - # special cases for worker admin (deprecated), global and system API - - match: { prefix: "/api/global/" } - route: - cluster: worker-service - - - match: { prefix: "/api/admin/" } - route: - cluster: worker-service - - - match: { prefix: "/api/system/" } - route: - cluster: worker-service - - - match: { path: "/" } - route: - cluster: app-service - - - match: - safe_regex: - google_re2: {} - regex: "/api/.*/export" - route: - timeout: 0s - cluster: app-service - - - match: { path: "/api/deploy" } - route: - timeout: 60s - cluster: app-service - - # special case for when API requests are made, can just forward, not to minio - - match: { prefix: "/api/" } - route: - cluster: app-service - - - match: { prefix: "/worker/" } - route: - cluster: worker-service - prefix_rewrite: "/" - - - match: { prefix: "/db/" } - route: - cluster: couchdb-service - prefix_rewrite: "/" - - # minio is on the default route because this works - # best, minio + AWS SDK doesn't handle path proxy - - match: { prefix: "/" } - route: - cluster: minio-service - - http_filters: - - name: envoy.filters.http.router - - clusters: - - name: app-service - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: app-service - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: app-service.budibase.svc.cluster.local - port_value: 4002 - - - name: minio-service - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: minio-service - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: minio-service.budibase.svc.cluster.local - port_value: 9000 - - - name: worker-service - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: worker-service - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: worker-service.budibase.svc.cluster.local - port_value: 4001 - - - name: couchdb-service - connect_timeout: 0.25s - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: couchdb-service - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: budibase-prod-svc-couchdb - port_value: 5984 - diff --git a/hosting/kubernetes/nginx/Dockerfile b/hosting/kubernetes/nginx/Dockerfile new file mode 100644 index 0000000000..754f9f9be3 --- /dev/null +++ b/hosting/kubernetes/nginx/Dockerfile @@ -0,0 +1,2 @@ +FROM nginx:latest +COPY nginx.conf /etc/nginx/nginx.conf diff --git a/hosting/kubernetes/nginx/nginx.conf b/hosting/kubernetes/nginx/nginx.conf new file mode 100644 index 0000000000..9d03aaab18 --- /dev/null +++ b/hosting/kubernetes/nginx/nginx.conf @@ -0,0 +1,141 @@ +user nginx; +error_log /var/log/nginx/error.log debug; +pid /var/run/nginx.pid; +worker_processes auto; +worker_rlimit_nofile 33282; + +events { + worker_connections 1024; +} + +http { + limit_req_zone $binary_remote_addr zone=ratelimit:10m rate=10r/s; + include /etc/nginx/mime.types; + default_type application/octet-stream; + charset utf-8; + sendfile on; + tcp_nopush on; + tcp_nodelay on; + server_tokens off; + types_hash_max_size 2048; + + # buffering + client_body_buffer_size 1K; + client_header_buffer_size 1k; + client_max_body_size 10M; + ignore_invalid_headers off; + proxy_buffering off; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + map $http_upgrade $connection_upgrade { + default "upgrade"; + } + + server { + listen 10000 default_server; + listen [::]:10000 default_server; + server_name _; + port_in_redirect off; + + # Security Headers + add_header X-Frame-Options SAMEORIGIN always; + add_header X-Content-Type-Options nosniff always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.budi.live https://js.intercomcdn.com https://widget.intercom.io; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://rsms.me; object-src 'none'; base-uri 'self'; connect-src 'self' https://api-iam.intercom.io https://app.posthog.com wss://nexus-websocket-a.intercom.io; font-src 'self' data: https://cdn.jsdelivr.net https://fonts.gstatic.com https://rsms.me; frame-src 'self'; img-src http: https: data:; manifest-src 'self'; media-src 'self'; worker-src 'none';" always; + + location /app { + proxy_pass http://app-service.budibase.svc.cluster.local:4002; + rewrite ^/app/(.*)$ /$1 break; + } + + location = / { + proxy_http_version 1.1; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://app-service.budibase.svc.cluster.local:4002; + } + + location /builder/ { + proxy_http_version 1.1; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://app-service.budibase.svc.cluster.local:4002; + } + + location ~ ^/(builder|app_) { + proxy_http_version 1.1; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://app-service.budibase.svc.cluster.local:4002; + } + + location ~ ^/api/(system|admin|global)/ { + proxy_pass http://worker-service.budibase.svc.cluster.local:4001; + } + + location /worker/ { + proxy_pass http://worker-service.budibase.svc.cluster.local:4001; + rewrite ^/worker/(.*)$ /$1 break; + } + + location /api/ { + # calls to the API are rate limited with bursting + limit_req zone=ratelimit burst=10 nodelay; + + # 120s timeout on API requests + proxy_read_timeout 120s; + proxy_connect_timeout 120s; + proxy_send_timeout 120s; + + proxy_http_version 1.1; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_pass http://app-service.budibase.svc.cluster.local:4002; + } + + location /db/ { + proxy_pass http://budibase-prod-svc-couchdb:5984; + rewrite ^/db/(.*)$ /$1 break; + } + + location / { + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + proxy_set_header Connection ""; + proxy_http_version 1.1; + chunked_transfer_encoding off; + + proxy_connect_timeout 300; + proxy_pass http://minio-service.budibase.svc.cluster.local:9000; + } + + client_header_timeout 60; + client_body_timeout 60; + keepalive_timeout 60; + + # gzip + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; + } +} diff --git a/hosting/nginx.dev.conf.hbs b/hosting/nginx.dev.conf.hbs new file mode 100644 index 0000000000..51b55cd49b --- /dev/null +++ b/hosting/nginx.dev.conf.hbs @@ -0,0 +1,91 @@ +user nginx; +error_log /var/log/nginx/error.log debug; +pid /var/run/nginx.pid; +worker_processes auto; +worker_rlimit_nofile 33282; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + map $http_upgrade $connection_upgrade { + default "upgrade"; + } + + server { + listen 10000 default_server; + server_name _; + client_max_body_size 1000m; + ignore_invalid_headers off; + proxy_buffering off; + + location /db/ { + proxy_pass http://couchdb-service:5984; + rewrite ^/db/(.*)$ /$1 break; + } + + location ~ ^/api/(system|admin|global)/ { + proxy_pass http://{{ address }}:4002; + } + + location /api/ { + proxy_read_timeout 120s; + proxy_connect_timeout 120s; + proxy_send_timeout 120s; + proxy_pass http://{{ address }}:4001; + } + + location /app_ { + proxy_pass http://{{ address }}:4001; + } + + location /app/ { + proxy_pass http://{{ address }}:4001; + rewrite ^/app/(.*)$ /$1 break; + } + + location /builder { + proxy_pass http://{{ address }}:3000; + rewrite ^/builder(.*)$ /builder/$1 break; + } + + location /builder/ { + proxy_pass http://{{ address }}:3000; + + proxy_http_version 1.1; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location / { + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + + proxy_connect_timeout 300; + proxy_http_version 1.1; + proxy_set_header Connection ""; + chunked_transfer_encoding off; + + proxy_pass http://minio-service:9000; + } + + client_header_timeout 60; + client_body_timeout 60; + keepalive_timeout 60; + gzip off; + gzip_comp_level 4; + } +} \ No newline at end of file diff --git a/hosting/proxy/Dockerfile b/hosting/proxy/Dockerfile new file mode 100644 index 0000000000..2fd64a9d68 --- /dev/null +++ b/hosting/proxy/Dockerfile @@ -0,0 +1,2 @@ +FROM nginx:latest +COPY nginx.conf /etc/nginx/nginx.conf \ No newline at end of file diff --git a/hosting/proxy/nginx.conf b/hosting/proxy/nginx.conf new file mode 100644 index 0000000000..ff12e2f49e --- /dev/null +++ b/hosting/proxy/nginx.conf @@ -0,0 +1,145 @@ +user nginx; +error_log /var/log/nginx/error.log debug; +pid /var/run/nginx.pid; +worker_processes auto; +worker_rlimit_nofile 33282; + +events { + worker_connections 1024; +} + +http { + limit_req_zone $binary_remote_addr zone=ratelimit:10m rate=10r/s; + include /etc/nginx/mime.types; + default_type application/octet-stream; + charset utf-8; + sendfile on; + tcp_nopush on; + tcp_nodelay on; + server_tokens off; + types_hash_max_size 2048; + + # buffering + client_body_buffer_size 1K; + client_header_buffer_size 1k; + client_max_body_size 1k; + ignore_invalid_headers off; + + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + map $http_upgrade $connection_upgrade { + default "upgrade"; + } + + server { + listen 10000 default_server; + listen [::]:10000 default_server; + server_name _; + client_max_body_size 1000m; + ignore_invalid_headers off; + proxy_buffering off; + port_in_redirect off; + + # Security Headers + add_header X-Frame-Options SAMEORIGIN always; + add_header X-Content-Type-Options nosniff always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.budi.live https://js.intercomcdn.com https://widget.intercom.io; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://rsms.me; object-src 'none'; base-uri 'self'; connect-src 'self' https://api-iam.intercom.io https://app.posthog.com wss://nexus-websocket-a.intercom.io; font-src 'self' data: https://cdn.jsdelivr.net https://fonts.gstatic.com https://rsms.me; frame-src 'self'; img-src http: https: data:; manifest-src 'self'; media-src 'self'; worker-src 'none';" always; + + location /app { + proxy_pass http://app-service:4002; + rewrite ^/app/(.*)$ /$1 break; + } + + location = / { + port_in_redirect off; + proxy_pass http://app-service:4002; + } + + location = /v1/update { + proxy_pass http://watchtower-service:8080; + } + + location /builder/ { + port_in_redirect off; + proxy_http_version 1.1; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://app-service:4002; + } + + location ~ ^/(builder|app_) { + port_in_redirect off; + proxy_http_version 1.1; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://app-service:4002; + } + + location ~ ^/api/(system|admin|global)/ { + proxy_pass http://worker-service:4003; + } + + location /worker/ { + proxy_pass http://worker-service:4003; + rewrite ^/worker/(.*)$ /$1 break; + } + + location /api/ { + # calls to the API are rate limited with bursting + limit_req zone=ratelimit burst=10 nodelay; + + # 120s timeout on API requests + proxy_read_timeout 120s; + proxy_connect_timeout 120s; + proxy_send_timeout 120s; + + proxy_http_version 1.1; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_pass http://app-service:4002; + } + + location /db/ { + proxy_pass http://couchdb-service:5984; + rewrite ^/db/(.*)$ /$1 break; + } + + location / { + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + + proxy_connect_timeout 300; + proxy_http_version 1.1; + proxy_set_header Connection ""; + chunked_transfer_encoding off; + proxy_pass http://minio-service:9000; + } + + client_header_timeout 60; + client_body_timeout 60; + keepalive_timeout 60; + + # gzip + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; + } +} \ No newline at end of file diff --git a/hosting/scripts/airgapped/airgappedDockerBuild.js b/hosting/scripts/airgapped/airgappedDockerBuild.js index 5be19fdeb8..4bd324364c 100755 --- a/hosting/scripts/airgapped/airgappedDockerBuild.js +++ b/hosting/scripts/airgapped/airgappedDockerBuild.js @@ -5,7 +5,7 @@ const path = require("path") const IMAGES = { worker: "budibase/worker", apps: "budibase/apps", - proxy: "envoyproxy/envoy:v1.16-latest", + proxy: "budibase/proxy", minio: "minio/minio", couch: "ibmcom/couchdb3", curl: "curlimages/curl", @@ -15,8 +15,7 @@ const IMAGES = { const FILES = { COMPOSE: "docker-compose.yaml", - ENVOY: "envoy.yaml", - PROPERTIES: "hosting.properties" + NGINX: "nginx.conf" } const OUTPUT_DIR = path.join(__dirname, "../", "bb-airgapped") diff --git a/hosting/scripts/linux/release-to-docker-hub.sh b/hosting/scripts/linux/release-to-docker-hub.sh index 642a8682fb..599a10f914 100755 --- a/hosting/scripts/linux/release-to-docker-hub.sh +++ b/hosting/scripts/linux/release-to-docker-hub.sh @@ -9,8 +9,10 @@ fi echo "Tagging images with tag: $tag" +docker tag proxy-service budibase/proxy:$tag docker tag app-service budibase/apps:$tag docker tag worker-service budibase/worker:$tag docker push --all-tags budibase/apps docker push --all-tags budibase/worker +docker push --all-tags budibase/proxy diff --git a/lerna.json b/lerna.json index a9e7e849df..39b194d354 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "1.0.50-alpha.4", + "version": "1.0.66-alpha.0", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/package.json b/package.json index 5960d15e75..9ab9a4411f 100644 --- a/package.json +++ b/package.json @@ -44,9 +44,10 @@ "lint:fix": "yarn run lint:fix:ts && yarn run lint:fix:prettier && yarn run lint:fix:eslint", "test:e2e": "lerna run cy:test", "test:e2e:ci": "lerna run cy:ci", - "build:docker": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh $BUDIBASE_RELEASE_VERSION && cd -", + "build:docker": "lerna run build:docker && npm run build:docker:proxy && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh $BUDIBASE_RELEASE_VERSION && cd -", + "build:docker:proxy": "docker build hosting/proxy -t proxy-service", "build:docker:selfhost": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh latest && cd -", - "build:docker:develop": "node scripts/pinVersions && lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh develop && cd -", + "build:docker:develop": "node scripts/pinVersions && lerna run build:docker && npm run build:docker:proxy && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh develop && cd -", "build:docker:airgap": "node hosting/scripts/airgapped/airgappedDockerBuild", "build:digitalocean": "cd hosting/digitalocean && ./build.sh && cd -", "build:docs": "lerna run build:docs", diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index b89fdbe593..d98829c4b9 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/backend-core", - "version": "1.0.50-alpha.4", + "version": "1.0.66-alpha.0", "description": "Budibase backend core libraries used in server and worker", "main": "src/index.js", "author": "Budibase", diff --git a/packages/backend-core/src/environment.js b/packages/backend-core/src/environment.js index c26ad1c199..d112ad8599 100644 --- a/packages/backend-core/src/environment.js +++ b/packages/backend-core/src/environment.js @@ -11,6 +11,8 @@ module.exports = { COUCH_DB_URL: process.env.COUCH_DB_URL, COUCH_DB_USERNAME: process.env.COUCH_DB_USER, COUCH_DB_PASSWORD: process.env.COUCH_DB_PASSWORD, + GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID, + GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET, SALT_ROUNDS: process.env.SALT_ROUNDS, REDIS_URL: process.env.REDIS_URL, REDIS_PASSWORD: process.env.REDIS_PASSWORD, diff --git a/packages/backend-core/src/middleware/passport/datasource/google.js b/packages/backend-core/src/middleware/passport/datasource/google.js index bfc2e4a61e..c7553cee50 100644 --- a/packages/backend-core/src/middleware/passport/datasource/google.js +++ b/packages/backend-core/src/middleware/passport/datasource/google.js @@ -1,22 +1,17 @@ -const { getScopedConfig } = require("../../../db/utils") -const { getGlobalDB } = require("../../../tenancy") const google = require("../google") -const { Configs, Cookies } = require("../../../constants") +const { Cookies } = require("../../../constants") const { clearCookie, getCookie } = require("../../../utils") const { getDB } = require("../../../db") +const environment = require("../../../environment") async function preAuth(passport, ctx, next) { - const db = getGlobalDB() // get the relevant config - const config = await getScopedConfig(db, { - type: Configs.GOOGLE, - workspace: ctx.query.workspace, - }) - const publicConfig = await getScopedConfig(db, { - type: Configs.SETTINGS, - }) - let callbackUrl = `${publicConfig.platformUrl}/api/global/auth/datasource/google/callback` - const strategy = await google.strategyFactory(config, callbackUrl) + const googleConfig = { + clientID: environment.GOOGLE_CLIENT_ID, + clientSecret: environment.GOOGLE_CLIENT_SECRET, + } + let callbackUrl = `${environment.PLATFORM_URL}/api/global/auth/datasource/google/callback` + const strategy = await google.strategyFactory(googleConfig, callbackUrl) if (!ctx.query.appId || !ctx.query.datasourceId) { ctx.throw(400, "appId and datasourceId query params not present.") @@ -30,18 +25,13 @@ async function preAuth(passport, ctx, next) { } async function postAuth(passport, ctx, next) { - const db = getGlobalDB() + // get the relevant config + const config = { + clientID: environment.GOOGLE_CLIENT_ID, + clientSecret: environment.GOOGLE_CLIENT_SECRET, + } - const config = await getScopedConfig(db, { - type: Configs.GOOGLE, - workspace: ctx.query.workspace, - }) - - const publicConfig = await getScopedConfig(db, { - type: Configs.SETTINGS, - }) - - let callbackUrl = `${publicConfig.platformUrl}/api/global/auth/datasource/google/callback` + let callbackUrl = `${environment.PLATFORM_URL}/api/global/auth/datasource/google/callback` const strategy = await google.strategyFactory( config, callbackUrl, diff --git a/packages/backend-core/src/middleware/passport/local.js b/packages/backend-core/src/middleware/passport/local.js index f95c3a173e..2149bd3e18 100644 --- a/packages/backend-core/src/middleware/passport/local.js +++ b/packages/backend-core/src/middleware/passport/local.js @@ -8,7 +8,7 @@ const { newid } = require("../../hashing") const { createASession } = require("../../security/sessions") const { getTenantId } = require("../../tenancy") -const INVALID_ERR = "Invalid Credentials" +const INVALID_ERR = "Invalid credentials" const SSO_NO_PASSWORD = "SSO user does not have a password set" const EXPIRED = "This account has expired. Please reset your password" diff --git a/packages/bbui/package.json b/packages/bbui/package.json index 5de946fbcf..a7570b12a0 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "1.0.50-alpha.4", + "version": "1.0.66-alpha.0", "license": "MPL-2.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", diff --git a/packages/bbui/src/Button/Button.svelte b/packages/bbui/src/Button/Button.svelte index da4d405f02..8e5cc5468c 100644 --- a/packages/bbui/src/Button/Button.svelte +++ b/packages/bbui/src/Button/Button.svelte @@ -1,5 +1,6 @@ diff --git a/packages/bbui/src/ButtonGroup/ButtonGroup.svelte b/packages/bbui/src/ButtonGroup/ButtonGroup.svelte index b845a770e1..50d0073a25 100644 --- a/packages/bbui/src/ButtonGroup/ButtonGroup.svelte +++ b/packages/bbui/src/ButtonGroup/ButtonGroup.svelte @@ -1,6 +1,16 @@ - -
- {#if !!error} +
+ {#if !!error} + + {/if} + +
+
- -
-
+ +{/key} {#if open}
{/if} diff --git a/packages/bbui/src/Form/Core/Dropzone.svelte b/packages/bbui/src/Form/Core/Dropzone.svelte index 6b8022a36c..d739e751c9 100644 --- a/packages/bbui/src/Form/Core/Dropzone.svelte +++ b/packages/bbui/src/Form/Core/Dropzone.svelte @@ -3,7 +3,7 @@ import "@spectrum-css/typography/dist/index-vars.css" import "@spectrum-css/illustratedmessage/dist/index-vars.css" import { createEventDispatcher } from "svelte" - import { generateID } from "../../utils/helpers" + import { uuid } from "../../helpers" import Icon from "../../Icon/Icon.svelte" import Link from "../../Link/Link.svelte" import Tag from "../../Tags/Tag.svelte" @@ -37,7 +37,7 @@ "jfif", ] - const fieldId = id || generateID() + const fieldId = id || uuid() let selectedImageIdx = 0 let fileDragged = false let selectedUrl diff --git a/packages/bbui/src/Form/DatePicker.svelte b/packages/bbui/src/Form/DatePicker.svelte index 7d5656a22d..9298c49177 100644 --- a/packages/bbui/src/Form/DatePicker.svelte +++ b/packages/bbui/src/Form/DatePicker.svelte @@ -9,6 +9,7 @@ export let disabled = false export let error = null export let enableTime = true + export let timeOnly = false export let placeholder = null export let appendTo = undefined @@ -27,6 +28,7 @@ {value} {placeholder} {enableTime} + {timeOnly} {appendTo} on:change={onChange} /> diff --git a/packages/bbui/src/Table/DateTimeRenderer.svelte b/packages/bbui/src/Table/DateTimeRenderer.svelte index 8a06082d58..ff750cecd8 100644 --- a/packages/bbui/src/Table/DateTimeRenderer.svelte +++ b/packages/bbui/src/Table/DateTimeRenderer.svelte @@ -2,9 +2,18 @@ import dayjs from "dayjs" export let value + + // adding the 0- will turn a string like 00:00:00 into a valid ISO + // date, but will make actual ISO dates invalid + $: time = new Date(`0-${value}`) + $: isTime = !isNaN(time) -
{dayjs(value).format("MMMM D YYYY, HH:mm")}
+
+ {dayjs(isTime ? time : value).format( + isTime ? "HH:mm:ss" : "MMMM D YYYY, HH:mm" + )} +
diff --git a/packages/builder/src/components/backend/DataTable/modals/ManageAccessModal.svelte b/packages/builder/src/components/backend/DataTable/modals/ManageAccessModal.svelte index 0998a5be2e..aa6dbc93e0 100644 --- a/packages/builder/src/components/backend/DataTable/modals/ManageAccessModal.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/ManageAccessModal.svelte @@ -14,15 +14,19 @@ export let permissions async function changePermission(level, role) { - await permissionsStore.save({ - level, - role, - resource: resourceId, - }) + try { + await permissionsStore.save({ + level, + role, + resource: resourceId, + }) - // Show updated permissions in UI: REMOVE - permissions = await permissionsStore.forResource(resourceId) - notifications.success("Updated permissions.") + // Show updated permissions in UI: REMOVE + permissions = await permissionsStore.forResource(resourceId) + notifications.success("Updated permissions") + } catch (error) { + notifications.error("Error updating permissions") + } } diff --git a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte index af345ddcdf..661c125377 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/DatasourceNavigator.svelte @@ -10,6 +10,7 @@ import TableNavigator from "components/backend/TableNavigator/TableNavigator.svelte" import { customQueryIconText, customQueryIconColor } from "helpers/data/utils" import ICONS from "./icons" + import { notifications } from "@budibase/bbui" let openDataSources = [] $: enrichedDataSources = Array.isArray($datasources.list) @@ -63,9 +64,13 @@ } } - onMount(() => { - datasources.fetch() - queries.fetch() + onMount(async () => { + try { + await datasources.fetch() + await queries.fetch() + } catch (error) { + notifications.error("Error fetching datasources and queries") + } }) const containsActiveEntity = datasource => { diff --git a/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/CreateExternalTableModal.svelte b/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/CreateExternalTableModal.svelte index 52402a0396..f6cd6af758 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/CreateExternalTableModal.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/CreateExternalTableModal.svelte @@ -1,5 +1,5 @@ diff --git a/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/PlusConfigForm.svelte b/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/PlusConfigForm.svelte index 45bc5ff330..c94e750c29 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/PlusConfigForm.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/PlusConfigForm.svelte @@ -90,8 +90,8 @@ await datasources.updateSchema(datasource) notifications.success(`Datasource ${name} tables updated successfully.`) await tables.fetch() - } catch (err) { - notifications.error(`Error updating datasource schema: ${err}`) + } catch (error) { + notifications.error("Error updating datasource schema") } } diff --git a/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/index.svelte b/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/index.svelte index 79ebdb1020..9277207e37 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/index.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/TableIntegrationMenu/index.svelte @@ -1,7 +1,7 @@ diff --git a/packages/builder/src/components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte b/packages/builder/src/components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte index 97168358cf..8f1218f087 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte @@ -20,7 +20,7 @@ $goto(`./datasource/${resp._id}`) notifications.success(`Datasource updated successfully.`) } catch (err) { - notifications.error(`Error saving datasource: ${err}`) + notifications.error("Error saving datasource") } } diff --git a/packages/builder/src/components/backend/DatasourceNavigator/modals/ImportRestQueriesModal.svelte b/packages/builder/src/components/backend/DatasourceNavigator/modals/ImportRestQueriesModal.svelte index cd6cef42d1..8a34edade0 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/modals/ImportRestQueriesModal.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/modals/ImportRestQueriesModal.svelte @@ -79,8 +79,8 @@ }) return true - } catch (err) { - notifications.error(`Error importing: ${err}`) + } catch (error) { + notifications.error("Error importing queries") return false } } @@ -130,6 +130,3 @@ - - diff --git a/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditDatasourcePopover.svelte b/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditDatasourcePopover.svelte index 1354c31b87..ae0023f682 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditDatasourcePopover.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditDatasourcePopover.svelte @@ -12,24 +12,28 @@ let updateDatasourceDialog async function deleteDatasource() { - let wasSelectedSource = $datasources.selected - if (!wasSelectedSource && $queries.selected) { - const queryId = $queries.selected - wasSelectedSource = $datasources.list.find(ds => - queryId.includes(ds._id) - )?._id - } - const wasSelectedTable = $tables.selected - await datasources.delete(datasource) - notifications.success("Datasource deleted") - // navigate to first index page if the source you are deleting is selected - const entities = Object.values(datasource?.entities || {}) - if ( - wasSelectedSource === datasource._id || - (entities && - entities.find(entity => entity._id === wasSelectedTable?._id)) - ) { - $goto("./datasource") + try { + let wasSelectedSource = $datasources.selected + if (!wasSelectedSource && $queries.selected) { + const queryId = $queries.selected + wasSelectedSource = $datasources.list.find(ds => + queryId.includes(ds._id) + )?._id + } + const wasSelectedTable = $tables.selected + await datasources.delete(datasource) + notifications.success("Datasource deleted") + // Navigate to first index page if the source you are deleting is selected + const entities = Object.values(datasource?.entities || {}) + if ( + wasSelectedSource === datasource._id || + (entities && + entities.find(entity => entity._id === wasSelectedTable?._id)) + ) { + $goto("./datasource") + } + } catch (error) { + notifications.error("Error deleting datasource") } } diff --git a/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditQueryPopover.svelte b/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditQueryPopover.svelte index b15746735b..e18deab2dd 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditQueryPopover.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/popovers/EditQueryPopover.svelte @@ -10,26 +10,30 @@ let confirmDeleteDialog async function deleteQuery() { - const wasSelectedQuery = $queries.selected - // need to calculate this before the query is deleted - const navigateToDatasource = wasSelectedQuery === query._id + try { + const wasSelectedQuery = $queries.selected + // need to calculate this before the query is deleted + const navigateToDatasource = wasSelectedQuery === query._id - await queries.delete(query) - await datasources.fetch() + await queries.delete(query) + await datasources.fetch() - if (navigateToDatasource) { - await datasources.select(query.datasourceId) - $goto(`./datasource/${query.datasourceId}`) + if (navigateToDatasource) { + await datasources.select(query.datasourceId) + $goto(`./datasource/${query.datasourceId}`) + } + notifications.success("Query deleted") + } catch (error) { + notifications.error("Error deleting query") } - notifications.success("Query deleted") } async function duplicateQuery() { try { const newQuery = await queries.duplicate(query) onClickQuery(newQuery) - } catch (e) { - notifications.error(e.message) + } catch (error) { + notifications.error("Error duplicating query") } } diff --git a/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte b/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte index f76c91e133..990aa736f6 100644 --- a/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte +++ b/packages/builder/src/components/backend/Datasources/CreateEditRelationship.svelte @@ -9,7 +9,7 @@ Body, } from "@budibase/bbui" import { tables } from "stores/backend" - import { uuid } from "builderStore/uuid" + import { Helpers } from "@budibase/bbui" import { writable } from "svelte/store" export let save @@ -140,7 +140,7 @@ const manyToMany = fromRelationship.relationshipType === RelationshipTypes.MANY_TO_MANY // main is simply used to know this is the side the user configured it from - const id = uuid() + const id = Helpers.uuid() if (!manyToMany) { delete fromRelationship.through delete toRelationship.through diff --git a/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte b/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte index 44cb374092..774aac0677 100644 --- a/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte +++ b/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte @@ -1,7 +1,7 @@ diff --git a/packages/builder/src/components/common/Dropzone.svelte b/packages/builder/src/components/common/Dropzone.svelte index 328cd31f23..9a86554b49 100644 --- a/packages/builder/src/components/common/Dropzone.svelte +++ b/packages/builder/src/components/common/Dropzone.svelte @@ -1,6 +1,6 @@ diff --git a/packages/builder/src/components/common/LinkedRowSelector.svelte b/packages/builder/src/components/common/LinkedRowSelector.svelte index 1f3dea137f..592ee47565 100644 --- a/packages/builder/src/components/common/LinkedRowSelector.svelte +++ b/packages/builder/src/components/common/LinkedRowSelector.svelte @@ -1,6 +1,6 @@
@@ -39,7 +43,10 @@ name="SaveFloppy" hoverable size="S" - on:click={() => setEditing(false)} + on:click={() => { + setEditing(false) + save() + }} /> {/if}
diff --git a/packages/builder/src/components/deploy/DeployModal.svelte b/packages/builder/src/components/deploy/DeployModal.svelte index 33fe8d5321..9f2aae56c5 100644 --- a/packages/builder/src/components/deploy/DeployModal.svelte +++ b/packages/builder/src/components/deploy/DeployModal.svelte @@ -1,6 +1,6 @@ diff --git a/packages/builder/src/components/deploy/DeploymentHistory.svelte b/packages/builder/src/components/deploy/DeploymentHistory.svelte index 36c2433c27..e933142348 100644 --- a/packages/builder/src/components/deploy/DeploymentHistory.svelte +++ b/packages/builder/src/components/deploy/DeploymentHistory.svelte @@ -3,7 +3,7 @@ import Spinner from "components/common/Spinner.svelte" import { slide } from "svelte/transition" import { Heading, Button, Modal, ModalContent } from "@budibase/bbui" - import api from "builderStore/api" + import { API } from "api" import { notifications } from "@budibase/bbui" import CreateWebhookDeploymentModal from "./CreateWebhookDeploymentModal.svelte" import { store } from "builderStore" @@ -63,20 +63,14 @@ async function fetchDeployments() { try { - const response = await api.get(`/api/deployments`) - const json = await response.json() - + const newDeployments = await API.getAppDeployments() if (deployments.length > 0) { - checkIncomingDeploymentStatus(deployments, json) + checkIncomingDeploymentStatus(deployments, newDeployments) } - - deployments = json + deployments = newDeployments } catch (err) { - console.error(err) clearInterval(poll) - notifications.error( - "Error fetching deployment history. Please try again." - ) + notifications.error("Error fetching deployment history") } } diff --git a/packages/builder/src/components/deploy/RevertModal.svelte b/packages/builder/src/components/deploy/RevertModal.svelte index bc2ad0d0aa..717c55f05e 100644 --- a/packages/builder/src/components/deploy/RevertModal.svelte +++ b/packages/builder/src/components/deploy/RevertModal.svelte @@ -7,7 +7,7 @@ ModalContent, } from "@budibase/bbui" import { store } from "builderStore" - import api from "builderStore/api" + import { API } from "api" let revertModal let appName @@ -16,24 +16,14 @@ const revert = async () => { try { - const response = await api.post(`/api/dev/${appId}/revert`) - const json = await response.json() - if (response.status !== 200) throw json.message + await API.revertAppChanges(appId) // Reset frontend state after revert - const applicationPkg = await api.get( - `/api/applications/${appId}/appPackage` - ) - const pkg = await applicationPkg.json() - if (applicationPkg.ok) { - await store.actions.initialise(pkg) - } else { - throw new Error(pkg) - } - - notifications.info("Changes reverted.") - } catch (err) { - notifications.error(`Error reverting changes: ${err}`) + const applicationPkg = await API.fetchAppPackage(appId) + await store.actions.initialise(applicationPkg) + notifications.info("Changes reverted successfully") + } catch (error) { + notifications.error(`Error reverting changes: ${error}`) } } diff --git a/packages/builder/src/components/deploy/VersionModal.svelte b/packages/builder/src/components/deploy/VersionModal.svelte index 0fb061face..9707517c54 100644 --- a/packages/builder/src/components/deploy/VersionModal.svelte +++ b/packages/builder/src/components/deploy/VersionModal.svelte @@ -8,7 +8,7 @@ Button, } from "@budibase/bbui" import { store } from "builderStore" - import api from "builderStore/api" + import { API } from "api" import clientPackage from "@budibase/client/package.json" let updateModal @@ -18,26 +18,17 @@ $: revertAvailable = $store.revertableVersion != null const refreshAppPackage = async () => { - const applicationPkg = await api.get( - `/api/applications/${appId}/appPackage` - ) - const pkg = await applicationPkg.json() - if (applicationPkg.ok) { + try { + const pkg = await API.fetchAppPackage(appId) await store.actions.initialise(pkg) - } else { - throw new Error(pkg) + } catch (error) { + notifications.error("Error fetching app package") } } const update = async () => { try { - const response = await api.post( - `/api/applications/${appId}/client/update` - ) - const json = await response.json() - if (response.status !== 200) { - throw json.message - } + await API.updateAppClientVersion(appId) // Don't wait for the async refresh, since this causes modal flashing refreshAppPackage() @@ -47,23 +38,17 @@ } catch (err) { notifications.error(`Error updating app: ${err}`) } + updateModal.hide() } const revert = async () => { try { - const revertableVersion = $store.revertableVersion - const response = await api.post( - `/api/applications/${appId}/client/revert` - ) - const json = await response.json() - if (response.status !== 200) { - throw json.message - } + await API.revertAppClientVersion(appId) // Don't wait for the async refresh, since this causes modal flashing refreshAppPackage() notifications.success( - `App reverted successfully to version ${revertableVersion}` + `App reverted successfully to version ${$store.revertableVersion}` ) } catch (err) { notifications.error(`Error reverting app: ${err}`) diff --git a/packages/builder/src/components/design/AppPreview/AppThemeSelect.svelte b/packages/builder/src/components/design/AppPreview/AppThemeSelect.svelte index a1a5a7a242..cdab47db66 100644 --- a/packages/builder/src/components/design/AppPreview/AppThemeSelect.svelte +++ b/packages/builder/src/components/design/AppPreview/AppThemeSelect.svelte @@ -1,5 +1,5 @@ diff --git a/packages/builder/src/components/design/AppPreview/ComponentSelectionList.svelte b/packages/builder/src/components/design/AppPreview/ComponentSelectionList.svelte index a968effaf5..cabcea756e 100644 --- a/packages/builder/src/components/design/AppPreview/ComponentSelectionList.svelte +++ b/packages/builder/src/components/design/AppPreview/ComponentSelectionList.svelte @@ -1,5 +1,11 @@ diff --git a/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte b/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte index 3dac5838aa..2a886fab0c 100644 --- a/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte +++ b/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte @@ -13,7 +13,7 @@ Body, notifications, } from "@budibase/bbui" - import ErrorSVG from "assets/error.svg?raw" + import ErrorSVG from "@budibase/frontend-core/assets/error.svg?raw" import { findComponent, findComponentPath } from "builderStore/componentUtils" let iframe @@ -146,44 +146,49 @@ } }) - const handleBudibaseEvent = event => { + const handleBudibaseEvent = async event => { const { type, data } = event.data || event.detail if (!type) { return } - if (type === "select-component" && data.id) { - store.actions.components.select({ _id: data.id }) - } else if (type === "update-prop") { - store.actions.components.updateProp(data.prop, data.value) - } else if (type === "delete-component" && data.id) { - confirmDeleteComponent(data.id) - } else if (type === "preview-loaded") { - // Wait for this event to show the client library if intelligent - // loading is supported - loading = false - } else if (type === "move-component") { - const { componentId, destinationComponentId } = data - const rootComponent = get(currentAsset).props + try { + if (type === "select-component" && data.id) { + store.actions.components.select({ _id: data.id }) + } else if (type === "update-prop") { + await store.actions.components.updateProp(data.prop, data.value) + } else if (type === "delete-component" && data.id) { + confirmDeleteComponent(data.id) + } else if (type === "preview-loaded") { + // Wait for this event to show the client library if intelligent + // loading is supported + loading = false + } else if (type === "move-component") { + const { componentId, destinationComponentId } = data + const rootComponent = get(currentAsset).props - // Get source and destination components - const source = findComponent(rootComponent, componentId) - const destination = findComponent(rootComponent, destinationComponentId) + // Get source and destination components + const source = findComponent(rootComponent, componentId) + const destination = findComponent(rootComponent, destinationComponentId) - // Stop if the target is a child of source - const path = findComponentPath(source, destinationComponentId) - const ids = path.map(component => component._id) - if (ids.includes(data.destinationComponentId)) { - return + // Stop if the target is a child of source + const path = findComponentPath(source, destinationComponentId) + const ids = path.map(component => component._id) + if (ids.includes(data.destinationComponentId)) { + return + } + + // Cut and paste the component to the new destination + if (source && destination) { + store.actions.components.copy(source, true) + await store.actions.components.paste(destination, data.mode) + } + } else { + console.warn(`Client sent unknown event type: ${type}`) } - - // Cut and paste the component to the new destination - if (source && destination) { - store.actions.components.copy(source, true) - store.actions.components.paste(destination, data.mode) - } - } else { - console.warn(`Client sent unknown event type: ${type}`) + } catch (error) { + console.warn(error) + notifications.error("Error handling event from app preview") } } @@ -196,7 +201,7 @@ try { await store.actions.components.delete({ _id: idToDelete }) } catch (error) { - notifications.error(error) + notifications.error("Error deleting component") } idToDelete = null } diff --git a/packages/builder/src/components/design/AppPreview/ThemeEditor.svelte b/packages/builder/src/components/design/AppPreview/ThemeEditor.svelte index 14747191c2..e10ac4f0cd 100644 --- a/packages/builder/src/components/design/AppPreview/ThemeEditor.svelte +++ b/packages/builder/src/components/design/AppPreview/ThemeEditor.svelte @@ -9,6 +9,7 @@ Label, Select, Button, + notifications, } from "@budibase/bbui" import { store } from "builderStore" import AppThemeSelect from "./AppThemeSelect.svelte" @@ -43,23 +44,31 @@ ] const updateProperty = property => { - return e => { - store.actions.customTheme.save({ - ...get(store).customTheme, - [property]: e.detail, - }) + return async e => { + try { + store.actions.customTheme.save({ + ...get(store).customTheme, + [property]: e.detail, + }) + } catch (error) { + notifications.error("Error updating custom theme") + } } } const resetTheme = () => { - const theme = get(store).theme - store.actions.customTheme.save({ - ...defaultTheme, - navBackground: - theme === "spectrum--light" - ? "var(--spectrum-global-color-gray-50)" - : "var(--spectrum-global-color-gray-100)", - }) + try { + const theme = get(store).theme + store.actions.customTheme.save({ + ...defaultTheme, + navBackground: + theme === "spectrum--light" + ? "var(--spectrum-global-color-gray-50)" + : "var(--spectrum-global-color-gray-100)", + }) + } catch (error) { + notifications.error("Error saving custom theme") + } } diff --git a/packages/builder/src/components/design/NavigationPanel/ComponentDropdownMenu.svelte b/packages/builder/src/components/design/NavigationPanel/ComponentDropdownMenu.svelte index 8ab8c23134..0fcd43b58e 100644 --- a/packages/builder/src/components/design/NavigationPanel/ComponentDropdownMenu.svelte +++ b/packages/builder/src/components/design/NavigationPanel/ComponentDropdownMenu.svelte @@ -29,10 +29,14 @@ if (currentIndex === 0) { return } - const newChildren = parent._children.filter(c => c !== component) - newChildren.splice(currentIndex - 1, 0, component) - parent._children = newChildren - store.actions.preview.saveSelected() + try { + const newChildren = parent._children.filter(c => c !== component) + newChildren.splice(currentIndex - 1, 0, component) + parent._children = newChildren + store.actions.preview.saveSelected() + } catch (error) { + notifications.error("Error saving screen") + } } const moveDownComponent = () => { @@ -45,10 +49,14 @@ if (currentIndex === parent._children.length - 1) { return } - const newChildren = parent._children.filter(c => c !== component) - newChildren.splice(currentIndex + 1, 0, component) - parent._children = newChildren - store.actions.preview.saveSelected() + try { + const newChildren = parent._children.filter(c => c !== component) + newChildren.splice(currentIndex + 1, 0, component) + parent._children = newChildren + store.actions.preview.saveSelected() + } catch (error) { + notifications.error("Error saving screen") + } } const duplicateComponent = () => { @@ -60,7 +68,7 @@ try { await store.actions.components.delete(component) } catch (error) { - notifications.error(error) + notifications.error("Error deleting component") } } @@ -70,8 +78,12 @@ } const pasteComponent = (mode, preserveBindings = false) => { - // lives in store - also used by drag drop - store.actions.components.paste(component, mode, preserveBindings) + try { + // lives in store - also used by drag drop + store.actions.components.paste(component, mode, preserveBindings) + } catch (error) { + notifications.error("Error saving component") + } } diff --git a/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ComponentTree.svelte b/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ComponentTree.svelte index 910ffc18f8..c5dfd63cf9 100644 --- a/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ComponentTree.svelte +++ b/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ComponentTree.svelte @@ -4,6 +4,7 @@ import ComponentDropdownMenu from "../ComponentDropdownMenu.svelte" import NavItem from "components/common/NavItem.svelte" import { capitalise } from "helpers" + import { notifications } from "@budibase/bbui" export let components = [] export let currentComponent @@ -62,6 +63,14 @@ } closedNodes = closedNodes } + + const onDrop = async () => { + try { + await dragDropStore.actions.drop() + } catch (error) { + notifications.error("Error saving component") + } + }