Skip to content
Closed
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
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ NEXT_PUBLIC_E2B_DOMAIN=e2b.dev
### OPTIONAL SERVER ENVIRONMENT VARIABLES
### =================================

### Devin Outposts worker template. Leave unset for the public production alias;
### development environments may set a team-owned alias or tag.
# DEVIN_OUTPOSTS_TEMPLATE=devin-outposts-worker

### Self-hosted Ory admin endpoints (alternative to ORY_PROJECT_API_TOKEN).
### Set both when running self-hosted; leave unset to use Ory Network with the PAT above.
# ORY_KRATOS_ADMIN_URL=http://localhost:4434
Expand Down
24 changes: 24 additions & 0 deletions src/app/dashboard/[teamSlug]/connections/devin/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Metadata } from 'next'
import { DevinConnectionForm } from '@/features/dashboard/connections/devin-connection-form'
import { Page } from '@/features/dashboard/layouts/page'

export const metadata: Metadata = {
title: 'Devin connection - E2B',
robots: 'noindex, nofollow',
}

interface DevinConnectionPageProps {
params: Promise<{ teamSlug: string }>
}

export default async function DevinConnectionPage({
params,
}: DevinConnectionPageProps) {
const { teamSlug } = await params

return (
<Page>
<DevinConnectionForm teamSlug={teamSlug} />
</Page>
)
}
49 changes: 49 additions & 0 deletions src/app/dashboard/[teamSlug]/connections/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { notFound, redirect } from 'next/navigation'
import { AUTH_URLS } from '@/configs/urls'
import { featureFlags } from '@/core/modules/feature-flags/feature-flags.server'
import { getAuthContext } from '@/core/server/auth'
import { getTeamIdFromSlug } from '@/core/server/functions/team/get-team-id-from-slug'

type ConnectionsLayoutProps = {
children: React.ReactNode
params: Promise<{ teamSlug: string }>
}

export default async function ConnectionsLayout({
children,
params,
}: ConnectionsLayoutProps) {
const [{ teamSlug }, authContext] = await Promise.all([
params,
getAuthContext(),
])

if (!authContext) {
redirect(AUTH_URLS.SIGN_IN)
}

const teamId = await getTeamIdFromSlug(teamSlug, authContext.accessToken)

if (!teamId.ok || !teamId.data) {
notFound()
}

const connectionsEnabled = await featureFlags.isEnabled(
'connectionsEnabled',
{
user: {
id: authContext.user.id,
email: authContext.user.email ?? undefined,
},
team: {
id: teamId.data,
},
}
)

if (!connectionsEnabled) {
notFound()
}

return children
}
87 changes: 45 additions & 42 deletions src/app/dashboard/[teamSlug]/connections/page.tsx
Original file line number Diff line number Diff line change
@@ -1,55 +1,58 @@
import type { Metadata } from 'next'
import { notFound, redirect } from 'next/navigation'
import { AUTH_URLS } from '@/configs/urls'
import { featureFlags } from '@/core/modules/feature-flags/feature-flags.server'
import { getAuthContext } from '@/core/server/auth'
import { getTeamIdFromSlug } from '@/core/server/functions/team/get-team-id-from-slug'
import Link from 'next/link'
import { PROTECTED_URLS } from '@/configs/urls'
import { Page } from '@/features/dashboard/layouts/page'
import { ArrowRightIcon, TerminalIcon } from '@/ui/primitives/icons'

export const metadata: Metadata = {
title: 'Connections - E2B',
}

