Files
Jordi Enric 6c6ac567b1 feat(studio): workers list behind the workers flag (FE-4188) (#49193)
## What

The Workers list page at `/project/[ref]/workers`, behind
`useFlag('workers')`. Reads `GET /v2/projects/{ref}/workers`.

- Sidebar and command-menu entries, both hidden when the flag is off
- Name search, state and access filters, pagination
- Read-only

Gating, in order: flag off redirects to the project home; a 404 from the
API means the project is outside the alpha allow-list ("not enabled for
this project"); a 403 means the caller lacks the permission
(`NoPermission`); anything else is an `AlertError`.

`parseWorker` in `data/workers/workers.utils.ts` is the only place the
API shape becomes the view model. It validates with zod, so a drifted
response fails the query instead of half-rendering a row.

## How to test

Only on the **Mockamaster** project in staging — it is the one project
in the alpha allow-list, and standing a worker up anywhere else is
involved right now.

1. Staging dashboard → Mockamaster → **Compute** in the sidebar
2. Expect the `dashboard-test` worker: state `Active`, runtime Deno,
private, US West, 2 GB · 1 vCPU · 1 inst
3. Open any other project's `/workers` URL → "Compute is not enabled for
this project"
4. Turn the `workers` flag off → the sidebar entry disappears and the
URL redirects to the project home

Closes FE-4188
2026-08-20 17:54:57 +02:00

90 lines
2.3 KiB
TypeScript

import type { Worker, WorkerBuildState } from './Workers.types'
// Locked to one region at alpha, so nothing in the UI lets you change it.
export const WORKERS_REGION = 'us-west-2'
export const WORKERS_REGION_LABEL = 'US West (Oregon)'
export const WORKERS_REGION_SHORT = 'US West'
// Workers answer on the project's own domain, alongside /functions/v1.
export const workerUrl = ({
endpoint,
protocol = 'https',
name,
}: {
endpoint: string | undefined
protocol?: string
name: string
}) => (endpoint ? `${protocol}://${endpoint}/workers/v1/${name}` : undefined)
export const LISTENING_PORT = 8080
export interface RuntimeMeta {
label: string
baseImage: string
entrypoint: string
swatchClassName: string
}
// Keyed by the `spec.runtime` values the Management API returns.
export const RUNTIMES: Record<string, RuntimeMeta> = {
node: {
label: 'Node.js 24',
baseImage: 'node:24-slim',
entrypoint: 'node index.js',
swatchClassName: 'bg-[#5FA04E]',
},
deno: {
label: 'Deno 2',
baseImage: 'denoland/deno:latest',
entrypoint: 'deno run main.ts',
swatchClassName: 'bg-[#70FFAF]',
},
bun: {
label: 'Bun 1',
baseImage: 'oven/bun:latest',
entrypoint: 'bun run index.ts',
swatchClassName: 'bg-[#FBF0DF]',
},
python: {
label: 'Python 3.14',
baseImage: 'python:3.14-slim',
entrypoint: 'python main.py',
swatchClassName: 'bg-[#3776AB]',
},
dockerfile: {
label: 'Dockerfile',
baseImage: 'From ./Dockerfile',
entrypoint: 'CMD from Dockerfile',
swatchClassName: 'bg-[#2496ED]',
},
}
interface WorkerStateMeta {
label: string
dotClassName: string
textClassName: string
}
const BUILD_STATE_META: Record<WorkerBuildState, WorkerStateMeta> = {
building: {
label: 'Building',
dotClassName: 'bg-warning',
textClassName: 'text-foreground-light',
},
active: { label: 'Active', dotClassName: 'bg-brand', textClassName: 'text-foreground-light' },
failed: {
label: 'Failed',
dotClassName: 'bg-destructive',
textClassName: 'text-foreground-light',
},
}
const DELETING_STATE_META: WorkerStateMeta = {
label: 'Deleting',
dotClassName: 'bg-foreground-muted',
textClassName: 'text-foreground-lighter',
}
export const getWorkerStateMeta = (worker: Worker): WorkerStateMeta =>
worker.isDeleting ? DELETING_STATE_META : BUILD_STATE_META[worker.buildState]