Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 0 additions & 32 deletions apps/sim/app/_styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -666,39 +666,7 @@ input[type="search"]::-ms-clear {
}
}

/**
* Notification toast enter animation — pop-open with stack offset
*/
@keyframes notification-enter {
from {
opacity: 0;
transform: translateX(calc(var(--stack-offset, 0px) - 8px)) scale(0.97);
}
to {
opacity: 1;
transform: translateX(var(--stack-offset, 0px)) scale(1);
}
}

@keyframes notification-countdown {
from {
stroke-dashoffset: 0;
}
to {
stroke-dashoffset: 34.56;
}
}

@keyframes notification-exit {
from {
opacity: 1;
transform: translateX(var(--stack-offset, 0px)) scale(1);
}
to {
opacity: 0;
transform: translateX(calc(var(--stack-offset, 0px) + 8px)) scale(0.97);
}
}

/**
* @depricated
Expand Down
61 changes: 61 additions & 0 deletions apps/sim/app/api/knowledge/[id]/restore/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { db } from '@sim/db'
import { knowledgeBase } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request'
import { restoreKnowledgeBase } from '@/lib/knowledge/service'
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'

const logger = createLogger('RestoreKnowledgeBaseAPI')

export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const requestId = generateRequestId()
const { id } = await params

try {
const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}

const [kb] = await db
.select({
id: knowledgeBase.id,
workspaceId: knowledgeBase.workspaceId,
userId: knowledgeBase.userId,
})
.from(knowledgeBase)
.where(eq(knowledgeBase.id, id))
.limit(1)

if (!kb) {
return NextResponse.json({ error: 'Knowledge base not found' }, { status: 404 })
}

if (kb.workspaceId) {
const permission = await getUserEntityPermissions(auth.userId, 'workspace', kb.workspaceId)
if (permission !== 'admin' && permission !== 'write') {
return NextResponse.json({ error: 'Insufficient permissions' }, { status: 403 })
}
} else if (kb.userId !== auth.userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}

await restoreKnowledgeBase(id, requestId)

logger.info(`[${requestId}] Restored knowledge base ${id}`)

return NextResponse.json({ success: true })
} catch (error) {
logger.error(`[${requestId}] Error restoring knowledge base ${id}`, error)
return NextResponse.json(
{ error: error instanceof Error ? error.message : 'Internal server error' },
{ status: 500 }
)
}
}
45 changes: 45 additions & 0 deletions apps/sim/app/api/table/[tableId]/restore/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request'
import { getTableById, restoreTable } from '@/lib/table'
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'

const logger = createLogger('RestoreTableAPI')

export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ tableId: string }> }
) {
const requestId = generateRequestId()
const { tableId } = await params

try {
const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: 'Authentication required' }, { status: 401 })
}

const table = await getTableById(tableId, { includeArchived: true })
if (!table) {
return NextResponse.json({ error: 'Table not found' }, { status: 404 })
}

const permission = await getUserEntityPermissions(auth.userId, 'workspace', table.workspaceId)
if (permission !== 'admin' && permission !== 'write') {
return NextResponse.json({ error: 'Insufficient permissions' }, { status: 403 })
}

await restoreTable(tableId, requestId)

logger.info(`[${requestId}] Restored table ${tableId}`)

return NextResponse.json({ success: true })
} catch (error) {
logger.error(`[${requestId}] Error restoring table ${tableId}`, error)
return NextResponse.json(
{ error: error instanceof Error ? error.message : 'Internal server error' },
{ status: 500 }
)
}
}
55 changes: 55 additions & 0 deletions apps/sim/app/api/workflows/[id]/restore/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request'
import { restoreWorkflow } from '@/lib/workflows/lifecycle'
import { getWorkflowById } from '@/lib/workflows/utils'
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'

const logger = createLogger('RestoreWorkflowAPI')

export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const requestId = generateRequestId()
const { id: workflowId } = await params

try {
const auth = await checkSessionOrInternalAuth(request, { requireWorkflowId: false })
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}

const workflowData = await getWorkflowById(workflowId, { includeArchived: true })
if (!workflowData) {
return NextResponse.json({ error: 'Workflow not found' }, { status: 404 })
}

if (workflowData.workspaceId) {
const permission = await getUserEntityPermissions(
auth.userId,
'workspace',
workflowData.workspaceId
)
if (permission !== 'admin' && permission !== 'write') {
return NextResponse.json({ error: 'Insufficient permissions' }, { status: 403 })
}
} else if (workflowData.userId !== auth.userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}

const result = await restoreWorkflow(workflowId, { requestId })

if (!result.restored) {
return NextResponse.json({ error: 'Workflow is not archived' }, { status: 400 })
}

logger.info(`[${requestId}] Restored workflow ${workflowId}`)

return NextResponse.json({ success: true })
} catch (error) {
logger.error(`[${requestId}] Error restoring workflow ${workflowId}`, error)
return NextResponse.json(
{ error: error instanceof Error ? error.message : 'Internal server error' },
{ status: 500 }
)
}
}
40 changes: 40 additions & 0 deletions apps/sim/app/api/workspaces/[id]/files/[fileId]/restore/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { generateRequestId } from '@/lib/core/utils/request'
import { restoreWorkspaceFile } from '@/lib/uploads/contexts/workspace'
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'

const logger = createLogger('RestoreWorkspaceFileAPI')

export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ id: string; fileId: string }> }
) {
const requestId = generateRequestId()
const { id: workspaceId, fileId } = await params

try {
const session = await getSession()
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}

const userPermission = await getUserEntityPermissions(session.user.id, 'workspace', workspaceId)
if (userPermission !== 'admin' && userPermission !== 'write') {
return NextResponse.json({ error: 'Insufficient permissions' }, { status: 403 })
}

await restoreWorkspaceFile(workspaceId, fileId)

logger.info(`[${requestId}] Restored workspace file ${fileId}`)

return NextResponse.json({ success: true })
} catch (error) {
logger.error(`[${requestId}] Error restoring workspace file ${fileId}`, error)
return NextResponse.json(
{ error: error instanceof Error ? error.message : 'Internal server error' },
{ status: 500 }
)
}
}
4 changes: 3 additions & 1 deletion apps/sim/app/workspace/[workspaceId]/files/files.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -792,7 +792,9 @@ function DeleteConfirmModal({
<p className='text-[var(--text-secondary)]'>
Are you sure you want to delete{' '}
<span className='font-medium text-[var(--text-primary)]'>{fileName}</span>?{' '}
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
<span className='text-[var(--text-tertiary)]'>
You can restore it from Recently Deleted in Settings.
</span>
</p>
</ModalBody>
<ModalFooter>
Expand Down
8 changes: 5 additions & 3 deletions apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1105,9 +1105,11 @@ export function KnowledgeBase({
<p className='text-[var(--text-secondary)]'>
Are you sure you want to delete{' '}
<span className='font-medium text-[var(--text-primary)]'>{knowledgeBaseName}</span>?
This will permanently delete the knowledge base and all {pagination.total} document
{pagination.total === 1 ? '' : 's'} within it.{' '}
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
The knowledge base and all {pagination.total} document
{pagination.total === 1 ? '' : 's'} within it will be removed.{' '}
<span className='text-[var(--text-tertiary)]'>
You can restore it from Recently Deleted in Settings.
</span>
</p>
</ModalBody>
<ModalFooter>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,14 @@ export function DeleteKnowledgeBaseModal({
<>
Are you sure you want to delete{' '}
<span className='font-medium text-[var(--text-primary)]'>{knowledgeBaseName}</span>?
This will permanently remove all associated documents, chunks, and embeddings.
All associated documents, chunks, and embeddings will be removed.
</>
) : (
'Are you sure you want to delete this knowledge base? This will permanently remove all associated documents, chunks, and embeddings.'
'Are you sure you want to delete this knowledge base? All associated documents, chunks, and embeddings will be removed.'
)}{' '}
<span className='text-[var(--text-error)]'>This action cannot be undone.</span>
<span className='text-[var(--text-tertiary)]'>
You can restore it from Recently Deleted in Settings.
</span>
</p>
</ModalBody>
<ModalFooter>
Expand Down
5 changes: 3 additions & 2 deletions apps/sim/app/workspace/[workspaceId]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client'

import { ToastProvider } from '@/components/emcn'
import { GlobalCommandsProvider } from '@/app/workspace/[workspaceId]/providers/global-commands-provider'
import { ProviderModelsLoader } from '@/app/workspace/[workspaceId]/providers/provider-models-loader'
import { SettingsLoader } from '@/app/workspace/[workspaceId]/providers/settings-loader'
Expand All @@ -8,7 +9,7 @@ import { Sidebar } from '@/app/workspace/[workspaceId]/w/components/sidebar/side

export default function WorkspaceLayout({ children }: { children: React.ReactNode }) {
return (
<>
<ToastProvider>
<SettingsLoader />
<ProviderModelsLoader />
<GlobalCommandsProvider>
Expand All @@ -25,6 +26,6 @@ export default function WorkspaceLayout({ children }: { children: React.ReactNod
</WorkspacePermissionsProvider>
</div>
</GlobalCommandsProvider>
</>
</ToastProvider>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const SECTION_TITLES: Record<string, string> = {
skills: 'Skills',
'workflow-mcp-servers': 'MCP Servers',
'credential-sets': 'Email Polling',
'recently-deleted': 'Recently Deleted',
debug: 'Debug',
} as const

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,13 @@ const Debug = dynamic(
import('@/app/workspace/[workspaceId]/settings/components/debug/debug').then((m) => m.Debug),
{ loading: () => <DebugSkeleton /> }
)
const RecentlyDeleted = dynamic(
() =>
import(
'@/app/workspace/[workspaceId]/settings/components/recently-deleted/recently-deleted'
).then((m) => m.RecentlyDeleted),
{ loading: () => <SettingsSectionSkeleton /> }
)
const AccessControl = dynamic(
() => import('@/ee/access-control/components/access-control').then((m) => m.AccessControl),
{ loading: () => <SettingsSectionSkeleton /> }
Expand All @@ -160,24 +167,25 @@ export function SettingsPage({ section }: SettingsPageProps) {
return (
<div>
<h2 className='mb-[28px] font-medium text-[22px] text-[var(--text-primary)]'>{label}</h2>
{effectiveSection === 'general' && <General />}
{effectiveSection === 'integrations' && <Integrations />}
{effectiveSection === 'secrets' && <Credentials />}
{effectiveSection === 'template-profile' && <TemplateProfile />}
{effectiveSection === 'credential-sets' && <CredentialSets />}
{effectiveSection === 'access-control' && <AccessControl />}
{effectiveSection === 'apikeys' && <ApiKeys />}
{isBillingEnabled && effectiveSection === 'subscription' && <Subscription />}
{isBillingEnabled && effectiveSection === 'team' && <TeamManagement />}
{effectiveSection === 'sso' && <SSO />}
{effectiveSection === 'byok' && <BYOK />}
{effectiveSection === 'copilot' && <Copilot />}
{effectiveSection === 'mcp' && <MCP initialServerId={mcpServerId} />}
{effectiveSection === 'custom-tools' && <CustomTools />}
{effectiveSection === 'skills' && <Skills />}
{effectiveSection === 'workflow-mcp-servers' && <WorkflowMcpServers />}
{effectiveSection === 'inbox' && <Inbox />}
{effectiveSection === 'debug' && <Debug />}
{effectiveSection === 'general' && <General />}
{effectiveSection === 'integrations' && <Integrations />}
{effectiveSection === 'secrets' && <Credentials />}
{effectiveSection === 'template-profile' && <TemplateProfile />}
{effectiveSection === 'credential-sets' && <CredentialSets />}
{effectiveSection === 'access-control' && <AccessControl />}
{effectiveSection === 'apikeys' && <ApiKeys />}
{isBillingEnabled && effectiveSection === 'subscription' && <Subscription />}
{isBillingEnabled && effectiveSection === 'team' && <TeamManagement />}
{effectiveSection === 'sso' && <SSO />}
{effectiveSection === 'byok' && <BYOK />}
{effectiveSection === 'copilot' && <Copilot />}
{effectiveSection === 'mcp' && <MCP initialServerId={mcpServerId} />}
{effectiveSection === 'custom-tools' && <CustomTools />}
{effectiveSection === 'skills' && <Skills />}
{effectiveSection === 'workflow-mcp-servers' && <WorkflowMcpServers />}
{effectiveSection === 'inbox' && <Inbox />}
{effectiveSection === 'recently-deleted' && <RecentlyDeleted />}
{effectiveSection === 'debug' && <Debug />}
</div>
)
}
Loading