Merge branch 'js-binding-drawer' of github.com:Budibase/budibase into js-binding-drawer
This commit is contained in:
commit
29659361fe
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
From opening a bug report to creating a pull request: every contribution is appreciated and welcome. If you're planning to implement a new feature or change the api please create an issue first. This way we can ensure that your precious work is not in vain.
|
From opening a bug report to creating a pull request: every contribution is appreciated and welcome. If you're planning to implement a new feature or change the api please create an issue first. This way we can ensure that your precious work is not in vain.
|
||||||
|
|
||||||
### Not Sure Where to Start?
|
## Not Sure Where to Start?
|
||||||
|
|
||||||
Budibase is a low-code web application builder that creates svelte based web applications.
|
Budibase is a low-code web application builder that creates svelte based web applications.
|
||||||
|
|
||||||
|
@ -14,6 +14,8 @@ Budibase is a monorepo managed by [lerna](https://github.com/lerna/lerna). Lerna
|
||||||
|
|
||||||
- **packages/server** - The budibase server. This [Koa](https://koajs.com/) app is responsible for serving the JS for the builder and budibase apps, as well as providing the API for interaction with the database and file system.
|
- **packages/server** - The budibase server. This [Koa](https://koajs.com/) app is responsible for serving the JS for the builder and budibase apps, as well as providing the API for interaction with the database and file system.
|
||||||
|
|
||||||
|
- **packages/worker** - This [Koa](https://koajs.com/) app is responsible for providing global apis for managing your budibase installation. Authentication, Users, Email, Org and Auth configs are all provided by the worker.
|
||||||
|
|
||||||
## Contributor License Agreement (CLA)
|
## Contributor License Agreement (CLA)
|
||||||
|
|
||||||
In order to accept your pull request, we need you to submit a CLA. You only need to do this once. If you are submitting a pull request for the first time, just submit a Pull Request and our CLA Bot will give you instructions on how to sign the CLA before merging your Pull Request.
|
In order to accept your pull request, we need you to submit a CLA. You only need to do this once. If you are submitting a pull request for the first time, just submit a Pull Request and our CLA Bot will give you instructions on how to sign the CLA before merging your Pull Request.
|
||||||
|
@ -62,8 +64,6 @@ A component is the basic frontend building block of a budibase app.
|
||||||
|
|
||||||
Component libraries are collections of components as well as the definition of their props contained in a file called `components.json`.
|
Component libraries are collections of components as well as the definition of their props contained in a file called `components.json`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Contributing to Budibase
|
## Contributing to Budibase
|
||||||
|
|
||||||
* Please maintain the existing code style.
|
* Please maintain the existing code style.
|
||||||
|
@ -74,31 +74,30 @@ Component libraries are collections of components as well as the definition of t
|
||||||
|
|
||||||
* If the project diverges from your branch, please rebase instead of merging. This makes the commit graph easier to read.
|
* If the project diverges from your branch, please rebase instead of merging. This makes the commit graph easier to read.
|
||||||
|
|
||||||
* Once your work is completed, please raise a PR against the main branch with some information about what has changed and why.
|
* Once your work is completed, please raise a PR against the `develop` branch with some information about what has changed and why.
|
||||||
|
|
||||||
### Getting Started For Contributors
|
### Getting Started For Contributors
|
||||||
|
#### 1. Prerequisites
|
||||||
### 1. Prerequisites
|
|
||||||
|
|
||||||
*yarn -* `npm install -g yarn`
|
*yarn -* `npm install -g yarn`
|
||||||
|
|
||||||
*jest* - `npm install -g jest`
|
*jest* - `npm install -g jest`
|
||||||
|
|
||||||
### 2. Clone this repository
|
#### 2. Clone this repository
|
||||||
|
|
||||||
`git clone https://github.com/Budibase/budibase.git`
|
`git clone https://github.com/Budibase/budibase.git`
|
||||||
|
|
||||||
then `cd ` into your local copy.
|
then `cd ` into your local copy.
|
||||||
|
|
||||||
### 3. Install and Build
|
#### 3. Install and Build
|
||||||
|
|
||||||
To develop the Budibase platform you'll need [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/) installed.
|
To develop the Budibase platform you'll need [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/) installed.
|
||||||
|
|
||||||
#### Quick method
|
##### Quick method
|
||||||
|
|
||||||
`yarn setup` will check that all necessary components are installed and setup the repo for usage.
|
`yarn setup` will check that all necessary components are installed and setup the repo for usage.
|
||||||
|
|
||||||
#### Manual method
|
##### Manual method
|
||||||
|
|
||||||
The following commands can be executed to manually get Budibase up and running (assuming Docker/Docker Compose has been installed).
|
The following commands can be executed to manually get Budibase up and running (assuming Docker/Docker Compose has been installed).
|
||||||
|
|
||||||
|
@ -108,15 +107,7 @@ The following commands can be executed to manually get Budibase up and running (
|
||||||
|
|
||||||
`yarn build` will build all budibase packages.
|
`yarn build` will build all budibase packages.
|
||||||
|
|
||||||
### 4. Initialising Budibase and Creating a Budibase App
|
#### 4. Running
|
||||||
|
|
||||||
Starting up the budibase electron app should initialise budibase for you. A Budibase apps folder will have been created in `~/.budibase`.
|
|
||||||
|
|
||||||
This is a blank apps folder, so you will need to create yourself an app, you can do this by clicking "Create New App" from the budibase builder.
|
|
||||||
|
|
||||||
This will create a new budibase application in the `~/.budibase/<your-app-uuid>` directory, and NPM install the component libraries for that application. Let's start building your app with the budibase builder!
|
|
||||||
|
|
||||||
### 4. Running
|
|
||||||
|
|
||||||
To run the budibase server and builder in dev mode (i.e. with live reloading):
|
To run the budibase server and builder in dev mode (i.e. with live reloading):
|
||||||
|
|
||||||
|
@ -126,7 +117,7 @@ To run the budibase server and builder in dev mode (i.e. with live reloading):
|
||||||
|
|
||||||
This will enable watch mode for both the builder app, server, client library and any component libraries.
|
This will enable watch mode for both the builder app, server, client library and any component libraries.
|
||||||
|
|
||||||
### 5. Debugging using VS Code
|
#### 5. Debugging using VS Code
|
||||||
|
|
||||||
To debug the budibase server and worker a VS Code launch configuration has been provided.
|
To debug the budibase server and worker a VS Code launch configuration has been provided.
|
||||||
|
|
||||||
|
@ -135,75 +126,90 @@ Alternatively to start both components simultaneously select `Start Budibase`.
|
||||||
|
|
||||||
In addition to the above, the remaining budibase components may be ran in dev mode using: `yarn dev:noserver`.
|
In addition to the above, the remaining budibase components may be ran in dev mode using: `yarn dev:noserver`.
|
||||||
|
|
||||||
### 6. Cleanup
|
#### 6. Cleanup
|
||||||
|
|
||||||
If you wish to delete all the apps created in development and reset the environment then run the following:
|
If you wish to delete all the apps created in development and reset the environment then run the following:
|
||||||
|
|
||||||
1. `yarn nuke:docker` will wipe all the Budibase services
|
1. `yarn nuke:docker` will wipe all the Budibase services
|
||||||
2. `yarn dev` will restart all the services
|
2. `yarn dev` will restart all the services
|
||||||
|
|
||||||
## Data Storage
|
### Backend
|
||||||
|
|
||||||
When you are running locally, budibase stores data on disk using [PouchDB](https://pouchdb.com/), as well as some JSON on local files. After setting up budibase, you can find all of this data in the `~/.budibase` directory.
|
|
||||||
|
|
||||||
A client can have one or more budibase applications. Budibase applications are stored in `~/.budibase/<app-uuid>`. Files used by your budibase application when running are stored in the `public` directory. Everything else is dev files used for the development of your apps in the builder.
|
|
||||||
|
|
||||||
#### Frontend
|
|
||||||
|
|
||||||
To see the current individual JSON definitions for your pages and screens used by the builder, have a look at `~/.budibase/<app-uuid>/pages`.
|
|
||||||
|
|
||||||
For your actual running application (not in dev), the frontend tree structure of the application (known as `clientFrontendDefinition`) is stored as JSON on disk. This is what the budibase client library reads to create your app at runtime. This can be found at `~/.budibase/<app-uuid>/public/clientFrontendDefinition.js`
|
|
||||||
|
|
||||||
The HTML and CSS for your apps runtime pages, as well as the budibase client library JS is stored at:
|
|
||||||
|
|
||||||
- `~/.budibase/<app-uuid>/public/main`
|
|
||||||
- `~/.budibase/<app-uuid>/public/unauthenticated`
|
|
||||||
|
|
||||||
#### 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 [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.
|
||||||
|
|
||||||
### Publishing Budibase to NPM
|
### Data Storage
|
||||||
|
|
||||||
#### Testing In Electron
|
When you are running locally, budibase stores data on disk using docker volumes. The volumes and the types of data associated with each are:
|
||||||
|
|
||||||
At budibase, we pride ourselves on giving our users a fast, native and slick local development experience. As a result, we use the electron to provide a native GUI for the budibase builder. In order to release budibase out into the wild, you should test your changes in a packaged electron application. To do this, first build budibase from the root directory.
|
- `redis_data`
|
||||||
|
- Sessions, email tokens
|
||||||
|
- `couchdb3_data`
|
||||||
|
- Global and app databases
|
||||||
|
- `minio_data`
|
||||||
|
- App manifest, budibase client, static assets
|
||||||
|
|
||||||
|
### Devlopment Modes
|
||||||
|
|
||||||
|
A combination of environment variables controls the mode that budibase runs in.
|
||||||
|
Yarn commands can be used to mimic the different modes that budibase can be ran in
|
||||||
|
|
||||||
|
#### Self Hosted
|
||||||
|
The default mode. A single tenant installation with no usage restrictions.
|
||||||
|
|
||||||
|
To enable this mode, use:
|
||||||
```
|
```
|
||||||
yarn build
|
yarn mode:self
|
||||||
```
|
```
|
||||||
|
|
||||||
Now everything is built, you can package up your electron application.
|
#### Cloud
|
||||||
|
The cloud mode, with account portal turned off.
|
||||||
|
|
||||||
|
To enable this mode, use:
|
||||||
```
|
```
|
||||||
cd packages/server
|
yarn mode:cloud
|
||||||
yarn build:electron
|
|
||||||
```
|
```
|
||||||
|
#### Cloud & Account
|
||||||
Your new electron application will be stored in `packages/server/dist/<operating-system>`. Open up the executable and make sure everything is working smoothly.
|
The cloud mode, with account portal turned on. This is a replica of the mode that runs at https://budibase.app
|
||||||
|
|
||||||
|
|
||||||
#### Publishing to NPM
|
To enable this mode, use:
|
||||||
|
|
||||||
Once you are happy that your changes work in electron, you can publish all the latest versions of the monorepo packages by running:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
yarn publishnpm
|
yarn mode:account
|
||||||
```
|
```
|
||||||
|
### CI
|
||||||
|
|
||||||
from your root directory.
|
#### PR Job
|
||||||
|
|
||||||
#### CI Release
|
After your pr is submitted a github action (can be found at `.github/workflows/budibase_ci.yml`) will run to perform some checks against the changes such as linting, build and test.
|
||||||
|
|
||||||
After NPM has successfully published the budibase packages, a new tag will be pushed to master. This will kick off a github action (can be found at `.github/workflows/release.yml`) this will build and package the electron application for every OS (Windows, Mac, Linux). The binaries will be stored under the new tag on the [budibase releases page](https://github.com/Budibase/budibase/releases).
|
The job will run when changes are pushed to or targetted at `master` and `develop`
|
||||||
|
#### Release Develop
|
||||||
|
|
||||||
|
To test changes before a release, a prerelease action (can be found at `.github/workflows/release-develop.yml`) will run to build and release develop versions of npm packages and docker images. On each subsequent commit to develop a new alpha version of npm packages will be created and released.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
- `feature1` -> `develop` = `v0.9.160-alpha.1`
|
||||||
|
- `feature2` -> `develop` = `v0.9.160-alpha.0`
|
||||||
|
|
||||||
|
The job will run when changes are pushed to `develop`
|
||||||
|
#### Release Job
|
||||||
|
|
||||||
|
To release changes a release job (can be found at `.github/workflows/release.yml`) will run to create final versions of npm packages and docker images.
|
||||||
|
|
||||||
|
Following the example above:
|
||||||
|
|
||||||
|
- `develop` -> `master` = `v0.9.160`
|
||||||
|
|
||||||
|
The job will run when changes are pushed to `master`
|
||||||
|
|
||||||
|
#### Release Self Host Job
|
||||||
|
|
||||||
|
To release the self hosted version of docker images, an additional job (can be found at `.github/workflows/release-selfhost.yml`) must be ran manually. This will releaae docker images to docker hub under the tag `latest` to be picked up by self hosted installations.
|
||||||
|
|
||||||
### Troubleshooting
|
### Troubleshooting
|
||||||
|
|
||||||
Sometimes, things go wrong. This can be due to incompatible updates on the budibase platform. To clear down your development environment and start again:
|
Sometimes, things go wrong. This can be due to incompatible updates on the budibase platform. To clear down your development environment and start again follow **Step 6. Cleanup**, then proceed from **Step 3. Install and Build** in the setup guide above. You should have a fresh Budibase installation.
|
||||||
|
|
||||||
```
|
|
||||||
rm -rf ~/.budibase
|
|
||||||
```
|
|
||||||
Follow from **Step 3. Install and Build** in the setup guide above. You should have a fresh Budibase installation.
|
|
||||||
|
|
||||||
### Running tests
|
### Running tests
|
||||||
|
|
||||||
#### End-to-end Tests
|
#### End-to-end Tests
|
||||||
|
|
Binary file not shown.
|
@ -1,9 +1,35 @@
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
entries:
|
entries:
|
||||||
budibase:
|
budibase:
|
||||||
|
- apiVersion: v2
|
||||||
|
appVersion: 0.9.163
|
||||||
|
created: "2021-10-12T21:58:00.515555+01:00"
|
||||||
|
dependencies:
|
||||||
|
- condition: services.couchdb.enabled
|
||||||
|
name: couchdb
|
||||||
|
repository: https://apache.github.io/couchdb-helm
|
||||||
|
version: 3.3.4
|
||||||
|
- condition: ingress.nginx
|
||||||
|
name: ingress-nginx
|
||||||
|
repository: https://github.com/kubernetes/ingress-nginx
|
||||||
|
version: 3.35.0
|
||||||
|
description: Budibase is an open source low-code platform, helping thousands of teams build apps for their workplace in minutes.
|
||||||
|
digest: f369536c0eac1f6959d51e8ce6d74a87a7a9df29ae84fb9cbed0a273ab77429b
|
||||||
|
keywords:
|
||||||
|
- low-code
|
||||||
|
- database
|
||||||
|
- cluster
|
||||||
|
name: budibase
|
||||||
|
sources:
|
||||||
|
- https://github.com/Budibase/budibase
|
||||||
|
- https://budibase.com
|
||||||
|
type: application
|
||||||
|
urls:
|
||||||
|
- https://budibase.github.io/budibase/budibase-0.2.0.tgz
|
||||||
|
version: 0.2.0
|
||||||
- apiVersion: v2
|
- apiVersion: v2
|
||||||
appVersion: 0.9.56
|
appVersion: 0.9.56
|
||||||
created: "2021-08-18T18:41:52.640176+01:00"
|
created: "2021-10-12T21:58:00.512062+01:00"
|
||||||
dependencies:
|
dependencies:
|
||||||
- condition: services.couchdb.enabled
|
- condition: services.couchdb.enabled
|
||||||
name: couchdb
|
name: couchdb
|
||||||
|
@ -28,7 +54,7 @@ entries:
|
||||||
version: 0.1.1
|
version: 0.1.1
|
||||||
- apiVersion: v2
|
- apiVersion: v2
|
||||||
appVersion: 0.9.56
|
appVersion: 0.9.56
|
||||||
created: "2021-08-18T18:41:52.635603+01:00"
|
created: "2021-10-12T21:58:00.507257+01:00"
|
||||||
dependencies:
|
dependencies:
|
||||||
- condition: services.couchdb.enabled
|
- condition: services.couchdb.enabled
|
||||||
name: couchdb
|
name: couchdb
|
||||||
|
@ -51,4 +77,4 @@ entries:
|
||||||
urls:
|
urls:
|
||||||
- https://budibase.github.io/budibase/budibase-0.1.0.tgz
|
- https://budibase.github.io/budibase/budibase-0.1.0.tgz
|
||||||
version: 0.1.0
|
version: 0.1.0
|
||||||
generated: "2021-08-18T18:41:52.629415+01:00"
|
generated: "2021-10-12T21:58:00.503447+01:00"
|
||||||
|
|
|
@ -22,13 +22,13 @@ type: application
|
||||||
# This is the chart version. This version number should be incremented each time you make changes
|
# This is the chart version. This version number should be incremented each time you make changes
|
||||||
# to the chart and its templates, including the app version.
|
# to the chart and its templates, including the app version.
|
||||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||||
version: 0.1.1
|
version: 0.2.0
|
||||||
|
|
||||||
# This is the version number of the application being deployed. This version number should be
|
# This is the version number of the application being deployed. This version number should be
|
||||||
# incremented each time you make changes to the application. Versions are not expected to
|
# incremented each time you make changes to the application. Versions are not expected to
|
||||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||||
# It is recommended to use it with quotes.
|
# It is recommended to use it with quotes.
|
||||||
appVersion: "0.9.56"
|
appVersion: "0.9.163"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
- name: couchdb
|
- name: couchdb
|
||||||
|
@ -37,5 +37,5 @@ dependencies:
|
||||||
condition: services.couchdb.enabled
|
condition: services.couchdb.enabled
|
||||||
- name: ingress-nginx
|
- name: ingress-nginx
|
||||||
version: 3.35.0
|
version: 3.35.0
|
||||||
repository: https://kubernetes.github.io/ingress-nginx
|
repository: https://github.com/kubernetes/ingress-nginx
|
||||||
condition: services.ingress.nginx
|
condition: ingress.nginx
|
||||||
|
|
|
@ -7,6 +7,8 @@ metadata:
|
||||||
kubernetes.io/ingress.class: alb
|
kubernetes.io/ingress.class: alb
|
||||||
alb.ingress.kubernetes.io/scheme: internet-facing
|
alb.ingress.kubernetes.io/scheme: internet-facing
|
||||||
alb.ingress.kubernetes.io/target-type: ip
|
alb.ingress.kubernetes.io/target-type: ip
|
||||||
|
alb.ingress.kubernetes.io/success-codes: 200,301
|
||||||
|
alb.ingress.kubernetes.io/healthcheck-path: /
|
||||||
{{- if .Values.ingress.certificateArn }}
|
{{- if .Values.ingress.certificateArn }}
|
||||||
alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}'
|
alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}'
|
||||||
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
|
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
|
||||||
|
|
|
@ -14,7 +14,7 @@ spec:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
io.kompose.service: app-service
|
io.kompose.service: app-service
|
||||||
strategy:
|
strategy:
|
||||||
type: Recreate
|
type: RollingUpdate
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
|
@ -73,13 +73,11 @@ spec:
|
||||||
name: {{ template "budibase.fullname" . }}
|
name: {{ template "budibase.fullname" . }}
|
||||||
key: objectStoreSecret
|
key: objectStoreSecret
|
||||||
- name: MINIO_URL
|
- name: MINIO_URL
|
||||||
{{ if .Values.services.objectStore.url }}
|
|
||||||
value: {{ .Values.services.objectStore.url }}
|
value: {{ .Values.services.objectStore.url }}
|
||||||
{{ else }}
|
|
||||||
value: http://minio-service:{{ .Values.services.objectStore.port }}
|
|
||||||
{{ end }}
|
|
||||||
- name: PORT
|
- name: PORT
|
||||||
value: {{ .Values.services.apps.port | quote }}
|
value: {{ .Values.services.apps.port | quote }}
|
||||||
|
- name: MULTI_TENANCY
|
||||||
|
value: "1"
|
||||||
- name: REDIS_PASSWORD
|
- name: REDIS_PASSWORD
|
||||||
value: {{ .Values.services.redis.password }}
|
value: {{ .Values.services.redis.password }}
|
||||||
- name: REDIS_URL
|
- name: REDIS_URL
|
||||||
|
@ -92,14 +90,20 @@ spec:
|
||||||
value: {{ .Values.globals.selfHosted | quote }}
|
value: {{ .Values.globals.selfHosted | quote }}
|
||||||
- name: SENTRY_DSN
|
- name: SENTRY_DSN
|
||||||
value: {{ .Values.globals.sentryDSN }}
|
value: {{ .Values.globals.sentryDSN }}
|
||||||
|
- name: POSTHOG_TOKEN
|
||||||
|
value: {{ .Values.globals.posthogToken }}
|
||||||
- name: WORKER_URL
|
- name: WORKER_URL
|
||||||
value: worker-service:{{ .Values.services.worker.port }}
|
value: http://worker-service:{{ .Values.services.worker.port }}
|
||||||
- name: COOKIE_DOMAIN
|
- name: PLATFORM_URL
|
||||||
value: {{ .Values.globals.cookieDomain | quote }}
|
value: {{ .Values.globals.platformUrl | quote }}
|
||||||
|
- name: USE_QUOTAS
|
||||||
|
value: "1"
|
||||||
- name: ACCOUNT_PORTAL_URL
|
- name: ACCOUNT_PORTAL_URL
|
||||||
value: {{ .Values.globals.accountPortalUrl | quote }}
|
value: {{ .Values.globals.accountPortalUrl | quote }}
|
||||||
- name: ACCOUNT_PORTAL_API_KEY
|
- name: ACCOUNT_PORTAL_API_KEY
|
||||||
value: {{ .Values.globals.accountPortalApiKey | quote }}
|
value: {{ .Values.globals.accountPortalApiKey | quote }}
|
||||||
|
- name: COOKIE_DOMAIN
|
||||||
|
value: {{ .Values.globals.cookieDomain | quote }}
|
||||||
image: budibase/apps
|
image: budibase/apps
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
name: bbapps
|
name: bbapps
|
||||||
|
|
|
@ -14,7 +14,7 @@ spec:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app.kubernetes.io/name: budibase-proxy
|
app.kubernetes.io/name: budibase-proxy
|
||||||
strategy:
|
strategy:
|
||||||
type: Recreate
|
type: RollingUpdate
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
|
@ -26,7 +26,7 @@ spec:
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- image: budibase/proxy
|
- image: budibase/proxy
|
||||||
imagePullPolicy: ""
|
imagePullPolicy: Always
|
||||||
name: proxy-service
|
name: proxy-service
|
||||||
ports:
|
ports:
|
||||||
- containerPort: {{ .Values.services.proxy.port }}
|
- containerPort: {{ .Values.services.proxy.port }}
|
||||||
|
|
|
@ -15,7 +15,7 @@ spec:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
io.kompose.service: worker-service
|
io.kompose.service: worker-service
|
||||||
strategy:
|
strategy:
|
||||||
type: Recreate
|
type: RollingUpdate
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
|
@ -70,13 +70,11 @@ spec:
|
||||||
name: {{ template "budibase.fullname" . }}
|
name: {{ template "budibase.fullname" . }}
|
||||||
key: objectStoreSecret
|
key: objectStoreSecret
|
||||||
- name: MINIO_URL
|
- name: MINIO_URL
|
||||||
{{ if .Values.services.objectStore.url }}
|
|
||||||
value: {{ .Values.services.objectStore.url }}
|
value: {{ .Values.services.objectStore.url }}
|
||||||
{{ else }}
|
|
||||||
value: http://minio-service:{{ .Values.services.objectStore.port }}
|
|
||||||
{{ end }}
|
|
||||||
- name: PORT
|
- name: PORT
|
||||||
value: {{ .Values.services.worker.port | quote }}
|
value: {{ .Values.services.worker.port | quote }}
|
||||||
|
- name: MULTI_TENANCY
|
||||||
|
value: "1"
|
||||||
- name: REDIS_PASSWORD
|
- name: REDIS_PASSWORD
|
||||||
value: {{ .Values.services.redis.password | quote }}
|
value: {{ .Values.services.redis.password | quote }}
|
||||||
- name: REDIS_URL
|
- name: REDIS_URL
|
||||||
|
@ -91,8 +89,22 @@ spec:
|
||||||
value: {{ .Values.globals.accountPortalUrl | quote }}
|
value: {{ .Values.globals.accountPortalUrl | quote }}
|
||||||
- name: ACCOUNT_PORTAL_API_KEY
|
- name: ACCOUNT_PORTAL_API_KEY
|
||||||
value: {{ .Values.globals.accountPortalApiKey | quote }}
|
value: {{ .Values.globals.accountPortalApiKey | quote }}
|
||||||
|
- name: PLATFORM_URL
|
||||||
|
value: {{ .Values.globals.platformUrl | quote }}
|
||||||
- name: COOKIE_DOMAIN
|
- name: COOKIE_DOMAIN
|
||||||
value: {{ .Values.globals.cookieDomain | quote }}
|
value: {{ .Values.globals.cookieDomain | quote }}
|
||||||
|
- name: SMTP_FALLBACK_ENABLED
|
||||||
|
value: {{ .Values.globals.smtp.enabled | quote }}
|
||||||
|
- name: SMTP_USER
|
||||||
|
value: {{ .Values.globals.smtp.user | quote }}
|
||||||
|
- name: SMTP_PASSWORD
|
||||||
|
value: {{ .Values.globals.smtp.password | quote }}
|
||||||
|
- name: SMTP_HOST
|
||||||
|
value: {{ .Values.globals.smtp.host | quote }}
|
||||||
|
- name: SMTP_PORT
|
||||||
|
value: {{ .Values.globals.smtp.port | quote }}
|
||||||
|
- name: SMTP_FROM_ADDRESS
|
||||||
|
value: {{ .Values.globals.smtp.from | quote }}
|
||||||
image: budibase/worker
|
image: budibase/worker
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
name: bbworker
|
name: bbworker
|
||||||
|
|
|
@ -40,11 +40,12 @@ service:
|
||||||
port: 10000
|
port: 10000
|
||||||
|
|
||||||
ingress:
|
ingress:
|
||||||
enabled: true
|
enabled: false
|
||||||
nginx: true
|
aws: false
|
||||||
certificateArn: ""
|
nginx: true
|
||||||
|
certificateArn: ""
|
||||||
className: ""
|
className: ""
|
||||||
annotations:
|
annotations:
|
||||||
kubernetes.io/ingress.class: nginx
|
kubernetes.io/ingress.class: nginx
|
||||||
hosts:
|
hosts:
|
||||||
- host: # change if using custom domain
|
- host: # change if using custom domain
|
||||||
|
@ -55,7 +56,7 @@ ingress:
|
||||||
service:
|
service:
|
||||||
name: proxy-service
|
name: proxy-service
|
||||||
port:
|
port:
|
||||||
number: 10000
|
number: 10000
|
||||||
|
|
||||||
resources: {}
|
resources: {}
|
||||||
# We usually recommend not to specify default resources and to leave this as a conscious
|
# We usually recommend not to specify default resources and to leave this as a conscious
|
||||||
|
@ -84,20 +85,24 @@ affinity: {}
|
||||||
|
|
||||||
globals:
|
globals:
|
||||||
budibaseEnv: PRODUCTION
|
budibaseEnv: PRODUCTION
|
||||||
enableAnalytics: false
|
enableAnalytics: true
|
||||||
posthogToken: ""
|
|
||||||
sentryDSN: ""
|
sentryDSN: ""
|
||||||
|
posthogToken: ""
|
||||||
logLevel: info
|
logLevel: info
|
||||||
selfHosted: 1
|
selfHosted: ""
|
||||||
accountPortalUrL: ""
|
accountPortalUrl: ""
|
||||||
accountPortalApiKey: ""
|
accountPortalApiKey: ""
|
||||||
cookieDomain: ""
|
cookieDomain: ""
|
||||||
|
platformUrl: ""
|
||||||
|
|
||||||
createSecrets: true # creates an internal API key, JWT secrets and redis password for you
|
createSecrets: true # creates an internal API key, JWT secrets and redis password for you
|
||||||
|
|
||||||
# if createSecrets is set to false, you can hard-code your secrets here
|
# if createSecrets is set to false, you can hard-code your secrets here
|
||||||
internalApiKey: ""
|
internalApiKey: ""
|
||||||
jwtSecret: ""
|
jwtSecret: ""
|
||||||
|
|
||||||
|
smtp:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
services:
|
services:
|
||||||
dns: cluster.local
|
dns: cluster.local
|
||||||
|
@ -118,12 +123,12 @@ services:
|
||||||
couchdb:
|
couchdb:
|
||||||
enabled: true
|
enabled: true
|
||||||
replicaCount: 3
|
replicaCount: 3
|
||||||
url: "" # only change if pointing to existing couch server
|
# url: "" # only change if pointing to existing couch server
|
||||||
user: "" # only change if pointing to existing couch server
|
# user: "" # only change if pointing to existing couch server
|
||||||
password: "" # only change if pointing to existing couch server
|
# password: "" # only change if pointing to existing couch server
|
||||||
port: 5984
|
port: 5984
|
||||||
storage: 100Mi
|
storage: 100Mi
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
enabled: true # disable if using external redis
|
enabled: true # disable if using external redis
|
||||||
port: 6379
|
port: 6379
|
||||||
|
@ -131,7 +136,7 @@ services:
|
||||||
url: "" # only change if pointing to existing redis cluster and enabled: false
|
url: "" # only change if pointing to existing redis cluster and enabled: false
|
||||||
password: "budibase" # recommended to override if using built-in redis
|
password: "budibase" # recommended to override if using built-in redis
|
||||||
storage: 100Mi
|
storage: 100Mi
|
||||||
|
|
||||||
objectStore:
|
objectStore:
|
||||||
minio: true
|
minio: true
|
||||||
browser: true
|
browser: true
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "0.9.160-alpha.0",
|
"version": "0.9.166",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
18
package.json
18
package.json
|
@ -32,6 +32,7 @@
|
||||||
"kill-port": "kill-port 4001",
|
"kill-port": "kill-port 4001",
|
||||||
"dev": "yarn run kill-port && lerna link && lerna run --parallel dev:builder --concurrency 1",
|
"dev": "yarn run kill-port && lerna link && lerna run --parallel dev:builder --concurrency 1",
|
||||||
"dev:noserver": "lerna link && lerna run dev:stack:up && lerna run --parallel dev:builder --concurrency 1 --ignore @budibase/server --ignore @budibase/worker",
|
"dev:noserver": "lerna link && lerna run dev:stack:up && lerna run --parallel dev:builder --concurrency 1 --ignore @budibase/server --ignore @budibase/worker",
|
||||||
|
"dev:server": "lerna run --parallel dev:builder --concurrency 1 --scope @budibase/worker --scope @budibase/server",
|
||||||
"test": "lerna run test",
|
"test": "lerna run test",
|
||||||
"lint:eslint": "eslint packages",
|
"lint:eslint": "eslint packages",
|
||||||
"lint:prettier": "prettier --check \"packages/**/*.{js,svelte}\"",
|
"lint:prettier": "prettier --check \"packages/**/*.{js,svelte}\"",
|
||||||
|
@ -46,12 +47,17 @@
|
||||||
"build:docker:production": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh $BUDIBASE_RELEASE_VERSION release && cd -",
|
"build:docker:production": "lerna run build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh $BUDIBASE_RELEASE_VERSION release && 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 && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh develop && cd -",
|
||||||
"release:helm": "./scripts/release_helm_chart.sh",
|
"release:helm": "./scripts/release_helm_chart.sh",
|
||||||
"multi:enable": "lerna run multi:enable",
|
"env:multi:enable": "lerna run env:multi:enable",
|
||||||
"multi:disable": "lerna run multi:disable",
|
"env:multi:disable": "lerna run env:multi:disable",
|
||||||
"selfhost:enable": "lerna run selfhost:enable",
|
"env:selfhost:enable": "lerna run env:selfhost:enable",
|
||||||
"selfhost:disable": "lerna run selfhost:disable",
|
"env:selfhost:disable": "lerna run env:selfhost:disable",
|
||||||
"localdomain:enable": "lerna run localdomain:enable",
|
"env:localdomain:enable": "lerna run env:localdomain:enable",
|
||||||
"localdomain:disable": "lerna run localdomain:disable",
|
"env:localdomain:disable": "lerna run env:localdomain:disable",
|
||||||
|
"env:account:enable": "lerna run env:account:enable",
|
||||||
|
"env:account:disable": "lerna run env:account:disable",
|
||||||
|
"mode:self": "yarn env:selfhost:enable && yarn env:multi:disable && yarn env:account:disable",
|
||||||
|
"mode:cloud": "yarn env:selfhost:disable && yarn env:multi:enable && yarn env:account:disable",
|
||||||
|
"mode:account": "yarn mode:cloud && yarn env:account:enable",
|
||||||
"postinstall": "husky install"
|
"postinstall": "husky install"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/auth",
|
"name": "@budibase/auth",
|
||||||
"version": "0.9.160-alpha.0",
|
"version": "0.9.166",
|
||||||
"description": "Authentication middlewares for budibase builder and apps",
|
"description": "Authentication middlewares for budibase builder and apps",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
|
|
|
@ -42,8 +42,9 @@ module.exports = (
|
||||||
internal = false
|
internal = false
|
||||||
if (authCookie) {
|
if (authCookie) {
|
||||||
let error = null
|
let error = null
|
||||||
const sessionId = authCookie.sessionId,
|
const sessionId = authCookie.sessionId
|
||||||
userId = authCookie.userId
|
const userId = authCookie.userId
|
||||||
|
|
||||||
const session = await getSession(userId, sessionId)
|
const session = await getSession(userId, sessionId)
|
||||||
if (!session) {
|
if (!session) {
|
||||||
error = "No session found"
|
error = "No session found"
|
||||||
|
|
|
@ -24,17 +24,24 @@ exports.createASession = async (userId, session) => {
|
||||||
await client.store(makeSessionID(userId, sessionId), session, EXPIRY_SECONDS)
|
await client.store(makeSessionID(userId, sessionId), session, EXPIRY_SECONDS)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.invalidateSessions = async (userId, sessionId = null) => {
|
exports.invalidateSessions = async (userId, sessionIds = null) => {
|
||||||
let sessions = []
|
let sessions = []
|
||||||
if (sessionId) {
|
|
||||||
sessions.push({ key: makeSessionID(userId, sessionId) })
|
// If no sessionIds, get all the sessions for the user
|
||||||
} else {
|
if (!sessionIds) {
|
||||||
sessions = await getSessionsForUser(userId)
|
sessions = await getSessionsForUser(userId)
|
||||||
sessions.forEach(
|
sessions.forEach(
|
||||||
session =>
|
session =>
|
||||||
(session.key = makeSessionID(session.userId, session.sessionId))
|
(session.key = makeSessionID(session.userId, session.sessionId))
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
// use the passed array of sessionIds
|
||||||
|
sessions = Array.isArray(sessionIds) ? sessionIds : [sessionIds]
|
||||||
|
sessions = sessions.map(sessionId => ({
|
||||||
|
key: makeSessionID(userId, sessionId),
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = await redis.getSessionClient()
|
const client = await redis.getSessionClient()
|
||||||
const promises = []
|
const promises = []
|
||||||
for (let session of sessions) {
|
for (let session of sessions) {
|
||||||
|
|
|
@ -7,7 +7,7 @@ const {
|
||||||
const jwt = require("jsonwebtoken")
|
const jwt = require("jsonwebtoken")
|
||||||
const { options } = require("./middleware/passport/jwt")
|
const { options } = require("./middleware/passport/jwt")
|
||||||
const { createUserEmailView } = require("./db/views")
|
const { createUserEmailView } = require("./db/views")
|
||||||
const { Headers, UserStatus } = require("./constants")
|
const { Headers, UserStatus, Cookies } = require("./constants")
|
||||||
const {
|
const {
|
||||||
getGlobalDB,
|
getGlobalDB,
|
||||||
updateTenantId,
|
updateTenantId,
|
||||||
|
@ -19,6 +19,7 @@ const accounts = require("./cloud/accounts")
|
||||||
const { hash } = require("./hashing")
|
const { hash } = require("./hashing")
|
||||||
const userCache = require("./cache/user")
|
const userCache = require("./cache/user")
|
||||||
const env = require("./environment")
|
const env = require("./environment")
|
||||||
|
const { getUserSessions, invalidateSessions } = require("./security/sessions")
|
||||||
|
|
||||||
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
||||||
|
|
||||||
|
@ -235,3 +236,28 @@ exports.saveUser = async (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs a user out from budibase. Re-used across account portal and builder.
|
||||||
|
*/
|
||||||
|
exports.platformLogout = async ({ ctx, userId, keepActiveSession }) => {
|
||||||
|
if (!ctx) throw new Error("Koa context must be supplied to logout.")
|
||||||
|
|
||||||
|
const currentSession = this.getCookie(ctx, Cookies.Auth)
|
||||||
|
let sessions = await getUserSessions(userId)
|
||||||
|
|
||||||
|
if (keepActiveSession) {
|
||||||
|
sessions = sessions.filter(
|
||||||
|
session => session.sessionId !== currentSession.sessionId
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// clear cookies
|
||||||
|
this.clearCookie(ctx, Cookies.Auth)
|
||||||
|
this.clearCookie(ctx, Cookies.CurrentApp)
|
||||||
|
}
|
||||||
|
|
||||||
|
await invalidateSessions(
|
||||||
|
userId,
|
||||||
|
sessions.map(({ sessionId }) => sessionId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/bbui",
|
"name": "@budibase/bbui",
|
||||||
"description": "A UI solution used in the different Budibase projects.",
|
"description": "A UI solution used in the different Budibase projects.",
|
||||||
"version": "0.9.160-alpha.0",
|
"version": "0.9.166",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"module": "dist/bbui.es.js",
|
"module": "dist/bbui.es.js",
|
||||||
|
|
|
@ -5,11 +5,14 @@
|
||||||
import RelationshipRenderer from "./RelationshipRenderer.svelte"
|
import RelationshipRenderer from "./RelationshipRenderer.svelte"
|
||||||
import AttachmentRenderer from "./AttachmentRenderer.svelte"
|
import AttachmentRenderer from "./AttachmentRenderer.svelte"
|
||||||
import ArrayRenderer from "./ArrayRenderer.svelte"
|
import ArrayRenderer from "./ArrayRenderer.svelte"
|
||||||
|
import InternalRenderer from "./InternalRenderer.svelte"
|
||||||
|
|
||||||
export let row
|
export let row
|
||||||
export let schema
|
export let schema
|
||||||
export let value
|
export let value
|
||||||
export let customRenderers = []
|
export let customRenderers = []
|
||||||
|
|
||||||
|
let renderer
|
||||||
const typeMap = {
|
const typeMap = {
|
||||||
boolean: BooleanRenderer,
|
boolean: BooleanRenderer,
|
||||||
datetime: DateTimeRenderer,
|
datetime: DateTimeRenderer,
|
||||||
|
@ -20,7 +23,9 @@
|
||||||
number: StringRenderer,
|
number: StringRenderer,
|
||||||
longform: StringRenderer,
|
longform: StringRenderer,
|
||||||
array: ArrayRenderer,
|
array: ArrayRenderer,
|
||||||
|
internal: InternalRenderer,
|
||||||
}
|
}
|
||||||
|
|
||||||
$: type = schema?.type ?? "string"
|
$: type = schema?.type ?? "string"
|
||||||
$: customRenderer = customRenderers?.find(x => x.column === schema?.name)
|
$: customRenderer = customRenderers?.find(x => x.column === schema?.name)
|
||||||
$: renderer = customRenderer?.component ?? typeMap[type] ?? StringRenderer
|
$: renderer = customRenderer?.component ?? typeMap[type] ?? StringRenderer
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
<script>
|
||||||
|
import Icon from "../Icon/Icon.svelte"
|
||||||
|
import { notifications } from "../Stores/notifications"
|
||||||
|
export let value
|
||||||
|
|
||||||
|
const onClick = e => {
|
||||||
|
e.stopPropagation()
|
||||||
|
copyToClipboard(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyToClipboard(value) {
|
||||||
|
navigator.clipboard.writeText(value).then(() => {
|
||||||
|
notifications.success("Copied")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div on:click|stopPropagation={onClick}>
|
||||||
|
<Icon size="S" name="Copy" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,41 @@
|
||||||
|
context("Add Multi-Option Datatype", () => {
|
||||||
|
before(() => {
|
||||||
|
cy.login()
|
||||||
|
cy.createTestApp()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should create a new table, with data", () => {
|
||||||
|
cy.createTable("Multi Data")
|
||||||
|
cy.addColumn("Multi Data", "Test Data", "Multi-select", "1\n2\n3\n4\n5")
|
||||||
|
cy.addRowMultiValue(["1", "2", "3", "4", "5"])
|
||||||
|
})
|
||||||
|
|
||||||
|
it ("should add form with multi select picker, containing 5 options", () => {
|
||||||
|
cy.navigateToFrontend()
|
||||||
|
cy.wait(500)
|
||||||
|
// Add data provider
|
||||||
|
cy.get(`[data-cy="category-Data Provider"]`).click()
|
||||||
|
cy.get('[data-cy="dataSource-prop-control"]').click()
|
||||||
|
cy.get(".dropdown").contains("Multi Data").click()
|
||||||
|
cy.wait(500)
|
||||||
|
// Add Form with schema to match table
|
||||||
|
cy.addComponent("Form", "Form")
|
||||||
|
cy.get('[data-cy="dataSource-prop-control"').click()
|
||||||
|
cy.get(".dropdown").contains("Multi Data").click()
|
||||||
|
cy.wait(500)
|
||||||
|
// Add multi-select picker to form
|
||||||
|
cy.addComponent("Form", "Multi-select Picker").then((componentId) => {
|
||||||
|
cy.get('[data-cy="field-prop-control"]').type("Test Data").type('{enter}')
|
||||||
|
cy.wait(1000)
|
||||||
|
cy.getComponent(componentId).contains("Choose some options").click()
|
||||||
|
// Check picker has 5 items
|
||||||
|
cy.getComponent(componentId).find('li').should('have.length', 5)
|
||||||
|
// Select all items
|
||||||
|
for (let i = 1; i < 6; i++) {
|
||||||
|
cy.getComponent(componentId).find('li').contains(i).click()
|
||||||
|
}
|
||||||
|
// Check items have been selected
|
||||||
|
cy.getComponent(componentId).find('.spectrum-Picker-label').contains("(5)")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,35 @@
|
||||||
|
context("Add Radio Buttons", () => {
|
||||||
|
before(() => {
|
||||||
|
cy.login()
|
||||||
|
cy.createTestApp()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should add Radio Buttons options picker on form, add data, and confirm", () => {
|
||||||
|
cy.navigateToFrontend()
|
||||||
|
cy.addComponent("Form", "Form")
|
||||||
|
cy.addComponent("Form", "Options Picker").then((componentId) => {
|
||||||
|
// Provide field setting
|
||||||
|
cy.get(`[data-cy="field-prop-control"]`).type("1")
|
||||||
|
// Open dropdown and select Radio buttons
|
||||||
|
cy.get(`[data-cy="optionsType-prop-control"]`).click().then(() => {
|
||||||
|
cy.get('.spectrum-Popover').contains('Radio buttons')
|
||||||
|
.wait(500)
|
||||||
|
.click()
|
||||||
|
})
|
||||||
|
const radioButtonsTotal = 3
|
||||||
|
// Add values and confirm total
|
||||||
|
addRadioButtonData(radioButtonsTotal)
|
||||||
|
cy.getComponent(componentId).find('[type="radio"]')
|
||||||
|
.should('have.length', radioButtonsTotal)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const addRadioButtonData = (totalRadioButtons) => {
|
||||||
|
cy.get(`[data-cy="optionsSource-prop-control"]`).click().then(() => {
|
||||||
|
cy.get('.spectrum-Popover').contains('Custom')
|
||||||
|
.wait(500)
|
||||||
|
.click()
|
||||||
|
})
|
||||||
|
cy.addCustomSourceOptions(totalRadioButtons)
|
||||||
|
}
|
||||||
|
})
|
|
@ -31,15 +31,16 @@ context("Create a Table", () => {
|
||||||
cy.contains("nameupdated ").should("contain", "nameupdated")
|
cy.contains("nameupdated ").should("contain", "nameupdated")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
it("edits a row", () => {
|
it("edits a row", () => {
|
||||||
cy.contains("button", "Edit").click({ force: true })
|
cy.contains("button", "Edit").click({ force: true })
|
||||||
cy.wait(1000)
|
cy.wait(1000)
|
||||||
cy.get(".spectrum-Modal input").clear()
|
cy.get(".spectrum-Modal input").clear()
|
||||||
cy.get(".spectrum-Modal input").type("RoverUpdated")
|
cy.get(".spectrum-Modal input").type("Updated")
|
||||||
cy.contains("Save").click()
|
cy.contains("Save").click()
|
||||||
cy.contains("RoverUpdated").should("have.text", "RoverUpdated")
|
cy.contains("Updated").should("have.text", "Updated")
|
||||||
})
|
})
|
||||||
|
*/
|
||||||
it("deletes a row", () => {
|
it("deletes a row", () => {
|
||||||
cy.get(".spectrum-Checkbox-input").check({ force: true })
|
cy.get(".spectrum-Checkbox-input").check({ force: true })
|
||||||
cy.contains("Delete 1 row(s)").click()
|
cy.contains("Delete 1 row(s)").click()
|
||||||
|
|
|
@ -62,7 +62,7 @@ context("Create a View", () => {
|
||||||
cy.get(".spectrum-Picker-label").eq(1).click()
|
cy.get(".spectrum-Picker-label").eq(1).click()
|
||||||
cy.contains("age").click({ force: true })
|
cy.contains("age").click({ force: true })
|
||||||
|
|
||||||
cy.contains("Save").click()
|
cy.get(".spectrum-Button").contains("Save").click({ force: true })
|
||||||
})
|
})
|
||||||
cy.wait(1000)
|
cy.wait(1000)
|
||||||
|
|
||||||
|
@ -123,7 +123,7 @@ context("Create a View", () => {
|
||||||
cy.contains(".nav-item", "Test View")
|
cy.contains(".nav-item", "Test View")
|
||||||
.find(".actions .icon")
|
.find(".actions .icon")
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
cy.contains("Edit").click()
|
cy.get(".spectrum-Menu-itemLabel").contains("Edit").click()
|
||||||
cy.get(".modal-inner-wrapper").within(() => {
|
cy.get(".modal-inner-wrapper").within(() => {
|
||||||
cy.get("input").type(" Updated")
|
cy.get("input").type(" Updated")
|
||||||
cy.contains("Save").click()
|
cy.contains("Save").click()
|
||||||
|
@ -138,8 +138,8 @@ context("Create a View", () => {
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
cy.contains("Delete").click()
|
cy.contains("Delete").click()
|
||||||
cy.contains("Delete View").click()
|
cy.contains("Delete View").click()
|
||||||
cy.wait(1000)
|
cy.wait(500)
|
||||||
cy.contains("TestView Updated").should("not.be.visible")
|
cy.contains("TestView Updated").should("not.exist")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
context("Custom Theming Properties", () => {
|
||||||
|
before(() => {
|
||||||
|
cy.login()
|
||||||
|
cy.createTestApp()
|
||||||
|
cy.navigateToFrontend()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Default Values
|
||||||
|
// Button roundness = Large
|
||||||
|
// Accent colour = Blue 600
|
||||||
|
// Accent colour (hover) = Blue 500
|
||||||
|
// Navigation bar background colour = Gray 100
|
||||||
|
// Navigation bar text colour = Gray 800
|
||||||
|
it("should reset the color property values", () => {
|
||||||
|
// Open Theme modal and change colours
|
||||||
|
cy.get(".spectrum-ActionButton-label").contains("Theme").click()
|
||||||
|
cy.get(".spectrum-Picker").contains("Large").click()
|
||||||
|
.parents()
|
||||||
|
.get(".spectrum-Menu-itemLabel").contains("None").click()
|
||||||
|
changeThemeColors()
|
||||||
|
// Reset colours
|
||||||
|
cy.get(".spectrum-Button-label").contains("Reset").click({force: true})
|
||||||
|
// Check values have reset
|
||||||
|
checkThemeColorDefaults()
|
||||||
|
})
|
||||||
|
|
||||||
|
const changeThemeColors = () => {
|
||||||
|
// Changes the theme colours
|
||||||
|
cy.get(".spectrum-FieldLabel").contains("Accent color")
|
||||||
|
.parent().find(".container.svelte-z3cm5a").click()
|
||||||
|
.find('[title="Red 400"]').click()
|
||||||
|
cy.get(".spectrum-FieldLabel").contains("Accent color (hover)")
|
||||||
|
.parent().find(".container.svelte-z3cm5a").click()
|
||||||
|
.find('[title="Orange 400"]').click()
|
||||||
|
cy.get(".spectrum-FieldLabel").contains("Navigation bar background color")
|
||||||
|
.parent().find(".container.svelte-z3cm5a").click()
|
||||||
|
.find('[title="Yellow 400"]').click()
|
||||||
|
cy.get(".spectrum-FieldLabel").contains("Navigation bar text color")
|
||||||
|
.parent().find(".container.svelte-z3cm5a").click()
|
||||||
|
.find('[title="Green 400"]').click()
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkThemeColorDefaults = () => {
|
||||||
|
cy.get(".spectrum-FieldLabel").contains("Accent color")
|
||||||
|
.parent().find(".container.svelte-z3cm5a").click()
|
||||||
|
.get('[title="Blue 600"]').children().find('[aria-label="Checkmark"]')
|
||||||
|
cy.get(".spectrum-Dialog-grid").click()
|
||||||
|
cy.get(".spectrum-FieldLabel").contains("Accent color (hover)")
|
||||||
|
.parent().find(".container.svelte-z3cm5a").click()
|
||||||
|
.get('[title="Blue 500"]').children().find('[aria-label="Checkmark"]')
|
||||||
|
cy.get(".spectrum-Dialog-grid").click()
|
||||||
|
cy.get(".spectrum-FieldLabel").contains("Navigation bar background color")
|
||||||
|
.parent().find(".container.svelte-z3cm5a").click()
|
||||||
|
.get('[title="Gray 100"]').children().find('[aria-label="Checkmark"]')
|
||||||
|
cy.get(".spectrum-Dialog-grid").click()
|
||||||
|
cy.get(".spectrum-FieldLabel").contains("Navigation bar text color")
|
||||||
|
.parent().find(".container.svelte-z3cm5a").click()
|
||||||
|
.get('[title="Gray 800"]').children().find('[aria-label="Checkmark"]')
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
|
@ -5,9 +5,13 @@
|
||||||
// ***********************************************
|
// ***********************************************
|
||||||
//
|
//
|
||||||
|
|
||||||
|
Cypress.on("uncaught:exception", () => {
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("login", () => {
|
Cypress.Commands.add("login", () => {
|
||||||
cy.visit(`localhost:${Cypress.env("PORT")}/builder`)
|
cy.visit(`localhost:${Cypress.env("PORT")}/builder`)
|
||||||
cy.wait(500)
|
cy.wait(2000)
|
||||||
cy.url().then(url => {
|
cy.url().then(url => {
|
||||||
if (url.includes("builder/admin")) {
|
if (url.includes("builder/admin")) {
|
||||||
// create admin user
|
// create admin user
|
||||||
|
@ -22,6 +26,7 @@ Cypress.Commands.add("login", () => {
|
||||||
cy.get("input").first().type("test@test.com")
|
cy.get("input").first().type("test@test.com")
|
||||||
cy.get('input[type="password"]').type("test")
|
cy.get('input[type="password"]').type("test")
|
||||||
cy.get("button").first().click()
|
cy.get("button").first().click()
|
||||||
|
cy.wait(1000)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -100,24 +105,32 @@ Cypress.Commands.add("createTable", tableName => {
|
||||||
cy.contains(tableName).should("be.visible")
|
cy.contains(tableName).should("be.visible")
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("addColumn", (tableName, columnName, type) => {
|
Cypress.Commands.add(
|
||||||
// Select Table
|
"addColumn",
|
||||||
cy.selectTable(tableName)
|
(tableName, columnName, type, multiOptions = null) => {
|
||||||
cy.contains(".nav-item", tableName).click()
|
// Select Table
|
||||||
cy.contains("Create column").click()
|
cy.selectTable(tableName)
|
||||||
|
cy.contains(".nav-item", tableName).click()
|
||||||
|
cy.contains("Create column").click()
|
||||||
|
|
||||||
// Configure column
|
// Configure column
|
||||||
cy.get(".spectrum-Modal").within(() => {
|
cy.get(".spectrum-Modal").within(() => {
|
||||||
cy.get("input").first().type(columnName).blur()
|
cy.get("input").first().type(columnName).blur()
|
||||||
|
|
||||||
// Unset table display column
|
// Unset table display column
|
||||||
cy.contains("display column").click({ force: true })
|
cy.contains("display column").click({ force: true })
|
||||||
cy.get(".spectrum-Picker-label").click()
|
cy.get(".spectrum-Picker-label").click()
|
||||||
cy.contains(type).click()
|
cy.contains(type).click()
|
||||||
|
|
||||||
cy.contains("Save Column").click()
|
// Add options for Multi-select Type
|
||||||
})
|
if (multiOptions !== null) {
|
||||||
})
|
cy.get(".spectrum-Textfield-input").eq(1).type(multiOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
cy.contains("Save Column").click()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
Cypress.Commands.add("addRow", values => {
|
Cypress.Commands.add("addRow", values => {
|
||||||
cy.contains("Create row").click()
|
cy.contains("Create row").click()
|
||||||
|
@ -129,6 +142,21 @@ Cypress.Commands.add("addRow", values => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add("addRowMultiValue", values => {
|
||||||
|
cy.contains("Create row").click()
|
||||||
|
cy.get(".spectrum-Form-itemField")
|
||||||
|
.click()
|
||||||
|
.then(() => {
|
||||||
|
cy.get(".spectrum-Popover").within(() => {
|
||||||
|
for (let i = 0; i < values.length; i++) {
|
||||||
|
cy.get(".spectrum-Menu-item").eq(i).click()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
cy.get(".spectrum-Dialog-grid").click("top")
|
||||||
|
cy.get(".spectrum-ButtonGroup").contains("Create").click()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("createUser", email => {
|
Cypress.Commands.add("createUser", email => {
|
||||||
// quick hacky recorded way to create a user
|
// quick hacky recorded way to create a user
|
||||||
cy.contains("Users").click()
|
cy.contains("Users").click()
|
||||||
|
@ -147,7 +175,9 @@ Cypress.Commands.add("addComponent", (category, component) => {
|
||||||
if (category) {
|
if (category) {
|
||||||
cy.get(`[data-cy="category-${category}"]`).click()
|
cy.get(`[data-cy="category-${category}"]`).click()
|
||||||
}
|
}
|
||||||
cy.get(`[data-cy="component-${component}"]`).click()
|
if (component) {
|
||||||
|
cy.get(`[data-cy="component-${component}"]`).click()
|
||||||
|
}
|
||||||
cy.wait(1000)
|
cy.wait(1000)
|
||||||
cy.location().then(loc => {
|
cy.location().then(loc => {
|
||||||
const params = loc.pathname.split("/")
|
const params = loc.pathname.split("/")
|
||||||
|
@ -169,8 +199,11 @@ Cypress.Commands.add("getComponent", componentId => {
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("navigateToFrontend", () => {
|
Cypress.Commands.add("navigateToFrontend", () => {
|
||||||
|
// Clicks on Design tab and then the Home nav item
|
||||||
cy.wait(1000)
|
cy.wait(1000)
|
||||||
cy.contains("Design").click()
|
cy.contains("Design").click()
|
||||||
|
cy.get(".spectrum-Search").type("/")
|
||||||
|
cy.get(".nav-item").contains("Home").click()
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add("createScreen", (screenName, route) => {
|
Cypress.Commands.add("createScreen", (screenName, route) => {
|
||||||
|
@ -193,3 +226,24 @@ Cypress.Commands.add("selectTable", tableName => {
|
||||||
cy.expandBudibaseConnection()
|
cy.expandBudibaseConnection()
|
||||||
cy.contains(".nav-item", tableName).click()
|
cy.contains(".nav-item", tableName).click()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add("addCustomSourceOptions", totalOptions => {
|
||||||
|
cy.get(".spectrum-ActionButton")
|
||||||
|
.contains("Define Options")
|
||||||
|
.click()
|
||||||
|
.then(() => {
|
||||||
|
for (let i = 0; i < totalOptions; i++) {
|
||||||
|
// Add radio button options
|
||||||
|
cy.get(".spectrum-Button")
|
||||||
|
.contains("Add Option")
|
||||||
|
.click({ force: true })
|
||||||
|
.then(() => {
|
||||||
|
cy.wait(500)
|
||||||
|
cy.get("[placeholder='Label']").eq(i).type(i)
|
||||||
|
cy.get("[placeholder='Value']").eq(i).type(i)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// Save options
|
||||||
|
cy.get(".spectrum-Button").contains("Save").click({ force: true })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "0.9.160-alpha.0",
|
"version": "0.9.166",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -65,10 +65,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^0.9.160-alpha.0",
|
"@budibase/bbui": "^0.9.166",
|
||||||
"@budibase/client": "^0.9.160-alpha.0",
|
"@budibase/client": "^0.9.166",
|
||||||
"@budibase/colorpicker": "1.1.2",
|
"@budibase/colorpicker": "1.1.2",
|
||||||
"@budibase/string-templates": "^0.9.160-alpha.0",
|
"@budibase/string-templates": "^0.9.166",
|
||||||
"@sentry/browser": "5.19.1",
|
"@sentry/browser": "5.19.1",
|
||||||
"@spectrum-css/page": "^3.0.1",
|
"@spectrum-css/page": "^3.0.1",
|
||||||
"@spectrum-css/vars": "^3.0.1",
|
"@spectrum-css/vars": "^3.0.1",
|
||||||
|
|
|
@ -14,7 +14,7 @@ export default class Automation {
|
||||||
}
|
}
|
||||||
|
|
||||||
addTestData(data) {
|
addTestData(data) {
|
||||||
this.automation.testData = data
|
this.automation.testData = { ...this.automation.testData, ...data }
|
||||||
}
|
}
|
||||||
|
|
||||||
addBlock(block, idx) {
|
addBlock(block, idx) {
|
||||||
|
|
|
@ -4,10 +4,11 @@
|
||||||
import { externalActions } from "./ExternalActions"
|
import { externalActions } from "./ExternalActions"
|
||||||
|
|
||||||
export let blockIdx
|
export let blockIdx
|
||||||
|
export let blockComplete
|
||||||
|
|
||||||
let selectedAction
|
let selectedAction
|
||||||
let actionVal
|
let actionVal
|
||||||
let actions = Object.entries($automationStore.blockDefinitions.ACTION)
|
let actions = Object.entries($automationStore.blockDefinitions.ACTION)
|
||||||
export let blockComplete
|
|
||||||
|
|
||||||
const external = actions.reduce((acc, elm) => {
|
const external = actions.reduce((acc, elm) => {
|
||||||
const [k, v] = elm
|
const [k, v] = elm
|
||||||
|
@ -36,10 +37,9 @@
|
||||||
actionVal.stepId,
|
actionVal.stepId,
|
||||||
actionVal
|
actionVal
|
||||||
)
|
)
|
||||||
automationStore.actions.addBlockToAutomation(newBlock)
|
automationStore.actions.addBlockToAutomation(newBlock, blockIdx + 1)
|
||||||
await automationStore.actions.save(
|
await automationStore.actions.save(
|
||||||
$automationStore.selectedAutomation?.automation,
|
$automationStore.selectedAutomation?.automation
|
||||||
blockIdx
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -151,7 +151,7 @@
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
<Button
|
<Button
|
||||||
disabled={disableAddButton ? true : !hasCompletedInputs}
|
disabled={!hasCompletedInputs}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
setupToggled = false
|
setupToggled = false
|
||||||
actionModal.show()
|
actionModal.show()
|
||||||
|
|
|
@ -5,20 +5,24 @@
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
|
||||||
let failedParse = null
|
let failedParse = null
|
||||||
|
let trigger = {}
|
||||||
|
let schemaProperties = {}
|
||||||
|
|
||||||
// clone the trigger so we're not mutating the reference
|
// clone the trigger so we're not mutating the reference
|
||||||
let trigger = cloneDeep(
|
$: trigger = cloneDeep(
|
||||||
$automationStore.selectedAutomation.automation.definition.trigger
|
$automationStore.selectedAutomation.automation.definition.trigger
|
||||||
)
|
)
|
||||||
let schemaProperties = Object.entries(trigger.schema.outputs.properties || {})
|
|
||||||
|
// get the outputs so we can define the fields
|
||||||
|
$: schemaProperties = Object.entries(trigger?.schema?.outputs?.properties)
|
||||||
|
|
||||||
if (!$automationStore.selectedAutomation.automation.testData) {
|
if (!$automationStore.selectedAutomation.automation.testData) {
|
||||||
$automationStore.selectedAutomation.automation.testData = {}
|
$automationStore.selectedAutomation.automation.testData = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the outputs so we can define the fields
|
|
||||||
|
|
||||||
// check to see if there is existing test data in the store
|
// check to see if there is existing test data in the store
|
||||||
$: testData = $automationStore.selectedAutomation.automation.testData
|
$: testData = $automationStore.selectedAutomation.automation.testData || {}
|
||||||
|
|
||||||
// Check the schema to see if required fields have been entered
|
// Check the schema to see if required fields have been entered
|
||||||
$: isError = !trigger.schema.outputs.required.every(
|
$: isError = !trigger.schema.outputs.required.every(
|
||||||
required => testData[required]
|
required => testData[required]
|
||||||
|
@ -41,7 +45,6 @@
|
||||||
showConfirmButton={true}
|
showConfirmButton={true}
|
||||||
disabled={isError}
|
disabled={isError}
|
||||||
onConfirm={() => {
|
onConfirm={() => {
|
||||||
automationStore.actions.addTestDataToAutomation(testData)
|
|
||||||
automationStore.actions.test(
|
automationStore.actions.test(
|
||||||
$automationStore.selectedAutomation?.automation,
|
$automationStore.selectedAutomation?.automation,
|
||||||
testData
|
testData
|
||||||
|
@ -53,7 +56,7 @@
|
||||||
><Tab icon="Form" title="Form">
|
><Tab icon="Form" title="Form">
|
||||||
<div class="tab-content-padding">
|
<div class="tab-content-padding">
|
||||||
<AutomationBlockSetup
|
<AutomationBlockSetup
|
||||||
bind:testData
|
{testData}
|
||||||
{schemaProperties}
|
{schemaProperties}
|
||||||
isTestModal
|
isTestModal
|
||||||
block={trigger}
|
block={trigger}
|
||||||
|
|
|
@ -9,7 +9,10 @@
|
||||||
Label,
|
Label,
|
||||||
ActionButton,
|
ActionButton,
|
||||||
Drawer,
|
Drawer,
|
||||||
|
Modal,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
|
import CreateWebhookModal from "components/automation/Shared/CreateWebhookModal.svelte"
|
||||||
|
|
||||||
import { automationStore } from "builderStore"
|
import { automationStore } from "builderStore"
|
||||||
import { tables } from "stores/backend"
|
import { tables } from "stores/backend"
|
||||||
import WebhookDisplay from "../Shared/WebhookDisplay.svelte"
|
import WebhookDisplay from "../Shared/WebhookDisplay.svelte"
|
||||||
|
@ -27,13 +30,14 @@
|
||||||
import { buildLuceneQuery } from "helpers/lucene"
|
import { buildLuceneQuery } from "helpers/lucene"
|
||||||
|
|
||||||
export let block
|
export let block
|
||||||
export let webhookModal
|
|
||||||
export let testData
|
export let testData
|
||||||
export let schemaProperties
|
export let schemaProperties
|
||||||
export let isTestModal = false
|
export let isTestModal = false
|
||||||
|
let webhookModal
|
||||||
let drawer
|
let drawer
|
||||||
let tempFilters = lookForFilters(schemaProperties) || []
|
let tempFilters = lookForFilters(schemaProperties) || []
|
||||||
let fillWidth = true
|
let fillWidth = true
|
||||||
|
|
||||||
$: stepId = block.stepId
|
$: stepId = block.stepId
|
||||||
$: bindings = getAvailableBindings(
|
$: bindings = getAvailableBindings(
|
||||||
block || $automationStore.selectedBlock,
|
block || $automationStore.selectedBlock,
|
||||||
|
@ -50,6 +54,18 @@
|
||||||
const onChange = debounce(
|
const onChange = debounce(
|
||||||
async function (e, key) {
|
async function (e, key) {
|
||||||
if (isTestModal) {
|
if (isTestModal) {
|
||||||
|
// Special case for webhook, as it requires a body, but the schema already brings back the body's contents
|
||||||
|
if (stepId === "WEBHOOK") {
|
||||||
|
automationStore.actions.addTestDataToAutomation({
|
||||||
|
body: {
|
||||||
|
[key]: e.detail,
|
||||||
|
...$automationStore.selectedAutomation.automation.testData.body,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
automationStore.actions.addTestDataToAutomation({
|
||||||
|
[key]: e.detail,
|
||||||
|
})
|
||||||
testData[key] = e.detail
|
testData[key] = e.detail
|
||||||
} else {
|
} else {
|
||||||
block.inputs[key] = e.detail
|
block.inputs[key] = e.detail
|
||||||
|
@ -206,7 +222,10 @@
|
||||||
{bindings}
|
{bindings}
|
||||||
/>
|
/>
|
||||||
{:else if value.customType === "webhookUrl"}
|
{:else if value.customType === "webhookUrl"}
|
||||||
<WebhookDisplay value={inputData[key]} />
|
<WebhookDisplay
|
||||||
|
on:change={e => onChange(e, key)}
|
||||||
|
value={inputData[key]}
|
||||||
|
/>
|
||||||
{:else if value.customType === "triggerSchema"}
|
{:else if value.customType === "triggerSchema"}
|
||||||
<SchemaSetup on:change={e => onChange(e, key)} value={inputData[key]} />
|
<SchemaSetup on:change={e => onChange(e, key)} value={inputData[key]} />
|
||||||
{:else if value.customType === "code"}
|
{:else if value.customType === "code"}
|
||||||
|
@ -249,6 +268,10 @@
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
<Modal bind:this={webhookModal} width="30%">
|
||||||
|
<CreateWebhookModal />
|
||||||
|
</Modal>
|
||||||
|
|
||||||
{#if stepId === "WEBHOOK"}
|
{#if stepId === "WEBHOOK"}
|
||||||
<Button secondary on:click={() => webhookModal.show()}>Set Up Webhook</Button>
|
<Button secondary on:click={() => webhookModal.show()}>Set Up Webhook</Button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -5,16 +5,29 @@
|
||||||
import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte"
|
import AutomationBindingPanel from "../../common/bindings/ServerBindingPanel.svelte"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
|
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
|
||||||
|
import LinkedRowSelector from "components/common/LinkedRowSelector.svelte"
|
||||||
import { automationStore } from "builderStore"
|
import { automationStore } from "builderStore"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
export let bindings
|
export let bindings
|
||||||
$: table = $tables.list.find(table => table._id === value?.tableId)
|
let table
|
||||||
$: schemaFields = Object.entries(table?.schema ?? {})
|
let schemaFields
|
||||||
|
|
||||||
|
$: {
|
||||||
|
table = $tables.list.find(table => table._id === value?.tableId)
|
||||||
|
schemaFields = Object.entries(table?.schema ?? {})
|
||||||
|
// surface the schema so the user can see it in the json
|
||||||
|
schemaFields.map(([, schema]) => {
|
||||||
|
if (!schema.autocolumn && !value[schema.name]) {
|
||||||
|
value[schema.name] = ""
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const onChangeTable = e => {
|
const onChangeTable = e => {
|
||||||
value = { tableId: e.detail }
|
value["tableId"] = e.detail
|
||||||
dispatch("change", value)
|
dispatch("change", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,6 +82,8 @@
|
||||||
label={field}
|
label={field}
|
||||||
options={schema.constraints.inclusion}
|
options={schema.constraints.inclusion}
|
||||||
/>
|
/>
|
||||||
|
{:else if schema.type === "link"}
|
||||||
|
<LinkedRowSelector bind:linkedRows={value[field]} {schema} />
|
||||||
{:else if schema.type === "string" || schema.type === "number"}
|
{:else if schema.type === "string" || schema.type === "number"}
|
||||||
{#if $automationStore.selectedAutomation.automation.testData}
|
{#if $automationStore.selectedAutomation.automation.testData}
|
||||||
<ModalBindableInput
|
<ModalBindableInput
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { Icon } from "@budibase/bbui"
|
import { Icon } from "@budibase/bbui"
|
||||||
import { automationStore } from "builderStore"
|
import { automationStore } from "builderStore"
|
||||||
import { database } from "stores/backend"
|
|
||||||
import WebhookDisplay from "./WebhookDisplay.svelte"
|
import WebhookDisplay from "./WebhookDisplay.svelte"
|
||||||
import { ModalContent } from "@budibase/bbui"
|
import { ModalContent } from "@budibase/bbui"
|
||||||
import { onMount, onDestroy } from "svelte"
|
import { onMount, onDestroy } from "svelte"
|
||||||
|
@ -12,7 +11,6 @@
|
||||||
let schemaURL
|
let schemaURL
|
||||||
let propCount = 0
|
let propCount = 0
|
||||||
|
|
||||||
$: instanceId = $database._id
|
|
||||||
$: automation = $automationStore.selectedAutomation?.automation
|
$: automation = $automationStore.selectedAutomation?.automation
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
|
|
@ -16,11 +16,29 @@
|
||||||
import { Pagination } from "@budibase/bbui"
|
import { Pagination } from "@budibase/bbui"
|
||||||
|
|
||||||
let hideAutocolumns = true
|
let hideAutocolumns = true
|
||||||
|
let schema
|
||||||
$: isUsersTable = $tables.selected?._id === TableNames.USERS
|
$: isUsersTable = $tables.selected?._id === TableNames.USERS
|
||||||
$: schema = $tables.selected?.schema
|
|
||||||
$: type = $tables.selected?.type
|
$: type = $tables.selected?.type
|
||||||
$: isInternal = type !== "external"
|
$: isInternal = type !== "external"
|
||||||
|
$: {
|
||||||
|
schema = $tables.selected?.schema
|
||||||
|
|
||||||
|
// Manually add these as we don't want them to be 'real' auto-columns
|
||||||
|
schema._id = {
|
||||||
|
type: "internal",
|
||||||
|
editable: false,
|
||||||
|
displayName: "ID",
|
||||||
|
autocolumn: true,
|
||||||
|
}
|
||||||
|
if (isInternal) {
|
||||||
|
schema._rev = {
|
||||||
|
type: "internal",
|
||||||
|
editable: false,
|
||||||
|
displayName: "Revision",
|
||||||
|
autocolumn: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
$: id = $tables.selected?._id
|
$: id = $tables.selected?._id
|
||||||
$: search = searchTable(id)
|
$: search = searchTable(id)
|
||||||
$: columnOptions = Object.keys($search.schema || {})
|
$: columnOptions = Object.keys($search.schema || {})
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
const FORMULA_TYPE = FIELDS.FORMULA.type
|
const FORMULA_TYPE = FIELDS.FORMULA.type
|
||||||
const LINK_TYPE = FIELDS.LINK.type
|
const LINK_TYPE = FIELDS.LINK.type
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
const PROHIBITED_COLUMN_NAMES = ["type", "_id", "_rev", "tableId"]
|
||||||
const { hide } = getContext(Context.Modal)
|
const { hide } = getContext(Context.Modal)
|
||||||
let fieldDefinitions = cloneDeep(FIELDS)
|
let fieldDefinitions = cloneDeep(FIELDS)
|
||||||
|
|
||||||
|
@ -66,7 +67,11 @@
|
||||||
(field.type === LINK_TYPE && !field.tableId) ||
|
(field.type === LINK_TYPE && !field.tableId) ||
|
||||||
Object.keys($tables.draft?.schema ?? {}).some(
|
Object.keys($tables.draft?.schema ?? {}).some(
|
||||||
key => key !== originalName && key === field.name
|
key => key !== originalName && key === field.name
|
||||||
)
|
) ||
|
||||||
|
columnNameInvalid
|
||||||
|
$: columnNameInvalid = PROHIBITED_COLUMN_NAMES.some(
|
||||||
|
name => field.name === name
|
||||||
|
)
|
||||||
|
|
||||||
// used to select what different options can be displayed for column type
|
// used to select what different options can be displayed for column type
|
||||||
$: canBeSearched =
|
$: canBeSearched =
|
||||||
|
@ -200,6 +205,9 @@
|
||||||
label="Name"
|
label="Name"
|
||||||
bind:value={field.name}
|
bind:value={field.name}
|
||||||
disabled={uneditable || (linkEditDisabled && field.type === LINK_TYPE)}
|
disabled={uneditable || (linkEditDisabled && field.type === LINK_TYPE)}
|
||||||
|
error={columnNameInvalid
|
||||||
|
? `${PROHIBITED_COLUMN_NAMES.join(", ")} are not allowed as column names`
|
||||||
|
: ""}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
|
|
|
@ -1,10 +1,18 @@
|
||||||
<script>
|
<script>
|
||||||
import { Label, Input, Layout, Toggle, Button } from "@budibase/bbui"
|
import {
|
||||||
|
Label,
|
||||||
|
Input,
|
||||||
|
Layout,
|
||||||
|
Toggle,
|
||||||
|
Button,
|
||||||
|
TextArea,
|
||||||
|
} from "@budibase/bbui"
|
||||||
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
|
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
|
||||||
import { capitalise } from "helpers"
|
import { capitalise } from "helpers"
|
||||||
|
|
||||||
export let integration
|
export let integration
|
||||||
export let schema
|
export let schema
|
||||||
|
|
||||||
let addButton
|
let addButton
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -29,6 +37,15 @@
|
||||||
<Label>{capitalise(configKey)}</Label>
|
<Label>{capitalise(configKey)}</Label>
|
||||||
<Toggle text="" bind:value={integration[configKey]} />
|
<Toggle text="" bind:value={integration[configKey]} />
|
||||||
</div>
|
</div>
|
||||||
|
{:else if schema[configKey].type === "longForm"}
|
||||||
|
<div class="form-row">
|
||||||
|
<Label>{capitalise(configKey)}</Label>
|
||||||
|
<TextArea
|
||||||
|
type={schema[configKey].type}
|
||||||
|
on:change
|
||||||
|
bind:value={integration[configKey]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<Label>{capitalise(configKey)}</Label>
|
<Label>{capitalise(configKey)}</Label>
|
||||||
|
|
|
@ -60,7 +60,7 @@
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal bind:this={externalDatasourceModal}>
|
<Modal bind:this={externalDatasourceModal}>
|
||||||
<DatasourceConfigModal {integration} />
|
<DatasourceConfigModal {integration} {modal} />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
|
|
|
@ -64,7 +64,7 @@
|
||||||
? "Fetch tables from database"
|
? "Fetch tables from database"
|
||||||
: "Save and continue to query"}
|
: "Save and continue to query"}
|
||||||
cancelText="Back"
|
cancelText="Back"
|
||||||
size="M"
|
size="L"
|
||||||
>
|
>
|
||||||
<Layout noPadding>
|
<Layout noPadding>
|
||||||
<Body size="XS"
|
<Body size="XS"
|
||||||
|
|
|
@ -17,9 +17,9 @@
|
||||||
|
|
||||||
$: urlDisplay =
|
$: urlDisplay =
|
||||||
schema.urlDisplay &&
|
schema.urlDisplay &&
|
||||||
`${datasource.config.url}${query.fields.path ?? ""}${
|
`${datasource.config.url}${
|
||||||
query.fields.queryString ?? ""
|
query.fields.path ? "/" + query.fields.path : ""
|
||||||
}`
|
}${query.fields.queryString ? "?" + query.fields.queryString : ""}`
|
||||||
|
|
||||||
function updateQuery({ detail }) {
|
function updateQuery({ detail }) {
|
||||||
query.fields[schema.type] = detail.value
|
query.fields[schema.type] = detail.value
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { notifications, ModalContent, Dropzone, Body } from "@budibase/bbui"
|
import { notifications, ModalContent, Dropzone, Body } from "@budibase/bbui"
|
||||||
import { post } from "builderStore/api"
|
import { post } from "builderStore/api"
|
||||||
|
import { admin } from "stores/portal"
|
||||||
|
|
||||||
let submitting = false
|
let submitting = false
|
||||||
|
|
||||||
|
@ -20,8 +21,8 @@
|
||||||
if (!importResp.ok) {
|
if (!importResp.ok) {
|
||||||
throw new Error(importJson.message)
|
throw new Error(importJson.message)
|
||||||
}
|
}
|
||||||
// now reload to get to login
|
await admin.checkImportComplete()
|
||||||
window.location.reload()
|
notifications.success("Import complete, please finish registration!")
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error(error)
|
notifications.error(error)
|
||||||
submitting = false
|
submitting = false
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
import PasswordRepeatInput from "components/common/users/PasswordRepeatInput.svelte"
|
import PasswordRepeatInput from "components/common/users/PasswordRepeatInput.svelte"
|
||||||
import ImportAppsModal from "./_components/ImportAppsModal.svelte"
|
import ImportAppsModal from "./_components/ImportAppsModal.svelte"
|
||||||
import Logo from "assets/bb-emblem.svg"
|
import Logo from "assets/bb-emblem.svg"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
let adminUser = {}
|
let adminUser = {}
|
||||||
let error
|
let error
|
||||||
|
@ -23,6 +24,7 @@
|
||||||
$: tenantId = $auth.tenantId
|
$: tenantId = $auth.tenantId
|
||||||
$: multiTenancyEnabled = $admin.multiTenancy
|
$: multiTenancyEnabled = $admin.multiTenancy
|
||||||
$: cloud = $admin.cloud
|
$: cloud = $admin.cloud
|
||||||
|
$: imported = $admin.importComplete
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
try {
|
try {
|
||||||
|
@ -40,6 +42,12 @@
|
||||||
notifications.error(`Failed to create admin user`)
|
notifications.error(`Failed to create admin user`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
if (!cloud) {
|
||||||
|
await admin.checkImportComplete()
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:this={modal} padding={false} width="600px">
|
<Modal bind:this={modal} padding={false} width="600px">
|
||||||
|
@ -73,7 +81,7 @@
|
||||||
>
|
>
|
||||||
Change organisation
|
Change organisation
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
{:else if !cloud}
|
{:else if !cloud && !imported}
|
||||||
<ActionButton
|
<ActionButton
|
||||||
quiet
|
quiet
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
|
|
|
@ -9,6 +9,7 @@ export function createAdminStore() {
|
||||||
cloud: false,
|
cloud: false,
|
||||||
disableAccountPortal: false,
|
disableAccountPortal: false,
|
||||||
accountPortalUrl: "",
|
accountPortalUrl: "",
|
||||||
|
importComplete: false,
|
||||||
onboardingProgress: 0,
|
onboardingProgress: 0,
|
||||||
checklist: {
|
checklist: {
|
||||||
apps: { checked: false },
|
apps: { checked: false },
|
||||||
|
@ -45,6 +46,17 @@ export function createAdminStore() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function checkImportComplete() {
|
||||||
|
const response = await api.get(`/api/cloud/import/complete`)
|
||||||
|
if (response.status === 200) {
|
||||||
|
const json = await response.json()
|
||||||
|
admin.update(store => {
|
||||||
|
store.importComplete = json ? json.imported : false
|
||||||
|
return store
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function getEnvironment() {
|
async function getEnvironment() {
|
||||||
let multiTenancyEnabled = false
|
let multiTenancyEnabled = false
|
||||||
let cloud = false
|
let cloud = false
|
||||||
|
@ -79,6 +91,7 @@ export function createAdminStore() {
|
||||||
return {
|
return {
|
||||||
subscribe: admin.subscribe,
|
subscribe: admin.subscribe,
|
||||||
init,
|
init,
|
||||||
|
checkImportComplete,
|
||||||
unload,
|
unload,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "0.9.160-alpha.0",
|
"version": "0.9.166",
|
||||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "0.9.160-alpha.0",
|
"version": "0.9.166",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"module": "dist/budibase-client.js",
|
"module": "dist/budibase-client.js",
|
||||||
"main": "dist/budibase-client.js",
|
"main": "dist/budibase-client.js",
|
||||||
|
@ -19,9 +19,9 @@
|
||||||
"dev:builder": "rollup -cw"
|
"dev:builder": "rollup -cw"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^0.9.160-alpha.0",
|
"@budibase/bbui": "^0.9.166",
|
||||||
"@budibase/standard-components": "^0.9.139",
|
"@budibase/standard-components": "^0.9.139",
|
||||||
"@budibase/string-templates": "^0.9.160-alpha.0",
|
"@budibase/string-templates": "^0.9.166",
|
||||||
"regexparam": "^1.3.0",
|
"regexparam": "^1.3.0",
|
||||||
"shortid": "^2.2.15",
|
"shortid": "^2.2.15",
|
||||||
"svelte-spa-router": "^3.0.5"
|
"svelte-spa-router": "^3.0.5"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"watch": ["src"],
|
"watch": ["src", "../auth"],
|
||||||
"ext": "js,ts,json",
|
"ext": "js,ts,json",
|
||||||
"ignore": ["src/**/*.spec.ts", "src/**/*.spec.js"],
|
"ignore": ["src/**/*.spec.ts", "src/**/*.spec.js"],
|
||||||
"exec": "ts-node src/index.ts"
|
"exec": "ts-node src/index.ts"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/server",
|
"name": "@budibase/server",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "0.9.160-alpha.0",
|
"version": "0.9.166",
|
||||||
"description": "Budibase Web Server",
|
"description": "Budibase Web Server",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -24,12 +24,14 @@
|
||||||
"lint": "eslint --fix src/",
|
"lint": "eslint --fix src/",
|
||||||
"lint:fix": "yarn run format && yarn run lint",
|
"lint:fix": "yarn run format && yarn run lint",
|
||||||
"initialise": "node scripts/initialise.js",
|
"initialise": "node scripts/initialise.js",
|
||||||
"multi:enable": "node scripts/multiTenancy.js enable",
|
"env:multi:enable": "node scripts/multiTenancy.js enable",
|
||||||
"multi:disable": "node scripts/multiTenancy.js disable",
|
"env:multi:disable": "node scripts/multiTenancy.js disable",
|
||||||
"selfhost:enable": "node scripts/selfhost.js enable",
|
"env:selfhost:enable": "node scripts/selfhost.js enable",
|
||||||
"selfhost:disable": "node scripts/selfhost.js disable",
|
"env:selfhost:disable": "node scripts/selfhost.js disable",
|
||||||
"localdomain:enable": "node scripts/localdomain.js enable",
|
"env:localdomain:enable": "node scripts/localdomain.js enable",
|
||||||
"localdomain:disable": "node scripts/localdomain.js disable"
|
"env:localdomain:disable": "node scripts/localdomain.js disable",
|
||||||
|
"env:account:enable": "node scripts/account.js enable",
|
||||||
|
"env:account:disable": "node scripts/account.js disable"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"preset": "ts-jest",
|
"preset": "ts-jest",
|
||||||
|
@ -66,9 +68,9 @@
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/auth": "^0.9.160-alpha.0",
|
"@budibase/auth": "^0.9.166",
|
||||||
"@budibase/client": "^0.9.160-alpha.0",
|
"@budibase/client": "^0.9.166",
|
||||||
"@budibase/string-templates": "^0.9.160-alpha.0",
|
"@budibase/string-templates": "^0.9.166",
|
||||||
"@elastic/elasticsearch": "7.10.0",
|
"@elastic/elasticsearch": "7.10.0",
|
||||||
"@koa/router": "8.0.0",
|
"@koa/router": "8.0.0",
|
||||||
"@sendgrid/mail": "7.1.1",
|
"@sendgrid/mail": "7.1.1",
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
const updateDotEnv = require("update-dotenv")
|
||||||
|
|
||||||
|
const arg = process.argv.slice(2)[0]
|
||||||
|
|
||||||
|
updateDotEnv({
|
||||||
|
DISABLE_ACCOUNT_PORTAL: arg === "enable" ? "" : "1",
|
||||||
|
}).then(() => console.log("Updated server!"))
|
|
@ -86,6 +86,7 @@ async function getAppUrlIfNotInUse(ctx) {
|
||||||
if (
|
if (
|
||||||
url &&
|
url &&
|
||||||
deployedApps[url] != null &&
|
deployedApps[url] != null &&
|
||||||
|
ctx.params != null &&
|
||||||
deployedApps[url].appId !== ctx.params.appId
|
deployedApps[url].appId !== ctx.params.appId
|
||||||
) {
|
) {
|
||||||
ctx.throw(400, "App name/URL is already in use.")
|
ctx.throw(400, "App name/URL is already in use.")
|
||||||
|
|
|
@ -28,15 +28,18 @@ exports.exportApps = async ctx => {
|
||||||
ctx.throw(400, "Exporting only allowed in multi-tenant cloud environments.")
|
ctx.throw(400, "Exporting only allowed in multi-tenant cloud environments.")
|
||||||
}
|
}
|
||||||
const apps = await getAllApps(CouchDB, { all: true })
|
const apps = await getAllApps(CouchDB, { all: true })
|
||||||
const globalDBString = await exportDB(getGlobalDBName())
|
const globalDBString = await exportDB(getGlobalDBName(), {
|
||||||
|
filter: doc => !doc._id.startsWith(DocumentTypes.USER),
|
||||||
|
})
|
||||||
let allDBs = {
|
let allDBs = {
|
||||||
global: globalDBString,
|
global: globalDBString,
|
||||||
}
|
}
|
||||||
for (let app of apps) {
|
for (let app of apps) {
|
||||||
|
const appId = app.appId || app._id
|
||||||
// only export the dev apps as they will be the latest, the user can republish the apps
|
// only export the dev apps as they will be the latest, the user can republish the apps
|
||||||
// in their self hosted environment
|
// in their self hosted environment
|
||||||
if (isDevAppID(app._id)) {
|
if (isDevAppID(appId)) {
|
||||||
allDBs[app.name] = await exportDB(app._id)
|
allDBs[app.name] = await exportDB(appId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const filename = `cloud-export-${new Date().getTime()}.txt`
|
const filename = `cloud-export-${new Date().getTime()}.txt`
|
||||||
|
@ -53,16 +56,26 @@ async function getAllDocType(db, docType) {
|
||||||
return response.rows.map(row => row.doc)
|
return response.rows.map(row => row.doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function hasBeenImported() {
|
||||||
|
if (!env.SELF_HOSTED || env.MULTI_TENANCY) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const apps = await getAllApps(CouchDB, { all: true })
|
||||||
|
return apps.length !== 0
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.hasBeenImported = async ctx => {
|
||||||
|
ctx.body = {
|
||||||
|
imported: await hasBeenImported(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
exports.importApps = async ctx => {
|
exports.importApps = async ctx => {
|
||||||
if (!env.SELF_HOSTED || env.MULTI_TENANCY) {
|
if (!env.SELF_HOSTED || env.MULTI_TENANCY) {
|
||||||
ctx.throw(400, "Importing only allowed in self hosted environments.")
|
ctx.throw(400, "Importing only allowed in self hosted environments.")
|
||||||
}
|
}
|
||||||
const apps = await getAllApps(CouchDB, { all: true })
|
const beenImported = await hasBeenImported()
|
||||||
if (
|
if (beenImported || !ctx.request.files || !ctx.request.files.importFile) {
|
||||||
apps.length !== 0 ||
|
|
||||||
!ctx.request.files ||
|
|
||||||
!ctx.request.files.importFile
|
|
||||||
) {
|
|
||||||
ctx.throw(
|
ctx.throw(
|
||||||
400,
|
400,
|
||||||
"Import file is required and environment must be fresh to import apps."
|
"Import file is required and environment must be fresh to import apps."
|
||||||
|
@ -80,11 +93,17 @@ exports.importApps = async ctx => {
|
||||||
for (let [appName, appImport] of Object.entries(dbs)) {
|
for (let [appName, appImport] of Object.entries(dbs)) {
|
||||||
await createApp(appName, appImport)
|
await createApp(appName, appImport)
|
||||||
}
|
}
|
||||||
// once apps are created clean up the global db
|
|
||||||
|
// if there are any users make sure to remove them
|
||||||
let users = await getAllDocType(globalDb, DocumentTypes.USER)
|
let users = await getAllDocType(globalDb, DocumentTypes.USER)
|
||||||
|
let userDeletionPromises = []
|
||||||
for (let user of users) {
|
for (let user of users) {
|
||||||
delete user.tenantId
|
userDeletionPromises.push(globalDb.remove(user._id, user._rev))
|
||||||
}
|
}
|
||||||
|
if (userDeletionPromises.length > 0) {
|
||||||
|
await Promise.all(userDeletionPromises)
|
||||||
|
}
|
||||||
|
|
||||||
await globalDb.bulkDocs(users)
|
await globalDb.bulkDocs(users)
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
message: "Apps successfully imported.",
|
message: "Apps successfully imported.",
|
||||||
|
|
|
@ -9,5 +9,6 @@ router
|
||||||
.get("/api/cloud/export", authorized(BUILDER), controller.exportApps)
|
.get("/api/cloud/export", authorized(BUILDER), controller.exportApps)
|
||||||
// has to be public, only run if apps don't exist
|
// has to be public, only run if apps don't exist
|
||||||
.post("/api/cloud/import", controller.importApps)
|
.post("/api/cloud/import", controller.importApps)
|
||||||
|
.get("/api/cloud/import/complete", controller.hasBeenImported)
|
||||||
|
|
||||||
module.exports = router
|
module.exports = router
|
||||||
|
|
|
@ -77,7 +77,7 @@ exports.run = async function ({ inputs }) {
|
||||||
const { status, message } = await getFetchResponse(response)
|
const { status, message } = await getFetchResponse(response)
|
||||||
return {
|
return {
|
||||||
httpStatus: status,
|
httpStatus: status,
|
||||||
success: status === 200,
|
success: status === 200 || status === 204,
|
||||||
response: message,
|
response: message,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
jest.mock("../../utilities/usageQuota")
|
jest.mock("../../utilities/usageQuota")
|
||||||
jest.mock("../thread")
|
jest.mock("../thread")
|
||||||
|
jest.mock("../../utilities/redis", () => ({
|
||||||
|
init: jest.fn(),
|
||||||
|
checkTestFlag: () => {
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
jest.spyOn(global.console, "error")
|
jest.spyOn(global.console, "error")
|
||||||
|
|
||||||
require("../../environment")
|
require("../../environment")
|
||||||
|
|
|
@ -22,6 +22,7 @@ exports.definition = {
|
||||||
fields: {
|
fields: {
|
||||||
type: "object",
|
type: "object",
|
||||||
description: "Fields submitted from the app frontend",
|
description: "Fields submitted from the app frontend",
|
||||||
|
customType: "triggerSchema",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
required: ["fields"],
|
required: ["fields"],
|
||||||
|
|
|
@ -81,16 +81,20 @@ exports.externalTrigger = async function (
|
||||||
params,
|
params,
|
||||||
{ getResponses } = {}
|
{ getResponses } = {}
|
||||||
) {
|
) {
|
||||||
if (automation.definition != null && automation.definition.trigger != null) {
|
if (
|
||||||
if (automation.definition.trigger.stepId === "APP") {
|
automation.definition != null &&
|
||||||
// values are likely to be submitted as strings, so we shall convert to correct type
|
automation.definition.trigger != null &&
|
||||||
const coercedFields = {}
|
automation.definition.trigger.stepId === definitions.APP.stepId &&
|
||||||
const fields = automation.definition.trigger.inputs.fields
|
automation.definition.trigger.stepId === "APP" &&
|
||||||
for (let key of Object.keys(fields)) {
|
!checkTestFlag(automation._id)
|
||||||
coercedFields[key] = coerce(params.fields[key], fields[key])
|
) {
|
||||||
}
|
// values are likely to be submitted as strings, so we shall convert to correct type
|
||||||
params.fields = coercedFields
|
const coercedFields = {}
|
||||||
|
const fields = automation.definition.trigger.inputs.fields
|
||||||
|
for (let key of Object.keys(fields)) {
|
||||||
|
coercedFields[key] = coerce(params.fields[key], fields[key])
|
||||||
}
|
}
|
||||||
|
params.fields = coercedFields
|
||||||
}
|
}
|
||||||
const data = { automation, event: params }
|
const data = { automation, event: params }
|
||||||
if (getResponses) {
|
if (getResponses) {
|
||||||
|
|
|
@ -20,12 +20,14 @@ export enum QueryTypes {
|
||||||
|
|
||||||
export enum DatasourceFieldTypes {
|
export enum DatasourceFieldTypes {
|
||||||
STRING = "string",
|
STRING = "string",
|
||||||
|
LONGFORM = "longForm",
|
||||||
BOOLEAN = "boolean",
|
BOOLEAN = "boolean",
|
||||||
NUMBER = "number",
|
NUMBER = "number",
|
||||||
PASSWORD = "password",
|
PASSWORD = "password",
|
||||||
LIST = "list",
|
LIST = "list",
|
||||||
OBJECT = "object",
|
OBJECT = "object",
|
||||||
JSON = "json",
|
JSON = "json",
|
||||||
|
FILE = "file",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SourceNames {
|
export enum SourceNames {
|
||||||
|
|
|
@ -74,9 +74,10 @@ module.exports = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert any strings to numbers if required, like "0" would be true otherwise
|
// clean up any environment variable edge cases
|
||||||
for (let [key, value] of Object.entries(module.exports)) {
|
for (let [key, value] of Object.entries(module.exports)) {
|
||||||
if (typeof value === "string" && !isNaN(parseInt(value))) {
|
// handle the edge case of "0" to disable an environment variable
|
||||||
module.exports[key] = parseInt(value)
|
if (value === "0") {
|
||||||
|
module.exports[key] = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,8 @@ module PostgresModule {
|
||||||
user: string
|
user: string
|
||||||
password: string
|
password: string
|
||||||
ssl?: boolean
|
ssl?: boolean
|
||||||
|
ca?: string
|
||||||
|
rejectUnauthorized?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const SCHEMA: Integration = {
|
const SCHEMA: Integration = {
|
||||||
|
@ -67,6 +69,16 @@ module PostgresModule {
|
||||||
default: false,
|
default: false,
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
|
rejectUnauthorized: {
|
||||||
|
type: DatasourceFieldTypes.BOOLEAN,
|
||||||
|
default: false,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
ca: {
|
||||||
|
type: DatasourceFieldTypes.LONGFORM,
|
||||||
|
default: false,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
create: {
|
create: {
|
||||||
|
@ -144,7 +156,12 @@ module PostgresModule {
|
||||||
|
|
||||||
let newConfig = {
|
let newConfig = {
|
||||||
...this.config,
|
...this.config,
|
||||||
ssl: this.config.ssl ? { rejectUnauthorized: true } : undefined,
|
ssl: this.config.ssl
|
||||||
|
? {
|
||||||
|
rejectUnauthorized: this.config.rejectUnauthorized,
|
||||||
|
ca: this.config.ca,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
}
|
}
|
||||||
if (!this.pool) {
|
if (!this.pool) {
|
||||||
this.pool = new Pool(newConfig)
|
this.pool = new Pool(newConfig)
|
||||||
|
|
|
@ -152,13 +152,17 @@ module RestModule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUrl(path: string, queryString: string): string {
|
||||||
|
return `${this.config.url}/${path}?${queryString}`
|
||||||
|
}
|
||||||
|
|
||||||
async create({ path = "", queryString = "", headers = {}, json = {} }) {
|
async create({ path = "", queryString = "", headers = {}, json = {} }) {
|
||||||
this.headers = {
|
this.headers = {
|
||||||
...this.config.defaultHeaders,
|
...this.config.defaultHeaders,
|
||||||
...headers,
|
...headers,
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(this.config.url + path + queryString, {
|
const response = await fetch(this.getUrl(path, queryString), {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: this.headers,
|
headers: this.headers,
|
||||||
body: JSON.stringify(json),
|
body: JSON.stringify(json),
|
||||||
|
@ -173,7 +177,7 @@ module RestModule {
|
||||||
...headers,
|
...headers,
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(this.config.url + path + queryString, {
|
const response = await fetch(this.getUrl(path, queryString), {
|
||||||
headers: this.headers,
|
headers: this.headers,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -186,7 +190,7 @@ module RestModule {
|
||||||
...headers,
|
...headers,
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(this.config.url + path + queryString, {
|
const response = await fetch(this.getUrl(path, queryString), {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: this.headers,
|
headers: this.headers,
|
||||||
body: JSON.stringify(json),
|
body: JSON.stringify(json),
|
||||||
|
@ -201,7 +205,7 @@ module RestModule {
|
||||||
...headers,
|
...headers,
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(this.config.url + path + queryString, {
|
const response = await fetch(this.getUrl(path, queryString), {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
headers: this.headers,
|
headers: this.headers,
|
||||||
body: JSON.stringify(json),
|
body: JSON.stringify(json),
|
||||||
|
@ -216,7 +220,7 @@ module RestModule {
|
||||||
...headers,
|
...headers,
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(this.config.url + path + queryString, {
|
const response = await fetch(this.getUrl(path, queryString), {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: this.headers,
|
headers: this.headers,
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,98 +1,100 @@
|
||||||
jest.mock("node-fetch", () => jest.fn(() => ({ json: jest.fn(), text: jest.fn() })))
|
jest.mock("node-fetch", () =>
|
||||||
|
jest.fn(() => ({ json: jest.fn(), text: jest.fn() }))
|
||||||
|
)
|
||||||
const fetch = require("node-fetch")
|
const fetch = require("node-fetch")
|
||||||
const RestIntegration = require("../rest")
|
const RestIntegration = require("../rest")
|
||||||
|
|
||||||
class TestConfiguration {
|
class TestConfiguration {
|
||||||
constructor(config = {}) {
|
constructor(config = {}) {
|
||||||
this.integration = new RestIntegration.integration(config)
|
this.integration = new RestIntegration.integration(config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("REST Integration", () => {
|
describe("REST Integration", () => {
|
||||||
const BASE_URL = "https://myapi.com"
|
const BASE_URL = "https://myapi.com"
|
||||||
let config
|
let config
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
config = new TestConfiguration({
|
config = new TestConfiguration({
|
||||||
url: BASE_URL
|
url: BASE_URL,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("calls the create method with the correct params", async () => {
|
it("calls the create method with the correct params", async () => {
|
||||||
const query = {
|
const query = {
|
||||||
path: "/api",
|
path: "api",
|
||||||
queryString: "?test=1",
|
queryString: "test=1",
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/json"
|
Accept: "application/json",
|
||||||
},
|
},
|
||||||
json: {
|
json: {
|
||||||
name: "test"
|
name: "test",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
const response = await config.integration.create(query)
|
const response = await config.integration.create(query)
|
||||||
expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/api?test=1`, {
|
expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/api?test=1`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: "{\"name\":\"test\"}",
|
body: '{"name":"test"}',
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/json"
|
Accept: "application/json",
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("calls the read method with the correct params", async () => {
|
it("calls the read method with the correct params", async () => {
|
||||||
const query = {
|
const query = {
|
||||||
path: "/api",
|
path: "api",
|
||||||
queryString: "?test=1",
|
queryString: "test=1",
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "text/html"
|
Accept: "text/html",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
const response = await config.integration.read(query)
|
const response = await config.integration.read(query)
|
||||||
expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/api?test=1`, {
|
expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/api?test=1`, {
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "text/html"
|
Accept: "text/html",
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("calls the update method with the correct params", async () => {
|
it("calls the update method with the correct params", async () => {
|
||||||
const query = {
|
const query = {
|
||||||
path: "/api",
|
path: "api",
|
||||||
queryString: "?test=1",
|
queryString: "test=1",
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/json"
|
Accept: "application/json",
|
||||||
},
|
},
|
||||||
json: {
|
json: {
|
||||||
name: "test"
|
name: "test",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
const response = await config.integration.update(query)
|
const response = await config.integration.update(query)
|
||||||
expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/api?test=1`, {
|
expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/api?test=1`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: "{\"name\":\"test\"}",
|
body: '{"name":"test"}',
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/json"
|
Accept: "application/json",
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("calls the delete method with the correct params", async () => {
|
it("calls the delete method with the correct params", async () => {
|
||||||
const query = {
|
const query = {
|
||||||
path: "/api",
|
path: "api",
|
||||||
queryString: "?test=1",
|
queryString: "test=1",
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/json"
|
Accept: "application/json",
|
||||||
},
|
},
|
||||||
json: {
|
json: {
|
||||||
name: "test"
|
name: "test",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
const response = await config.integration.delete(query)
|
const response = await config.integration.delete(query)
|
||||||
expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/api?test=1`, {
|
expect(fetch).toHaveBeenCalledWith(`${BASE_URL}/api?test=1`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/json"
|
Accept: "application/json",
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/string-templates",
|
"name": "@budibase/string-templates",
|
||||||
"version": "0.9.160-alpha.0",
|
"version": "0.9.166",
|
||||||
"description": "Handlebars wrapper for Budibase templating.",
|
"description": "Handlebars wrapper for Budibase templating.",
|
||||||
"main": "src/index.cjs",
|
"main": "src/index.cjs",
|
||||||
"module": "dist/bundle.mjs",
|
"module": "dist/bundle.mjs",
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"watch": ["src", "../auth"]
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/worker",
|
"name": "@budibase/worker",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "0.9.160-alpha.0",
|
"version": "0.9.166",
|
||||||
"description": "Budibase background service",
|
"description": "Budibase background service",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -15,20 +15,22 @@
|
||||||
"run:docker": "node src/index.js",
|
"run:docker": "node src/index.js",
|
||||||
"build:docker": "docker build . -t worker-service",
|
"build:docker": "docker build . -t worker-service",
|
||||||
"dev:stack:init": "node ./scripts/dev/manage.js init",
|
"dev:stack:init": "node ./scripts/dev/manage.js init",
|
||||||
"dev:builder": "npm run dev:stack:init && nodemon src/index.js",
|
"dev:builder": "npm run dev:stack:init && nodemon",
|
||||||
"test": "jest --runInBand",
|
"test": "jest --runInBand",
|
||||||
"multi:enable": "node scripts/multiTenancy.js enable",
|
"env:multi:enable": "node scripts/multiTenancy.js enable",
|
||||||
"multi:disable": "node scripts/multiTenancy.js disable",
|
"env:multi:disable": "node scripts/multiTenancy.js disable",
|
||||||
"selfhost:enable": "node scripts/selfhost.js enable",
|
"env:selfhost:enable": "node scripts/selfhost.js enable",
|
||||||
"selfhost:disable": "node scripts/selfhost.js disable",
|
"env:selfhost:disable": "node scripts/selfhost.js disable",
|
||||||
"localdomain:enable": "node scripts/localdomain.js enable",
|
"env:localdomain:enable": "node scripts/localdomain.js enable",
|
||||||
"localdomain:disable": "node scripts/localdomain.js disable"
|
"env:localdomain:disable": "node scripts/localdomain.js disable",
|
||||||
|
"env:account:enable": "node scripts/account.js enable",
|
||||||
|
"env:account:disable": "node scripts/account.js disable"
|
||||||
},
|
},
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/auth": "^0.9.160-alpha.0",
|
"@budibase/auth": "^0.9.166",
|
||||||
"@budibase/string-templates": "^0.9.160-alpha.0",
|
"@budibase/string-templates": "^0.9.166",
|
||||||
"@koa/router": "^8.0.0",
|
"@koa/router": "^8.0.0",
|
||||||
"@techpass/passport-openidconnect": "^0.3.0",
|
"@techpass/passport-openidconnect": "^0.3.0",
|
||||||
"aws-sdk": "^2.811.0",
|
"aws-sdk": "^2.811.0",
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
const updateDotEnv = require("update-dotenv")
|
||||||
|
|
||||||
|
const arg = process.argv.slice(2)[0]
|
||||||
|
|
||||||
|
updateDotEnv({
|
||||||
|
DISABLE_ACCOUNT_PORTAL: arg === "enable" ? "" : "1",
|
||||||
|
}).then(() => console.log("Updated worker!"))
|
|
@ -3,8 +3,14 @@ const { google } = require("@budibase/auth/src/middleware")
|
||||||
const { oidc } = require("@budibase/auth/src/middleware")
|
const { oidc } = require("@budibase/auth/src/middleware")
|
||||||
const { Configs, EmailTemplatePurpose } = require("../../../constants")
|
const { Configs, EmailTemplatePurpose } = require("../../../constants")
|
||||||
const { sendEmail, isEmailConfigured } = require("../../../utilities/email")
|
const { sendEmail, isEmailConfigured } = require("../../../utilities/email")
|
||||||
const { setCookie, getCookie, clearCookie, getGlobalUserByEmail, hash } =
|
const {
|
||||||
authPkg.utils
|
setCookie,
|
||||||
|
getCookie,
|
||||||
|
clearCookie,
|
||||||
|
getGlobalUserByEmail,
|
||||||
|
hash,
|
||||||
|
platformLogout,
|
||||||
|
} = authPkg.utils
|
||||||
const { Cookies } = authPkg.constants
|
const { Cookies } = authPkg.constants
|
||||||
const { passport } = authPkg.auth
|
const { passport } = authPkg.auth
|
||||||
const { checkResetPasswordCode } = require("../../../utilities/redis")
|
const { checkResetPasswordCode } = require("../../../utilities/redis")
|
||||||
|
@ -121,8 +127,7 @@ exports.resetUpdate = async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.logout = async ctx => {
|
exports.logout = async ctx => {
|
||||||
clearCookie(ctx, Cookies.Auth)
|
await platformLogout({ ctx, userId: ctx.user._id })
|
||||||
clearCookie(ctx, Cookies.CurrentApp)
|
|
||||||
ctx.body = { message: "User logged out." }
|
ctx.body = { message: "User logged out." }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,8 @@ const {
|
||||||
StaticDatabases,
|
StaticDatabases,
|
||||||
generateNewUsageQuotaDoc,
|
generateNewUsageQuotaDoc,
|
||||||
} = require("@budibase/auth/db")
|
} = require("@budibase/auth/db")
|
||||||
const { hash, getGlobalUserByEmail, saveUser } = require("@budibase/auth").utils
|
const { hash, getGlobalUserByEmail, saveUser, platformLogout } =
|
||||||
|
require("@budibase/auth").utils
|
||||||
const { EmailTemplatePurpose } = require("../../../constants")
|
const { EmailTemplatePurpose } = require("../../../constants")
|
||||||
const { checkInviteCode } = require("../../../utilities/redis")
|
const { checkInviteCode } = require("../../../utilities/redis")
|
||||||
const { sendEmail } = require("../../../utilities/email")
|
const { sendEmail } = require("../../../utilities/email")
|
||||||
|
@ -111,14 +112,16 @@ exports.destroy = async ctx => {
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
const dbUser = await db.get(ctx.params.id)
|
const dbUser = await db.get(ctx.params.id)
|
||||||
|
|
||||||
// root account holder can't be deleted from inside budibase
|
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
|
||||||
const email = dbUser.email
|
// root account holder can't be deleted from inside budibase
|
||||||
const account = await accounts.getAccount(email)
|
const email = dbUser.email
|
||||||
if (account) {
|
const account = await accounts.getAccount(email)
|
||||||
if (email === ctx.user.email) {
|
if (account) {
|
||||||
ctx.throw(400, 'Please visit "Account" to delete this user')
|
if (email === ctx.user.email) {
|
||||||
} else {
|
ctx.throw(400, 'Please visit "Account" to delete this user')
|
||||||
ctx.throw(400, "Account holder cannot be deleted")
|
} else {
|
||||||
|
ctx.throw(400, "Account holder cannot be deleted")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,7 +174,14 @@ exports.updateSelf = async ctx => {
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
const user = await db.get(ctx.user._id)
|
const user = await db.get(ctx.user._id)
|
||||||
if (ctx.request.body.password) {
|
if (ctx.request.body.password) {
|
||||||
|
// changing password
|
||||||
ctx.request.body.password = await hash(ctx.request.body.password)
|
ctx.request.body.password = await hash(ctx.request.body.password)
|
||||||
|
// Log all other sessions out apart from the current one
|
||||||
|
await platformLogout({
|
||||||
|
ctx,
|
||||||
|
userId: ctx.user._id,
|
||||||
|
keepActiveSession: true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
// don't allow sending up an ID/Rev, always use the existing one
|
// don't allow sending up an ID/Rev, always use the existing one
|
||||||
delete ctx.request.body._id
|
delete ctx.request.body._id
|
||||||
|
|
|
@ -52,3 +52,11 @@ module.exports = {
|
||||||
return !isDev()
|
return !isDev()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clean up any environment variable edge cases
|
||||||
|
for (let [key, value] of Object.entries(module.exports)) {
|
||||||
|
// handle the edge case of "0" to disable an environment variable
|
||||||
|
if (value === "0") {
|
||||||
|
module.exports[key] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ const { Headers } = require("@budibase/auth").constants
|
||||||
* Ensure that the correct API key has been supplied.
|
* Ensure that the correct API key has been supplied.
|
||||||
*/
|
*/
|
||||||
module.exports = async (ctx, next) => {
|
module.exports = async (ctx, next) => {
|
||||||
if (!env.SELF_HOSTED) {
|
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
|
||||||
const apiKey = ctx.request.headers[Headers.API_KEY]
|
const apiKey = ctx.request.headers[Headers.API_KEY]
|
||||||
if (apiKey !== env.INTERNAL_API_KEY) {
|
if (apiKey !== env.INTERNAL_API_KEY) {
|
||||||
ctx.throw(403, "Unauthorized")
|
ctx.throw(403, "Unauthorized")
|
||||||
|
|
Loading…
Reference in New Issue