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
5 changes: 5 additions & 0 deletions src/lib/components/task-queue/worker-table-row.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script lang="ts">
import Badge from '$lib/holocene/badge.svelte';
import { translate } from '$lib/i18n/translate';
import type { WorkerInfo } from '$lib/types';
import { routeForWorkerInstance } from '$lib/utilities/route-for';
Expand Down Expand Up @@ -84,6 +85,10 @@
>{worker.workerHeartbeat?.nexusTaskSlotsInfo?.currentUsedSlots ?? 0} / {worker
.workerHeartbeat?.nexusTaskSlotsInfo?.currentAvailableSlots ?? 0}</td
>
{:else if label === translate('workers.type')}
<td><Badge>{translate('workers.type-traditional')}</Badge></td>
{:else}
<td></td>
{/if}
{/each}
</tr>
87 changes: 87 additions & 0 deletions src/lib/components/workers/serverless-worker-table-row.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<script lang="ts">
import Badge from '$lib/holocene/badge.svelte';
import Icon from '$lib/holocene/icon/icon.svelte';
import {
Menu,
MenuButton,
MenuContainer,
MenuItem,
} from '$lib/holocene/menu';
import { translate } from '$lib/i18n/translate';
import type { ServerlessWorker } from '$lib/types/serverless-workers';
import {
routeForServerlessWorker,
routeForServerlessWorkerEdit,
} from '$lib/utilities/route-for';

type Props = {
worker: ServerlessWorker;
namespace: string;
columns: { label: string }[];
};

let { worker, namespace, columns }: Props = $props();

const detailHref = $derived(
routeForServerlessWorker({ namespace, id: worker.id }),
);
const editHref = $derived(
routeForServerlessWorkerEdit({ namespace, id: worker.id }),
);
const menuId = $derived(`serverless-worker-menu-${worker.id}`);
</script>

