Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
e91bb46
initial: trpc builds router
ben-fornefeld Nov 19, 2025
99516e7
test: procedures
ben-fornefeld Nov 20, 2025
4eddb56
implement: builds header and table + refactor: procedures
ben-fornefeld Nov 20, 2025
277a1c4
implement: server/client side filtering
ben-fornefeld Nov 20, 2025
391bfba
improve: status message styling
ben-fornefeld Nov 20, 2025
c87775e
finalize: ui
ben-fornefeld Nov 20, 2025
9c9491b
chore
ben-fornefeld Nov 20, 2025
0c0e602
fix: table cell sizing:
ben-fornefeld Nov 20, 2025
5d78425
chore'
ben-fornefeld Nov 20, 2025
33c97b8
improve: repository cleanup + add: builds status pulsing
ben-fornefeld Nov 21, 2025
c58e1d3
Merge branch 'main' into implement-builds-trpc-procedures-eng-3319
ben-fornefeld Nov 21, 2025
4d85892
improve: live template building
ben-fornefeld Nov 21, 2025
8d1ab6a
finalize
ben-fornefeld Nov 21, 2025
7774ed7
ui nits
ben-fornefeld Nov 21, 2025
526ef04
improve: id and reason cell + overflow-x-hidden
ben-fornefeld Nov 21, 2025
c3cda23
ui
ben-fornefeld Nov 21, 2025
d0d7fb7
cleanup
ben-fornefeld Nov 21, 2025
7f22209
refactor: build list fetching strategy
ben-fornefeld Nov 24, 2025
65a4cb2
remove: templates prefetch
ben-fornefeld Nov 24, 2025
ea56d5c
finalize
ben-fornefeld Nov 24, 2025
bf45859
Merge branch 'main' into implement-builds-trpc-procedures-eng-3319
ben-fornefeld Nov 25, 2025
df348d1
test: no query hydration
ben-fornefeld Nov 25, 2025
f4d94e8
remove: latest build check, build.list refetch instead
ben-fornefeld Nov 26, 2025
9305e2b
improve: ui/ux
ben-fornefeld Nov 26, 2025
76f9879
change loading layout to slash loader
ben-fornefeld Nov 26, 2025
b4a2a90
improve: maxPages stored in query + previous cursor
ben-fornefeld Nov 26, 2025
d4b2b84
improve: pagination
ben-fornefeld Nov 26, 2025
109414c
wip
ben-fornefeld Nov 26, 2025
443bf32
remove: virtualization
ben-fornefeld Nov 26, 2025
a86235d
wip: pagination cursor fix
ben-fornefeld Nov 26, 2025
1241515
remove: bidirectional cursors to avoid stale data
ben-fornefeld Nov 26, 2025
b84944e
fix query limit
ben-fornefeld Nov 26, 2025
1a71773
chore: repo cleanup
ben-fornefeld Nov 26, 2025
8955d1f
fix: back to top
ben-fornefeld Nov 26, 2025
cc3a7ff
clean-up: repository
ben-fornefeld Nov 26, 2025
ec0190f
finalize: ui nits
ben-fornefeld Nov 26, 2025
5b96dd9
Remove teams router and simplify build queries
ben-fornefeld Nov 27, 2025
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
52 changes: 36 additions & 16 deletions bun.lock

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ const config = {
authInterrupts: true,
clientSegmentCache: true,
},
turbopack: {
resolveAlias: {
// Stub Node.js modules for browser builds
// e2b package bundles these packages. when dealing with browser chunks,
// we need to stub these packages for builds.
fs: { browser: './stubs/fs.ts' },
'node:fs': { browser: './stubs/fs.ts' },
'node:fs/promises': { browser: './stubs/fs-promises.ts' },
},
},
logging: {
fetches: {
fullUrl: true,
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"scripts:create-migration": "bun scripts/create-migration.ts",
"<<<<<<< Development": "",
"shad": "bunx shadcn@canary",
"test:development:traffic": "vitest run src/__test__/development/traffic.test.ts",
"test:dev:traffic": "vitest run src/__test__/development/traffic.test.ts",
"test:dev:build": "vitest run src/__test__/development/template-build.test.ts",
"clean": "rm -rf .next node_modules && bun install",
"<<<<<<< Testing": "",
"test:run": "bun scripts:check-all-env && vitest run",
Expand Down Expand Up @@ -81,7 +82,7 @@
"@tanstack/match-sorter-utils": "^8.19.4",
"@tanstack/react-query": "^5.90.7",
"@tanstack/react-table": "^8.20.6",
"@tanstack/react-virtual": "^3.11.3",
"@tanstack/react-virtual": "^3.13.12",
"@theguild/remark-mermaid": "^0.2.0",
"@trpc/client": "^11.7.1",
"@trpc/react-query": "^11.7.1",
Expand All @@ -102,7 +103,7 @@
"date-fns": "^4.1.0",
"date-fns-tz": "^3.2.0",
"deepmerge": "^4.3.1",
"e2b": "1.10.0",
"e2b": "^2.7.0",
"echarts": "^6.0.0",
"echarts-for-react": "^3.0.2",
"fast-xml-parser": "^4.5.1",
Expand Down Expand Up @@ -140,7 +141,7 @@
"styled-components": "^6.1.19",
"superjson": "^2.2.5",
"swr": "^2.3.4",
"tailwind-merge": "^3.3.1",
"tailwind-merge": "^3.4.0",
"tw-animate-css": "^1.3.6",
"usehooks-ts": "^3.1.0",
"vaul": "^1.1.2",
Expand Down
63 changes: 63 additions & 0 deletions src/__test__/development/template-build.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* This test builds a basic sandbox template using the E2B SDK,
* useful for development and testing of template building features in the dashboard.
*/

import { Template } from 'e2b'
import { describe, expect, it } from 'vitest'

const l = console

const { TEST_E2B_DOMAIN, TEST_E2B_API_KEY } = import.meta.env

if (!TEST_E2B_DOMAIN || !TEST_E2B_API_KEY) {
throw new Error(
'Missing environment variables: TEST_E2B_DOMAIN and/or TEST_E2B_API_KEY'
)
}

const BUILD_TIMEOUT_MS = 5 * 60 * 1000

describe('E2B Template build test', () => {
it(
'builds a basic template with Node.js',
{ timeout: BUILD_TIMEOUT_MS },
async () => {
const templateName = `test-template-${Date.now()}`

l.info('test:starting_template_build', {
templateName,
startTime: new Date().toISOString(),
})

const template = Template()
.skipCache()
.fromNodeImage('lts')
.setWorkdir('/app')
.runCmd('echo "Hello from template build"')
.setStartCmd('node --version', 'node --version')

const buildInfo = await Template.build(template, {
alias: templateName,
apiKey: TEST_E2B_API_KEY,
domain: TEST_E2B_DOMAIN,
onBuildLogs: (log) => {
l.info('test:build_log', {
level: log.level,
message: log.message,
})
},
})

l.info('test:template_build_completed', {
templateId: buildInfo.templateId,
buildId: buildInfo.buildId,
alias: buildInfo.alias,
endTime: new Date().toISOString(),
})

expect(buildInfo.templateId).toBeDefined()
expect(buildInfo.buildId).toBeDefined()
}
)
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Default() {
return null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import BuildsHeader from '@/features/dashboard/templates/builds/header'
import BuildsTable from '@/features/dashboard/templates/builds/table'

export default function BuildsPage() {
return (
<div className="h-full min-h-0 flex-1 p-3 md:p-6 flex flex-col gap-3">
<BuildsHeader />
<BuildsTable />
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Default() {
return null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import LoadingLayout from '@/features/dashboard/loading-layout'
import TemplatesTable from '@/features/dashboard/templates/list/table'
import { Suspense } from 'react'

export default async function ListPage() {
return (
<Suspense fallback={<LoadingLayout />}>
<TemplatesTable />
</Suspense>
)
}
33 changes: 33 additions & 0 deletions src/app/dashboard/[teamIdOrSlug]/templates/(tabs)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { DashboardTab, DashboardTabs } from '@/ui/dashboard-tabs'
import { BuildIcon, ListIcon } from '@/ui/primitives/icons'

export default function TemplatesLayout({
list,
builds,
}: LayoutProps<'/dashboard/[teamIdOrSlug]/templates'> & {
list: React.ReactNode
builds: React.ReactNode
}) {
return (
<DashboardTabs
type="query"
layoutKey="tabs-indicator-templates"
className="pt-2 flex-1 md:pt-3"
>
<DashboardTab
id="list"
label="List"
icon={<ListIcon className="size-4" />}
>
{list}
</DashboardTab>
<DashboardTab
id="builds"
label="Builds"
icon={<BuildIcon className="size-4" />}
>
{builds}
</DashboardTab>
</DashboardTabs>
)
}
25 changes: 0 additions & 25 deletions src/app/dashboard/[teamIdOrSlug]/templates/page.tsx

This file was deleted.

5 changes: 5 additions & 0 deletions src/configs/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ export const PROTECTED_URLS = {
WEBHOOKS: (teamIdOrSlug: string) => `/dashboard/${teamIdOrSlug}/webhooks`,

TEMPLATES: (teamIdOrSlug: string) => `/dashboard/${teamIdOrSlug}/templates`,
TEMPLATES_LIST: (teamIdOrSlug: string) =>
`/dashboard/${teamIdOrSlug}/templates?tab=list`,
TEMPLATES_BUILDS: (teamIdOrSlug: string) =>
`/dashboard/${teamIdOrSlug}/templates?tab=builds`,

USAGE: (teamIdOrSlug: string) => `/dashboard/${teamIdOrSlug}/usage`,
BILLING: (teamIdOrSlug: string) => `/dashboard/${teamIdOrSlug}/billing`,
BUDGET: (teamIdOrSlug: string) => `/dashboard/${teamIdOrSlug}/budget`,
Expand Down
54 changes: 35 additions & 19 deletions src/features/dashboard/layout/wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,32 @@ import { getDashboardLayoutConfig } from '@/configs/layout'
import { CatchErrorBoundary } from '@/ui/error'
import { usePathname } from 'next/navigation'

export default function DashboardLayoutWrapper({
export function DefaultDashboardLayout({
children,
}: {
children: React.ReactNode
}) {
const pathname = usePathname()
const config = getDashboardLayoutConfig(pathname)

if (config.type === 'default') {
return (
<div className="flex-1 overflow-y-auto">
<div className="container mx-auto p-0 md:p-8 2xl:p-24 h-min w-full">
<CatchErrorBoundary
classNames={{
wrapper: 'h-full w-full',
errorBoundary: 'h-full w-full',
}}
>
{children}
</CatchErrorBoundary>
</div>
return (
<div className="flex-1 overflow-y-auto">
<div className="container mx-auto p-0 md:p-8 2xl:p-24 h-min w-full">
<CatchErrorBoundary
classNames={{
wrapper: 'h-full w-full',
errorBoundary: 'h-full w-full',
}}
>
{children}
</CatchErrorBoundary>
</div>
)
}
</div>
)
}

export function CustomDashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="flex-1 min-h-0 max-h-dvh w-full max-w-full overflow-y-auto md:overflow-hidden">
<CatchErrorBoundary
Expand All @@ -42,3 +43,18 @@ export default function DashboardLayoutWrapper({
</div>
)
}

export default function DashboardLayoutWrapper({
children,
}: {
children: React.ReactNode
}) {
const pathname = usePathname()
const config = getDashboardLayoutConfig(pathname)

if (config.type === 'default') {
return <DefaultDashboardLayout>{children}</DefaultDashboardLayout>
}

return <CustomDashboardLayout>{children}</CustomDashboardLayout>
}
6 changes: 4 additions & 2 deletions src/features/dashboard/loading-layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Loader } from '@/ui/primitives/loader_d'
'use client'

import { Loader } from '@/ui/primitives/loader'

export default function LoadingLayout() {
return (
<div className="flex h-full w-full flex-1 items-center justify-center">
<Loader className="text-xl" />
<Loader variant="slash" />
</div>
)
}
5 changes: 2 additions & 3 deletions src/features/dashboard/sandbox/inspect/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ interface SandboxInspectProviderProps {
seedEntries?: EntryInfo[]
}

export function SandboxInspectProvider({
export default function SandboxInspectProvider({
children,
rootPath,
seedEntries,
Expand Down Expand Up @@ -232,8 +232,7 @@ export function SandboxInspectProvider({
sandboxManagerRef.current = new SandboxManager(
storeRef.current,
sandbox,
rootPath,
sandboxInfo.envdAccessToken !== undefined
rootPath
)

trackInteraction('started_watching', {
Expand Down
12 changes: 1 addition & 11 deletions src/features/dashboard/sandbox/inspect/sandbox-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ export class SandboxManager {
private readonly rootPath: string
private store: FilesystemStore
private sandbox: Sandbox
private readonly isSandboxSecure: boolean = false

private static readonly LOAD_DEBOUNCE_MS = 250
private static readonly READ_DEBOUNCE_MS = 250
Expand All @@ -50,16 +49,10 @@ export class SandboxManager {
}
> = new Map()

constructor(
store: FilesystemStore,
sandbox: Sandbox,
rootPath: string,
isSandboxSecure: boolean
) {
constructor(store: FilesystemStore, sandbox: Sandbox, rootPath: string) {
this.store = store
this.sandbox = sandbox
this.rootPath = normalizePath(rootPath)
this.isSandboxSecure = isSandboxSecure

// immediately start a single recursive watcher at the root
void this.startRootWatcher()
Expand Down Expand Up @@ -360,11 +353,8 @@ export class SandboxManager {

const downloadUrl = await this.sandbox.downloadUrl(normalizedPath, {
user: 'root',
useSignature: this.isSandboxSecure || undefined,
})

console.log('downloadUrl', downloadUrl)

return downloadUrl
}

Expand Down
2 changes: 1 addition & 1 deletion src/features/dashboard/sandbox/inspect/view.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import { SandboxInspectProvider } from '@/features/dashboard/sandbox/inspect/context'
import SandboxInspectProvider from '@/features/dashboard/sandbox/inspect/context'
import SandboxInspectFilesystem from '@/features/dashboard/sandbox/inspect/filesystem'
import SandboxInspectViewer from '@/features/dashboard/sandbox/inspect/viewer'
import { cn } from '@/lib/utils'
Expand Down
2 changes: 1 addition & 1 deletion src/features/dashboard/sandboxes/list/table-cells.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { PROTECTED_URLS } from '@/configs/urls'
import ResourceUsage from '@/features/dashboard/common/resource-usage'
import { useTemplateTableStore } from '@/features/dashboard/templates/stores/table-store'
import { useTemplateTableStore } from '@/features/dashboard/templates/list/stores/table-store'
import {
defaultErrorToast,
defaultSuccessToast,
Expand Down
7 changes: 7 additions & 0 deletions src/features/dashboard/templates/builds/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { BuildStatusDTO } from '@/server/api/models/builds.models'

export const INITIAL_BUILD_STATUSES: BuildStatusDTO[] = [
'building',
'failed',
'success',
]
Loading
Loading