Skip to content

Commit

Permalink
Add support for Kubernetes backend (#3)
Browse files Browse the repository at this point in the history
Co-authored-by: Jonatan Kłosko <[email protected]>
  • Loading branch information
mruoss and jonatanklosko authored Sep 5, 2024
1 parent e64e027 commit 000fa97
Show file tree
Hide file tree
Showing 11 changed files with 698 additions and 350 deletions.
404 changes: 90 additions & 314 deletions assets/runner_cell/src/App.js

Large diffs are not rendered by default.

95 changes: 95 additions & 0 deletions assets/runner_cell/src/Fly.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React from "react";
import { MultiSelectField, SelectField, TextField } from "./form_elements";
import Pool from "./Pool";

const FLY_CPU_KIND_OPTIONS = ["shared", "performance"].map((kind) => ({
value: kind,
label: kind,
}));

const FLY_GPU_KIND_OPTIONS = [{ value: "", label: "None" }].concat(
["a10", "a100-pcie-40gb", "a100-sxm4-80gb", "l40s"].map((kind) => ({
value: kind,
label: kind,
}))
);

export default function Fly({
fields,
allEnvs,
handleBlur,
handleChange,
handleFieldChange,
}) {
return (
<div>
<Pool
fields={fields}
handleChange={handleChange}
handleBlur={handleBlur}
/>
<div className="w-full border-t border-gray-200" />
<div className="flex flex-wrap gap-2 p-4">
<SelectField
name="fly_cpu_kind"
label="CPU kind"
value={fields.fly_cpu_kind}
onChange={handleChange}
options={FLY_CPU_KIND_OPTIONS}
/>
<div className="w-36">
<TextField
type="number"
name="fly_cpus"
label="CPUs"
value={fields.fly_cpus}
onChange={(event) => handleChange(event, false)}
onBlur={handleBlur}
min="1"
required
/>
</div>
<div className="w-36">
<TextField
type="number"
name="fly_memory_gb"
label="Memory (GB)"
value={fields.fly_memory_gb}
onChange={(event) => handleChange(event, false)}
onBlur={handleBlur}
min="1"
required
/>
</div>
<SelectField
name="fly_gpu_kind"
label="GPU kind"
value={fields.fly_gpu_kind || ""}
onChange={handleChange}
options={FLY_GPU_KIND_OPTIONS}
/>
<div className="w-36">
<TextField
type="number"
name="fly_gpus"
label="GPUs"
value={fields.fly_gpus}
onChange={(event) => handleChange(event, false)}
onBlur={handleBlur}
min="1"
/>
</div>
</div>
<div className="w-full border-t border-gray-200" />
<div className="flex flex-wrap gap-2 p-4">
<MultiSelectField
name="fly_envs"
label="Env vars"
value={fields.fly_envs}
onChange={(value) => handleFieldChange("fly_envs", value)}
options={allEnvs.map((env) => ({ label: env, value: env }))}
/>
</div>
</div>
);
}
14 changes: 14 additions & 0 deletions assets/runner_cell/src/K8s.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from "react";
import Pool from "./Pool";

export default function K8s({ fields, handleChange, handleBlur }) {
return (
<div>
<Pool
fields={fields}
handleChange={handleChange}
handleBlur={handleBlur}
/>
</div>
);
}
45 changes: 45 additions & 0 deletions assets/runner_cell/src/Pool.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from "react";
import { SelectField, TextField } from "./form_elements";

const Pool = ({ fields, handleChange, handleBlur }) => (
<div className="flex flex-wrap gap-2 p-4">
<div className="w-36">
<TextField
type="number"
name="min"
label="Min runners"
value={fields.min}
onChange={(event) => handleChange(event, false)}
onBlur={handleBlur}
min="0"
required
/>
</div>
<div className="w-36">
<TextField
type="number"
name="max"
label="Max runners"
value={fields.max}
onChange={(event) => handleChange(event, false)}
onBlur={handleBlur}
min="1"
required
/>
</div>
<div className="w-36">
<TextField
type="number"
name="max_concurrency"
label="Max concurrency"
value={fields.max_concurrency}
onChange={(event) => handleChange(event, false)}
onBlur={handleBlur}
min="1"
required
/>
</div>
</div>
);

export default Pool;
192 changes: 192 additions & 0 deletions assets/runner_cell/src/form_elements.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import React from "react";
import { RiCloseLine, RiArrowDownSLine } from "@remixicon/react";
import classNames from "classnames";

export function SelectField({
label = null,
value,
className,
options = [],
optionGroups = [],
...props
}) {
function renderOptions(options) {
return options.map((option) => (
<option key={option.value || ""} value={option.value || ""}>
{option.label}
</option>
));
}

return (
<div className="flex flex-col">
{label && (
<label className="color-gray-800 mb-0.5 block text-sm font-medium">
{label}
</label>
)}
<div className="relative block">
<select
{...props}
value={value}
className={classNames([
"w-full appearance-none rounded-lg border border-gray-200 bg-gray-50 px-3 py-2 pr-7 text-sm text-gray-600 placeholder-gray-400 focus:outline-none",
className,
])}
>
{renderOptions(options)}
{optionGroups.map(({ label, options }) => (
<optgroup key={label} label={label}>
{renderOptions(options)}
</optgroup>
))}
</select>
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-500">
<RiArrowDownSLine size={16} />
</div>
</div>
</div>
);
}

export function MultiSelectField({
label = null,
value,
className,
options = [],
onChange,
...props
}) {
const availableOptions = options.filter(
(option) => !value.includes(option.value)
);

function labelForValue(value) {
const option = options.find((option) => option.value === value);

if (option) {
return option.label;
} else {
return value;
}
}

function handleSelectChange(event) {
const subvalue = event.target.value;
const newValue = value.concat([subvalue]).sort();
onChange && onChange(newValue);
}

function handleDelete(subvalue) {
const newValue = value.filter(
(otherSubvalue) => otherSubvalue !== subvalue
);
onChange && onChange(newValue);
}

return (
<div className="flex flex-col min-w-36">
{label && (
<label className="color-gray-800 mb-0.5 block text-sm font-medium">
{label}
</label>
)}
<div
className={classNames([
"relative w-full min-h-[38px] flex rounded-lg border border-gray-200 bg-gray-50 px-3 py-1.5 pr-0 text-sm text-gray-600 placeholder-gray-400",
className,
])}
>
<div className="flex flex-wrap gap-1">
{value.map((value) => (
<div
key={value}
className="py-0.5 px-2 flex gap-1 items-center rounded-lg bg-gray-200"
>
<span>{labelForValue(value)}</span>
<button
className="rounded-lg hover:bg-gray-300"
onClick={() => handleDelete(value)}
>
<RiCloseLine size={12} />
</button>
</div>
))}
</div>
<select
{...props}
value=""
onChange={handleSelectChange}
className="grow min-w-8 w-0 opacity-0 appearance-none focus:outline-none"
>
<option value="" disabled></option>
{availableOptions.map((option) => (
<option key={option.value || ""} value={option.value || ""}>
{option.label}
</option>
))}
</select>
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-500">
<RiArrowDownSLine size={16} />
</div>
</div>
</div>
);
}

export function FieldWrapper({ children }) {
return <div className="flex items-center gap-1.5">{children}</div>;
}

export function InlineLabel({ label }) {
return (
<label className="block text-sm font-medium uppercase text-gray-600">
{label}
</label>
);
}

export function TextField({
label = null,
value,
type = "text",
className,
required = false,
fullWidth = false,
inputRef,
startAdornment,
...props
}) {
return (
<div
className={classNames([
"flex max-w-full flex-col",
fullWidth ? "w-full" : "w-[20ch]",
])}
>
{label && (
<label className="color-gray-800 mb-0.5 block text-sm font-medium">
{label}
</label>
)}
<div
className={classNames([
"flex items-stretch overflow-hidden rounded-lg border bg-gray-50",
required && value === null ? "border-red-300" : "border-gray-200",
])}
>
{startAdornment}
<input
{...props}
ref={inputRef}
type={type}
value={value === null ? "" : value}
className={classNames([
"w-full bg-transparent px-3 py-2 text-sm text-gray-600 placeholder-gray-400 focus:outline-none",
className,
])}
/>
</div>
</div>
);
}
2 changes: 1 addition & 1 deletion lib/assets/runner_cell/build/main.css

Large diffs are not rendered by default.

15 changes: 9 additions & 6 deletions lib/assets/runner_cell/build/main.js

Large diffs are not rendered by default.

Loading

0 comments on commit 000fa97

Please sign in to comment.