Skip to content

Commit 0fbf31b

Browse files
waleedlatif1Sg312claudePlaneInABottletest
authored
feat(admin): user search by email/ID, remove table border (#3646)
* fix(mothership): fix mothership file uploads (#3640) * Fix files * Fix * Fix * fix(workspace): prevent stale placeholder data from corrupting workflow registry on switch * feat(csp): allow chat UI to be embedded in iframes (#3643) * feat(csp): allow chat UI to be embedded in iframes Mirror the existing form embed CSP pattern for chat pages: add getChatEmbedCSPPolicy() with frame-ancestors *, configure /chat/:path* headers in next.config.ts without X-Frame-Options, and early-return in proxy.ts so chat routes skip the strict runtime CSP. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(csp): extract shared getEmbedCSPPolicy helper Deduplicate getChatEmbedCSPPolicy and getFormEmbedCSPPolicy into a shared private helper to prevent future divergence. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix(logs): add durable execution diagnostics foundation (#3564) * fix(logs): persist execution diagnostics markers Store last-started and last-completed block markers with finalization metadata so later read surfaces can explain how a run ended without reconstructing executor state. * fix(executor): preserve durable diagnostics ordering Await only the persistence needed to keep diagnostics durable before terminal completion while keeping callback failures from changing execution behavior. * fix(logs): preserve fallback diagnostics semantics Keep successful fallback output and accumulated cost intact while tightening progress-write draining and deduplicating trace span counting for diagnostics helpers. * fix(api): restore async execute route test mock Add the missing AuthType export to the hybrid auth mock so the async execution route test exercises the 202 queueing path instead of crashing with a 500 in CI. * fix(executor): align async block error handling * fix(logs): tighten marker ordering scope Allow same-millisecond marker writes to replace prior markers and drop the unused diagnostics read helper so this PR stays focused on persistence rather than unread foundation code. * fix(logs): remove unused finalization type guard Drop the unused helper so this PR only ships the persistence-side status types it actually uses. * fix(executor): await subflow diagnostics callbacks Ensure empty-subflow and subflow-error lifecycle callbacks participate in progress-write draining before terminal finalization while still swallowing callback failures. --------- Co-authored-by: test <test@example.com> Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai> * feat(admin): add user search by email and ID, remove table border - Replace Load Users button with a live search input; query fires on any input - Email search uses listUsers with contains operator - User ID search (UUID format) uses admin.getUser directly for exact lookup - Remove outer border on user table that rendered white in dark mode - Reset pagination to page 0 on new search Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(admin): replace live search with explicit search button - Split searchInput (controlled input) from searchQuery (committed value) so the hook only fires on Search click or Enter, not every keystroke - Gate table render on searchQuery.length > 0 to prevent stale results showing after input is cleared Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Siddharth Ganesan <33737564+Sg312@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: PlaneInABottle <y.mirza.altay@gmail.com> Co-authored-by: test <test@example.com> Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
1 parent 4f3bc37 commit 0fbf31b

File tree

23 files changed

+1365
-271
lines changed

23 files changed

+1365
-271
lines changed

apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ function toDisplayAttachment(f: TaskStoredFileAttachment): ChatMessageAttachment
132132
media_type: f.media_type,
133133
size: f.size,
134134
previewUrl: f.media_type.startsWith('image/')
135-
? `/api/files/serve/${encodeURIComponent(f.key)}?context=copilot`
135+
? `/api/files/serve/${encodeURIComponent(f.key)}?context=mothership`
136136
: undefined,
137137
}
138138
}

apps/sim/app/workspace/[workspaceId]/settings/components/admin/admin.tsx

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,21 @@ export function Admin() {
3131

3232
const [workflowId, setWorkflowId] = useState('')
3333
const [usersOffset, setUsersOffset] = useState(0)
34-
const [usersEnabled, setUsersEnabled] = useState(false)
34+
const [searchInput, setSearchInput] = useState('')
35+
const [searchQuery, setSearchQuery] = useState('')
3536
const [banUserId, setBanUserId] = useState<string | null>(null)
3637
const [banReason, setBanReason] = useState('')
3738

3839
const {
3940
data: usersData,
4041
isLoading: usersLoading,
4142
error: usersError,
42-
refetch: refetchUsers,
43-
} = useAdminUsers(usersOffset, PAGE_SIZE, usersEnabled)
43+
} = useAdminUsers(usersOffset, PAGE_SIZE, searchQuery)
44+
45+
const handleSearch = () => {
46+
setUsersOffset(0)
47+
setSearchQuery(searchInput.trim())
48+
}
4449