type ConnectionsPageProps = {
params: Promise<{
teamSlug: string
}>
interface ConnectionsPageProps {
params: Promise<{ teamSlug: string }>
}

export default async function ConnectionsPage({
params,
}: ConnectionsPageProps) {
const [{ teamSlug }, authContext] = await Promise.all([
params,
getAuthContext(),
])

if (!authContext) {
redirect(AUTH_URLS.SIGN_IN)
}

const teamId = await getTeamIdFromSlug(teamSlug, authContext.accessToken)

if (!teamId.ok || !teamId.data) {
notFound()
}

const connectionsEnabled = await featureFlags.isEnabled(
'connectionsEnabled',
{
user: {
id: authContext.user.id,
email: authContext.user.email ?? undefined,
},
team: {
id: teamId.data,
slug: teamSlug,
},
}
const { teamSlug } = await params

return (
<Page>
<div className="flex flex-col gap-6">
<header className="flex flex-col gap-1">
<h1 className="prose-title text-fg">Connections</h1>
<p className="prose-body text-fg-tertiary max-w-2xl">
Connect services that run workloads in your E2B team.
</p>
</header>

<div className="border-stroke border-y">
<Link
className="hover:bg-bg-hover focus-visible:outline-accent-main-highlight flex min-h-24 items-center gap-4 px-3 py-4 outline-none transition-colors md:px-4"
href={PROTECTED_URLS.CONNECTION_DEVIN(teamSlug)}
>
<div className="border-stroke bg-bg-1 flex size-11 shrink-0 items-center justify-center border">
<TerminalIcon className="size-5" aria-hidden />
</div>
<div className="min-w-0 flex-1">
<div className="flex flex-wrap items-center gap-2">
<h2 className="prose-body-highlight text-fg">Devin</h2>
<span className="prose-label text-fg-tertiary border-stroke border px-1.5 py-0.5 uppercase">
Outposts
</span>
</div>
<p className="prose-body text-fg-tertiary mt-1">
Run Devin sessions on workers isolated in E2B sandboxes.
</p>
</div>
<span className="prose-label-highlight text-fg-secondary hidden items-center gap-1 sm:flex">
Configure
<ArrowRightIcon className="size-4" aria-hidden />
</span>
</Link>
</div>
</div>
</Page>
)

if (!connectionsEnabled) {
notFound()
}

return null
}
13 changes: 13 additions & 0 deletions src/configs/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@
}),
'/dashboard/*/sandboxes/*/*': (pathname) => {
const parts = pathname.split('/')
const teamSlug = parts[2]!

Check warning on line 45 in src/configs/layout.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/style/noNonNullAssertion

Forbidden non-null assertion.
const sandboxId = parts[4]!

Check warning on line 46 in src/configs/layout.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/style/noNonNullAssertion

Forbidden non-null assertion.

return {
title: [
Expand Down Expand Up @@ -74,8 +74,8 @@
}),
'/dashboard/*/templates/*/builds/*': (pathname) => {
const parts = pathname.split('/')
const teamSlug = parts[2]!

Check warning on line 77 in src/configs/layout.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/style/noNonNullAssertion

Forbidden non-null assertion.
const buildId = parts.pop()!

Check warning on line 78 in src/configs/layout.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/style/noNonNullAssertion

Forbidden non-null assertion.
const buildIdSliced = `${buildId.slice(0, 6)}...${buildId.slice(-6)}`

return {
Expand Down Expand Up @@ -120,6 +120,19 @@
webhookDetailLayoutConfig(pathname),
'/dashboard/*/webhooks/*/deliveries': (pathname) =>
webhookDetailLayoutConfig(pathname),
'/dashboard/*/connections/devin': (pathname) => {
const teamSlug = pathname.split('/')[2] ?? ''
return {
title: [
{
label: 'Connections',
href: PROTECTED_URLS.CONNECTIONS(teamSlug),
},
{ label: 'Devin' },
],
type: 'default',
}
},

// team
'/dashboard/*/general': () => ({
Expand Down Expand Up @@ -161,7 +174,7 @@
}),
'/dashboard/*/billing/plan': (pathname) => {
const parts = pathname.split('/')
const teamSlug = parts[2]!

Check warning on line 177 in src/configs/layout.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/style/noNonNullAssertion

Forbidden non-null assertion.

return {
title: [
Expand All @@ -175,7 +188,7 @@
},
'/dashboard/*/billing/plan/select': (pathname) => {
const parts = pathname.split('/')
const teamSlug = parts[2]!

Check warning on line 191 in src/configs/layout.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/style/noNonNullAssertion

Forbidden non-null assertion.

return {
title: [
Expand All @@ -199,8 +212,8 @@
// Pathname fallback for detail tabs; usePageTitle replaces with the friendly template name once data loads.
function templateDetailLayoutConfig(pathname: string): DashboardLayoutConfig {
const parts = pathname.split('/')
const teamSlug = parts[2]!

Check warning on line 215 in src/configs/layout.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/style/noNonNullAssertion

Forbidden non-null assertion.
const templateId = parts[4]!

Check warning on line 216 in src/configs/layout.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/style/noNonNullAssertion

Forbidden non-null assertion.
const templateIdSliced =
templateId.length > 14
? `${templateId.slice(0, 6)}...${templateId.slice(-6)}`
Expand Down
2 changes: 1 addition & 1 deletion src/configs/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const SIDEBAR_MAIN_LINKS: SidebarNavItem[] = [
group: 'integration',
href: (args) => PROTECTED_URLS.CONNECTIONS(args.teamSlug!),
icon: IntegrationsIcon,
activeMatch: `/dashboard/*/connections`,
activeMatch: `/dashboard/*/connections/**`,
featureFlag: 'connectionsEnabled',
},
{
Expand Down
2 changes: 2 additions & 0 deletions src/configs/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export const PROTECTED_URLS = {
AGENTS: (teamSlug: string) => `/dashboard/${teamSlug}/agents`,
BYOC: (teamSlug: string) => `/dashboard/${teamSlug}/byoc`,
CONNECTIONS: (teamSlug: string) => `/dashboard/${teamSlug}/connections`,
CONNECTION_DEVIN: (teamSlug: string) =>
`/dashboard/${teamSlug}/connections/devin`,

SANDBOXES: (teamSlug: string) =>
`/dashboard/${teamSlug}/sandboxes/monitoring`,
Expand Down
Loading
Loading