Skip to content
Draft
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
88 changes: 46 additions & 42 deletions src/app/dashboard/[teamSlug]/usage/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getUsage } from '@/core/server/functions/usage/get-usage'
import { UsageChartsProvider } from '@/features/dashboard/usage/usage-charts-context'
import { UsageMetricChart } from '@/features/dashboard/usage/usage-metric-chart'
import { trpcCaller } from '@/trpc/server'
import ErrorBoundary from '@/ui/error'
import Frame from '@/ui/frame'

Expand All @@ -10,57 +10,61 @@ export default async function UsagePage({
params: Promise<{ teamSlug: string }>
}) {
const { teamSlug } = await params
const result = await getUsage({ teamSlug })

if (!result?.data || result.serverError || result.validationErrors) {
try {
const usageData = await trpcCaller.billing.getUsage({ teamSlug })

return (
<UsageChartsProvider data={usageData}>
<div className="flex-1 overflow-y-auto max-h-full min-h-0">
<div className="container mx-auto p-0 md:p-8 2xl:p-24 max-w-[1800px]">
<Frame
classNames={{
wrapper: 'w-full lg:h-[75vh] lg:min-h-[700px]',
frame: 'lg:h-full max-lg:border-0',
}}
>
<div className="grid grid-cols-1 lg:grid-cols-2 lg:grid-rows-2 lg:h-full">
<UsageMetricChart
metric="sandboxes"
className="min-h-[48svh] lg:min-h-0 lg:h-full"
timeRangeControlsClassName="flex lg:hidden"
/>
<UsageMetricChart
metric="cost"
className="min-h-[48svh] lg:min-h-0 lg:h-full"
timeRangeControlsClassName="hidden lg:flex"
/>
<UsageMetricChart
metric="vcpu"
className="min-h-[48svh] lg:min-h-0 lg:h-full lg:border-t lg:border-stroke"
timeRangeControlsClassName="flex lg:hidden"
/>
<UsageMetricChart
metric="ram"
className="min-h-[48svh] lg:min-h-0 lg:h-full lg:border-t lg:border-stroke"
timeRangeControlsClassName="hidden"
/>
</div>
</Frame>
</div>
</div>
</UsageChartsProvider>
)
} catch (error) {
return (
<ErrorBoundary
error={
{
name: 'Usage Error',
message: result?.serverError ?? 'Failed to load usage data',
message:
error instanceof Error
? error.message
: 'Failed to load usage data',
} satisfies Error
}
description="Could not load usage data"
/>
)
}

return (
<UsageChartsProvider data={result.data}>
<div className="flex-1 overflow-y-auto max-h-full min-h-0">
<div className="container mx-auto p-0 md:p-8 2xl:p-24 max-w-[1800px]">
<Frame
classNames={{
wrapper: 'w-full lg:h-[75vh] lg:min-h-[700px]',
frame: 'lg:h-full max-lg:border-0',
}}
>
<div className="grid grid-cols-1 lg:grid-cols-2 lg:grid-rows-2 lg:h-full">
<UsageMetricChart
metric="sandboxes"
className="min-h-[48svh] lg:min-h-0 lg:h-full"
timeRangeControlsClassName="flex lg:hidden"
/>
<UsageMetricChart
metric="cost"
className="min-h-[48svh] lg:min-h-0 lg:h-full"
timeRangeControlsClassName="hidden lg:flex"
/>
<UsageMetricChart
metric="vcpu"
className="min-h-[48svh] lg:min-h-0 lg:h-full lg:border-t lg:border-stroke"
timeRangeControlsClassName="flex lg:hidden"
/>
<UsageMetricChart
metric="ram"
className="min-h-[48svh] lg:min-h-0 lg:h-full lg:border-t lg:border-stroke"
timeRangeControlsClassName="hidden"
/>
</div>
</Frame>
</div>
</div>
</UsageChartsProvider>
)
}
43 changes: 43 additions & 0 deletions src/core/modules/sandboxes/repository.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export interface SandboxesRepository {
sandboxId: string,
options: GetSandboxMetricsOptions
): Promise<RepoResult<InfraComponents['schemas']['SandboxMetric'][]>>
killSandbox(sandboxId: string): Promise<RepoResult<void>>
listSandboxes(): Promise<RepoResult<Sandboxes>>
getSandboxesMetrics(
sandboxIds: string[]
Expand Down Expand Up @@ -356,6 +357,48 @@ export function createSandboxesRepository(

return ok(result.data)
},
async killSandbox(sandboxId) {
const result = await deps.infraClient.DELETE('/sandboxes/{sandboxID}', {
headers: {
...deps.authHeaders(scope.accessToken, scope.teamId),
},
params: {
path: {
sandboxID: sandboxId,
},
},
})

if (!result.response.ok || result.error) {
const status = result.response.status

l.error(
{
key: 'repositories:sandboxes:kill_sandbox:infra_error',
error: result.error,
team_id: scope.teamId,
context: {
status,
path: '/sandboxes/{sandboxID}',
sandbox_id: sandboxId,
},
},
`failed to delete /sandboxes/{sandboxID}: ${result.error?.message || 'Unknown error'}`
)

return err(
repoErrorFromHttp(
status,
status === 404
? 'Sandbox not found'
: (result.error?.message ?? 'Failed to kill sandbox'),
result.error
)
)
}

return ok(undefined)
},
async listSandboxes() {
const result = await deps.infraClient.GET('/sandboxes', {
headers: {
Expand Down
63 changes: 0 additions & 63 deletions src/core/server/actions/sandbox-actions.ts

This file was deleted.

11 changes: 0 additions & 11 deletions src/core/server/actions/user-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { authActionClient } from '@/core/server/actions/client'
import { auth } from '@/core/server/auth'
import { supabaseAuthFlows } from '@/core/server/auth/supabase/flows'
import { l, serializeErrorForLog } from '@/core/shared/clients/logger/logger'
import { generateE2BUserAccessToken } from '@/lib/utils/server'

const UpdateUserSchema = z
.object({
Expand Down Expand Up @@ -128,13 +127,3 @@ export const updateUserAction = authActionClient
throw error
}
})

export const getUserAccessTokenAction = authActionClient
.metadata({ actionName: 'getUserAccessToken' })
.action(async ({ ctx }) => {
const { session } = ctx

const token = await generateE2BUserAccessToken(session.access_token)

return token
})
25 changes: 25 additions & 0 deletions src/core/server/api/routers/account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { TRPCError } from '@trpc/server'
import { ActionError } from '@/core/server/actions/utils'
import { createTRPCRouter } from '@/core/server/trpc/init'
import { protectedProcedure } from '@/core/server/trpc/procedures'
import { generateE2BUserAccessToken } from '@/lib/utils/server'

const accountRouter = createTRPCRouter({
getUserAccessToken: protectedProcedure.mutation(async ({ ctx }) => {
try {
return await generateE2BUserAccessToken(ctx.session.access_token)
} catch (error) {
if (error instanceof ActionError) {
throw new TRPCError({
code: error.expected ? 'BAD_REQUEST' : 'INTERNAL_SERVER_ERROR',
message: error.message,
cause: error.cause,
})
}

throw error
}
}),
})

export { accountRouter }
2 changes: 2 additions & 0 deletions src/core/server/api/routers/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createCallerFactory, createTRPCRouter } from '@/core/server/trpc/init'
import { accountRouter } from './account'
import { billingRouter } from './billing'
import { buildsRouter } from './builds'
import { sandboxRouter } from './sandbox'
Expand All @@ -9,6 +10,7 @@ import { templatesRouter } from './templates'
import { webhooksRouter } from './webhooks'

export const trpcAppRouter = createTRPCRouter({
account: accountRouter,
sandbox: sandboxRouter,
sandboxes: sandboxesRouter,
templates: templatesRouter,
Expand Down
10 changes: 10 additions & 0 deletions src/core/server/api/routers/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,4 +205,14 @@ export const sandboxRouter = createTRPCRouter({
}),

// MUTATIONS
kill: sandboxRepositoryProcedure
.input(
z.object({
sandboxId: SandboxIdSchema,
})
)
.mutation(async ({ ctx, input }) => {
const result = await ctx.sandboxesRepository.killSandbox(input.sandboxId)
if (!result.ok) throwTRPCErrorFromRepoError(result.error)
}),
})
Loading
Loading