Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
149c9b5
feat: add sandbox events page and table component
sarimrmalik Apr 20, 2026
7f418d1
feat: enhance SandboxEventsTable with sorting and empty state
sarimrmalik Apr 20, 2026
7c88ab3
feat: enhance timestamp formatting in SandboxEventsTable
sarimrmalik Apr 21, 2026
147f5f7
refactor: simplify EventTypeCell layout in SandboxEventsTable
sarimrmalik Apr 21, 2026
8ea55bf
feat: add lifecycle event filtering and display in SandboxEvents
sarimrmalik Apr 21, 2026
78e9fee
style: update cell alignment and styling in SandboxEventsTable
sarimrmalik Apr 21, 2026
e455d68
refactor: replace DataTable with Table components in SandboxEventsTable
sarimrmalik Apr 21, 2026
2e3ecef
feat: introduce IdBadge component for improved event ID display in Sa…
sarimrmalik Apr 21, 2026
600ed23
style: enhance header cell styling in SandboxEventsTable
sarimrmalik Apr 21, 2026
82e8bd2
refactor: remove listSandboxLifecycleEvents method and related query …
sarimrmalik Apr 23, 2026
9c60048
refactor: simplify SandboxEventTypeBadge and adjust table cell styles
sarimrmalik Apr 23, 2026
e859f5e
Merge remote-tracking branch 'origin/main' into feat/sandbox-details-…
sarimrmalik Apr 23, 2026
e68d611
refactor: enhance SandboxEventTypeBadge and update EventTypeFilter st…
sarimrmalik Apr 23, 2026
a4c92d5
refactor: update event type filter and table styling
sarimrmalik Apr 23, 2026
ae0d5f4
refactor: simplify EventDetailsCell and enhance JSON display
sarimrmalik Apr 23, 2026
5935c98
Run biome format
sarimrmalik Apr 23, 2026
7e9f3c1
refactor: remove 'types' parameter from API specifications and relate…
sarimrmalik Apr 23, 2026
0480f6f
refactor: streamline sandbox lifecycle event handling
sarimrmalik Apr 23, 2026
d1ef298
Rollback pagination changes
sarimrmalik Apr 23, 2026
30781d7
refactor: rename sandbox event data schema for consistency
sarimrmalik Apr 23, 2026
6451a77
Run biome format
sarimrmalik Apr 23, 2026
21236c2
refactor: simplify SandboxEventsTable and remove unused components
sarimrmalik Apr 28, 2026
1615cab
Merge remote-tracking branch 'origin/main' into feat/sandbox-details-…
sarimrmalik Apr 29, 2026
f569522
refactor: update ID badge component to use Button instead of IconButton
sarimrmalik Apr 29, 2026
cc323b8
Remove default exports
sarimrmalik Apr 29, 2026
fec5e28
Merge remote-tracking branch 'origin/main' into feat/sandbox-details-…
sarimrmalik May 4, 2026
5f46012
refactor: enhance event type badge and table components
sarimrmalik May 4, 2026
1df7039
refactor: simplify EventTypeFilter component structure
sarimrmalik May 4, 2026
a1e1a38
Merge branch 'main' into feat/sandbox-details-events-table
ben-fornefeld May 5, 2026
cb125a8
Refactor: centralize IdBadge component usage
sarimrmalik May 5, 2026
4661bbe
feat: enable client-side rendering for event type filter
sarimrmalik May 5, 2026
7040cb9
fix: improve event type parsing in EventTypeFilter component
sarimrmalik May 5, 2026
12cb290
refactor: update column styles in SandboxEventsTable
sarimrmalik May 5, 2026
f7b502c
refactor: replace button with Button component in SandboxEventsTable
sarimrmalik May 5, 2026
891ad0a
refactor: simplify empty state rendering in SandboxEventsTable
sarimrmalik May 5, 2026
a01c7cc
refactor: enhance event type parsing in useSandboxEventFilters
sarimrmalik May 5, 2026
37879bd
refactor: add comment for event ordering in SandboxEventsView
sarimrmalik May 5, 2026
fc342ad
refactor: replace WEBHOOK_EVENTS with SandboxLifecycleEventTypeSchema…
sarimrmalik May 5, 2026
a0d5a1e
refactor: replace log rendering components with virtualized alternatives
sarimrmalik May 7, 2026
a1d4de2
refactor: remove outdated comment in virtualized table UI
sarimrmalik May 7, 2026
c5b8778
Remove unused fragment
sarimrmalik May 7, 2026
9b433da
refactor: enhance sandbox lifecycle event handling and filtering
sarimrmalik May 8, 2026
dd645a7
refactor: enhance SandboxEventsTable layout and data display
sarimrmalik May 8, 2026
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { SandboxEventsView } from '@/features/dashboard/sandbox/events'

