Merge branch 'main' into release/marketplace-sprint-12

This commit is contained in:
Ganesh Kumar 2025-07-02 17:47:21 +05:30
commit a80fc752d8
13 changed files with 121 additions and 59 deletions

View file

@ -42,6 +42,20 @@ else
echo "Using external PostgREST at $PGRST_HOST."
fi
# Check WORKLOW_WORKER and skip SETUP_CMD if true
if [ "${WORKFLOW_WORKER}" == "true" ]; then
echo "WORKFLOW_WORKER is set to true. Running worker process."
npm run worker:prod
else
# Determine setup command based on the presence of ./server/dist
if [ -d "./server/dist" ]; then
SETUP_CMD='npm run db:setup:prod'
else
SETUP_CMD='npm run db:setup'
fi
fi
# Neo4j configuration
# ----------------------------------
# Default Neo4j environment values
@ -63,14 +77,14 @@ if [ -n "$NEO4J_AUTH" ]; then
export NEO4J_USERNAME
export NEO4J_PASSWORD
echo "Neo4j authentication configured with username: $NEO4J_USERNAME"
echo "Neo4j authentication configured with username: $NEO4J_USERNAME" >/dev/null 2>&1
else
echo "NEO4J_AUTH not set, using default authentication"
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..."
echo "Setting Neo4j initial password..." >/dev/null 2>&1
# Ensure Neo4j is not running before setting the initial password
neo4j stop || true
@ -78,27 +92,27 @@ if [ "$NEO4J_AUTH" != "none" ] && [ -n "$NEO4J_PASSWORD" ]; then
# 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"
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"
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"
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"
echo "Warning: Could not set Neo4j password, it may already be set" >/dev/null 2>&1
}
fi
fi
# Update Neo4j configuration
echo "Configuring Neo4j..."
echo "Configuring Neo4j..." >/dev/null 2>&1
cat > /etc/neo4j/neo4j.conf << EOF
# Neo4j configuration
dbms.security.auth_enabled=true
@ -124,12 +138,12 @@ 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..."
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 (via status check)"
echo "Neo4j is ready 🚀"
NEO4J_READY=true
break
fi
@ -143,7 +157,7 @@ for i in {1..60}; do
fi
fi
echo "Waiting for Neo4j to start... ($i/60)"
echo "Waiting for Neo4j to start... ($i/60)" >/dev/null 2>&1
sleep 2
done
@ -151,19 +165,6 @@ if [ "$NEO4J_READY" = false ]; then
echo "WARNING: Neo4j may not be fully started yet, but continuing..."
fi
# Check WORKLOW_WORKER and skip SETUP_CMD if true
if [ "${WORKFLOW_WORKER}" == "true" ]; then
echo "WORKFLOW_WORKER is set to true. Running worker process."
npm run worker:prod
else
# Determine setup command based on the presence of ./server/dist
if [ -d "./server/dist" ]; then
SETUP_CMD='npm run db:setup:prod'
else
SETUP_CMD='npm run db:setup'
fi
fi
# Wait for PostgreSQL connection
if [ -z "$DATABASE_URL" ]; then
./server/scripts/wait-for-it.sh $PG_HOST:${PG_PORT:-5432} --strict --timeout=300 -- echo "PostgreSQL is up"

View file

