diff --git a/src/app/dashboard/[teamSlug]/usage/page.tsx b/src/app/dashboard/[teamSlug]/usage/page.tsx
index aa80e4d88..3662adf41 100644
--- a/src/app/dashboard/[teamSlug]/usage/page.tsx
+++ b/src/app/dashboard/[teamSlug]/usage/page.tsx
@@ -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'
@@ -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 (
+
+
+
+ )
+ } catch (error) {
return (
)
}
-
- return (
-
-
-
- )
}
diff --git a/src/core/modules/sandboxes/repository.server.ts b/src/core/modules/sandboxes/repository.server.ts
index 2f4ad1aba..54caa3bb0 100644
--- a/src/core/modules/sandboxes/repository.server.ts
+++ b/src/core/modules/sandboxes/repository.server.ts
@@ -60,6 +60,7 @@ export interface SandboxesRepository {
sandboxId: string,
options: GetSandboxMetricsOptions
): Promise>
+ killSandbox(sandboxId: string): Promise>
listSandboxes(): Promise>
getSandboxesMetrics(
sandboxIds: string[]
@@ -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: {
diff --git a/src/core/server/actions/sandbox-actions.ts b/src/core/server/actions/sandbox-actions.ts
deleted file mode 100644
index b0c35cb27..000000000
--- a/src/core/server/actions/sandbox-actions.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-'use server'
-
-import { updateTag } from 'next/cache'
-import { z } from 'zod'
-import { SUPABASE_AUTH_HEADERS } from '@/configs/api'
-import { CACHE_TAGS } from '@/configs/cache'
-import {
- authActionClient,
- withTeamSlugResolution,
-} from '@/core/server/actions/client'
-import { returnServerError } from '@/core/server/actions/utils'
-import { infra } from '@/core/shared/clients/api'
-import { l } from '@/core/shared/clients/logger/logger'
-import { TeamSlugSchema } from '@/core/shared/schemas/team'
-
-const KillSandboxSchema = z.object({
- teamSlug: TeamSlugSchema,
- sandboxId: z.string().min(1, 'Sandbox ID is required'),
-})
-
-export const killSandboxAction = authActionClient
- .schema(KillSandboxSchema)
- .metadata({ actionName: 'killSandbox' })
- .use(withTeamSlugResolution)
- .action(async ({ parsedInput, ctx }) => {
- const { sandboxId } = parsedInput
- const { session, teamId } = ctx
-
- const res = await infra.DELETE('/sandboxes/{sandboxID}', {
- headers: {
- ...SUPABASE_AUTH_HEADERS(session.access_token, teamId),
- },
- params: {
- path: {
- sandboxID: sandboxId,
- },
- },
- })
-
- if (res.error) {
- const status = res.response.status
-
- l.error(
- {
- key: 'kill_sandbox_action:infra_error',
- error: res.error,
- user_id: session.user.id,
- team_id: teamId,
- sandbox_id: sandboxId,
- context: {
- status,
- },
- },
- `Failed to kill sandbox: ${res.error.message}`
- )
-
- if (status === 404) {
- return returnServerError('Sandbox not found')
- }
-
- return returnServerError('Failed to kill sandbox')
- }
- })
diff --git a/src/core/server/actions/user-actions.ts b/src/core/server/actions/user-actions.ts
index 78a97eb7b..fcd4c9217 100644
--- a/src/core/server/actions/user-actions.ts
+++ b/src/core/server/actions/user-actions.ts
@@ -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({
@@ -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
- })
diff --git a/src/core/server/api/routers/account.ts b/src/core/server/api/routers/account.ts
new file mode 100644
index 000000000..cb1b884aa
--- /dev/null
+++ b/src/core/server/api/routers/account.ts
@@ -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 }
diff --git a/src/core/server/api/routers/index.ts b/src/core/server/api/routers/index.ts
index 8c5cce9ed..ad0249181 100644
--- a/src/core/server/api/routers/index.ts
+++ b/src/core/server/api/routers/index.ts
@@ -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'
@@ -9,6 +10,7 @@ import { templatesRouter } from './templates'
import { webhooksRouter } from './webhooks'
export const trpcAppRouter = createTRPCRouter({
+ account: accountRouter,
sandbox: sandboxRouter,
sandboxes: sandboxesRouter,
templates: templatesRouter,
diff --git a/src/core/server/api/routers/sandbox.ts b/src/core/server/api/routers/sandbox.ts
index 948367c59..ba083ea82 100644
--- a/src/core/server/api/routers/sandbox.ts
+++ b/src/core/server/api/routers/sandbox.ts
@@ -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)
+ }),
})
diff --git a/src/core/server/functions/sandboxes/get-team-metrics-max.ts b/src/core/server/functions/sandboxes/get-team-metrics-max.ts
deleted file mode 100644
index 0a2078f8a..000000000
--- a/src/core/server/functions/sandboxes/get-team-metrics-max.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-import 'server-only'
-
-import { z } from 'zod'
-import { SUPABASE_AUTH_HEADERS } from '@/configs/api'
-import { USE_MOCK_DATA } from '@/configs/flags'
-import { MOCK_TEAM_METRICS_MAX_DATA } from '@/configs/mock-data'
-import {
- authActionClient,
- withTeamSlugResolution,
-} from '@/core/server/actions/client'
-import { handleDefaultInfraError } from '@/core/server/actions/utils'
-import { infra } from '@/core/shared/clients/api'
-import { l } from '@/core/shared/clients/logger/logger'
-import { TeamSlugSchema } from '@/core/shared/schemas/team'
-import { MAX_DAYS_AGO } from '@/features/dashboard/sandboxes/monitoring/time-picker/constants'
-
-export const GetTeamMetricsMaxSchema = z
- .object({
- teamSlug: TeamSlugSchema,
- startDate: z
- .number()
- .int()
- .positive()
- .describe('Unix timestamp in milliseconds')
- .refine(
- (start) => {
- const now = Date.now()
-
- return start >= now - MAX_DAYS_AGO
- },
- {
- message: `Start date cannot be more than ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days ago`,
- }
- ),
- endDate: z
- .number()
- .int()
- .positive()
- .describe('Unix timestamp in milliseconds')
- .refine((end) => end <= Date.now(), {
- message: 'End date cannot be in the future',
- }),
- metric: z.enum(['concurrent_sandboxes', 'sandbox_start_rate']),
- })
- .refine(
- (data) => {
- return data.endDate - data.startDate <= MAX_DAYS_AGO
- },
- {
- message: `Date range cannot exceed ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days`,
- }
- )
-
-export const getTeamMetricsMax = authActionClient
- .metadata({ serverFunctionName: 'getTeamMetricsMax' })
- .schema(GetTeamMetricsMaxSchema)
- .use(withTeamSlugResolution)
- .action(async ({ parsedInput, ctx }) => {
- const { session, teamId } = ctx
- const { startDate: startDateMs, endDate: endDateMs, metric } = parsedInput
-
- if (USE_MOCK_DATA) {
- return MOCK_TEAM_METRICS_MAX_DATA(startDateMs, endDateMs, metric)
- }
-
- // convert milliseconds to seconds for the API
- const startSeconds = Math.floor(startDateMs / 1000)
- const endSeconds = Math.floor(endDateMs / 1000)
-
- const res = await infra.GET('/teams/{teamID}/metrics/max', {
- params: {
- path: {
- teamID: teamId,
- },
- query: {
- start: startSeconds,
- end: endSeconds,
- metric,
- },
- },
- headers: {
- ...SUPABASE_AUTH_HEADERS(session.access_token, teamId),
- },
- cache: 'no-store',
- })
-
- if (res.error) {
- const status = res.response.status
-
- l.error(
- {
- key: 'get_team_metrics_max:infra_error',
- error: res.error,
- team_id: teamId,
- user_id: session.user.id,
- context: {
- status,
- startDate: startDateMs,
- endDate: endDateMs,
- metric,
- },
- },
- `Failed to get team metrics max: ${res.error.message}`
- )
-
- return handleDefaultInfraError(status, res.error)
- }
-
- // since javascript timestamps are in milliseconds, we want to convert the timestamp back to milliseconds
- const timestampMs = res.data.timestampUnix * 1000
-
- return {
- timestamp: timestampMs,
- value: res.data.value,
- metric,
- }
- })
diff --git a/src/core/server/functions/sandboxes/get-team-metrics.ts b/src/core/server/functions/sandboxes/get-team-metrics.ts
deleted file mode 100644
index cc90ddacf..000000000
--- a/src/core/server/functions/sandboxes/get-team-metrics.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import 'server-only'
-
-import { z } from 'zod'
-import {
- authActionClient,
- withTeamSlugResolution,
-} from '@/core/server/actions/client'
-import { returnServerError } from '@/core/server/actions/utils'
-import { getPublicErrorMessage } from '@/core/shared/errors'
-import { TeamSlugSchema } from '@/core/shared/schemas/team'
-import { MAX_DAYS_AGO } from '@/features/dashboard/sandboxes/monitoring/time-picker/constants'
-import { getTeamMetricsCore } from './get-team-metrics-core'
-
-export const GetTeamMetricsSchema = z
- .object({
- teamSlug: TeamSlugSchema,
- startDate: z
- .number()
- .int()
- .positive()
- .describe('Unix timestamp in milliseconds')
- .refine(
- (start) => {
- const now = Date.now()
-
- return start >= now - MAX_DAYS_AGO
- },
- {
- message: `Start date cannot be more than ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days ago`,
- }
- ),
- endDate: z
- .number()
- .int()
- .positive()
- .describe('Unix timestamp in milliseconds')
- .refine((end) => end <= Date.now(), {
- message: 'End date cannot be in the future',
- }),
- })
- .refine(
- (data) => {
- return data.endDate - data.startDate <= MAX_DAYS_AGO
- },
- {
- message: `Date range cannot exceed ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days`,
- }
- )
-
-export const getTeamMetrics = authActionClient
- .schema(GetTeamMetricsSchema)
- .metadata({ serverFunctionName: 'getTeamMetrics' })
- .use(withTeamSlugResolution)
- .action(async ({ parsedInput, ctx }) => {
- const { session, teamId } = ctx
-
- const { startDate: startDateMs, endDate: endDateMs } = parsedInput
-
- const result = await getTeamMetricsCore({
- accessToken: session.access_token,
- teamId,
- userId: session.user.id,
- startMs: startDateMs,
- endMs: endDateMs,
- })
-
- if (result.error) {
- return returnServerError(getPublicErrorMessage({ status: result.status }))
- }
-
- return result.data
- })
diff --git a/src/core/server/functions/usage/get-usage.ts b/src/core/server/functions/usage/get-usage.ts
deleted file mode 100644
index e01c4aeaa..000000000
--- a/src/core/server/functions/usage/get-usage.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import 'server-only'
-
-import { cacheLife, cacheTag } from 'next/cache'
-import { z } from 'zod'
-import { CACHE_TAGS } from '@/configs/cache'
-import { createBillingRepository } from '@/core/modules/billing/repository.server'
-import {
- authActionClient,
- withTeamSlugResolution,
-} from '@/core/server/actions/client'
-import { returnServerError } from '@/core/server/actions/utils'
-import { getPublicRepoErrorMessage } from '@/core/shared/errors'
-import { TeamSlugSchema } from '@/core/shared/schemas/team'
-
-const GetUsageAuthActionSchema = z.object({
- teamSlug: TeamSlugSchema,
-})
-
-export const getUsage = authActionClient
- .schema(GetUsageAuthActionSchema)
- .metadata({ serverFunctionName: 'getUsage' })
- .use(withTeamSlugResolution)
- .action(async ({ ctx }) => {
- 'use cache'
-
- const { teamId } = ctx
-
- cacheLife('hours')
- cacheTag(CACHE_TAGS.TEAM_USAGE(teamId))
-
- const result = await createBillingRepository({
- accessToken: ctx.session.access_token,
- teamId,
- }).getUsage()
-
- if (!result.ok) {
- return returnServerError(getPublicRepoErrorMessage(result.error))
- }
-
- return result.data
- })
diff --git a/src/features/dashboard/account/user-access-token.tsx b/src/features/dashboard/account/user-access-token.tsx
index 433b26c22..0c69d353a 100644
--- a/src/features/dashboard/account/user-access-token.tsx
+++ b/src/features/dashboard/account/user-access-token.tsx
@@ -1,9 +1,9 @@
'use client'
-import { useAction } from 'next-safe-action/hooks'
+import { useMutation } from '@tanstack/react-query'
import { useState } from 'react'
-import { getUserAccessTokenAction } from '@/core/server/actions/user-actions'
import { defaultErrorToast, useToast } from '@/lib/hooks/use-toast'
+import { useTRPC } from '@/trpc/client'
import CopyButton from '@/ui/copy-button'
import { IconButton } from '@/ui/primitives/icon-button'
import { EyeIcon, EyeOffIcon } from '@/ui/primitives/icons'
@@ -16,22 +16,20 @@ interface UserAccessTokenProps {
export default function UserAccessToken({ className }: UserAccessTokenProps) {
const { toast } = useToast()
+ const trpc = useTRPC()
const [token, setToken] = useState()
const [isVisible, setIsVisible] = useState(false)
- const { execute: fetchToken, isPending } = useAction(
- getUserAccessTokenAction,
- {
+ const getUserAccessTokenMutation = useMutation(
+ trpc.account.getUserAccessToken.mutationOptions({
onSuccess: (result) => {
- if (result.data) {
- setToken(result.data.token)
- setIsVisible(true)
- }
+ setToken(result.token)
+ setIsVisible(true)
},
onError: () => {
toast(defaultErrorToast('Failed to fetch access token'))
},
- }
+ })
)
return (
@@ -51,12 +49,12 @@ export default function UserAccessToken({ className }: UserAccessTokenProps) {
setIsVisible(!isVisible)
setToken(undefined)
} else {
- fetchToken()
+ getUserAccessTokenMutation.mutate()
}
}}
- disabled={isPending}
+ disabled={getUserAccessTokenMutation.isPending}
>
- {isPending ? (
+ {getUserAccessTokenMutation.isPending ? (
) : token ? (
isVisible ? (
diff --git a/src/features/dashboard/sandbox/header/kill-button.tsx b/src/features/dashboard/sandbox/header/kill-button.tsx
index f177191bf..94c6b8111 100644
--- a/src/features/dashboard/sandbox/header/kill-button.tsx
+++ b/src/features/dashboard/sandbox/header/kill-button.tsx
@@ -1,9 +1,9 @@
'use client'
-import { useAction } from 'next-safe-action/hooks'
+import { useMutation } from '@tanstack/react-query'
import { useState } from 'react'
import { toast } from 'sonner'
-import { killSandboxAction } from '@/core/server/actions/sandbox-actions'
+import { useTRPC } from '@/trpc/client'
import { AlertPopover } from '@/ui/alert-popover'
import { Button } from '@/ui/primitives/button'
import { TrashIcon } from '@/ui/primitives/icons'
@@ -18,27 +18,30 @@ export default function KillButton({ className }: KillButtonProps) {
const [open, setOpen] = useState(false)
const { sandboxInfo, refetchSandboxInfo } = useSandboxContext()
const { team } = useDashboard()
+ const trpc = useTRPC()
const canKill = Boolean(
sandboxInfo?.sandboxID && sandboxInfo.state !== 'killed'
)
- const { execute, isExecuting } = useAction(killSandboxAction, {
- onSuccess: async () => {
- toast.success('Sandbox killed successfully')
- setOpen(false)
- refetchSandboxInfo()
- },
- onError: ({ error }) => {
- toast.error(
- error.serverError || 'Failed to kill sandbox. Please try again.'
- )
- },
- })
+ const killSandboxMutation = useMutation(
+ trpc.sandbox.kill.mutationOptions({
+ onSuccess: async () => {
+ toast.success('Sandbox killed successfully')
+ setOpen(false)
+ refetchSandboxInfo()
+ },
+ onError: (error) => {
+ toast.error(
+ error.message || 'Failed to kill sandbox. Please try again.'
+ )
+ },
+ })
+ )
const handleKill = () => {
if (!canKill || !sandboxInfo?.sandboxID) return
- execute({
+ killSandboxMutation.mutate({
teamSlug: team.slug,
sandboxId: sandboxInfo.sandboxID,
})
@@ -58,8 +61,8 @@ export default function KillButton({ className }: KillButtonProps) {
}
confirmProps={{
- disabled: isExecuting,
- loading: isExecuting ? 'Killing...' : undefined,
+ disabled: killSandboxMutation.isPending,
+ loading: killSandboxMutation.isPending ? 'Killing...' : undefined,
}}
onConfirm={handleKill}
onCancel={() => setOpen(false)}
diff --git a/src/features/dashboard/sandboxes/live-counter.client.tsx b/src/features/dashboard/sandboxes/live-counter.client.tsx
index e49977db2..ffc61d598 100644
--- a/src/features/dashboard/sandboxes/live-counter.client.tsx
+++ b/src/features/dashboard/sandboxes/live-counter.client.tsx
@@ -1,15 +1,11 @@
'use client'
-import type { InferSafeActionFnResult } from 'next-safe-action'
-import type { NonUndefined } from 'react-hook-form'
-import type { getTeamMetrics } from '@/core/server/functions/sandboxes/get-team-metrics'
+import type { TRPCRouterOutputs } from '@/trpc/client'
import { LiveSandboxCounter } from './live-counter'
import { useRecentMetrics } from './monitoring/hooks/use-recent-metrics'
interface LiveSandboxCounterClientProps {
- initialData: NonUndefined<
- InferSafeActionFnResult['data']
- >
+ initialData: TRPCRouterOutputs['sandboxes']['getTeamMetrics']
className?: string
}
diff --git a/src/features/dashboard/sandboxes/live-counter.server.tsx b/src/features/dashboard/sandboxes/live-counter.server.tsx
index 5f87fdda4..47ebe1154 100644
--- a/src/features/dashboard/sandboxes/live-counter.server.tsx
+++ b/src/features/dashboard/sandboxes/live-counter.server.tsx
@@ -1,8 +1,8 @@
import { Suspense } from 'react'
-import { getTeamMetrics } from '@/core/server/functions/sandboxes/get-team-metrics'
-import { l } from '@/core/shared/clients/logger/logger'
+import { l, serializeErrorForLog } from '@/core/shared/clients/logger/logger'
import { cn } from '@/lib/utils'
import { getNowMemo } from '@/lib/utils/server'
+import { trpcCaller } from '@/trpc/server'
import { Skeleton } from '@/ui/primitives/skeleton'
import { LiveSandboxCounterClient } from './live-counter.client'
@@ -36,19 +36,26 @@ async function LiveSandboxCounterResolver({
const now = getNowMemo()
const start = now - 60_000
- const teamMetricsResult = await getTeamMetrics({
- teamSlug,
- startDate: start,
- endDate: now,
- })
+ try {
+ const teamMetrics = await trpcCaller.sandboxes.getTeamMetrics({
+ teamSlug,
+ startDate: start,
+ endDate: now,
+ })
- if (!teamMetricsResult?.data || teamMetricsResult.serverError) {
+ return (
+
+ )
+ } catch (error) {
l.error(
{
key: 'live_sandbox_counter:error',
+ error: serializeErrorForLog(error),
context: {
teamSlug,
- serverError: teamMetricsResult?.serverError,
},
},
'Failed to load live sandbox count'
@@ -56,11 +63,4 @@ async function LiveSandboxCounterResolver({
return null
}
-
- return (
-
- )
}
diff --git a/src/features/dashboard/sandboxes/monitoring/charts/charts.tsx b/src/features/dashboard/sandboxes/monitoring/charts/charts.tsx
index 4d1b5551a..bc0e621df 100644
--- a/src/features/dashboard/sandboxes/monitoring/charts/charts.tsx
+++ b/src/features/dashboard/sandboxes/monitoring/charts/charts.tsx
@@ -1,6 +1,6 @@
import { Suspense } from 'react'
import { TEAM_METRICS_INITIAL_RANGE_MS } from '@/configs/intervals'
-import { getTeamMetrics } from '@/core/server/functions/sandboxes/get-team-metrics'
+import { trpcCaller } from '@/trpc/server'
import { TeamMetricsChartsProvider } from '../charts-context'
import ConcurrentChartClient from './concurrent-chart'
import ChartFallback from './fallback'
@@ -46,21 +46,22 @@ async function TeamMetricsChartsResolver({
: now - TEAM_METRICS_INITIAL_RANGE_MS
const end = endParam ? parseInt(endParam, 10) : now
- const teamMetricsResult = await getTeamMetrics({
- teamSlug,
- startDate: start,
- endDate: end,
- })
+ try {
+ const teamMetrics = await trpcCaller.sandboxes.getTeamMetrics({
+ teamSlug,
+ startDate: start,
+ endDate: end,
+ })
- if (
- !teamMetricsResult?.data ||
- teamMetricsResult.serverError ||
- teamMetricsResult.validationErrors
- ) {
+ return (
+
+
+
+
+ )
+ } catch (error) {
const errorMessage =
- teamMetricsResult?.serverError ||
- teamMetricsResult?.validationErrors?.formErrors[0] ||
- 'Failed to load metrics data.'
+ error instanceof Error ? error.message : 'Failed to load metrics data.'
return (
<>
@@ -77,11 +78,4 @@ async function TeamMetricsChartsResolver({
>
)
}
-
- return (
-
-
-
-
- )
}
diff --git a/src/features/dashboard/sandboxes/monitoring/header.client.tsx b/src/features/dashboard/sandboxes/monitoring/header.client.tsx
index b66c82ded..82cb6f8df 100644
--- a/src/features/dashboard/sandboxes/monitoring/header.client.tsx
+++ b/src/features/dashboard/sandboxes/monitoring/header.client.tsx
@@ -1,18 +1,14 @@
'use client'
-import type { InferSafeActionFnResult } from 'next-safe-action'
import { useMemo } from 'react'
-import type { NonUndefined } from 'react-hook-form'
-import type { getTeamMetrics } from '@/core/server/functions/sandboxes/get-team-metrics'
import { useDashboard } from '@/features/dashboard/context'
import { formatDecimal, formatNumber } from '@/lib/utils/formatting'
+import type { TRPCRouterOutputs } from '@/trpc/client'
import { AnimatedNumber } from '@/ui/primitives/animated-number'
import { useRecentMetrics } from './hooks/use-recent-metrics'
interface TeamMonitoringHeaderClientProps {
- initialData: NonUndefined<
- InferSafeActionFnResult['data']
- >
+ initialData: TRPCRouterOutputs['sandboxes']['getTeamMetrics']
}
export function ConcurrentSandboxesClient({
diff --git a/src/features/dashboard/sandboxes/monitoring/header.tsx b/src/features/dashboard/sandboxes/monitoring/header.tsx
index 2e038efe0..9c991c49b 100644
--- a/src/features/dashboard/sandboxes/monitoring/header.tsx
+++ b/src/features/dashboard/sandboxes/monitoring/header.tsx
@@ -1,7 +1,6 @@
import { Suspense } from 'react'
-import { getTeamMetrics } from '@/core/server/functions/sandboxes/get-team-metrics'
-import { getTeamMetricsMax } from '@/core/server/functions/sandboxes/get-team-metrics-max'
import { getNowMemo } from '@/lib/utils/server'
+import { trpcCaller } from '@/trpc/server'
import ErrorTooltip from '@/ui/error-tooltip'
import { SemiLiveBadge } from '@/ui/live'
import { WarningIcon } from '@/ui/primitives/icons'
@@ -105,22 +104,23 @@ export const ConcurrentSandboxes = async ({
const now = getNowMemo()
const start = now - 60_000
- const teamMetricsResult = await getTeamMetrics({
- teamSlug,
- startDate: start,
- endDate: now,
- })
+ try {
+ const teamMetrics = await trpcCaller.sandboxes.getTeamMetrics({
+ teamSlug,
+ startDate: start,
+ endDate: now,
+ })
- if (!teamMetricsResult?.data || teamMetricsResult.serverError) {
+ return
+ } catch (error) {
return (
- {teamMetricsResult?.serverError ||
- 'Failed to load concurrent sandboxes'}
+ {error instanceof Error
+ ? error.message
+ : 'Failed to load concurrent sandboxes'}
)
}
-
- return
}
export const SandboxesStartRate = async ({
@@ -134,22 +134,23 @@ export const SandboxesStartRate = async ({
const now = getNowMemo()
const start = now - 60_000
- const teamMetricsResult = await getTeamMetrics({
- teamSlug,
- startDate: start,
- endDate: now,
- })
+ try {
+ const teamMetrics = await trpcCaller.sandboxes.getTeamMetrics({
+ teamSlug,
+ startDate: start,
+ endDate: now,
+ })
- if (!teamMetricsResult?.data || teamMetricsResult.serverError) {
+ return
+ } catch (error) {
return (
- {teamMetricsResult?.serverError ||
- 'Failed to load max sandbox start rate'}
+ {error instanceof Error
+ ? error.message
+ : 'Failed to load max sandbox start rate'}
)
}
-
- return
}
export const MaxConcurrentSandboxes = async ({
@@ -162,25 +163,24 @@ export const MaxConcurrentSandboxes = async ({
const end = Date.now()
const start = end - (MAX_DAYS_AGO - 60_000) // 1 minute margin to avoid validation errors
- const teamMetricsResult = await getTeamMetricsMax({
- teamSlug,
- startDate: start,
- endDate: end,
- metric: 'concurrent_sandboxes',
- })
+ try {
+ const teamMetrics = await trpcCaller.sandboxes.getTeamMetricsMax({
+ teamSlug,
+ startDate: start,
+ endDate: end,
+ metric: 'concurrent_sandboxes',
+ })
- if (!teamMetricsResult?.data || teamMetricsResult.serverError) {
+ return (
+
+ )
+ } catch (error) {
return (
- {teamMetricsResult?.serverError ||
- 'Failed to load max concurrent sandboxes'}
+ {error instanceof Error
+ ? error.message
+ : 'Failed to load max concurrent sandboxes'}
)
}
-
- return (
-
- )
}