|
|
@@ -0,0 +1,185 @@
|
|
|
+<script lang="ts">
|
|
|
+ import * as Field from '$lib/components/controls/field';
|
|
|
+ import { Input } from '$lib/components/controls/input';
|
|
|
+ import * as Select from '$lib/components/controls/select';
|
|
|
+ import { Textarea } from '$lib/components/controls/textarea';
|
|
|
+
|
|
|
+ import type { DeploymentFormValue } from './types';
|
|
|
+
|
|
|
+ interface Runtime {
|
|
|
+ value: string;
|
|
|
+ label: string;
|
|
|
+ versions: {
|
|
|
+ value: string;
|
|
|
+ label: string;
|
|
|
+ }[];
|
|
|
+ }
|
|
|
+
|
|
|
+ let {
|
|
|
+ disabled = false,
|
|
|
+ form = $bindable(),
|
|
|
+ runtimes = [],
|
|
|
+ imageRequired = false,
|
|
|
+ }: {
|
|
|
+ disabled?: boolean;
|
|
|
+ form: DeploymentFormValue;
|
|
|
+ runtimes?: Runtime[];
|
|
|
+ imageRequired?: boolean;
|
|
|
+ } = $props();
|
|
|
+
|
|
|
+ const selectedRuntime = $derived(
|
|
|
+ form.runtime === ''
|
|
|
+ ? { value: '', label: '<Custom>', versions: [{ value: '', label: '<Custom>' }] }
|
|
|
+ : runtimes.find((r) => r.value === form.runtime),
|
|
|
+ );
|
|
|
+ const selectedRuntimeVersion = $derived(
|
|
|
+ selectedRuntime?.versions.find((v) => v.value === form.runtimeVersion) ?? {
|
|
|
+ value: '',
|
|
|
+ label: '<Default>',
|
|
|
+ },
|
|
|
+ );
|
|
|
+
|
|
|
+ const formatEnv = (env: { name: string; value: string }[] | undefined): string => {
|
|
|
+ if (!env || env.length === 0) return '';
|
|
|
+ return env.map((entry) => `${entry.name}=${entry.value}`).join('\n');
|
|
|
+ };
|
|
|
+
|
|
|
+ const parseEnv = (text: string): { name: string; value: string }[] | undefined => {
|
|
|
+ const parsed = text
|
|
|
+ .split('\n')
|
|
|
+ .map((line) => line.trim())
|
|
|
+ .filter((line) => line.length > 0)
|
|
|
+ .map((line) => {
|
|
|
+ const separatorIdx = line.indexOf('=');
|
|
|
+ if (separatorIdx === -1) {
|
|
|
+ return { name: line, value: '' };
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ name: line.slice(0, separatorIdx).trim(),
|
|
|
+ value: line.slice(separatorIdx + 1),
|
|
|
+ };
|
|
|
+ })
|
|
|
+ .filter((entry) => entry.name.length > 0);
|
|
|
+
|
|
|
+ return parsed.length > 0 ? parsed : undefined;
|
|
|
+ };
|
|
|
+</script>
|
|
|
+
|
|
|
+<Field.Field>
|
|
|
+ <Field.Label for="runtime">Runtime</Field.Label>
|
|
|
+ <Select.Root {disabled} type="single" bind:value={form.runtime}>
|
|
|
+ <Select.Trigger id="runtime" class="w-full"
|
|
|
+ >{selectedRuntime?.label ?? form.runtime}</Select.Trigger
|
|
|
+ >
|
|
|
+ <Select.Content>
|
|
|
+ <Select.Item label="<Custom>" value="" />
|
|
|
+ {#each runtimes as r (r.value)}
|
|
|
+ <Select.Item label={r.label} value={r.value} />
|
|
|
+ {/each}
|
|
|
+ </Select.Content>
|
|
|
+ </Select.Root>
|
|
|
+</Field.Field>
|
|
|
+<Field.Field>
|
|
|
+ <Field.Label for="runtimeVersion">Runtime Version</Field.Label>
|
|
|
+ <Select.Root {disabled} type="single" bind:value={form.runtimeVersion}>
|
|
|
+ <Select.Trigger id="runtimeVersion" class="w-full">
|
|
|
+ {selectedRuntimeVersion?.label ?? form.runtimeVersion}
|
|
|
+ </Select.Trigger>
|
|
|
+ <Select.Content>
|
|
|
+ <Select.Item label="<Default>" value="" />
|
|
|
+ {#each selectedRuntime?.versions ?? [] as v (v.value)}
|
|
|
+ <Select.Item label={v.label} value={v.value} />
|
|
|
+ {/each}
|
|
|
+ </Select.Content>
|
|
|
+ </Select.Root>
|
|
|
+</Field.Field>
|
|
|
+<Field.Field>
|
|
|
+ <Field.Label for="replicas">Replicas</Field.Label>
|
|
|
+ <Input
|
|
|
+ id="replicas"
|
|
|
+ {disabled}
|
|
|
+ min={1}
|
|
|
+ placeholder="1"
|
|
|
+ type="number"
|
|
|
+ bind:value={form.replicas}
|
|
|
+ />
|
|
|
+</Field.Field>
|
|
|
+<Field.Field>
|
|
|
+ <Field.Label for="image">Container Image</Field.Label>
|
|
|
+ <Input
|
|
|
+ id="image"
|
|
|
+ {disabled}
|
|
|
+ placeholder={imageRequired ? undefined : 'Default'}
|
|
|
+ required={imageRequired}
|
|
|
+ type="text"
|
|
|
+ bind:value={form.image}
|
|
|
+ />
|
|
|
+</Field.Field>
|
|
|
+<Field.Field>
|
|
|
+ <Field.Label for="command">Container Command</Field.Label>
|
|
|
+ <Textarea
|
|
|
+ id="command"
|
|
|
+ {disabled}
|
|
|
+ placeholder="Runtime default"
|
|
|
+ rows={4}
|
|
|
+ bind:value={() => form.command?.join('\n') || '', (v) => v.split('\n')}
|
|
|
+ />
|
|
|
+</Field.Field>
|
|
|
+<Field.Field>
|
|
|
+ <Field.Label for="args">Container Args</Field.Label>
|
|
|
+ <Textarea
|
|
|
+ id="args"
|
|
|
+ {disabled}
|
|
|
+ placeholder="Runtime default"
|
|
|
+ rows={4}
|
|
|
+ bind:value={() => form.args?.join('\n') || '', (v) => v.split('\n')}
|
|
|
+ />
|
|
|
+</Field.Field>
|
|
|
+<Field.Field>
|
|
|
+ <Field.Label for="args">Container Extra Args</Field.Label>
|
|
|
+ <Textarea
|
|
|
+ id="extraArgs"
|
|
|
+ {disabled}
|
|
|
+ rows={4}
|
|
|
+ bind:value={() => form.extraArgs?.join('\n') || '', (v) => v.split('\n')}
|
|
|
+ />
|
|
|
+</Field.Field>
|
|
|
+<Field.Field>
|
|
|
+ <Field.Label for="env">Environment Variables</Field.Label>
|
|
|
+ <Textarea
|
|
|
+ id="env"
|
|
|
+ {disabled}
|
|
|
+ placeholder="KEY=VALUE"
|
|
|
+ rows={6}
|
|
|
+ bind:value={() => formatEnv(form.env), (v) => (form.env = parseEnv(v))}
|
|
|
+ />
|
|
|
+</Field.Field>
|
|
|
+<Field.Field>
|
|
|
+ <Field.Label for="port">Port</Field.Label>
|
|
|
+ <Input
|
|
|
+ id="port"
|
|
|
+ {disabled}
|
|
|
+ min={0}
|
|
|
+ placeholder="Runtime default"
|
|
|
+ type="number"
|
|
|
+ bind:value={form.port}
|
|
|
+ />
|
|
|
+</Field.Field>
|
|
|
+<div class="grid grid-cols-2 gap-4">
|
|
|
+ <Field.Field>
|
|
|
+ <Field.Label for="cpu">CPU</Field.Label>
|
|
|
+ <Input id="cpu" {disabled} placeholder="Unbound" bind:value={form.cpu} />
|
|
|
+ </Field.Field>
|
|
|
+ <Field.Field>
|
|
|
+ <Field.Label for="memory">Memory</Field.Label>
|
|
|
+ <Input id="memory" {disabled} placeholder="Unbound" bind:value={form.memory} />
|
|
|
+ </Field.Field>
|
|
|
+ <Field.Field>
|
|
|
+ <Field.Label for="gpuModel">GPU Model</Field.Label>
|
|
|
+ <Input id="gpuModel" {disabled} placeholder="Any" bind:value={form.gpuModel} />
|
|
|
+ </Field.Field>
|
|
|
+ <Field.Field>
|
|
|
+ <Field.Label for="gpu">GPU</Field.Label>
|
|
|
+ <Input id="gpu" {disabled} placeholder="0" bind:value={form.gpu} />
|
|
|
+ </Field.Field>
|
|
|
+</div>
|