+
+
+
+ onUpdate({ ...component, name: value })}
+ />
+ onUpdate({ ...component, serviceName: value })}
+ />
+
+
+
item.id !== component.id).map((item) => item.id).join(', ')}
+ isOptional
+ onChange={(value) => {
+ const dependencies = value
+ .split(',')
+ .map((entry) => entry.trim())
+ .filter(Boolean);
+ onUpdate({ ...component, dependencies });
+ }}
+ />
+
+
+
+
+ ({
+ value: containerType.name,
+ label: `${containerType.name} ($${containerType.monthlyBudgetPerWorker}/mo)`,
+ }))}
+ onChange={(value) => {
+ const selected = genericContainerTypes.find((item) => item.name === value);
+ if (!selected) {
+ return;
+ }
+
+ onUpdate({
+ ...component,
+ containerTypeName: selected.name,
+ jobType: selected.jobType,
+ });
+ }}
+ />
+
+ onUpdate({ ...component, internalPort: Number(value) })}
+ />
+
+ onUpdate({ ...component, paymentMonthsCount: Number(value) })}
+ />
+
+
+
+ {runtimeKind === 'container' ? (
+
+ onUpdate({ ...component, image: value })}
+ />
+
+ ) : (
+
+
+ onUpdate({ ...component, workerRepositoryUrl: value })}
+ />
+ onUpdate({ ...component, workerImage: value })}
+ />
+
+
+
+
+ onUpdate({
+ ...component,
+ workerRepositoryVisibility: value as 'public' | 'private',
+ })
+ }
+ />
+ onUpdate({ ...component, workerUsername: value })}
+ />
+ onUpdate({ ...component, workerAccessToken: value })}
+ />
+
+
+
+
+
+
+ )}
+
+
+
+
+ onUpdate({
+ ...component,
+ networkMode: value as StackComponent['networkMode'],
+ publicPort: value === 'public' ? component.publicPort || component.internalPort : undefined,
+ })
+ }
+ />
+
+
+ onUpdate({
+ ...component,
+ publicPort: value ? Number(value) : undefined,
+ })
+ }
+ />
+
+
+ onUpdate({
+ ...component,
+ tunnelingToken: value || undefined,
+ })
+ }
+ />
+
+
+
+
+ {!!component.env.length && (
+
+ {component.env.map((entry, envIndex) => (
+
+
+
+
+ {
+ const env = [...component.env];
+ env[envIndex] = { ...entry, key: event.target.value };
+ onUpdate({ ...component, env });
+ }}
+ />
+
+ {
+ const env = [...component.env];
+ env[envIndex] = { ...entry, value: event.target.value };
+ onUpdate({ ...component, env });
+ }}
+ />
+
+
+
{
+ const env = component.env.filter((_, index) => index !== envIndex);
+ onUpdate({ ...component, env });
+ }}
+ />
+
+ ))}
+
+ )}
+
+
onUpdate({ ...component, env: [...component.env, { key: '', value: '' }] })}
+ remove={(index) => {
+ if (typeof index !== 'number') {
+ return;
+ }
+
+ const env = component.env.filter((_, envIndex) => envIndex !== index);
+ onUpdate({ ...component, env });
+ }}
+ />
+
+
+ );
+}
+
+export default function StackManager({
+ projectHash,
+ runningJobs,
+ mode = 'overview',
+ onDone,
+ projectName,
+}: {
+ projectHash: string;
+ runningJobs?: RunningJobWithDetails[];
+ mode?: 'overview' | 'create';
+ onDone?: () => void;
+ projectName?: string;
+}) {
+ const { confirm } = useInteractionContext() as InteractionContextType;
+ const { setProjectOverviewTab, setProjectPage, setStep } = useDeploymentContext() as DeploymentContextType;
+
+ const stacks = useLiveQuery(() => db.stacks.where('projectHash').equals(projectHash).toArray(), [projectHash], []);
+
+ const inferredTargetNode = useMemo