diff --git a/apps/sim/.env.example b/apps/sim/.env.example index 0f3a44bb94..a533cd0a8e 100644 --- a/apps/sim/.env.example +++ b/apps/sim/.env.example @@ -1,5 +1,6 @@ # Database (Required) DATABASE_URL="postgresql://postgres:password@localhost:5432/postgres" +# DATABASE_SSL=TRUE # Optional: Enable SSL for database connections (defaults to FALSE) # PostgreSQL Port (Optional) - defaults to 5432 if not specified # POSTGRES_PORT=5432 diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/search-modal/search-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/search-modal/search-modal.tsx index 7f36a3bcf0..9f5eca4f0a 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/search-modal/search-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/search-modal/search-modal.tsx @@ -19,10 +19,6 @@ import { Dialog, DialogOverlay, DialogPortal, DialogTitle } from '@/components/u import { Input } from '@/components/ui/input' import { useBrandConfig } from '@/lib/branding/branding' import { cn } from '@/lib/utils' -import { - TemplateCard, - TemplateCardSkeleton, -} from '@/app/workspace/[workspaceId]/templates/components/template-card' import { getKeyboardShortcutText } from '@/app/workspace/[workspaceId]/w/hooks/use-keyboard-shortcuts' import { getAllBlocks } from '@/blocks' import { type NavigationSection, useSearchNavigation } from './hooks/use-search-navigation' @@ -30,28 +26,12 @@ import { type NavigationSection, useSearchNavigation } from './hooks/use-search- interface SearchModalProps { open: boolean onOpenChange: (open: boolean) => void - templates?: TemplateData[] workflows?: WorkflowItem[] workspaces?: WorkspaceItem[] - loading?: boolean + knowledgeBases?: KnowledgeBaseItem[] isOnWorkflowPage?: boolean } -interface TemplateData { - id: string - title: string - description: string - author: string - usageCount: string - stars: number - icon: string - iconColor: string - state?: { - blocks?: Record - } - isStarred?: boolean -} - interface WorkflowItem { id: string name: string @@ -93,6 +73,14 @@ interface PageItem { shortcut?: string } +interface KnowledgeBaseItem { + id: string + name: string + description?: string + href: string + isCurrent?: boolean +} + interface DocItem { id: string name: string @@ -104,10 +92,9 @@ interface DocItem { export function SearchModal({ open, onOpenChange, - templates = [], workflows = [], workspaces = [], - loading = false, + knowledgeBases = [], isOnWorkflowPage = false, }: SearchModalProps) { const [searchQuery, setSearchQuery] = useState('') @@ -116,14 +103,6 @@ export function SearchModal({ const workspaceId = params.workspaceId as string const brand = useBrandConfig() - // Local state for templates to handle star changes - const [localTemplates, setLocalTemplates] = useState(templates) - - // Update local templates when props change - useEffect(() => { - setLocalTemplates(templates) - }, [templates]) - // Get all available blocks - only when on workflow page const blocks = useMemo(() => { if (!isOnWorkflowPage) return [] @@ -131,10 +110,7 @@ export function SearchModal({ const allBlocks = getAllBlocks() const regularBlocks = allBlocks .filter( - (block) => - block.type !== 'starter' && - !block.hideFromToolbar && - (block.category === 'blocks' || block.category === 'triggers') + (block) => block.type !== 'starter' && !block.hideFromToolbar && block.category === 'blocks' ) .map( (block): BlockItem => ({ @@ -171,6 +147,30 @@ export function SearchModal({ return [...regularBlocks, ...specialBlocks].sort((a, b) => a.name.localeCompare(b.name)) }, [isOnWorkflowPage]) + // Get all available triggers - only when on workflow page + const triggers = useMemo(() => { + if (!isOnWorkflowPage) return [] + + const allBlocks = getAllBlocks() + return allBlocks + .filter( + (block) => + block.type !== 'starter' && !block.hideFromToolbar && block.category === 'triggers' + ) + .map( + (block): BlockItem => ({ + id: block.type, + name: block.name, + description: block.description || '', + longDescription: block.longDescription, + icon: block.icon, + bgColor: block.bgColor || '#6B7280', + type: block.type, + }) + ) + .sort((a, b) => a.name.localeCompare(b.name)) + }, [isOnWorkflowPage]) + // Get all available tools - only when on workflow page const tools = useMemo(() => { if (!isOnWorkflowPage) return [] @@ -252,24 +252,18 @@ export function SearchModal({ return blocks.filter((block) => block.name.toLowerCase().includes(query)) }, [blocks, searchQuery]) + const filteredTriggers = useMemo(() => { + if (!searchQuery.trim()) return triggers + const query = searchQuery.toLowerCase() + return triggers.filter((trigger) => trigger.name.toLowerCase().includes(query)) + }, [triggers, searchQuery]) + const filteredTools = useMemo(() => { if (!searchQuery.trim()) return tools const query = searchQuery.toLowerCase() return tools.filter((tool) => tool.name.toLowerCase().includes(query)) }, [tools, searchQuery]) - const filteredTemplates = useMemo(() => { - if (!searchQuery.trim()) return localTemplates.slice(0, 8) - const query = searchQuery.toLowerCase() - return localTemplates - .filter( - (template) => - template.title.toLowerCase().includes(query) || - template.description.toLowerCase().includes(query) - ) - .slice(0, 8) - }, [localTemplates, searchQuery]) - const filteredWorkflows = useMemo(() => { if (!searchQuery.trim()) return workflows const query = searchQuery.toLowerCase() @@ -282,6 +276,14 @@ export function SearchModal({ return workspaces.filter((workspace) => workspace.name.toLowerCase().includes(query)) }, [workspaces, searchQuery]) + const filteredKnowledgeBases = useMemo(() => { + if (!searchQuery.trim()) return knowledgeBases + const query = searchQuery.toLowerCase() + return knowledgeBases.filter( + (kb) => kb.name.toLowerCase().includes(query) || kb.description?.toLowerCase().includes(query) + ) + }, [knowledgeBases, searchQuery]) + const filteredPages = useMemo(() => { if (!searchQuery.trim()) return pages const query = searchQuery.toLowerCase() @@ -308,23 +310,23 @@ export function SearchModal({ }) } - if (filteredTools.length > 0) { + if (filteredTriggers.length > 0) { sections.push({ - id: 'tools', - name: 'Tools', + id: 'triggers', + name: 'Triggers', type: 'grid', - items: filteredTools, - gridCols: filteredTools.length, // Single row - all items in one row + items: filteredTriggers, + gridCols: filteredTriggers.length, // Single row - all items in one row }) } - if (filteredTemplates.length > 0) { + if (filteredTools.length > 0) { sections.push({ - id: 'templates', - name: 'Templates', + id: 'tools', + name: 'Tools', type: 'grid', - items: filteredTemplates, - gridCols: filteredTemplates.length, // Single row - all templates in one row + items: filteredTools, + gridCols: filteredTools.length, // Single row - all items in one row }) } @@ -332,6 +334,7 @@ export function SearchModal({ const listItems = [ ...filteredWorkspaces.map((item) => ({ type: 'workspace', data: item })), ...filteredWorkflows.map((item) => ({ type: 'workflow', data: item })), + ...filteredKnowledgeBases.map((item) => ({ type: 'knowledgebase', data: item })), ...filteredPages.map((item) => ({ type: 'page', data: item })), ...filteredDocs.map((item) => ({ type: 'doc', data: item })), ] @@ -348,10 +351,11 @@ export function SearchModal({ return sections }, [ filteredBlocks, + filteredTriggers, filteredTools, - filteredTemplates, filteredWorkspaces, filteredWorkflows, + filteredKnowledgeBases, filteredPages, filteredDocs, ]) @@ -463,23 +467,6 @@ export function SearchModal({ return () => window.removeEventListener('keydown', handleKeyDown) }, [open, handlePageClick, workspaceId]) - // Handle template usage callback (closes modal after template is used) - const handleTemplateUsed = useCallback(() => { - onOpenChange(false) - }, [onOpenChange]) - - // Handle star change callback from template card - const handleStarChange = useCallback( - (templateId: string, isStarred: boolean, newStarCount: number) => { - setLocalTemplates((prevTemplates) => - prevTemplates.map((template) => - template.id === templateId ? { ...template, isStarred, stars: newStarCount } : template - ) - ) - }, - [] - ) - // Handle item selection based on current item const handleItemSelection = useCallback(() => { const current = getCurrentItem() @@ -487,11 +474,8 @@ export function SearchModal({ const { section, item } = current - if (section.id === 'blocks' || section.id === 'tools') { + if (section.id === 'blocks' || section.id === 'triggers' || section.id === 'tools') { handleBlockClick(item.type) - } else if (section.id === 'templates') { - // Templates don't have direct selection, but we close the modal - onOpenChange(false) } else if (section.id === 'list') { switch (item.type) { case 'workspace': @@ -508,6 +492,13 @@ export function SearchModal({ handleNavigationClick(item.data.href) } break + case 'knowledgebase': + if (item.data.isCurrent) { + onOpenChange(false) + } else { + handleNavigationClick(item.data.href) + } + break case 'page': handlePageClick(item.data.href) break @@ -570,15 +561,6 @@ export function SearchModal({ [getCurrentItem] ) - // Render skeleton cards for loading state - const renderSkeletonCards = () => { - return Array.from({ length: 8 }).map((_, index) => ( -
- -
- )) - } - return ( @@ -654,26 +636,26 @@ export function SearchModal({ )} - {/* Tools Section */} - {filteredTools.length > 0 && ( + {/* Triggers Section */} + {filteredTriggers.length > 0 && (

- Tools + Triggers

{ - if (el) scrollRefs.current.set('tools', el) + if (el) scrollRefs.current.set('triggers', el) }} className='scrollbar-none flex gap-2 overflow-x-auto px-6 pb-1' style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }} > - {filteredTools.map((tool, index) => ( + {filteredTriggers.map((trigger, index) => ( @@ -700,45 +682,48 @@ export function SearchModal({
)} - {/* Templates Section */} - {(loading || filteredTemplates.length > 0) && ( + {/* Tools Section */} + {filteredTools.length > 0 && (

- Templates + Tools

{ - if (el) scrollRefs.current.set('templates', el) + if (el) scrollRefs.current.set('tools', el) }} - className='scrollbar-none flex gap-4 overflow-x-auto pr-6 pb-1 pl-6' + className='scrollbar-none flex gap-2 overflow-x-auto px-6 pb-1' style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }} > - {loading - ? renderSkeletonCards() - : filteredTemplates.map((template, index) => ( + {filteredTools.map((tool, index) => ( + + ))}
)} @@ -826,6 +811,43 @@ export function SearchModal({
)} + {/* Knowledge Bases */} + {filteredKnowledgeBases.length > 0 && ( +
+

+ Knowledge Bases +

+
+ {filteredKnowledgeBases.map((kb, kbIndex) => { + const globalIndex = + filteredWorkspaces.length + filteredWorkflows.length + kbIndex + return ( + + ) + })} +
+
+ )} + {/* Pages */} {filteredPages.length > 0 && (
@@ -835,7 +857,10 @@ export function SearchModal({
{filteredPages.map((page, pageIndex) => { const globalIndex = - filteredWorkspaces.length + filteredWorkflows.length + pageIndex + filteredWorkspaces.length + + filteredWorkflows.length + + filteredKnowledgeBases.length + + pageIndex return (
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx index 016c8bb2c0..50101af34c 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx @@ -32,6 +32,7 @@ import { getKeyboardShortcutText, useGlobalShortcuts, } from '@/app/workspace/[workspaceId]/w/hooks/use-keyboard-shortcuts' +import { useKnowledgeBasesList } from '@/hooks/use-knowledge' import { useSubscriptionStore } from '@/stores/subscription/store' import { useWorkflowDiffStore } from '@/stores/workflow-diff/store' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' @@ -115,6 +116,9 @@ export function Sidebar() { const [templates, setTemplates] = useState([]) const [isTemplatesLoading, setIsTemplatesLoading] = useState(false) + // Knowledge bases for search modal + const { knowledgeBases } = useKnowledgeBasesList(workspaceId) + // Refs const workflowScrollAreaRef = useRef(null) const workspaceIdRef = useRef(workspaceId) @@ -726,6 +730,17 @@ export function Sidebar() { })) }, [workspaces, workspaceId]) + // Prepare knowledge bases for search modal + const searchKnowledgeBases = useMemo(() => { + return knowledgeBases.map((kb) => ({ + id: kb.id, + name: kb.name, + description: kb.description, + href: `/workspace/${workspaceId}/knowledge/${kb.id}`, + isCurrent: knowledgeBaseId === kb.id, + })) + }, [knowledgeBases, workspaceId, knowledgeBaseId]) + // Create workflow handler const handleCreateWorkflow = async (folderId?: string): Promise => { if (isCreatingWorkflow) { @@ -1035,10 +1050,9 @@ export function Sidebar() { diff --git a/apps/sim/lib/env.ts b/apps/sim/lib/env.ts index baef2a9cfa..e03cac4313 100644 --- a/apps/sim/lib/env.ts +++ b/apps/sim/lib/env.ts @@ -17,6 +17,7 @@ export const env = createEnv({ server: { // Core Database & Authentication DATABASE_URL: z.string().url(), // Primary database connection string + DATABASE_SSL: z.boolean().optional(), // Enable SSL for database connections (defaults to false) BETTER_AUTH_URL: z.string().url(), // Base URL for Better Auth service BETTER_AUTH_SECRET: z.string().min(32), // Secret key for Better Auth JWT signing DISABLE_REGISTRATION: z.boolean().optional(), // Flag to disable new user registration diff --git a/apps/sim/socket-server/database/operations.ts b/apps/sim/socket-server/database/operations.ts index 99715cd8ba..64b4c06fe0 100644 --- a/apps/sim/socket-server/database/operations.ts +++ b/apps/sim/socket-server/database/operations.ts @@ -3,13 +3,15 @@ import { workflow, workflowBlocks, workflowEdges, workflowSubflows } from '@sim/ import { and, eq, or, sql } from 'drizzle-orm' import { drizzle } from 'drizzle-orm/postgres-js' import postgres from 'postgres' -import { env } from '@/lib/env' +import { env, isTruthy } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/db-helpers' const logger = createLogger('SocketDatabase') const connectionString = env.DATABASE_URL +const useSSL = env.DATABASE_SSL === undefined ? false : isTruthy(env.DATABASE_SSL) + const socketDb = drizzle( postgres(connectionString, { prepare: false, @@ -18,7 +20,7 @@ const socketDb = drizzle( max: 25, onnotice: () => {}, debug: false, - ssl: 'require', + ssl: useSSL ? 'require' : false, }), { schema } ) diff --git a/apps/sim/socket-server/rooms/manager.ts b/apps/sim/socket-server/rooms/manager.ts index feef1d1bc8..1c8d9630d4 100644 --- a/apps/sim/socket-server/rooms/manager.ts +++ b/apps/sim/socket-server/rooms/manager.ts @@ -4,10 +4,12 @@ import { and, eq, isNull } from 'drizzle-orm' import { drizzle } from 'drizzle-orm/postgres-js' import postgres from 'postgres' import type { Server } from 'socket.io' -import { env } from '@/lib/env' +import { env, isTruthy } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' const connectionString = env.DATABASE_URL +const useSSL = env.DATABASE_SSL === undefined ? false : isTruthy(env.DATABASE_SSL) + const db = drizzle( postgres(connectionString, { prepare: false, @@ -15,7 +17,7 @@ const db = drizzle( connect_timeout: 20, max: 5, onnotice: () => {}, - ssl: 'require', + ssl: useSSL ? 'require' : false, }), { schema } ) diff --git a/packages/db/index.ts b/packages/db/index.ts index 01f6e99a33..5a441a8108 100644 --- a/packages/db/index.ts +++ b/packages/db/index.ts @@ -10,13 +10,20 @@ if (!connectionString) { throw new Error('Missing DATABASE_URL environment variable') } +function isTruthy(value: string | undefined): boolean { + if (!value) return false + return value.toLowerCase() === 'true' || value === '1' +} + +const useSSL = process.env.DATABASE_SSL === undefined ? false : isTruthy(process.env.DATABASE_SSL) + const postgresClient = postgres(connectionString, { prepare: false, idle_timeout: 20, connect_timeout: 30, max: 80, onnotice: () => {}, - ssl: 'require', + ssl: useSSL ? 'require' : false, }) const drizzleClient = drizzle(postgresClient, { schema }) diff --git a/packages/db/scripts/migrate-deployment-versions.ts b/packages/db/scripts/migrate-deployment-versions.ts index f0d0453d67..54a15345a5 100644 --- a/packages/db/scripts/migrate-deployment-versions.ts +++ b/packages/db/scripts/migrate-deployment-versions.ts @@ -117,13 +117,20 @@ const workflowDeploymentVersion = pgTable( ) // ---------- DB client ---------- +function isTruthy(value: string | undefined): boolean { + if (!value) return false + return value.toLowerCase() === 'true' || value === '1' +} + +const useSSL = process.env.DATABASE_SSL === undefined ? false : isTruthy(process.env.DATABASE_SSL) + const postgresClient = postgres(CONNECTION_STRING, { prepare: false, idle_timeout: 20, connect_timeout: 30, max: 10, onnotice: () => {}, - ssl: 'require', + ssl: useSSL ? 'require' : false, }) const db = drizzle(postgresClient) diff --git a/packages/db/scripts/register-sso-provider.ts b/packages/db/scripts/register-sso-provider.ts index fbe04e939c..1eb585439c 100644 --- a/packages/db/scripts/register-sso-provider.ts +++ b/packages/db/scripts/register-sso-provider.ts @@ -140,14 +140,20 @@ if (!CONNECTION_STRING) { process.exit(1) } -// Initialize database connection (following migration script pattern) +function isTruthy(value: string | undefined): boolean { + if (!value) return false + return value.toLowerCase() === 'true' || value === '1' +} + +const useSSL = process.env.DATABASE_SSL === undefined ? false : isTruthy(process.env.DATABASE_SSL) + const postgresClient = postgres(CONNECTION_STRING, { prepare: false, idle_timeout: 20, connect_timeout: 30, max: 10, onnotice: () => {}, - ssl: 'require', + ssl: useSSL ? 'require' : false, }) const db = drizzle(postgresClient)