export default function SandboxEventsPage() {
return <SandboxEventsView />
}
2 changes: 2 additions & 0 deletions src/configs/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export const PROTECTED_URLS = {
`/dashboard/${teamSlug}/sandboxes/${sandboxId}/monitoring`,
SANDBOX_MONITORING: (teamSlug: string, sandboxId: string) =>
`/dashboard/${teamSlug}/sandboxes/${sandboxId}/monitoring`,
SANDBOX_EVENTS: (teamSlug: string, sandboxId: string) =>
`/dashboard/${teamSlug}/sandboxes/${sandboxId}/events`,
SANDBOX_LOGS: (teamSlug: string, sandboxId: string) =>
`/dashboard/${teamSlug}/sandboxes/${sandboxId}/logs`,
SANDBOX_FILESYSTEM: (teamSlug: string, sandboxId: string) =>
Expand Down
19 changes: 19 additions & 0 deletions src/core/modules/sandboxes/lifecycle-event-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { z } from 'zod'

const SANDBOX_LIFECYCLE_EVENT_TYPE_PREFIX = 'sandbox.lifecycle.'

const SandboxLifecycleEventTypeSchema = z.enum([
Comment thread
sarimrmalik marked this conversation as resolved.
`${SANDBOX_LIFECYCLE_EVENT_TYPE_PREFIX}created`,
`${SANDBOX_LIFECYCLE_EVENT_TYPE_PREFIX}updated`,
`${SANDBOX_LIFECYCLE_EVENT_TYPE_PREFIX}paused`,
`${SANDBOX_LIFECYCLE_EVENT_TYPE_PREFIX}resumed`,
`${SANDBOX_LIFECYCLE_EVENT_TYPE_PREFIX}killed`,
])

type SandboxLifecycleEventType = z.infer<typeof SandboxLifecycleEventTypeSchema>

export {
SANDBOX_LIFECYCLE_EVENT_TYPE_PREFIX,
SandboxLifecycleEventTypeSchema,
type SandboxLifecycleEventType,
}
22 changes: 12 additions & 10 deletions src/features/dashboard/build/logs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ import { LogLevelFilter } from '@/features/dashboard/common/log-level-filter'
import {
LogStatusCell,
LogsEmptyBody,
LogsLoaderBody,
LogsTableHeader,
LogVirtualRow,
} from '@/features/dashboard/common/log-viewer-ui'
import {
VirtualizedTableLoaderBody,
VirtualizedTableRow,
} from '@/features/dashboard/common/virtualized-table-ui'
import { cn } from '@/lib/utils'
import { Loader } from '@/ui/primitives/loader'
import { Table, TableBody, TableCell } from '@/ui/primitives/table'
Expand Down Expand Up @@ -70,7 +72,7 @@ export default function Logs({
levelWidth={COLUMN_WIDTHS_PX.level}
timestampSortDirection="asc"
/>
<LogsLoaderBody />
<VirtualizedTableLoaderBody />
</Table>
</div>
</div>
Expand Down Expand Up @@ -172,7 +174,7 @@ function LogsContent({
timestampSortDirection="asc"
/>

{showLoader && <LogsLoaderBody />}
{showLoader && <VirtualizedTableLoaderBody />}
{showEmpty && (
<EmptyBody hasRetainedLogs={buildDetails.hasRetainedLogs} />
)}
Expand Down Expand Up @@ -514,7 +516,7 @@ function LogRow({
const millisAfterStart = log.timestampUnix - startedAt

return (
<LogVirtualRow
<VirtualizedTableRow
virtualRow={virtualRow}
virtualizer={virtualizer}
height={ROW_HEIGHT_PX}
Expand Down Expand Up @@ -551,7 +553,7 @@ function LogRow({
>
<Message message={log.message} />
</TableCell>
</LogVirtualRow>
</VirtualizedTableRow>
)
}

Expand All @@ -567,7 +569,7 @@ function StatusRow({
isFetchingNextPage,
}: StatusRowProps) {
return (
<LogVirtualRow
<VirtualizedTableRow
virtualRow={virtualRow}
virtualizer={virtualizer}
height={ROW_HEIGHT_PX}
Expand All @@ -587,7 +589,7 @@ function StatusRow({
)}
</span>
</LogStatusCell>
</LogVirtualRow>
</VirtualizedTableRow>
)
}

Expand All @@ -603,7 +605,7 @@ function LiveStatusRow({
isBuilding,
}: LiveStatusRowProps) {
return (
<LogVirtualRow
<VirtualizedTableRow
virtualRow={virtualRow}
virtualizer={virtualizer}
height={LIVE_STATUS_ROW_HEIGHT_PX}
Expand All @@ -628,6 +630,6 @@ function LiveStatusRow({
</span>
</span>
</LogStatusCell>
</LogVirtualRow>
</VirtualizedTableRow>
)
}
57 changes: 0 additions & 57 deletions src/features/dashboard/common/log-viewer-ui.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import type { VirtualItem, Virtualizer } from '@tanstack/react-virtual'
import type { CSSProperties, ReactNode } from 'react'
import { cn } from '@/lib/utils'
import { ArrowDownIcon, ListIcon } from '@/ui/primitives/icons'
import { Loader } from '@/ui/primitives/loader'
import {
TableBody,
TableCell,
Expand Down Expand Up @@ -57,20 +55,6 @@ export function LogsTableHeader({
)
}

export function LogsLoaderBody() {
return (
<TableBody style={{ display: 'grid' }}>
<TableRow style={{ display: 'flex', minWidth: '100%', marginTop: 8 }}>
<TableCell className="flex-1">
<div className="h-[35svh] w-full flex justify-center items-center">
<Loader variant="slash" size="lg" />
</div>
</TableCell>
</TableRow>
</TableBody>
)
}

interface LogsEmptyBodyProps {
description?: ReactNode
}
Expand All @@ -95,47 +79,6 @@ export function LogsEmptyBody({ description }: LogsEmptyBodyProps) {
)
}

export function getLogVirtualRowStyle(
virtualRow: VirtualItem,
height: number
): CSSProperties {
return {
display: 'flex',
position: 'absolute',
left: 0,
transform: `translateY(${virtualRow.start}px)`,
minWidth: '100%',
height,
}
}

interface LogVirtualRowProps {
virtualRow: VirtualItem
virtualizer: Virtualizer<HTMLDivElement, Element>
height: number
className?: string
children: ReactNode
}

export function LogVirtualRow({
virtualRow,
virtualizer,
height,
className,
children,
}: LogVirtualRowProps) {
return (
<TableRow
data-index={virtualRow.index}
ref={(node) => virtualizer.measureElement(node)}
className={className}
style={getLogVirtualRowStyle(virtualRow, height)}
>
{children}
</TableRow>
)
}

const STATUS_ROW_CELL_STYLE: CSSProperties = {
display: 'flex',
alignItems: 'center',
Expand Down
53 changes: 53 additions & 0 deletions src/features/dashboard/common/virtualized-table-ui.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { VirtualItem, Virtualizer } from '@tanstack/react-virtual'
import type { CSSProperties, ReactNode } from 'react'
import { Loader } from '@/ui/primitives/loader'
import { TableBody, TableCell, TableRow } from '@/ui/primitives/table'

export const getVirtualizedRowStyle = (
virtualRow: VirtualItem,
height: number
): CSSProperties => ({
display: 'flex',
position: 'absolute',
left: 0,
transform: `translateY(${virtualRow.start}px)`,
minWidth: '100%',
height,
})

interface VirtualizedTableRowProps {
virtualRow: VirtualItem
virtualizer: Virtualizer<HTMLDivElement, Element>
height: number
className?: string
children: ReactNode
}

export const VirtualizedTableRow = ({
virtualRow,
virtualizer,
height,
className,
children,
}: VirtualizedTableRowProps) => (
<TableRow
data-index={virtualRow.index}
ref={(node) => virtualizer.measureElement(node)}
className={className}
style={getVirtualizedRowStyle(virtualRow, height)}
>
{children}
</TableRow>
)

export const VirtualizedTableLoaderBody = () => (
<TableBody className="grid">
<TableRow className="flex min-w-full mt-2">
<TableCell className="flex-1">
<div className="h-[35svh] w-full flex justify-center items-center">
<Loader variant="slash" size="lg" />
</div>
</TableCell>
</TableRow>
</TableBody>
)
24 changes: 24 additions & 0 deletions src/features/dashboard/sandbox/events/event-type-badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { SandboxLifecycleEventTypeSchema } from '@/core/modules/sandboxes/lifecycle-event-types'
import { Badge } from '@/ui/primitives/badge'
import { SANDBOX_EVENT_TYPE_MAP } from './event-type-map'

export const SandboxEventTypeBadge = ({ type }: { type: string }) => {
const parsed = SandboxLifecycleEventTypeSchema.safeParse(type)

if (!parsed.success) {
return (
<Badge variant="default" size="sm" className="align-middle uppercase">
{type}
</Badge>
)
}

const { icon: IconComponent, label } = SANDBOX_EVENT_TYPE_MAP[parsed.data]

return (
<Badge variant="default" size="sm" className="align-middle uppercase">
<IconComponent />
{label}
</Badge>
)
}
79 changes: 79 additions & 0 deletions src/features/dashboard/sandbox/events/event-type-filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use client'

import {
Comment thread
sarimrmalik marked this conversation as resolved.
type SandboxLifecycleEventType,
SandboxLifecycleEventTypeSchema,
} from '@/core/modules/sandboxes/lifecycle-event-types'
import { Button } from '@/ui/primitives/button'
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/ui/primitives/dropdown-menu'
import { SandboxEventTypeBadge } from './event-type-badge'
import { SANDBOX_EVENT_TYPE_MAP } from './event-type-map'

const getTriggerLabel = (selected: SandboxLifecycleEventType[]) => {
if (selected.length === SandboxLifecycleEventTypeSchema.options.length)
return 'All'
if (selected.length === 0) return 'None'
const [first] = selected
if (selected.length === 1 && first) return SANDBOX_EVENT_TYPE_MAP[first].label
return `${selected.length}/${SandboxLifecycleEventTypeSchema.options.length}`
}

interface EventTypeFilterProps {
types: SandboxLifecycleEventType[]
onTypesChange: (types: SandboxLifecycleEventType[]) => void
}

export const EventTypeFilter = ({
types,
onTypesChange,
}: EventTypeFilterProps) => {
const isAllSelected =
types.length === SandboxLifecycleEventTypeSchema.options.length

const toggleType = (type: SandboxLifecycleEventType) => {
const next = types.includes(type)
? types.filter((t) => t !== type)
: [...types, type]
onTypesChange(next)
}

const toggleAll = (checked: boolean) => {
onTypesChange(checked ? [...SandboxLifecycleEventTypeSchema.options] : [])
}

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="secondary" className="font-sans w-min normal-case">
Events · {getTriggerLabel(types)}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuCheckboxItem
checked={isAllSelected}
onCheckedChange={toggleAll}
onSelect={(e) => e.preventDefault()}
>
All events
</DropdownMenuCheckboxItem>
<DropdownMenuSeparator />
{SandboxLifecycleEventTypeSchema.options.map((type) => (
<DropdownMenuCheckboxItem
key={type}
checked={types.includes(type)}
onCheckedChange={() => toggleType(type)}
onSelect={(e) => e.preventDefault()}
>
<SandboxEventTypeBadge type={type} />
</DropdownMenuCheckboxItem>
))}
</DropdownMenuContent>
</DropdownMenu>
)
}
22 changes: 22 additions & 0 deletions src/features/dashboard/sandbox/events/event-type-map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { SandboxLifecycleEventType } from '@/core/modules/sandboxes/lifecycle-event-types'
import {
BlockIcon,
CheckIcon,
type Icon,
PausedIcon,
RefreshIcon,
RunningIcon,
} from '@/ui/primitives/icons'

const SANDBOX_EVENT_TYPE_MAP: Record<
SandboxLifecycleEventType,
{ icon: Icon; label: string }
> = {
'sandbox.lifecycle.created': { icon: CheckIcon, label: 'Created' },
'sandbox.lifecycle.updated': { icon: RefreshIcon, label: 'Updated' },
'sandbox.lifecycle.paused': { icon: PausedIcon, label: 'Paused' },
'sandbox.lifecycle.resumed': { icon: RunningIcon, label: 'Resumed' },
'sandbox.lifecycle.killed': { icon: BlockIcon, label: 'Killed' },
}

export { SANDBOX_EVENT_TYPE_MAP }
Loading
Loading