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
20 changes: 20 additions & 0 deletions spec/openapi.dashboard-api.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/core/modules/builds/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type _BuildStatusExhaustiveCheck = AssertTrue<
// TypeCheck: End

export const BuildStatusSchema = z.enum(BUILD_STATUS_VALUES)

export interface ListedBuildModel {
id: string
// id or alias
Expand All @@ -31,6 +32,10 @@ export interface ListedBuildModel {
statusMessage: string | null
createdAt: number
finishedAt: number | null
cpuCount: number
memoryMB: number
diskSizeMB: number | null
envdVersion: string | null
}

export interface RunningBuildStatusModel {
Expand Down
8 changes: 8 additions & 0 deletions src/core/modules/builds/repository.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ function normalizeListBuildsLimit(limit?: number): number {
}

interface ListBuildsOptions {
cpuCount?: number
memoryMB?: number
limit?: number
cursor?: string
}
Expand Down Expand Up @@ -101,6 +103,8 @@ export function createBuildsRepository(
query: {
build_id_or_template: buildIdOrTemplate?.trim() || undefined,
statuses,
cpuCount: options.cpuCount,
memoryMB: options.memoryMB,
limit,
cursor: options.cursor,
},
Expand Down Expand Up @@ -149,6 +153,10 @@ export function createBuildsRepository(
finishedAt: build.finishedAt
? new Date(build.finishedAt).getTime()
: null,
cpuCount: build.cpuCount,
memoryMB: build.memoryMB,
diskSizeMB: build.diskSizeMB,
envdVersion: build.envdVersion,
})
),
nextCursor: result.data?.nextCursor ?? null,
Expand Down
7 changes: 6 additions & 1 deletion src/core/server/api/routers/builds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,22 @@ export const buildsRouter = createTRPCRouter({
z.object({
buildIdOrTemplate: z.string().optional(),
statuses: z.array(BuildStatusSchema),
cpuCount: z.number().int().min(1).optional(),
memoryMB: z.number().int().min(1).optional(),
limit: z.number().min(1).max(100).default(50),
cursor: z.string().optional(),
})
)
.query(async ({ ctx, input }) => {
const { buildIdOrTemplate, statuses, limit, cursor } = input
const { buildIdOrTemplate, statuses, cpuCount, memoryMB, limit, cursor } =
input

const result = await ctx.buildsRepository.listBuilds(
buildIdOrTemplate,
statuses,
{
cpuCount,
memoryMB,
limit,
cursor,
}
Expand Down
8 changes: 8 additions & 0 deletions src/core/shared/contracts/dashboard-api.types.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 39 additions & 0 deletions src/features/dashboard/common/envd-version.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { cn } from '@/lib/utils'
import { isVersionCompatible } from '@/lib/utils/version'
import HelpTooltip from '@/ui/help-tooltip'

const INVALID_ENVD_VERSION = '0.0.1'
const SDK_V2_MINIMAL_ENVD_VERSION = '0.2.0'

export function EnvdVersion({
version,
className,
}: {
version: string | null | undefined
className?: string
}) {
const versionValue =
version && version !== INVALID_ENVD_VERSION ? version : null

const isNotV2Compatible = versionValue
? isVersionCompatible(versionValue, SDK_V2_MINIMAL_ENVD_VERSION) === false
: false

return (
<div
className={cn(
'text-fg-tertiary whitespace-nowrap font-mono flex flex-row gap-1.5',
{ 'text-accent-error-highlight': isNotV2Compatible },
className
)}
>
{versionValue ?? '--'}
{isNotV2Compatible && (
<HelpTooltip>
The envd version is not compatible with the SDK v2. To update the envd
version, you need to rebuild the template.
</HelpTooltip>
)}
</div>
)
}
103 changes: 103 additions & 0 deletions src/features/dashboard/common/resources-filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
'use client'

import { cn } from '@/lib/utils'
import { NumberInput } from '@/ui/number-input'
import { Button } from '@/ui/primitives/button'
import { Label } from '@/ui/primitives/label'
import { Separator } from '@/ui/primitives/separator'

export interface ResourcesFilterValue {
cpuCount?: number
memoryMB?: number
}

interface ResourcesFilterProps {
value: ResourcesFilterValue
onChange: (value: ResourcesFilterValue) => void
className?: string
}

const formatMemoryDisplay = (memoryValue: number) => {
if (memoryValue === 0) return 'Unfiltered'

return memoryValue < 1024 ? `${memoryValue} MB` : `${memoryValue / 1024} GB`
}

/**
* State-agnostic CPU + memory filter. The parent owns the value (URL params,
* a store, …) and is responsible for any debouncing.
*/
export function ResourcesFilter({
value,
onChange,
className,
}: ResourcesFilterProps) {
const cpu = value.cpuCount ?? 0
const memory = value.memoryMB ?? 0

const handleCpuChange = (next: number) => {
onChange({ cpuCount: next || undefined, memoryMB: value.memoryMB })
}

const handleMemoryChange = (next: number) => {
onChange({ cpuCount: value.cpuCount, memoryMB: next || undefined })
}

return (
<div className={cn('w-80 p-4', className)}>
<div className="grid gap-4">
<div className="space-y-3">
<div className="flex items-center justify-between">
<Label>CPU Cores</Label>
<span className="text-accent-main-highlight text-xs">
{cpu === 0 ? 'Unfiltered' : `${cpu} core${cpu === 1 ? '' : 's'}`}
</span>
</div>
<div className="flex items-center gap-2">
<NumberInput
value={cpu}
onChange={handleCpuChange}
min={0}
max={8}
step={1}
className="w-full"
/>
{cpu > 0 && (
<Button
variant="secondary"
onClick={() => handleCpuChange(0)}
className="h-9 text-xs"
>
Clear
</Button>
)}
</div>
</div>
<Separator />
<div className="space-y-3">
<div className="flex items-center justify-between">
<Label>Memory</Label>
<span className="text-accent-main-highlight text-xs">
{formatMemoryDisplay(memory)}
</span>
</div>
<div className="flex items-center gap-2">
<NumberInput
value={memory}
onChange={handleMemoryChange}
min={0}
max={8192}
step={512}
className="w-full"
/>
{memory > 0 && (
<Button variant="secondary" onClick={() => handleMemoryChange(0)}>
Clear
</Button>
)}
</div>
</div>
</div>
</div>
)
}
3 changes: 3 additions & 0 deletions src/features/dashboard/templates/builds/filter-params.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
createLoader,
parseAsArrayOf,
parseAsInteger,
parseAsString,
parseAsStringEnum,
} from 'nuqs/server'
Expand All @@ -10,6 +11,8 @@ export const templateBuildsFilterParams = {
parseAsStringEnum(['building', 'failed', 'success'])
),
buildIdOrTemplate: parseAsString,
cpuCount: parseAsInteger,
memoryMB: parseAsInteger,
}

export const loadTemplateBuildsFilters = createLoader(
Expand Down
Loading
Loading