diff --git a/.github/workflows/packer-build.yml b/.github/workflows/packer-build.yml index aa60c6444f..7c1dfbb583 100644 --- a/.github/workflows/packer-build.yml +++ b/.github/workflows/packer-build.yml @@ -16,11 +16,11 @@ jobs: name: packer-ee steps: - - name: Checkout code to lts-4.0 + - name: Checkout code to lts-3.6 branch if: contains(github.event.release.tag_name, '-ee-lts') uses: actions/checkout@v2 with: - ref: refs/heads/lts-4.0 + ref: refs/heads/lts-3.6 - name: Setting tag if: "${{ github.event.inputs.version != '' }}" @@ -69,7 +69,7 @@ jobs: with: command: build #The the below argument is specific for building EE AMI image - arguments: -color=false -on-error=abort -var ami_name=tooljet_${{ env.RELEASE_VERSION }}.ubuntu_focal + arguments: -color=false -on-error=abort -var ami_name=tooljet_${{ env.RELEASE_VERSION }}.ubuntu_jammy target: . working_directory: deploy/ec2/ee env: @@ -78,9 +78,9 @@ jobs: - name: Send Slack Notification run: | if [[ "${{ job.status }}" == "success" ]]; then - message="ToolJet enterprise AWS AMI published:\\n\`tooljet_${{ env.RELEASE_VERSION }}.ubuntu_focal\`" + message="ToolJet enterprise AWS AMI published:\\n\`tooljet_${{ env.RELEASE_VERSION }}.ubuntu-jammy\`" else - message="ToolJet enterprise AWS AMI release failed! \\n\`tooljet_${{ env.RELEASE_VERSION }}.ubuntu_focal\`" + message="ToolJet enterprise AWS AMI release failed! \\n\`tooljet_${{ env.RELEASE_VERSION }}.ubuntu-jammy\`" fi curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$message\"}" ${{ secrets.SLACK_WEBHOOK_URL }} \ No newline at end of file diff --git a/cypress-tests/cypress-marketplace.config.js b/cypress-tests/cypress-marketplace.config.js index b8ffbeaa26..ce955b3c66 100644 --- a/cypress-tests/cypress-marketplace.config.js +++ b/cypress-tests/cypress-marketplace.config.js @@ -77,7 +77,7 @@ module.exports = defineConfig({ baseUrl: "http://localhost:8082", specPattern: [ "cypress/e2e/happyPath/marketplace/commonTestcases/**/*.cy.js", - ], + ] numTestsKeptInMemory: 1, redirectionLimit: 7, experimentalRunAllSpecs: true, diff --git a/cypress-tests/cypress/commands/commands.js b/cypress-tests/cypress/commands/commands.js index 98cbcb31d0..79fea8849f 100644 --- a/cypress-tests/cypress/commands/commands.js +++ b/cypress-tests/cypress/commands/commands.js @@ -239,9 +239,9 @@ Cypress.Commands.add( .invoke("text") .then((text) => { cy.wrap(subject).realType(createBackspaceText(text)), - { - delay: 0, - }; + { + delay: 0, + }; }); } ); @@ -561,7 +561,7 @@ Cypress.Commands.add("installMarketplacePlugin", (pluginName) => { } }); - function installPlugin (pluginName) { + function installPlugin(pluginName) { cy.get('[data-cy="-list-item"]').eq(1).click(); cy.wait(1000); @@ -621,6 +621,7 @@ Cypress.Commands.add("uninstallMarketplacePlugin", (pluginName) => { Cypress.Commands.add( "verifyRequiredFieldValidation", (fieldName, expectedColor) => { + cy.get(commonSelectors.textField(fieldName)).type("some text").clear(); cy.get(commonSelectors.textField(fieldName)).should( "have.css", "border-color", diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTableHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTableHappyPath.cy.js index 0f3cf9c7a5..9e383b041d 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTableHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTableHappyPath.cy.js @@ -202,10 +202,10 @@ describe("Data source Airtable", () => { ); cy.get(dataSourceSelector.queryPreviewButton).click(); - cy.verifyToastMessage( - commonSelectors.toastMessage, - `Query (${data.dsName}) completed.` - ); + // cy.verifyToastMessage( + // commonSelectors.toastMessage, + // `Query (${data.dsName}) completed.` + // ); // Verfiy Retrieve record operation @@ -225,10 +225,10 @@ describe("Data source Airtable", () => { ); cy.get(dataSourceSelector.queryPreviewButton).click(); - cy.verifyToastMessage( - commonSelectors.toastMessage, - `Query (${data.dsName}) completed.` - ); + // cy.verifyToastMessage( + // commonSelectors.toastMessage, + // `Query (${data.dsName}) completed.` + // ); // Verfiy Create record operation @@ -251,10 +251,10 @@ describe("Data source Airtable", () => { .realType('": {}', { force: true, delay: 0 }); cy.get(dataSourceSelector.queryPreviewButton).click(); - cy.verifyToastMessage( - commonSelectors.toastMessage, - `Query (${data.dsName}) completed.` - ); + // cy.verifyToastMessage( + // commonSelectors.toastMessage, + // `Query (${data.dsName}) completed.` + // ); // Verfiy Update record operation @@ -285,10 +285,10 @@ describe("Data source Airtable", () => { .realType('"Phone Number": "555_98"', { force: true, delay: 0 }); cy.get(dataSourceSelector.queryPreviewButton).click(); - cy.verifyToastMessage( - commonSelectors.toastMessage, - `Query (${data.queryName}) completed.` - ); + // cy.verifyToastMessage( + // commonSelectors.toastMessage, + // `Query (${data.queryName}) completed.` + // ); // Verify Delete record operation @@ -337,10 +337,10 @@ describe("Data source Airtable", () => { ); cy.get(dataSourceSelector.queryPreviewButton).click(); - cy.verifyToastMessage( - commonSelectors.toastMessage, - `Query (${data.queryName}) completed.` - ); + // cy.verifyToastMessage( + // commonSelectors.toastMessage, + // `Query (${data.queryName}) completed.` + // ); cy.apiDeleteApp(`${data.dsName}-airtable-app`); cy.apiDeleteGDS(`cypress-${data.dsName}-airtable`); diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/postgresHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/postgresHappyPath.cy.js index b86ca7cb17..962baeb991 100644 --- a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/postgresHappyPath.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/postgresHappyPath.cy.js @@ -254,7 +254,7 @@ describe("Data sources", () => { .and("be.disabled"); cy.get(dataSourceSelector.connectionAlertText).verifyVisibleElement( "have.text", - "connect ECONNREFUSED 127.0.0.1:5432" + postgreSqlText.serverNotSuppotSsl ); cy.apiDeleteGDS(`cypress-${data.dataSourceName}-postgresql`); diff --git a/deploy/ec2/ce/.env b/deploy/ec2/ce/.env deleted file mode 100644 index 8668d3d824..0000000000 --- a/deploy/ec2/ce/.env +++ /dev/null @@ -1,60 +0,0 @@ -# https://docs.tooljet.io/docs/setup/env-vars -TOOLJET_HOST=__required__ -LOCKBOX_MASTER_KEY=__required__ -SECRET_KEY_BASE=__required__ -PG_USER=__required__ -PG_HOST=__required__ -PG_PASS=__required__ -PG_DB=tooljet_prod -ORM_LOGGING=true -NODE_ENV=production -DEPLOYMENT_PLATFORM=ec2 - -# ToolJet Database -TOOLJET_DB=tooljet_db -TOOLJET_DB_USER= -TOOLJET_DB_HOST= -TOOLJET_DB_PASS= -PGRST_HOST=localhost:3001 -PGRST_SERVER_PORT=3001 -PGRST_JWT_SECRET= -PGRST_DB_URI= -PGRST_DB_PRE_CONFIG=postgrest.pre_config - -# Checks every 24 hours to see if a new version of ToolJet is available -# (Enabled by default. Set 0 to disable) -CHECK_FOR_UPDATES= - -# Checks every 24 hours to update app telemetry data to ToolJet hub. -# (Telemetry is enabled by default. Set value to true to disable.) -# DISABLE_APP_TELEMETRY=false - -GOOGLE_CLIENT_ID= -GOOGLE_CLIENT_SECRET= - -# EMAIL CONFIGURATION -DEFAULT_FROM_EMAIL=hello@tooljet.io -SMTP_USERNAME= -SMTP_PASSWORD= -SMTP_DOMAIN= -SMTP_PORT= - -# DISABLE USER SIGNUPS (true or false). Default: true -DISABLE_SIGNUPS= - -# OBSERVABILITY -APM_VENDOR= -SENTRY_DNS= -SENTRY_DEBUG= - -# FEATURE TOGGLE -COMMENT_FEATURE_ENABLE= -ENABLE_MULTIPLAYER_EDITING=true - -#SSO -SSO_DISABLE_SIGNUP= -SSO_RESTRICTED_DOMAIN= -SSO_GOOGLE_OAUTH2_CLIENT_ID= -SSO_GIT_OAUTH2_CLIENT_ID= -SSO_GIT_OAUTH2_CLIENT_SECRET= -SSO_GIT_OAUTH2_HOST= diff --git a/deploy/ec2/ce/nest.service b/deploy/ec2/ce/nest.service deleted file mode 100644 index 61a1127e2f..0000000000 --- a/deploy/ec2/ce/nest.service +++ /dev/null @@ -1,17 +0,0 @@ -[Unit] -Description=Nest Server -After=network.target - -[Service] -Type=simple -User=ubuntu - -WorkingDirectory=/home/ubuntu/app -Environment="NODE_ENV=production" -EnvironmentFile=/home/ubuntu/app/.env -RestartSec=1 -ExecStart=/usr/bin/npm --prefix /home/ubuntu/app run start:prod -Restart=always - -[Install] -WantedBy=multi-user.target diff --git a/deploy/ec2/ce/postgrest.service b/deploy/ec2/ce/postgrest.service deleted file mode 100644 index 806c6c8ee1..0000000000 --- a/deploy/ec2/ce/postgrest.service +++ /dev/null @@ -1,16 +0,0 @@ -[Unit] -Description=PostgREST Server -After=network.target - -[Service] -Type=simple -User=ubuntu - -WorkingDirectory=/bin -EnvironmentFile=/home/ubuntu/app/.env -RestartSec=1 -ExecStart=/bin/postgrest -Restart=always - -[Install] -WantedBy=multi-user.target \ No newline at end of file diff --git a/deploy/ec2/ce/setup_app b/deploy/ec2/ce/setup_app deleted file mode 100755 index b07a1299d5..0000000000 --- a/deploy/ec2/ce/setup_app +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/bash - -if grep __required__ .env -then - echo "Please set the required values within the .env file" - exit 1 -fi - -export $(grep -v '^#' .env | xargs) - -if psql -d postgresql://$PG_USER:$PG_PASS@$PG_HOST/postgres -c 'select now()' > /dev/null 2>&1 -then - echo "Successfully pinged the database!"; -else - echo "Can't connect to the database. Kindly check the credenials provided in the .env file!" - exit 1 -fi - -if sudo systemctl start openresty -then - echo "Successfully started reverse proxy!" -else - echo "Failed to start reverse proxy" - exit 1 -fi - -if $ENABLE_TOOLJET_DB == "true" -then - if sudo systemctl start postgrest - then - echo "Successfully started PostgREST server!" - else - echo "Failed to start PostgREST server" - exit 1 - fi -fi - -TOOLJET_EDTION=ce npm --prefix server run db:setup:prod - -if sudo systemctl start nest -then - echo "The app will be served at ${TOOLJET_HOST}" -else - echo "Failed to start the server!" - exit 1 -fi diff --git a/deploy/ec2/ce/setup_machine.sh b/deploy/ec2/ce/setup_machine.sh deleted file mode 100644 index 8e23853c7c..0000000000 --- a/deploy/ec2/ce/setup_machine.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/bin/bash - -set -e -# Setup prerequisite dependencies -sudo apt-get update -sudo apt-get -y install --no-install-recommends wget gnupg ca-certificates apt-utils git curl postgresql-client -curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash -export NVM_DIR="$HOME/.nvm" -[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" -nvm install 22.15.1 -sudo ln -s "$(which node)" /usr/bin/node -sudo ln -s "$(which npm)" /usr/bin/npm - -sudo npm i -g npm@10.9.2 - -# Setup openresty -wget -O - https://openresty.org/package/pubkey.gpg | sudo apt-key add - -echo "deb http://openresty.org/package/ubuntu bionic main" > openresty.list -sudo mv openresty.list /etc/apt/sources.list.d/ -sudo apt-get update -sudo apt-get -y install --no-install-recommends openresty -sudo apt-get install -y curl g++ gcc autoconf automake bison libc6-dev \ - libffi-dev libgdbm-dev libncurses5-dev libsqlite3-dev libtool \ - libyaml-dev make pkg-config sqlite3 zlib1g-dev libgmp-dev \ - libreadline-dev libssl-dev libmysqlclient-dev build-essential \ - freetds-dev libpq-dev -sudo apt-get install -y luarocks -sudo luarocks install lua-resty-auto-ssl -sudo mkdir /etc/resty-auto-ssl /var/log/openresty /etc/fallback-certs -sudo chown -R www-data:www-data /etc/resty-auto-ssl - -# Oracle db client library setup -sudo apt install -y libaio1 -curl -o instantclient-basiclite.zip https://download.oracle.com/otn_software/linux/instantclient/instantclient-basiclite-linuxx64.zip -SL && \ -curl -o instantclient-basiclite-11.zip https://tooljet-plugins-production.s3.us-east-2.amazonaws.com/marketplace-assets/oracledb/instantclients/instantclient-basiclite-linux.x64-11.2.0.4.0.zip -SL && \ - unzip instantclient-basiclite.zip && \ - unzip instantclient-basiclite-11.zip && \ - sudo mkdir -p /usr/lib/instantclient && sudo mv instantclient*/ /usr/lib/instantclient && \ - rm instantclient-basiclite.zip && \ - rm instantclient-basiclite-11.zip && \ - echo /usr/lib/instantclient/* | sudo tee /etc/ld.so.conf.d/oracle-instantclient.conf > /dev/null && sudo ldconfig -# Set the Instant Client library paths -export LD_LIBRARY_PATH="/usr/lib/instantclient/instantclient_11_2:/usr/lib/instantclient/instantclient_21_10${LD_LIBRARY_PATH}" - -# Gen fallback certs -sudo openssl rand -out /home/ubuntu/.rnd -hex 256 -sudo chown www-data:www-data /home/ubuntu/.rnd -sudo openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 \ - -subj '/CN=sni-support-required-for-valid-ssl' \ - -keyout /etc/fallback-certs/resty-auto-ssl-fallback.key \ - -out /etc/fallback-certs/resty-auto-ssl-fallback.crt - -# Setup nginx config -export SERVER_HOST="${SERVER_HOST:=localhost}" -export SERVER_USER="${SERVER_USER:=www-data}" -VARS_TO_SUBSTITUTE='$SERVER_HOST:$SERVER_USER' -envsubst "${VARS_TO_SUBSTITUTE}" < /tmp/nginx.conf > /tmp/nginx-substituted.conf -sudo cp /tmp/nginx-substituted.conf /usr/local/openresty/nginx/conf/nginx.conf - -# Download and setup postgrest binary -curl -OL https://github.com/PostgREST/postgrest/releases/download/v12.2.0/postgrest-v12.2.0-linux-static-x64.tar.xz -tar xJf postgrest-v12.2.0-linux-static-x64.tar.xz -sudo mv ./postgrest /bin/postgrest -sudo rm postgrest-v12.2.0-linux-static-x64.tar.xz - -# Setup app and postgrest as systemd service -sudo cp /tmp/nest.service /lib/systemd/system/nest.service -sudo cp /tmp/postgrest.service /lib/systemd/system/postgrest.service - -# Setup app directory -mkdir -p ~/app -git clone -b main https://github.com/ToolJet/ToolJet.git ~/app && cd ~/app - - -mv /tmp/.env ~/app/.env -mv /tmp/setup_app ~/app/setup_app -sudo chmod +x ~/app/setup_app - -npm install -g npm@10.9.2 - -# Building ToolJet app -npm install -g @nestjs/cli -TOOLJET_EDTION=ce npm run build diff --git a/deploy/ec2/ce/tooljet_ubuntu_focal.pkr.hcl b/deploy/ec2/ce/tooljet_ubuntu_focal.pkr.hcl deleted file mode 100644 index 9c61b0d554..0000000000 --- a/deploy/ec2/ce/tooljet_ubuntu_focal.pkr.hcl +++ /dev/null @@ -1,63 +0,0 @@ -packer { - required_plugins { - amazon = { - version = ">= 0.0.1" - source = "github.com/hashicorp/amazon" - } - } -} - -source "amazon-ebs" "ubuntu" { - ami_name = "${var.ami_name}" - instance_type = "${var.instance_type}" - region = "${var.ami_region}" - ami_regions = "${var.ami_regions}" - ami_groups = "${var.ami_groups}" - source_ami_filter { - filters = { - name = "ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*" - root-device-type = "ebs" - virtualization-type = "hvm" - } - most_recent = true - owners = ["099720109477"] - } - ssh_username = "ubuntu" - ssh_clear_authorized_keys = "true" -} - - -build { - sources = [ - "source.amazon-ebs.ubuntu" - ] - - provisioner "file" { - source = "nest.service" - destination = "/tmp/nest.service" - } - - provisioner "file" { - source = "../../frontend/config/nginx.conf.template" - destination = "/tmp/nginx.conf" - } - - provisioner "file" { - source = ".env" - destination = "/tmp/.env" - } - - provisioner "file" { - source = "setup_app" - destination = "/tmp/setup_app" - } - - provisioner "file" { - source = "postgrest.service" - destination = "/tmp/postgrest.service" - } - - provisioner "shell" { - script = "setup_machine.sh" - } -} diff --git a/deploy/ec2/ce/variables.pkr.hcl b/deploy/ec2/ce/variables.pkr.hcl deleted file mode 100644 index fcd6254505..0000000000 --- a/deploy/ec2/ce/variables.pkr.hcl +++ /dev/null @@ -1,23 +0,0 @@ -variable "ami_name" { - type = string -} - -variable "instance_type" { - type = string - default = "t2.medium" -} - -variable "ami_region" { - type = string - default = "us-west-1" -} - -variable "ami_groups" { - type = list(string) - default = ["all"] -} - -variable "ami_regions" { - type = list(string) - default = ["us-west-1", "us-east-1", "us-east-2", "eu-west-2", "eu-central-1", "ap-northeast-1", "ap-southeast-1","ap-northeast-3", "ap-south-1", "ap-northeast-2", "ap-southeast-2", "ca-central-1", "eu-west-1", "eu-north-1", "sa-east-1", "ap-east-1"] -} diff --git a/deploy/ec2/ee/setup_app b/deploy/ec2/ee/setup_app index 3dad6ebeef..deec307c1d 100755 --- a/deploy/ec2/ee/setup_app +++ b/deploy/ec2/ee/setup_app @@ -161,6 +161,21 @@ else exit 1 fi +if [[ "$WORKFLOW_WORKER" == "true" ]]; then + echo "WORKER is true. Running the worker..." + npm run worker:prod & +else + echo "WORKER is not true. Skipping the worker execution." +fi + +if sudo systemctl start neo4j && sudo systemctl enable neo4j +then + echo "Successfully started Neo4j!" +else + echo "Failed to start and enable Neo4j" + exit 1 +fi + TOOLJET_EDTION=ee npm --prefix server run db:setup:prod if sudo -E systemctl start nest @@ -172,4 +187,4 @@ else fi sudo systemctl restart nest -sudo -E systemctl restart postgrest \ No newline at end of file +sudo -E systemctl restart postgrest diff --git a/deploy/ec2/ee/setup_machine.sh b/deploy/ec2/ee/setup_machine.sh index 7c8427b9e2..5dd6c635fd 100644 --- a/deploy/ec2/ee/setup_machine.sh +++ b/deploy/ec2/ee/setup_machine.sh @@ -78,6 +78,28 @@ sudo cp /tmp/redis-server.service /lib/systemd/system/redis-server.service # Start and enable Redis service sudo systemctl daemon-reload + +# Setup Neo4j with APOC plugin +wget -O - https://debian.neo4j.com/neotechnology.gpg.key | sudo apt-key add - +echo "deb https://debian.neo4j.com stable 5" | sudo tee /etc/apt/sources.list.d/neo4j.list +sudo apt-get update +sudo apt-get install -y neo4j=1:5.26.6 +sudo apt-mark hold neo4j + +# Setup APOC plugin +sudo mkdir -p /var/lib/neo4j/plugins +sudo wget -P /var/lib/neo4j/plugins https://github.com/neo4j/apoc/releases/download/5.26.6/apoc-5.26.6-core.jar + +# Update Neo4j config +echo "dbms.security.procedures.unrestricted=apoc.*" | sudo tee -a /etc/neo4j/neo4j.conf +echo "dbms.security.procedures.allowlist=apoc.*,algo.*,gds.*" | sudo tee -a /etc/neo4j/neo4j.conf +echo "dbms.directories.plugins=/var/lib/neo4j/plugins" | sudo tee -a /etc/neo4j/neo4j.conf +echo "dbms.security.auth_enabled=true" | sudo tee -a /etc/neo4j/neo4j.conf + +# Clean up APT cache +sudo apt-get clean +sudo rm -rf /var/lib/apt/lists/* + # Setup app directory mkdir -p ~/app @@ -96,4 +118,5 @@ npm install -g npm@10.9.2 # Building ToolJet app npm install -g @nestjs/cli -TOOLJET_EDTION=ee npm run build \ No newline at end of file +export NODE_OPTIONS='--max-old-space-size=8000' +TOOLJET_EDTION=ee npm run build diff --git a/deploy/ec2/ee/tooljet_ubuntu_focal.pkr.hcl b/deploy/ec2/ee/tooljet_ubuntu_focal.pkr.hcl index 144803d55c..675c67f4b5 100644 --- a/deploy/ec2/ee/tooljet_ubuntu_focal.pkr.hcl +++ b/deploy/ec2/ee/tooljet_ubuntu_focal.pkr.hcl @@ -16,7 +16,7 @@ source "amazon-ebs" "ubuntu" { source_ami_filter { filters = { - name = "ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*" + name = "ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*" root-device-type = "ebs" virtualization-type = "hvm" } @@ -30,7 +30,7 @@ source "amazon-ebs" "ubuntu" { launch_block_device_mappings { device_name = "/dev/sda1" - volume_size = 10 + volume_size = 30 delete_on_termination = true } @@ -47,7 +47,7 @@ build { } provisioner "file" { - source = "../../frontend/config/nginx.conf.template" + source = "../../../frontend/config/nginx.conf.template" destination = "/tmp/nginx.conf" } diff --git a/deploy/ec2/ee/variables.pkr.hcl b/deploy/ec2/ee/variables.pkr.hcl index 39dcdfd3cd..f18073b73b 100644 --- a/deploy/ec2/ee/variables.pkr.hcl +++ b/deploy/ec2/ee/variables.pkr.hcl @@ -4,7 +4,7 @@ variable "ami_name" { variable "instance_type" { type = string - default = "t2.medium" + default = "t2.large" } variable "ami_region" { diff --git a/docker/ee/ee-try-entrypoint-lts.sh b/docker/ee/ee-try-entrypoint-lts.sh index f716ae8a20..c46d799a5b 100755 --- a/docker/ee/ee-try-entrypoint-lts.sh +++ b/docker/ee/ee-try-entrypoint-lts.sh @@ -3,6 +3,116 @@ set -e echo "🚀 Starting Try ToolJet container initialization..." +# 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 + + # Configure PostgreSQL authentication echo "🔧 Configuring PostgreSQL authentication..." sed -i 's/^local\s\+all\s\+postgres\s\+\(peer\|md5\)/local all postgres trust/' /etc/postgresql/13/main/pg_hba.conf >/dev/null 2>&1 diff --git a/docker/ee/ee-try-entrypoint.sh b/docker/ee/ee-try-entrypoint.sh index 8e2332ba19..df6128f9da 100755 --- a/docker/ee/ee-try-entrypoint.sh +++ b/docker/ee/ee-try-entrypoint.sh @@ -3,6 +3,115 @@ set -e echo "🚀 Starting Try ToolJet container initialization..." +# 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 + # Configure PostgreSQL authentication echo "🔧 Configuring PostgreSQL authentication..." sed -i 's/^local\s\+all\s\+postgres\s\+\(peer\|md5\)/local all postgres trust/' /etc/postgresql/13/main/pg_hba.conf >/dev/null 2>&1 diff --git a/docker/ee/ee-try-tooljet-lts.Dockerfile b/docker/ee/ee-try-tooljet-lts.Dockerfile index 2dcf56edfe..c9fa440db2 100644 --- a/docker/ee/ee-try-tooljet-lts.Dockerfile +++ b/docker/ee/ee-try-tooljet-lts.Dockerfile @@ -6,7 +6,7 @@ COPY --from=postgrest/postgrest:v12.2.0 /bin/postgrest /bin # 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/ bookworm-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list +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 apt update && apt -y install postgresql-13 postgresql-client-13 supervisor USER postgres @@ -52,6 +52,18 @@ RUN apt update && apt install -y gettext-base curl \ 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 +# 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/* + # Configure Supervisor to manage PostgREST, ToolJet, and Redis RUN echo "[supervisord] \n" \ "nodaemon=true \n" \ diff --git a/docker/ee/ee-try-tooljet.Dockerfile b/docker/ee/ee-try-tooljet.Dockerfile index 3aa416b87d..a108f30691 100644 --- a/docker/ee/ee-try-tooljet.Dockerfile +++ b/docker/ee/ee-try-tooljet.Dockerfile @@ -6,7 +6,7 @@ COPY --from=postgrest/postgrest:v12.2.0 /bin/postgrest /bin # 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/ bookworm-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list +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 apt update && apt -y install postgresql-13 postgresql-client-13 supervisor USER postgres @@ -52,6 +52,18 @@ RUN apt update && apt install -y gettext-base curl \ 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 +# 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/* + # Configure Supervisor to manage PostgREST, ToolJet, and Redis RUN echo "[supervisord] \n" \ "nodaemon=true \n" \ diff --git a/frontend/src/App/App.jsx b/frontend/src/App/App.jsx index d3cda91e3f..e7a5b21f11 100644 --- a/frontend/src/App/App.jsx +++ b/frontend/src/App/App.jsx @@ -38,6 +38,7 @@ import { getDataSourcesRoutes, getAuditLogsRoutes, } from '@/modules'; +import { isWorkflowsFeatureEnabled } from '@/modules/common/helpers/utils'; import { shallow } from 'zustand/shallow'; import useStore from '@/AppBuilder/_stores/store'; import { checkIfToolJetCloud } from '@/_helpers/utils'; @@ -112,6 +113,7 @@ class AppComponent extends React.Component { const featureAccess = await licenseService.getFeatureAccess(); const isBasicPlan = !featureAccess?.licenseStatus?.isLicenseValid || featureAccess?.licenseStatus?.isExpired; this.setState({ showBanner: isBasicPlan }); + this.updateColorScheme(); } // check if its getting routed from editor checkPreviousRoute = (route) => { @@ -121,7 +123,7 @@ class AppComponent extends React.Component { return false; }; - componentDidUpdate(prevProps) { + componentDidUpdate(prevProps, prevState) { // Check if the current location is the dashboard (homepage) if ( this.props.location.pathname === `/${getWorkspaceIdOrSlugFromURL()}` && @@ -134,18 +136,24 @@ class AppComponent extends React.Component { } // Update margin when showBanner changes this.updateMargin(); + // Update color scheme if darkMode changed + if (prevState.darkMode !== this.state.darkMode) { + this.updateColorScheme(); + } } switchDarkMode = (newMode) => { this.setState({ darkMode: newMode }); this.props.updateIsTJDarkMode(newMode); localStorage.setItem('darkMode', newMode); + this.updateColorScheme(newMode); }; isEditorOrViewerFromPath = () => { const pathname = this.props.location.pathname; if (pathname.includes('/apps/')) { return 'editor'; - } else if (pathname.includes('/applications/') || pathname.includes('/embed-apps/')) { + } + if (pathname.includes('/applications/') || pathname.includes('/embed-apps/')) { return 'viewer'; } return ''; @@ -156,6 +164,14 @@ class AppComponent extends React.Component { isExistingPlanUser = (date) => { return new Date(date) < new Date('2025-04-24'); //show banner if user created before 2 april (24 for testing) }; + updateColorScheme = (darkModeValue) => { + const isDark = darkModeValue !== undefined ? darkModeValue : this.state.darkMode; + if (isDark) { + document.documentElement.style.setProperty('color-scheme', 'dark'); + } else { + document.documentElement.style.removeProperty('color-scheme'); + } + }; render() { const { updateAvailable, darkMode, isEditorOrViewer, showBanner } = this.state; const mergedProps = { @@ -278,7 +294,7 @@ class AppComponent extends React.Component { } /> - {window.public_config?.ENABLE_WORKFLOWS_FEATURE === 'true' && ( + {isWorkflowsFeatureEnabled() && ( )} - } - > + } /> } - > - }> + /> + + } + /> - + diff --git a/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx b/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx index 869ab065f2..c6a024eaf7 100644 --- a/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx +++ b/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx @@ -348,7 +348,7 @@ const MultiLineCodeEditor = (props) => { view={editorView} isPanelOpen={isSearchPanelOpen} renderCopilot={() => - renderCopilot({ + renderCopilot?.({ darkMode, language: lang, editorRef, diff --git a/frontend/src/AppBuilder/QueryManager/Components/DataSourcePicker.jsx b/frontend/src/AppBuilder/QueryManager/Components/DataSourcePicker.jsx index 1c6ebe9ebd..30f2828ae8 100644 --- a/frontend/src/AppBuilder/QueryManager/Components/DataSourcePicker.jsx +++ b/frontend/src/AppBuilder/QueryManager/Components/DataSourcePicker.jsx @@ -14,6 +14,7 @@ import { useQueryPanelActions } from '@/_stores/queryPanelStore'; import { Tooltip } from 'react-tooltip'; import { canCreateDataSource } from '@/_helpers'; import SolidIcon from '@/_ui/Icon/SolidIcons'; +import { isWorkflowsFeatureEnabled } from '@/modules/common/helpers/utils'; import '../queryManager.theme.scss'; import useStore from '@/AppBuilder/_stores/store'; import { staticDataSources } from '../constants'; @@ -80,7 +81,7 @@ function DataSourcePicker({ darkMode }) { navigate(`/${workspaceId}/data-sources`); }; - const workflowsEnabled = window.public_config?.ENABLE_WORKFLOWS_FEATURE == 'true'; + const workflowsEnabled = isWorkflowsFeatureEnabled(); return ( <> diff --git a/frontend/src/AppBuilder/QueryManager/Components/DataSourceSelect.jsx b/frontend/src/AppBuilder/QueryManager/Components/DataSourceSelect.jsx index ed40086216..52a9e8c1ea 100644 --- a/frontend/src/AppBuilder/QueryManager/Components/DataSourceSelect.jsx +++ b/frontend/src/AppBuilder/QueryManager/Components/DataSourceSelect.jsx @@ -15,6 +15,7 @@ import { DataBaseSources, ApiSources, CloudStorageSources } from '@/modules/comm import { canCreateDataSource } from '@/_helpers'; import './../queryManager.theme.scss'; import { DATA_SOURCE_TYPE } from '@/_helpers/constants'; +import { isWorkflowsFeatureEnabled } from '@/modules/common/helpers/utils'; import useStore from '@/AppBuilder/_stores/store'; function DataSourceSelect({ isDisabled, selectRef, closePopup, workflowDataSources, onNewNode, defaultDataSources }) { @@ -39,7 +40,7 @@ function DataSourceSelect({ isDisabled, selectRef, closePopup, workflowDataSourc closePopup(); }; - const workflowsEnabled = window.public_config?.ENABLE_WORKFLOWS_FEATURE == 'true'; + const workflowsEnabled = isWorkflowsFeatureEnabled(); const staticDataSources = workflowsEnabled ? staticDatasources : staticDatasources.filter((ds) => ds?.kind !== 'workflows'); diff --git a/frontend/src/AppBuilder/QueryManager/QueryEditors/Restapi/index.jsx b/frontend/src/AppBuilder/QueryManager/QueryEditors/Restapi/index.jsx index 547aa5452c..e5a9849c35 100644 --- a/frontend/src/AppBuilder/QueryManager/QueryEditors/Restapi/index.jsx +++ b/frontend/src/AppBuilder/QueryManager/QueryEditors/Restapi/index.jsx @@ -9,6 +9,7 @@ import { BaseUrl } from './BaseUrl'; import { queryManagerSelectComponentStyle } from '@/_ui/Select/styles'; import CodeHinter from '@/AppBuilder/CodeEditor'; import { deepClone } from '@/_helpers/utilities/utils.helpers'; +import './styles.css'; class Restapi extends React.Component { constructor(props) { @@ -287,14 +288,15 @@ class Restapi extends React.Component { const { options } = this.state; const dataSourceURL = this.props.selectedDataSource?.options?.url?.value; const queryName = this.props.queryName; + const isWorkflowNode = queryName === 'workflowNode'; const currentValue = { label: options.method?.toUpperCase(), value: options.method }; return ( -
+
{this.props.selectedDataSource?.scope == 'global' &&
}{' '}
-
-
+
+

