Merge pull request #13671 from ToolJet/merge-to-main/3.16.1-mgs

Merge to main v3.16.1-lts
This commit is contained in:
Midhun G S 2025-08-05 12:48:58 +05:30 committed by GitHub
commit 4cfd39afe6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
62 changed files with 1094 additions and 217 deletions

View file

@ -127,7 +127,7 @@ jobs:
SENTRY_PROJECT: ${{ secrets.CLOUD_PROD_CLOUD_SENTRY_PROJECT }}
SERVE_CLIENT: ${{ secrets.CLOUD_PROD_CLOUD_SERVE_CLIENT }}
SERVER_IP: ${{ secrets.CLOUD_PROD_CLOUD_SERVER_IP }}
TJDB_SQL_MODE_DISABLE: ${{ secrets.CLOUD_TJDB_SQL_MODE_DISABLE }}
TOOLJET_SERVER_URL: ${{ secrets.CLOUD_TOOLJET_SERVER_URL }}
TJDB_SQL_MODE_DISABLE: ${{ secrets.CLOUD_PROD_TJDB_SQL_MODE_DISABLE }}
TOOLJET_SERVER_URL: ${{ secrets.CLOUD_PROD_TOOLJET_SERVER_URL }}
WEBSITE_SIGNUP_URL: https://tooljet.ai/ai-create-account
TOOLJET_EDITION: cloud

View file

@ -10,10 +10,10 @@ on:
dockerfile_path:
description: 'Path to Dockerfile'
required: true
default: './docker/cloud/cloud-server.Dockerfile'
default: './docker/LTS/cloud/cloud-server.Dockerfile'
type: choice
options:
- ./docker/cloud/cloud-server.Dockerfile
- ./docker/LTS/cloud/cloud-server.Dockerfile
docker_tag:
description: 'Docker tag suffix (e.g., cloud-staging-v14)'
required: true

View file

@ -81,17 +81,6 @@ jobs:
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$message\"}" ${{ secrets.SLACK_WEBHOOK_URL }}
- name: Send Slack Notification
run: |
if [[ "${{ job.status }}" == "success" ]]; then
message="ToolJet community image published:\n\`tooljet/tooljet-ce:${{ github.event.release.tag_name }}\`"
else
message="Job '${{ env.JOB_NAME }}' failed! tooljet/tooljet-ce:${{ github.event.release.tag_name }}"
fi
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$message\"}" ${{ secrets.SLACK_WEBHOOK_URL }}
build-tooljet-image-for-ee-edtion:
runs-on: ubuntu-latest
@ -140,7 +129,7 @@ jobs:
build-args: |
CUSTOM_GITHUB_TOKEN=${{ secrets.CUSTOM_GITHUB_TOKEN }}
BRANCH_NAME=main
file: docker/ee/ee-production.Dockerfile
file: docker/pre-release/ee/ee-production.Dockerfile
push: true
tags: tooljet/tooljet-ee:${{ github.event.release.tag_name }},tooljet/tooljet-ee:ee-latest,tooljet/tooljet:ee-latest,tooljet/tooljet:${{ github.event.release.tag_name }}
platforms: linux/amd64
@ -156,7 +145,7 @@ jobs:
build-args: |
CUSTOM_GITHUB_TOKEN=${{ secrets.CUSTOM_GITHUB_TOKEN }}
BRANCH_NAME=lts-3.16
file: docker/ee/ee-production.Dockerfile
file: docker/LTS/ee/ee-production.Dockerfile
push: true
tags: tooljet/tooljet-ee:${{ github.event.release.tag_name }},tooljet/tooljet-ee:ee-lts-latest,tooljet/tooljet:ee-lts-latest,tooljet/tooljet:${{ github.event.release.tag_name }}
platforms: linux/amd64
@ -216,7 +205,7 @@ jobs:
build-args: |
CUSTOM_GITHUB_TOKEN=${{ secrets.CUSTOM_GITHUB_TOKEN }}
BRANCH_NAME=lts-3.16
file: docker/cloud/cloud-server.Dockerfile
file: docker/LTS/cloud/cloud-server.Dockerfile
push: true
tags: tooljet/saas:${{ github.event.release.tag_name }}
platforms: linux/amd64
@ -295,7 +284,7 @@ jobs:
uses: docker/build-push-action@v4
with:
context: .
file: docker/ee/ee-try-tooljet.Dockerfile
file: docker/pre-release/ee/ee-try-tooljet.Dockerfile
push: true
tags: tooljet/try:${{ github.event.release.tag_name }},tooljet/try:ee-latest
platforms: linux/amd64
@ -308,7 +297,7 @@ jobs:
uses: docker/build-push-action@v4
with:
context: .
file: docker/ee/ee-try-tooljet-lts.Dockerfile
file: docker/LTS/ee/ee-try-tooljet-lts.Dockerfile
push: true
tags: tooljet/try:${{ github.event.release.tag_name }},tooljet/try:ee-lts-latest
platforms: linux/amd64

View file

@ -6,7 +6,7 @@ on:
jobs:
merge-submodules:
if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'main'
if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'main' && github.event.pull_request.base.ref == 'lts-3.16'
runs-on: ubuntu-latest
steps:
@ -40,7 +40,7 @@ jobs:
update-submodule-sha:
needs: merge-submodules
if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'main'
if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'main' && github.event.pull_request.base.ref == 'lts-3.16'
runs-on: ubuntu-latest
steps:

View file

@ -1,8 +1,8 @@
name: AWS AMI build using Packer config
on:
# release:
# types: [published]
release:
types: [published]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
@ -56,7 +56,7 @@ jobs:
# Echo RENDER_GITHUB_PAT
- name: Set PACKER_GITHUB_PAT
run: echo "PACKER_GITHUB_PAT=${{ secrets.PACKER_GITHUB_PAT}}" >> $GITHUB_ENV
run: echo "PACKER_GITHUB_PAT=${{ secrets.CUSTOM_GITHUB_TOKEN }}" >> $GITHUB_ENV
# Dynamically update setup_machine.sh with PAT
- name: Validate PAT

View file

