Merge branch 'release/1.2.0'
2
.version
|
|
@ -1 +1 @@
|
|||
1.1.0
|
||||
1.2.0
|
||||
|
|
@ -52,4 +52,4 @@ We use GitHub issues to track public bugs. Report a bug by [opening a new issue]
|
|||
By contributing, you agree that your contributions will be licensed under its AGPL v3 License.
|
||||
|
||||
## Questions?
|
||||
Contact us on slack [Slack](https://join.slack.com/t/tooljet/shared_invite/zt-r2neyfcw-KD1COL6t2kgVTlTtAV5rtg) or mail us at [hello@tooljet.io](mailto:hello@tooljet.io.).
|
||||
Contact us on [Slack](https://join.slack.com/t/tooljet/shared_invite/zt-r2neyfcw-KD1COL6t2kgVTlTtAV5rtg) or mail us at [hello@tooljet.io](mailto:hello@tooljet.io).
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 18
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# BigQuery
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 4
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# Custom JavaScript
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 5
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
# DynamoDB
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 6
|
||||
sidebar_position: 7
|
||||
---
|
||||
|
||||
# Elasticsearch
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 3
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# Cloud Firestore
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 7
|
||||
sidebar_position: 8
|
||||
---
|
||||
|
||||
# Google Cloud Storage
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 8
|
||||
sidebar_position: 9
|
||||
---
|
||||
|
||||
# Google Sheets
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 9
|
||||
sidebar_position: 10
|
||||
---
|
||||
|
||||
# GraphQL
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
---
|
||||
sidebar_position: 11
|
||||
---
|
||||
|
||||
# MinIO
|
||||
|
||||
ToolJet can connect to minio and perform various operation on them.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 10
|
||||
sidebar_position: 12
|
||||
---
|
||||
|
||||
# MongoDB
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 11
|
||||
sidebar_position: 13
|
||||
---
|
||||
|
||||
# MS SQL Server / Azure SQL databases
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 12
|
||||
sidebar_position: 14
|
||||
---
|
||||
|
||||
# MySQL
|
||||
|
|
|
|||
49
docs/docs/data-sources/n8n.md
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
|
||||
# n8n
|
||||
|
||||
ToolJet can trigger n8n workflows using webhook URLs. Please refer [this](https://docs.n8n.io/) to know more about n8n.
|
||||
|
||||
## Connection
|
||||
|
||||
Go to the data source manager on the left sidebar and click on `+` button to add new data source. Select n8n from the list of available data sources in the modal that pops-up.
|
||||
|
||||
n8n webhooks can be called with or without an **Authentication**. You can keep the `Authentication type` as `none` if your webhook didn't have one or if it has one then you can choose the one from the dropdown and provide credentials:
|
||||
|
||||
#### Authentication Types
|
||||
- **Basic Auth**: To connect your n8n webhooks using basic auth you'll need to provide the following credentials:
|
||||
- **Username**
|
||||
- **Password**
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
- **Header Auth**: To connect your n8n webhooks using header auth the following fields are required:
|
||||
- **Name / Key**
|
||||
- **Value**
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
:::tip
|
||||
Webhook credentials and instance credentials are different. Please use the credentials that you use with the webhook trigger. Know more: **[Webhook Authentication](https://docs.n8n.io/nodes/n8n-nodes-base.webhook/#:~:text=then%20gets%20deactivated.-,Authentication,-%3A%20The%20Webhook%20node)**.
|
||||
:::
|
||||
|
||||
## Trigger Workflow
|
||||
|
||||
Click on `+` button of the query manager at the bottom panel of the editor and the select n8n as the datasource.
|
||||
|
||||
You can trigger a workflow with `GET/POST` URL. Choose the request type from the `Methods` dropdown and then provide the required fields:
|
||||
- **URL parameters** (Support for GET & POST) `Optional`
|
||||
- **Body** (Only for POST URL) `Required`
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 13
|
||||
sidebar_position: 15
|
||||
---
|
||||
|
||||
# PostgreSQL
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 14
|
||||
sidebar_position: 16
|
||||
---
|
||||
|
||||
# Redis
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 15
|
||||
sidebar_position: 17
|
||||
---
|
||||
|
||||
# REST API
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ ToolJet can connect to Amazon S3 buckets and perform various operation on them.
|
|||
|
||||
To add a new S3 source, go to the **Datasources manager** on the left sidebar of the app editor and click on `Add datasource` button. Select **AWS S3** from the modal that pops up.
|
||||
|
||||
ToolJet requires the following to connect to your DynamoDB:
|
||||
ToolJet requires the following to connect to your AWS S3:
|
||||
|
||||
- **Region**
|
||||
- **Access key**
|
||||
|
|
@ -18,7 +18,11 @@ ToolJet requires the following to connect to your DynamoDB:
|
|||
|
||||
It is recommended to create a new IAM user for the database so that you can control the access levels of ToolJet.
|
||||
|
||||

|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
Click on **Test connection** button to verify if the credentials are correct and that the database is accessible to ToolJet server. Click on **Save** button to save the data source.
|
||||
|
||||
|
|
@ -26,16 +30,103 @@ Click on **Test connection** button to verify if the credentials are correct and
|
|||
|
||||
Click on `+` button of the **query manager** at the bottom panel of the editor and select the data source added in the previous step as the data source. Select the operation that you want to perform and click **Save** to save the query.
|
||||
|
||||

|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
Click on the **run** button to run the query.
|
||||
|
||||
**NOTE**: Query should be saved before running.
|
||||
|
||||
:::tip
|
||||
Query results can be transformed using transformations. Read our transformations documentation to see how: [link](/docs/tutorial/transformations)
|
||||
Query results can be transformed using transformations. Read our transformations documentation to see how: **[link](/docs/tutorial/transformations)**
|
||||
:::
|
||||
|
||||
## Query operations
|
||||
|
||||
You can create query for AWS S3 data source to perform several actions such as:
|
||||
1. **[Read object](/docs/data-sources/s3#read-object)**
|
||||
2. **[Upload object](/docs/data-sources/s3#upload-object)**
|
||||
3. **[List buckets](/docs/data-sources/s3#list-buckets)**
|
||||
4. **[List objects in a bucket](/docs/data-sources/s3#list-objects-in-a-bucket)**
|
||||
5. **[Signed url for download](/docs/data-sources/s3#signed-url-for-download)**
|
||||
6. **[Signed url for upload](/docs/data-sources/s3#signed-url-for-upload)**
|
||||
|
||||
### Read object
|
||||
|
||||
You can read an object in a bucket by using this operation. It requires two parameters - **Bucket** name and **Key**.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
### Upload object
|
||||
|
||||
You can use this operation to upload objects(files) to your S3 bucket. It requires four parameters:
|
||||
1. **Bucket**: Specify the bucket name
|
||||
2. **Key**: Key of the object/file
|
||||
3. **Content type**: Specify file type such as text, image etc.
|
||||
4. **Upload data**: File/object that is to be uploaded.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
### List buckets
|
||||
|
||||
This operation will list all the buckets in your S3. This does not require any parameter.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
### List objects in a bucket
|
||||
|
||||
This operation will fetch the list of all the files in your bucket. It requires two parameters:
|
||||
1. **Bucket**: Bucket name (mandatory)
|
||||
2. **Prefix**: To limit the response to keys that begin with the specified prefix (optional)
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
### Signed url for download
|
||||
|
||||
The object owner can optionally share objects with others by creating a presigned URL, using their own security credentials, to grant time-limited permission to download the objects. For creating a presigned URL, the required parameters are:
|
||||
1. **Bucket**: name of the bucket for uploading the file
|
||||
2. **Key**: an object key
|
||||
3. **Expires in**: an expiration time of URL
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
### Signed url for upload
|
||||
|
||||
The presigned URLs are useful if you want your user/customer to be able to upload a specific object to your bucket, but you don't require them to have AWS security credentials or permissions. For creating a presigned URL, the required parameters are:
|
||||
1. **Bucket**: name of the bucket for uploading the file
|
||||
2. **Key**: an object key
|
||||
3. **Expires in**: an expiration time of URL
|
||||
4. **Content type**: the content type such as text, image etc.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
:::info
|
||||
We built an app to view and upload files to AWS S3 buckets. Check out the complete tutorial [here](https://blog.tooljet.com/building-an-app-to-view-and-upload-files-in-aws-s3-bucket/).
|
||||
We built an app to view and upload files to AWS S3 buckets. Check out the complete tutorial **[here](https://blog.tooljet.com/building-an-app-to-view-and-upload-files-in-aws-s3-bucket/)**.
|
||||
:::
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 16
|
||||
sidebar_position: 18
|
||||
---
|
||||
|
||||
# SendGrid
|
||||
|
|
|
|||
49
docs/docs/data-sources/smtp.md
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
|
||||
# SMTP
|
||||
|
||||
SMTP plugin can connect ToolJet applications to **SMTP servers** for sending emails.
|
||||
|
||||
## Connection
|
||||
|
||||
A SMTP server can be connected with the following credentails:
|
||||
- **Host**
|
||||
- **Port**
|
||||
- **User**
|
||||
- **Password**
|
||||
|
||||
:::info
|
||||
You can also test your connection before saving the configuration by clicking on `Test Connection` button.
|
||||
:::
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
## Querying SMTP
|
||||
|
||||
Go to the query manager at the bottom panel of the editor and click on the `+` button on the left to create a new query. Select `SMTP` from the datasource dropdown.
|
||||
|
||||
To create a query for sending email, you will need to provide the following properties:
|
||||
- **From** `required` : Email address of the sender
|
||||
- **From Name** : Name of the sender
|
||||
- **To** `required` : Recipient's email address
|
||||
- **Subject** : Subject of the email
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
- **Body** : You can enter the body text either in the form of `raw text` or `html` in their respective fields.
|
||||
- **Attachments** : Attachments can be added to a SMTP query by referencing the file from the `File Picker` component in the attachments field.
|
||||
|
||||
For example, you can set the `Attachments` field value to `{{ components.filepicker1.file }}` or you can pass an array of `{{ name: 'filename.jpg', dataURL: '......' }}` object to accomplish this.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
|
@ -1,3 +1,7 @@
|
|||
---
|
||||
sidebar_position: 19
|
||||
---
|
||||
|
||||
# Snowflake
|
||||
|
||||
ToolJet can connect to Snowflake databases to read and write data.
|
||||
|
|
@ -7,10 +11,10 @@ ToolJet can connect to Snowflake databases to read and write data.
|
|||
|
||||
## Connection
|
||||
|
||||
Please make sure the host/ip of the database is accessible from your VPC if you have self-hosted ToolJet. If you are using ToolJet cloud, please whitelist our IP. You can find snowflake docs on network policies [https://docs.snowflake.com/en/user-guide/network-policies.html](here)
|
||||
Please make sure the host/ip of the database is accessible from your VPC if you have self-hosted ToolJet. If you are using ToolJet cloud, please whitelist our IP. You can find snowflake docs on network policies **[here](https://docs.snowflake.com/en/user-guide/network-policies.html)**.
|
||||
|
||||
|
||||
To add a new Snowflake database, click on the '+' button on data sources panel at the left-bottom corner of the app editor. Select Snowflake from the modal that pops up.
|
||||
To add a new Snowflake database, click on the `+` button on data sources panel at the left-bottom corner of the app editor. Select Snowflake from the modal that pops up.
|
||||
|
||||
ToolJet requires the following to connect to your Snowflake database.
|
||||
|
||||
|
|
@ -18,17 +22,29 @@ ToolJet requires the following to connect to your Snowflake database.
|
|||
- **Username**
|
||||
- **Password**
|
||||
|
||||
You can also configure for [additional optional parameters](https://docs.snowflake.com/en/user-guide/nodejs-driver-use.html#additional-connection-options).
|
||||
:::info
|
||||
You can also configure for **[additional optional parameters](https://docs.snowflake.com/en/user-guide/nodejs-driver-use.html#additional-connection-options)**.
|
||||
:::
|
||||
|
||||
<img src="/img/datasource-reference/snowflake/snowflake-connect.png" alt="ToolJet - Snowflake connection" height="250"/>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
## Querying Snowflake
|
||||
|
||||
Click on '+' button of the query manager at the bottom panel of the editor and select the database added in the previous step as the datasource. Query manager then can be used to write raw SQL queries.
|
||||
Click on `+` button of the query manager at the bottom panel of the editor and select the database added in the previous step as the datasource. Query manager then can be used to write raw SQL queries.
|
||||
|
||||
<img src="/img/datasource-reference/snowflake/snowflake-query.png" alt="ToolJet - Snowflake query" height="250"/>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||
Click on the 'run' button to run the query. NOTE: Query should be saved before running.
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
Click on the `run` button to run the query.
|
||||
|
||||
**NOTE:** Query should be saved before running.
|
||||
|
||||
:::tip
|
||||
Query results can be transformed using transformations. Read our transformations documentation to see how: [link](/docs/tutorial/transformations)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
sidebar_position: 17
|
||||
sidebar_position: 20
|
||||
---
|
||||
|
||||
# TypeSense
|
||||
|
|
|
|||
|
|
@ -10,16 +10,18 @@ sidebar_label: Architecture
|
|||
|
||||
ToolJet have two main components: **ToolJet Server** and **ToolJet Client**.
|
||||
|
||||
1. ### ToolJet Server
|
||||
ToolJet server is a Node.js API application. Server is responsible for authentication, authorization, persisting application definitions, running queries, storing data source credentials securely and more.
|
||||
### 1. ToolJet Server
|
||||
|
||||
Dependencies:
|
||||
- PostgreSQL - ToolJet server persists data to a postgres database.
|
||||
- Email service (SMTP/Sendgrid/Mailgun/etc) - Required to send user invitations and password reset emails.
|
||||
ToolJet server is a Node.js API application. Server is responsible for authentication, authorization, persisting application definitions, running queries, storing data source credentials securely and more.
|
||||
|
||||
**Dependencies:**
|
||||
- **PostgreSQL** - ToolJet server persists data to a postgres database.
|
||||
- **Email service** (SMTP/Sendgrid/Mailgun/etc) - Required to send user invitations and password reset emails.
|
||||
|
||||
2. ### ToolJet Client
|
||||
ToolJet client is a ReactJS application. Client is responsible for visually editing the applications, building & editing queries, rendering applications, executing events and their trigger, etc.
|
||||
### 2. ToolJet Client
|
||||
|
||||
ToolJet client is a ReactJS application. Client is responsible for visually editing the applications, building & editing queries, rendering applications, executing events and their trigger, etc.
|
||||
|
||||
## Requirements
|
||||
|
||||
1. Node version 14.x
|
||||
1. **Node version 14.x**
|
||||
|
|
@ -6,83 +6,84 @@ sidebar_label: Docker
|
|||
# Deploying ToolJet using docker-compose
|
||||
|
||||
:::info
|
||||
You should setup a PostgreSQL database manually to be used by the ToolJet server.
|
||||
You should setup a PostgreSQL database manually to be used by the ToolJet server.
|
||||
:::
|
||||
|
||||
Follow the steps below to deploy ToolJet on a server using docker-compose. This setup will deploy both ToolJet server and ToolJet client.
|
||||
Follow the steps below to deploy ToolJet on a server using docker-compose. This setup will deploy both **ToolJet server** and **ToolJet client**.
|
||||
|
||||
1. Setup a PostgreSQL database and make sure that the database is accessible.
|
||||
|
||||
2. Make sure that the server can receive traffic on port 80, 443 and 22.
|
||||
For example, if the server is an AWS EC2 instance and the installation should receive traffic from the internet, the inbound rules of the security group should look like this:
|
||||
2. Make sure that the server can receive traffic on port 80, 443 and 22.
|
||||
For example, if the server is an AWS EC2 instance and the installation should receive traffic from the internet, the inbound rules of the security group should look like this:
|
||||
|
||||
protocol| port | allowed_cidr|
|
||||
--------| ------- | ----------- |
|
||||
tcp | 22 | your IP |
|
||||
tcp | 80 | 0.0.0.0/0 |
|
||||
tcp | 443 | 0.0.0.0/0 |
|
||||
| protocol | port | allowed_cidr |
|
||||
| -------- | ---- | ------------ |
|
||||
| tcp | 22 | your IP |
|
||||
| tcp | 80 | 0.0.0.0/0 |
|
||||
| tcp | 443 | 0.0.0.0/0 |
|
||||
|
||||
3. Install docker and docker-compose on the server.
|
||||
[Docker Installation](https://docs.docker.com/engine/install/)
|
||||
[Docker Compose Installation](https://docs.docker.com/compose/install/)
|
||||
- Docs for [Docker Installation](https://docs.docker.com/engine/install/)
|
||||
- Docs for [Docker Compose Installation](https://docs.docker.com/compose/install/)
|
||||
|
||||
4. Download our production docker-compose file into the server by running:
|
||||
```bash
|
||||
curl -LO https://raw.githubusercontent.com/ToolJet/ToolJet/main/deploy/docker/docker-compose.yaml
|
||||
```
|
||||
|
||||
```bash
|
||||
curl -LO https://raw.githubusercontent.com/ToolJet/ToolJet/main/deploy/docker/docker-compose.yaml
|
||||
```
|
||||
|
||||
5. Create `.env` file in the current directory (where the docker-compose.yaml file is downloaded):
|
||||
|
||||
```bash
|
||||
curl -LO https://raw.githubusercontent.com/ToolJet/ToolJet/main/.env.example mv .env.example .env
|
||||
```
|
||||
```bash
|
||||
curl -LO https://raw.githubusercontent.com/ToolJet/ToolJet/main/.env.example mv .env.example .env
|
||||
```
|
||||
|
||||
Set up environment variables in `.env` file as explained in [environment variables reference](/docs/deployment/env-vars)
|
||||
Set up environment variables in `.env` file as explained in [environment variables reference](/docs/deployment/env-vars)
|
||||
|
||||
`TOOLJET_HOST` environment variable can either be the public ipv4 address of your server or a custom domain that you want to use.
|
||||
|
||||
`TOOLJET_HOST` environment variable can either be the public ipv4 address of your server or a custom domain that you want to use.
|
||||
:::info
|
||||
We use a [lets encrypt](https://letsencrypt.org/) plugin on top of nginx to create TLS certificates on the fly.
|
||||
:::
|
||||
|
||||
:::info
|
||||
We use a [lets encrypt](https://letsencrypt.org/) plugin on top of nginx to create TLS certificates on the fly.
|
||||
:::
|
||||
Examples:
|
||||
`TOOLJET_HOST=http://12.34.56.78` or
|
||||
`TOOLJET_HOST=https://yourdomain.com` or
|
||||
`TOOLJET_HOST=https://tooljet.yourdomain.com`
|
||||
|
||||
Examples:
|
||||
`TOOLJET_HOST=http://12.34.56.78` or
|
||||
`TOOLJET_HOST=https://yourdomain.com` or
|
||||
`TOOLJET_HOST=https://tooljet.yourdomain.com`
|
||||
:::info
|
||||
Please make sure that `TOOLJET_HOST` starts with either `http://` or `https://`
|
||||
:::
|
||||
|
||||
:::info
|
||||
Please make sure that `TOOLJET_HOST` starts with either `http://` or `https://`
|
||||
:::
|
||||
|
||||
:::info
|
||||
If there are self signed HTTPS endpoints that Tooljet needs to connect to, please make sure that `NODE_EXTRA_CA_CERTS` environment variable is set to the absolute path containing the certificates.
|
||||
:::
|
||||
:::info
|
||||
If there are self signed HTTPS endpoints that Tooljet needs to connect to, please make sure that `NODE_EXTRA_CA_CERTS` environment variable is set to the absolute path containing the certificates.
|
||||
:::
|
||||
|
||||
6. Once you've populated the `.env` file, run
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
to start all the required services.
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
:::info
|
||||
If you're running on a linux server, `docker` might need sudo permissions. In that case you can either run:
|
||||
`sudo docker-compose up -d`
|
||||
OR
|
||||
Setup docker to run without root privileges by following the instructions written here https://docs.docker.com/engine/install/linux-postinstall/
|
||||
:::
|
||||
to start all the required services.
|
||||
|
||||
:::info
|
||||
If you're running on a linux server, `docker` might need sudo permissions. In that case you can either run:
|
||||
`sudo docker-compose up -d`
|
||||
OR
|
||||
Setup docker to run without root privileges by following the instructions written here https://docs.docker.com/engine/install/linux-postinstall/
|
||||
:::
|
||||
|
||||
7. If you've set a custom domain for `TOOLJET_HOST`, add a `A record` entry in your DNS settings to point to the IP address of the server.
|
||||
|
||||
8. Seed the database:
|
||||
|
||||
8. Seed the database:
|
||||
```bash
|
||||
docker-compose run server npm run db:seed
|
||||
```
|
||||
This seeds the database with a default user with the following credentials:
|
||||
email: `dev@tooljet.io`
|
||||
password: `password`
|
||||
```bash
|
||||
docker-compose run server npm run db:seed
|
||||
```
|
||||
|
||||
This seeds the database with a default user with the following credentials:
|
||||
email: `dev@tooljet.io`
|
||||
password: `password`
|
||||
|
||||
9. You're all done, ToolJet client would now be served at the URL you've set in `TOOLJET_HOST`.
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ Follow the steps below to deploy ToolJet on AWS EC2 instances.
|
|||
7. Switch to the app directory by running `cd ~/app`. Modify the contents of the `.env` file. ( Eg: `vim .env` )
|
||||
|
||||
The default `.env` file looks like this:
|
||||
```
|
||||
```bash
|
||||
TOOLJET_HOST=http://<example>
|
||||
LOCKBOX_MASTER_KEY=<example>
|
||||
SECRET_KEY_BASE=<example>
|
||||
|
|
@ -43,7 +43,7 @@ Follow the steps below to deploy ToolJet on AWS EC2 instances.
|
|||
PG_HOST=<pg host>
|
||||
PG_PASS=<pg user password>
|
||||
```
|
||||
Read [environment variables reference](/docs/deployment/env-vars)
|
||||
Read **[environment variables reference](/docs/deployment/env-vars)**
|
||||
|
||||
:::info
|
||||
If there are self signed HTTPS endpoints that Tooljet needs to connect to, please make sure that `NODE_EXTRA_CA_CERTS` environment variable is set to the absolute path containing the certificates.
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ Follow the steps below to deploy ToolJet on Cloud run with `gcloud` CLI.
|
|||
|
||||
2. Deploy new cloud run service
|
||||
|
||||
:::note
|
||||
:::info
|
||||
This command takes the assumption that certain required environment has already been created in secrets. If you haven't created already then use the [secret manager](https://console.cloud.google.com/security/secret-manager).
|
||||
:::
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ The default username of the admin is `dev@tooljet.io` and the password is `passw
|
|||
|
||||
2. Deploy new cloud run service
|
||||
|
||||
:::note
|
||||
:::info
|
||||
This command takes the assumption that certain required environment has already been created in secrets. If you haven't created already then use the [secret manager](https://console.cloud.google.com/security/secret-manager).
|
||||
:::
|
||||
|
||||
|
|
@ -123,7 +123,7 @@ If you are to use [Public IP](https://cloud.google.com/sql/docs/mysql/connect-ru
|
|||
If there are self signed HTTPS endpoints that Tooljet needs to connect to, please make sure that `NODE_EXTRA_CA_CERTS` environment variable is set to the absolute path containing the certificates. The certificate can be mount as a volume onto the container using secrets.
|
||||
:::
|
||||
|
||||
3. Create default user (Optional)
|
||||
3. Create default user **(Optional)**
|
||||
|
||||
Signing up requires [SMTP configuration](https://docs.tooljet.com/docs/deployment/env-vars#smtp-configuration--optional-) to be done, but if you want to start off with default user you can run the command by modifying the `args` flag for a one time usage.
|
||||
|
||||
|
|
|
|||
|
|
@ -8,12 +8,16 @@ sidebar_label: Heroku
|
|||
Follow the steps below to deploy ToolJet on Heroku:
|
||||
|
||||
1. Click the button below to start one click deployment.
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||
[](https://heroku.com/deploy?template=https://github.com/tooljet/tooljet/tree/main)
|
||||
|
||||
</div>
|
||||
|
||||
2. Navigate to Heroku dashboard and go to resources tab to verify that the dyno is turned on.
|
||||
3. Go to settings tab on Heroku dashboard and select `reveal config vars` to configure additional environment variables that your installation might need.
|
||||
|
||||
Read [environment variables reference](/docs/deployment/env-vars)
|
||||
Read **[environment variables reference](/docs/deployment/env-vars)**
|
||||
|
||||
4. Open the app.
|
||||
5. The default username of the admin is `dev@tooljet.io` and the password is `password`.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ You should setup a PostgreSQL database manually to be used by ToolJet. We recomm
|
|||
|
||||
Follow the steps below to deploy ToolJet on a AKS Kubernetes cluster.
|
||||
|
||||
1. Create an AKS cluster and connect to it to start with the deployement. You can follow the steps as mentioned on the [doc](https://docs.microsoft.com/en-us/azure/aks/kubernetes-walkthrough-portal).
|
||||
1. Create an AKS cluster and connect to it to start with the deployement. You can follow the steps as mentioned on the [ Azure's documentation](https://docs.microsoft.com/en-us/azure/aks/kubernetes-walkthrough-portal).
|
||||
|
||||
2. Create k8s deployment
|
||||
|
||||
|
|
@ -21,9 +21,10 @@ Follow the steps below to deploy ToolJet on a AKS Kubernetes cluster.
|
|||
|
||||
Make sure to edit the environment variables in the `deployment.yaml`. We advise to use secrets to setup sensitive information. You can check out the available options [here](https://docs.tooljet.com/docs/deployment/env-vars).
|
||||
|
||||
:::info
|
||||
If there are self signed HTTPS endpoints that Tooljet needs to connect to, please make sure that `NODE_EXTRA_CA_CERTS` environment variable is set to the absolute path containing the certificates. You can make use of kubernetes secrets to mount the certificate file onto the containers.
|
||||
:::
|
||||
:::info
|
||||
If there are self signed HTTPS endpoints that Tooljet needs to connect to, please make sure that `NODE_EXTRA_CA_CERTS` environment variable is set to the absolute path containing the certificates. You can make use of kubernetes secrets to mount the certificate file onto the containers.
|
||||
:::
|
||||
|
||||
3. Create k8s service and reserve a static IP and inorder expose it via a service load balancer as mentioned in the [doc](https://docs.microsoft.com/en-us/azure/aks/static-ip). You can refer `service.yaml`.
|
||||
```bash
|
||||
curl -LO https://raw.githubusercontent.com/ToolJet/ToolJet/main/deploy/kubernetes/AKS/service.yaml
|
||||
|
|
@ -38,8 +39,11 @@ Make sure to edit the environment variables in the `deployment.yaml`. We advise
|
|||
You will be able to access your ToolJet installation once the pods and services running.
|
||||
|
||||
If you want to seed the database with a sample user, please SSH into a pod and run:
|
||||
`npm run db:seed --prefix server`.
|
||||
|
||||
`npm run db:seed --prefix server`
|
||||
|
||||
This seeds the database with a default user with the following credentials:
|
||||
|
||||
email: `dev@tooljet.io`
|
||||
password: `password`
|
||||
**email**: `dev@tooljet.io`
|
||||
|
||||
**password**: `password`
|
||||
|
|
|
|||
|
|
@ -64,8 +64,11 @@ It might take a few minutes to provision the managed certificates. [Managed cert
|
|||
You will be able to access your ToolJet installation once the pods, service and the ingress is running.
|
||||
|
||||
If you want to seed the database with a sample user, please SSH into a pod and run:
|
||||
`npm run db:seed --prefix server`.
|
||||
|
||||
`npm run db:seed --prefix server`
|
||||
|
||||
This seeds the database with a default user with the following credentials:
|
||||
|
||||
email: `dev@tooljet.io`
|
||||
password: `password`
|
||||
**emai**: `dev@tooljet.io`
|
||||
|
||||
**password**: `password`
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ Follow the steps below to deploy ToolJet on a Kubernetes cluster.
|
|||
|
||||
2. Create a Kubernetes secret with name `server`. For the minimal setup, ToolJet requires `pg_host`, `pg_db`, `pg_user`, `pg_password`, `secret_key_base` & `lockbox_key` keys in the secret.
|
||||
|
||||
Read [environment variables reference](/docs/deployment/env-vars)
|
||||
Read **[environment variables reference](/docs/deployment/env-vars)**
|
||||
|
||||
3. Create a Kubernetes deployment
|
||||
|
||||
|
|
@ -39,10 +39,11 @@ If there are self signed HTTPS endpoints that Tooljet needs to connect to, pleas
|
|||
```
|
||||
|
||||
5. Create a Kubernetes services to publish the Kubernetes deployment that you've created. This step varies with cloud providers. We have a [template](https://raw.githubusercontent.com/ToolJet/ToolJet/main/deploy/kubernetes/service.yaml) for exposing the ToolJet server as a service using an AWS loadbalancer.
|
||||
Examples:
|
||||
Application load balancing on Amazon EKS: https://docs.aws.amazon.com/eks/latest/userguide/alb-ingress.html
|
||||
GKE Ingress for HTTP(S) Load Balancing: https://cloud.google.com/kubernetes-engine/docs/concepts/ingress
|
||||
|
||||
**Examples:**
|
||||
- [Application load balancing on Amazon EKS](https://docs.aws.amazon.com/eks/latest/userguide/alb-ingress.html)
|
||||
- [GKE Ingress for HTTP(S) Load Balancing](https://cloud.google.com/kubernetes-engine/docs/concepts/ingress)
|
||||
|
||||
:::tip
|
||||
If you want to serve ToolJet client from services such as Firebase or Netlify, please read the client deployment documentation [here](/docs/deployment/client).
|
||||
:::
|
||||
If you want to serve ToolJet client from services such as Firebase or Netlify, please read the client deployment documentation **[here](/docs/deployment/client)**.
|
||||
:::
|
||||
|
|
@ -4,19 +4,27 @@ sidebar_position: 1
|
|||
|
||||
# Introduction
|
||||
|
||||
ToolJet is an **open-source low-code framework** to build and deploy custom internal tools. ToolJet can connect to your data sources such as databases ( PostgreSQL, MongoDB, MySQL, Elasticsearch, Firestore, DynamoDB, Redis and more ), API endpoints ( ToolJet supports OAuth2 authorization ) and external services ( Stripe, Slack, Google Sheets, airtable and more ). Once the data sources are connected, ToolJet can run queries on these data sources to fetch and update data. The data fetched from data sources can be visualised and modified using the UI widgets such as tables, charts, forms, etc.
|
||||
ToolJet is an **open-source low-code framework** to build and deploy custom internal tools. ToolJet can connect to your data sources such as databases ( PostgreSQL, MongoDB, MySQL, Elasticsearch, Firestore, DynamoDB, Redis and more ), API endpoints ( ToolJet supports OAuth2 authorization ) and external services ( Stripe, Slack, Google Sheets, Airtable and more ). Once the data sources are connected, ToolJet can run queries on these data sources to fetch and update data. The data fetched from data sources can be visualised and modified using the UI widgets such as tables, charts, forms, etc.
|
||||
|
||||
<img class="screenshot-full" src="https://user-images.githubusercontent.com/7828962/144586771-c6d6cba5-8f79-4e0c-80b4-aa1a38657229.png" alt="ToolJet - introduction" />
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
## How ToolJet works
|
||||
|
||||
<img class="screenshot-full" src="/img/how-it-works.png" alt="ToolJet - adding datasources" />
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||
ToolJet has just 3 fundamental principles to build apps:
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
**ToolJet has just 3 fundamental principles to build apps:**
|
||||
|
||||
- **Connect to data sources:** Connect to your existing data sources such as PostgreSQL, MySQL, Firestore, Stripe, Google Sheets, API endpoints, etc.
|
||||
- **Build queries:** ToolJet comes with query builders for all supported data sources. ToolJet also supports the use of custom JavaScript code to transform the query results.
|
||||
- **Customise widgets:** Widgets are the UI components that can be edited using ToolJet's visual app builder ( Eg: tables, charts, forms, etc ) Widgets have events such as `on click`, `on row selected`, `on page changed`, etc. Every UI widget has a dark version.
|
||||
- **Customise widgets:** Widgets are the UI components that can be edited using ToolJet's visual app builder ( Eg: tables, charts, forms, etc ). Widgets have events such as `on click`, `on row selected`, `on page changed`, etc. Every UI widget has a dark version.
|
||||
|
||||
ToolJet binds together the data sources, queries and widgets to convert business logic into custom applications.
|
||||
## Getting Started
|
||||
|
|
@ -27,18 +35,20 @@ These resources will help you to quickly build and deploy apps using ToolJet:
|
|||
- **[Basic Tutorial](/docs/tutorial/creating-app)** - Learn how to build simple UI and connect to data sources.
|
||||
- **[Deploy](/docs/contributing-guide/setup/docker)** - Learn how to deploy ToolJet on Heroku, Kubernetes, etc
|
||||
|
||||
The references for datasources and widgets:
|
||||
The references for data sources and widgets:
|
||||
|
||||
- **[Datasource Reference](/docs/data-sources/redis)**
|
||||
- **[Widget Reference](/docs/widgets/table)**
|
||||
|
||||
## Complete tutorials
|
||||
- [Build a WhatsApp CRM](https://blog.tooljet.com/build-a-whatsapp-crm-using-tooljet-within-10-mins/)
|
||||
- [Build a cryptocurrency dashboard](https://blog.tooljet.com/how-to-build-a-cryptocurrency-dashboard-in-10-minutes/)
|
||||
- [Build a Redis GUI](https://blog.tooljet.com/building-a-redis-gui-using-tooljet-in-5-minutes/)
|
||||
- **[Build a GitHub star history tracker](https://blog.tooljet.com/build-github-stars-history-app-in-5-minutes-using-low-code/)**
|
||||
- **[Build an AWS S3 file explorer app](https://blog.tooljet.com/building-an-app-to-view-and-upload-files-in-aws-s3-bucket/)**
|
||||
- **[Build a WhatsApp CRM](https://blog.tooljet.com/build-a-whatsapp-crm-using-tooljet-within-10-mins/)**
|
||||
- **[Build a cryptocurrency dashboard](https://blog.tooljet.com/how-to-build-a-cryptocurrency-dashboard-in-10-minutes/)**
|
||||
- **[Build a Redis GUI](https://blog.tooljet.com/building-a-redis-gui-using-tooljet-in-5-minutes/)**
|
||||
|
||||
## Help and Support
|
||||
We have extensively documented the features of ToolJet, but in case you are stuck, please feel to mail us: hello@tooljet.com.
|
||||
If you are using ToolJet cloud, click on the chat icon at the bottom-left corner for instant help.
|
||||
If you have found a bug, please create a GitHub issue for the same.
|
||||
Also, feel free to join our highly active [slack community](https://join.slack.com/t/tooljet/shared_invite/zt-r2neyfcw-KD1COL6t2kgVTlTtAV5rtg).
|
||||
- We have extensively documented the features of ToolJet, but in case you are stuck, please feel to e-mail us at **hello@tooljet.com**
|
||||
- If you are using ToolJet cloud, click on the chat icon at the bottom-left corner for instant help.
|
||||
- If you have found a bug, please create a **[GitHub issue](https://github.com/ToolJet/ToolJet/issues)** for the same.
|
||||
- Feel free to join our highly active **[Slack Community](https://join.slack.com/t/tooljet/shared_invite/zt-r2neyfcw-KD1COL6t2kgVTlTtAV5rtg)**.
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ Before triggering `Show modal action` we need to add a modal widget to the canva
|
|||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||

|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -64,13 +64,15 @@ Click on the widget to open the inspect panel on right sidebar. Here you can cha
|
|||
## Connecting data with widget
|
||||
Now we will connect the `data` object of the `fetch customers` query with the table. Click on the table widget to open the inspector on right sidebar. We can see that the data property of the table have an empty array as the value. The data field, like almost every other field on the editor supports single-line javascript code within double brackets. Variable suggestions will be shows as a dropdown while you type the code in the field.
|
||||
|
||||
Let's select `data` object of the 'postgresql' query.
|
||||
Let's select `data` object of the 'postgresql' query.
|
||||
|
||||
` {{queries.postgresql1.data}}`
|
||||
|
||||
Since we have already run the query in previous step, the data will be immediately displayed in the table.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||

|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ Query results can be previewed by clicking the `preview` button. Previewing quer
|
|||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||

|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ The query will now look like this:
|
|||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||

|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ Click the `create` button to create the query. Saved queries can be run using th
|
|||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||

|
||||

|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,6 @@ On select event is triggered when an option is selected.
|
|||
| properties | description |
|
||||
| ----------- | ----------- |
|
||||
| Label | The text is to be used as the label for the multiselect widget. |
|
||||
| Default value | The value of the default option. |
|
||||
| Default value | The value of the default option. This should always be an array. |
|
||||
| Option values | Values for different items/options in the list of the multiselect. |
|
||||
| Option labels | Labels for different items/options in the list of the multiselect. |
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 108 KiB |
BIN
docs/static/img/datasource-reference/aws-s3/aws-s3-modal.png
vendored
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
docs/static/img/datasource-reference/aws-s3/aws-s3-query.png
vendored
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
docs/static/img/datasource-reference/aws-s3/list-buckets.png
vendored
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
docs/static/img/datasource-reference/aws-s3/list-objects.png
vendored
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
docs/static/img/datasource-reference/aws-s3/read-object.png
vendored
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
docs/static/img/datasource-reference/aws-s3/signed-download.png
vendored
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
docs/static/img/datasource-reference/aws-s3/signed-upload.png
vendored
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
docs/static/img/datasource-reference/aws-s3/upload-object.png
vendored
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
docs/static/img/datasource-reference/n8n/basicauth.png
vendored
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
docs/static/img/datasource-reference/n8n/headerauth.png
vendored
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
docs/static/img/datasource-reference/n8n/query.png
vendored
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
docs/static/img/datasource-reference/smtp/connect.png
vendored
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
docs/static/img/datasource-reference/smtp/query1.png
vendored
Normal file
|
After Width: | Height: | Size: 116 KiB |
BIN
docs/static/img/datasource-reference/smtp/query2.png
vendored
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
docs/static/img/introduction/githubstar.png
vendored
Normal file
|
After Width: | Height: | Size: 217 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 11 MiB |
BIN
docs/static/img/tutorial/adding-widget/modal.gif
vendored
Normal file
|
After Width: | Height: | Size: 1 MiB |
|
Before Width: | Height: | Size: 3.9 MiB |
BIN
docs/static/img/tutorial/adding-widget/table-data.png
vendored
Normal file
|
After Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 6.7 MiB |
BIN
docs/static/img/tutorial/building-queries/preview.png
vendored
Normal file
|
After Width: | Height: | Size: 132 KiB |
BIN
docs/static/img/tutorial/transformations/result.gif
vendored
|
Before Width: | Height: | Size: 12 MiB |
BIN
docs/static/img/tutorial/transformations/result.png
vendored
Normal file
|
After Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 5.5 MiB |
BIN
docs/static/img/tutorial/transformations/transform.png
vendored
Normal file
|
After Width: | Height: | Size: 148 KiB |
|
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
"presets": [
|
||||
"@babel/preset-env",
|
||||
"@babel/preset-react"
|
||||
],
|
||||
"plugins": [
|
||||
[
|
||||
"@babel/plugin-proposal-class-properties"
|
||||
],
|
||||
[
|
||||
"console-source",
|
||||
{
|
||||
"segments": 1, // NOT REQUIRED
|
||||
// 0 = full file path (Default)
|
||||
// 1 = file name ONLY
|
||||
// 2 = file name and last segment
|
||||
"splitSegment": "/" // How to split the path - NOT REQUIRED
|
||||
// Default is / for Linux and OSX
|
||||
// Windows users can use "\\" here if needed
|
||||
}
|
||||
],
|
||||
[
|
||||
"@babel/transform-runtime"
|
||||
]
|
||||
]
|
||||
}
|
||||
|
|
@ -2,7 +2,8 @@
|
|||
"env": {
|
||||
"browser": true,
|
||||
"amd": true,
|
||||
"es2021": true
|
||||
"es2021": true,
|
||||
"jest/globals": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
|
|
@ -20,7 +21,7 @@
|
|||
"ecmaVersion": 12,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["react", "prettier"],
|
||||
"plugins": ["react", "prettier", "jest"],
|
||||
"rules": {
|
||||
"prettier/prettier": [
|
||||
"error",
|
||||
|
|
@ -40,7 +41,12 @@
|
|||
"varsIgnorePattern": "^_"
|
||||
}],
|
||||
"react/no-deprecated": 0,
|
||||
"no-prototype-builtins": 0
|
||||
"no-prototype-builtins": 0,
|
||||
"jest/no-disabled-tests": "warn",
|
||||
"jest/no-focused-tests": "error",
|
||||
"jest/no-identical-title": "error",
|
||||
"jest/prefer-to-have-length": "warn",
|
||||
"jest/valid-expect": "error"
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
|
|
|
|||
9
frontend/__mocks__/svg.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
module.exports = {
|
||||
process() {
|
||||
return 'module.exports = {};';
|
||||
},
|
||||
getCacheKey() {
|
||||
// The output is always the same.
|
||||
return 'svgTransform';
|
||||
},
|
||||
};
|
||||
19
frontend/babel.config.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
module.exports = {
|
||||
presets: ['@babel/preset-env', '@babel/preset-react'],
|
||||
plugins: [
|
||||
['@babel/plugin-proposal-class-properties'],
|
||||
[
|
||||
'console-source',
|
||||
{
|
||||
segments: 1, // NOT REQUIRED
|
||||
// 0 = full file path (Default)
|
||||
// 1 = file name ONLY
|
||||
// 2 = file name and last segment
|
||||
splitSegment: '/', // How to split the path - NOT REQUIRED
|
||||
// Default is / for Linux and OSX
|
||||
// Windows users can use "\\" here if needed
|
||||
},
|
||||
],
|
||||
['@babel/transform-runtime'],
|
||||
],
|
||||
};
|
||||
13596
frontend/package-lock.json
generated
|
|
@ -88,9 +88,11 @@
|
|||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-import-resolver-webpack": "^0.13.1",
|
||||
"eslint-plugin-import": "^2.24.2",
|
||||
"eslint-plugin-jest": "^26.1.1",
|
||||
"eslint-plugin-prettier": "^3.4.1",
|
||||
"eslint-plugin-react": "^7.25.2",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"jest": "^27.5.1",
|
||||
"path": "^0.12.7",
|
||||
"prettier": "^2.3.2",
|
||||
"webpack-dev-server": "^3.11.2"
|
||||
|
|
@ -99,7 +101,8 @@
|
|||
"start": "webpack serve --port 8082 --host 0.0.0.0",
|
||||
"build": "webpack --mode production && cp -a ./assets/. ./build/assets/",
|
||||
"lint": "eslint . '**/*.{js,jsx}'",
|
||||
"format": "eslint . --fix '**/*.{js,jsx}'"
|
||||
"format": "eslint . --fix '**/*.{js,jsx}'",
|
||||
"test": "jest"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
|
|
@ -107,5 +110,27 @@
|
|||
"browserslist": {
|
||||
"production": [],
|
||||
"development": []
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
"^.+\\.js?$": "babel-jest",
|
||||
"^.+\\.svg$": "<rootDir>/__mocks__/svg.js"
|
||||
},
|
||||
"transformIgnorePatterns": [
|
||||
"node_modules/(?!(react|mpx-error-boundary)/)"
|
||||
],
|
||||
"moduleNameMapper": {
|
||||
"@/(.*)": "<rootDir>/src/$1"
|
||||
},
|
||||
"testEnvironment": "jest-environment-jsdom",
|
||||
"moduleDirectories": [
|
||||
"node_modules",
|
||||
"src"
|
||||
],
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"jsx"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,12 +21,14 @@ export const AppVersionsManager = function AppVersionsManager({
|
|||
|
||||
useEffect(() => {
|
||||
setCreateAppVersionFrom(editingAppVersion);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [appVersions]);
|
||||
|
||||
useEffect(() => {
|
||||
appVersionService.getAll(appId).then((data) => {
|
||||
setAppVersions(data.versions);
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const wrapperRef = useRef(null);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import React from 'react';
|
||||
import { ToolTip } from './Components/ToolTip';
|
||||
// import { ToolTip } from './Components/ToolTip';
|
||||
import FxButton from './FxButton';
|
||||
|
||||
export const Toggle = ({ value, onChange, paramLabel, forceCodeBox }) => {
|
||||
export const Toggle = ({ value, onChange, forceCodeBox }) => {
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
|
|
|
|||
|
|
@ -172,20 +172,19 @@ export const FilePicker = ({
|
|||
});
|
||||
|
||||
setSelectedFiles(fileData);
|
||||
onComponentOptionChanged(component, 'file', fileData).then(() =>
|
||||
onEvent('onFileSelected', { component }).then(() => {
|
||||
setAccepted(true);
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
return new Promise(function (resolve, reject) {
|
||||
setTimeout(() => {
|
||||
setShowSelectedFiles(true);
|
||||
setAccepted(false);
|
||||
onComponentOptionChanged(component, 'isParsing', false);
|
||||
resolve();
|
||||
}, 600);
|
||||
});
|
||||
})
|
||||
);
|
||||
onComponentOptionChanged(component, 'file', fileData);
|
||||
onEvent('onFileSelected', { component }).then(() => {
|
||||
setAccepted(true);
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
return new Promise(function (resolve, reject) {
|
||||
setTimeout(() => {
|
||||
setShowSelectedFiles(true);
|
||||
setAccepted(false);
|
||||
onComponentOptionChanged(component, 'isParsing', false);
|
||||
resolve();
|
||||
}, 600);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (fileRejections.length > 0) {
|
||||
|
|
@ -231,7 +230,7 @@ export const FilePicker = ({
|
|||
<FilePicker.Signifiers
|
||||
signifier={selectedFiles.length > 0}
|
||||
feedback={acceptedFile.name}
|
||||
cls="text-secondary d-flex justify-content-start file-list"
|
||||
cls="text-secondary d-flex justify-content-start file-list mb-2"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-2 mt-1">
|
||||
|
|
@ -274,7 +273,7 @@ export const FilePicker = ({
|
|||
|
||||
FilePicker.Signifiers = ({ signifier, feedback, cls }) => {
|
||||
if (signifier) {
|
||||
return <center>{feedback === null ? <div className={cls}></div> : <p className={cls}>{feedback}</p>}</center>;
|
||||
return <>{feedback === null ? <div className={cls}></div> : <p className={cls}>{feedback}</p>}</>;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export const Modal = function Modal({
|
|||
keyboard={true}
|
||||
enforceFocus={false}
|
||||
animation={false}
|
||||
onEscapeKeyDown={() => setShowModal(false)}
|
||||
onEscapeKeyDown={() => hideModal()}
|
||||
>
|
||||
{containerProps.mode === 'edit' && (
|
||||
<ConfigHandle id={id} component={component} setSelectedComponent={containerProps.onComponentClick} />
|
||||
|
|
|
|||
|
|
@ -243,12 +243,22 @@ export function Table({
|
|||
return rows.filter((row) => row.values[columnIds[0]] === filterValue.value);
|
||||
}
|
||||
|
||||
if (filterValue.operation === 'ne') {
|
||||
return rows.filter((row) => row.values[columnIds[0]] !== filterValue.value);
|
||||
}
|
||||
|
||||
if (filterValue.operation === 'matches') {
|
||||
return rows.filter((row) =>
|
||||
row.values[columnIds[0]].toString().toLowerCase().includes(filterValue.value.toLowerCase())
|
||||
);
|
||||
}
|
||||
|
||||
if (filterValue.operation === 'nl') {
|
||||
return rows.filter(
|
||||
(row) => !row.values[columnIds[0]].toString().toLowerCase().includes(filterValue.value.toLowerCase())
|
||||
);
|
||||
}
|
||||
|
||||
if (filterValue.operation === 'gt') {
|
||||
return rows.filter((row) => row.values[columnIds[0]] > filterValue.value);
|
||||
}
|
||||
|
|
@ -689,6 +699,7 @@ export function Table({
|
|||
() => [...leftActionsCellData, ...columnData, ...rightActionsCellData],
|
||||
[
|
||||
JSON.stringify(columnData),
|
||||
JSON.stringify(actions),
|
||||
leftActionsCellData.length,
|
||||
rightActionsCellData.length,
|
||||
componentState.changeSet,
|
||||
|
|
@ -1051,7 +1062,9 @@ export function Table({
|
|||
options={[
|
||||
{ name: 'contains', value: 'contains' },
|
||||
{ name: 'matches', value: 'matches' },
|
||||
{ name: 'does not match', value: 'nl' },
|
||||
{ name: 'equals', value: 'equals' },
|
||||
{ name: 'does not equal', value: 'ne' },
|
||||
{ name: 'greater than', value: 'gt' },
|
||||
{ name: 'less than', value: 'lt' },
|
||||
{ name: 'greater than or equals', value: 'gte' },
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ export const Toggle = ({ readOnly, value, onChange, activeColor }) => {
|
|||
onClick={() => {
|
||||
if (!readOnly) toggle();
|
||||
}}
|
||||
disabled={readOnly}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -30,12 +30,14 @@ export const Timer = function Timer({ height, properties = {}, styles, setExpose
|
|||
setState('initial');
|
||||
fireEvent('onCountDownFinish');
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [time]);
|
||||
|
||||
useEffect(() => {
|
||||
intervalId && clearInterval(intervalId);
|
||||
setState('initial');
|
||||
setTime(getTimeObj(getDefaultValue));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [properties.type, getDefaultValue]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -25,8 +25,6 @@ import {
|
|||
setStateAsync,
|
||||
computeComponentState,
|
||||
getSvgIcon,
|
||||
addToLocalStorage,
|
||||
getDataFromLocalStorage,
|
||||
} from '@/_helpers/appUtils';
|
||||
import { Confirm } from './Viewer/Confirm';
|
||||
import ReactTooltip from 'react-tooltip';
|
||||
|
|
@ -45,9 +43,9 @@ import DesktopSelectedIcon from './Icons/desktop-selected.svg';
|
|||
import Modal from 'react-bootstrap/Modal';
|
||||
import Button from 'react-bootstrap/Button';
|
||||
import { AppVersionsManager } from './AppVersionsManager';
|
||||
import * as Driver from 'driver.js';
|
||||
import 'driver.js/dist/driver.min.css';
|
||||
import { SearchBoxComponent } from '@/_ui/Search';
|
||||
import { initEditorWalkThrough } from '@/_helpers/createWalkThrough';
|
||||
import { createWebsocketConnection } from '@/_helpers/websocketConnection';
|
||||
|
||||
setAutoFreeze(false);
|
||||
enablePatches();
|
||||
|
|
@ -58,8 +56,13 @@ class Editor extends React.Component {
|
|||
const appId = this.props.match.params.id;
|
||||
|
||||
const currentUser = authenticationService.currentUserValue;
|
||||
|
||||
const { socket } = createWebsocketConnection(appId);
|
||||
|
||||
let userVars = {};
|
||||
|
||||
this.socket = socket;
|
||||
|
||||
if (currentUser) {
|
||||
userVars = {
|
||||
email: currentUser.email,
|
||||
|
|
@ -114,7 +117,6 @@ class Editor extends React.Component {
|
|||
isDeletingDataQuery: false,
|
||||
showHiddenOptionsForDataQueryId: null,
|
||||
showQueryConfirmation: false,
|
||||
socket: null,
|
||||
showInitVersionCreateModal: false,
|
||||
isCreatingInitVersion: false,
|
||||
initVersionName: 'v1',
|
||||
|
|
@ -139,100 +141,12 @@ class Editor extends React.Component {
|
|||
this.fetchApp();
|
||||
this.initComponentVersioning();
|
||||
this.initEventListeners();
|
||||
config.COMMENT_FEATURE_ENABLE && this.initWebSocket();
|
||||
this.setState({
|
||||
currentSidebarTab: 2,
|
||||
selectedComponent: null,
|
||||
});
|
||||
}
|
||||
|
||||
initWalkThrough() {
|
||||
const driver = new Driver({
|
||||
allowClose: true,
|
||||
closeBtnText: 'Skip',
|
||||
nextBtnText: 'Next',
|
||||
prevBtnText: 'Previous',
|
||||
padding: 2,
|
||||
onReset: () => {
|
||||
// Here we need to write the logic to update walkthroughCompleted column of the current user.
|
||||
addToLocalStorage({ key: 'walkthroughCompleted', value: true });
|
||||
},
|
||||
className: `${this.props.darkMode ? 'dark-theme' : 'light-theme'}-walkthrough`,
|
||||
});
|
||||
|
||||
if (
|
||||
getDataFromLocalStorage('walkthroughCompleted') == undefined ||
|
||||
!getDataFromLocalStorage('walkthroughCompleted')
|
||||
) {
|
||||
driver.defineSteps([
|
||||
{
|
||||
element: '.component-image-holder',
|
||||
popover: {
|
||||
title: 'Drag and drop widgets',
|
||||
description: 'From the widget sidebar, drag and drop widgets to the canvas.',
|
||||
position: 'left',
|
||||
closeBtnText: 'Skip (1/6)',
|
||||
},
|
||||
},
|
||||
{
|
||||
element: '.sidebar-datasources',
|
||||
popover: {
|
||||
title: 'Connect to data sources',
|
||||
description: 'You can manage your data sources from here.',
|
||||
position: 'right',
|
||||
closeBtnText: 'Skip (2/6)',
|
||||
},
|
||||
},
|
||||
{
|
||||
element: '.left-sidebar-inspector',
|
||||
popover: {
|
||||
title: 'Inspector',
|
||||
description: 'Inspector lets you check the properties of widgets, results of queries etc.',
|
||||
position: 'right',
|
||||
closeBtnText: 'Skip (3/6)',
|
||||
},
|
||||
},
|
||||
{
|
||||
element: '.queries-header ',
|
||||
popover: {
|
||||
title: 'Create queries',
|
||||
description:
|
||||
'Create queries to interact with your data sources, run JavaScript snippets and to make API requests.',
|
||||
position: 'top',
|
||||
closeBtnText: 'Skip (4/6)',
|
||||
},
|
||||
},
|
||||
{
|
||||
element: '.release-buttons',
|
||||
popover: {
|
||||
title: 'Preview, release & share',
|
||||
description:
|
||||
'Click on preview to view the current changes on app viewer. Click on share button to view the sharing options. Release the editing version to make the changes live. Released versions cannot be modified, you will have to create another version to make more changes.',
|
||||
position: 'bottom',
|
||||
closeBtnText: 'Skip (5/6)',
|
||||
},
|
||||
},
|
||||
{
|
||||
element: '.sidebar-comments',
|
||||
popover: {
|
||||
title: 'Collaborate',
|
||||
description: 'Add comments on canvas and tag your team members to collaborate.',
|
||||
position: 'right',
|
||||
closeBtnText: 'Skip (6/6)',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
driver.start();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevState.editingVersion == undefined && this.state.editingVersion) {
|
||||
this.initWalkThrough();
|
||||
}
|
||||
}
|
||||
|
||||
isVersionReleased = (version = this.state.editingVersion) => {
|
||||
if (isEmpty(version)) {
|
||||
return false;
|
||||
|
|
@ -291,59 +205,10 @@ class Editor extends React.Component {
|
|||
componentWillUnmount() {
|
||||
document.removeEventListener('mousemove', this.onMouseMove);
|
||||
document.removeEventListener('mouseup', this.onMouseUp);
|
||||
if (this.state.socket) {
|
||||
this.state.socket?.close();
|
||||
}
|
||||
document.title = 'Tooljet - Dashboard';
|
||||
this.socket && this.socket?.close();
|
||||
}
|
||||
|
||||
getWebsocketUrl = () => {
|
||||
const re = /https?:\/\//g;
|
||||
if (re.test(config.apiUrl)) return config.apiUrl.replace(/(^\w+:|^)\/\//, '').replace('/api', '');
|
||||
|
||||
return window.location.host;
|
||||
};
|
||||
|
||||
initWebSocket = () => {
|
||||
// TODO: add retry policy
|
||||
const socket = new WebSocket(`${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${this.getWebsocketUrl()}`);
|
||||
|
||||
const appId = this.props.match.params.id;
|
||||
|
||||
// Connection opened
|
||||
socket.addEventListener('open', function (event) {
|
||||
console.log('connection established', event);
|
||||
const currentUser = JSON.parse(localStorage.getItem('currentUser'));
|
||||
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
event: 'authenticate',
|
||||
data: currentUser.auth_token,
|
||||
})
|
||||
);
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
event: 'subscribe',
|
||||
data: appId,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Connection closed
|
||||
socket.addEventListener('close', function (event) {
|
||||
console.log('connection closed', event);
|
||||
});
|
||||
|
||||
// Listen for possible errors
|
||||
socket.addEventListener('error', function (event) {
|
||||
console.log('WebSocket error: ', event);
|
||||
});
|
||||
|
||||
this.setState({
|
||||
socket,
|
||||
});
|
||||
};
|
||||
|
||||
// 1. When we receive an undoable action – we can always undo but cannot redo anymore.
|
||||
// 2. Whenever you perform an undo – you can always redo and keep doing undo as long as we have a patch for it.
|
||||
// 3. Whenever you redo – you can always undo and keep doing redo as long as we have a patch for it.
|
||||
|
|
@ -912,12 +777,14 @@ class Editor extends React.Component {
|
|||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
handleKeyPress = (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
// eslint-disable-next-line no-undef
|
||||
this.createInitVersion();
|
||||
}
|
||||
};
|
||||
|
||||
createInitVersion = () => {
|
||||
const newVersionName = this.state.initVersionName;
|
||||
const appId = this.state.appId;
|
||||
|
|
@ -925,10 +792,15 @@ class Editor extends React.Component {
|
|||
if (!isEmpty(newVersionName?.trim())) {
|
||||
this.setState({ isCreatingInitVersion: true });
|
||||
appVersionService.create(appId, newVersionName).then(() => {
|
||||
this.setState({
|
||||
showInitVersionCreateModal: false,
|
||||
isCreatingInitVersion: false,
|
||||
});
|
||||
this.setState(
|
||||
{
|
||||
showInitVersionCreateModal: false,
|
||||
isCreatingInitVersion: false,
|
||||
},
|
||||
() => {
|
||||
initEditorWalkThrough();
|
||||
}
|
||||
);
|
||||
toast.success('Version Created');
|
||||
this.fetchApp();
|
||||
});
|
||||
|
|
@ -961,7 +833,6 @@ class Editor extends React.Component {
|
|||
enforceFocus={false}
|
||||
animation={false}
|
||||
centered={true}
|
||||
// eslint-disable-next-line no-undef
|
||||
>
|
||||
<Modal.Header>
|
||||
<Modal.Title>Create Version</Modal.Title>
|
||||
|
|
@ -1211,7 +1082,7 @@ class Editor extends React.Component {
|
|||
<>
|
||||
<Container
|
||||
canvasWidth={this.getCanvasWidth()}
|
||||
socket={this.state.socket}
|
||||
socket={this.socket}
|
||||
showComments={showComments}
|
||||
appVersionsId={this.state?.editingVersion?.id}
|
||||
appDefinition={appDefinition}
|
||||
|
|
@ -1445,7 +1316,7 @@ class Editor extends React.Component {
|
|||
</div>
|
||||
{config.COMMENT_FEATURE_ENABLE && showComments && (
|
||||
<CommentNotifications
|
||||
socket={this.state.socket}
|
||||
socket={this.socket}
|
||||
appVersionsId={this.state?.editingVersion?.id}
|
||||
toggleComments={this.toggleComments}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { allSvgs } from '@tooljet/plugins/client';
|
|||
import { EventManager } from '../Inspector/EventManager';
|
||||
import { CodeHinter } from '../CodeBuilder/CodeHinter';
|
||||
import { DataSourceTypes } from '../DataSourceManager/SourceComponents';
|
||||
import RunjsIcon from '../Icons/runjs.svg';
|
||||
|
||||
const queryNameRegex = new RegExp('^[A-Za-z0-9_-]*$');
|
||||
|
||||
|
|
@ -223,12 +224,21 @@ let QueryManager = class QueryManager extends React.Component {
|
|||
};
|
||||
|
||||
renderDataSourceOption = (props) => {
|
||||
//Todo: add icon for the "runjs" query
|
||||
const Icon = allSvgs[props.kind];
|
||||
return (
|
||||
<div>
|
||||
{Icon && <Icon style={{ height: 25, width: 25 }} />}
|
||||
<span className={`mx-2 ${this.props.darkMode ? 'text-white' : 'text-muted'}`}>{props.label}</span>
|
||||
{props.kind === 'runjs' ? (
|
||||
<RunjsIcon style={{ height: 25, width: 25, marginTop: '-3px' }} />
|
||||
) : (
|
||||
Icon && <Icon style={{ height: 25, width: 25 }} />
|
||||
)}
|
||||
|
||||
<span
|
||||
style={{ height: '25px', display: 'inline-block', marginTop: '3.5px' }}
|
||||
className={`mx-2 ${this.props.darkMode ? 'text-white' : 'text-muted'}`}
|
||||
>
|
||||
{props.label}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -399,7 +409,7 @@ let QueryManager = class QueryManager extends React.Component {
|
|||
}}
|
||||
className={`btn button-family-secondary m-1 float-right1 ${previewLoading ? 'button-loading' : ''} ${
|
||||
this.props.darkMode ? 'dark' : ''
|
||||
} `}
|
||||
} ${this.state.selectedDataSource ? '' : 'disabled'}`}
|
||||
style={{ width: '72px', height: '28px' }}
|
||||
>
|
||||
Preview
|
||||
|
|
@ -409,7 +419,9 @@ let QueryManager = class QueryManager extends React.Component {
|
|||
<button
|
||||
onClick={this.createOrUpdateDataQuery}
|
||||
disabled={buttonDisabled}
|
||||
className={`btn btn-primary m-1 float-right ${isUpdating || isCreating ? 'btn-loading' : ''}`}
|
||||
className={`btn btn-primary m-1 float-right ${isUpdating || isCreating ? 'btn-loading' : ''} ${
|
||||
this.state.selectedDataSource ? '' : 'disabled'
|
||||
}`}
|
||||
style={{ width: '72px', height: '28px' }}
|
||||
>
|
||||
{buttonText}
|
||||
|
|
|
|||
|
|
@ -390,7 +390,7 @@ export const SubContainer = ({
|
|||
id={`canvas-${parent}`}
|
||||
className={`real-canvas ${(isDragging || isResizing) && !readOnly ? ' show-grid' : ''}`}
|
||||
>
|
||||
{Object.keys(childComponents).map((key, index) => (
|
||||
{Object.keys(childComponents).map((key) => (
|
||||
<DraggableBox
|
||||
onComponentClick={onComponentClick}
|
||||
onEvent={onEvent}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ export default function AppCard({
|
|||
|
||||
useEffect(() => {
|
||||
!isMenuOpen && setFocused(!!isHovered);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isHovered]);
|
||||
|
||||
const updated = moment(app.created_at).fromNow(true);
|
||||
|
|
|
|||
|
|
@ -201,9 +201,18 @@ const DynamicForm = ({
|
|||
return (
|
||||
<>
|
||||
<div className="row">
|
||||
<div className="col-md-12 my-2">
|
||||
{flipComponentDropdown.commonFields && getLayout(flipComponentDropdown.commonFields)}
|
||||
<div
|
||||
className={cx('my-2', {
|
||||
'col-md-12': !flipComponentDropdown.className,
|
||||
[flipComponentDropdown.className]: !!flipComponentDropdown.className,
|
||||
})}
|
||||
>
|
||||
{flipComponentDropdown.label && <label className="form-label">{flipComponentDropdown.label}</label>}
|
||||
<Select {...getElementProps(flipComponentDropdown)} />
|
||||
{flipComponentDropdown.helpText && (
|
||||
<span className="flip-dropdown-help-text">{flipComponentDropdown.helpText}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{getLayout(obj[selector])}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@ import { Link } from 'react-router-dom';
|
|||
import { authenticationService } from '@/_services';
|
||||
import { history } from '@/_helpers';
|
||||
import { DarkModeToggle } from './DarkModeToggle';
|
||||
import cx from 'classnames';
|
||||
|
||||
import LogoIcon from '../Editor/Icons/logo.svg';
|
||||
|
||||
export const Header = function Header({ switchDarkMode, darkMode }) {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [pathName, setPathName] = useState(document.location.pathname);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
113
frontend/src/_helpers/__tests__/appUtils.test.js
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
const { getQueryVariables, runQuery, setTablePageIndex, computeComponentState } = require('../appUtils.js');
|
||||
|
||||
jest.mock(
|
||||
'config',
|
||||
() => {
|
||||
return {
|
||||
apiUrl: `http://localhost:3000/api`,
|
||||
SERVER_IP: process.env.SERVER_IP,
|
||||
COMMENT_FEATURE_ENABLE: true,
|
||||
};
|
||||
},
|
||||
{ virtual: true }
|
||||
);
|
||||
|
||||
describe('getQueryVariables method', () => {
|
||||
test('Returns empty object.', async () => {
|
||||
const options = []; // not one.of(string, object) which are the available options
|
||||
|
||||
await expect(getQueryVariables(options)).toStrictEqual({});
|
||||
});
|
||||
|
||||
test('Tests when options type is string', async () => {
|
||||
const options = 'options type is string';
|
||||
|
||||
await expect(getQueryVariables(options)).toStrictEqual({});
|
||||
});
|
||||
|
||||
test('Tests when options type is object', async () => {
|
||||
const options = { key: 'value' };
|
||||
|
||||
await expect(getQueryVariables(options)).toStrictEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('runQuery method', () => {
|
||||
test('Returns undefined when no query has been associated with the action.', async () => {
|
||||
const _ref = {
|
||||
state: {
|
||||
app: {
|
||||
data_queries: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await expect(runQuery(_ref)).toBe(undefined);
|
||||
});
|
||||
|
||||
test('Returns undefined when requestConfirmation is true.', async () => {
|
||||
const _ref = {
|
||||
state: {
|
||||
app: {
|
||||
data_queries: [{ id: 1, options: { requestConfirmation: true } }],
|
||||
},
|
||||
},
|
||||
setState: jest.fn(),
|
||||
};
|
||||
|
||||
await expect(runQuery(_ref, 1)).toBe(undefined);
|
||||
});
|
||||
|
||||
// test('Tests when kind=runjs.', async () => {
|
||||
// const _ref = {
|
||||
// state: {
|
||||
// app: {
|
||||
// data_queries: [{ id: 1, kind: 'runjs', options: { requestConfirmation: false } }],
|
||||
// },
|
||||
// currentState: {
|
||||
// queries: {},
|
||||
// },
|
||||
// },
|
||||
// setState: jest.fn(() => {}),
|
||||
// };
|
||||
|
||||
// // Unable to call the callback function in setState ..
|
||||
// await expect(runQuery(_ref, 1)).resolves.toBe({});
|
||||
// });
|
||||
});
|
||||
|
||||
describe('setTablePageIndex method', () => {
|
||||
test('Returns undefined when no table is associated with this event', async () => {
|
||||
const _ref = {
|
||||
state: {
|
||||
app: {
|
||||
data_queries: [],
|
||||
},
|
||||
currentState: {
|
||||
components: {},
|
||||
},
|
||||
},
|
||||
setState: jest.fn(() => {}),
|
||||
};
|
||||
|
||||
await expect(setTablePageIndex(_ref)).resolves.toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('computeComponentState method', () => {
|
||||
test('Returns default', async () => {
|
||||
const _ref = {
|
||||
state: {
|
||||
app: {
|
||||
data_queries: [],
|
||||
},
|
||||
currentState: {
|
||||
components: {},
|
||||
},
|
||||
},
|
||||
setState: (_, resolve) => resolve('resolved'),
|
||||
};
|
||||
|
||||
await expect(computeComponentState(_ref)).resolves.toBe('resolved');
|
||||
});
|
||||
});
|
||||
|
|
@ -464,7 +464,7 @@ export async function onEvent(_ref, eventName, options, mode = 'edit') {
|
|||
}
|
||||
}
|
||||
|
||||
function getQueryVariables(options, state) {
|
||||
export function getQueryVariables(options, state) {
|
||||
let queryVariables = {};
|
||||
const optionsType = typeof options;
|
||||
switch (optionsType) {
|
||||
|
|
@ -591,6 +591,7 @@ export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode)
|
|||
_self.setState({ currentState: newState }, () => {
|
||||
let queryExecutionPromise = null;
|
||||
if (query.kind === 'runjs') {
|
||||
console.log('here');
|
||||
queryExecutionPromise = executeMultilineJS(_self.state.currentState, query.options.code);
|
||||
} else {
|
||||
queryExecutionPromise = dataqueryService.run(queryId, options);
|
||||
|
|
@ -732,7 +733,7 @@ export function runQuery(_ref, queryId, queryName, confirmed = undefined, mode)
|
|||
});
|
||||
}
|
||||
|
||||
function setTablePageIndex(_ref, tableId, index) {
|
||||
export function setTablePageIndex(_ref, tableId, index) {
|
||||
if (_.isEmpty(tableId)) {
|
||||
console.log('No table is associated with this event.');
|
||||
return Promise.resolve();
|
||||
|
|
@ -752,7 +753,7 @@ export function renderTooltip({ props, text }) {
|
|||
);
|
||||
}
|
||||
|
||||
export function computeComponentState(_ref, components) {
|
||||
export function computeComponentState(_ref, components = {}) {
|
||||
let componentState = {};
|
||||
const currentComponents = _ref.state.currentState.components;
|
||||
Object.keys(components).forEach((key) => {
|
||||
|
|
|
|||
85
frontend/src/_helpers/createWalkThrough.js
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import * as Driver from 'driver.js';
|
||||
import { addToLocalStorage, getDataFromLocalStorage } from '@/_helpers/appUtils';
|
||||
import 'driver.js/dist/driver.min.css';
|
||||
|
||||
export const initEditorWalkThrough = () => {
|
||||
if (
|
||||
getDataFromLocalStorage('walkthroughCompleted') == undefined ||
|
||||
!getDataFromLocalStorage('walkthroughCompleted')
|
||||
) {
|
||||
const darkMode = getDataFromLocalStorage('darkMode') === 'true';
|
||||
const driver = new Driver({
|
||||
allowClose: true,
|
||||
closeBtnText: 'Skip',
|
||||
nextBtnText: 'Next',
|
||||
prevBtnText: 'Previous',
|
||||
padding: 2,
|
||||
onReset: () => {
|
||||
// Here we need to write the logic to update walkthroughCompleted column of the current user.
|
||||
addToLocalStorage({ key: 'walkthroughCompleted', value: true });
|
||||
},
|
||||
className: `${darkMode ? 'dark-theme' : 'light-theme'}-walkthrough`,
|
||||
});
|
||||
|
||||
driver.defineSteps([
|
||||
{
|
||||
element: '.component-image-holder',
|
||||
popover: {
|
||||
title: 'Drag and drop widgets',
|
||||
description: 'From the widget sidebar, drag and drop widgets to the canvas.',
|
||||
position: 'left',
|
||||
closeBtnText: 'Skip (1/6)',
|
||||
},
|
||||
},
|
||||
{
|
||||
element: '.sidebar-datasources',
|
||||
popover: {
|
||||
title: 'Connect to data sources',
|
||||
description: 'You can manage your data sources from here.',
|
||||
position: 'right',
|
||||
closeBtnText: 'Skip (2/6)',
|
||||
},
|
||||
},
|
||||
{
|
||||
element: '.left-sidebar-inspector',
|
||||
popover: {
|
||||
title: 'Inspector',
|
||||
description: 'Inspector lets you check the properties of widgets, results of queries etc.',
|
||||
position: 'right',
|
||||
closeBtnText: 'Skip (3/6)',
|
||||
},
|
||||
},
|
||||
{
|
||||
element: '.queries-header ',
|
||||
popover: {
|
||||
title: 'Create queries',
|
||||
description:
|
||||
'Create queries to interact with your data sources, run JavaScript snippets and to make API requests.',
|
||||
position: 'top',
|
||||
closeBtnText: 'Skip (4/6)',
|
||||
},
|
||||
},
|
||||
{
|
||||
element: '.release-buttons',
|
||||
popover: {
|
||||
title: 'Preview, release & share',
|
||||
description:
|
||||
'Click on preview to view the current changes on app viewer. Click on share button to view the sharing options. Release the editing version to make the changes live. Released versions cannot be modified, you will have to create another version to make more changes.',
|
||||
position: 'bottom',
|
||||
closeBtnText: 'Skip (5/6)',
|
||||
},
|
||||
},
|
||||
{
|
||||
element: '.sidebar-comments',
|
||||
popover: {
|
||||
title: 'Collaborate',
|
||||
description: 'Add comments on canvas and tag your team members to collaborate.',
|
||||
position: 'right',
|
||||
closeBtnText: 'Skip (6/6)',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
driver.start();
|
||||
}
|
||||
};
|
||||
45
frontend/src/_helpers/websocketConnection.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import config from 'config';
|
||||
|
||||
class WebSocketConnection {
|
||||
constructor(appId) {
|
||||
this.socket = new WebSocket(`${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${this.getWebsocketUrl()}`);
|
||||
|
||||
this.addListeners(appId);
|
||||
}
|
||||
|
||||
getWebsocketUrl() {
|
||||
const re = /https?:\/\//g;
|
||||
if (re.test(config.apiUrl)) return config.apiUrl.replace(/(^\w+:|^)\/\//, '').replace('/api', '');
|
||||
|
||||
return window.location.host;
|
||||
}
|
||||
|
||||
addListeners(appId) {
|
||||
// Connection opened
|
||||
this.socket.addEventListener('open', (event) => {
|
||||
console.log('connection established', event);
|
||||
const currentUser = JSON.parse(localStorage.getItem('currentUser'));
|
||||
|
||||
this.socket.send(
|
||||
JSON.stringify({
|
||||
event: 'authenticate',
|
||||
data: currentUser.auth_token,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// Connection closed
|
||||
this.socket.addEventListener('close', (event) => {
|
||||
console.log('connection closed', event);
|
||||
});
|
||||
|
||||
// Listen for possible errors
|
||||
this.socket.addEventListener('error', (event) => {
|
||||
console.log('WebSocket error: ', event);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const createWebsocketConnection = (appId) => {
|
||||
return new WebSocketConnection(appId);
|
||||
};
|
||||
|
|
@ -163,7 +163,7 @@ button {
|
|||
}
|
||||
|
||||
.query-name-field input {
|
||||
max-width: 120px;
|
||||
max-width: 180px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
|
@ -1094,9 +1094,9 @@ button {
|
|||
background-color: #2c405c;
|
||||
|
||||
button.edit-button {
|
||||
background: #ffffff;
|
||||
border: 1px solid #4d72fa;
|
||||
color: #4d72fa;
|
||||
background: transparent;
|
||||
border: 1px solid #ffffff;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
button.launch-button {
|
||||
|
|
@ -2808,7 +2808,7 @@ input:focus-visible {
|
|||
|
||||
.editor-sidebar {
|
||||
border: solid rgba(255, 255, 255, 0.09);
|
||||
border-width: 0px 0px 0px 1px !important;
|
||||
border-width: 0px 0px 0px 0px !important;
|
||||
|
||||
.nav-tabs {
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.09) !important;
|
||||
|
|
@ -4425,4 +4425,11 @@ div#driver-highlighted-element-stage, div#driver-page-overlay {
|
|||
}
|
||||
.tablr-gutter-x-0 {
|
||||
--tblr-gutter-x: 0!important;
|
||||
}
|
||||
}
|
||||
|
||||
.flip-dropdown-help-text{
|
||||
padding: 10px 5px 0 0;
|
||||
float: left;
|
||||
font-size: 14px;
|
||||
color: $light-gray;
|
||||
}
|
||||
|
|
|
|||
77
plugins/package-lock.json
generated
|
|
@ -21,12 +21,14 @@
|
|||
"@tooljet-plugins/mongodb": "file:packages/mongodb",
|
||||
"@tooljet-plugins/mssql": "file:packages/mssql",
|
||||
"@tooljet-plugins/mysql": "file:packages/mysql",
|
||||
"@tooljet-plugins/n8n": "file:packages/n8n",
|
||||
"@tooljet-plugins/postgresql": "file:packages/postgresql",
|
||||
"@tooljet-plugins/redis": "file:packages/redis",
|
||||
"@tooljet-plugins/restapi": "file:packages/restapi",
|
||||
"@tooljet-plugins/s3": "file:packages/s3",
|
||||
"@tooljet-plugins/sendgrid": "file:packages/sendgrid",
|
||||
"@tooljet-plugins/slack": "file:packages/slack",
|
||||
"@tooljet-plugins/smtp": "file:packages/smtp",
|
||||
"@tooljet-plugins/snowflake": "file:packages/snowflake",
|
||||
"@tooljet-plugins/stripe": "file:packages/stripe",
|
||||
"@tooljet-plugins/twilio": "file:packages/twilio",
|
||||
|
|
@ -4438,6 +4440,10 @@
|
|||
"resolved": "packages/mysql",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@tooljet-plugins/n8n": {
|
||||
"resolved": "packages/n8n",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@tooljet-plugins/postgresql": {
|
||||
"resolved": "packages/postgresql",
|
||||
"link": true
|
||||
|
|
@ -4462,6 +4468,10 @@
|
|||
"resolved": "packages/slack",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@tooljet-plugins/smtp": {
|
||||
"resolved": "packages/smtp",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@tooljet-plugins/snowflake": {
|
||||
"resolved": "packages/snowflake",
|
||||
"link": true
|
||||
|
|
@ -4599,6 +4609,15 @@
|
|||
"form-data": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/nodemailer": {
|
||||
"version": "6.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.4.tgz",
|
||||
"integrity": "sha512-Ksw4t7iliXeYGvIQcSIgWQ5BLuC/mljIEbjf615svhZL10PE9t+ei8O9gDaD3FPCasUJn9KTLwz2JFJyiiyuqw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/normalize-package-data": {
|
||||
"version": "2.4.1",
|
||||
"dev": true,
|
||||
|
|
@ -11448,6 +11467,14 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nodemailer": {
|
||||
"version": "6.7.2",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.2.tgz",
|
||||
"integrity": "sha512-Dz7zVwlef4k5R71fdmxwR8Q39fiboGbu3xgswkzGwczUfjp873rVxt1O46+Fh0j1ORnAC6L9+heI8uUpO6DT7Q==",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nopt": {
|
||||
"version": "4.0.3",
|
||||
"dev": true,
|
||||
|
|
@ -15478,6 +15505,14 @@
|
|||
"rimraf": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"packages/n8n": {
|
||||
"name": "@tooljet-plugins/n8n",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@tooljet-plugins/common": "file:../common",
|
||||
"react": "^17.0.2"
|
||||
}
|
||||
},
|
||||
"packages/postgresql": {
|
||||
"name": "@tooljet-plugins/postgresql",
|
||||
"version": "1.0.0",
|
||||
|
|
@ -15540,6 +15575,18 @@
|
|||
"rimraf": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"packages/smtp": {
|
||||
"name": "@tooljet-plugins/smtp",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@tooljet-plugins/common": "file:../common",
|
||||
"nodemailer": "^6.7.2",
|
||||
"react": "^17.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/nodemailer": "^6.4.4"
|
||||
}
|
||||
},
|
||||
"packages/snowflake": {
|
||||
"name": "@tooljet-plugins/snowflake",
|
||||
"version": "1.0.0",
|
||||
|
|
@ -19084,6 +19131,13 @@
|
|||
"rimraf": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"@tooljet-plugins/n8n": {
|
||||
"version": "file:packages/n8n",
|
||||
"requires": {
|
||||
"@tooljet-plugins/common": "file:../common",
|
||||
"react": "^17.0.2"
|
||||
}
|
||||
},
|
||||
"@tooljet-plugins/postgresql": {
|
||||
"version": "file:packages/postgresql",
|
||||
"requires": {
|
||||
|
|
@ -19140,6 +19194,15 @@
|
|||
"rimraf": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"@tooljet-plugins/smtp": {
|
||||
"version": "file:packages/smtp",
|
||||
"requires": {
|
||||
"@tooljet-plugins/common": "file:../common",
|
||||
"@types/nodemailer": "^6.4.4",
|
||||
"nodemailer": "^6.7.2",
|
||||
"react": "^17.0.2"
|
||||
}
|
||||
},
|
||||
"@tooljet-plugins/snowflake": {
|
||||
"version": "file:packages/snowflake",
|
||||
"requires": {
|
||||
|
|
@ -19278,6 +19341,15 @@
|
|||
"form-data": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"@types/nodemailer": {
|
||||
"version": "6.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.4.tgz",
|
||||
"integrity": "sha512-Ksw4t7iliXeYGvIQcSIgWQ5BLuC/mljIEbjf615svhZL10PE9t+ei8O9gDaD3FPCasUJn9KTLwz2JFJyiiyuqw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/normalize-package-data": {
|
||||
"version": "2.4.1",
|
||||
"dev": true
|
||||
|
|
@ -23963,6 +24035,11 @@
|
|||
"version": "2.0.1",
|
||||
"dev": true
|
||||
},
|
||||
"nodemailer": {
|
||||
"version": "6.7.2",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.2.tgz",
|
||||
"integrity": "sha512-Dz7zVwlef4k5R71fdmxwR8Q39fiboGbu3xgswkzGwczUfjp873rVxt1O46+Fh0j1ORnAC6L9+heI8uUpO6DT7Q=="
|
||||
},
|
||||
"nopt": {
|
||||
"version": "4.0.3",
|
||||
"dev": true,
|
||||
|
|
|
|||
|
|
@ -28,12 +28,14 @@
|
|||
"@tooljet-plugins/mongodb": "file:packages/mongodb",
|
||||
"@tooljet-plugins/mssql": "file:packages/mssql",
|
||||
"@tooljet-plugins/mysql": "file:packages/mysql",
|
||||
"@tooljet-plugins/n8n": "file:packages/n8n",
|
||||
"@tooljet-plugins/postgresql": "file:packages/postgresql",
|
||||
"@tooljet-plugins/redis": "file:packages/redis",
|
||||
"@tooljet-plugins/restapi": "file:packages/restapi",
|
||||
"@tooljet-plugins/s3": "file:packages/s3",
|
||||
"@tooljet-plugins/sendgrid": "file:packages/sendgrid",
|
||||
"@tooljet-plugins/slack": "file:packages/slack",
|
||||
"@tooljet-plugins/smtp": "file:packages/smtp",
|
||||
"@tooljet-plugins/snowflake": "file:packages/snowflake",
|
||||
"@tooljet-plugins/stripe": "file:packages/stripe",
|
||||
"@tooljet-plugins/twilio": "file:packages/twilio",
|
||||
|
|
|
|||
4
plugins/packages/n8n/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
node_modules
|
||||
lib/*.d.*
|
||||
lib/*.js
|
||||
lib/*.js.map
|
||||
4
plugins/packages/n8n/README.md
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
# N8n
|
||||
|
||||
Documentation on: https://docs.tooljet.com/docs/data-sources/n8n
|
||||
7
plugins/packages/n8n/__tests_/index.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const n8n = require('../lib');
|
||||
|
||||
describe('n8n', () => {
|
||||
it.todo('needs tests');
|
||||
});
|
||||
22
plugins/packages/n8n/lib/icon.svg
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 500 500" style="enable-background:new 0 0 500 500;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FF6D5A;}
|
||||
</style>
|
||||
<path class="st0" d="M448.22,122.54c-23.84,0-43.89,16.42-49.55,38.52h-71.31c-27.98,0-50.75,22.76-50.75,50.75
|
||||
c0,13.98-11.37,25.37-25.37,25.37h-10.17c-5.66-22.1-25.69-38.52-49.55-38.52c-23.84,0-43.89,16.42-49.55,38.52h-40.74
|
||||
c-5.66-22.1-25.69-38.52-49.55-38.52c-28.23,0-51.21,22.96-51.21,51.21c0,28.23,22.96,51.21,51.21,51.21
|
||||
c23.84,0,43.89-16.42,49.55-38.52h40.79c5.66,22.1,25.69,38.52,49.55,38.52c23.69,0,43.6-16.18,49.43-38.06h10.27
|
||||
c13.98,0,25.37,11.37,25.37,25.37c0,27.98,22.76,50.75,50.75,50.75h16.61c5.66,22.1,25.69,38.52,49.55,38.52
|
||||
c28.23,0,51.21-22.96,51.21-51.21c0-28.23-22.96-51.21-51.21-51.21c-23.84,0-43.89,16.42-49.55,38.52h-16.61
|
||||
c-13.98,0-25.37-11.37-25.37-25.37c0-15.27-6.83-28.98-17.54-38.28c10.73-9.32,17.54-23.01,17.54-38.28
|
||||
c0-13.98,11.37-25.37,25.37-25.37h71.31c5.66,22.1,25.69,38.52,49.55,38.52c28.23,0,51.21-22.96,51.21-51.21
|
||||
C499.43,145.52,476.45,122.54,448.22,122.54z M51.71,275.68c-14.25,0-25.84-11.59-25.84-25.84c0-14.25,11.59-25.84,25.84-25.84
|
||||
s25.84,11.59,25.84,25.84C77.55,264.09,65.96,275.68,51.71,275.68z M191.56,275.68c-14.25,0-25.84-11.59-25.84-25.84
|
||||
c0-14.25,11.59-25.84,25.84-25.84s25.84,11.59,25.84,25.84C217.39,264.09,205.81,275.68,191.56,275.68z M393.52,300.59
|
||||
c14.25,0,25.84,11.59,25.84,25.84s-11.59,25.84-25.84,25.84c-14.25,0-25.84-11.59-25.84-25.84
|
||||
C367.71,312.18,379.3,300.59,393.52,300.59z M448.22,199.59c-14.25,0-25.84-11.59-25.84-25.84c0-14.25,11.59-25.84,25.84-25.84
|
||||
s25.84,11.59,25.84,25.84C474.06,188,462.47,199.59,448.22,199.59z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
81
plugins/packages/n8n/lib/index.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import { QueryError, QueryResult, QueryService, ConnectionTestResult, parseJson } from '@tooljet-plugins/common';
|
||||
import { SourceOptions, QueryOptions } from './types';
|
||||
import got from 'got';
|
||||
import { HTTPError, OptionsOfTextResponseBody } from 'got';
|
||||
|
||||
const constructHeaders = (sourceOptions: SourceOptions) =>{
|
||||
let headers = {};
|
||||
if(sourceOptions.auth_type === 'header'){
|
||||
headers[sourceOptions.name] = sourceOptions.value
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
const extractJsonData = (content:any)=>{
|
||||
return typeof content === 'string' ? JSON.parse(content) : content;
|
||||
}
|
||||
|
||||
export default class N8n implements QueryService {
|
||||
async run(sourceOptions: SourceOptions, queryOptions: QueryOptions, dataSourceId: string): Promise<QueryResult> {
|
||||
const authType = sourceOptions.auth_type;
|
||||
const headers = constructHeaders(sourceOptions);
|
||||
|
||||
const operation = queryOptions.method;
|
||||
const url = queryOptions.url;
|
||||
const url_params = queryOptions.url_params;
|
||||
const body = queryOptions.body;
|
||||
|
||||
let result = {};
|
||||
|
||||
// Remove invalid headers from the headers object
|
||||
Object.keys(headers).forEach((key) => (headers[key] === '' ? delete headers[key] : {}));
|
||||
|
||||
const paramsContent = url_params ? extractJsonData(url_params) : '';
|
||||
const bodyContent = body ? extractJsonData(body) : '';
|
||||
|
||||
const constructPayload = (method:string) : OptionsOfTextResponseBody => {
|
||||
return {
|
||||
method: method === 'post' ? 'POST' : 'GET',
|
||||
headers: headers,
|
||||
username: authType === 'basic' && sourceOptions.username,
|
||||
password: authType === 'basic' && sourceOptions.password,
|
||||
searchParams: paramsContent,
|
||||
json: method === 'post' ? bodyContent : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
switch(operation){
|
||||
case 'post' : {
|
||||
if(bodyContent === ''){
|
||||
throw new Error("Please provide body content");
|
||||
}
|
||||
const response = await got(url, constructPayload('post'));
|
||||
result = JSON.parse(response.body);
|
||||
break;
|
||||
}
|
||||
case 'get': {
|
||||
const response = await got(url,constructPayload('get'));
|
||||
result = JSON.parse(response.body);
|
||||
break;
|
||||
}
|
||||
default : {
|
||||
throw new Error("Select a method");
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
if (error instanceof HTTPError) {
|
||||
result = {
|
||||
code: error.code,
|
||||
};
|
||||
}
|
||||
throw new QueryError('Query could not be completed', error.message, result);
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'ok',
|
||||
data: result,
|
||||
};
|
||||
}
|
||||
}
|
||||
85
plugins/packages/n8n/lib/manifest.json
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/ToolJet/ToolJet/develop/plugins/schemas/manifest.schema.json",
|
||||
"title": "n8n datasource",
|
||||
"description": "A schema defining N8n datasource",
|
||||
"type": "api",
|
||||
"source": {
|
||||
"name": "n8n",
|
||||
"kind": "n8n",
|
||||
"exposedVariables": {
|
||||
"isLoading": false,
|
||||
"data": {},
|
||||
"rawData": {}
|
||||
},
|
||||
"options": {
|
||||
"password": {
|
||||
"encrypted": true
|
||||
},
|
||||
"name": {
|
||||
"encrypted": true
|
||||
},
|
||||
"value": {
|
||||
"encrypted": true
|
||||
}
|
||||
},
|
||||
"customTesting": true
|
||||
},
|
||||
"defaults": {
|
||||
"auth_type": {"value":"none"}
|
||||
},
|
||||
"properties": {
|
||||
"auth_type": {
|
||||
"label": "Authentication Type",
|
||||
"key": "auth_type",
|
||||
"type": "dropdown-component-flip",
|
||||
"description": "Select auth type",
|
||||
"helpText": "Webhook credentials and instance credentials are different. Please use the credentials that you use with the Webhook trigger.",
|
||||
"list": [
|
||||
{
|
||||
"value": "none",
|
||||
"name": "None"
|
||||
},
|
||||
{
|
||||
"value": "basic",
|
||||
"name": "Basic Auth"
|
||||
},
|
||||
{
|
||||
"value": "header",
|
||||
"name": "Header Auth"
|
||||
}
|
||||
]
|
||||
},
|
||||
"basic":{
|
||||
"username": {
|
||||
"label": "Username",
|
||||
"key": "username",
|
||||
"type": "text",
|
||||
"description": "Enter username"
|
||||
},
|
||||
"password": {
|
||||
"label": "Password",
|
||||
"key": "password",
|
||||
"type": "password",
|
||||
"description": "Enter username"
|
||||
}
|
||||
},
|
||||
"header":{
|
||||
"name": {
|
||||
"label": "Name",
|
||||
"key": "name",
|
||||
"type": "text",
|
||||
"encrypted": true,
|
||||
"description": "Enter key name"
|
||||
},
|
||||
"value": {
|
||||
"label": "Value",
|
||||
"key": "value",
|
||||
"type": "text",
|
||||
"encrypted": true,
|
||||
"description": "Enter value"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
}
|
||||
69
plugins/packages/n8n/lib/operations.json
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"$schema": "https://raw.githubusercontent.com/ToolJet/ToolJet/develop/plugins/schemas/operations.schema.json",
|
||||
"title": "N8n datasource",
|
||||
"description": "A schema defining N8n datasource",
|
||||
"type": "api",
|
||||
"defaults": {},
|
||||
"properties": {
|
||||
"method": {
|
||||
"label": "Method",
|
||||
"key": "method",
|
||||
"className":"col-md-4",
|
||||
"type": "dropdown-component-flip",
|
||||
"description": "Single request type",
|
||||
"list": [
|
||||
{
|
||||
"value": "get",
|
||||
"name": "GET"
|
||||
},
|
||||
{
|
||||
"value": "post",
|
||||
"name": "POST"
|
||||
}
|
||||
],
|
||||
"commonFields":{
|
||||
"url":{
|
||||
"label": "Webhook URL",
|
||||
"key": "url",
|
||||
"type": "codehinter",
|
||||
"lineNumbers": false,
|
||||
"height": "36px",
|
||||
"className": "codehinter-plugins",
|
||||
"placeholder": "webhook URL of your workflow",
|
||||
"description": "Field for url"
|
||||
}
|
||||
}
|
||||
},
|
||||
"get":{
|
||||
"parameters": {
|
||||
"label": "URL parameters",
|
||||
"key": "url_params",
|
||||
"type": "codehinter",
|
||||
"mode": "javascript",
|
||||
"placeholder": "{ \"name\": \"bob\" }",
|
||||
"description": "Enter options",
|
||||
"height": "150px"
|
||||
}
|
||||
},
|
||||
"post":{
|
||||
"parameters": {
|
||||
"label": "URL parameters",
|
||||
"key": "url_params",
|
||||
"type": "codehinter",
|
||||
"mode": "javascript",
|
||||
"placeholder": "{ \"name\": \"bob\" }",
|
||||
"description": "Enter url parameters",
|
||||
"height": "150px"
|
||||
},
|
||||
"body": {
|
||||
"label": "Body",
|
||||
"key": "body",
|
||||
"type": "codehinter",
|
||||
"mode": "javascript",
|
||||
"placeholder": "{ \"age\": \"12\" }",
|
||||
"description": "Enter body parameters",
|
||||
"height": "150px"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
plugins/packages/n8n/lib/types.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export type SourceOptions = { auth_type:string; username: string; password: string; name:string; value:string; };
|
||||
export type QueryOptions = {
|
||||
url:string,
|
||||
method: string;
|
||||
url_params: string;
|
||||
body: string;
|
||||
};
|
||||
23
plugins/packages/n8n/package.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"name": "@tooljet-plugins/n8n",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"directories": {
|
||||
"lib": "lib",
|
||||
"test": "__tests__"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "echo \"Error: run tests from root\" && exit 1",
|
||||
"build": "tsc -b",
|
||||
"clean": "rimraf ./dist && rimraf tsconfig.tsbuildinfo"
|
||||
},
|
||||
"homepage": "https://github.com/tooljet/tooljet#readme",
|
||||
"dependencies": {
|
||||
"@tooljet-plugins/common": "file:../common",
|
||||
"react": "^17.0.2"
|
||||
}
|
||||
}
|
||||
11
plugins/packages/n8n/tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "lib"
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||