-
-
+
+
- - -
- )} +   + {appType !== 'workflow' + ? t('blankPage.importApplication', 'Import an app') + : t('blankPage.importWorkflow', 'Import a workflow')} + + + +
diff --git a/frontend/src/HomePage/Folders.jsx b/frontend/src/HomePage/Folders.jsx index 3a94991f4e..4e4a735687 100644 --- a/frontend/src/HomePage/Folders.jsx +++ b/frontend/src/HomePage/Folders.jsx @@ -14,6 +14,8 @@ import _ from 'lodash'; import { validateName, handleHttpErrorMessages, getWorkspaceId } from '@/_helpers/utils'; import { useNavigate, useLocation } from 'react-router-dom'; import FolderSkeleton from '@/_ui/FolderSkeleton/FolderSkeleton'; +import { Button } from '@/components/ui/Button/Button'; + export const Folders = function Folders({ folders, foldersLoading, @@ -246,24 +248,36 @@ export const Folders = function Folders({
{canCreateFolder && ( <> -
{ setNewFolderName(''); setShowForm(true); }} data-cy="create-new-folder-button" > - -
-
+ +
+ + )}
@@ -287,8 +301,7 @@ export const Folders = function Folders({ className={cx( `list-group-item border-0 list-group-item-action d-flex align-items-center all-apps-link tj-text-xsm`, { - 'bg-light-indigo': _.isEmpty(activeFolder) && !darkMode, - 'bg-dark-indigo': _.isEmpty(activeFolder) && darkMode, + 'tw-bg-interactive-default': _.isEmpty(activeFolder), } )} style={{ height: '32px' }} @@ -314,8 +327,7 @@ export const Folders = function Folders({ className={cx( `folder-list-group-item rounded-2 list-group-item h-4 mb-1 list-group-item-action no-border d-flex align-items-center`, { - 'bg-light-indigo': activeFolder.id === folder.id && !darkMode, - 'bg-dark-indigo': activeFolder.id === folder.id && darkMode, + 'tw-bg-interactive-default': activeFolder.id === folder.id, } )} onClick={() => { diff --git a/frontend/src/HomePage/Header.jsx b/frontend/src/HomePage/Header.jsx index aa6580cb29..d37e19bbbc 100644 --- a/frontend/src/HomePage/Header.jsx +++ b/frontend/src/HomePage/Header.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { SearchBox } from '@/_components/SearchBox'; +import { SearchBox } from '@/_components/PageSearchBox'; import { useTranslation } from 'react-i18next'; export default function HomeHeader({ onSearchSubmit, darkMode, appType }) { @@ -14,17 +14,15 @@ export default function HomeHeader({ onSearchSubmit, darkMode, appType }) { : t('globals.workflowsSearchItem', 'Search workflows in this workspace'); return ( -
-
- -
+
+
); } diff --git a/frontend/src/HomePage/HomePage.jsx b/frontend/src/HomePage/HomePage.jsx index 8f387a1650..31aa2dfee0 100644 --- a/frontend/src/HomePage/HomePage.jsx +++ b/frontend/src/HomePage/HomePage.jsx @@ -12,7 +12,7 @@ import { } from '@/_services'; import { ConfirmDialog, AppModal, ToolTip } from '@/_components'; import Select from '@/_ui/Select'; -import _, { sample, isEmpty, capitalize } from 'lodash'; +import _, { sample, isEmpty, capitalize, has } from 'lodash'; import { Folders } from './Folders'; import { BlankPage } from './BlankPage'; import { toast } from 'react-hot-toast'; @@ -48,6 +48,7 @@ import { } from '@/modules/dashboard/components'; import CreateAppWithPrompt from '@/modules/AiBuilder/components/CreateAppWithPrompt'; import SolidIcon from '@/_ui/Icon/SolidIcons'; +import { isWorkflowsFeatureEnabled } from '@/modules/common/helpers/utils'; import EmptyModuleSvg from '../../assets/images/icons/empty-modules.svg'; const { iconList, defaultIcon } = configs; @@ -256,7 +257,11 @@ class HomePageComponent extends React.Component { }; getAppType = () => { - return this.props.appType === 'module' ? 'Module' : this.props.appType === 'workflow' ? 'Workflow' : 'App'; + const { appType } = this.props; + if (appType === 'front-end') return 'App'; + if (appType === 'workflow') return 'Workflow'; + if (appType === 'module') return 'Module'; + return 'app'; }; createApp = async (appName, type, prompt) => { @@ -339,6 +344,66 @@ class HomePageComponent extends React.Component { this.setState({ isExportingApp: true, app: app }); }; + exportAppDirectly = async (app) => { + try { + const fetchVersions = await appsService.getVersions(app.id); + const { versions } = fetchVersions; + + const currentEditingVersion = versions?.filter((version) => version?.isCurrentEditingVersion)[0]; + if (!currentEditingVersion) { + toast.error('Could not find current editing version.', { + position: 'top-center', + }); + return; + } + + // Export all TJDB tables used by default + const fetchTables = await appsService.getTables(app.id); + const { tables: allTables } = fetchTables; + + const versionId = currentEditingVersion.id; + const exportTjDb = true; + const exportTables = allTables; + + const appOpts = { + app: [ + { + id: app.id, + search_params: { version_id: versionId }, + }, + ], + }; + + const requestBody = { + ...appOpts, + ...(exportTjDb && { tooljet_database: exportTables }), + organization_id: app.organization_id, + }; + + const data = await appsService.exportResource(requestBody); + + const appName = app.name.replace(/\s+/g, '-').toLowerCase(); + const fileName = `${appName}-export-${new Date().getTime()}`; + const json = JSON.stringify(data, null, 2); + const blob = new Blob([json], { type: 'application/json' }); + const href = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = href; + link.download = fileName + '.json'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + toast.success('Workflow exported successfully!', { + position: 'top-center', + }); + } catch (error) { + toast.error(`Could not export workflow: ${error?.data?.message || error.message}`, { + position: 'top-center', + }); + } + }; + readAndImport = (event) => { try { const file = event.target.files[0]; @@ -413,7 +478,7 @@ class HomePageComponent extends React.Component { let installedPluginsInfo = []; try { if (this.state.dependentPlugins.length) { - ({ installedPluginsInfo = [] } = await pluginsService.installDependentPlugins( + ({ installedPluginsInfo =[] } = await pluginsService.installDependentPlugins( this.state.dependentPlugins, true )); @@ -421,8 +486,7 @@ class HomePageComponent extends React.Component { if (importJSON.app[0].definition.appV2.type !== this.props.appType) { toast.error( - `${this.props.appType === 'module' ? 'App' : 'Module'} could not be imported in ${ - this.props.appType === 'module' ? 'modules' : 'apps' + `${this.props.appType === 'module' ? 'App' : 'Module'} could not be imported in ${this.props.appType === 'module' ? 'modules' : 'apps' } section. Switch to ${this.props.appType === 'module' ? 'apps' : 'modules'} section and try again.`, { style: { maxWidth: '425px' } } ); @@ -453,7 +517,7 @@ class HomePageComponent extends React.Component { this.setState({ isImportingApp: false }); if (error.statusCode === 409) return false; - toast.error(error?.error || error?.message || 'App import failed'); + toast.error(error?.error || error?.message || `${capitalize(this.getAppType())} import failed`); } }; @@ -485,7 +549,7 @@ class HomePageComponent extends React.Component { }; canViewWorkflow = () => { - return this.canUserPerform(this.state.currentUser, 'view'); + return this.canUserPerform(this.state.currentUser, 'view') && isWorkflowsFeatureEnabled(); }; canUserPerform(user, action, app) { @@ -953,6 +1017,53 @@ class HomePageComponent extends React.Component { importingGitAppOperations: validationMessage, }); }; + + // Helper functions for workflow limit checks + hasWorkflowLimitReached = () => { + const { workflowInstanceLevelLimit, workflowWorkspaceLevelLimit } = this.state; + + const instanceLimitReached = + workflowInstanceLevelLimit.total === 0 || workflowInstanceLevelLimit.current >= workflowInstanceLevelLimit.total; + const workspaceLimitReached = + workflowWorkspaceLevelLimit.total === 0 || + workflowWorkspaceLevelLimit.current >= workflowWorkspaceLevelLimit.total; + + return instanceLimitReached || workspaceLimitReached; + }; + + hasWorkflowLimitWarning = () => { + const { workflowInstanceLevelLimit, workflowWorkspaceLevelLimit } = this.state; + return this.hasInstanceLimitWarning() || this.hasWorkspaceLimitWarning(); + }; + + hasInstanceLimitWarning = () => { + const { workflowInstanceLevelLimit } = this.state; + const percentage = workflowInstanceLevelLimit.percentage; + + return ( + workflowInstanceLevelLimit.current >= workflowInstanceLevelLimit.total || + (percentage >= 90 && percentage < 100) || + workflowInstanceLevelLimit.current === workflowInstanceLevelLimit.total - 1 + ); + }; + + hasWorkspaceLimitWarning = () => { + const { workflowWorkspaceLevelLimit } = this.state; + const percentage = workflowWorkspaceLevelLimit.percentage; + + return ( + workflowWorkspaceLevelLimit.current >= workflowWorkspaceLevelLimit.total || + (percentage >= 90 && percentage < 100) || + workflowWorkspaceLevelLimit.current === workflowWorkspaceLevelLimit.total - 1 + ); + }; + + getWorkflowLimit = () => { + return this.hasInstanceLimitWarning() + ? this.state.workflowInstanceLevelLimit + : this.state.workflowWorkspaceLevelLimit; + }; + render() { const { apps, @@ -1012,7 +1123,7 @@ class HomePageComponent extends React.Component { } else if (this.props.appType === 'front-end') { return appsLimit?.percentage >= 100; } else { - return workflowInstanceLevelLimit.percentage >= 100 || workflowWorkspaceLevelLimit.percentage >= 100; + return this.hasWorkflowLimitReached(); } }; const modalConfigs = { @@ -1113,9 +1224,8 @@ class HomePageComponent extends React.Component {
User groups @@ -1191,8 +1301,8 @@ class HomePageComponent extends React.Component { this.props.appType === 'workflow' ? 'homePage.deleteWorkflowAndData' : this.props.appType === 'front-end' - ? 'homePage.deleteAppAndData' - : deleteModuleText, + ? 'homePage.deleteAppAndData' + : deleteModuleText, { appName: appToBeDeleted?.name, } @@ -1457,22 +1567,18 @@ class HomePageComponent extends React.Component { {this.props.appType === 'module' ? 'Create new module' : this.props.t( - `${ - this.props.appType === 'workflow' ? 'workflowsDashboard' : 'homePage' - }.header.createNewApplication`, - 'Create new app' - )} + `${this.props.appType === 'workflow' ? 'workflowsDashboard' : 'homePage' + }.header.createNewApplication`, + 'Create new app' + )} - - {this.props.appType !== 'workflow' && ( - - )} + = workflowInstanceLevelLimit.total || - 100 > workflowInstanceLevelLimit.percentage >= 90 || - workflowInstanceLevelLimit.current === workflowInstanceLevelLimit.total - 1 + 100 > workflowInstanceLevelLimit.percentage >= 90 || + workflowInstanceLevelLimit.current === workflowInstanceLevelLimit.total - 1 ? workflowInstanceLevelLimit : workflowWorkspaceLevelLimit } @@ -1545,12 +1651,7 @@ class HomePageComponent extends React.Component {
-
+
{featuresLoaded && !isLoading ? ( <> @@ -1577,15 +1678,12 @@ class HomePageComponent extends React.Component { {(meta?.total_count > 0 || appSearchKey) && ( <> {!(isLoading && !appSearchKey) && ( - <> - -
- + )}
{currentFolder?.count ?? meta?.total_count} APPS @@ -1633,8 +1731,8 @@ class HomePageComponent extends React.Component { appType={this.props.appType} workflowsLimit={ workflowInstanceLevelLimit.current >= workflowInstanceLevelLimit.total || - 100 > workflowInstanceLevelLimit.percentage >= 90 || - workflowInstanceLevelLimit.current === workflowInstanceLevelLimit.total - 1 + 100 > workflowInstanceLevelLimit.percentage >= 90 || + workflowInstanceLevelLimit.current === workflowInstanceLevelLimit.total - 1 ? workflowInstanceLevelLimit : workflowWorkspaceLevelLimit } @@ -1679,7 +1777,7 @@ class HomePageComponent extends React.Component { canUpdateApp={this.canUpdateApp} deleteApp={this.deleteApp} cloneApp={this.cloneApp} - exportApp={this.exportApp} + exportApp={this.props.appType === 'workflow' ? this.exportAppDirectly : this.exportApp} meta={meta} currentFolder={currentFolder} isLoading={isLoading || !featuresLoaded} diff --git a/frontend/src/HomePage/styles/homepage.scss b/frontend/src/HomePage/styles/homepage.scss new file mode 100644 index 0000000000..5f94c9668f --- /dev/null +++ b/frontend/src/HomePage/styles/homepage.scss @@ -0,0 +1,295 @@ +.home-page-sidebar { + height: calc(100vh - 48px) !important; //64 is navbar height + + .folder-list-user { + height: calc(100vh - 116px) !important; //64 is navbar height + 52 px footer + } +} + +.app-list { + margin: 24px 0; +} + +.home-search-holder { + height: 48px; + width: 100%; + margin-top: 32px; +} +.homepage-app-card-list-item-wrap { + column-gap: 24px; + row-gap: 24px; + flex-wrap: wrap; + display: flex; +} + +.homepage-app-card-list-item { + max-width: 272px; + flex-basis: 33%; + padding: 0 !important; +} + +.homepage-dropdown-style { + min-width: 11rem; + display: block; + align-items: center; + margin: 0; + line-height: 1.4285714; + width: 100%; + padding: 0.5rem 0.75rem !important; + font-weight: 400; + white-space: nowrap; + border: 0; + cursor: pointer; + font-size: 12px; +} + +.homepage-dropdown-style:hover { + background: rgba(101, 109, 119, 0.06); +} + +.menu-icon--trigger { + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 10px; + background-color: var(--background-surface-layer-01); + box-shadow: none; + transition: all 0.15s ease-in-out; + will-change: background-color, box-shadow; + + &:hover { + background-color: var(--background-surface-layer-02); + box-shadow: var(--elevation-000-box-shadow); + } +} + +.home-app-card-header { + margin-bottom: 32px; +} + +.homepage-app-card { + height: 160px; + padding: 16px; + + .app-icon-main { + background: var(--indigo3) !important; + border-radius: 6px !important; + display: flex; + justify-content: center; + align-items: center; + width: 48px; + height: 48px; + will-change: height, width; + transition: all 0.15s ease-in-out; + } + + .appcard-buttons-wrap { + visibility: hidden; + opacity: 0; + height: 0; + } + + .home-app-card-header { + .menu-ico { + visibility: hidden !important; + } + } + + &:hover { + .home-app-card-header { + margin-bottom: 12px; + + .menu-ico { + visibility: visible !important; + } + } + + .app-creation-time-container { + margin-bottom: 0px; + } + + .app-card-name { + margin-bottom: 0px; + } + + .app-creation-time { + // display: none; + } + + .appcard-buttons-wrap { + display: flex; + visibility: visible; + opacity: 1; + padding: 0px; + gap: 12px; + width: 240px; + height: 28px; + flex-direction: row; + transition: all 0.15s ease-in-out; + will-change: opacity, visibility; + + div { + a { + text-decoration: none; + } + } + } + + .app-icon-main { + width: 36px; + height: 36px; + } + } +} + +.home-page-content-container { + max-width: 880px; + + @media only screen and (max-width: 768px) { + margin-bottom: 0rem !important; + + .liner { + width: unset !important; + } + + .app-list { + overflow-y: auto; + height: calc(100vh - 26rem); + + .skeleton-container { + display: flex; + flex-direction: column; + + .col { + display: flex; + justify-content: center; + margin-bottom: 1rem; + } + + .card-skeleton-container { + width: 304px; + } + } + } + + .menu-ico { + display: none !important; + } + } +} + + +.home-page-footer { + height: 52px; + background-color: var(--page-weak) !important; + border-top: 1px solid var(--border-weak) !important; + width: calc(100% - 336px) !important; + + @media only screen and (max-width: 768px) { + position: unset; + width: 100%; + + .col-4, + .col-5 { + display: none; + } + + .pagination-container { + display: flex !important; + align-items: center; + justify-content: center; + } + } +} + +@media only screen and (min-width: 1728px) { + .homepage-app-card-list-item { + // max-width: 304px; + max-width: calc(33.3% - 16px); + + .edit-button, + .launch-button { + width: 129px !important; + } + } + + .home-page-content-container { + max-width: 976px; + } + + .liner { + width: 976px; + } +} + +@media only screen and (min-width: 1584px) and (max-width: 1727px) { + .homepage-app-card-list-item { + max-width: calc(33.3% - 16px); + } + + .edit-button, + .launch-button { + width: 113px !important; + } +} + +@media only screen and (min-width: 1312px) and (max-width: 1583px) { + .homepage-app-card-list-item { + // max-width: 264px; + max-width: calc(33.3% - 16px); + + .edit-button, + .launch-button { + width: 109px !important; + } + } +} + +@media only screen and (min-width: 993px) and (max-width: 1311px) { + .home-page-content-container { + max-width: 568px; + } + + .homepage-app-card-list-item-wrap { + row-gap: 20px; + } + + .homepage-app-card-list-item { + // max-width: 269px; + max-width: calc(50% - 12px); + flex-basis: 50%; + flex-grow: 1; + flex-shrink: 0; + + .edit-button, + .launch-button { + width: 111.5px !important; + } + } + + .liner { + width: 568px; + } +} + +@media only screen and (max-width: 992px) { + .homepage-app-card-list-item-wrap { + display: flex; + justify-content: center; + width: 100%; + gap: 24px; + } + + .homepage-app-card-list-item { + // max-width: 304px !important; + max-width: calc(50% - 12px); + flex-basis: 100%; + + .edit-button, + .launch-button { + width: 129px !important; + } + } +} diff --git a/frontend/src/MarketplacePage/MarketplaceCard.jsx b/frontend/src/MarketplacePage/MarketplaceCard.jsx index 1a2c07cf61..daf6e470f6 100644 --- a/frontend/src/MarketplacePage/MarketplaceCard.jsx +++ b/frontend/src/MarketplacePage/MarketplaceCard.jsx @@ -52,7 +52,7 @@ export const MarketplaceCard = ({ id, name, repo, description, version, isInstal return (
-
+
diff --git a/frontend/src/SettingsPage/SettingsPage.jsx b/frontend/src/SettingsPage/SettingsPage.jsx index 53dfcad2d8..e7768dcf5f 100644 --- a/frontend/src/SettingsPage/SettingsPage.jsx +++ b/frontend/src/SettingsPage/SettingsPage.jsx @@ -169,7 +169,7 @@ function SettingsPage(props) {
-
+

{t('header.profileSettingPage.profile', 'Profile')} @@ -244,8 +244,7 @@ function SettingsPage(props) {

-
-
+

{t('header.profileSettingPage.changePassword', 'Change password')} diff --git a/frontend/src/TooljetDatabase/Forms/TableSchema.jsx b/frontend/src/TooljetDatabase/Forms/TableSchema.jsx index 9bee86278a..e6a2359739 100644 --- a/frontend/src/TooljetDatabase/Forms/TableSchema.jsx +++ b/frontend/src/TooljetDatabase/Forms/TableSchema.jsx @@ -368,6 +368,12 @@ function TableSchema({ isDisabled={ isEditMode && columnDetails[index]?.constraints_type?.is_primary_key === true ? true : false } + classNames={{ + control: (state) => cx({ + '!tw-border-border-default': true, + }), + + }} />

diff --git a/frontend/src/TooljetDatabase/Forms/styles.scss b/frontend/src/TooljetDatabase/Forms/styles.scss index 7bc6fe366c..233850ee4e 100644 --- a/frontend/src/TooljetDatabase/Forms/styles.scss +++ b/frontend/src/TooljetDatabase/Forms/styles.scss @@ -548,7 +548,7 @@ } .empty-foreignkey-container { - border: 1px dashed #d7dbdf; + border: 1px dashed var(--border-default); height: 40px; width: 270px !important; border-radius: 100px !important; diff --git a/frontend/src/TooljetDatabase/Table/Header.jsx b/frontend/src/TooljetDatabase/Table/Header.jsx index 9774822a6f..53e43309fe 100644 --- a/frontend/src/TooljetDatabase/Table/Header.jsx +++ b/frontend/src/TooljetDatabase/Table/Header.jsx @@ -148,8 +148,8 @@ const Header = ({ return ( <>
-
-
+
+
<> diff --git a/frontend/src/TooljetDatabase/Table/styles.scss b/frontend/src/TooljetDatabase/Table/styles.scss index 5f32c6e68d..120148b5d8 100644 --- a/frontend/src/TooljetDatabase/Table/styles.scss +++ b/frontend/src/TooljetDatabase/Table/styles.scss @@ -97,8 +97,8 @@ z-index: 1; position: sticky; left: 66px; - border-right: 2px solid var(--light-slate-08, #C1C8CD); - background-color: white; + border-right: 2px solid var(--border-weak); + background-color: var(--surfaces-surface-01); } th { @@ -145,14 +145,14 @@ th:nth-child(2) { z-index: 2; left: 66px; - border-right: 2px solid var(--light-slate-08, #C1C8CD); + border-right: 2px solid var(--border-weak); } .dark-background { td:nth-child(1), td:nth-child(2) { - background-color: #2B394A; + background-color: var(--surfaces-surface-01); } } @@ -283,26 +283,6 @@ background-color: #2B2F30 !important; } -.empty-table-description { - font-size: 14px !important; - line-height: 20px !important; - margin-top: 5px !important; -} - -.empty-table-container { - display: flex; - align-items: center; - justify-content: center; - height: calc(100% - 95px); -} - -.tjdb-create-new-table { - width: 180px !important; - margin: 0px auto !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; -} .keyPress-actions { diff --git a/frontend/src/TooljetDatabase/TableList/index.jsx b/frontend/src/TooljetDatabase/TableList/index.jsx index 61afc607c3..6686ffb989 100644 --- a/frontend/src/TooljetDatabase/TableList/index.jsx +++ b/frontend/src/TooljetDatabase/TableList/index.jsx @@ -8,6 +8,7 @@ import { ListItem } from '../TableListItem'; import { BreadCrumbContext } from '../../App/App'; import Search from '../Search'; import SolidIcon from '@/_ui/Icon/SolidIcons'; +import { Button } from '@/components/ui/Button/Button'; const List = () => { const { @@ -83,15 +84,23 @@ const List = () => { <> All tables ({filteredTables.length}) -
{ setShowInput(true); }} data-cy="create-new-folder-button" > - -
+ + ) : ( + + diff --git a/frontend/src/WorkflowEditor/LogsPanel/icons/triangle-right.svg b/frontend/src/WorkflowEditor/LogsPanel/icons/triangle-right.svg new file mode 100644 index 0000000000..9f8648e692 --- /dev/null +++ b/frontend/src/WorkflowEditor/LogsPanel/icons/triangle-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/_components/DynamicForm.jsx b/frontend/src/_components/DynamicForm.jsx index bdc7f71054..dcb0078909 100644 --- a/frontend/src/_components/DynamicForm.jsx +++ b/frontend/src/_components/DynamicForm.jsx @@ -253,6 +253,8 @@ const DynamicForm = ({ }) => { const source = schema?.source?.kind; const darkMode = localStorage.getItem('darkMode') === 'true'; + const workspaceConstant = options?.[key]?.workspace_constant; + const isWorkspaceConstant = !!workspaceConstant; if (!options) return; @@ -264,7 +266,7 @@ const DynamicForm = ({ (options?.[key]?.encrypted !== undefined ? options?.[key].encrypted : encrypted) || type === 'password'; return { type, - placeholder: useEncrypted ? '**************' : description, + placeholder: workspaceConstant ? workspaceConstant : useEncrypted ? '**************' : description, className: `form-control${handleToggle(controller)} ${useEncrypted && 'dynamic-form-encrypted-field'}`, style: { marginBottom: '0px !important' }, value: options?.[key]?.value || '', @@ -276,6 +278,7 @@ const DynamicForm = ({ workspaceVariables, workspaceConstants: currentOrgEnvironmentConstants, encrypted: useEncrypted, + isWorkspaceConstant: isWorkspaceConstant, }; } case 'toggle': @@ -509,10 +512,16 @@ const DynamicForm = ({ return; } const isEditing = computedProps[field]['disabled']; + const workspaceConstant = options?.[field]?.workspace_constant; + const isWorkspaceConstant = !!workspaceConstant; + if (isEditing) { - optionchanged(field, ''); + if (isWorkspaceConstant) { + optionchanged(field, workspaceConstant); + } else { + optionchanged(field, ''); + } } else { - //Send old field value if editing mode disabled for encrypted fields const newOptions = { ...options }; const oldFieldValue = selectedDataSource?.['options']?.[field]; if (oldFieldValue) { diff --git a/frontend/src/_components/DynamicFormV2.jsx b/frontend/src/_components/DynamicFormV2.jsx index 6554f43da2..3ea431b67e 100644 --- a/frontend/src/_components/DynamicFormV2.jsx +++ b/frontend/src/_components/DynamicFormV2.jsx @@ -8,7 +8,6 @@ import Headers from '@/_ui/HttpHeaders'; import Toggle from '@/_ui/Toggle'; import InputV3 from '@/_ui/Input-V3'; import { filter, find, isEmpty } from 'lodash'; -import { ButtonSolid } from './AppButton'; import { useGlobalDataSourcesStatus } from '@/_stores/dataSourcesStore'; import { canDeleteDataSource, canUpdateDataSource } from '@/_helpers'; import { OverlayTrigger, Tooltip } from 'react-bootstrap'; @@ -206,39 +205,63 @@ const DynamicFormV2 = ({ } const processFields = (fieldsObject) => { - Object.keys(fieldsObject).forEach((key) => { - const field = fieldsObject[key]; - const { widget, encrypted, key: propertyKey } = field; + const processNestedField = (field, propertyKey) => { + const { widget, encrypted } = field; - if (!canUpdateDataSource(selectedDataSource?.id) && !canDeleteDataSource()) { - encryptedFieldsProps[propertyKey] = { - disabled: !!selectedDataSource?.id, - }; - } else if (!isDataSourceEditing) { - if (widget === 'password' || encrypted) { - encryptedFieldsProps[propertyKey] = { - disabled: true, - }; - } - } else { - if ((widget === 'password' || encrypted) && !(propertyKey in computedProps)) { + const isEncryptedField = + widget === 'password-v3' || + widget === 'password-v3-textarea' || + widget === 'password' || + encrypted || + encryptedProperties.includes(propertyKey); + + if (isEncryptedField) { + if (computedProps[propertyKey] !== undefined && computedProps[propertyKey].disabled === false) { + encryptedFieldsProps[propertyKey] = { disabled: false }; + } else if (!isDataSourceEditing) { + encryptedFieldsProps[propertyKey] = { disabled: true }; + } else if (!(propertyKey in computedProps)) { encryptedFieldsProps[propertyKey] = { disabled: !!selectedDataSource?.id, }; } } + }; - // To check for nested dropdown-component-flip - if (widget === 'dropdown-component-flip') { - const selectedOption = options?.[field.key]?.value; + Object.keys(fieldsObject).forEach((key) => { + const field = fieldsObject[key]; - if (field.commonFields) { - processFields(field.commonFields); + if (field.key) { + processNestedField(field, field.key); + } + + // Check for nested structures and recursively process them + if (typeof field === 'object') { + if (field.widget === 'dropdown-component-flip') { + const selectedOption = options?.[field.key]?.value; + + if (field.commonFields) { + Object.keys(field.commonFields).forEach((commonKey) => { + const commonField = field.commonFields[commonKey]; + processNestedField(commonField, commonField.key); + }); + } + + if (selectedOption && fieldsObject[selectedOption]) { + processFields(fieldsObject[selectedOption]); + } } - if (selectedOption && fieldsObject[selectedOption]) { - processFields(fieldsObject[selectedOption]); - } + // For other nested objects, recursively process them + Object.keys(field).forEach((subKey) => { + if (typeof field[subKey] === 'object' && field[subKey] !== null) { + if (field[subKey].widget || field[subKey].key) { + processNestedField(field[subKey], field[subKey].key); + } else { + processFields({ [subKey]: field[subKey] }); + } + } + }); } }); }; @@ -264,6 +287,11 @@ const DynamicFormV2 = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedDataSource?.id, options, isDataSourceEditing]); + React.useEffect(() => { + const requiredFields = processAllOfConditions(schema, options); + setConditionallyRequiredProperties(requiredFields); + }, [options, processAllOfConditions, schema, selectedDataSource.id]); + const getElement = (type) => { switch (type) { case 'password': @@ -295,6 +323,8 @@ const DynamicFormV2 = ({ const currentValue = options?.[key]?.value; const skipValidation = (!hasUserInteracted && !showValidationErrors) || (!interactedFields.has(key) && !showValidationErrors); + const workspaceConstant = options?.[key]?.workspace_constant; + const isEditing = computedProps[key] && computedProps[key].disabled === false; const handleOptionChange = (key, value, flag = true) => { if (!hasUserInteracted) { @@ -309,10 +339,10 @@ const DynamicFormV2 = ({ case 'text': case 'textarea': { return { - key, + propertyKey: key, widget, label, - placeholder: isEncrypted ? '**************' : description, + placeholder: workspaceConstant ? workspaceConstant : isEncrypted ? '**************' : description, className: cx('form-control', { 'dynamic-form-encrypted-field': isEncrypted, }), @@ -321,20 +351,20 @@ const DynamicFormV2 = ({ value: currentValue || '', onChange: (e) => optionchanged(key, e.target.value, true), isGDS: true, - workspaceVariables: [], - workspaceConstants: [], encrypted: isEncrypted, onBlur, + workspaceVariables, + workspaceConstants: currentOrgEnvironmentConstants, }; } case 'password-v3': case 'password-v3-textarea': case 'text-v3': { return { - key, + propertyKey: key, widget, label, - placeholder: isEncrypted ? '**************' : description, + placeholder: workspaceConstant ? workspaceConstant : isEncrypted ? '**************' : description, className: cx('form-control', { 'dynamic-form-encrypted-field': isEncrypted, }), @@ -343,8 +373,6 @@ const DynamicFormV2 = ({ value: currentValue || '', onChange: (e) => handleOptionChange(key, e.target.value, true), isGDS: true, - workspaceVariables: [], - workspaceConstants: [], encrypted: isEncrypted, onBlur, isRequired: isRequired, @@ -356,6 +384,10 @@ const DynamicFormV2 = ({ ? { valid: true, message: '' } : { valid: null, message: '' }, // handle optional && encrypted fields isDisabled: !canUpdateDataSource(selectedDataSource?.id) && !canDeleteDataSource(), + workspaceVariables, + workspaceConstants: currentOrgEnvironmentConstants, + isEditing: isEditing, + labelDisabled: false, }; } case 'react-component-headers': { @@ -411,11 +443,18 @@ const DynamicFormV2 = ({ if (!canUpdateDataSource(selectedDataSource?.id) && !canDeleteDataSource()) { return; } + const isEditing = computedProps[field]['disabled']; + const workspaceConstant = options?.[field]?.workspace_constant; + const isWorkspaceConstant = !!workspaceConstant; + if (isEditing) { - optionchanged(field, ''); + if (isWorkspaceConstant) { + optionchanged(field, workspaceConstant); + } else { + optionchanged(field, ''); + } } else { - //Send old field value if editing mode disabled for encrypted fields const newOptions = { ...options }; const oldFieldValue = selectedDataSource?.['options']?.[field]; if (oldFieldValue) { @@ -425,6 +464,7 @@ const DynamicFormV2 = ({ optionsChanged({ ...newOptions }); } } + setComputedProps({ ...computedProps, [field]: { @@ -511,6 +551,7 @@ const DynamicFormV2 = ({ dataCy={uiProperties[key].key.replace(/_/g, '-')} //to be removed after whole ui is same isHorizontalLayout={isHorizontalLayout} + handleEncryptedFieldsToggle={handleEncryptedFieldsToggle} />
diff --git a/frontend/src/_components/NotificationCenter/index.jsx b/frontend/src/_components/NotificationCenter/index.jsx index 4f031857f5..f1fc1cbf8f 100644 --- a/frontend/src/_components/NotificationCenter/index.jsx +++ b/frontend/src/_components/NotificationCenter/index.jsx @@ -38,7 +38,7 @@ export const NotificationCenter = ({ darkMode }) => { const overlay = (
{ + const [searchText, setSearchText] = useState(''); + const debouncedSearchTerm = useDebounce(searchText, debounceDelay); + const [isFocused, setFocussed] = useState(false); + + const handleChange = (e) => { + setSearchText(e.target.value); + callBack?.(e); + }; + + const clearSearchText = () => { + setSearchText(''); + onClearCallback?.(); + }; + + const handleClickOutside = (event) => { + if (ref?.current && !ref.current.contains(event.target) && clearTextOnBlur) { + clearSearchText(); + // Your function to be triggered + } + }; + + const mounted = useMounted(); + + useEffect(() => { + document.addEventListener('mousedown', handleClickOutside); + if (mounted) { + onSubmit?.(debouncedSearchTerm); + } + return () => { + // Cleanup event listener on component unmount + document.removeEventListener('mousedown', handleClickOutside); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [debouncedSearchTerm, onSubmit]); + + useEffect(() => { + initialValue !== undefined && setSearchText(initialValue); + }, [initialValue]); + + return ( +
+
+ {!isFocused && ( + + + + )} + setFocussed(true)} + onBlur={() => setFocussed(false)} + data-cy={`${dataCy}-search-bar`} + autoFocus={autoFocus} + ref={ref} + /> + {searchText.length >= 0 ? ( + +
+ +
+
+ ) : ( + '' + )} +
+
+ ); + } +); + +SearchBox.propTypes = { + onSubmit: PropTypes.func.isRequired, + debounceDelay: PropTypes.number, + width: PropTypes.string, +}; diff --git a/frontend/src/_components/SearchBox.jsx b/frontend/src/_components/SearchBox.jsx index f0c09054dc..58afc173de 100644 --- a/frontend/src/_components/SearchBox.jsx +++ b/frontend/src/_components/SearchBox.jsx @@ -4,6 +4,7 @@ import cx from 'classnames'; import useDebounce from '@/_hooks/useDebounce'; import { useMounted } from '@/_hooks/use-mount'; import SolidIcon from '@/_ui/Icon/SolidIcons'; +import './_styles/search-box.scss'; export const SearchBox = forwardRef( ( diff --git a/frontend/src/_components/_styles/page-search-box.scss b/frontend/src/_components/_styles/page-search-box.scss new file mode 100644 index 0000000000..f1be3e2be4 --- /dev/null +++ b/frontend/src/_components/_styles/page-search-box.scss @@ -0,0 +1,85 @@ +.ghost-search-box-wrapper { + .form-control.ghost-search { + background: none !important; + color: var(--slate12); + height: 48px; + border: none !important; + border-radius: 0 !important; + border-bottom: 1px solid var(--border-weak) !important; + transition: border-bottom 0.2s ease-in-out; + + &:hover { + background: none !important; + border-bottom: 1px solid var(--border-accent-weak) !important; + color: var(--slate12); + } + + &:focus { + background: none !important; + border: none !important; + border-bottom: 1px solid var(--border-accent-strong) !important; + } + + } + .input-icon { + .input-icon-addon { + padding-right: 6px; + display: flex; + } + } +} + + +/** + * Search Box + */ + .ghost-search-box-wrapper { + input { + width: 200px; + border-radius: 5px !important; + color: var(--slate12); + background-color: var(--base); + } + + .input-icon .form-control:not(:first-child), + .input-icon .form-select:not(:last-child) { + padding-left: 28px !important; + } + + input:focus { + width: 200px; + background-color: var(--base); + } + + .input-icon .input-icon-addon { + display: flex; + } + + .input-icon .input-icon-addon.end { + pointer-events: auto; + + .tj-common-search-input-clear-icon { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 4px; + width: 20px; + height: 20px; + background: var(--indigo3) !important; + border-radius: 4px; + } + + div { + border-radius: 12px; + color: #ffffff; + padding: 1px; + cursor: pointer; + + svg { + height: 14px; + width: 14px; + } + } + } +} diff --git a/frontend/src/_components/_styles/search-box.scss b/frontend/src/_components/_styles/search-box.scss new file mode 100644 index 0000000000..202f98b688 --- /dev/null +++ b/frontend/src/_components/_styles/search-box.scss @@ -0,0 +1,65 @@ +.search-box-wrapper { + input { + width: 200px; + border-radius: 5px !important; + color: var(--text-primary); + background-color: var(--surfaces-surface-01) !important; + border: 1px solid var(--border-weak) !important; + } + + .input-icon .form-control:not(:first-child), + .input-icon .form-select:not(:last-child) { + padding-left: 28px !important; + } + + input:focus { + width: 200px; + background-color: var(--surfaces-surface-02); + } + + .input-icon .input-icon-addon { + display: flex; + } + + .input-icon .input-icon-addon.end { + pointer-events: auto; + + .tj-common-search-input-clear-icon { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 4px; + width: 20px; + height: 20px; + background: var(--indigo3) !important; + border-radius: 4px; + } + + div { + border-radius: 12px; + color: #ffffff; + padding: 1px; + cursor: pointer; + + svg { + height: 14px; + width: 14px; + } + } + } +} + +.searchbox-wrapper { + margin-top: 0 !important; + + .search-icon { + margin: 0.30rem + } + + input { + border-radius: 8px !important; + padding-left: 1.75rem !important; + border-radius: 8px !important; + } +} \ No newline at end of file diff --git a/frontend/src/_services/ai.service.js b/frontend/src/_services/ai.service.js index 94f273d27d..98f7f10698 100644 --- a/frontend/src/_services/ai.service.js +++ b/frontend/src/_services/ai.service.js @@ -4,7 +4,6 @@ import { fetchEventSource } from '@microsoft/fetch-event-source'; export const aiService = { generateApp, - createComponent, createQuery, updateComponent, createEvent, @@ -60,14 +59,6 @@ function generateApp(prompt) { return fetch(`${config.apiUrl}/ai/generateApp`, requestOptions).then(handleResponse); } -function createComponent(prompt) { - const body = { - prompt, - }; - const requestOptions = { method: 'POST', headers: authHeader(), credentials: 'include', body: JSON.stringify(body) }; - return fetch(`${config.apiUrl}/agents/create-components`, requestOptions).then(handleResponse); -} - function createQuery(prompt) { const body = { prompt, diff --git a/frontend/src/_services/dataquery.service.js b/frontend/src/_services/dataquery.service.js index 3dc5e26593..c69b125237 100644 --- a/frontend/src/_services/dataquery.service.js +++ b/frontend/src/_services/dataquery.service.js @@ -11,6 +11,7 @@ export const dataqueryService = { changeQueryDataSource, updateStatus, bulkUpdateQueryOptions, + createWorkflowQuery, }; function getAll(appVersionId, mode) { @@ -36,6 +37,21 @@ function create(app_id, app_version_id, name, kind, options, data_source_id, plu ).then(handleResponse); } +function createWorkflowQuery(app_id, app_version_id, name, kind, options, data_source_id, plugin_id) { + const body = { + app_id, + app_version_id, + name, + kind, + options, + data_source_id, + plugin_id, + }; + + const requestOptions = { method: 'POST', headers: authHeader(), credentials: 'include', body: JSON.stringify(body) }; + return fetch(`${config.apiUrl}/data-queries/workflow-node`, requestOptions).then(handleResponse); +} + function update(id, versionId, name, options, dataSourceId) { const body = { options, diff --git a/frontend/src/_services/workflow_executions.service.js b/frontend/src/_services/workflow_executions.service.js index 8eac985252..8abcdfe569 100644 --- a/frontend/src/_services/workflow_executions.service.js +++ b/frontend/src/_services/workflow_executions.service.js @@ -10,11 +10,15 @@ export const workflowExecutionsService = { all, enableWebhook, previewQueryNode, + getPaginatedExecutions, + getPaginatedNodes, + trigger, + streamSSE, }; -function previewQueryNode(queryId, appVersionId, nodeId) { +function previewQueryNode(queryId, appVersionId, nodeId, state = {}) { const currentSession = authenticationService.currentSessionValue; - const body = { appVersionId, userId: currentSession.current_user?.id, queryId, nodeId }; + const body = { appVersionId, userId: currentSession.current_user?.id, queryId, nodeId, state }; const requestOptions = { method: 'POST', headers: authHeader(), body: JSON.stringify(body), credentials: 'include' }; return fetch(`${config.apiUrl}/workflow_executions/previewQueryNode`, requestOptions).then(handleResponse); } @@ -70,3 +74,40 @@ function enableWebhook(appId, value) { const requestOptions = { method: 'PATCH', headers: authHeader(), body: JSON.stringify(body), credentials: 'include' }; return fetch(`${config.apiUrl}/v2/webhooks/workflows/${appId}`, requestOptions).then(handleResponse); } + +function getPaginatedExecutions(appVersionId, page = 1, perPage = 10) { + const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' }; + return fetch( + `${config.apiUrl}/workflow_executions?appVersionId=${appVersionId}&page=${page}&per_page=${perPage}`, + requestOptions + ).then(handleResponse); +} + +function getPaginatedNodes(executionId, page = 1, perPage = 20) { + const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' }; + return fetch( + `${config.apiUrl}/workflow_executions/${executionId}/nodes?page=${page}&per_page=${perPage}`, + requestOptions + ).then(handleResponse); +} + +function trigger(workflowAppId, params, environmentId) { + const currentSession = authenticationService.currentSessionValue; + const body = { + appId: workflowAppId, + userId: currentSession.current_user?.id, + executeUsing: 'app', + params: Array.isArray(params) + ? Object.fromEntries(params.filter((param) => param.key !== '').map((param) => [param.key, param.value])) + : params || {}, + environmentId, + }; + const requestOptions = { method: 'POST', headers: authHeader(), body: JSON.stringify(body), credentials: 'include' }; + return fetch(`${config.apiUrl}/workflow_executions/${workflowAppId}/trigger`, requestOptions).then(handleResponse); +} + +function streamSSE(workflowExecutionId) { + return new EventSource(`${config.apiUrl}/workflow_executions/${workflowExecutionId}/stream`, { + withCredentials: true, + }); +} diff --git a/frontend/src/_stores/workflowStore.js b/frontend/src/_stores/workflowStore.js new file mode 100644 index 0000000000..89ba9d0c8a --- /dev/null +++ b/frontend/src/_stores/workflowStore.js @@ -0,0 +1,8 @@ +import create from 'zustand'; + +const useWorkflowStore = create((set) => ({ + workflowId: null, + setWorkflowId: (id) => set({ workflowId: id }), +})); + +export default useWorkflowStore; diff --git a/frontend/src/_styles/componentdesign.scss b/frontend/src/_styles/componentdesign.scss index b5879cd2f9..9175677b51 100644 --- a/frontend/src/_styles/componentdesign.scss +++ b/frontend/src/_styles/componentdesign.scss @@ -109,6 +109,17 @@ //upgrade --upgrade-default: #FFAF41; --upgrade-weak: #FFAF4140; + + // Shadows + --elevation-000-box-shadow: 0px 1px 0px 0px rgba(0, 0, 0, 0.10); + --elevation-200-box-shadow: 0px 2px 4px 0px rgba(48, 50, 51, 0.10), 0px 0px 1px 0px rgba(48, 50, 51, 0.05); + --elevation-300-box-shadow: 0px 4px 8px 0px rgba(48, 50, 51, 0.10), 0px 0px 1px 0px rgba(48, 50, 51, 0.05); + --elevation-400-box-shadow: 0px 8px 16px 0px rgba(48, 50, 51, 0.10), 0px 0px 1px 0px rgba(48, 50, 51, 0.05); + --elevation-500-box-shadow: 0px 16px 24px 0px rgba(48, 50, 51, 0.09), 0px 0px 1px 0px rgba(48, 50, 51, 0.05); + --elevation-600-box-shadow: 0px 24px 40px 0px rgba(48, 50, 51, 0.08), 0px 0px 1px 0px rgba(48, 50, 51, 0.05); + --elevation-700-box-shadow: 0px 32px 50px 0px rgba(48, 50, 51, 0.08), 0px 0px 1px 0px rgba(48, 50, 51, 0.05); + --elevation-100-box-shadow: 0px 1px 1px 0px rgba(48, 50, 51, 0.10), 0px 0px 1px 0px rgba(48, 50, 51, 0.05); + } .dark-theme { @@ -222,4 +233,15 @@ //upgrade --upgrade-default: #FFAF41; --upgrade-weak: #FFAF4140; + + //box-shadow + --elevation-000-box-shadow: 0px 1px 0px 0px rgba(0, 0, 0, 0.40); + --elevation-100-box-shadow: 0px 1px 1px 0px #000, 0px 0px 1px 0px rgba(0, 0, 0, 0.90); + --elevation-200-box-shadow: 0px 2px 4px 0px #000, 0px 0px 1px 0px rgba(0, 0, 0, 0.90); + --elevation-300-box-shadow: 0px 4px 8px 0px #000, 0px 0px 1px 0px rgba(0, 0, 0, 0.90); + --elevation-400-box-shadow: 0px 8px 16px 0px #000, 0px 0px 1px 0px rgba(0, 0, 0, 0.90); + --elevation-500-box-shadow: 0px 16px 24px 0px rgba(0, 0, 0, 0.99), 0px 0px 1px 0px rgba(0, 0, 0, 0.90); + --elevation-600-box-shadow: 0px 24px 40px 0px rgba(0, 0, 0, 0.98), 0px 0px 1px 0px rgba(0, 0, 0, 0.90); + --elevation-700-box-shadow: 0px 32px 50px 0px rgba(0, 0, 0, 0.98), 0px 0px 1px 0px rgba(0, 0, 0, 0.90); + } \ No newline at end of file diff --git a/frontend/src/_styles/designtheme.scss b/frontend/src/_styles/designtheme.scss index bfdb6cee41..091fe42737 100644 --- a/frontend/src/_styles/designtheme.scss +++ b/frontend/src/_styles/designtheme.scss @@ -120,7 +120,7 @@ --interactive-overlays-column-resize: #1B1F244D; //interactive - --interactive-default: #CCD1D54D; + --interactive-default: #88909914; --interactive-hover: #ACB2B959; @@ -211,7 +211,7 @@ --interactive-overlays-column-resize: #FFFFFF80; //interactive - --interactive-default: #A1A7AE1F; + --interactive-default: #858C940D; --interactive-hover: #A1A7AE29; diff --git a/frontend/src/_styles/drawer.scss b/frontend/src/_styles/drawer.scss index 4b2f9b89d5..e9945e3572 100644 --- a/frontend/src/_styles/drawer.scss +++ b/frontend/src/_styles/drawer.scss @@ -3,15 +3,14 @@ } .drawer { - background: var(--base); + background: var(--surfaces-surface-01); width: 540px; height: 100%; position: fixed; - border: 1px solid var(--slate5); - box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); + border: 1px solid var(--border-weak); + box-shadow: var(--elevation-400-box-shadow); transition: transform var(--transition-speed) ease; z-index: 1000; - background: var(--base); overflow-y: auto; &.left { diff --git a/frontend/src/_styles/dropdown-custom.scss b/frontend/src/_styles/dropdown-custom.scss index 7ca2a022c0..5c3f7136c8 100644 --- a/frontend/src/_styles/dropdown-custom.scss +++ b/frontend/src/_styles/dropdown-custom.scss @@ -1,80 +1,94 @@ // for selects and dropdowns across app dashboard .react-select__control { - background-color: var(--base) !important; - border: 1px solid var(--slate7) !important; + background-color: var(--surfaces-surface-01) !important; + border: 1px solid var(--border-weak) !important; - &:active { - border: 1px solid var(--indigo9); - } + &:active { + border: 1px solid var(--indigo9); + } } .react-select__menu-portal { - z-index: 100 !important; + z-index: 100 !important; - .react-select__option { - color: var(--slate12); - z-index: 100; - - } + .react-select__option { + color: var(--text-default); + height: 32px; + z-index: 100; + padding: 4px 8px; + } } .react-select__single-value { - color: var(--slate12) ; + color: var(--text-default); } .react-select__menu { - background-color: var(--base) !important; - border: 1px solid var(--slate3) !important; - box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03) !important; - margin: 0px !important; - z-index: 100; + background-color: var(--surfaces-surface-01) !important; + border: 1px solid var(--border-weak) !important; + box-shadow: var(--elevation-00-box-shadow) !important; + margin: 0px !important; + z-index: 100; - .react-select__menu-list { - background-color: var(--base) !important; - overflow-y: auto; + .react-select__menu-list { + background-color: var(--surfaces-surface-01) !important; + padding: 4px; + overflow-y: auto; - .react-select__option { - background-color: var(--base) !important; + .react-select__option { + background-color: var(--surfaces-surface-01) !important; + border-radius: 6px; - &:hover { - background-color: var(--slate3) !important; - } + + > div { + color: var(--text-default) !important; + background-color: transparent !important; + } + + &:hover { + background-color: var(--interactive-hover) !important; + > div { + background-color: transparent !important; } + } } + } } .org-select-container { - height: 52px; - display: flex; - align-items: center; - justify-content: center; - border-top: 1px solid var(--slate5); - margin-bottom: var(--dynamic-margin, 0px); //please Remove after Basicplan banner is removed.. + height: 52px; + display: flex; + align-items: center; + justify-content: center; + border-top: 1px solid var(--border-weak); + margin-bottom: var( + --dynamic-margin, + 0px + ); //please Remove after Basicplan banner is removed.. } .tj-org-select { - .react-select__control { - width: 262px; - height: 32px; - border: none !important; - background-color: var(--page-default) !important; + .react-select__control { + width: 262px; + height: 32px; + border: none !important; + background-color: var(--surfaces-surface-01) !important; - &:hover { - background: var(--slate2) !important; - } - - &:active { - background: var(--slate3) !important; - } + &:hover { + background: var(--slate2) !important; } - .tj-text-xsm { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - width: 200px; + &:active { + background: var(--slate3) !important; } + } + .tj-text-xsm { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 200px; + } } .users-filter-dropdown, @@ -85,59 +99,58 @@ .select-order-field, .select-column-field, .records-dropdown-field { - .react-select__control { - border: 1px solid var(--slate7) !important; - } + .react-select__control { + border: 1px solid var(--border-default) !important; + } } .css-1ms6gku-MenuPortal, .css-169zxdi-MenuList { - .react-select__option { - border-radius: 6px; - } + .react-select__option { + border-radius: 6px; + } } .css-nw08ma-menu { - box-shadow: none !important; + box-shadow: none !important; } .react-select__menu-portal { - z-index: 9999 !important; + z-index: 9999 !important; } // following is the styles for table select column type menu list and options styles. If its same for all the select elements in the editor, then we can make it common and not specific for table select -.table-select-custom-menu-list{ - .react-select__menu-list{ - padding: 2px; - // this is needed otherwise :active state doesn't look nice, gap is required - display: flex; - flex-direction: column; - gap: 4px !important; - background-color: var(--base) !important; - overflow-y: auto; +.table-select-custom-menu-list { + .react-select__menu-list { + padding: 2px; + // this is needed otherwise :active state doesn't look nice, gap is required + display: flex; + flex-direction: column; + gap: 4px !important; + background-color: var(--surfaces-surface-01) !important; + overflow-y: auto; + } + .react-select__option { + display: flex; + justify-content: space-between; + padding: 8px 12px; + align-self: stretch; + align-items: center; + color: var(--slate12) !important; + border-radius: 6px; + /* Paragraph/Extrasmall/Regular */ + font-family: "IBM Plex Sans"; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 20px; /* 166.667% */ + &.react-select__option--is-selected { + color: var(--indigo9) !important; } - .react-select__option{ - display: flex; - justify-content: space-between; - padding: 8px 12px; - align-self: stretch; - align-items: center; - color: var(--slate12) !important; - border-radius: 6px; - /* Paragraph/Extrasmall/Regular */ - font-family: 'IBM Plex Sans'; - font-size: 12px; - font-style: normal; - font-weight: 400; - line-height: 20px; /* 166.667% */ - &.react-select__option--is-selected{ - color: var(--indigo9) !important; - } - &:active{ - background: var(--base) !important; - box-shadow: 0px 0px 0px 4px var(--slate6); - color : var(--slate12) !important; - } + &:active { + background: var(--surfaces-surface-01) !important; + box-shadow: 0px 0px 0px 4px var(--slate6); + color: var(--slate12) !important; } + } } - diff --git a/frontend/src/_styles/editor/react-select-search.scss b/frontend/src/_styles/editor/react-select-search.scss index 707c59e8a4..3660056f68 100644 --- a/frontend/src/_styles/editor/react-select-search.scss +++ b/frontend/src/_styles/editor/react-select-search.scss @@ -20,10 +20,10 @@ } .select-search-container { - --select-search-background: var(--base); - --select-search-border: var(--slate7); + --select-search-background: var(--surfaces-surface-01); + --select-search-border: var(--border-weak); --select-search-selected: #dadcde; - --select-search-text: var(--slate12); + --select-search-text: var(--text-default); --select-search-subtle-text: #6c6f85; --select-search-inverted-text: var(--select-search-background); --select-search-highlight: var(--indigo3); diff --git a/frontend/src/_styles/global-datasources.scss b/frontend/src/_styles/global-datasources.scss index d365b1ac0c..97d9108c44 100644 --- a/frontend/src/_styles/global-datasources.scss +++ b/frontend/src/_styles/global-datasources.scss @@ -2,12 +2,13 @@ @import "./designtheme.scss"; .global-datasources-sidebar { - height: calc(100vh - 64px); + height: calc(100vh - 48px); max-width: 288px; - background: var(--page-default); + background: var(--page-weak); display: grid; grid-template-rows: auto 1fr auto; - border-right: 1px solid var(--slate5); + border-right: 1px solid var(--border-weak); + gap: 30px; .add-datasource-btn { height: 40px; @@ -28,7 +29,7 @@ padding: 6px 15px; width: 248px; height: 32px; - margin-bottom: 10px; + margin-bottom: 8px; &:focus-visible { box-shadow: 0px 0px 0px 4px #dfe3e6; @@ -69,7 +70,8 @@ } .datasources-list-item { - background-color: var(--indigo3); + background-color: var(--interactive-default); + color: var(--text-default); } } @@ -109,7 +111,7 @@ .datasource-modal-container { position: relative; - background: var(--page-default); + background: var(--page-weak); .modal-header { background-color: var(--slate3) !important; @@ -118,12 +120,12 @@ .modal { position: absolute; z-index: 1050; - background: var(--slate2); + background: var(--page-weak); } .modal-content { - border: 1px solid var(--slate5); - background-color: var(--base) !important; + border: 1px solid var(--border-weak); + background-color: var(--page-weak) !important; .input-icon { &:hover { @@ -165,6 +167,12 @@ display: flex; justify-content: center; align-items: center; + + svg { + top: 1px; + left: 1px; + position: relative; + } } } @@ -184,26 +192,24 @@ .datasource-list-container { overflow-y: auto; padding-left: 20px; - max-height: calc(100vh - 64px); - border-left: 1px solid var(--slate5); + max-height: calc(100vh - 48px); + + .datasource-list { width: 976px; margin: 0 auto; - max-height: calc(100vh - 70px); + padding-bottom: 48px; .datasource-search-holder { width: 100%; - margin-top: 22px; + margin-top: 24px; + margin-bottom: 24px; + } - .liner { - margin-top: 5px; - width: 100% !important; - } - - input { - background: none !important; - border: none !important; - } + .ghost-search-box-wrapper .form-control.ghost-search { + padding-top: 16px; + padding-bottom: 16px; + height: 64px; } } diff --git a/frontend/src/_styles/instancelogout.scss b/frontend/src/_styles/instancelogout.scss index 5205472426..2e83aa64de 100644 --- a/frontend/src/_styles/instancelogout.scss +++ b/frontend/src/_styles/instancelogout.scss @@ -1,16 +1,14 @@ .instance-logout-wrapper{ - background: var(--base); + background: var(--page-weak); .instance-logout-header{ padding: 24px 24px; gap: 12px; height: 72px; border-top-left-radius: 6px; border-top-right-radius: 6px; - border-bottom: 1px solid rgb(230, 232, 235); /* Light gray border */ + border-bottom: 1px solid var(--border-weak); padding-bottom: 1rem; - &.dark-mode { - border-bottom: 1px solid rgb(43, 47, 49) !important; - } + .instance-logout-title{ font-size: 18px; line-height: 28px; diff --git a/frontend/src/_styles/left-sidebar.scss b/frontend/src/_styles/left-sidebar.scss index 2f5dde474b..3149e86a48 100644 --- a/frontend/src/_styles/left-sidebar.scss +++ b/frontend/src/_styles/left-sidebar.scss @@ -1,7 +1,7 @@ @import "./colors.scss"; @import "./designtheme.scss"; .left-sidebar { - background: var(--page-default) !important; + background: var(--page-weak) !important; display: flex; gap: 16px; @@ -785,7 +785,7 @@ align-items: center; padding-top: 0px; width: 48px; - border-right: 1px solid var(--slate5); + border-right: 1px solid var(--border-weak); } .tj-leftsidebar-icon-wrap { diff --git a/frontend/src/_styles/license.scss b/frontend/src/_styles/license.scss index e058dab5e7..62e4011730 100644 --- a/frontend/src/_styles/license.scss +++ b/frontend/src/_styles/license.scss @@ -6,16 +6,17 @@ width: 880px; margin: auto; border-radius: 6px; + border: 1px solid var(--border-weak); .body-wrapper { - border: 1px solid var(--slate5); + height: 100%; min-height: 620px; } .license-page-sidebar { max-width: 220px; - background-color: var(--base); - border-right: 1px solid var(--slate5) !important; + background-color: var(--surfaces-surface-01); + border-right: 1px solid var(--border-weak) !important; display: grid !important; grid-template-rows: auto 1fr auto !important; @@ -29,7 +30,7 @@ } .license-content-wrapper { - background-color: var(--base); + background-color: var(--surfaces-surface-01); .groups-sub-header-wrap { width: 100%; @@ -253,11 +254,10 @@ .license-header-wrap { display: flex; justify-content: space-between; - padding-right: 40px; - padding-left: 20px; + padding: 24px 40px 16px; align-items: center; height: unset !important; - background-color: var(--base); + background-color: var(--surfaces-surface-01); .status-container { border-radius: 20px; @@ -599,9 +599,9 @@ align-items: center; align-self: stretch; border-radius: 8px; - background-color: #FFFFFF; - border: 1px solid var(--upgrade-weak, #FFAF4140); - box-shadow: 0px 0px 1px 0px var(--dropshadow-100700-layer-1, rgba(48, 50, 51, 0.05)), 0px 1px 1px 0px var(--dropshadow-100400-layer-2, rgba(48, 50, 51, 0.10)); + background-color: var(--surfaces-surface-01); + border: 1px solid var(--border-weak, #FFAF4140); + box-shadow: var(--elevation-000-box-shadow); .license-loader { justify-content: center; @@ -796,7 +796,7 @@ } .license-error-modal { - background-color: var(--base); + background-color: var(--surfaces-surface-01); .modal-header { background-color: var(--slate3) !important; @@ -859,7 +859,7 @@ width: 100%; height: 88px; border-top: 1px solid var(--slate5) !important; - background: var(--base); + background: var(--surfaces-surface-01); margin-top: 0px !important; } diff --git a/frontend/src/_styles/modules.scss b/frontend/src/_styles/modules.scss index 239cc3201c..b86e7043dd 100644 --- a/frontend/src/_styles/modules.scss +++ b/frontend/src/_styles/modules.scss @@ -1,6 +1,7 @@ -.apps-modules-tabs { - .nav-link { - background-color: var(--page-default); +.apps-modules-tabs.nav-tabs { + .nav-link, + ul > li.nav-link.active { + background-color: var(--page-weak); } .nav-link.active { diff --git a/frontend/src/_styles/rocket/card.scss b/frontend/src/_styles/rocket/card.scss new file mode 100644 index 0000000000..6dc527eb5d --- /dev/null +++ b/frontend/src/_styles/rocket/card.scss @@ -0,0 +1,13 @@ +// Card +.card { + border: 0 !important; + outline: 1px solid var(--border-weak); + box-shadow: var(--elevation-100-box-shadow); + border-radius: 8px; + background-color: var(--background-surface-layer-01) !important; + + + &.card--clickable:hover { + box-shadow: var(--elevation-200-box-shadow); + } +} \ No newline at end of file diff --git a/frontend/src/_styles/tabler.scss b/frontend/src/_styles/tabler.scss index ad724a1833..f21427d807 100644 --- a/frontend/src/_styles/tabler.scss +++ b/frontend/src/_styles/tabler.scss @@ -18972,7 +18972,7 @@ img { @media not print { .theme-dark { color: #f4f6fa; - background-color: #1f2936 + background-color: #1E2226; } .theme-dark .card, diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index d3b556bba8..3f614e476c 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -20,6 +20,8 @@ @import "./componentdesign.scss"; @import './pages-sidebar.scss'; @import './modules.scss'; +@import "./rocket/card.scss"; +@import "../HomePage/styles/homepage.scss"; /* ibm-plex-sans-100 - latin */ @font-face { @@ -214,6 +216,7 @@ &::-webkit-scrollbar-thumb { background: transparent; } + &::-webkit-scrollbar-track { background: transparent; } @@ -225,6 +228,7 @@ &::-webkit-scrollbar-thumb { background: var(--slate8); } + &::-webkit-scrollbar-track { background: transparent; } @@ -943,70 +947,6 @@ button { } } -.home-search-holder { - height: 20px; - width: 100%; - margin-top: 32px; - - .search-box-wrapper { - .input-icon { - .input-icon-addon { - padding-right: 6px; - } - } - } - - .homepage-search { - background: none !important; - color: var(--slate12); - height: 20px; - border: none !important; - - &:focus { - background: none !important; - border: none !important; - } - - &:hover { - background: none !important; - border: none !important; - color: var(--slate12); - } - } -} - -.homepage-app-card-list-item-wrap { - row-gap: 16px; - column-gap: 32px; - display: flex; - margin-top: 22px; -} - -.homepage-app-card-list-item { - max-width: 272px; - flex-basis: 33%; - padding: 0 !important; -} - -.homepage-dropdown-style { - min-width: 11rem; - display: block; - align-items: center; - margin: 0; - line-height: 1.4285714; - width: 100%; - padding: 0.5rem 0.75rem !important; - font-weight: 400; - white-space: nowrap; - border: 0; - cursor: pointer; - font-size: 12px; -} - -.homepage-dropdown-style:hover { - background: rgba(101, 109, 119, 0.06); -} - .card-skeleton-container { border: 0.5px solid #b4bbc6; padding: 1rem; @@ -1250,7 +1190,7 @@ button { font-weight: 500; .modal-header { - background-color: var(--base) !important; + background-color: var(--surfaces-surface-01) !important; border-bottom: 1px solid var(--slate5); } @@ -1267,7 +1207,7 @@ button { .modal-body { height: 80%; padding: 0 10px; - background-color: var(--base) !important; + background-color: var(--surfaces-surface-01) !important; .container-fluid { @@ -1401,7 +1341,7 @@ button { .modal-body { height: 80%; padding: 0 10px; - background-color: var(--base) !important; + background-color: var(--surfaces-surface-01) !important; .container-fluid { height: 100%; @@ -1417,7 +1357,7 @@ button { .modal-header, .modal-content { color: white; - background-color: #2b394a; + background-color: var(--surfaces-surface-01); } .template-categories { @@ -2316,10 +2256,10 @@ tr:focus { #popover-app-menu { border-radius: 4px; width: 150px; - box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); - background: var(--base); - color: var(--slate12); - border: 1px solid var(--slate3); + box-shadow: var(--elevation-200-box-shadow); + background: var(--surfaces-surface-01); + color: var(--text-default); + border: 1px solid var(--border-default); .popover-arrow { display: none; @@ -3422,7 +3362,7 @@ input:focus-visible { .modal-content, .modal-header { - background-color: #1f2936; + background-color: var(--surfaces-surface-01); .text-muted { color: var(--slate9) !important; @@ -3756,7 +3696,7 @@ input:focus-visible { position: relative; min-height: 100%; min-width: 100%; - background-color: #2b394b; + background-color: var(--page-weak); } .jet-table { @@ -3794,6 +3734,7 @@ input:focus-visible { .nav-tabs { font-weight: 300; + border-bottom: 1px solid var(--border-weak); } .nav-tabs .nav-link.active { @@ -3814,7 +3755,9 @@ input:focus-visible { } input[type="text"] { - outline-color: #dadcde !important; + outline-color: var(--border-default) !important; + border-color: var(--border-default) !important; + } .widget-header { @@ -4752,74 +4695,6 @@ input[type="text"] { } } -/** - * Search Box - */ -.search-box-wrapper { - input { - width: 200px; - border-radius: 5px !important; - color: var(--slate12); - background-color: var(--base); - } - - .input-icon .form-control:not(:first-child), - .input-icon .form-select:not(:last-child) { - padding-left: 28px !important; - } - - input:focus { - width: 200px; - background-color: var(--base); - } - - .input-icon .input-icon-addon { - display: flex; - } - - .input-icon .input-icon-addon.end { - pointer-events: auto; - - .tj-common-search-input-clear-icon { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - padding: 4px; - width: 20px; - height: 20px; - background: var(--indigo3) !important; - border-radius: 4px; - } - - div { - border-radius: 12px; - color: #ffffff; - padding: 1px; - cursor: pointer; - - svg { - height: 14px; - width: 14px; - } - } - } -} - -.searchbox-wrapper { - margin-top: 0 !important; - - .search-icon { - margin: 0.30rem - } - - input { - border-radius: $border-radius !important; - padding-left: 1.75rem !important; - border-radius: $border-radius !important; - } -} - .fixedHeader { table thead { position: -webkit-sticky; // this is for all Safari (Desktop & iOS), not for Chrome @@ -4834,7 +4709,7 @@ input[type="text"] { * Folder List */ .folder-list { - overflow-y: scroll; + overflow-y: auto; scrollbar-width: thin; scrollbar-color: #888 transparent; @@ -4932,7 +4807,7 @@ input[type="text"] { .modal-content.home-modal-component { border-radius: 8px; overflow: hidden; - background-color: var(--base); + background-color: var(--surfaces-surface-01); color: var(--slate12); box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); @@ -4943,7 +4818,7 @@ input[type="text"] { .modal-header, .modal-body { padding: 16px 28px; - background: var(--base); + background: var(--surfaces-surface-01); } .modal-title { @@ -4953,7 +4828,7 @@ input[type="text"] { input:not([type=checkbox]) { border-radius: 5px !important; - background: var(--base); + background: var(--surfaces-surface-01); color: var(--slate12); } @@ -4973,13 +4848,13 @@ input[type="text"] { .modal-header, .modal-body { - background-color: #232e3c; + background-color: var(--surfaces-surface-01); color: #fff; } .form-control { color: #fff; - background-color: #232e3c !important; + background-color: var(--surfaces-surface-01) !important; } .btn-close { @@ -5024,7 +4899,7 @@ input[type="text"] { } .modal-header { - background-color: $bg-dark-light !important; + background-color: var(--surfaces-surface-01) !important; color: $white !important; border-bottom: 2px solid #3A3F42 !important; } @@ -5039,7 +4914,7 @@ input[type="text"] { } input { - background-color: $bg-dark-light !important; + background-color: var(--surfaces-surface-01) !important; } .form-select { @@ -5700,7 +5575,7 @@ div#driver-page-overlay { } .sso-card-wrapper { - background: var(--base); + background: var(--surfaces-surface-01); min-height: 100%; // height: calc(100vh - 156px) !important; @@ -5732,18 +5607,18 @@ div#driver-page-overlay { background: var(--base); margin-top: 0px !important; } +} +.form-footer.sso-card-footer { + border-top: 0 !important; } .workspace-settings-page { width: 880px; margin: 0 auto; - background: var(--base); + background: var(--surfaces-surface-01); .card { - background: var(--base); - border: 1px solid var(--slate7) !important; - box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05) !important; width: 880px; .card-header { @@ -5775,13 +5650,18 @@ div#driver-page-overlay { align-items: center; padding: 24px 32px; gap: 8px; - border-top: 1px solid var(--slate5) !important; - background: var(--base); + border-top: 1px solid var(--border-weak) !important; + background: var(--surfaces-surface-01); margin-top: 0px !important; align-Self: 'stretch'; height: 88px; } + .card-footer { + border-top: 1px solid var(--border-weak) !important; + background: var(--surfaces-surface-01); + } + .card-body { height: 467px; padding: 24px; @@ -6309,7 +6189,7 @@ div#driver-page-overlay { .org-constant-page { .card-footer { - background: var(--base); + background: var(--page-weak); color: var(--slate12); } } @@ -6682,13 +6562,13 @@ a.step-item-disabled { .card { min-width: 400px; - background: var(--base); - color: var(--slate12); - box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); + background: var(--surfaces-surface-01); + color: var(--text-default); + box-shadow: var(--elevation-400-box-shadow); } .card-footer { - background: var(--base); + background: var(--surfaces-surface-01); color: var(--slate12); } @@ -7671,11 +7551,11 @@ tbody { height: 36px; &:not(.table-row-selected):hover { - background: var(--Slate-02, #F8F9FA); + background: var(--slate2); td:nth-child(1), td:nth-child(2) { - background: var(--Slate-02, #F8F9FA); + background: var(--slate2); } .tjdb-checkbox-cell { @@ -7959,16 +7839,6 @@ tbody { color: var(--tblr-breadcrumb-item-active-color); } -.app-icon-main { - background: var(--indigo3) !important; - border-radius: 6px !important; - display: flex; - justify-content: center; - align-items: center; - width: 48px; - height: 48px; -} - .settings-nav-item, .audit-log-nav-item, .notification-center-nav-item { @@ -7994,15 +7864,8 @@ tbody { padding-left: 20px; } -.workspace-content-wrapper, -.database-page-content-wrap { - background-color: var(--page-default); - height: calc(100vh - 64px) !important; -} - -.instance-settings-wrapper {} - -.database-page-content-wrap { +.workspace-content-wrapper { + background-color: var(--page-weak); height: calc(100vh - 64px) !important; } @@ -8020,12 +7883,12 @@ tbody { } .organization-page-sidebar { - height: calc(100vh - 64px); + height: calc(100vh - 48px); max-width: 288px; - background-color: var(--page-default); - border-right: 1px solid var(--slate5) !important; - display: grid !important; - grid-template-rows: auto 1fr auto !important; + background-color: var(--page-weak); + border-right: 1px solid var(--border-weak) !important; + display: flex !important; + flex-direction: column !important; position: relative; .trial-banner { @@ -8041,10 +7904,10 @@ tbody { } .marketplace-page-sidebar { - height: calc(100vh - 64px); + height: calc(100vh - 48px); max-width: 272px; - background-color: var(--page-default); - border-right: 1px solid var(--slate5) !important; + background-color: var(--page-weak); + border-right: 1px solid var(--border-weak) !important; display: grid !important; grid-template-rows: auto 1fr auto !important; position: fixed; @@ -8052,8 +7915,8 @@ tbody { .home-page-sidebar { max-width: 288px; - background-color: var(--page-default); - border-right: 1px solid var(--slate5); + background-color: var(--page-weak); + border-right: 1px solid var(--border-weak); display: grid; grid-template-rows: auto 1fr auto; @@ -8066,38 +7929,6 @@ tbody { margin-top: 14px; } -.create-new-table-btn { - width: 248px; - - button { - height: 40px !important; - - } -} - -.tooljet-database-sidebar { - max-width: 288px; - background: var(--page-default); - border-right: 1px solid var(--slate5); - height: calc(100vh - 64px) !important; - - - .sidebar-container { - height: 40px !important; - padding-top: 4px !important; - margin: 0 auto; - display: flex; - justify-content: center; - } - - .sidebar-container-with-banner { - height: 40px !important; - padding-top: 1px !important; - margin: 0 auto; - display: flex; - justify-content: center; - } -} .create-new-app-dropdown { width: 248px !important; @@ -8492,11 +8323,11 @@ tbody { right: 0; left: 48px; z-index: 1; - background: var(--base); - height: 64px; + background: var(--page-weak); + height: 48px; @media only screen and (max-width: 767px) { - border-bottom: 1px solid var(--slate5); + border-bottom: 1px solid var(--border-weak); .row { display: flex; @@ -8765,24 +8596,33 @@ tbody { .tj-dashboard-section-header { - background-color: var(--page-default); + background-color: var(--page-weak); max-width: 288px; + height: 48px; max-height: 64px; - padding-top: 20px; + padding-top: 8px; padding-left: 20px; - padding-bottom: 24px; - border-right: 1px solid var(--slate5); + padding-bottom: 8px; + border-right: 1px solid var(--border-weak); + display: flex; + align-items: center; + // justify-content: center; &[data-name="Audit logs"], &[data-name="Workspace constants"], &[data-name="Profile settings"] { border-right: none; - border-bottom: 1px solid var(--slate5); + border-bottom: 1px solid var(--border-weak); .paid-feature-banner { margin-left: 15px; } } + + &[data-name="Audit logs"] { + flex: 1 0 0%; + max-width: unset; + } } .layout-sidebar-icon { @@ -8902,28 +8742,6 @@ tbody { bottom: 0px; } -.home-page-footer { - height: 52px; - background-color: var(--page-default) !important; - border-top: 1px solid var(--slate5) !important; - width: calc(100% - 336px) !important; - - @media only screen and (max-width: 768px) { - position: unset; - width: 100%; - - .col-4, - .col-5 { - display: none; - } - - .pagination-container { - display: flex !important; - align-items: center; - justify-content: center; - } - } -} .pagination-container { display: flex; @@ -9095,17 +8913,9 @@ tbody { padding-top: 4px; } -.home-page-sidebar { - height: calc(100vh - 64px) !important; //64 is navbar height - - .folder-list-user { - height: calc(100vh - 116px) !important; //64 is navbar height + 52 px footer - } -} - .home-page-content { - background-color: var(--page-default); - height: calc(100vh - 64px) !important; + background-color: var(--page-weak); + height: calc(100vh - 48px) !important; overflow-y: auto; position: relative; @@ -9155,100 +8965,6 @@ tbody { // DASHBOARD STYLES END -// TABLE -.table-left-sidebar { - height: calc(100vh - 104px) !important; // 62px [navbar] + 40px [ add table and search ] + extra 2 px(border) - overflow-y: auto; -} - -.toojet-db-table-footer { - height: 52px; - background: var(--page-default) !important; - width: calc(100vw - 336px); -} - -.toojet-db-table-footer-collapse { - height: 52px; - background: var(--page-default) !important; - width: calc(100vw - 48px); -} - -.toojet-db-table-footer-collapse { - height: 52px; - background: var(--page-default) !important; - width: calc(100vw - 48px); -} - -.home-app-card-header { - margin-bottom: 32px; -} - -.homepage-app-card { - height: 166px; - outline: 1px solid var(--slate3); - box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); - border-radius: 6px; - padding: 16px; - background-color: var(--base) !important; - - .appcard-buttons-wrap { - display: none; - } - - .home-app-card-header { - .menu-ico { - visibility: hidden !important; - } - } - - &:hover { - box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); - - .home-app-card-header { - margin-bottom: 12px; - - .menu-ico { - visibility: visible !important; - } - } - - .app-creation-time-container { - margin-bottom: 0px; - } - - .app-card-name { - margin-bottom: 0px; - } - - .app-creation-time { - display: none; - } - - - .appcard-buttons-wrap { - display: flex; - padding: 0px; - gap: 12px; - width: 240px; - height: 28px; - flex-direction: row; - - div { - a { - text-decoration: none; - } - } - - } - - .app-icon-main { - width: 36px; - height: 36px; - - } - } -} - .app-creation-time-container { height: 16px; } @@ -9739,7 +9455,7 @@ tbody { color: var(--slate11); .with-border { - border-bottom: 1px solid var(--slate5) !important; + border-bottom: 1px solid var(--border-weak) !important; } a { @@ -9789,19 +9505,6 @@ tbody { color: var(—-slate12) !important; } -.tj-dashboard-header-wrap { - background-color: var(--page-default); - padding-top: 22px; - padding-bottom: 22px; - padding-left: 40px; - height: 64px; - border-bottom: 1px solid var(--slate5); - - @media only screen and (max-width: 768px) { - border-bottom: none; - } -} - .dashboard-breadcrumb-header-name:hover { text-decoration: none !important; } @@ -9858,7 +9561,6 @@ tbody { .users-table { background: var(--base); padding: 16px; - width: 848px; margin: 0 auto; padding: 16px; @@ -9880,7 +9582,7 @@ tbody { } &[data-name="name-header"] { - max-width: 220px !important; + max-width: 210px !important; } &[data-name="meta-header"] { @@ -9904,16 +9606,15 @@ tbody { thead { tr { - padding: 6px 0px 0px 6px; + padding: 6px 24px 0px 24px; gap: 8px; - width: 848px; height: 40px; display: flex; align-items: center; } tr>th { - background: var(--base) !important; + background: var(--page-weak) !important; border-bottom: none !important; padding: 0 !important; width: 230px; @@ -9937,10 +9638,10 @@ tbody { } tr { - background: var(--base); + background: var(--page-weak); height: 66px; - padding: 13px 6px; - border-bottom: 1px solid var(--slate7); + padding: 13px 24px; + border-bottom: 1px solid var(--border-weak); display: flex; justify-content: space-between; gap: 8px; @@ -10011,10 +9712,6 @@ tbody { max-width: 880px; margin: 0 auto; - .tj-user-table-wrapper { - padding-right: 4px; - } - &:hover { .tj-user-table-wrapper { padding-right: 0px; @@ -10043,7 +9740,7 @@ tbody { border: 1px solid var(--slate5); border-radius: 6px; margin: 10px auto; - background-color: #FFFFFF; + background-color: var(--page-weak); display: flex; flex-direction: column; @@ -10078,7 +9775,7 @@ tbody { .worskspace-sub-header-wrap-nav-ws { width: 100%; height: 64px; - border-bottom: 1px solid var(--slate5); + border-bottom: 1px solid var(--surfaces-surface-01); display: flex; .nav-link.active { @@ -10175,30 +9872,29 @@ tbody { } .manage-workspace-table-wrap.dark-mode { - border: 1px solid var(--slate7) !important; + border: 1px solid var(--border-weak) !important; border-radius: 6px !important; - ; .worskspace-sub-header-wrap-nav-ws { - background-color: var(--slate3) !important; - border-bottom: 1px solid var(--slate7) !important; + background-color: var(--surfaces-surface-01) !important; + border-bottom: 1px solid var(--border-weak) !important; } .tab-content-ws { - background-color: var(--base) !important; + background-color: var(--surfaces-surface-01) !important; } .pagination-container-box { - background-color: var(--base) !important; + background-color: var(--surfaces-surface-01) !important; } .workspace-table-row { - border-bottom: 1px solid var(--slate7); + border-bottom: 1px solid var(--border-weak); } .worspace-list-table-body-header { - border-bottom: 1px solid var(--slate7); + border-bottom: 1px solid var(--border-weak); } } @@ -10213,8 +9909,7 @@ tbody { .workspace-setting-table-wrapper { box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); - outline: 1px solid var(--slate7); - background: var(--base); + outline: 1px solid var(--border-weak); width: 880px; margin: 0 auto; border-radius: 6px; @@ -10260,11 +9955,11 @@ tbody { column-gap: 8px; .limit { - width: 100px !important; + width: 140px !important; display: flex; flex-direction: column; align-items: center; - padding: 5px !important; + padding: 5px 10px 10px !important; background-color: var(--base); border-radius: 5px; border: 1px solid var(--slate5); @@ -10279,6 +9974,7 @@ tbody { div { width: unset !important; font-size: 11px; + margin-bottom: 2px !important; } } } @@ -10486,146 +10182,8 @@ tbody { } } -.tj-db-operations-header { - height: 48px; - padding: 0 !important; - display: flex; - align-items: center; - background-color: var(--base); - - .row { - margin-left: 0px; - width: 98%; - } - - .col-8 { - padding-left: 0px; - display: flex; - gap: 12px; - align-items: center; - } -} - -.add-new-column-btn { - margin-left: 16px; - height: 28px; - border-radius: 6px; - padding: 0 !important; - display: flex; - align-items: center; - justify-content: center; - background: transparent; - color: var(--slate12); - border: none; -} - -.tj-db-filter-btn { - width: 100%; - height: 28px; - display: flex; - border-radius: 6px; - background: transparent; - color: var(--slate12); - border: none; - display: flex; - align-items: center; - justify-content: center; -} - -.tj-db-filter-btn-applied, -.tj-db-sort-btn-applied { - display: flex !important; - flex-direction: row !important; - justify-content: center !important; - align-items: center !important; - width: 100% !important; - height: 28px !important; - background: var(--grass2) !important; - border-radius: 6px !important; -} - -.tj-db-filter-btn-applied, -.tj-db-filter-clear-icon { - background-color: var(--indigo4) !important; - color: var(--indigo9) !important; - - &:hover { - background-color: var(--button-secondary-pressed) !important; - } -} - -.tj-db-filter-clear-icon { - border-radius: 0px 6px 6px 0px; -} - -.tj-db-filter-btn-active, -.tj-db-sort-btn-active { - display: flex !important; - flex-direction: row !important; - justify-content: center !important; - align-items: center !important; - width: 100% !important; - height: 28px !important; - border-radius: 6px !important; - background: var(--indigo4) !important; - color: var(--indigo9) !important; -} - -.tj-db-filter-btn-active { - background: var(--button-outline-pressed) !important; - color: var(--text-default) !important; -} - -.tj-db-filter-btn-active-filter { - display: flex !important; - flex-direction: row !important; - justify-content: center !important; - align-items: center !important; - width: 100% !important; - height: 28px !important; - border-radius: 6px !important; - background: var(--button-secondary-pressed) !important; - color: var(--text-brand) !important; -} - -.tj-db-header-add-new-row-btn { - height: 28px; - background: transparent; - border-radius: 6px !important; - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - gap: 6px; - border: none; - - padding: span {} -} - -.tj-db-sort-btn { - width: 100%; - height: 28px; - background: transparent; - color: var(--slate12); - border: none; - display: flex; - align-items: center; - justify-content: center; - margin: 0 -} - -.edit-row-btn { - background: transparent; - color: var(--slate12); - border: none; - display: flex; - align-items: center; - justify-content: center; -} - .workspace-variable-header { width: 880px; - ; margin: 0 auto; display: flex; padding: 0; @@ -10789,10 +10347,9 @@ tbody { .org-settings-wrapper-card { display: flex; flex-direction: row; - background: var(--base); + background: var(--surfaces-surface-01); width: 880px; - outline: 1px solid var(--slate5); - box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); + outline: 1px solid var(--border-weak); border-radius: 6px; max-height: calc(100vh - 156px); } @@ -10809,7 +10366,7 @@ tbody { padding: 0px 24px; width: 660px; height: 72px; - border-bottom: 1px solid var(--slate5); + border-bottom: 1px solid var(--border-weak); } @@ -10828,7 +10385,7 @@ tbody { gap: 40px; width: 188px; height: 32px; - background: var(--base); + background: var(--surfaces-surface-01); border-radius: 6px; cursor: pointer; } @@ -11916,15 +11473,12 @@ tbody { } .workspace-settings-filter-wrap { - background: var(--slate3); + background: var(--page-weak); padding: 15px 16px; gap: 12px; width: 880px; height: 62px; - border-right: 1px solid var(--slate7); - border-top: 1px solid var(--slate7); - border-left: 1px solid var(--slate7); - box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); + border: 1px solid var(--border-weak); border-top-left-radius: 6px; border-top-right-radius: 6px; } @@ -11951,7 +11505,7 @@ tbody { } .new-app-dropdown { - background: var(--base) !important; + background: var(--surfaces-surface-01) !important; color: var(--slate12); } @@ -11962,11 +11516,11 @@ tbody { .card, thead { - background: var(--base) !important; + background: var(--page-weak) !important; tr>th, tbody>tr>td { - background: var(--base) !important; + background: var(--page-weak) !important; } } @@ -11993,8 +11547,8 @@ tbody { align-items: center; padding: 7px 8px; gap: 10px; - width: 34px; - height: 34px; + width: 24px; + height: 24px; background: var(--slate4) !important; color: var(--slate9); border-radius: 6px; @@ -12008,8 +11562,8 @@ tbody { align-items: center; padding: 7px 8px; gap: 10px; - width: 34px; - height: 34px; + width: 24px; + height: 24px; border-radius: 6px; } @@ -12103,7 +11657,7 @@ tbody { } .confirm-dialogue-body { - background: var(--base); + background: var(--surfaces-surface-01); color: var(--slate12); } @@ -12188,7 +11742,7 @@ tbody { .sidebar-list-wrap { margin-top: 24px; padding: 0px 20px 20px 20px; - height: calc(100vh - 180px); + flex-grow: 1; overflow: auto; span { @@ -12240,13 +11794,13 @@ tbody { padding: 16px 24px; gap: 8px; height: 72px; - border-top: 1px solid var(--slate5); - background: var(--base); + border-top: 1px solid var(--border-weak); + background: var(--surfaces-surface-01); } .drawer-card-title { padding: 16px 20px; - border-bottom: 1px solid var(--slate5); + border-bottom: 1px solid var(--border-weak); height: 64px; h3 { @@ -12309,7 +11863,7 @@ tbody { .tj-user-table-wrapper { height: calc(100vh - 392px); //52+64+40+32+20+62 overflow-y: auto; - background: var(--base); + background: var(--page-weak); border-right: 1px solid var(--slate7); border-bottom: 1px solid var(--slate7); border-left: 1px solid var(--slate7); @@ -12697,8 +12251,10 @@ tbody { } .workspace-nav-list-wrap { - padding: 20px 20px 20px 20px; - height: calc(100vh - 116px) !important; + flex-grow: 1; + padding: 12px 24px; + width: 100%; + box-sizing: border-box; } .upload-user-form span.file-upload-error { @@ -12967,12 +12523,12 @@ tbody { } .profile-page-content-wrap { - background-color: var(--page-default); + background-color: var(--page-weak); padding-top: 40px; } .profile-page-card { - background-color: var(--base); + background-color: var(--surfaces-surface-01); border-radius: 6px; } @@ -13003,7 +12559,6 @@ tbody { .workspace-constant-card-body { min-height: calc(100vh - 408px); - background: var(--base); } .constant-table-wrapper { @@ -13014,13 +12569,9 @@ tbody { .constant-table-card { min-height: 420px; - padding: 16px; - padding-top: 0px; - padding-bottom: 0px; .p-3-constants { padding: 1rem !important; - padding-left: 0px !important; } } @@ -13045,125 +12596,6 @@ tbody { } } -.home-page-content-container { - max-width: 880px; - - @media only screen and (max-width: 768px) { - margin-bottom: 0rem !important; - - .liner { - width: unset !important; - } - - .app-list { - overflow-y: auto; - height: calc(100vh - 26rem); - - .skeleton-container { - display: flex; - flex-direction: column; - - .col { - display: flex; - justify-content: center; - margin-bottom: 1rem; - } - - .card-skeleton-container { - width: 304px; - } - } - } - - .menu-ico { - display: none !important; - } - } -} - -@media only screen and (min-width: 1584px) and (max-width: 1727px) { - - .edit-button, - .launch-button { - width: 113px !important; - } -} - -@media only screen and (max-width: 1583px) and (min-width: 1312px) { - .homepage-app-card-list-item { - max-width: 264px; - - .edit-button, - .launch-button { - width: 109px !important; - } - } -} - -@media only screen and (min-width: 1728px) { - .homepage-app-card-list-item { - max-width: 304px; - - .edit-button, - .launch-button { - width: 129px !important; - } - } - - .home-page-content-container { - max-width: 976px; - } - - .liner { - width: 976px; - } -} - -@media only screen and (max-width: 992px) { - .homepage-app-card-list-item-wrap { - display: flex; - justify-content: center; - margin-left: auto; - margin-right: auto; - width: 100%; - margin-top: 22px; - } - - .homepage-app-card-list-item { - max-width: 304px !important; - flex-basis: 100%; - - .edit-button, - .launch-button { - width: 129px !important; - } - } -} - -@media only screen and (min-width: 993px) and (max-width: 1311px) { - .home-page-content-container { - max-width: 568px; - } - - .homepage-app-card-list-item-wrap { - row-gap: 20px; - } - - .homepage-app-card-list-item { - max-width: 269px; - flex-basis: 100%; - - .edit-button, - .launch-button { - width: 111.5px !important; - } - } - - .liner { - width: 568px; - } -} - .tj-docs-link { color: var(--indigo9) !important; text-decoration: none; @@ -13189,9 +12621,9 @@ tbody { .modal-body-content, .modal-sidebar, .card { - background-color: var(--base) !important; + background-color: var(--page-weak) !important; color: var(--slate12) !important; - border-color: var(--slate5) !important; + border-color: var(--border-weak) !important; } .datasource-modal-sidebar-footer { @@ -13240,14 +12672,10 @@ tbody { .marketplace-body { height: calc(100vh - 64px) !important; overflow-y: auto; - background-color: var(--page-default); + background-color: var(--page-weak); } .plugins-card { - background-color: var(--base); - border: 1px solid var(--slate3); - box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05); - border-radius: 6px; .card-body-alignment { min-height: 145px; @@ -13529,16 +12957,14 @@ tbody { .instance-settings-page { width: 880px; margin: 0 auto; - background: var(--base); + background: var(--surfaces-surface-01); .page-wrapper { margin-bottom: 50px !important; } .card { - background: var(--base); - border: 1px solid var(--slate7) !important; - box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05) !important; + background: var(--surfaces-surface-01); width: 880px; .card-header { @@ -13570,8 +12996,8 @@ tbody { align-items: center; padding: 24px 32px; gap: 8px; - border-top: 1px solid var(--slate5) !important; - background: var(--base); + border-top: 1px solid var(--border-weak) !important; + background: var(--surfaces-surface-01); margin-top: 0px !important; align-Self: 'stretch'; height: 88px; @@ -13695,7 +13121,7 @@ tbody { } .audit-log { - background-color: var(--slate2); + background-color: var(--page-weak); width: unset; .tj-ms { @@ -14782,7 +14208,7 @@ tbody { } .modal-content { - background: var(--base); + background: var(--surfaces-surface-01); color: var(--slate12); } @@ -15142,7 +14568,7 @@ tbody { } .modal-content { - background: var(--base); + background: var(--surfaces-surface-01); color: var(--slate12); } @@ -15218,7 +14644,7 @@ tbody { } .popover-body { - background-color: var(--base); + background-color: var(--surfaces-surface-01); color: var(--slate12); border-radius: 6px; } @@ -16793,6 +16219,11 @@ fieldset:disabled { header { display: flex; gap: 12px; + padding: 16px 24px; + border-radius: 8px; + background: var(--bg-surface-layer-01); + box-shadow: var(--elevation-000-box-shadow); + border: 1px solid var(--border-weak); .left { border-radius: 24px; @@ -16887,12 +16318,49 @@ fieldset:disabled { font-weight: 400; line-height: 18px; } + + .right { + display: flex; + gap: 8px; + flex-wrap: wrap; + + button { + border: none; + outline: none; + display: flex; + gap: 6px; + align-items: center; + padding: 5px 10px; + background: transparent; + border-radius: 6px; + border: 1px solid var(--border-weak, #E4E7EB); + background: var(--button-secondary, #FFF); + box-shadow: 0px 0px 1px 0px var(--dropshadow-100700-layer-1, rgba(48, 50, 51, 0.05)), 0px 1px 1px 0px var(--dropshadow-100400-layer-2, rgba(48, 50, 51, 0.10)); + + span { + color: var(--text-default, #1B1F24); + + /* base/medium */ + font-family: "IBM Plex Sans"; + font-size: 12px; + font-style: normal; + font-weight: 500; + line-height: 18px; + /* 150% */ + } + + &:hover { + border: 1px solid var(--border-default, #CCD1D5); + background: linear-gradient(0deg, var(--button-outline-hover, rgba(136, 144, 153, 0.12)) 0%, var(--button-outline-hover, rgba(136, 144, 153, 0.12)) 100%), var(--button-outline, #FFF); + } + } + } } } section.ai-message-prompt-input-wrapper { border-radius: 6px; - // border: 1px solid var(--border-accent-strong, #4368E3); + border: 1px solid var(--border-weak, #E4E7EB); background: var(--background-surface-layer-01, #FFFFFF); margin-top: 12px; padding: 12px; diff --git a/frontend/src/_ui/Card/Card.jsx b/frontend/src/_ui/Card/Card.jsx index e070cb0f14..967fbed113 100644 --- a/frontend/src/_ui/Card/Card.jsx +++ b/frontend/src/_ui/Card/Card.jsx @@ -11,6 +11,7 @@ const Card = ({ width = 50, usePluginIcon = false, className, + cardClassName, titleClassName, actionButton, darkMode, @@ -37,7 +38,7 @@ const Card = ({ return (
{ e.preventDefault(); diff --git a/frontend/src/_ui/FolderList/FolderList.scss b/frontend/src/_ui/FolderList/FolderList.scss index 921243f1b3..89ae9937fc 100644 --- a/frontend/src/_ui/FolderList/FolderList.scss +++ b/frontend/src/_ui/FolderList/FolderList.scss @@ -5,7 +5,7 @@ line-height: 20px; display: flex; align-items: center; - color: var(--slate12); + color: var(--text-default); min-height: 32px; cursor: pointer; padding: 6px 8px; @@ -71,5 +71,5 @@ } .tj-list-item-selected { - background-color: var(--slate5);; + background-color: var(--interactive-default); } \ No newline at end of file diff --git a/frontend/src/_ui/Header/index.jsx b/frontend/src/_ui/Header/index.jsx index dc46793984..66425c4619 100644 --- a/frontend/src/_ui/Header/index.jsx +++ b/frontend/src/_ui/Header/index.jsx @@ -71,9 +71,9 @@ function Header({
{!collapseSidebar && (
-
+
-

+

{pathname}

{routesWithTags(pathname) && ( @@ -117,7 +117,7 @@ function Header({
)}
-
+
{enableCollapsibleSidebar && collapseSidebar && (
diff --git a/frontend/src/_ui/Icon/solidIcons/AppLimitSvg.jsx b/frontend/src/_ui/Icon/solidIcons/AppLimitSvg.jsx index e9490c038c..91759919a0 100644 --- a/frontend/src/_ui/Icon/solidIcons/AppLimitSvg.jsx +++ b/frontend/src/_ui/Icon/solidIcons/AppLimitSvg.jsx @@ -1,22 +1,22 @@ import React from 'react'; -const AppLimitSvg = () => ( - +const AppLimitSvg = ({ fill }) => ( + ); diff --git a/frontend/src/_ui/Icon/solidIcons/Plus.jsx b/frontend/src/_ui/Icon/solidIcons/Plus.jsx index 1af087cdeb..7f7b601dad 100644 --- a/frontend/src/_ui/Icon/solidIcons/Plus.jsx +++ b/frontend/src/_ui/Icon/solidIcons/Plus.jsx @@ -5,7 +5,7 @@ const Plus = ({ fill = '#C1C8CD', width = '25', className = '', viewBox = '0 0 2 width={width} height={width} viewBox={viewBox} - fill="none" + fill={fill} xmlns="http://www.w3.org/2000/svg" className={className} data-cy={dataCy} diff --git a/frontend/src/_ui/Icon/solidIcons/Search.jsx b/frontend/src/_ui/Icon/solidIcons/Search.jsx index 9fae3533ee..c7045732cb 100644 --- a/frontend/src/_ui/Icon/solidIcons/Search.jsx +++ b/frontend/src/_ui/Icon/solidIcons/Search.jsx @@ -5,7 +5,7 @@ const Search = ({ fill = '#C1C8CD', width = '24', className = '', viewBox = '0 0 width={width} height={width} viewBox={viewBox} - fill="none" + fill={fill} xmlns="http://www.w3.org/2000/svg" className={className} style={style} diff --git a/frontend/src/_ui/Input-V3/index.js b/frontend/src/_ui/Input-V3/index.js index 71b2b3cf2a..d80606981d 100644 --- a/frontend/src/_ui/Input-V3/index.js +++ b/frontend/src/_ui/Input-V3/index.js @@ -6,7 +6,7 @@ import { toast } from 'react-hot-toast'; import InputComponent from '@/components/ui/Input/Index'; const InputV3 = ({ helpText, ...props }) => { - const { workspaceVariables, workspaceConstants, value, widget, disabled, encrypted } = props; + const { workspaceVariables, workspaceConstants, value, widget, encrypted, onBlur } = props; const [isFocused, setIsFocused] = useState(false); const [isCopied, setIsCopied] = useState(false); @@ -37,6 +37,11 @@ const InputV3 = ({ helpText, ...props }) => { setIsFocused(true)} + onBlur={(event) => { + setIsFocused(false); + onBlur(event); + }} styles="tw-bg-transparent" label={props.label} placeholder={props.placeholder} @@ -49,6 +54,11 @@ const InputV3 = ({ helpText, ...props }) => { {...props} type="password" value={value} + onFocus={() => setIsFocused(true)} + onBlur={(event) => { + setIsFocused(false); + onBlur(event); + }} styles="tw-bg-transparent" label={props.label} placeholder={props.placeholder} diff --git a/frontend/src/_ui/Input/index.js b/frontend/src/_ui/Input/index.js index 7b876d4e45..2e632c74e7 100644 --- a/frontend/src/_ui/Input/index.js +++ b/frontend/src/_ui/Input/index.js @@ -5,20 +5,21 @@ import SolidIcon from '../Icon/SolidIcons'; import { toast } from 'react-hot-toast'; const Input = ({ helpText, onBlur, ...props }) => { - const { workspaceVariables, workspaceConstants, value, type, disabled, encrypted } = props; + const { workspaceVariables, workspaceConstants, value, type, disabled, encrypted, isWorkspaceConstant } = props; const [isFocused, setIsFocused] = useState(false); const [isCopied, setIsCopied] = useState(false); - const [showPasswordProps, setShowPasswordProps] = useState({ - inputType: type, - iconType: 'eyedisable', - }); + const [showPassword, setShowPassword] = useState(false); + const inputType = type === 'password' || encrypted ? (showPassword ? 'text' : 'password') : type; + const iconType = showPassword ? 'eye' : 'eyedisable'; + + useEffect(() => { + if (isWorkspaceConstant) { + setShowPassword(true); + } + }, [isWorkspaceConstant]); const toggleShowPassword = () => { - if (inputType !== 'text') { - setShowPasswordProps({ inputType: 'text', iconType: 'eye' }); - } else { - setShowPasswordProps({ inputType: 'password', iconType: 'eyedisable' }); - } + setShowPassword(!showPassword); }; const handleCopyToClipboard = async () => { @@ -36,12 +37,6 @@ const Input = ({ helpText, onBlur, ...props }) => { } }; - useEffect(() => { - if (disabled && encrypted) setShowPasswordProps({ inputType: 'password', iconType: 'eyedisable' }); - }, [disabled]); - - const { inputType, iconType } = showPasswordProps; - return (
{ }} /> {(type === 'password' || encrypted) && ( -
- {' '} +
)} @@ -66,12 +63,10 @@ const Input = ({ helpText, onBlur, ...props }) => { value && (!isCopied ? (
- {' '}
) : (
- {' '} Copied!
))} diff --git a/frontend/src/_ui/Layout/index.jsx b/frontend/src/_ui/Layout/index.jsx index aa784e04cf..1f7095dd04 100644 --- a/frontend/src/_ui/Layout/index.jsx +++ b/frontend/src/_ui/Layout/index.jsx @@ -148,7 +148,7 @@ function Layout({ collapseSidebar={collapseSidebar} toggleCollapsibleSidebar={toggleCollapsibleSidebar} /> -
{children}
+
{children}
.form-check-input:not(:checked) { - background-color: #ffffff; + background-color: var(--slider-track); } .text-wrappers{ display: flex; diff --git a/frontend/src/components/ui/Card/Index.jsx b/frontend/src/components/ui/Card/Index.jsx new file mode 100644 index 0000000000..3005fcdea5 --- /dev/null +++ b/frontend/src/components/ui/Card/Index.jsx @@ -0,0 +1,35 @@ +import * as React from 'react'; + +import { cn } from '@/lib/utils'; + +const Card = React.forwardRef(({ className, ...props }, ref) => ( +
+)); +Card.displayName = 'Card'; + +const CardHeader = React.forwardRef(({ className, ...props }, ref) => ( +
+)); +CardHeader.displayName = 'CardHeader'; + +const CardTitle = React.forwardRef(({ className, ...props }, ref) => ( +
+)); +CardTitle.displayName = 'CardTitle'; + +const CardDescription = React.forwardRef(({ className, ...props }, ref) => ( +
+)); +CardDescription.displayName = 'CardDescription'; + +const CardContent = React.forwardRef(({ className, ...props }, ref) => ( +
+)); +CardContent.displayName = 'CardContent'; + +const CardFooter = React.forwardRef(({ className, ...props }, ref) => ( +
+)); +CardFooter.displayName = 'CardFooter'; + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }; diff --git a/frontend/src/components/ui/Input/CommonInput/Index.jsx b/frontend/src/components/ui/Input/CommonInput/Index.jsx index 2b968a0940..c992db5aaa 100644 --- a/frontend/src/components/ui/Input/CommonInput/Index.jsx +++ b/frontend/src/components/ui/Input/CommonInput/Index.jsx @@ -6,14 +6,26 @@ import { ButtonSolid } from '../../../../_components/AppButton'; import { generateCypressDataCy } from '../../../../modules/common/helpers/cypressHelpers.js'; const CommonInput = ({ label, helperText, disabled, required, onChange: change, ...restProps }) => { - const { type, encrypted, validation, isValidatedMessages, isDisabled } = restProps; + const { + propertyKey, + type, + encrypted, + validation, + isValidatedMessages, + isDisabled, + isEditing, + handleEncryptedFieldsToggle, + labelDisabled, + } = restProps; const InputComponentType = type === 'number' ? NumberInput : TextInput; const [isValid, setIsValid] = useState(null); const [message, setMessage] = useState(''); - const [isEditing, setIsEditing] = useState(false); const isEncrypted = type === 'password' || encrypted; + const isWorkspaceConstant = + restProps.placeholder && + (restProps.placeholder.includes('{{constants') || restProps.placeholder.includes('{{secrets')); const handleChange = (e) => { if (validation) { @@ -39,20 +51,12 @@ const CommonInput = ({ label, helperText, disabled, required, onChange: change, } }, [isValid, isValidatedMessages]); - const toggleEditing = () => { - if (isDisabled) return; - - const willBeInEditMode = !isEditing; - setIsEditing(willBeInEditMode); - change({ target: { value: '' } }); - }; - return (
{label && (
- +
)} {type === 'password' && ( @@ -65,7 +69,7 @@ const CommonInput = ({ label, helperText, disabled, required, onChange: change, target="_blank" rel="noreferrer" disabled={isDisabled} - onClick={toggleEditing} + onClick={(e) => handleEncryptedFieldsToggle(e, propertyKey)} data-cy={`button-${generateCypressDataCy(isEditing ? 'Cancel' : 'Edit')}`} > {isEditing ? 'Cancel' : 'Edit'} @@ -86,6 +90,7 @@ const CommonInput = ({ label, helperText, disabled, required, onChange: change, required={required} response={isValid} onChange={handleChange} + isWorkspaceConstant={isWorkspaceConstant} {...restProps} /> {helperText && ( diff --git a/frontend/src/components/ui/Input/Input.jsx b/frontend/src/components/ui/Input/Input.jsx index 0f227023e1..24090abc2f 100644 --- a/frontend/src/components/ui/Input/Input.jsx +++ b/frontend/src/components/ui/Input/Input.jsx @@ -2,56 +2,65 @@ import * as React from 'react'; import { cn } from '@/lib/utils'; import { inputVariants } from './InputUtils/Variants'; import SolidIcon from '../../../_ui/Icon/SolidIcons'; +import { useEffect } from 'react'; -const Input = React.forwardRef(({ className, size, type, multiline, response, rows = 3, ...props }, ref) => { - const [isPasswordVisible, setIsPasswordVisible] = React.useState(false); - const isPasswordField = type === 'password'; +const Input = React.forwardRef( + ({ className, size, type, multiline, response, isWorkspaceConstant, rows = 3, ...props }, ref) => { + const [isPasswordVisible, setIsPasswordVisible] = React.useState(false); + const isPasswordField = type === 'password'; - const togglePasswordVisibility = () => { - if (!props.disabled) { - setIsPasswordVisible((prev) => !prev); - } - }; + const togglePasswordVisibility = () => { + if (!props.disabled) { + setIsPasswordVisible((prev) => !prev); + } + }; - const validationClass = response === true ? 'valid-textarea' : response === false ? 'invalid-textarea' : ''; + useEffect(() => { + if (isWorkspaceConstant) { + setIsPasswordVisible(true); + } + }, [isWorkspaceConstant]); - return ( -
- {multiline ? ( -