diff --git a/.github/workflows/cloud-frontend.yml b/.github/workflows/cloud-frontend.yml index 35d13aaa40..93f0bf901e 100644 --- a/.github/workflows/cloud-frontend.yml +++ b/.github/workflows/cloud-frontend.yml @@ -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 diff --git a/.github/workflows/deploy-to-stage.yml b/.github/workflows/deploy-to-stage.yml index 2881ba16c7..99b9f930a7 100644 --- a/.github/workflows/deploy-to-stage.yml +++ b/.github/workflows/deploy-to-stage.yml @@ -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 diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index 75e6f3440a..3cab5d4913 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -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 diff --git a/.github/workflows/merging-pr.yml b/.github/workflows/merging-pr.yml index 63c76b46a3..d31a89eeb6 100644 --- a/.github/workflows/merging-pr.yml +++ b/.github/workflows/merging-pr.yml @@ -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: diff --git a/.github/workflows/packer-build.yml b/.github/workflows/packer-build.yml index ad687cc3ed..2345aad050 100644 --- a/.github/workflows/packer-build.yml +++ b/.github/workflows/packer-build.yml @@ -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 diff --git a/.github/workflows/render-preview-deploy.yml b/.github/workflows/render-preview-deploy.yml index c84260c9ce..b502dbaa33 100644 --- a/.github/workflows/render-preview-deploy.yml +++ b/.github/workflows/render-preview-deploy.yml @@ -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, diff --git a/.version b/.version index 1eeac129c5..c99dce2c22 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -3.16.0 +3.16.1-lts diff --git a/deploy/ec2/ee/setup_machine.sh b/deploy/ec2/ee/setup_machine.sh index 5dd6c635fd..ac1f75dda5 100644 --- a/deploy/ec2/ee/setup_machine.sh +++ b/deploy/ec2/ee/setup_machine.sh @@ -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' diff --git a/docker/cloud/cloud-entrypoint.sh b/docker/LTS/cloud/cloud-entrypoint.sh similarity index 100% rename from docker/cloud/cloud-entrypoint.sh rename to docker/LTS/cloud/cloud-entrypoint.sh diff --git a/docker/cloud/cloud-production.Dockerfile b/docker/LTS/cloud/cloud-production.Dockerfile similarity index 97% rename from docker/cloud/cloud-production.Dockerfile rename to docker/LTS/cloud/cloud-production.Dockerfile index 7590566fc7..8f10aeca65 100644 --- a/docker/cloud/cloud-production.Dockerfile +++ b/docker/LTS/cloud/cloud-production.Dockerfile @@ -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 \ diff --git a/docker/cloud/cloud-server.Dockerfile b/docker/LTS/cloud/cloud-server.Dockerfile similarity index 97% rename from docker/cloud/cloud-server.Dockerfile rename to docker/LTS/cloud/cloud-server.Dockerfile index c2eb40d8d0..e8416cc5f6 100644 --- a/docker/cloud/cloud-server.Dockerfile +++ b/docker/LTS/cloud/cloud-server.Dockerfile @@ -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 diff --git a/docker/ee/ee-entrypoint.sh b/docker/LTS/ee/ee-entrypoint.sh similarity index 100% rename from docker/ee/ee-entrypoint.sh rename to docker/LTS/ee/ee-entrypoint.sh diff --git a/docker/LTS/ee/ee-preview.Dockerfile b/docker/LTS/ee/ee-preview.Dockerfile new file mode 100644 index 0000000000..a8a32aedd2 --- /dev/null +++ b/docker/LTS/ee/ee-preview.Dockerfile @@ -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"] diff --git a/docker/LTS/ee/ee-production.Dockerfile b/docker/LTS/ee/ee-production.Dockerfile new file mode 100644 index 0000000000..032927e032 --- /dev/null +++ b/docker/LTS/ee/ee-production.Dockerfile @@ -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"] diff --git a/docker/ee/ee-try-entrypoint-lts.sh b/docker/LTS/ee/ee-try-entrypoint-lts.sh similarity index 100% rename from docker/ee/ee-try-entrypoint-lts.sh rename to docker/LTS/ee/ee-try-entrypoint-lts.sh diff --git a/docker/ee/ee-try-tooljet-lts.Dockerfile b/docker/LTS/ee/ee-try-tooljet-lts.Dockerfile similarity index 92% rename from docker/ee/ee-try-tooljet-lts.Dockerfile rename to docker/LTS/ee/ee-try-tooljet-lts.Dockerfile index c9fa440db2..0292fcdad5 100644 --- a/docker/ee/ee-try-tooljet-lts.Dockerfile +++ b/docker/LTS/ee/ee-try-tooljet-lts.Dockerfile @@ -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"] diff --git a/docker/ee/temporal-server.yaml b/docker/LTS/ee/temporal-server.yaml similarity index 100% rename from docker/ee/temporal-server.yaml rename to docker/LTS/ee/temporal-server.yaml diff --git a/docker/ee/temporal-ui-server.yaml b/docker/LTS/ee/temporal-ui-server.yaml similarity index 100% rename from docker/ee/temporal-ui-server.yaml rename to docker/LTS/ee/temporal-ui-server.yaml diff --git a/docker/pre-release/ee/ee-entrypoint.sh b/docker/pre-release/ee/ee-entrypoint.sh new file mode 100755 index 0000000000..23cd04ab0e --- /dev/null +++ b/docker/pre-release/ee/ee-entrypoint.sh @@ -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 "$@" diff --git a/docker/ee/ee-preview.Dockerfile b/docker/pre-release/ee/ee-preview.Dockerfile similarity index 100% rename from docker/ee/ee-preview.Dockerfile rename to docker/pre-release/ee/ee-preview.Dockerfile diff --git a/docker/ee/ee-production.Dockerfile b/docker/pre-release/ee/ee-production.Dockerfile similarity index 99% rename from docker/ee/ee-production.Dockerfile rename to docker/pre-release/ee/ee-production.Dockerfile index ffe2353ac1..4935cae2bc 100644 --- a/docker/ee/ee-production.Dockerfile +++ b/docker/pre-release/ee/ee-production.Dockerfile @@ -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 && \ diff --git a/docker/ee/ee-try-entrypoint.sh b/docker/pre-release/ee/ee-try-entrypoint.sh similarity index 100% rename from docker/ee/ee-try-entrypoint.sh rename to docker/pre-release/ee/ee-try-entrypoint.sh diff --git a/docker/ee/ee-try-tooljet.Dockerfile b/docker/pre-release/ee/ee-try-tooljet.Dockerfile similarity index 92% rename from docker/ee/ee-try-tooljet.Dockerfile rename to docker/pre-release/ee/ee-try-tooljet.Dockerfile index a108f30691..560acb31c4 100644 --- a/docker/ee/ee-try-tooljet.Dockerfile +++ b/docker/pre-release/ee/ee-try-tooljet.Dockerfile @@ -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"] \ No newline at end of file diff --git a/docker/pre-release/ee/temporal-server.yaml b/docker/pre-release/ee/temporal-server.yaml new file mode 100644 index 0000000000..45324165a2 --- /dev/null +++ b/docker/pre-release/ee/temporal-server.yaml @@ -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" \ No newline at end of file diff --git a/docker/pre-release/ee/temporal-ui-server.yaml b/docker/pre-release/ee/temporal-ui-server.yaml new file mode 100644 index 0000000000..4daf530ae2 --- /dev/null +++ b/docker/pre-release/ee/temporal-ui-server.yaml @@ -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 diff --git a/frontend/.version b/frontend/.version index 1eeac129c5..c99dce2c22 100644 --- a/frontend/.version +++ b/frontend/.version @@ -1 +1 @@ -3.16.0 +3.16.1-lts diff --git a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx index 71564120dc..4fac71bce2 100644 --- a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx +++ b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx @@ -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' && ( { pagePositionType={position} appType={appType} /> - - + {currentMode === 'edit' && ( + <> + + + + )}
{appType !== 'module' &&
}
diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index ce1bb608eb..c3dafc657d 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -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" diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index 508fbeff36..ab7b4bcd70 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -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)`; diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js b/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js index 0415aaed6e..53bdc92bcc 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js +++ b/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js @@ -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'); diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/helpers/dragEnd.js b/frontend/src/AppBuilder/AppCanvas/Grid/helpers/dragEnd.js index 5d1a625b0d..578046a2b7 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/helpers/dragEnd.js +++ b/frontend/src/AppBuilder/AppCanvas/Grid/helpers/dragEnd.js @@ -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') diff --git a/frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js b/frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js index 626b7e231c..04c7f0cb52 100644 --- a/frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js +++ b/frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js @@ -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.' diff --git a/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/DateTimePicker/DateTimePicker.jsx b/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/DateTimePicker/DateTimePicker.jsx index 40818a3bb9..ef7738f4ea 100644 --- a/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/DateTimePicker/DateTimePicker.jsx +++ b/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/DateTimePicker/DateTimePicker.jsx @@ -300,7 +300,7 @@ export const DateTimePicker = ({ return (
{ 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] diff --git a/frontend/src/AppBuilder/Widgets/NewTable/Table.jsx b/frontend/src/AppBuilder/Widgets/NewTable/Table.jsx index 819a5c45d4..b1251d185d 100644 --- a/frontend/src/AppBuilder/Widgets/NewTable/Table.jsx +++ b/frontend/src/AppBuilder/Widgets/NewTable/Table.jsx @@ -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, }} > { + 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) { diff --git a/frontend/src/AppBuilder/Widgets/NewTable/_components/TableData/TableData.jsx b/frontend/src/AppBuilder/Widgets/NewTable/_components/TableData/TableData.jsx index 97c7afbe79..c36971b6f5 100644 --- a/frontend/src/AppBuilder/Widgets/NewTable/_components/TableData/TableData.jsx +++ b/frontend/src/AppBuilder/Widgets/NewTable/_components/TableData/TableData.jsx @@ -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(); }; diff --git a/frontend/src/AppBuilder/Widgets/NewTable/_components/TableData/_components/TableRow.jsx b/frontend/src/AppBuilder/Widgets/NewTable/_components/TableData/_components/TableRow.jsx index 2566d9db5b..9cdb39835c 100644 --- a/frontend/src/AppBuilder/Widgets/NewTable/_components/TableData/_components/TableRow.jsx +++ b/frontend/src/AppBuilder/Widgets/NewTable/_components/TableData/_components/TableRow.jsx @@ -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, diff --git a/frontend/src/AppBuilder/Widgets/NewTable/_components/TableExposedVariables/TableExposedVariables.jsx b/frontend/src/AppBuilder/Widgets/NewTable/_components/TableExposedVariables/TableExposedVariables.jsx index fbb907c3e6..8388cf4359 100644 --- a/frontend/src/AppBuilder/Widgets/NewTable/_components/TableExposedVariables/TableExposedVariables.jsx +++ b/frontend/src/AppBuilder/Widgets/NewTable/_components/TableExposedVariables/TableExposedVariables.jsx @@ -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'); } diff --git a/frontend/src/AppBuilder/Widgets/NewTable/_utils/generateActionColumns.js b/frontend/src/AppBuilder/Widgets/NewTable/_utils/generateActionColumns.js index 91240c90e8..ab26c6a4de 100644 --- a/frontend/src/AppBuilder/Widgets/NewTable/_utils/generateActionColumns.js +++ b/frontend/src/AppBuilder/Widgets/NewTable/_utils/generateActionColumns.js @@ -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 }) => ( - - ), - }); + 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 }) => ( + + ), + }; + }; const columns = []; if (leftActions.length > 0) columns.push(createActionColumn('left')); diff --git a/frontend/src/AppBuilder/Widgets/NewTable/_utils/generateColumnsData.js b/frontend/src/AppBuilder/Widgets/NewTable/_utils/generateColumnsData.js index a8fc86865e..0e9eb96774 100644 --- a/frontend/src/AppBuilder/Widgets/NewTable/_utils/generateColumnsData.js +++ b/frontend/src/AppBuilder/Widgets/NewTable/_utils/generateColumnsData.js @@ -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]; diff --git a/frontend/src/AppBuilder/Widgets/NewTable/table.scss b/frontend/src/AppBuilder/Widgets/NewTable/table.scss new file mode 100644 index 0000000000..54e0713301 --- /dev/null +++ b/frontend/src/AppBuilder/Widgets/NewTable/table.scss @@ -0,0 +1,4 @@ + +.card.jet-table.table-component { + border: 1px solid var(--cc-table-border-color) !important; +} \ No newline at end of file diff --git a/frontend/src/AppBuilder/Widgets/Tabs.jsx b/frontend/src/AppBuilder/Widgets/Tabs.jsx index cb4108f4f2..f5fc2b659f 100644 --- a/frontend/src/AppBuilder/Widgets/Tabs.jsx +++ b/frontend/src/AppBuilder/Widgets/Tabs.jsx @@ -492,39 +492,79 @@ export const Tabs = function Tabs({ position: 'relative', }} > -
- {tabItems.map((tab) => ( + {transition === 'none' ? ( + // Simple show/hide when no transition + tabItems.map((tab) => (
- + {shouldRenderTabContent(tab) && ( + + )}
- ))} -
+ )) + ) : ( + // Sliding animation when transition is enabled +
+ {tabItems.map((tab) => ( +
+ {shouldRenderTabContent(tab) && ( + + )} +
+ ))} +
+ )}
)}
@@ -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({ ); }, - areEqual); +areEqual); diff --git a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js index 49fe37835d..d45f7697dd 100644 --- a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js @@ -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; diff --git a/frontend/src/Editor/Components/Datepicker.jsx b/frontend/src/Editor/Components/Datepicker.jsx index abcd02759f..ee8163dd54 100644 --- a/frontend/src/Editor/Components/Datepicker.jsx +++ b/frontend/src/Editor/Components/Datepicker.jsx @@ -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={} diff --git a/frontend/src/Editor/Components/datepicker.scss b/frontend/src/Editor/Components/datepicker.scss index 64293be85d..9fae104bd2 100644 --- a/frontend/src/Editor/Components/datepicker.scss +++ b/frontend/src/Editor/Components/datepicker.scss @@ -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 // } - // } \ No newline at end of file + // } + +.legacy-datepicker-poppper.react-datepicker-popper.tj-datepicker-widget { + width: 254px !important; + border-radius: 6px !important; +} \ No newline at end of file diff --git a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/DateTimePicker/DateTimePicker.jsx b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/DateTimePicker/DateTimePicker.jsx index 874de20b33..2b34de65b3 100644 --- a/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/DateTimePicker/DateTimePicker.jsx +++ b/frontend/src/Editor/QueryManager/QueryEditors/TooljetDatabase/DateTimePicker/DateTimePicker.jsx @@ -300,7 +300,7 @@ export const DateTimePicker = ({ return (
div { @@ -52,6 +53,9 @@ background-color: transparent !important; } } + &:hover .current-org-indicator { + display: block; + } } } } diff --git a/frontend/src/_styles/table-component.scss b/frontend/src/_styles/table-component.scss index 578e854f40..4e4385e9e3 100644 --- a/frontend/src/_styles/table-component.scss +++ b/frontend/src/_styles/table-component.scss @@ -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); + } } } } \ No newline at end of file diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 40292b6803..f4a06ea7ed 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -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); + } } } diff --git a/server/.version b/server/.version index 1eeac129c5..c99dce2c22 100644 --- a/server/.version +++ b/server/.version @@ -1 +1 @@ -3.16.0 +3.16.1-lts diff --git a/server/migrations/1754283161900-IncreaseWhiteLabellingLogoFaviconSize.ts b/server/migrations/1754283161900-IncreaseWhiteLabellingLogoFaviconSize.ts new file mode 100644 index 0000000000..4d94726893 --- /dev/null +++ b/server/migrations/1754283161900-IncreaseWhiteLabellingLogoFaviconSize.ts @@ -0,0 +1,13 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class IncreaseWhiteLabellingLogoFaviconSize1722729600000 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + 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 { } +} \ No newline at end of file diff --git a/server/src/mails/default_invite_user.hbs b/server/src/mails/default_invite_user.hbs index 9d1a0e245d..560fc83b6d 100644 --- a/server/src/mails/default_invite_user.hbs +++ b/server/src/mails/default_invite_user.hbs @@ -35,7 +35,7 @@ - +