-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for Kubernetes backend (#3)
Co-authored-by: Jonatan Kłosko <[email protected]>
- Loading branch information
1 parent
e64e027
commit 000fa97
Showing
11 changed files
with
698 additions
and
350 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Oops, something went wrong.