@ -427,6 +427,25 @@ jobs:
if: env.is_fork == 'false'
uses: actions/checkout@v3
- name: Detect base branch and set Dockerfile path
run: |
BASE_BRANCH="${{ github.event.pull_request.base.ref }}"
echo "Base branch: $BASE_BRANCH"
if [[ "$BASE_BRANCH" == "main" || "$BASE_BRANCH" == release/* ]]; then
DOCKERFILE="./docker/pre-release/ee/ee-preview.Dockerfile"
echo "Using pre-release track"
elif [[ "$BASE_BRANCH" == "lts-3.16" || "$BASE_BRANCH" == release-lts/* ]]; then
DOCKERFILE="./docker/LTS/ee/ee-preview.Dockerfile"
echo "Using LTS track"
else
echo "Error: Unsupported base branch '$BASE_BRANCH'"
echo "Supported branches: main, release/*, lts-3.16, release-lts/*"
exit 1
fi
echo "Selected Dockerfile: $DOCKERFILE"
echo "DOCKERFILE=$DOCKERFILE" >> $GITHUB_ENV
- name: Creating deployment for Enterprise Edition
id: create-ee-deployment
@ -584,7 +603,7 @@ jobs:
"envSpecificDetails": {
"dockerCommand": "",
"dockerContext": "./",
"dockerfilePath": "./docker/ee/ee-preview.Dockerfile"
"dockerfilePath": "'"$DOCKERFILE"'"
},
"healthCheckPath": "/api/health",
"numInstances": 1,

View file

@ -1 +1 @@
3.16.0
3.16.1-lts

View file

@ -106,7 +106,7 @@ mkdir -p ~/app
git config --global url."https://x-access-token:CUSTOM_GITHUB_TOKEN@github.com/".insteadOf "https://github.com/"
#The below url will be edited dynamically when actions is triggered
git clone -b main https://github.com/ToolJet/ToolJet.git ~/app && cd ~/app
git clone -b lts-3.16 https://github.com/ToolJet/ToolJet.git ~/app && cd ~/app
git submodule update --init --recursive
git submodule foreach 'git checkout main || true'

View file

@ -25,14 +25,14 @@ RUN git checkout ${BRANCH_NAME}
RUN git submodule update --init --recursive
# Checkout the same branch in submodules if it exists, otherwise stay on default branch
# Checkout the same branch in submodules if it exists, otherwise fallback to lts-3.16
RUN git submodule foreach " \
if git show-ref --verify --quiet refs/heads/${BRANCH_NAME} || \
git ls-remote --exit-code --heads origin ${BRANCH_NAME}; then \
git checkout ${BRANCH_NAME}; \
else \
echo 'Branch ${BRANCH_NAME} not found in submodule \$name, falling back to main'; \
git checkout main; \
echo 'Branch ${BRANCH_NAME} not found in submodule \$name, falling back to lts-3.16'; \
git checkout lts-3.16; \
fi"
# Scripts for building
@ -127,7 +127,7 @@ COPY --from=builder /app/server/scripts ./app/server/scripts
COPY --from=builder /app/server/dist ./app/server/dist
COPY --from=builder --chown=appuser:0 /app/server/ee/ai/assets ./app/server/ee/ai/assets
COPY ./docker/cloud/cloud-entrypoint.sh ./app/server/cloud-entrypoint.sh
COPY ./docker/LTS/cloud/cloud-entrypoint.sh ./app/server/cloud-entrypoint.sh
# Define non-sudo user
RUN useradd --create-home --home-dir /home/appuser appuser \

View file

@ -26,14 +26,14 @@ RUN git checkout ${BRANCH_NAME}
RUN git submodule update --init --recursive
# Checkout the same branch in submodules if it exists, otherwise stay on default branch
# Checkout the same branch in submodules if it exists, otherwise fallback to lts-3.16
RUN git submodule foreach " \
if git show-ref --verify --quiet refs/heads/${BRANCH_NAME} || \
git ls-remote --exit-code --heads origin ${BRANCH_NAME}; then \
git checkout ${BRANCH_NAME}; \
else \
echo 'Branch ${BRANCH_NAME} not found in submodule \$name, falling back to main'; \
git checkout main; \
echo 'Branch ${BRANCH_NAME} not found in submodule \$name, falling back to lts-3.16'; \
git checkout lts-3.16; \
fi"
@ -115,7 +115,7 @@ COPY --from=builder /app/server/scripts ./app/server/scripts
COPY --from=builder /app/server/dist ./app/server/dist
COPY --from=builder --chown=appuser:0 /app/server/ee/ai/assets ./app/server/ee/ai/assets
COPY ./docker/cloud/cloud-entrypoint.sh ./app/server/cloud-entrypoint.sh
COPY ./docker/LTS/cloud/cloud-entrypoint.sh ./app/server/cloud-entrypoint.sh
# Installing git for simple git commands

View file

@ -0,0 +1,184 @@
FROM node:22.15.1 AS builder
# Fix for JS heap limit allocation issue
ENV NODE_OPTIONS="--max-old-space-size=4096"
RUN mkdir -p /app
WORKDIR /app
# Set GitHub token and branch as build arguments
ARG CUSTOM_GITHUB_TOKEN
ARG BRANCH_NAME
# Clone and checkout the frontend repository
RUN git config --global url."https://x-access-token:${CUSTOM_GITHUB_TOKEN}@github.com/".insteadOf "https://github.com/"
RUN git config --global http.version HTTP/1.1
RUN git config --global http.postBuffer 524288000
RUN git clone https://github.com/ToolJet/ToolJet.git .
# The branch name needs to be changed the branch with modularisation in CE repo
RUN git checkout ${BRANCH_NAME}
RUN git submodule update --init --recursive
# Checkout the same branch in submodules if it exists, otherwise fallback to lts-3.16
RUN git submodule foreach " \
if git show-ref --verify --quiet refs/heads/${BRANCH_NAME} || \
git ls-remote --exit-code --heads origin ${BRANCH_NAME}; then \
git checkout ${BRANCH_NAME}; \
else \
echo 'Branch ${BRANCH_NAME} not found in submodule \$name, falling back to lts-3.16'; \
git checkout lts-3.16; \
fi"
# Scripts for building
COPY ./package.json ./package.json
# Build plugins
COPY ./plugins/package.json ./plugins/package-lock.json ./plugins/
RUN npm --prefix plugins install
COPY ./plugins/ ./plugins/
RUN NODE_ENV=production npm --prefix plugins run build
RUN npm --prefix plugins prune --production
ENV TOOLJET_EDITION=ee
# Build frontend
COPY ./frontend/package.json ./frontend/package-lock.json ./frontend/
RUN npm --prefix frontend install
COPY ./frontend/ ./frontend/
RUN npm --prefix frontend run build --production
RUN npm --prefix frontend prune --production
ENV NODE_ENV=production
ENV TOOLJET_EDITION=ee
# Build server
COPY ./server/package.json ./server/package-lock.json ./server/
RUN npm --prefix server install
COPY ./server/ ./server/
RUN npm install -g @nestjs/cli
RUN npm install -g copyfiles
RUN npm --prefix server run build
FROM node:22.15.1-bullseye
RUN apt-get update -yq \
&& apt-get install curl gnupg zip -yq \
&& apt-get install -yq build-essential \
&& apt-get clean -y
# copy postgrest executable
COPY --from=postgrest/postgrest:v12.2.0 /bin/postgrest /bin
ENV NODE_ENV=production
ENV TOOLJET_EDITION=ee
ENV NODE_OPTIONS="--max-old-space-size=4096"
RUN apt-get update && apt-get install -y freetds-dev libaio1 wget supervisor
# Install Instantclient Basic Light Oracle and Dependencies
WORKDIR /opt/oracle
RUN wget https://tooljet-plugins-production.s3.us-east-2.amazonaws.com/marketplace-assets/oracledb/instantclients/instantclient-basiclite-linuxx64.zip && \
wget https://tooljet-plugins-production.s3.us-east-2.amazonaws.com/marketplace-assets/oracledb/instantclients/instantclient-basiclite-linux.x64-11.2.0.4.0.zip && \
unzip instantclient-basiclite-linuxx64.zip && rm -f instantclient-basiclite-linuxx64.zip && \
unzip instantclient-basiclite-linux.x64-11.2.0.4.0.zip && rm -f instantclient-basiclite-linux.x64-11.2.0.4.0.zip && \
cd /opt/oracle/instantclient_21_10 && rm -f *jdbc* *occi* *mysql* *mql1* *ipc1* *jar uidrvci genezi adrci && \
cd /opt/oracle/instantclient_11_2 && rm -f *jdbc* *occi* *mysql* *mql1* *ipc1* *jar uidrvci genezi adrci && \
echo /opt/oracle/instantclient* > /etc/ld.so.conf.d/oracle-instantclient.conf && ldconfig
# Set the Instant Client library paths
ENV LD_LIBRARY_PATH="/opt/oracle/instantclient_11_2:/opt/oracle/instantclient_21_10:${LD_LIBRARY_PATH}"
WORKDIR /
# copy npm scripts
COPY --from=builder /app/package.json ./app/package.json
# copy plugins dependencies
COPY --from=builder /app/plugins/dist ./app/plugins/dist
COPY --from=builder /app/plugins/client.js ./app/plugins/client.js
COPY --from=builder /app/plugins/node_modules ./app/plugins/node_modules
COPY --from=builder /app/plugins/packages/common ./app/plugins/packages/common
COPY --from=builder /app/plugins/package.json ./app/plugins/package.json
# copy frontend build
COPY --from=builder /app/frontend/build ./app/frontend/build
# copy server build
COPY --from=builder /app/server/package.json ./app/server/package.json
COPY --from=builder /app/server/.version ./app/server/.version
COPY --from=builder /app/server/ee/keys ./app/server/ee/keys
COPY --from=builder /app/server/node_modules ./app/server/node_modules
COPY --from=builder /app/server/templates ./app/server/templates
COPY --from=builder /app/server/scripts ./app/server/scripts
COPY --from=builder /app/server/dist ./app/server/dist
WORKDIR /app
# Install PostgreSQL
USER root
RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ bullseye-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list
RUN apt update && apt -y install postgresql-13 postgresql-client-13 supervisor --fix-missing
# Explicitly create PG main directory with correct ownership
RUN mkdir -p /var/lib/postgresql/13/main && \
chown -R postgres:postgres /var/lib/postgresql
RUN mkdir -p /var/log/supervisor /var/run/postgresql && \
chown -R postgres:postgres /var/run/postgresql /var/log/supervisor
# Remove existing data and create directory with proper ownership
RUN rm -rf /var/lib/postgresql/13/main && \
mkdir -p /var/lib/postgresql/13/main && \
chown -R postgres:postgres /var/lib/postgresql
# Initialize PostgreSQL
RUN su - postgres -c "/usr/lib/postgresql/13/bin/initdb -D /var/lib/postgresql/13/main"
# Configure Supervisor to manage PostgREST, ToolJet, and Redis
RUN echo "[supervisord] \n" \
"nodaemon=true \n" \
"user=root \n" \
"\n" \
"[program:postgrest] \n" \
"command=/bin/postgrest \n" \
"autostart=true \n" \
"autorestart=true \n" \
"\n" \
"[program:tooljet] \n" \
"user=root \n" \
"command=/bin/bash -c '/app/server/scripts/boot.sh' \n" \
"autostart=true \n" \
"autorestart=true \n" \
"stderr_logfile=/dev/stdout \n" \
"stderr_logfile_maxbytes=0 \n" \
"stdout_logfile=/dev/stdout \n" \
"stdout_logfile_maxbytes=0 \n" | sed 's/ //' > /etc/supervisor/conf.d/supervisord.conf
# ENV defaults
ENV TOOLJET_HOST=http://localhost \
PORT=80 \
NODE_ENV=production \
LOCKBOX_MASTER_KEY=replace_with_lockbox_master_key \
SECRET_KEY_BASE=replace_with_secret_key_base \
PG_DB=tooljet_production \
PG_USER=postgres \
PG_PASS=postgres \
PG_HOST=localhost \
ENABLE_TOOLJET_DB=true \
TOOLJET_DB_HOST=localhost \
TOOLJET_DB_USER=postgres \
TOOLJET_DB_PASS=postgres \
TOOLJET_DB=tooljet_db \
PGRST_HOST=http://localhost:3000 \
PGRST_DB_URI=postgres://postgres:postgres@localhost/tooljet_db \
PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj \
PGRST_DB_PRE_CONFIG=postgrest.pre_config \
ORM_LOGGING=true \
DEPLOYMENT_PLATFORM=docker:local \
HOME=/home/appuser \
TERM=xterm
RUN chmod +x ./server/scripts/preview.sh
# Set the entrypoint
ENTRYPOINT ["./server/scripts/preview.sh"]

View file

@ -0,0 +1,214 @@
FROM node:22.15.1 AS builder
# Fix for JS heap limit allocation issue
ENV NODE_OPTIONS="--max-old-space-size=4096"
RUN npm i -g npm@10.9.2 && npm cache clean --force
RUN mkdir -p /app
WORKDIR /app
# Set GitHub token and branch as build arguments
ARG CUSTOM_GITHUB_TOKEN
ARG BRANCH_NAME
# Clone and checkout the frontend repository
RUN git config --global url."https://x-access-token:${CUSTOM_GITHUB_TOKEN}@github.com/".insteadOf "https://github.com/"
RUN git config --global http.version HTTP/1.1
RUN git config --global http.postBuffer 524288000
RUN git clone https://github.com/ToolJet/ToolJet.git .
# The branch name needs to be changed the branch with modularisation in CE repo
RUN git checkout ${BRANCH_NAME}
RUN git submodule update --init --recursive
# Checkout the same branch in submodules if it exists, otherwise fallback to lts-3.16
RUN git submodule foreach " \
if git show-ref --verify --quiet refs/heads/${BRANCH_NAME} || \
git ls-remote --exit-code --heads origin ${BRANCH_NAME}; then \
git checkout ${BRANCH_NAME}; \
else \
echo 'Branch ${BRANCH_NAME} not found in submodule \$name, falling back to lts-3.16'; \
git checkout lts-3.16; \
fi"
# Scripts for building
COPY ./package.json ./package.json
# Build plugins
COPY ./plugins/package.json ./plugins/package-lock.json ./plugins/
RUN npm --prefix plugins ci --omit=dev
COPY ./plugins/ ./plugins/
RUN NODE_ENV=production npm --prefix plugins run build && npm --prefix plugins prune --omit=dev
ENV TOOLJET_EDITION=ee
# Build frontend
COPY ./frontend/package.json ./frontend/package-lock.json ./frontend/
RUN npm --prefix frontend install
COPY ./frontend/ ./frontend/
RUN npm --prefix frontend run build --production && npm --prefix frontend prune --production
ENV NODE_ENV=production
ENV TOOLJET_EDITION=ee
# Build server
COPY ./server/package.json ./server/package-lock.json ./server/
RUN npm --prefix server ci --omit=dev
COPY ./server/ ./server/
RUN npm install -g @nestjs/cli && npm install -g copyfiles
RUN npm --prefix server run build && npm prune --production --prefix server
# Install dependencies for PostgREST, curl, tar, etc.
RUN apt-get update && apt-get install -y \
curl ca-certificates tar \
&& rm -rf /var/lib/apt/lists/*
ENV POSTGREST_VERSION=v12.2.0
RUN curl -Lo postgrest.tar.xz https://github.com/PostgREST/postgrest/releases/download/${POSTGREST_VERSION}/postgrest-v12.2.0-linux-static-x64.tar.xz && \
tar -xf postgrest.tar.xz && \
mv postgrest /postgrest && \
rm postgrest.tar.xz && \
chmod +x /postgrest
FROM debian:12-slim
RUN apt-get update && \
apt-get install -y --no-install-recommends \
curl \
wget \
gnupg \
unzip \
ca-certificates \
xz-utils \
tar \
postgresql-client \
redis \
libaio1 \
git \
openssh-client \
freetds-dev \
&& apt-get upgrade -y -o Dpkg::Options::="--force-confold" \
&& apt-get autoremove -y \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
RUN curl -O https://nodejs.org/dist/v22.15.1/node-v22.15.1-linux-x64.tar.xz \
&& tar -xf node-v22.15.1-linux-x64.tar.xz \
&& mv node-v22.15.1-linux-x64 /usr/local/lib/nodejs \
&& echo 'export PATH="/usr/local/lib/nodejs/bin:$PATH"' >> /etc/profile.d/nodejs.sh \
&& /bin/bash -c "source /etc/profile.d/nodejs.sh" \
&& rm node-v22.15.1-linux-x64.tar.xz
ENV PATH=/usr/local/lib/nodejs/bin:$PATH
ENV NODE_ENV=production
ENV TOOLJET_EDITION=ee
ENV NODE_OPTIONS="--max-old-space-size=4096"
# Install Neo4j + APOC
RUN wget -O - https://debian.neo4j.com/neotechnology.gpg.key | apt-key add - && \
echo "deb https://debian.neo4j.com stable 5" > /etc/apt/sources.list.d/neo4j.list && \
apt-get update && apt-get install -y neo4j=1:5.26.6 && apt-mark hold neo4j && \
mkdir -p /var/lib/neo4j/plugins && \
wget -P /var/lib/neo4j/plugins https://github.com/neo4j/apoc/releases/download/5.26.6/apoc-5.26.6-core.jar && \
echo "dbms.security.procedures.unrestricted=apoc.*" >> /etc/neo4j/neo4j.conf && \
echo "dbms.security.procedures.allowlist=apoc.*,algo.*,gds.*" >> /etc/neo4j/neo4j.conf && \
echo "dbms.directories.plugins=/var/lib/neo4j/plugins" >> /etc/neo4j/neo4j.conf && \
echo "dbms.security.auth_enabled=true" >> /etc/neo4j/neo4j.conf && \
apt-get clean && rm -rf /var/lib/apt/lists/*
# Install Instantclient Basic Light Oracle and Dependencies
WORKDIR /opt/oracle
RUN wget https://tooljet-plugins-production.s3.us-east-2.amazonaws.com/marketplace-assets/oracledb/instantclients/instantclient-basiclite-linuxx64.zip && \
wget https://tooljet-plugins-production.s3.us-east-2.amazonaws.com/marketplace-assets/oracledb/instantclients/instantclient-basiclite-linux.x64-11.2.0.4.0.zip && \
unzip instantclient-basiclite-linuxx64.zip && rm -f instantclient-basiclite-linuxx64.zip && \
unzip instantclient-basiclite-linux.x64-11.2.0.4.0.zip && rm -f instantclient-basiclite-linux.x64-11.2.0.4.0.zip && \
cd /opt/oracle/instantclient_21_10 && rm -f *jdbc* *occi* *mysql* *mql1* *ipc1* *jar uidrvci genezi adrci && \
cd /opt/oracle/instantclient_11_2 && rm -f *jdbc* *occi* *mysql* *mql1* *ipc1* *jar uidrvci genezi adrci && \
echo /opt/oracle/instantclient* > /etc/ld.so.conf.d/oracle-instantclient.conf && ldconfig
# Set the Instant Client library paths
ENV LD_LIBRARY_PATH="/opt/oracle/instantclient_11_2:/opt/oracle/instantclient_21_10:${LD_LIBRARY_PATH}"
RUN rm -f *.zip *.key && apt-get clean && rm -rf /var/lib/apt/lists/*
WORKDIR /
RUN mkdir -p /app
RUN useradd --create-home --home-dir /home/appuser appuser
# Use the PostgREST binary from the builder stage
COPY --from=builder --chown=appuser:0 /postgrest /usr/local/bin/postgrest
RUN mv /usr/local/bin/postgrest /usr/local/bin/postgrest-original && \
echo '#!/bin/bash\nexec /usr/local/bin/postgrest-original "$@" 2>&1 | sed "s/^/[PostgREST] /"' > /usr/local/bin/postgrest && \
chmod +x /usr/local/bin/postgrest
# Copy application with ownership set directly to avoid chown -R
COPY --from=builder --chown=appuser:0 /app/package.json ./app/package.json
COPY --from=builder --chown=appuser:0 /app/plugins/dist ./app/plugins/dist
COPY --from=builder --chown=appuser:0 /app/plugins/client.js ./app/plugins/client.js
COPY --from=builder --chown=appuser:0 /app/plugins/node_modules ./app/plugins/node_modules
COPY --from=builder --chown=appuser:0 /app/plugins/packages/common ./app/plugins/packages/common
COPY --from=builder --chown=appuser:0 /app/plugins/package.json ./app/plugins/package.json
COPY --from=builder --chown=appuser:0 /app/frontend/build ./app/frontend/build
COPY --from=builder --chown=appuser:0 /app/server/package.json ./app/server/package.json
COPY --from=builder --chown=appuser:0 /app/server/.version ./app/server/.version
COPY --from=builder --chown=appuser:0 /app/server/ee/keys ./app/server/ee/keys
COPY --from=builder --chown=appuser:0 /app/server/node_modules ./app/server/node_modules
COPY --from=builder --chown=appuser:0 /app/server/templates ./app/server/templates
COPY --from=builder --chown=appuser:0 /app/server/scripts ./app/server/scripts
COPY --from=builder --chown=appuser:0 /app/server/dist ./app/server/dist
COPY --from=builder --chown=appuser:0 /app/server/ee/ai/assets ./app/server/ee/ai/assets
COPY ./docker/LTS/ee/ee-entrypoint.sh ./app/server/ee-entrypoint.sh
RUN mkdir -p /var/lib/neo4j/data/databases /var/lib/neo4j/data/transactions /var/log/neo4j /opt/neo4j/run && \
chown -R appuser:0 /var/lib/neo4j /var/log/neo4j /etc/neo4j /opt/neo4j/run && \
chmod -R 770 /var/lib/neo4j /var/log/neo4j /etc/neo4j /opt/neo4j/run && \
chmod -R 644 /var/lib/neo4j/plugins/*.jar && \
chown -R appuser:0 /var/lib/neo4j/plugins && \
chmod 755 /var/lib/neo4j/plugins
# Create directory /home/appuser and set ownership to appuser
RUN mkdir -p /home/appuser \
&& chown -R appuser:0 /home/appuser \
&& chmod g+s /home/appuser \
&& chmod -R g=u /home/appuser \
&& npm cache clean --force
# Create directory /tmp/.npm/npm-cache/ and set ownership to appuser
RUN mkdir -p /tmp/.npm/npm-cache/ \
&& chown -R appuser:0 /tmp/.npm/npm-cache/ \
&& chmod g+s /tmp/.npm/npm-cache/ \
&& chmod -R g=u /tmp/.npm/npm-cache \
&& npm cache clean --force
# Set npm cache directory globally
RUN npm config set cache /tmp/.npm/npm-cache/ --global
ENV npm_config_cache /tmp/.npm/npm-cache/
# Create directory /tmp/.npm/npm-cache/_logs and set ownership to appuser
RUN mkdir -p /tmp/.npm/npm-cache/_logs \
&& chown -R appuser:0 /tmp/.npm/npm-cache/_logs \
&& chmod g+s /tmp/.npm/npm-cache/_logs \
&& chmod -R g=u /tmp/.npm/npm-cache/_logs
# Create Redis data, log, and configuration directories
RUN mkdir -p /var/lib/redis /var/log/redis /etc/redis \
&& chown -R appuser:0 /var/lib/redis /var/log/redis /etc/redis \
&& chmod g+s /var/lib/redis /var/log/redis /etc/redis \
&& chmod -R g=u /var/lib/redis /var/log/redis /etc/redis
ENV HOME=/home/appuser
# Switch back to appuser
USER appuser
WORKDIR /app
RUN npm install --prefix server --no-save dotenv@10.0.0 joi@17.4.1 && npm cache clean --force
ENTRYPOINT ["./server/ee-entrypoint.sh"]

View file

@ -3,11 +3,12 @@ FROM tooljet/tooljet:ee-lts-latest
# Copy postgrest executable
COPY --from=postgrest/postgrest:v12.2.0 /bin/postgrest /bin
RUN apt-get update && apt-get install -y wget libicu72 libldap-2.5-0 libssl3 || true
# Install Postgres
USER root
RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ bullseye-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list
RUN echo "deb http://deb.debian.org/debian"
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ bookworm-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list
RUN apt update && apt -y install postgresql-13 postgresql-client-13 supervisor
USER postgres
RUN service postgresql start && \
@ -49,8 +50,8 @@ RUN apt update && apt install -y gettext-base curl \
&& curl -sSL https://github.com/fullstorydev/grpcurl/releases/download/v1.8.0/grpcurl_1.8.0_linux_x86_64.tar.gz | tar -xzv -C /usr/local/bin grpcurl
# Copy Temporal configuration files
COPY ./docker/ee/temporal-server.yaml /etc/temporal/temporal-server.template.yaml
COPY ./docker/ee/temporal-ui-server.yaml /etc/temporal/temporal-ui-server.yaml
COPY ./docker/LTS/ee/temporal-server.yaml /etc/temporal/temporal-server.template.yaml
COPY ./docker/LTS/ee/temporal-ui-server.yaml /etc/temporal/temporal-ui-server.yaml
# Install Neo4j + APOC
RUN wget -O - https://debian.neo4j.com/neotechnology.gpg.key | apt-key add - && \
@ -136,6 +137,6 @@ ENV TOOLJET_HOST=http://localhost \
TEMPORAL_CORS_ORIGINS=http://localhost:8080
# Set the entrypoint
COPY ./docker/ee/ee-try-entrypoint-lts.sh /ee-try-entrypoint-lts.sh
RUN chmod +x /ee-try-entrypoint-lts
COPY ./docker/LTS/ee/ee-try-entrypoint-lts.sh /ee-try-entrypoint-lts.sh
RUN chmod +x /ee-try-entrypoint-lts.sh
ENTRYPOINT ["/ee-try-entrypoint-lts.sh"]

View file

@ -0,0 +1,183 @@
#!/bin/bash
set -e
npm cache clean --force
# Load environment variables from .env if the file exists
if [ -f "./.env" ]; then
export $(grep -v '^#' ./.env | xargs -d '\n') || true
fi
# Start Redis server only if REDIS_HOST is localhost or not set
if [ -z "$REDIS_HOST" ] || [ "$REDIS_HOST" = "localhost" ]; then
echo "Starting Redis server locally..."
redis-server /etc/redis/redis.conf &
elif [ -n "$REDIS_URL" ]; then
echo "REDIS_URL connection is set: $REDIS_URL"
else
echo "Using external Redis at $REDIS_HOST:$REDIS_PORT."
# Validate external Redis connection
if ! ./server/scripts/wait-for-it.sh "$REDIS_HOST:${REDIS_PORT:-6379}" --strict --timeout=300 -- echo "Redis is up"; then
echo "Error: Unable to connect to Redis at $REDIS_HOST:$REDIS_PORT."
exit 1
fi
fi
# Check if PGRST_HOST starts with "localhost"
if [[ "$PGRST_HOST" == localhost:* ]]; then
echo "Starting PostgREST server locally..."
# Generate PostgREST configuration in a writable directory
POSTGREST_CONFIG_PATH="/tmp/postgrest.conf"
echo "db-uri = \"${PGRST_DB_URI}\"" > "$POSTGREST_CONFIG_PATH"
echo "db-pre-config = \"postgrest.pre_config\"" >> "$POSTGREST_CONFIG_PATH"
echo "server-port = \"${PGRST_SERVER_PORT}\"" >> "$POSTGREST_CONFIG_PATH"
# Starting PostgREST
echo "Starting PostgREST..."
postgrest "$POSTGREST_CONFIG_PATH" &
else
echo "Using external PostgREST at $PGRST_HOST."
fi
# Check WORKLOW_WORKER and skip SETUP_CMD if true
if [ "${WORKFLOW_WORKER}" == "true" ]; then
echo "WORKFLOW_WORKER is set to true. Running worker process."
npm run worker:prod
else
# Determine setup command based on the presence of ./server/dist
if [ -d "./server/dist" ]; then
SETUP_CMD='npm run db:setup:prod'
else
SETUP_CMD='npm run db:setup'
fi
fi
# Neo4j configuration
# ----------------------------------
# Default Neo4j environment values
# ----------------------------------
export NEO4J_USER=${NEO4J_USER:-"neo4j"}
export NEO4J_PASSWORD=${NEO4J_PASSWORD:-"appaqvyvRLbeukhFE"}
export NEO4J_AUTH=${NEO4J_AUTH:-"neo4j/appaqvyvRLbeukhFE"}
export NEO4J_URI=${NEO4J_URI:-"bolt://localhost:7687"}
export NEO4J_PLUGINS=${NEO4J_PLUGINS:-'["apoc"]'}
export NEO4J_AUTH
# Extract username and password from NEO4J_AUTH if set
if [ -n "$NEO4J_AUTH" ]; then
# Extract username and password from NEO4J_AUTH (format: username/password)
NEO4J_USERNAME=$(echo "$NEO4J_AUTH" | cut -d'/' -f1)
NEO4J_PASSWORD=$(echo "$NEO4J_AUTH" | cut -d'/' -f2)
# Export these for application use
export NEO4J_USERNAME
export NEO4J_PASSWORD
echo "Neo4j authentication configured with username: $NEO4J_USERNAME" >/dev/null 2>&1
else
echo "NEO4J_AUTH not set, using default authentication" >/dev/null 2>&1
fi
# Check if Neo4j is already initialized and set password if necessary
if [ "$NEO4J_AUTH" != "none" ] && [ -n "$NEO4J_PASSWORD" ]; then
echo "Setting Neo4j initial password..." >/dev/null 2>&1
# Ensure Neo4j is not running before setting the initial password
neo4j stop || true
# Set the initial password using the correct command format for Neo4j 5.x
NEO4J_ADMIN_CMD=$(which neo4j-admin)
NEO4J_VERSION=$(neo4j --version | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+" | head -n 1)
echo "Detected Neo4j version: $NEO4J_VERSION" >/dev/null 2>&1
# Use version-specific command format
MAJOR_VERSION=$(echo $NEO4J_VERSION | cut -d. -f1)
if [ "$MAJOR_VERSION" -ge "5" ]; then
# For Neo4j 5.x and higher
echo "Using Neo4j 5.x+ password command format" >/dev/null 2>&1
$NEO4J_ADMIN_CMD dbms set-initial-password "$NEO4J_PASSWORD" --require-password-change=false >/dev/null 2>&1 || {
echo "Warning: Could not set Neo4j password, it may already be set" >/dev/null 2>&1
}
else
# For Neo4j 4.x and lower
echo "Using Neo4j 4.x password command format" >/dev/null 2>&1
$NEO4J_ADMIN_CMD set-initial-password "$NEO4J_PASSWORD" >/dev/null 2>&1 || {
echo "Warning: Could not set Neo4j password, it may already be set" >/dev/null 2>&1
}
fi
fi
# Update Neo4j configuration
echo "Configuring Neo4j..." >/dev/null 2>&1
cat > /etc/neo4j/neo4j.conf << EOF
# Neo4j configuration
dbms.security.auth_enabled=true
server.bolt.enabled=true
server.bolt.listen_address=0.0.0.0:7687
server.directories.data=/var/lib/neo4j/data
server.directories.logs=/var/log/neo4j
initial.dbms.default_database=neo4j
server.directories.plugins=/var/lib/neo4j/plugins
server.directories.import=/var/lib/neo4j/import
# APOC Settings
dbms.security.procedures.unrestricted=apoc.*
dbms.security.procedures.allowlist=apoc.*,algo.*,gds.*
EOF
if [ -w "$NEO4J_LOG_DIR" ]; then
chmod -R 770 "$NEO4J_LOG_DIR" || echo "Warning: Could not set log directory permissions" >/dev/null 2>&1
fi
# Start Neo4j
echo "Starting Neo4j service..."
neo4j console >/dev/null 2>&1 &
# Add a wait for Neo4j to be ready with more robust checking
echo "Waiting for Neo4j to be ready..." >/dev/null 2>&1
NEO4J_READY=false
for i in {1..60}; do
# First try standard status check
if neo4j status >/dev/null 2>&1; then
echo "Neo4j is ready 🚀"
NEO4J_READY=true
break
fi
# Also try connecting to the bolt port as a fallback
if command -v nc >/dev/null 2>&1; then
if nc -z localhost 7687 >/dev/null 2>&1; then
echo "Neo4j is ready (port 7687 is open)"
NEO4J_READY=true
break
fi
fi
echo "Waiting for Neo4j to start... ($i/60)" >/dev/null 2>&1
sleep 2
done
if [ "$NEO4J_READY" = false ]; then
echo "WARNING: Neo4j may not be fully started yet, but continuing..."
fi
# Wait for PostgreSQL connection
if [ -z "$DATABASE_URL" ]; then
./server/scripts/wait-for-it.sh $PG_HOST:${PG_PORT:-5432} --strict --timeout=300 -- echo "PostgreSQL is up"
else
PG_HOST=$(echo "$DATABASE_URL" | awk -F'[/:@?]' '{print $6}')
PG_PORT=$(echo "$DATABASE_URL" | awk -F'[/:@?]' '{print $7}')
./server/scripts/wait-for-it.sh "$PG_HOST:$PG_PORT" --strict --timeout=300 -- echo "PostgreSQL is up"
fi
# Run setup command if defined
if [ -n "$SETUP_CMD" ]; then
$SETUP_CMD
fi
exec "$@"

View file

@ -165,7 +165,7 @@ COPY --from=builder --chown=appuser:0 /app/server/templates ./app/server/templat
COPY --from=builder --chown=appuser:0 /app/server/scripts ./app/server/scripts
COPY --from=builder --chown=appuser:0 /app/server/dist ./app/server/dist
COPY --from=builder --chown=appuser:0 /app/server/ee/ai/assets ./app/server/ee/ai/assets
COPY ./docker/ee/ee-entrypoint.sh ./app/server/ee-entrypoint.sh
COPY ./docker/pre-release/ee/ee-entrypoint.sh ./app/server/ee-entrypoint.sh
RUN mkdir -p /var/lib/neo4j/data/databases /var/lib/neo4j/data/transactions /var/log/neo4j /opt/neo4j/run && \
chown -R appuser:0 /var/lib/neo4j /var/log/neo4j /etc/neo4j /opt/neo4j/run && \

View file

@ -1,13 +1,11 @@
FROM tooljet/tooljet:ee-latest
# Copy postgrest executable
COPY --from=postgrest/postgrest:v12.2.0 /bin/postgrest /bin
RUN apt-get update && apt-get install -y wget libicu72 libldap-2.5-0 libssl3 || true
# Install Postgres
USER root
RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ bullseye-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list
RUN echo "deb http://deb.debian.org/debian"
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ bookworm-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list
RUN apt update && apt -y install postgresql-13 postgresql-client-13 supervisor
USER postgres
RUN service postgresql start && \
@ -49,8 +47,8 @@ RUN apt update && apt install -y gettext-base curl \
&& curl -sSL https://github.com/fullstorydev/grpcurl/releases/download/v1.8.0/grpcurl_1.8.0_linux_x86_64.tar.gz | tar -xzv -C /usr/local/bin grpcurl
# Copy Temporal configuration files
COPY ./docker/ee/temporal-server.yaml /etc/temporal/temporal-server.template.yaml
COPY ./docker/ee/temporal-ui-server.yaml /etc/temporal/temporal-ui-server.yaml
COPY ./docker/pre-release/ee/temporal-server.yaml /etc/temporal/temporal-server.template.yaml
COPY ./docker/pre-release/ee/temporal-ui-server.yaml /etc/temporal/temporal-ui-server.yaml
# Install Neo4j + APOC
RUN wget -O - https://debian.neo4j.com/neotechnology.gpg.key | apt-key add - && \
@ -136,6 +134,6 @@ ENV TOOLJET_HOST=http://localhost \
TEMPORAL_CORS_ORIGINS=http://localhost:8080
# Set the entrypoint
COPY ./docker/ee/ee-try-entrypoint.sh /ee-try-entrypoint.sh
COPY ./docker/pre-release/ee/ee-try-entrypoint.sh /ee-try-entrypoint.sh
RUN chmod +x /ee-try-entrypoint.sh
ENTRYPOINT ["/ee-try-entrypoint.sh"]

View file

@ -0,0 +1,70 @@
log:
stdout: true
level: info
persistence:
defaultStore: postgres-default
visibilityStore: postgres-visibility
numHistoryShards: 4
dataStores:
postgres-default:
sql:
pluginName: "postgres12"
databaseName: "temporal"
connectAddr: "localhost:5432"
user: "tooljet"
password: "postgres"
postgres-visibility:
sql:
pluginName: "postgres12"
databaseName: "temporal_visibility"
connectAddr: "localhost:5432"
user: "tooljet"
password: "postgres"
global:
membership:
maxJoinDuration: 30s
broadcastAddress: "127.0.0.1"
pprof:
port: 7936
services:
frontend:
rpc:
grpcPort: 7233
membershipPort: 6933
bindOnLocalHost: true
httpPort: 7243
matching:
rpc:
grpcPort: 7235
membershipPort: 6935
bindOnLocalHost: true
history:
rpc:
grpcPort: 7234
membershipPort: 6934
bindOnLocalHost: true
worker:
rpc:
membershipPort: 6939
clusterMetadata:
enableGlobalNamespace: false
failoverVersionIncrement: 10
masterClusterName: "active"
currentClusterName: "active"
clusterInformation:
active:
enabled: true
initialFailoverVersion: 1
rpcName: "frontend"
rpcAddress: "localhost:7233"
httpAddress: "localhost:7243"
dcRedirectionPolicy:
policy: "noop"

View file

@ -0,0 +1,8 @@
temporalGrpcAddress: 127.0.0.1:7233 # Use the correct Temporal server address
host: 0.0.0.0
port: 8080
enableUi: true
cors:
allowOrigins:
- http://localhost:8080
defaultNamespace: default

View file

@ -1 +1 @@
3.16.0
3.16.1-lts

View file

@ -151,34 +151,36 @@ export const AppCanvas = ({ appId, switchDarkMode, darkMode }) => {
function getMinWidth() {
if (isModuleMode) return '100%';
const isSidebarOpenInEditor = currentMode === 'edit' ? isSidebarOpen : false;
const shouldAdjust = isSidebarOpen || (isRightSidebarOpen && currentMode === 'edit');
if (!shouldAdjust) return '';
let offset;
if (isViewerSidebarPinned && !isPagesSidebarHidden) {
if (position === 'side' && isSidebarOpen && isRightSidebarOpen && !isPagesSidebarHidden) {
if (position === 'side' && isSidebarOpenInEditor && isRightSidebarOpen && !isPagesSidebarHidden) {
offset = `${LEFT_SIDEBAR_WIDTH + RIGHT_SIDEBAR_WIDTH - PAGES_SIDEBAR_WIDTH_EXPANDED}px`;
} else if (position === 'side' && isSidebarOpen && !isRightSidebarOpen && !isPagesSidebarHidden) {
} else if (position === 'side' && isSidebarOpenInEditor && !isRightSidebarOpen && !isPagesSidebarHidden) {
offset = `${LEFT_SIDEBAR_WIDTH - PAGES_SIDEBAR_WIDTH_EXPANDED}px`;
} else if (position === 'side' && isRightSidebarOpen && !isSidebarOpen && !isPagesSidebarHidden) {
} else if (position === 'side' && isRightSidebarOpen && !isSidebarOpenInEditor && !isPagesSidebarHidden) {
offset = `${RIGHT_SIDEBAR_WIDTH - PAGES_SIDEBAR_WIDTH_EXPANDED}px`;
}
} else {
if (position === 'side' && isSidebarOpen && isRightSidebarOpen && !isPagesSidebarHidden) {
if (position === 'side' && isSidebarOpenInEditor && isRightSidebarOpen && !isPagesSidebarHidden) {
offset = `${LEFT_SIDEBAR_WIDTH + RIGHT_SIDEBAR_WIDTH - PAGES_SIDEBAR_WIDTH_COLLAPSED}px`;
} else if (position === 'side' && isSidebarOpen && !isRightSidebarOpen && !isPagesSidebarHidden) {
} else if (position === 'side' && isSidebarOpenInEditor && !isRightSidebarOpen && !isPagesSidebarHidden) {
offset = `${LEFT_SIDEBAR_WIDTH - PAGES_SIDEBAR_WIDTH_COLLAPSED}px`;
} else if (position === 'side' && isRightSidebarOpen && !isSidebarOpen && !isPagesSidebarHidden) {
} else if (position === 'side' && isRightSidebarOpen && !isSidebarOpenInEditor && !isPagesSidebarHidden) {
offset = `${RIGHT_SIDEBAR_WIDTH - PAGES_SIDEBAR_WIDTH_COLLAPSED}px`;
}
}
if (currentMode === 'edit') {
if ((position === 'top' || isPagesSidebarHidden) && isSidebarOpen && isRightSidebarOpen) {
if ((position === 'top' || isPagesSidebarHidden) && isSidebarOpenInEditor && isRightSidebarOpen) {
offset = `${LEFT_SIDEBAR_WIDTH + RIGHT_SIDEBAR_WIDTH}px`;
} else if ((position === 'top' || isPagesSidebarHidden) && isSidebarOpen && !isRightSidebarOpen) {
} else if ((position === 'top' || isPagesSidebarHidden) && isSidebarOpenInEditor && !isRightSidebarOpen) {
offset = `${LEFT_SIDEBAR_WIDTH}px`;
} else if ((position === 'top' || isPagesSidebarHidden) && isRightSidebarOpen && !isSidebarOpen) {
} else if ((position === 'top' || isPagesSidebarHidden) && isRightSidebarOpen && !isSidebarOpenInEditor) {
offset = `${RIGHT_SIDEBAR_WIDTH}px`;
}
}
@ -205,7 +207,7 @@ export const AppCanvas = ({ appId, switchDarkMode, darkMode }) => {
)}
style={canvasContainerStyles}
>
{showOnDesktop && appType !== 'module' && (
{appType !== 'module' && (
<PagesSidebarNavigation
showHeader={showHeader}
isMobileDevice={currentLayout === 'mobile'}
@ -248,8 +250,12 @@ export const AppCanvas = ({ appId, switchDarkMode, darkMode }) => {
pagePositionType={position}
appType={appType}
/>
<DragGhostWidget />
<ResizeGhostWidget />
{currentMode === 'edit' && (
<>
<DragGhostWidget />
<ResizeGhostWidget />
</>
)}
<div id="component-portal" />
{appType !== 'module' && <div id="component-portal" />}
</div>

View file

@ -39,7 +39,7 @@ const Container = React.memo(
componentType,
appType,
}) => {
const { moduleId } = useModuleContext();
const { moduleId, isModuleEditor } = useModuleContext();
const realCanvasRef = useRef(null);
const components = useStore((state) => state.getContainerChildrenMapping(id, moduleId), shallow);
const setLastCanvasClickPosition = useStore((state) => state.setLastCanvasClickPosition, shallow);
@ -170,8 +170,8 @@ const Container = React.memo(
currentMode === 'view'
? computeViewerBackgroundColor(darkMode, canvasBgColor)
: id === 'canvas'
? canvasBgColor
: '#f0f0f0',
? canvasBgColor
: '#f0f0f0',
width: '100%',
maxWidth: (() => {
// For Main Canvas
@ -196,6 +196,7 @@ const Container = React.memo(
'sub-canvas': id !== 'canvas' && appType !== 'module',
'show-grid': isDragging && (index === 0 || index === null) && currentMode === 'edit' && appType !== 'module',
'module-container': appType === 'module',
'is-module-editor': isModuleEditor,
})}
id={id === 'canvas' ? 'real-canvas' : `canvas-${id}`}
data-cy="real-canvas"

View file

@ -923,6 +923,10 @@ export default function Grid({ gridWidth, currentLayout }) {
const targetGridWidth = useGridStore.getState().subContainerWidths[targetSlotId] || gridWidth;
const isParentChangeAllowed = dragContext.isDroppable;
const isParentModuleContainer =
!isModuleEditor &&
document.getElementById(`canvas-${target.slotId}`)?.getAttribute('component-type') === 'ModuleContainer';
// Compute new position
let { left, top } = getAdjustedDropPosition(e, target, isParentChangeAllowed, targetGridWidth, dragged);
const componentParentType = target?.widget?.componentType;
@ -933,7 +937,7 @@ export default function Grid({ gridWidth, currentLayout }) {
componentParentType === 'Form' || componentParentType === 'Container'
? document.getElementById(`canvas-${target.slotId}`)?.scrollTop || 0
: computeScrollDelta({ source });
if (isParentChangeAllowed && !isModalToCanvas) {
if (isParentChangeAllowed && !isModalToCanvas && !isParentModuleContainer) {
// Special case for Modal; If source widget is modal, prevent drops to canvas
const parent = target.slotId === 'real-canvas' ? null : target.slotId;
handleDragEnd([{ id: e.target.id, x: left, y: top + scrollDelta, parent }]);
@ -944,6 +948,7 @@ export default function Grid({ gridWidth, currentLayout }) {
top = dragged.top;
!isModalToCanvas ??
toast.error(`${dragged.widgetType} is not compatible as a child component of ${target.widgetType}`);
isParentModuleContainer ? toast.error('Modules cannot be edited inside an app') : null;
}
// Apply transform for smooth transition
e.target.style.transform = `translate(${left}px, ${top + scrollDelta}px)`;

View file

@ -398,7 +398,18 @@ export function hasParentWithClass(child, className) {
export function showGridLines() {
var canvasElms = document.getElementsByClassName('real-canvas');
// Filter out module canvas
var elementsArray = Array.from(canvasElms).filter((element) => !element.classList.contains('module-container'));
var elementsArray = Array.from(canvasElms).filter((element) => {
if (element.classList.contains('module-container')) {
return false;
}
if (
!element.classList.contains('is-module-editor') &&
element.getAttribute('component-type') === 'ModuleContainer'
) {
return false;
}
return true;
});
elementsArray.forEach(function (element) {
element.classList.remove('hide-grid');
element.classList.add('show-grid');

View file

@ -215,7 +215,7 @@ export const getDroppableSlotIdOnScreen = (event, widgets) => {
// Hack: This is a temporary solution. We need to find a better way to handle this.
// We have added this solution to fix dragging widget not being correctly dropped when it is there is scroll
const widgetType = getWidgetById(widgets, event.target.id)?.component?.component || CANVAS_ID;
if (!DROPPABLE_PARENTS.has(widgetType)) {
if (!DROPPABLE_PARENTS.has(widgetType) && widgetType !== 'ModuleViewer') {
const targetElems = document.elementsFromPoint(event.clientX, event.clientY);
const draggedOverElements = targetElems.filter(
(ele) => (ele.id !== event.target.id && ele.classList.contains('target')) || ele.classList.contains('real-canvas')

View file

@ -15,12 +15,11 @@ import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
import { handleDeactivateTargets, hideGridLines } from '../AppCanvas/Grid/gridUtils';
export const useCanvasDropHandler = () => {
const { moduleId, isModuleEditor, appType } = useModuleContext();
const { isModuleEditor } = useModuleContext();
const addComponentToCurrentPage = useStore((state) => state.addComponentToCurrentPage, shallow);
const setActiveRightSideBarTab = useStore((state) => state.setActiveRightSideBarTab, shallow);
const setShowModuleBorder = useStore((state) => state.setShowModuleBorder, shallow) || noop;
const currentMode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow);
const currentLayout = useStore((state) => state.currentLayout, shallow);
const setCurrentDragCanvasId = useGridStore((state) => state.actions.setCurrentDragCanvasId);
const setRightSidebarOpen = useStore((state) => state.setRightSidebarOpen, shallow);
@ -30,19 +29,20 @@ export const useCanvasDropHandler = () => {
!canvasId || canvasId === 'canvas'
? document.getElementById(`real-canvas`)
: document.getElementById(`canvas-${canvasId}`);
const isParentModuleContainer = realCanvasRef?.getAttribute('component-type') === 'ModuleContainer';
handleDeactivateTargets();
hideGridLines();
setShowModuleBorder(false); // Hide the module border when dropping
if (
currentMode === 'view' ||
(!isModuleEditor && appType === 'module' && draggedComponentType !== 'ModuleContainer') ||
(isModuleEditor && canvasId === 'canvas')
) {
if (isModuleEditor && canvasId === 'canvas') {
return;
}
if (!isModuleEditor && isParentModuleContainer) {
return toast.error('Modules cannot be edited inside an app');
}
if (draggedComponentType === 'PDF' && !isPDFSupported()) {
toast.error(
'PDF is not supported in this version of browser. We recommend upgrading to the latest version for full support.'

View file

@ -300,7 +300,7 @@ export const DateTimePicker = ({
return (
<div
data-disabled={styles.disabledState}
className={cx('datepicker-widget', {
className={cx('datepicker-widget tjdb-datepicker-wrapper', {
'theme-tjdb': !darkMode,
'theme-dark': darkMode,
})}

View file

@ -226,4 +226,33 @@
line-height: normal !important;
}
}
}
}
.tjdb-datepicker-wrapper {
.react-datepicker__day--selected {
background-color: rgb(77,94,240) !important;
color: #fff !important;
}
.react-datepicker__day.react-datepicker__day--selected:hover {
background-color: rgb(77,94,240) !important;
}
.react-datepicker__day:not(.react-datepicker__day--selected):hover {
background-color: #f0f0f0 !important;
}
.react-datepicker__day--keyboard-selected {
background-color: #bad9f1 !important;
}
}
.tjdb-datepicker-wrapper.theme-dark {
.react-datepicker__day:not(.react-datepicker__day--selected):hover {
background-color: #636466 !important;
}
}

View file

@ -421,8 +421,8 @@ export const PagesSidebarNavigation = ({
const isTopPositioned = position === 'top';
const labelHidden = labelStyle?.label?.hidden;
const headerHidden = isLicensed ? hideHeader : false;
const logoHidden = isLicensed ? hideLogo : false;
const headerHidden = isLicensed ? hideHeader : true;
const logoHidden = isLicensed ? hideLogo : true;
if (headerHidden && logoHidden && isPagesSidebarHidden) {
return null;

View file

@ -118,7 +118,7 @@ const FilePicker = (props) => {
backgroundColor: 'var(--cc-surface1-surface)',
color: darkMode ? '#c3c9d2' : '#5e6571',
height: `${numericWidgetHeight + (containerPadding === 'default' ? 0 : 4)}px`,
overflowY: isSmallWidget ? 'auto' : 'visible',
overflowY: isSmallWidget ? 'auto' : 'scroll',
opacity: disabledState ? 0.5 : 1,
}),
[darkMode, numericWidgetHeight, isVisible, isSmallWidget, disabledState, containerPadding]

View file

@ -13,6 +13,7 @@ import { getColorModeFromLuminance, getCssVarValue, getModifiedColor } from '@/E
import { useDynamicHeight } from '@/_hooks/useDynamicHeight';
import { useHeightObserver } from '@/_hooks/useHeightObserver';
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
import './table.scss';
export const Table = memo(
({
@ -187,6 +188,7 @@ export const Table = memo(
'--cc-table-row-hover': hoverColor,
'--cc-table-row-active': activeColor,
'--cc-table-scroll-bar-color': activeColor,
'--cc-table-border-color': borderColor,
}}
>
<TableContainer

View file

@ -7,6 +7,21 @@ import HighLightSearch from '@/AppBuilder/Widgets/NewTable/_components/HighLight
import useTextColor from '../DataTypes/_hooks/useTextColor';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
// Utility function to generate input step for decimal places
const getInputStep = (allowedDecimalPlaces) => {
if (allowedDecimalPlaces === null || allowedDecimalPlaces === undefined) {
return 'any';
}
const num = Number(allowedDecimalPlaces);
if (!Number.isFinite(num) || num < 0) {
return 'any';
}
const validDecimalPlaces = Math.floor(num);
return validDecimalPlaces === 0 ? '1' : `0.${'0'.repeat(validDecimalPlaces - 1)}1`;
};
export const NumberColumn = ({
id,
isEditable,
@ -129,7 +144,7 @@ export const NumberColumn = ({
className={`table-column-type-input-element input-number h-100 ${!isValid ? 'is-invalid' : ''}`}
value={displayValue}
onChange={(e) => setDisplayValue(e.target.value)}
step={allowedDecimalPlaces !== null ? `0.${'0'.repeat(allowedDecimalPlaces - 1)}1` : 'any'}
step={getInputStep(allowedDecimalPlaces)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
if (displayValue !== cellValue) {

View file

@ -79,9 +79,15 @@ export const TableData = ({
// Handles row click for row selection
const handleRowClick = (row) => {
if (!allowSelection) return;
lastClickedRowRef.current = { row: row?.original, index: row.index };
if (!allowSelection) {
setExposedVariables({
selectedRow: row?.original ?? {},
selectedRowId: isNaN(row.index) ? String(row.index) : row.index,
});
fireEvent('onRowClicked');
return;
}
// Update row selection
row.toggleSelected();
};

View file

@ -54,10 +54,6 @@ export const TableRow = ({
>
{row.getVisibleCells().map((cell) => {
const cellStyles = {
width:
cell.column.id === 'rightActions' || cell.column.id === 'leftActions'
? 'fit-content'
: cell.column.getSize(),
backgroundColor: getResolvedValue(cell.column.columnDef?.meta?.cellBackgroundColor ?? 'inherit', {
rowData: row.original,
cellValue: cell.getValue(),
@ -66,10 +62,8 @@ export const TableRow = ({
display: 'flex',
alignItems: 'center',
textAlign: cell.column.columnDef?.meta?.horizontalAlignment,
width: cell.column.getSize(),
};
if (cell.column.id === 'rightActions' || cell.column.id === 'leftActions') {
cellStyles.maxWidth = 'fit-content';
}
const isEditable = getResolvedValue(cell.column.columnDef?.meta?.isEditable ?? false, {
rowData: row.original,

View file

@ -263,6 +263,8 @@ export const TableExposedVariables = ({
]);
useEffect(() => {
// onRowClicked event will be fired when a row is clicked
// it should be triggered even when allowSelection is false which is handled in the handleRowClick()
if (allowSelection && Object.keys(lastClickedRow).length > 0) {
fireEvent('onRowClicked');
}

View file

@ -1,28 +1,75 @@
import React from 'react';
import { ActionButtons } from '../_components/ActionButtons/ActionButtons';
// Function to calculate text width using improved canvas measurement
const calculateTextWidth = (text) => {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
// Use the exact font properties from the CSS
// .btn-sm has font-size: .75rem which is typically 12px
// IBM Plex Sans is the primary font family used in the app
context.font = '500 12px "IBM Plex Sans"';
const textMetrics = context.measureText(text);
const textWidth = textMetrics.width;
// Add padding from .btn-sm: .125rem .5rem = 2px 8px (top/bottom left/right)
// Add border: typically 1px on each side
// Add margins from .m-1: .25rem = 4px on each side
const horizontalPadding = 16; // 8px left + 8px right
const horizontalBorder = 2; // 1px left + 1px right
const horizontalMargin = 8; // 4px left + 4px right
return textWidth + horizontalPadding + horizontalBorder + horizontalMargin;
};
// Function to calculate total width needed for action buttons
const calculateActionColumnWidth = (actions) => {
if (!actions || actions.length === 0) return 90; // minimum width
let totalWidth = 0;
const containerPadding = 24; // container padding
actions.forEach((action) => {
const buttonWidth = calculateTextWidth(action.buttonText || action.name || 'Button');
totalWidth += buttonWidth;
});
// Add container padding and 2px as the calculation is not accurate in the decimal values
totalWidth += containerPadding + 2;
// Ensure minimum width
return Math.max(90, totalWidth);
};
export const generateActionColumns = ({ actions, fireEvent, setExposedVariables, id }) => {
const leftActions = actions?.filter((action) => action.position === 'left') || [];
const rightActions = actions?.filter((action) => [undefined, 'right'].includes(action.position)) || [];
const createActionColumn = (position) => ({
id: `${position}Actions`,
accessorKey: 'actions',
enableResizing: false,
meta: { columnType: 'action', position, skipFilter: true, skipAddNewRow: true },
size: 90,
header: 'Actions',
cell: ({ row, cell }) => (
<ActionButtons
actions={position === 'left' ? leftActions : rightActions}
row={row}
cell={cell}
fireEvent={fireEvent}
setExposedVariables={setExposedVariables}
id={id}
/>
),
});
const createActionColumn = (position) => {
const actionsForPosition = position === 'left' ? leftActions : rightActions;
const calculatedWidth = calculateActionColumnWidth(actionsForPosition);
return {
id: `${position}Actions`,
accessorKey: 'actions',
enableResizing: false,
meta: { columnType: 'action', position, skipFilter: true, skipAddNewRow: true },
size: calculatedWidth,
header: 'Actions',
cell: ({ row, cell }) => (
<ActionButtons
actions={actionsForPosition}
row={row}
cell={cell}
fireEvent={fireEvent}
setExposedVariables={setExposedVariables}
id={id}
/>
),
};
};
const columns = [];
if (leftActions.length > 0) columns.push(createActionColumn('left'));

View file

@ -121,7 +121,9 @@ export default function generateColumnsData({
? getAddNewRowDetailFromIndex(id, row.index)
: getEditedRowFromIndex(id, row.index);
let cellValue = changeSet ? changeSet[cell.column.columnDef?.meta?.name] ?? cell.getValue() : cell.getValue();
let cellValue = changeSet
? changeSet[cell.column.columnDef?.accessorKey] ?? cell.getValue()
: cell.getValue();
cellValue = cellValue === undefined || cellValue === null ? '' : cellValue;
const rowData = tableData?.[row.index];

View file

@ -0,0 +1,4 @@
.card.jet-table.table-component {
border: 1px solid var(--cc-table-border-color) !important;
}

View file

@ -492,39 +492,79 @@ export const Tabs = function Tabs({
position: 'relative',
}}
>
<div
style={{
display: 'flex',
width: `${tabItems.length * 100}%`,
transform: `translateX(-${findTabIndex(currentTab) * (100 / tabItems.length)}%)`,
transition: transition === 'none' ? 'none' : 'transform 0.3s ease-in-out',
height: '100%',
}}
>
{tabItems.map((tab) => (
{transition === 'none' ? (
// Simple show/hide when no transition
tabItems.map((tab) => (
<div
key={tab.id}
style={{
width: `${100 / tabItems.length}%`,
flexShrink: 0,
display: tab.id === currentTab ? 'block' : 'none',
width: '100%',
height: '100%',
overflow: 'hidden',
boxSizing: 'border-box',
}}
>
<TabContent
id={id}
tab={tab}
height={height}
width={width}
parsedHideTabs={parsedHideTabs}
bgColor={bgColor}
darkMode={darkMode}
dynamicHeight={dynamicHeight}
currentTab={currentTab}
isTransitioning={isTransitioning}
/>
{shouldRenderTabContent(tab) && (
<TabContent
id={id}
tab={tab}
height={height}
width={width}
parsedHideTabs={parsedHideTabs}
bgColor={bgColor}
darkMode={darkMode}
dynamicHeight={dynamicHeight}
currentTab={currentTab}
isTransitioning={isTransitioning}
/>
)}
</div>
))}
</div>
))
) : (
// Sliding animation when transition is enabled
<div
style={{
display: 'flex',
width: `${tabItems.length * 100}%`,
transform: `translateX(-${findTabIndex(currentTab) * (100 / tabItems.length)}%)`,
transition: 'transform 0.3s ease-in-out',
height: '100%',
position: 'relative',
overflow: 'hidden',
}}
>
{tabItems.map((tab) => (
<div
key={tab.id}
style={{
width: `${100 / tabItems.length}%`,
flexShrink: 0,
height: '100%',
overflow: 'hidden',
boxSizing: 'border-box',
minWidth: 0,
contain: 'layout style size',
}}
>
{shouldRenderTabContent(tab) && (
<TabContent
id={id}
tab={tab}
height={height}
width={width}
parsedHideTabs={parsedHideTabs}
bgColor={bgColor}
darkMode={darkMode}
dynamicHeight={dynamicHeight}
currentTab={currentTab}
isTransitioning={isTransitioning}
/>
)}
</div>
))}
</div>
)}
</div>
)}
</div>
@ -577,6 +617,8 @@ const TabContent = memo(function TabContent({
backgroundColor: fieldBackgroundColor || bgColor,
opacity: disable ? 0.5 : 1,
pointerEvents: disable ? 'none' : 'auto',
overflow: 'hidden', // Ensure TabContent doesn't overflow
boxSizing: 'border-box', // Include padding/border in size calculation
}}
>
{loading ? (
@ -600,6 +642,10 @@ const TabContent = memo(function TabContent({
overflow: isTransitioning ? 'hidden' : 'hidden auto',
backgroundColor: fieldBackgroundColor || bgColor,
opacity: disable ? 0.5 : 1,
width: '100%', // Ensure it doesn't exceed container width
maxWidth: '100%', // Additional constraint
boxSizing: 'border-box', // Include padding/border in width
contain: 'layout style', // Add containment for better overflow control
}}
darkMode={darkMode}
/>
@ -607,4 +653,4 @@ const TabContent = memo(function TabContent({
</div>
);
},
areEqual);
areEqual);

View file

@ -263,7 +263,8 @@ export const createQueryPanelSlice = (set, get) => ({
});
},
onComplete: async (result) => {
await processQueryResults(result);
const processedResult = { data: result };
await processQueryResults(processedResult);
// Remove the AsyncQueryHandler instance from asyncQueryRuns on completion
get().queryPanel.setAsyncQueryRuns((currentRuns) =>
currentRuns.filter((handler) => handler.jobId !== asyncHandler.jobId)
@ -508,10 +509,12 @@ export const createQueryPanelSlice = (set, get) => ({
queryId,
{
isLoading: false,
...(errorData?.data?.type === 'tj-401' ? {
metadata: errorData?.metadata,
response: errorData?.data?.responseObject,
} : query.kind === 'restapi'
...(errorData?.data?.type === 'tj-401'
? {
metadata: errorData?.metadata,
response: errorData?.data?.responseObject,
}
: query.kind === 'restapi'
? {
metadata: errorData?.metadata,
request: errorData?.data?.requestObject,
@ -666,7 +669,8 @@ export const createQueryPanelSlice = (set, get) => ({
break;
}
errorData = (query.kind === 'runpy' || query.kind === 'runjs') && (data?.data?.type !== 'tj-401') ? data?.data : data;
errorData =
(query.kind === 'runpy' || query.kind === 'runjs') && data?.data?.type !== 'tj-401' ? data?.data : data;
const result = handleFailure(errorData);
resolve(result);
return;
@ -756,6 +760,7 @@ export const createQueryPanelSlice = (set, get) => ({
} else if (query.kind === 'workflows') {
queryExecutionPromise = triggerWorkflow(
moduleId,
query,
query.options.workflowId,
query.options.blocking,
query.options?.params,
@ -773,7 +778,7 @@ export const createQueryPanelSlice = (set, get) => ({
// async queries in the future
if (query.kind === 'workflows') {
const processQueryResultsPreview = async (result) => {
let finalData = result;
let finalData = result?.data;
if (query.options.enableTransformation) {
finalData = await runTransformation(
finalData,
@ -1232,7 +1237,6 @@ export const createQueryPanelSlice = (set, get) => ({
},
createProxy: (obj, path = '') => {
return new Proxy(obj, {
get(target, prop) {
const fullPath = path ? `${path}.${prop}` : prop;

View file

@ -111,7 +111,7 @@ export const Datepicker = function Datepicker({
className={`input-field form-control ${
!isValid && showValidationError ? 'is-invalid' : ''
} validation-without-icon px-2 ${darkMode ? 'bg-dark color-white' : 'bg-light'}`}
popperClassName={cx('tj-datepicker-widget', { 'dark-theme': darkMode })}
popperClassName={cx('legacy-datepicker-poppper tj-datepicker-widget', { 'dark-theme': darkMode })}
selected={date}
value={date !== null ? computeDateString(date) : 'select date'}
onChange={(date) => onDateChange(date)}
@ -123,6 +123,7 @@ export const Datepicker = function Datepicker({
}}
showMonthDropdown
showYearDropdown
portalId="component-portal"
dropdownMode="select"
excludeDates={excludedDates}
customInput={<input style={{ borderRadius: `${borderRadius}px`, boxShadow, height }} />}

View file

@ -50,4 +50,9 @@
// select option {
// color: #fff !important // This needs to be black since this has no effect on mac but has on windows
// }
// }
// }
.legacy-datepicker-poppper.react-datepicker-popper.tj-datepicker-widget {
width: 254px !important;
border-radius: 6px !important;
}

View file

@ -300,7 +300,7 @@ export const DateTimePicker = ({
return (
<div
data-disabled={styles.disabledState}
className={cx('datepicker-widget position-relative', {
className={cx('datepicker-widget tjdb-datepicker-wrapper position-relative', {
'theme-tjdb': !darkMode,
'theme-dark': darkMode,
})}

View file

@ -245,3 +245,34 @@
}
}
}
.tjdb-datepicker-wrapper {
.react-datepicker__day--selected {
background-color: rgb(77,94,240) !important;
color: #fff !important;
}
.react-datepicker__day.react-datepicker__day--selected:hover {
background-color: rgb(77,94,240) !important;
}
.react-datepicker__day:not(.react-datepicker__day--selected):hover {
background-color: #f0f0f0 !important;
}
.react-datepicker__day--keyboard-selected {
background-color: #bad9f1 !important;
}
}
.tjdb-datepicker-wrapper.theme-dark {
.react-datepicker__day:not(.react-datepicker__day--selected):hover {
background-color: #636466 !important;
}
}

View file

@ -38,6 +38,7 @@
.react-select__option {
background-color: var(--surfaces-surface-01) !important;
border-radius: 6px;
min-height: 38px;
>div {
@ -52,6 +53,9 @@
background-color: transparent !important;
}
}
&:hover .current-org-indicator {
display: block;
}
}
}
}

View file

@ -972,10 +972,6 @@
.jet-data-table .has-actions {
padding: 0 12px;
.justify-content-start {
justify-content: center !important;
}
}
.jet-table.table-component {
@ -1600,37 +1596,39 @@
}
.card.jet-table.table-component {
.table-cell {
input[type="checkbox"] {
position: relative;
appearance: none;
-webkit-appearance: none;
border: 1px solid var(--cc-default-border,--borders-default);
border-radius: 3px;
background-color: var(--cc-surface1-surface);
cursor: pointer;
&:checked {
background-color: var(--cc-primary-brand);
border-color: var(--cc-primary-brand);
&:after {
content: '';
position: absolute;
left: 4px;
top: 1px;
width: 5px;
height: 9px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
}
&:not(:checked) {
.table-cell {
input[type="checkbox"] {
position: relative;
appearance: none;
-webkit-appearance: none;
border: 1px solid var(--cc-default-border,--borders-default);
border-radius: 3px;
background-color: var(--cc-surface1-surface);
border-color: var(--cc-default-border,--borders-default);
cursor: pointer;
&:checked {
background-color: var(--cc-primary-brand);
border-color: var(--cc-primary-brand);
&:after {
content: '';
position: absolute;
left: 4px;
top: 1px;
width: 5px;
height: 9px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
}
&:not(:checked) {
background-color: var(--cc-surface1-surface);
border-color: var(--cc-default-border,--borders-default);
}
}
}
}

View file

@ -3899,7 +3899,7 @@ input[type="text"] {
}
.react-datepicker__day--selected {
background-color: var(--cc-primary-brand);
background-color: var(--cc-primary-brand, #216ba5);
}
}
@ -7545,19 +7545,6 @@ tbody {
max-width: 230px !important;
}
.table-cell {
padding: 0;
input[type="checkbox"] {
accent-color: var(--cc-primary-brand);
background-color: var(--cc-surface1-surface);
}
input[type="checkbox"]:checked::before {
background-color: var(--cc-surface1-surface);
}
}
.add-more-columns-btn {
background: var(--indigo3);
font-weight: 500;
@ -11619,10 +11606,6 @@ tbody {
display: none;
}
&:hover .current-org-indicator {
display: block;
}
.org-name {
color: var(--slate12) !important;
white-space: nowrap;
@ -16276,12 +16259,12 @@ fieldset:disabled {
.create-app-with-ai-prompt-wrapper {
padding: 16px 0;
padding: 30px 0;
header {
display: flex;
gap: 12px;
padding: 16px 24px;
// padding: 16px 24px;
background: var(--bg-surface-layer-01);
.left {
@ -18415,14 +18398,16 @@ section.ai-message-prompt-input-wrapper {
line-height: 20px;
}
.table-cell {
input[type="checkbox"] {
accent-color: var(--cc-primary-brand);
background-color: var(--cc-surface1-surface);
}
.card.jet-table.table-component {
.table-cell {
input[type="checkbox"] {
accent-color: var(--cc-primary-brand);
background-color: var(--cc-surface1-surface);
}
input[type="checkbox"]:checked::before {
background-color: var(--cc-surface1-surface);
input[type="checkbox"]:checked::before {
background-color: var(--cc-surface1-surface);
}
}
}

View file

@ -1 +1 @@
3.16.0
3.16.1-lts

View file

@ -0,0 +1,13 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class IncreaseWhiteLabellingLogoFaviconSize1722729600000 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE white_labelling
ALTER COLUMN logo TYPE varchar(1024),
ALTER COLUMN favicon TYPE varchar(1024)
`);
}
public async down(queryRunner: QueryRunner): Promise<void> { }
}

View file

@ -35,7 +35,7 @@
</td>
</tr>
<tr>
<td class="padding-y-10">
<td class="padding-y-20">
<a href={{inviteUrl}} target="_blank" class="padding-r-40" href="#">
<button class="primary-btn">
Join workspace

View file

@ -34,7 +34,7 @@
</td>
</tr>
<tr>
<td class="padding-y-10">
<td class="padding-y-20">
<a href={{resetLink}} target="_blank" class="padding-r-40" href="#">
<button class="primary-btn">
Reset your password

View file

@ -31,7 +31,7 @@
</td>
</tr>
<tr>
<td class="padding-y-10">
<td class="padding-y-20">
<a href={{inviteUrl}} target="_blank" class="padding-r-40" href="#">
<button class="primary-btn">
Confirm email address

View file

@ -34,7 +34,7 @@
</td>
</tr>
<tr>
<td class="padding-y-10">
<td class="padding-y-20">
<a href={{inviteUrl}} target="_blank" class="padding-r-40" href="#">
<button class="primary-btn">
Join workspace

View file

@ -34,7 +34,7 @@
</td>
</tr>
<tr>
<td class="padding-y-10">
<td class="padding-y-20">
<a href={{resetLink}} target="_blank" class="padding-r-40" href="#">
<button class="primary-btn">
Reset your password

View file

@ -31,7 +31,7 @@
</td>
</tr>
<tr>
<td class="padding-y-10">
<td class="padding-y-20">
<a href={{inviteUrl}} target="_blank" class="padding-r-40" href="#">
<button class="primary-btn">
Confirm email address

View file

@ -239,7 +239,7 @@ export class EmailUtilService implements IEmailUtilService {
const transporter = nodemailer.createTransport({
host: smtpSettings.host,
port: smtpSettings.port,
secure: smtpSettings.port === 465, // Use `true` for port 465, `false` for others
secure: smtpSettings.port == 465, // Use `true` for port 465, `false` for others
auth: {
user: smtpSettings.username,
pass: smtpSettings.password,