Skip to content

Refactor /sandboxes/metrics to /sandboxes #23

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 31, 2025
Merged
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
5 changes: 2 additions & 3 deletions src/features/dashboard/sandboxes/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,16 @@ import { Circle, ListFilter } from 'lucide-react'
import { Table } from '@tanstack/react-table'
import { SearchInput } from './table-search'
import SandboxesTableFilters from './table-filters'
import { SandboxWithMetrics } from './table-config'
import { PollingButton } from '@/ui/polling-button'
import { useSandboxTableStore } from './stores/table-store'
import { Template } from '@/types/api'
import { Sandbox, Template } from '@/types/api'
import { useRouter } from 'next/navigation'
import { useTransition } from 'react'

interface SandboxesHeaderProps {
searchInputRef: React.RefObject<HTMLInputElement | null>
templates: Template[]
table: Table<SandboxWithMetrics>
table: Table<Sandbox>
}

export function SandboxesHeader({
Expand Down
9 changes: 4 additions & 5 deletions src/features/dashboard/sandboxes/table-body.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { Alert, AlertDescription, AlertTitle } from '@/ui/primitives/alert'
import { DataTableBody } from '@/ui/data-table'
import { Table, Row } from '@tanstack/react-table'
import { SandboxWithMetrics } from './table-config'
import { memo, useMemo } from 'react'
import Empty from '@/ui/empty'
import { Button } from '@/ui/primitives/button'
import { useSandboxTableStore } from './stores/table-store'
import { ExternalLink, X } from 'lucide-react'
import { TableRow } from './table-row'
import { Sandbox } from '@/types/api'

interface TableBodyProps {
sandboxes: SandboxWithMetrics[] | undefined
table: Table<SandboxWithMetrics>
visualRows: Row<SandboxWithMetrics>[]
sandboxes: Sandbox[] | undefined
table: Table<Sandbox>
visualRows: Row<Sandbox>[]
}

export const TableBody = memo(function TableBody({
Expand Down
86 changes: 74 additions & 12 deletions src/features/dashboard/sandboxes/table-config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const trackTableInteraction = (

// FILTERS

export const fuzzyFilter: FilterFn<SandboxWithMetrics> = (
export const fuzzyFilter: FilterFn<Sandbox> = (
row,
columnId,
value,
Expand Down Expand Up @@ -72,7 +72,8 @@ export const resourceRangeFilter: FilterFn<SandboxWithMetrics> = (
columnId,
value: number
) => {
if (columnId === 'cpuUsage') {
/* NOTE: Currently disabled due to issue with the metrics api
if (columnId === 'cpuUsage') {
const rowValue = row.original.cpuCount
if (!rowValue || !value || value === 0) return true
return rowValue === value
Expand All @@ -82,16 +83,28 @@ export const resourceRangeFilter: FilterFn<SandboxWithMetrics> = (
const rowValue = row.original.memoryMB
if (!rowValue || !value || value === 0) return true
return rowValue === value
} */

if (columnId === 'cpuCount') {
const rowValue = row.original.cpuCount
if (!rowValue || !value || value === 0) return true
return rowValue === value
}

if (columnId === 'memoryMB') {
const rowValue = row.original.memoryMB
if (!rowValue || !value || value === 0) return true
return rowValue === value
}

return true
}

// TABLE CONFIG

export const fallbackData: SandboxWithMetrics[] = []
export const fallbackData: Sandbox[] = []

export const COLUMNS: ColumnDef<SandboxWithMetrics>[] = [
export const COLUMNS: ColumnDef<Sandbox>[] = [
{
id: 'pin',
cell: ({ row }) => (
Expand Down Expand Up @@ -164,6 +177,46 @@ export const COLUMNS: ColumnDef<SandboxWithMetrics>[] = [
filterFn: 'arrIncludesSome',
},
{
id: 'cpuCount',
header: 'CPU Count',
cell: ({ row }) => {
const cpuCount = row.original.cpuCount

return (
<Badge className={cn('text-fg-500 px-0 font-mono whitespace-nowrap')}>
<span className={cn('text-contrast-2 flex items-center gap-0.5')}>
<Cpu className={cn('size-3')} /> {cpuCount}{' '}
</span>{' '}
core{cpuCount > 1 ? 's' : ''}
</Badge>
)
},
size: 130,
minSize: 130,
// @ts-expect-error resourceRange is not a valid filterFn
filterFn: 'resourceRange',
},
{
id: 'memoryMB',
header: 'Memory',
cell: ({ row }) => {
const memoryMB = row.original.memoryMB
return (
<Badge className={cn('text-fg-500 px-0 font-mono whitespace-nowrap')}>
<span className={cn('text-contrast-1 flex items-center gap-0.5')}>
<CgSmartphoneRam className={cn('size-3')} /> {memoryMB}{' '}
</span>{' '}
MB
</Badge>
)
},
size: 105,
minSize: 105,
// @ts-expect-error resourceRange is not a valid filterFn
filterFn: 'resourceRange',
},
// NOTE: Currently disabled due to issue with the metrics api
/* {
id: 'cpuUsage',
accessorFn: (row) => row.metrics[0]?.cpuUsedPct ?? 0,
header: 'CPU Usage',
Expand Down Expand Up @@ -240,7 +293,7 @@ export const COLUMNS: ColumnDef<SandboxWithMetrics>[] = [
minSize: 160,
// @ts-expect-error resourceRange is not a valid filterFn
filterFn: 'resourceRange',
},
}, */
{
id: 'metadata',
accessorFn: (row) => JSON.stringify(row.metadata ?? {}),
Expand All @@ -249,7 +302,14 @@ export const COLUMNS: ColumnDef<SandboxWithMetrics>[] = [
const value = getValue() as string
const json = useMemo(() => JSON.parse(value), [value])

return <JsonPopover json={json}>{value}</JsonPopover>
return (
<JsonPopover
className="text-fg-500 hover:text-fg hover:underline"
json={json}
>
{value}
</JsonPopover>
)
},
size: 200,
minSize: 160,
Expand All @@ -260,13 +320,15 @@ export const COLUMNS: ColumnDef<SandboxWithMetrics>[] = [
accessorFn: (row) => new Date(row.startedAt).toUTCString(),
header: 'Started At',
cell: ({ row, getValue }) => {
const dateTimeString = getValue() as string
// Split the date and time parts
const [day, date, month, year, time, timezone] = dateTimeString.split(' ')

return (
<div
className={cn(
'text-fg-500 hover:text-fg h-full truncate font-mono text-xs'
)}
>
{getValue() as string}
<div className={cn('h-full truncate font-mono text-xs')}>
<span className="text-fg-500">{`${day} ${date} ${month} ${year}`}</span>{' '}
<span className="text-fg">{time}</span>{' '}
<span className="text-fg-500">{timezone}</span>
</div>
)
},
Expand Down
6 changes: 3 additions & 3 deletions src/features/dashboard/sandboxes/table-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import {
DataTableRow,
} from '@/ui/data-table'
import { flexRender, HeaderGroup, Row, TableState } from '@tanstack/react-table'
import { SandboxWithMetrics } from './table-config'
import Scanline from '@/ui/scanline'
import { Sandbox } from '@/types/api'

interface TableHeaderProps {
topRows: Row<SandboxWithMetrics>[]
headerGroups: HeaderGroup<SandboxWithMetrics>[]
topRows: Row<Sandbox>[]
headerGroups: HeaderGroup<Sandbox>[]
state: TableState
}

Expand Down
5 changes: 2 additions & 3 deletions src/features/dashboard/sandboxes/table-row.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { memo } from 'react'
import { Row } from '@tanstack/react-table'
import { SandboxWithMetrics } from './table-config'
import { DataTableCell, DataTableRow } from '@/ui/data-table'
import { flexRender } from '@tanstack/react-table'
import { cn } from '@/lib/utils'
import { Sandbox } from '@/types/api'

interface TableRowProps {
row: Row<SandboxWithMetrics>
row: Row<Sandbox>
}

export const TableRow = memo(function TableRow({ row }: TableRowProps) {
Expand Down
25 changes: 20 additions & 5 deletions src/features/dashboard/sandboxes/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ import { SandboxesHeader } from './header'
import { TableBody } from './table-body'
import { subHours } from 'date-fns'
import { useColumnSizeVars } from '@/lib/hooks/use-column-size-vars'
import { Template } from '@/types/api'
import { Sandbox, Template } from '@/types/api'
import ClientOnly from '@/ui/client-only'
import TableHeader from './table-header'

const INITIAL_VISUAL_ROWS_COUNT = 50

interface SandboxesTableProps {
sandboxes: SandboxWithMetrics[]
sandboxes: Sandbox[]
templates: Template[]
}

Expand Down Expand Up @@ -116,20 +116,35 @@ export default function SandboxesTable({
}

// Handle CPU filter
if (!cpuCount) {
newFilters = newFilters.filter((f) => f.id !== 'cpuCount')
} else {
newFilters = newFilters.filter((f) => f.id !== 'cpuCount')
newFilters.push({ id: 'cpuCount', value: cpuCount })
}

// Handle memory filter
if (!memoryMB) {
newFilters = newFilters.filter((f) => f.id !== 'memoryMB')
} else {
newFilters = newFilters.filter((f) => f.id !== 'memoryMB')
newFilters.push({ id: 'memoryMB', value: memoryMB })
}

/* NOTE: Currently disabled due to issue with the metrics api
if (!cpuCount) {
newFilters = newFilters.filter((f) => f.id !== 'cpuUsage')
} else {
newFilters = newFilters.filter((f) => f.id !== 'cpuUsage')
newFilters.push({ id: 'cpuUsage', value: cpuCount })
}

// Handle memory filter
if (!memoryMB) {
newFilters = newFilters.filter((f) => f.id !== 'ramUsage')
} else {
newFilters = newFilters.filter((f) => f.id !== 'ramUsage')
newFilters.push({ id: 'ramUsage', value: memoryMB })
}
} */

resetScroll()
setColumnFilters(newFilters)
Expand Down Expand Up @@ -166,7 +181,7 @@ export default function SandboxesTable({
resourceRange: resourceRangeFilter,
},
enableGlobalFilter: true,
globalFilterFn: fuzzyFilter as FilterFn<SandboxWithMetrics>,
globalFilterFn: fuzzyFilter as FilterFn<Sandbox>,
onGlobalFilterChange: setGlobalFilter,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
Expand Down
7 changes: 4 additions & 3 deletions src/server/sandboxes/get-team-sandboxes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { SandboxWithMetrics } from '@/features/dashboard/sandboxes/table-config'
import { authActionClient } from '@/lib/clients/action'
import { returnServerError } from '@/lib/utils/action'
import { getApiUrl, getTeamApiKey } from '@/lib/utils/server'
import { Sandbox } from '@/types/api'

const GetTeamSandboxesSchema = z.object({
teamId: z.string().uuid(),
Expand Down Expand Up @@ -35,7 +36,7 @@ export const getTeamSandboxes = authActionClient
const apiKey = await getTeamApiKey(user.id, teamId)
const { url } = await getApiUrl()

const res = await fetch(`${url}/sandboxes/metrics`, {
const res = await fetch(`${url}/sandboxes`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Expand All @@ -45,15 +46,15 @@ export const getTeamSandboxes = authActionClient

if (!res.ok) {
const content = await res.text()
logError(ERROR_CODES.INFRA, '/sandboxes/metrics', content)
logError(ERROR_CODES.INFRA, '/sandboxes', content)

// this case should never happen for the original reason, hence we assume the user defined the wrong infra domain
return returnServerError(
"Something went wrong when accessing the API. Ensure you are using the correct Infrastructure Domain under 'Developer Settings'"
)
}

const json = (await res.json()) as SandboxWithMetrics[]
const json = (await res.json()) as Sandbox[]

return json
})
9 changes: 7 additions & 2 deletions src/ui/json-popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import {
import { useState } from 'react'
import ShikiHighlighter from 'react-shiki'
import { ScrollArea, ScrollBar } from './primitives/scroll-area'
import { cn } from '@/lib/utils'

interface JsonPopoverProps {
json: unknown
children: React.ReactNode
className?: string
}

export function JsonPopover({ json, children }: JsonPopoverProps) {
export function JsonPopover({ json, children, className }: JsonPopoverProps) {
const [isOpen, setIsOpen] = useState(false)

const shikiTheme = useShikiTheme()
Expand All @@ -22,7 +24,10 @@ export function JsonPopover({ json, children }: JsonPopoverProps) {
<Popover open={isOpen} onOpenChange={setIsOpen}>
<PopoverTrigger asChild>
<div
className="hover:text-fg text-fg-300 h-full cursor-pointer truncate whitespace-nowrap hover:underline"
className={cn(
'h-full cursor-pointer truncate whitespace-nowrap',
className
)}
onDoubleClick={() => setIsOpen(true)}
>
{children}
Expand Down