diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index d1e373003..2a57d6f38 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 5b3282313..10fadc36c 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 e842da9d7..f51648074 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 969a38b46..69eba842a 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 8c9d44f20..766657769 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 2e453d1c5..9ea7df160 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 8b6f5564a..b6c757cb9 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 bb1b54ad7..000000000 --- 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 000000000..39df76d01 --- /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 e5a883ac8..ffa63ad67 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 eaced64e0..df403c0a2 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 c94d1520a..17ed12a13 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 59363fab5..000000000 --- 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 c8e2f5c60..000000000 --- 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 96334fa72..000000000 --- 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 bab1f25c0..000000000 --- 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 000000000..754f9f9be --- /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 000000000..9d03aaab1 --- /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 000000000..51b55cd49 --- /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 000000000..2fd64a9d6 --- /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 000000000..ff12e2f49 --- /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 5be19fdeb..4bd324364 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 642a8682f..599a10f91 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 a9e7e849d..39b194d35 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 5960d15e7..9ab9a4411 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 b89fdbe59..d98829c4b 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 c26ad1c19..d112ad859 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 bfc2e4a61..c7553cee5 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() - - const config = await getScopedConfig(db, { - type: Configs.GOOGLE, - workspace: ctx.query.workspace, - }) - - const publicConfig = await getScopedConfig(db, { - type: Configs.SETTINGS, - }) + // get the relevant config + const config = { + clientID: environment.GOOGLE_CLIENT_ID, + clientSecret: environment.GOOGLE_CLIENT_SECRET, + } - 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 f95c3a173..2149bd3e1 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 5de946fbc..a7570b12a 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 da4d405f0..8e5cc5468 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 b845a770e..50d0073a2 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 6b8022a36..d739e751c 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 7d5656a22..9298c4917 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 8a06082d5..ff750cecd 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 0998a5be2..aa6dbc93e 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 af345ddcd..661c12537 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 52402a039..f6cd6af75 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 45bc5ff33..c94e750c2 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 79ebdb102..9277207e3 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 97168358c..8f1218f08 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 cd6cef42d..8a34edade 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 1354c31b8..ae0023f68 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 b15746735..e18deab2d 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 f76c91e13..990aa736f 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 44cb37409..774aac067 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 328cd31f2..9a86554b4 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 1f3dea137..592ee4756 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 33fe8d532..9f2aae56c 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 36c2433c2..e93314234 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 bc2ad0d0a..717c55f05 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 0fb061fac..9707517c5 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 a1a5a7a24..cdab47db6 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 a968effaf..cabcea756 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 3dac5838a..2a886fab0 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) - store.actions.components.paste(destination, data.mode) + // 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}`) } - } 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 14747191c..e10ac4f0c 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 8ab8c2313..0fcd43b58 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 910ffc18f..c5dfd63cf 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") + } + }