@ -34,8 +34,7 @@ COPY ./package.json ./package.json
COPY ./plugins/package.json ./plugins/package-lock.json ./plugins/
RUN npm --prefix plugins ci --omit=dev
COPY ./plugins/ ./plugins/
RUN NODE_ENV=production npm --prefix plugins run build
RUN npm --prefix plugins prune --omit=dev
RUN NODE_ENV=production npm --prefix plugins run build && npm --prefix plugins prune --omit=dev
ENV TOOLJET_EDITION=ee
@ -52,14 +51,12 @@ ENV TOOLJET_EDITION=ee
COPY ./server/package.json ./server/package-lock.json ./server/
RUN npm --prefix server ci --omit=dev
COPY ./server/ ./server/
RUN npm install -g @nestjs/cli
RUN npm install -g copyfiles
RUN npm --prefix server run build
RUN npm prune --production --prefix server
RUN npm install -g @nestjs/cli && npm install -g copyfiles
RUN npm --prefix server run build && npm prune --production --prefix server
# Install dependencies for PostgREST, curl, unzip, etc.
# Install dependencies for PostgREST, curl, tar, etc.
RUN apt-get update && apt-get install -y \
curl ca-certificates unzip tar \
curl ca-certificates tar \
&& rm -rf /var/lib/apt/lists/*
ENV POSTGREST_VERSION=v12.2.0
@ -81,7 +78,6 @@ RUN apt-get update && \
ca-certificates \
xz-utils \
tar \
zip \
postgresql-client \
redis \
libaio1 \

View file

@ -138,6 +138,15 @@ export const chartConfig = {
displayName: 'Background color',
validation: { schema: { type: 'string' }, defaultValue: 'var(--cc-surface1-surface)' },
},
borderColor: {
type: 'colorSwatches',
displayName: 'Border color',
validation: {
schema: { type: 'string' },
defaultValue: 'var(--cc-default-border)',
},
accordian: 'container',
},
padding: {
type: 'code',
displayName: 'Padding',
@ -233,6 +242,7 @@ export const chartConfig = {
borderRadius: { value: '{{4}}' },
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
borderColor: { value: 'var(--cc-default-border)' },
},
},
};

View file

@ -8,7 +8,7 @@ import { isEqual } from 'lodash';
import { deepClone } from '@/_helpers/utilities/utils.helpers';
import useStore from '@/AppBuilder/_stores/store';
import { shallow } from 'zustand/shallow';
import { getCssVarValue } from './utils';
import { getCssVarValue, getModifiedColor } from './utils';
var tinycolor = require('tinycolor2');
@ -33,11 +33,11 @@ export const Chart = function Chart({
return '#fff';
};
const { padding, visibility, disabledState, boxShadow, backgroundColor, borderRadius } = styles;
const { padding, visibility, disabledState, boxShadow, backgroundColor, borderRadius, borderColor } = styles;
const { title, markerColor, showGridLines, type, data, jsonDescription, plotFromJson, showAxes, barmode } =
properties;
const modifiedBackgroundColor = getCssVarValue(document.documentElement, backgroundColor);
const modifiedBackgroundColor = getModifiedColor(backgroundColor, 0);
const modifiedMarkerColor = getCssVarValue(document.documentElement, markerColor);
const modifiedGridLines = getCssVarValue(document.documentElement, 'var(--cc-weak-border)');
const modifiedTextColor = getCssVarValue(document.documentElement, 'var(--cc-primary-text)');
@ -55,7 +55,8 @@ export const Chart = function Chart({
width: width - 4,
height,
display: visibility ? '' : 'none',
background: darkMode ? '#1f2936' : 'white',
// background: darkMode ? '#1f2936' : 'white',
border: `1px solid ${borderColor}`,
boxShadow,
borderRadius,
};
@ -82,6 +83,7 @@ export const Chart = function Chart({
? '#1f2936'
: '#fff'
: modifiedBackgroundColor;
const fontColor = getColor(updatedBgColor);
const chartTitle = plotFromJson ? chartLayout?.title ?? title : title;
@ -105,8 +107,8 @@ export const Chart = function Chart({
}, [JSON.stringify(chartLayout, chartTitle)]);
const layout = {
width: width - 4,
height,
width: width - 6,
height: height - 4,
plot_bgcolor: updatedBgColor,
paper_bgcolor: updatedBgColor,
title: {

@ -1 +1 @@
Subproject commit 80f2e4cab88aa586e9b8a731f8643bcc0cef52e7
Subproject commit cc864000dd03cc345e53ae9fc43821d3174f4c64

View file

@ -53,6 +53,7 @@ import { SampleDBScheduler } from '@modules/data-sources/schedulers/sample-db.sc
import { SessionScheduler } from '@modules/session/scheduler';
import { AuditLogsClearScheduler } from '@modules/audit-logs/scheduler';
import { ModulesModule } from '@modules/modules/module';
import { EmailListenerModule } from '@modules/email-listener/module';
export class AppModule implements OnModuleInit {
static async register(configs: { IS_GET_CONTEXT: boolean }): Promise<DynamicModule> {
// Load static and dynamic modules
@ -113,6 +114,7 @@ export class AppModule implements OnModuleInit {
await AppGitModule.register(configs),
await CrmModule.register(configs),
await OrganizationPaymentModule.register(configs),
await EmailListenerModule.register(configs),
];
return {

View file

@ -138,6 +138,15 @@ export const chartConfig = {
displayName: 'Background color',
validation: { schema: { type: 'string' }, defaultValue: 'var(--cc-surface1-surface)' },
},
borderColor: {
type: 'colorSwatches',
displayName: 'Border color',
validation: {
schema: { type: 'string' },
defaultValue: 'var(--cc-default-border)',
},
accordian: 'container',
},
padding: {
type: 'code',
displayName: 'Padding',
@ -233,6 +242,7 @@ export const chartConfig = {
borderRadius: { value: '{{4}}' },
visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' },
borderColor: { value: 'var(--cc-default-border)' },
},
},
};

View file

@ -1,9 +1,8 @@
import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { Logger } from 'nestjs-pino';
import { EmailEventPayload } from './constants';
import { EmailEventPayload, EMAIL_EVENTS } from '@modules/email/constants';
import { EmailService } from '@modules/email/service';
import { EMAIL_EVENTS } from './constants';
@Injectable()
export class EmailListener {
@ -15,7 +14,6 @@ export class EmailListener {
@OnEvent('emailEvent')
async handleEmailEvent(eventData: EmailEventPayload) {
const { type, payload } = eventData;
try {
switch (type) {
case EMAIL_EVENTS.SEND_WELCOME_EMAIL:

View file

@ -0,0 +1,17 @@
import { DynamicModule } from '@nestjs/common';
import { SubModule } from '@modules/app/sub-module';
import { EmailModule } from '@modules/email/module';
import { getImportPath } from '@modules/app/constants';
export class EmailListenerModule extends SubModule {
static async register(configs?: { IS_GET_CONTEXT: boolean }): Promise<DynamicModule> {
const importPath = await getImportPath(configs?.IS_GET_CONTEXT);
const { EmailListener } = await import(`${importPath}/email-listener/listener`);
return {
module: EmailListenerModule,
imports: [await EmailModule.register(configs)],
providers: [EmailListener],
exports: [],
};
}
}

View file

@ -10,7 +10,6 @@ export class EmailModule extends SubModule {
const importPath = await getImportPath(configs?.IS_GET_CONTEXT);
const { EmailService } = await import(`${importPath}/email/service`);
const { EmailUtilService } = await import(`${importPath}/email/util.service`);
const { EmailListener } = await import(`${importPath}/email/listener`);
return {
module: EmailModule,
imports: [
@ -18,8 +17,8 @@ export class EmailModule extends SubModule {
await DataSourcesModule.register(configs),
await SMTPModule.register(configs),
],
providers: [EmailService, EmailListener, EmailUtilService],
exports: [EmailListener, EmailUtilService],
providers: [EmailService, EmailUtilService],
exports: [EmailUtilService, EmailService],
};
}
}

View file

@ -9,7 +9,6 @@ import {
} from '@modules/email/dto';
import { EmailUtilService } from './util.service';
import { IEmailService } from './interfaces/IService';
import { INSTANCE_SYSTEM_SETTINGS } from '@modules/instance-settings/constants';
import { WhiteLabellingUtilService } from '@modules/white-labelling/util.service';
handlebars.registerHelper('capitalize', function (value) {
@ -29,15 +28,6 @@ export class EmailService implements IEmailService {
protected WHITE_LABEL_TEXT;
protected WHITE_LABEL_LOGO;
protected SUB_PATH;
protected SMTP: {
[INSTANCE_SYSTEM_SETTINGS.SMTP_ENABLED]: boolean;
[INSTANCE_SYSTEM_SETTINGS.SMTP_DOMAIN]: string;
[INSTANCE_SYSTEM_SETTINGS.SMTP_PORT]: string;
[INSTANCE_SYSTEM_SETTINGS.SMTP_USERNAME]: string;
[INSTANCE_SYSTEM_SETTINGS.SMTP_PASSWORD]: string;
[INSTANCE_SYSTEM_SETTINGS.SMTP_FROM_EMAIL]: string;
[INSTANCE_SYSTEM_SETTINGS.SMTP_ENV_CONFIGURED]: boolean;
};
protected defaultWhiteLabelState: boolean;
constructor(
@ -63,14 +53,13 @@ export class EmailService implements IEmailService {
async init(organizationId?: string | null) {
const whiteLabelSettings = await this.emailUtilService.retrieveWhiteLabelSettings(null);
this.SMTP = await this.emailUtilService.retrieveSmtpSettings();
this.WHITE_LABEL_TEXT = whiteLabelSettings?.white_label_text;
this.WHITE_LABEL_LOGO = whiteLabelSettings?.white_label_logo;
this.defaultWhiteLabelState = whiteLabelSettings?.default;
}
protected compileTemplate(templatePath: string, templateData: object) {
this.emailUtilService.compileTemplate(templatePath, templateData);
return this.emailUtilService.compileTemplate(templatePath, templateData);
}
protected stripTrailingSlash(hostname: string) {
@ -88,6 +77,7 @@ export class EmailService implements IEmailService {
redirectTo,
} = payload;
await this.init(organizationId);
await this.emailUtilService.init(organizationId);
const isOrgInvite = organizationInvitationToken && sender && organizationName;
const inviteUrl = generateInviteURL(invitationtoken, organizationInvitationToken, organizationId, null, redirectTo);
const subject = isOrgInvite ? `Welcome to ${organizationName || 'ToolJet'}` : 'Set up your account!';

View file

@ -36,7 +36,11 @@ export class EmailUtilService implements IEmailUtilService {
constructor(
protected readonly whiteLabellingUtilService: WhiteLabellingUtilService,
protected readonly smtpUtilService: SMTPUtilService
) {}
) {
this.TOOLJET_HOST = this.stripTrailingSlash(process.env.TOOLJET_HOST);
this.SUB_PATH = process.env.SUB_PATH;
this.NODE_ENV = process.env.NODE_ENV || 'development';
}
async retrieveWhiteLabelSettings(organizationId?: string | null): Promise<any> {
const whiteLabelSetting = await this.whiteLabellingUtilService.getProcessedSettings(organizationId);
@ -253,4 +257,14 @@ export class EmailUtilService implements IEmailUtilService {
whiteLabelLogo: DEFAULT_WHITE_LABELLING_SETTINGS.white_label_logo,
});
}
protected stripTrailingSlash(hostname: string) {
return hostname?.endsWith('/') ? hostname.slice(0, -1) : hostname;
}
async init(organizationId?: string | null) {
const whiteLabelSettings = await this.retrieveWhiteLabelSettings(null);
this.SMTP = await this.retrieveSmtpSettings();
this.WHITE_LABEL_TEXT = whiteLabelSettings?.white_label_text;
this.WHITE_LABEL_LOGO = whiteLabelSettings?.white_label_logo;
this.defaultWhiteLabelState = whiteLabelSettings?.default;
}
}

View file

@ -274,6 +274,29 @@ export class LicenseCountsService implements ILicenseCountsService {
manager
);
}
async getUserIdWithEndUserRole(manager: EntityManager): Promise<string[]> {
const statusList = [WORKSPACE_USER_STATUS.INVITED, WORKSPACE_USER_STATUS.ACTIVE];
const users = await manager.find(User, {
select: ['id'],
where: {
status: Not(USER_STATUS.ARCHIVED),
organizationUsers: {
status: In(statusList),
},
userPermissions: {
name: USER_ROLE.END_USER,
organization: {
status: WORKSPACE_STATUS.ACTIVE,
},
},
},
relations: ['organizationUsers', 'userPermissions', 'userPermissions.organization'],
});
// Extract unique user IDs
return [...new Set(users.map((user) => user.id))];
}
async fetchTotalAppCount(organizationId: string, manager: EntityManager): Promise<number> {
if (getTooljetEdition() !== TOOLJET_EDITIONS.Cloud) {