4550
const totalPages = useMemo(
4651
() => Math.ceil((usersData?.total ?? 0) / PAGE_SIZE),
@@ -62,14 +67,6 @@ export function Admin() {
6267
)
6368
}
6469

65-
const handleLoadUsers = () => {
66-
if (usersEnabled) {
67-
refetchUsers()
68-
} else {
69-
setUsersEnabled(true)
70-
}
71-
}
72-
7370
const pendingUserIds = useMemo(() => {
7471
const ids = new Set<string>()
7572
if (setUserRole.isPending && (setUserRole.variables as { userId?: string })?.userId)
@@ -136,10 +133,16 @@ export function Admin() {
136133
<div className='h-px bg-[var(--border-secondary)]' />
137134

138135
<div className='flex flex-col gap-[12px]'>
139-
<div className='flex items-center justify-between'>
140-
<p className='font-medium text-[14px] text-[var(--text-primary)]'>User Management</p>
141-
<Button variant='active' onClick={handleLoadUsers} disabled={usersLoading}>
142-
{usersLoading ? 'Loading...' : usersEnabled ? 'Refresh' : 'Load Users'}
136+
<p className='font-medium text-[14px] text-[var(--text-primary)]'>User Management</p>
137+
<div className='flex gap-[8px]'>
138+
<EmcnInput
139+
value={searchInput}
140+
onChange={(e) => setSearchInput(e.target.value)}
141+
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
142+
placeholder='Search by email or paste a user ID...'
143+
/>
144+
<Button variant='primary' onClick={handleSearch} disabled={usersLoading}>
145+
{usersLoading ? 'Searching...' : 'Search'}
143146
</Button>
144147
</div>
145148

@@ -164,9 +167,9 @@ export function Admin() {
164167
</div>
165168
)}
166169

167-
{usersData && (
170+
{searchQuery.length > 0 && usersData && (
168171
<>
169-
<div className='flex flex-col gap-[2px] rounded-[8px] border border-[var(--border-secondary)]'>
172+
<div className='flex flex-col gap-[2px]'>
170173
<div className='flex items-center gap-[12px] border-[var(--border-secondary)] border-b px-[12px] py-[8px] text-[12px] text-[var(--text-tertiary)]'>
171174
<span className='w-[200px]'>Name</span>
172175
<span className='flex-1'>Email</span>
@@ -176,7 +179,7 @@ export function Admin() {
176179
</div>
177180

178181
{usersData.users.length === 0 && (
179-
<div className='px-[12px] py-[16px] text-center text-[13px] text-[var(--text-tertiary)]'>
182+
<div className='py-[16px] text-center text-[13px] text-[var(--text-tertiary)]'>
180183
No users found.
181184
</div>
182185
)}

apps/sim/executor/execution/block-executor.ts

Lines changed: 51 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export class BlockExecutor {
7777
if (!isSentinel) {
7878
blockLog = this.createBlockLog(ctx, node.id, block, node)
7979
ctx.blockLogs.push(blockLog)
80-
this.callOnBlockStart(ctx, node, block, blockLog.executionOrder)
80+
await this.callOnBlockStart(ctx, node, block, blockLog.executionOrder)
8181
}
8282

8383
const startTime = performance.now()
@@ -105,7 +105,7 @@ export class BlockExecutor {
105105
}
106106
} catch (error) {
107107
cleanupSelfReference?.()
108-
return this.handleBlockError(
108+
return await this.handleBlockError(
109109
error,
110110
ctx,
111111
node,
@@ -179,7 +179,7 @@ export class BlockExecutor {
179179
const displayOutput = filterOutputForLog(block.metadata?.id || '', normalizedOutput, {
180180
block,
181181
})
182-
this.callOnBlockComplete(
182+
await this.callOnBlockComplete(
183183
ctx,
184184
node,
185185
block,
@@ -195,7 +195,7 @@ export class BlockExecutor {
195195

196196
return normalizedOutput
197197
} catch (error) {
198-
return this.handleBlockError(
198+
return await this.handleBlockError(
199199
error,
200200
ctx,
201201
node,
@@ -226,7 +226,7 @@ export class BlockExecutor {
226226
return this.blockHandlers.find((h) => h.canHandle(block))
227227
}
228228

229-
private handleBlockError(
229+
private async handleBlockError(
230230
error: unknown,
231231
ctx: ExecutionContext,
232232
node: DAGNode,
@@ -236,7 +236,7 @@ export class BlockExecutor {
236236
resolvedInputs: Record<string, any>,
237237
isSentinel: boolean,
238238
phase: 'input_resolution' | 'execution'
239-
): NormalizedBlockOutput {
239+
): Promise<NormalizedBlockOutput> {
240240
const duration = performance.now() - startTime
241241
const errorMessage = normalizeError(error)
242242
const hasResolvedInputs =
@@ -287,7 +287,7 @@ export class BlockExecutor {
287287
? error.childWorkflowInstanceId
288288
: undefined
289289
const displayOutput = filterOutputForLog(block.metadata?.id || '', errorOutput, { block })
290-
this.callOnBlockComplete(
290+
await this.callOnBlockComplete(
291291
ctx,
292292
node,
293293
block,
@@ -439,31 +439,39 @@ export class BlockExecutor {
439439
return redactApiKeys(result)
440440
}
441441

442-
private callOnBlockStart(
442+
private async callOnBlockStart(
443443
ctx: ExecutionContext,
444444
node: DAGNode,
445445
block: SerializedBlock,
446446
executionOrder: number
447-
): void {
447+
): Promise<void> {
448448
const blockId = node.metadata?.originalBlockId ?? node.id
449449
const blockName = block.metadata?.name ?? blockId
450450
const blockType = block.metadata?.id ?? DEFAULTS.BLOCK_TYPE
451451

452452
const iterationContext = getIterationContext(ctx, node?.metadata)
453453

454454
if (this.contextExtensions.onBlockStart) {
455-
this.contextExtensions.onBlockStart(
456-
blockId,
457-
blockName,
458-
blockType,
459-
executionOrder,
460-
iterationContext,
461-
ctx.childWorkflowContext
462-
)
455+
try {
456+
await this.contextExtensions.onBlockStart(
457+
blockId,
458+
blockName,
459+
blockType,
460+
executionOrder,
461+
iterationContext,
462+
ctx.childWorkflowContext
463+
)
464+
} catch (error) {
465+
logger.warn('Block start callback failed', {
466+
blockId,
467+
blockType,
468+
error: error instanceof Error ? error.message : String(error),
469+
})
470+
}
463471
}
464472
}
465473

466-
private callOnBlockComplete(
474+
private async callOnBlockComplete(
467475
ctx: ExecutionContext,
468476
node: DAGNode,
469477
block: SerializedBlock,
@@ -474,30 +482,38 @@ export class BlockExecutor {
474482
executionOrder: number,
475483
endedAt: string,
476484
childWorkflowInstanceId?: string
477-
): void {
485+
): Promise<void> {
478486
const blockId = node.metadata?.originalBlockId ?? node.id
479487
const blockName = block.metadata?.name ?? blockId
480488
const blockType = block.metadata?.id ?? DEFAULTS.BLOCK_TYPE
481489

482490
const iterationContext = getIterationContext(ctx, node?.metadata)
483491

484492
if (this.contextExtensions.onBlockComplete) {
485-
this.contextExtensions.onBlockComplete(
486-
blockId,
487-
blockName,
488-
blockType,
489-
{
490-
input,
491-
output,
492-
executionTime: duration,
493-
startedAt,
494-
executionOrder,
495-
endedAt,
496-
childWorkflowInstanceId,
497-
},
498-
iterationContext,
499-
ctx.childWorkflowContext
500-
)
493+
try {
494+
await this.contextExtensions.onBlockComplete(
495+
blockId,
496+
blockName,
497+
blockType,
498+
{
499+
input,
500+
output,
501+
executionTime: duration,
502+
startedAt,
503+
executionOrder,
504+
endedAt,
505+
childWorkflowInstanceId,
506+
},
507+
iterationContext,
508+
ctx.childWorkflowContext
509+
)
510+
} catch (error) {
511+
logger.warn('Block completion callback failed', {
512+
blockId,
513+
blockType,
514+
error: error instanceof Error ? error.message : String(error),
515+
})
516+
}
501517
}
502518
}
503519

0 commit comments

Comments
 (0)