refactor(studio): merge the worker settings tab into overview

Move the Container, Resources, and Develop locally sections onto the
overview tab and remove the Settings tab. The invocation snippets that
lived in both tabs are now a single How to call section.
This commit is contained in:
Jordi Enric committed 2026-08-27 11:21:56 +02:00
1 parent 407d39fe32
commit a4fa232d59
3 files changed
+219 -270

No files matched your search

@@ -35,21 +35,19 @@ import { WorkerSnippetTabs } from '../WorkerSnippetTabs'
import { WorkerStatePill } from '../WorkerStatePill'
import { WorkerLogsTab } from './WorkerLogsTab'
import { WorkerOverviewTab } from './WorkerOverviewTab'
import { WorkerSettingsTab } from './WorkerSettingsTab'
import { AlertError } from '@/components/ui/AlertError'
import type { WorkerLogStream } from '@/data/workers/worker-logs-query'
import { workerQueryOptions } from '@/data/workers/worker-query'
import { PRODUCT_NAME } from '@/lib/constants/workers'
type WorkerTab = 'overview' | 'invocations' | 'logs' | 'activity' | 'settings'
const WORKER_TABS: WorkerTab[] = ['overview', 'invocations', 'logs', 'activity', 'settings']
type WorkerTab = 'overview' | 'invocations' | 'logs' | 'activity'
const WORKER_TABS: WorkerTab[] = ['overview', 'invocations', 'logs', 'activity']
const TAB_LABEL: Record<WorkerTab, string> = {
overview: 'Overview',
invocations: 'Invocations',
logs: 'Logs',
activity: 'Activity',
settings: 'Settings',
}
const TAB_STREAM: Partial<Record<WorkerTab, WorkerLogStream>> = {
@@ -171,7 +169,6 @@ export const WorkerDetail = () => {
<WorkerLogsTab workerName={worker.name} stream={stream} />
</div>
)}
{tab === 'settings' && <WorkerSettingsTab worker={worker} />}
</div>
)
}
@@ -1,4 +1,5 @@
import { ReactNode } from 'react'
import { Badge } from 'ui'
import { Admonition } from 'ui-patterns/Admonition'
import { InfoTooltip } from 'ui-patterns/info-tooltip'
import { PageContainer } from 'ui-patterns/PageContainer'
@@ -11,8 +12,12 @@ import {
PageSectionTitle,
} from 'ui-patterns/PageSection'
import { RuntimeBadge } from '../RuntimeBadge'
import { WorkerCommandLine } from '../WorkerCommandLine'
import { LISTENING_PORT, WORKERS_REGION_LABEL } from '../Workers.constants'
import type { Worker } from '../Workers.types'
import { formatSize, getRuntimeMeta } from '../Workers.utils'
import { buildWorkerCliCommands } from '../workerSnippets'
import { WorkerSnippetTabs } from '../WorkerSnippetTabs'
import { CLI_NAME } from '@/lib/constants/workers'
@@ -40,100 +45,216 @@ const InstanceCount = ({
</div>
)
export const WorkerOverviewTab = ({ worker }: WorkerOverviewTabProps) => (
<PageContainer size="small">
{worker.buildState === 'failed' && (
<PageSection>
<PageSectionContent>
<Admonition type="destructive" title="This worker failed to build">
<div className="space-y-3">
<p>{worker.stateReason ?? 'The build did not complete.'}</p>
<WorkerCommandLine
comment="Redeploy after fixing the build"
command={`supabase ${CLI_NAME} push ${worker.name}`}
/>
</div>
</Admonition>
</PageSectionContent>
</PageSection>
)}
{worker.instancesError !== undefined && (
<PageSection>
<PageSectionContent>
<Admonition type="warning" title="Instances reported an error">
{worker.instancesError}
</Admonition>
</PageSectionContent>
</PageSection>
)}
<PageSection>
<PageSectionMeta>
<PageSectionSummary>
<PageSectionTitle>Instances</PageSectionTitle>
</PageSectionSummary>
</PageSectionMeta>
<PageSectionContent>
{worker.instances === undefined ? (
<p className="text-sm text-foreground-light">
No instances are running for this worker yet.
</p>
) : (
<div className="space-y-3">
<p className="text-sm text-foreground-light">
<span className="tabular-nums text-foreground">{worker.instances.ready}</span> of{' '}
<span className="tabular-nums text-foreground">{worker.instances.declared}</span>{' '}
instances ready
</p>
<div className="grid grid-cols-2 divide-x divide-y rounded-md border border-default bg-surface-100 sm:grid-cols-4 sm:divide-y-0">
<InstanceCount
label="Instances"
value={worker.instances.declared}
tooltip="The number of instances you configured for this worker."
/>
<InstanceCount
label="Live"
value={worker.instances.live}
tooltip="Instances currently running."
/>
<InstanceCount
label="Ready"
value={worker.instances.ready}
tooltip="Instances passing health checks and serving requests."
/>
<InstanceCount
label="Stale"
value={worker.instances.stale}
tooltip="Instances from a previous deployment, being replaced."
/>
</div>
</div>
)}
</PageSectionContent>
</PageSection>
<PageSection>
<PageSectionMeta>
<PageSectionSummary>
<PageSectionTitle>How to call</PageSectionTitle>
<PageSectionDescription>
Call the worker over its gateway URL. Pass your project API key as a bearer token.
</PageSectionDescription>
</PageSectionSummary>
</PageSectionMeta>
<PageSectionContent>
<WorkerSnippetTabs
input={{
name: worker.name,
runtime: worker.runtime,
size: worker.size,
access: worker.access,
instances: worker.declaredInstances,
}}
tabs={['curl', 'js', 'python']}
/>
</PageSectionContent>
</PageSection>
</PageContainer>
const SettingsRow = ({
label,
children,
isFirst,
}: {
label: string
children: ReactNode
isFirst?: boolean
}) => (
<div
className={`flex items-center justify-between px-4 py-3 ${
isFirst ? '' : 'border-t border-default'
}`}
>
<span className="text-sm text-foreground-light">{label}</span>
<span className="text-sm text-foreground">{children}</span>
</div>
)
export const WorkerOverviewTab = ({ worker }: WorkerOverviewTabProps) => {
const runtime = getRuntimeMeta(worker.runtime)
const commands = buildWorkerCliCommands(worker.name)
return (
<PageContainer size="small">
{worker.buildState === 'failed' && (
<PageSection>
<PageSectionContent>
<Admonition type="destructive" title="This worker failed to build">
<div className="space-y-3">
<p>{worker.stateReason ?? 'The build did not complete.'}</p>
<WorkerCommandLine
comment="Redeploy after fixing the build"
command={`supabase ${CLI_NAME} push ${worker.name}`}
/>
</div>
</Admonition>
</PageSectionContent>
</PageSection>
)}
{worker.instancesError !== undefined && (
<PageSection>
<PageSectionContent>
<Admonition type="warning" title="Instances reported an error">
{worker.instancesError}
</Admonition>
</PageSectionContent>
</PageSection>
)}
<PageSection>
<PageSectionMeta>
<PageSectionSummary>
<PageSectionTitle>Instances</PageSectionTitle>
</PageSectionSummary>
</PageSectionMeta>
<PageSectionContent>
{worker.instances === undefined ? (
<p className="text-sm text-foreground-light">
No instances are running for this worker yet.
</p>
) : (
<div className="space-y-3">
<p className="text-sm text-foreground-light">
<span className="tabular-nums text-foreground">{worker.instances.ready}</span> of{' '}
<span className="tabular-nums text-foreground">{worker.instances.declared}</span>{' '}
instances ready
</p>
<div className="grid grid-cols-2 divide-x divide-y rounded-md border border-default bg-surface-100 sm:grid-cols-4 sm:divide-y-0">
<InstanceCount
label="Instances"
value={worker.instances.declared}
tooltip="The number of instances you configured for this worker."
/>
<InstanceCount
label="Live"
value={worker.instances.live}
tooltip="Instances currently running."
/>
<InstanceCount
label="Ready"
value={worker.instances.ready}
tooltip="Instances passing health checks and serving requests."
/>
<InstanceCount
label="Stale"
value={worker.instances.stale}
tooltip="Instances from a previous deployment, being replaced."
/>
</div>
</div>
)}
</PageSectionContent>
</PageSection>
<PageSection>
<PageSectionMeta>
<PageSectionSummary>
<PageSectionTitle>Container</PageSectionTitle>
<PageSectionDescription>
The runtime image and entrypoint resolved for this worker.
</PageSectionDescription>
</PageSectionSummary>
</PageSectionMeta>
<PageSectionContent>
<div className="rounded-md border border-default bg-surface-100">
<SettingsRow label="Runtime" isFirst>
<RuntimeBadge runtime={worker.runtime} />
</SettingsRow>
{worker.imageVersion !== undefined && (
<SettingsRow label="Image version">
<span className="font-mono text-xs text-foreground-light">
{worker.imageVersion}
</span>
</SettingsRow>
)}
{runtime !== undefined && (
<SettingsRow label="Base image">
<span className="font-mono text-xs text-foreground-light">{runtime.baseImage}</span>
</SettingsRow>
)}
{runtime !== undefined && (
<SettingsRow label="Entrypoint">
<span className="font-mono text-xs text-foreground-light">
{runtime.entrypoint}
</span>
</SettingsRow>
)}
<SettingsRow label="Listening port">
<span className="font-mono text-xs text-foreground-light">
$PORT → {LISTENING_PORT}
</span>
</SettingsRow>
</div>
</PageSectionContent>
</PageSection>
<PageSection>
<PageSectionMeta>
<PageSectionSummary>
<PageSectionTitle>Resources</PageSectionTitle>
</PageSectionSummary>
</PageSectionMeta>
<PageSectionContent>
<div className="rounded-md border border-default bg-surface-100">
<SettingsRow label="Size" isFirst>
{formatSize(worker.size)}
</SettingsRow>
<SettingsRow label="Instances">{worker.declaredInstances}</SettingsRow>
<SettingsRow label="Access">
{worker.access === 'public' ? (
<Badge variant="success">Public</Badge>
) : (
<Badge>Private</Badge>
)}
</SettingsRow>
<SettingsRow label="Region">
<span className="text-foreground-light">
{WORKERS_REGION_LABEL} <span className="text-foreground-lighter">(locked)</span>
</span>
</SettingsRow>
</div>
</PageSectionContent>
</PageSection>
<PageSection>
<PageSectionMeta>
<PageSectionSummary>
<PageSectionTitle>How to call</PageSectionTitle>
<PageSectionDescription>
Call the worker over its gateway URL. Pass your project API key as a bearer token.
</PageSectionDescription>
</PageSectionSummary>
</PageSectionMeta>
<PageSectionContent>
<WorkerSnippetTabs
input={{
name: worker.name,
runtime: worker.runtime,
size: worker.size,
access: worker.access,
instances: worker.declaredInstances,
}}
tabs={['curl', 'js', 'python']}
/>
</PageSectionContent>
</PageSection>
<PageSection>
<PageSectionMeta>
<PageSectionSummary>
<PageSectionTitle>Develop locally</PageSectionTitle>
<PageSectionDescription>
Manage this worker from the Supabase CLI.
</PageSectionDescription>
</PageSectionSummary>
</PageSectionMeta>
<PageSectionContent>
<div className="space-y-4 rounded-md border border-default bg-surface-100 p-4">
{commands.map((command) => (
<WorkerCommandLine
key={command.command}
comment={command.comment}
command={command.command}
/>
))}
</div>
</PageSectionContent>
</PageSection>
</PageContainer>
)
}
@@ -1,169 +0,0 @@
import { ReactNode } from 'react'
import { Badge } from 'ui'
import { PageContainer } from 'ui-patterns/PageContainer'
import {
PageSection,
PageSectionContent,
PageSectionDescription,
PageSectionMeta,
PageSectionSummary,
PageSectionTitle,
} from 'ui-patterns/PageSection'
import { RuntimeBadge } from '../RuntimeBadge'
import { WorkerCommandLine } from '../WorkerCommandLine'
import { LISTENING_PORT, WORKERS_REGION_LABEL } from '../Workers.constants'
import type { Worker } from '../Workers.types'
import { formatSize, getRuntimeMeta } from '../Workers.utils'
import { buildWorkerCliCommands } from '../workerSnippets'
import { WorkerSnippetTabs } from '../WorkerSnippetTabs'
interface WorkerSettingsTabProps {
worker: Worker
}
const SettingsRow = ({
label,
children,
isFirst,
}: {
label: string
children: ReactNode
isFirst?: boolean
}) => (
<div
className={`flex items-center justify-between px-4 py-3 ${
isFirst ? '' : 'border-t border-default'
}`}
>
<span className="text-sm text-foreground-light">{label}</span>
<span className="text-sm text-foreground">{children}</span>
</div>
)
export const WorkerSettingsTab = ({ worker }: WorkerSettingsTabProps) => {
const runtime = getRuntimeMeta(worker.runtime)
const commands = buildWorkerCliCommands(worker.name)
const snippetInput = {
name: worker.name,
runtime: worker.runtime,
size: worker.size,
access: worker.access,
instances: worker.declaredInstances,
}
return (
<>
<PageContainer size="small">
<PageSection>
<PageSectionMeta>
<PageSectionSummary>
<PageSectionTitle>Container</PageSectionTitle>
<PageSectionDescription>
The runtime image and entrypoint resolved for this worker.
</PageSectionDescription>
</PageSectionSummary>
</PageSectionMeta>
<PageSectionContent>
<div className="rounded-md border border-default bg-surface-100">
<SettingsRow label="Runtime" isFirst>
<RuntimeBadge runtime={worker.runtime} />
</SettingsRow>
{worker.imageVersion !== undefined && (
<SettingsRow label="Image version">
<span className="font-mono text-xs text-foreground-light">
{worker.imageVersion}
</span>
</SettingsRow>
)}
{runtime !== undefined && (
<SettingsRow label="Base image">
<span className="font-mono text-xs text-foreground-light">
{runtime.baseImage}
</span>
</SettingsRow>
)}
{runtime !== undefined && (
<SettingsRow label="Entrypoint">
<span className="font-mono text-xs text-foreground-light">
{runtime.entrypoint}
</span>
</SettingsRow>
)}
<SettingsRow label="Listening port">
<span className="font-mono text-xs text-foreground-light">
$PORT → {LISTENING_PORT}
</span>
</SettingsRow>
</div>
</PageSectionContent>
</PageSection>
<PageSection>
<PageSectionMeta>
<PageSectionSummary>
<PageSectionTitle>Resources</PageSectionTitle>
</PageSectionSummary>
</PageSectionMeta>
<PageSectionContent>
<div className="rounded-md border border-default bg-surface-100">
<SettingsRow label="Size" isFirst>
{formatSize(worker.size)}
</SettingsRow>
<SettingsRow label="Instances">{worker.declaredInstances}</SettingsRow>
<SettingsRow label="Access">
{worker.access === 'public' ? (
<Badge variant="success">Public</Badge>
) : (
<Badge>Private</Badge>
)}
</SettingsRow>
<SettingsRow label="Region">
<span className="text-foreground-light">
{WORKERS_REGION_LABEL} <span className="text-foreground-lighter">(locked)</span>
</span>
</SettingsRow>
</div>
</PageSectionContent>
</PageSection>
<PageSection>
<PageSectionMeta>
<PageSectionSummary>
<PageSectionTitle>Invoke worker</PageSectionTitle>
<PageSectionDescription>
Call the worker over its gateway URL. Pass your project API key as a bearer token.
</PageSectionDescription>
</PageSectionSummary>
</PageSectionMeta>
<PageSectionContent>
<WorkerSnippetTabs input={snippetInput} tabs={['curl', 'js', 'python']} />
</PageSectionContent>
</PageSection>
<PageSection>
<PageSectionMeta>
<PageSectionSummary>
<PageSectionTitle>Develop locally</PageSectionTitle>
<PageSectionDescription>
Manage this worker from the Supabase CLI.
</PageSectionDescription>
</PageSectionSummary>
</PageSectionMeta>
<PageSectionContent>
<div className="space-y-4 rounded-md border border-default bg-surface-100 p-4">
{commands.map((command) => (
<WorkerCommandLine
key={command.command}
comment={command.comment}
command={command.command}
/>
))}
</div>
</PageSectionContent>
</PageSection>
</PageContainer>
</>
)
}