<tr>
{#each columns as { label } (label)}
{#if label === translate('workers.type')}
<td
><Badge type="primary">{translate('workers.type-serverless')}</Badge
></td
>
{:else if label === translate('workers.status')}
<td
><Badge type={worker.status === 'active' ? 'success' : 'warning'}
>{worker.status}</Badge
></td
>
{:else if label === translate('workers.instance')}
<td>
<a href={detailHref} class="text-blue-700 hover:underline">
{worker.name}
</a>
</td>
{:else if label === translate('workers.task-queue')}
<td>{worker.taskQueue}</td>
{:else if label === translate('workers.identity')}
<td class="font-mono text-xs">{worker.lambdaArn.split(':').pop()}</td>
{:else if label === translate('workers.host-name')}
<td>{worker.region}</td>
{:else if label === ''}
<td class="w-10 text-right">
<MenuContainer>
<MenuButton
controls={menuId}
hasIndicator={false}
variant="ghost"
size="xs"
>
<Icon name="vertical-ellipsis" />
</MenuButton>
<Menu id={menuId} position="right">
<MenuItem href={detailHref}>
{translate('common.view')}
</MenuItem>
<MenuItem href={editHref}>
{translate('workers.edit-serverless-worker')}
</MenuItem>
<MenuItem destructive>
{translate('common.delete')}
</MenuItem>
</Menu>
</MenuContainer>
</td>
{:else}
<td class="text-secondary">—</td>
{/if}
{/each}
</tr>
25 changes: 22 additions & 3 deletions src/lib/components/workers/worker-search-table.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,39 @@
import EmptyState from '$lib/holocene/empty-state.svelte';
import PaginatedTable from '$lib/holocene/table/paginated-table/api-paginated.svelte';
import { translate } from '$lib/i18n/translate';
import { getServerlessWorkers } from '$lib/services/serverless-worker-service';
import { fetchPaginatedWorkers } from '$lib/services/worker-service';

import ServerlessWorkerTableRow from './serverless-worker-table-row.svelte';

let { namespace } = $props();

const query = $derived(page.url.searchParams.get('query') || '');
const serverlessWorkers = getServerlessWorkers();

const columns = [
{ label: translate('workers.status') },
{ label: translate('workers.instance') },
{ label: translate('workers.task-queue') },
{ label: translate('workers.status') },
{ label: translate('workers.type') },
{ label: translate('workers.identity') },
{ label: translate('workers.host-name') },
{ label: translate('workers.workflow-task-slots') },
{ label: translate('workers.activity-task-slots') },
{ label: translate('workers.nexus-task-slots') },
{ label: translate('workers.sdk') },
{ label: '' },
];

const onFetch = $derived(() => fetchPaginatedWorkers({ namespace, query }));
const onFetch = $derived(() =>
fetchPaginatedWorkers({ namespace, query }).then((fetcher) => {
return (pageSize: number, token: string) =>
fetcher(pageSize, token).catch(() => ({
items: [],
nextPageToken: '',
}));
}),
);
</script>

{#key query}
Expand All @@ -49,9 +63,14 @@
{#each visibleItems as worker, i (i)}
<WorkerTableRow {worker} {columns} {namespace} filterable />
{/each}
{#each serverlessWorkers as sw (sw.id)}
<ServerlessWorkerTableRow worker={sw} {columns} {namespace} />
{/each}

<svelte:fragment slot="empty">
<EmptyState title={translate('workers.empty-state-title')}></EmptyState>
{#if serverlessWorkers.length === 0}
<EmptyState title={translate('workers.empty-state-title')}></EmptyState>
{/if}
</svelte:fragment>
</PaginatedTable>
{/key}
119 changes: 119 additions & 0 deletions src/lib/i18n/locales/en/workers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,123 @@ export const Strings = {
'Viewing workers for pinned Build ID. Go to the Task Queue page to view all workers.',
'viewing-auto-upgrade-build-ids':
'Viewing workers for current and ramping Build IDs. Go to the Task Queue page to view all workers.',
configuration: 'Configuration',
type: 'Type',
'type-traditional': 'Traditional',
'type-serverless': 'Serverless',
'serverless-workers': 'Serverless Workers',
'serverless-workers-description':
'Manage serverless worker configurations for this namespace.',
'serverless-worker': 'Serverless Worker',
'config-serverless': 'Serverless Workers',
'config-general': 'General',
'config-policies': 'Policies',
'config-alerts': 'Alerts',
'serverless-empty-state': 'No Serverless Workers Configured',
'serverless-empty-state-description':
'Create a serverless worker to get started.',
'create-serverless-worker': 'Create Serverless Worker',
'back-to-configuration': 'Back to Configuration',
'serverless-detail-title': 'Serverless Worker Details',
'lambda-arn': 'Lambda ARN',
'iam-role-arn': 'IAM Role ARN',
region: 'Region',
'max-workers': 'Max Workers',
'max-concurrent-activities': 'Max Concurrent Activities',
'max-task-queue-rate': 'Max Task Queue Rate',
'idle-timeout': 'Idle Timeout',
'created-at': 'Created',
'updated-at': 'Last Updated',
'edit-serverless-worker': 'Edit',
'edit-serverless-worker-title': 'Edit Serverless Worker',
'connection-details': 'Connection Details',
'save-changes': 'Save Changes',
'delete-serverless-worker': 'Delete Serverless Worker',
'delete-confirm': 'Are you sure you want to delete this serverless worker?',
'create-serverless-title': 'Create Serverless Worker',
'name-label': 'Name',
'name-hint': 'A unique name for this serverless worker configuration.',
'name-placeholder': 'my-serverless-worker',
'lambda-arn-label': 'Lambda ARN',
'lambda-arn-hint': 'The ARN of the AWS Lambda function to invoke.',
'lambda-arn-placeholder':
'arn:aws:lambda:us-east-1:123456789:function:my-function',
'iam-role-label': 'IAM Role ARN',
'iam-role-hint': 'The ARN of the IAM role for Temporal to assume.',
'iam-role-placeholder':
'arn:aws:iam::123456789:role/temporal-serverless-role',
'region-label': 'Region',
'region-hint': 'The AWS region where the Lambda function is deployed.',
'task-queue-label': 'Task Queue',
'task-queue-hint':
'The Temporal task queue this serverless worker will poll.',
'task-queue-placeholder': 'my-task-queue',
'advanced-config': 'Advanced Configuration',
'max-workers-label': 'Max Workers',
'max-workers-hint': 'Maximum number of concurrent worker instances.',
'max-concurrent-label': 'Max Concurrent Activities',
'max-concurrent-hint': 'Maximum concurrent activities per worker.',
'max-rate-label': 'Max Task Queue Activities Per Second',
'max-rate-hint': 'Rate limit for task queue polling.',
'idle-timeout-label': 'Idle Timeout (seconds)',
'idle-timeout-hint': 'Time before an idle worker is terminated.',
'validation-checking': 'Checking...',
'validation-function-found': 'Lambda function found',
'validation-function-not-found': 'Lambda function not found',
'validation-permissions-verified': 'Permissions verified',
'validation-permissions-missing': 'Missing required permissions',
'validation-region-match': 'Region matches namespace',
'validation-region-mismatch': 'Cross-region: may increase latency',
'validation-queue-available': 'Task queue name available',
'validation-queue-exists': 'Task queue already in use',
'compute-provider': 'Compute Provider',
'compute-provider-description':
'Select how your worker activities will be executed.',
'provider-lambda': 'AWS Lambda',
'provider-lambda-description':
'Run activities as serverless Lambda function invocations.',
'provider-coming-soon': 'More providers coming soon',
'provider-coming-soon-description':
'Support for additional compute providers is planned.',
'setup-guide-title': 'Prerequisites: AWS Setup Guide',
'setup-guide-intro':
'Before creating a serverless worker, you need a Lambda function and an IAM role in your AWS account.',
'setup-guide-lambda-console': 'Open AWS Lambda Console',
'setup-guide-iam-console': 'Open AWS IAM Console',
'setup-guide-iam-note':
'The IAM role must have a trust policy allowing Temporal to assume it. Use the templates below as a starting point.',
'validation-checking-lambda': 'Validating Lambda function access...',
'validation-checking-iam': 'Validating IAM role permissions...',
'validation-checking-region': 'Checking region compatibility...',
'validation-checking-queue': 'Checking task queue availability...',
'validation-function-not-found-detail':
'Lambda function not found. Verify the ARN and ensure the function exists in the specified region.',
'validation-permissions-missing-detail':
'IAM role lacks required permissions. Ensure the role has a trust policy allowing Temporal to assume it.',
'validation-queue-new':
'This will create a new task queue. Serverless workers require a dedicated task queue.',
'serverless-empty-title': 'Get Started with Serverless Workers',
'serverless-empty-description':
'Serverless workers execute your Temporal activities as AWS Lambda function invocations, eliminating the need to manage worker infrastructure.',
'serverless-empty-prereq-title': 'What you need:',
'serverless-empty-prereq-lambda':
'An AWS Lambda function with your activity code',
'serverless-empty-prereq-iam':
'An IAM role allowing Temporal to invoke your function',
'serverless-empty-prereq-queue':
'A task queue name for your serverless worker',
'serverless-docs-link': 'Learn more about serverless workers',
'open-lambda-console': 'Open in Lambda Console',
'open-iam-console': 'Open in IAM Console',
'copy-arn': 'Copy ARN',
'copy-task-queue': 'Copy task queue name',
copied: 'Copied!',
'lambda-arn-help':
'The Amazon Resource Name of your Lambda function. Use an unqualified ARN (no version/alias suffix).',
'iam-role-help':
'Temporal assumes this role to invoke your Lambda function. The role needs a trust policy for Temporal and permission to invoke the function.',
'task-queue-help':
'Serverless workers require a dedicated task queue. You cannot share a task queue between serverless and traditional workers.',
'region-help':
'Choose the AWS region where your Lambda function is deployed. For best performance, match your Temporal namespace region.',
} as const;
24 changes: 24 additions & 0 deletions src/lib/pages/serverless-worker-create.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script lang="ts">
import { translate } from '$lib/i18n/translate';
import ServerlessWorkerForm from '$lib/pages/serverless-worker-form.svelte';
import { createServerlessWorker } from '$lib/services/serverless-worker-service';
import type { ServerlessWorkerCreateInput } from '$lib/types/serverless-workers';
import { routeForWorkers } from '$lib/utilities/route-for';

type Props = {
namespace: string;
onSuccess: () => void;
};

let { namespace, onSuccess }: Props = $props();
</script>

<ServerlessWorkerForm
{namespace}
submitButtonText={translate('workers.create-serverless-worker')}
cancelHref={routeForWorkers({ namespace })}
onSubmit={(data) => {
createServerlessWorker(data as unknown as ServerlessWorkerCreateInput);
onSuccess();
}}
/>
Loading