improve config

This commit is contained in:
mbecker20
2024-05-12 21:35:47 -07:00
parent de5502aec7
commit 5cb91c6f8d
13 changed files with 751 additions and 537 deletions
+70 -32
View File
@@ -4,7 +4,7 @@ import {
ConfirmUpdate,
} from "@components/config/util";
import { Section } from "@components/layouts";
import { snake_case_to_upper_space_case } from "@lib/formatting";
import { cn } from "@lib/utils";
import { Types } from "@monitor/client";
import { Button } from "@ui/button";
import { Card, CardHeader, CardTitle, CardContent } from "@ui/card";
@@ -73,6 +73,21 @@ export const ConfigLayout = <
type PrimitiveConfigArgs = { placeholder: string };
type ConfigComponent<T> = {
label: string;
icon?: ReactNode;
actions?: ReactNode;
hidden?: boolean;
labelHidden?: boolean;
contentHidden?: boolean;
components: {
[K in keyof Partial<T>]:
| boolean
| PrimitiveConfigArgs
| ((value: T[K], set: (value: Partial<T>) => void) => ReactNode);
};
};
export const Config = <T,>({
config,
update,
@@ -91,16 +106,8 @@ export const Config = <T,>({
selector?: ReactNode;
titleOther?: ReactNode;
components: Record<
string,
Record<
string,
{
[K in keyof Partial<T>]:
| boolean
| PrimitiveConfigArgs
| ((value: T[K], set: (value: Partial<T>) => void) => ReactNode);
}
>
string, // sidebar key
ConfigComponent<T>[]
>;
}) => {
const [show, setShow] = useState(keys(components)[0]);
@@ -136,6 +143,7 @@ export const Config = <T,>({
}
>
<div className="flex gap-4">
{/** The sidebar when large */}
<div className="hidden lg:flex flex-col gap-4 w-[300px]">
{keys(components).map((tab) => (
<Button
@@ -148,25 +156,53 @@ export const Config = <T,>({
</Button>
))}
</div>
<div className="flex flex-col gap-6 min-h-[500px] w-full">
{Object.entries(components[show]).map(([k, v]) => (
<Card className="w-full" key={k}>
{k && (
<CardHeader className="border-b">
<CardTitle>{snake_case_to_upper_space_case(k)}</CardTitle>
</CardHeader>
)}
<CardContent className="flex flex-col gap-2 mt-4">
<ConfigAgain
config={config}
update={update}
set={(u) => set((p) => ({ ...p, ...u }))}
components={v}
disabled={disabled}
/>
</CardContent>
</Card>
))}
{components[show].map(
({
label,
labelHidden,
icon,
actions,
hidden,
contentHidden,
components,
}) =>
!hidden && (
<Card className="w-full grid gap-2" key={label}>
{!labelHidden && (
<CardHeader
className={cn(
"flex-row items-center justify-between w-full py-0 h-[60px] space-y-0",
!contentHidden && "border-b"
)}
>
<CardTitle className="flex gap-4">
{icon}
{label}
</CardTitle>
{actions}
</CardHeader>
)}
{!contentHidden && (
<CardContent
className={cn(
"flex flex-col gap-2 pb-3",
labelHidden && "pt-3"
)}
>
<ConfigAgain
config={config}
update={update}
set={(u) => set((p) => ({ ...p, ...u }))}
components={components}
disabled={disabled}
/>
</CardContent>
)}
</Card>
)
)}
</div>
</div>
</ConfigLayout>
@@ -200,10 +236,13 @@ export const ConfigAgain = <
const value = update[key] ?? config[key];
if (typeof component === "function") {
return (
<Fragment key={key.toString()}>{component?.(value, set)}</Fragment>
<Fragment key={key.toString()}>{component(value, set)}</Fragment>
);
} else if (typeof component === "object" || component === true) {
const args = (typeof component === "object") ? component as PrimitiveConfigArgs : undefined;
const args =
typeof component === "object"
? (component as PrimitiveConfigArgs)
: undefined;
switch (typeof value) {
case "string":
return (
@@ -245,7 +284,6 @@ export const ConfigAgain = <
} else if (component === false) {
return <Fragment key={key.toString()} />;
}
})}
</>
);
+9 -7
View File
@@ -104,11 +104,12 @@ export const DoubleInput = <
leftpl,
rightval,
rightpl,
addName,
// addName,
onLeftChange,
onRightChange,
onAdd,
// onAdd,
onRemove,
containerClassName,
inputClassName,
}: {
disabled: boolean;
@@ -117,15 +118,16 @@ export const DoubleInput = <
leftpl: string;
rightval: R;
rightpl: string;
addName: string;
// addName: string;
onLeftChange: (value: T[L], i: number) => void;
onRightChange: (value: T[R], i: number) => void;
onAdd: () => void;
// onAdd: () => void;
onRemove: (i: number) => void;
containerClassName?: string;
inputClassName?: string;
}) => {
return (
<div className="flex flex-col gap-4">
<div className={cn("flex flex-col gap-4", containerClassName)}>
{values?.map((value, i) => (
<div className="flex items-center justify-between gap-4" key={i}>
<Input
@@ -150,7 +152,7 @@ export const DoubleInput = <
)}
</div>
))}
{!disabled && (
{/* {!disabled && (
<Button
variant="secondary"
className="flex items-center gap-2 w-[200px] place-self-end"
@@ -159,7 +161,7 @@ export const DoubleInput = <
<PlusCircle className="w-4 h-4" />
Add {addName}
</Button>
)}
)} */}
</div>
);
};
@@ -36,12 +36,15 @@ export const AlerterConfig = ({ id }: { id: string }) => {
await mutateAsync({ id, config: { type, params: update } });
}}
components={{
general: {
general: {
url: true,
enabled: true,
general: [
{
label: "General",
components: {
url: true,
enabled: true,
},
},
},
],
}}
selector={
<div className="flex gap-2 items-center text-sm">
+138 -120
View File
@@ -44,135 +44,153 @@ export const BuildConfig = ({ id }: { id: string }) => {
await mutateAsync({ id, config: update });
}}
components={{
general: {
general: {
version: (version, set) => {
const { major, minor, patch } = version ?? {
major: 0,
minor: 0,
patch: 0,
};
return (
<ConfigItem label="Version">
<div className="flex gap-4 items-center">
<div className="text-xl">
v{major}.{minor}.{patch}
general: [
{
label: "General",
components: {
version: (version, set) => {
const { major, minor, patch } = version ?? {
major: 0,
minor: 0,
patch: 0,
};
return (
<ConfigItem label="Version">
<div className="flex gap-4 items-center">
<div className="text-xl">
v{major}.{minor}.{patch}
</div>
{!disabled && (
<Button
variant="secondary"
onClick={() =>
set({
version: { major: major + 1, minor, patch: 0 },
})
}
>
+ Major
</Button>
)}
{!disabled && (
<Button
variant="secondary"
onClick={() =>
set({
version: { major, minor: minor + 1, patch: 0 },
})
}
>
+ Minor
</Button>
)}
</div>
{!disabled && (
<Button
variant="secondary"
onClick={() =>
set({
version: { major: major + 1, minor, patch: 0 },
})
}
>
+ Major
</Button>
)}
{!disabled && (
<Button
variant="secondary"
onClick={() =>
set({
version: { major, minor: minor + 1, patch: 0 },
})
}
>
+ Minor
</Button>
)}
</div>
</ConfigItem>
);
},
builder_id: (id, set) => (
<ConfigItem label="Builder">
<ResourceSelector
type="Builder"
selected={id}
onSelect={(builder_id) => set({ builder_id })}
disabled={disabled}
/>
</ConfigItem>
);
),
},
builder_id: (id, set) => (
<ConfigItem label="Builder">
<ResourceSelector
},
{
label: "Git",
components: {
repo: { placeholder: "Enter repo" },
branch: { placeholder: "Enter branch" },
commit: { placeholder: "Enter specific commit hash. Optional." },
github_account: (account, set) => (
<AccountSelector
id={update.builder_id ?? config.builder_id ?? undefined}
type="Builder"
selected={id}
onSelect={(builder_id) => set({ builder_id })}
account_type="github"
selected={account}
onSelect={(github_account) => set({ github_account })}
disabled={disabled}
placeholder="None"
/>
),
},
},
{
label: "Docker",
components: {
build_path: true,
dockerfile_path: true,
docker_account: (account, set) => (
<AccountSelector
id={update.builder_id ?? config.builder_id ?? undefined}
type="Builder"
account_type="docker"
selected={account}
onSelect={(docker_account) => set({ docker_account })}
disabled={disabled}
placeholder="None"
/>
),
docker_organization:
docker_organizations === undefined ||
docker_organizations.length === 0
? undefined
: (value, set) => (
<DockerOrganizations
value={value}
set={set}
disabled={disabled}
/>
),
use_buildx: true,
labels: (l, set) => (
<LabelsConfig labels={l ?? []} set={set} disabled={disabled} />
),
extra_args: (value, set) => (
<ExtraArgs args={value ?? []} set={set} disabled={disabled} />
),
},
},
{
label: "Pre Build",
components: {
pre_build: (value, set) => (
<SystemCommand
label="Pre Build"
value={value}
set={(value) => set({ pre_build: value })}
disabled={disabled}
/>
</ConfigItem>
),
),
},
},
git: {
repo: { placeholder: "Enter repo" },
branch: { placeholder: "Enter branch" },
commit: { placeholder: "Enter specific commit hash. Optional." },
github_account: (account, set) => (
<AccountSelector
id={update.builder_id ?? config.builder_id ?? undefined}
type="Builder"
account_type="github"
selected={account}
onSelect={(github_account) => set({ github_account })}
disabled={disabled}
placeholder="None"
/>
),
{
label: "Github Webhook",
components: {
["build" as any]: () => (
<ConfigItem label="Build">
<CopyGithubWebhook path={`/build/${id}`} />
</ConfigItem>
),
webhook_enabled: true,
},
},
docker: {
build_path: true,
dockerfile_path: true,
docker_account: (account, set) => (
<AccountSelector
id={update.builder_id ?? config.builder_id ?? undefined}
type="Builder"
account_type="docker"
selected={account}
onSelect={(docker_account) => set({ docker_account })}
disabled={disabled}
placeholder="None"
/>
),
docker_organization:
docker_organizations === undefined ||
docker_organizations.length === 0
? undefined
: (value, set) => (
<DockerOrganizations
value={value}
set={set}
disabled={disabled}
/>
),
use_buildx: true,
labels: (l, set) => (
<LabelsConfig labels={l ?? []} set={set} disabled={disabled} />
),
extra_args: (value, set) => (
<ExtraArgs args={value ?? []} set={set} disabled={disabled} />
),
],
"Build Args": [
{
label: "Build Args",
components: {
build_args: (vars, set) => (
<BuildArgs vars={vars ?? []} set={set} disabled={disabled} />
),
skip_secret_interp: true,
},
},
pre_build: {
pre_build: (value, set) => (
<SystemCommand
label="Pre Build"
value={value}
set={(value) => set({ pre_build: value })}
disabled={disabled}
/>
),
},
github_webhook: {
["build" as any]: () => (
<ConfigItem label="Build">
<CopyGithubWebhook path={`/build/${id}`} />
</ConfigItem>
),
webhook_enabled: true,
},
},
"Build Args": {
"Build Args": {
build_args: (vars, set) => (
<BuildArgs vars={vars ?? []} set={set} disabled={disabled} />
),
skip_secret_interp: true,
},
},
],
}}
/>
);
@@ -32,43 +32,46 @@ const AwsBuilderConfig = ({ id }: { id: string }) => {
await mutateAsync({ id, config: { type: "Aws", params: update } });
}}
components={{
general: {
general: {
region: true,
instance_type: true,
volume_gb: true,
ami_id: true,
subnet_id: true,
key_pair_name: true,
assign_public_ip: true,
use_public_ip: true,
security_group_ids: (values, set) => (
<InputList
field="security_group_ids"
values={values}
set={set}
disabled={disabled}
/>
),
github_accounts: (accounts, set) => (
<InputList
field="github_accounts"
values={accounts ?? []}
set={set}
disabled={disabled}
/>
),
docker_accounts: (accounts, set) => (
<InputList
field="docker_accounts"
values={accounts ?? []}
set={set}
disabled={disabled}
/>
),
port: true,
general: [
{
label: "General",
components: {
region: true,
instance_type: true,
volume_gb: true,
ami_id: true,
subnet_id: true,
key_pair_name: true,
assign_public_ip: true,
use_public_ip: true,
security_group_ids: (values, set) => (
<InputList
field="security_group_ids"
values={values}
set={set}
disabled={disabled}
/>
),
github_accounts: (accounts, set) => (
<InputList
field="github_accounts"
values={accounts ?? []}
set={set}
disabled={disabled}
/>
),
docker_accounts: (accounts, set) => (
<InputList
field="docker_accounts"
values={accounts ?? []}
set={set}
disabled={disabled}
/>
),
port: true,
},
},
},
],
}}
/>
);
@@ -95,21 +98,24 @@ const ServerBuilderConfig = ({ id }: { id: string }) => {
await mutateAsync({ id, config: { type: "Server", params: update } });
}}
components={{
general: {
general: {
server_id: (id, set) => (
<div className="flex justify-between items-center border-b pb-4">
Select Server
<ResourceSelector
type="Server"
selected={id}
onSelect={(server_id) => set({ server_id })}
disabled={disabled}
/>
</div>
),
general: [
{
label: "General",
components: {
server_id: (id, set) => (
<div className="flex justify-between items-center border-b pb-4">
Select Server
<ResourceSelector
type="Server"
selected={id}
onSelect={(server_id) => set({ server_id })}
disabled={disabled}
/>
</div>
),
},
},
},
],
}}
/>
);
+10 -10
View File
@@ -81,11 +81,13 @@ export const ResourceSelector = ({
selected,
onSelect,
disabled,
align,
}: {
type: UsableResource;
selected: string | undefined;
onSelect?: (id: string) => void;
disabled?: boolean;
align?: "start" | "center" | "end"
}) => {
const [open, setOpen] = useState(false);
const [input, setInput] = useState("");
@@ -103,7 +105,7 @@ export const ResourceSelector = ({
{!disabled && <ChevronsUpDown className="w-3 h-3" />}
</Button>
</PopoverTrigger>
<PopoverContent className="w-[200px] max-h-[200px] p-0" sideOffset={12}>
<PopoverContent className="w-[200px] max-h-[200px] p-0" align={align}>
<Command>
<CommandInput
placeholder={`Search ${type}s`}
@@ -284,18 +286,16 @@ export const LabelsConfig = ({
set: (input: Partial<Types.DeploymentConfig | Types.BuildConfig>) => void;
disabled: boolean;
}) => (
<ConfigItem
label="Labels"
className={labels.length > 0 ? "items-start" : undefined}
>
<div className="py-2 w-full flex justify-end">
<DoubleInput
disabled={disabled}
inputClassName="max-w-full"
containerClassName="w-fit"
values={labels}
leftval="variable"
leftpl="Key"
rightval="value"
rightpl="Value"
addName="Label"
onLeftChange={(variable, i) => {
labels[i].variable = variable;
set({ labels: [...labels] });
@@ -304,14 +304,11 @@ export const LabelsConfig = ({
labels[i].value = value;
set({ labels: [...labels] });
}}
onAdd={() =>
set({ labels: [...(labels ?? []), { variable: "", value: "" }] })
}
onRemove={(idx) =>
set({ labels: [...labels.filter((_, i) => i !== idx)] })
}
/>
</ConfigItem>
</div>
);
export const CopyGithubWebhook = ({
@@ -333,10 +330,12 @@ export const ServerSelector = ({
selected,
set,
disabled,
align,
}: {
selected: string | undefined;
set: (input: Partial<Types.DeploymentConfig>) => void;
disabled: boolean;
align?: "start" | "center" | "end"
}) => (
<ConfigItem label="Server">
<ResourceSelector
@@ -344,6 +343,7 @@ export const ServerSelector = ({
selected={selected}
onSelect={(server_id) => set({ server_id })}
disabled={disabled}
align={align}
/>
</ConfigItem>
);
@@ -1,4 +1,3 @@
import { ConfigItem } from "@components/config/util";
import { useRead } from "@lib/hooks";
import { Types } from "@monitor/client";
import { Button } from "@ui/button";
@@ -25,50 +24,46 @@ export const ExtraArgs = ({
disabled: boolean;
}) => {
return (
<ConfigItem
label="Extra Args"
className={args.length > 0 ? "items-start" : undefined}
>
<div className="flex flex-col gap-4 w-full max-w-[400px]">
{args.map((arg, i) => (
<div className="w-full flex gap-4" key={i}>
<Input
value={arg}
placeholder="--extra-arg=value"
onChange={(e) => {
args[i] = e.target.value;
set({ extra_args: [...args] });
}}
disabled={disabled}
/>
{!disabled && (
<Button
variant="secondary"
onClick={() =>
set({ extra_args: [...args.filter((_, idx) => idx !== i)] })
}
>
<MinusCircle className="w-4 h-4" />
</Button>
)}
</div>
))}
{!disabled && (
<div className="place-self-end">
<AddExtraArgMenu
onSelect={(suggestion) =>
set({ extra_args: [...args, suggestion] })
<div className="flex flex-col justify-end gap-4 w-full">
{args.map((arg, i) => (
<div className="w-full flex gap-4 justify-end" key={i}>
<Input
value={arg}
placeholder="--extra-arg=value"
onChange={(e) => {
args[i] = e.target.value;
set({ extra_args: [...args] });
}}
disabled={disabled}
className="w-[400px] max-w-full"
/>
{!disabled && (
<Button
variant="secondary"
onClick={() =>
set({ extra_args: [...args.filter((_, idx) => idx !== i)] })
}
/>
</div>
)}
</div>
</ConfigItem>
>
<MinusCircle className="w-4 h-4" />
</Button>
)}
</div>
))}
{/* {!disabled && (
<div className="place-self-end">
<AddExtraArgMenu
onSelect={(suggestion) =>
set({ extra_args: [...args, suggestion] })
}
/>
</div>
)} */}
</div>
);
};
const AddExtraArgMenu = ({
export const AddExtraArgMenu = ({
onSelect,
}: {
onSelect: (suggestion: string) => void;
@@ -86,7 +81,7 @@ const AddExtraArgMenu = ({
<PlusCircle className="w-4 h-4" /> Add Extra Arg
</Button>
</PopoverTrigger>
<PopoverContent className="w-[300px] max-h-[400px] p-0">
<PopoverContent className="w-[300px] max-h-[400px] p-0" align="end">
<Command>
<CommandInput
placeholder="Search suggestions"
@@ -102,7 +97,10 @@ const AddExtraArgMenu = ({
<CommandGroup>
<CommandItem
onSelect={() => onSelect("")}
onSelect={() => {
onSelect("");
setOpen(false);
}}
className="w-full cursor-pointer"
>
Empty Extra Arg
@@ -111,7 +109,10 @@ const AddExtraArgMenu = ({
{suggestions?.map((suggestion) => (
<CommandItem
key={suggestion}
onSelect={() => onSelect(suggestion)}
onSelect={() => {
onSelect(suggestion);
setOpen(false);
}}
className="w-full overflow-hidden overflow-ellipsis cursor-pointer"
>
{suggestion}
@@ -1,4 +1,4 @@
import { ConfigItem, DoubleInput } from "@components/config/util";
import { DoubleInput } from "@components/config/util";
import { Types } from "@monitor/client";
export const PortsConfig = ({
@@ -10,18 +10,16 @@ export const PortsConfig = ({
set: (input: Partial<Types.DeploymentConfig>) => void;
disabled: boolean;
}) => (
<ConfigItem
label="Ports"
className={ports.length > 0 ? "items-start" : undefined}
>
<div className="py-2 w-full flex justify-end">
<DoubleInput
disabled={disabled}
inputClassName="w-[200px] max-w-full"
containerClassName="w-fit"
values={ports}
leftval="local"
leftpl="Local"
rightval="container"
rightpl="Container"
addName="Port"
onLeftChange={(local, i) => {
ports[i].local = local;
set({ ports: [...ports] });
@@ -30,10 +28,7 @@ export const PortsConfig = ({
ports[i].container = container;
set({ ports: [...ports] });
}}
onAdd={() =>
set({ ports: [...(ports ?? []), { container: "", local: "" }] })
}
onRemove={(idx) => set({ ports: [...ports.filter((_, i) => i !== idx)] })}
/>
</ConfigItem>
</div>
);
@@ -1,4 +1,4 @@
import { ConfigItem, DoubleInput } from "@components/config/util";
import { DoubleInput } from "@components/config/util";
import { Types } from "@monitor/client";
export const VolumesConfig = ({
@@ -10,19 +10,16 @@ export const VolumesConfig = ({
set: (input: Partial<Types.DeploymentConfig>) => void;
disabled: boolean;
}) => (
<ConfigItem
label="Volumes"
className={volumes.length > 0 ? "items-start" : undefined}
>
<div className="py-2 w-full flex justify-end">
<DoubleInput
disabled={disabled}
inputClassName="w-[300px] max-w-full"
inputClassName="w-[400px] max-w-full"
containerClassName="w-fit"
values={volumes}
leftval="local"
leftpl="Local"
rightval="container"
rightpl="Container"
addName="Volume"
onLeftChange={(local, i) => {
volumes[i].local = local;
set({ volumes: [...volumes] });
@@ -31,12 +28,9 @@ export const VolumesConfig = ({
volumes[i].container = container;
set({ volumes: [...volumes] });
}}
onAdd={() =>
set({ volumes: [...(volumes ?? []), { container: "", local: "" }] })
}
onRemove={(idx) =>
set({ volumes: [...volumes.filter((_, i) => i !== idx)] })
}
/>
</ConfigItem>
</div>
);
@@ -8,7 +8,7 @@ import { NetworkModeSelector } from "./components/network";
import { PortsConfig } from "./components/ports";
import { EnvVars } from "./components/environment";
import { VolumesConfig } from "./components/volumes";
import { ExtraArgs } from "./components/extra-args";
import { AddExtraArgMenu, ExtraArgs } from "./components/extra-args";
import { Config } from "@components/config";
import {
DefaultTerminationSignal,
@@ -17,8 +17,16 @@ import {
} from "./components/term-signal";
import { LabelsConfig, ServerSelector } from "@components/resources/common";
import { TextUpdateMenu } from "@components/util";
import { Button } from "@ui/button";
import { PlusCircle } from "lucide-react";
export const DeploymentConfig = ({ id, titleOther }: { id: string; titleOther: ReactNode }) => {
export const DeploymentConfig = ({
id,
titleOther,
}: {
id: string;
titleOther: ReactNode;
}) => {
const perms = useRead("GetPermissionLevel", {
target: { type: "Deployment", id },
}).data;
@@ -28,10 +36,10 @@ export const DeploymentConfig = ({ id, titleOther }: { id: string; titleOther: R
if (!config) return null;
const show_ports = update.network
? update.network !== "host"
const hide_ports = update.network
? update.network === "host" || update.network === "none"
: config.network
? config.network !== "host"
? config.network === "host" || config.network === "none"
: false;
const disabled = perms !== Types.PermissionLevel.Write;
@@ -47,111 +55,226 @@ export const DeploymentConfig = ({ id, titleOther }: { id: string; titleOther: R
await mutateAsync({ id, config: update });
}}
components={{
general: {
"": {
server_id: (value, set) => (
<ServerSelector selected={value} set={set} disabled={disabled} />
),
general: [
{
label: "Server Id",
labelHidden: true,
components: {
server_id: (value, set) => (
<ServerSelector
selected={value}
set={set}
disabled={disabled}
align="end"
/>
),
},
},
container: {
image: (value, set) => (
<ImageConfig image={value} set={set} disabled={disabled} />
),
docker_account: (value, set) => (
<AccountSelector
id={update.server_id ?? config.server_id}
account_type="docker"
type="Server"
selected={value}
onSelect={(docker_account) => set({ docker_account })}
disabled={disabled}
placeholder={
(update.image?.type || config.image?.type) === "Build"
? "Same as build"
: "None"
{
label: "Container",
components: {
image: (value, set) => (
<ImageConfig image={value} set={set} disabled={disabled} />
),
docker_account: (value, set) => (
<AccountSelector
id={update.server_id ?? config.server_id}
account_type="docker"
type="Server"
selected={value}
onSelect={(docker_account) => set({ docker_account })}
disabled={disabled}
placeholder={
(update.image?.type || config.image?.type) === "Build"
? "Same as build"
: "None"
}
/>
),
restart: (value, set) => (
<RestartModeSelector
selected={value}
set={set}
disabled={disabled}
/>
),
network: (value, set) => (
<NetworkModeSelector
server_id={update.server_id ?? config.server_id}
selected={value}
onSelect={(network) => set({ network })}
disabled={disabled}
/>
),
command: (value, set) => (
<ConfigItem label="Command">
<TextUpdateMenu
title="Update Command"
placeholder="Set custom command"
value={value}
onUpdate={(command) => set({ command })}
triggerClassName="min-w-[300px] max-w-[400px]"
/>
</ConfigItem>
),
},
},
{
label: "Ports",
hidden: hide_ports,
contentHidden: (update.ports ?? config.ports)?.length === 0,
actions: (
<Button
variant="secondary"
onClick={() =>
set({
...update,
ports: [
...(update.ports ?? config.ports ?? []),
{ container: "", local: "" },
],
})
}
/>
className="flex items-center gap-2 w-[200px]"
>
<PlusCircle className="w-4 h-4" />
Add Port
</Button>
),
restart: (value, set) => (
<RestartModeSelector
selected={value}
set={set}
disabled={disabled}
/>
),
network: (value, set) => (
<NetworkModeSelector
server_id={update.server_id ?? config.server_id}
selected={value}
onSelect={(network) => set({ network })}
disabled={disabled}
/>
),
ports: (value, set) =>
show_ports && (
components: {
ports: (value, set) => (
<PortsConfig
ports={value ?? []}
set={set}
disabled={disabled}
/>
),
volumes: (v, set) => (
<VolumesConfig volumes={v ?? []} set={set} disabled={disabled} />
},
},
{
label: "Volumes",
contentHidden: (update.volumes ?? config.volumes)?.length === 0,
actions: (
<Button
variant="secondary"
onClick={() =>
set({
...update,
volumes: [
...(update.volumes ?? config.volumes ?? []),
{ container: "", local: "" },
],
})
}
className="flex items-center gap-2 w-[200px]"
>
<PlusCircle className="w-4 h-4" />
Add Volume
</Button>
),
labels: (l, set) => (
<LabelsConfig labels={l ?? []} set={set} disabled={disabled} />
),
extra_args: (value, set) => (
<ExtraArgs args={value ?? []} set={set} disabled={disabled} />
),
command: (value, set) => (
<ConfigItem label="Command">
<TextUpdateMenu
title="Update Command"
placeholder="Set custom command"
value={value}
onUpdate={(command) => set({ command })}
triggerClassName="min-w-[300px] max-w-[400px]"
components: {
volumes: (v, set) => (
<VolumesConfig
volumes={v ?? []}
set={set}
disabled={disabled}
/>
</ConfigItem>
),
),
},
},
settings: {
send_alerts: true,
redeploy_on_build:
(update.image?.type || config.image?.type) === "Build",
},
},
environment: {
environment: {
environment: (vars, set) => (
<EnvVars
vars={vars ?? []}
set={set}
server={update.server_id || config.server_id}
disabled={disabled}
{
label: "Extra Args",
contentHidden:
(update.extra_args ?? config.extra_args)?.length === 0,
actions: (
<AddExtraArgMenu
onSelect={(suggestion) =>
set({
extra_args: [
...(update.extra_args ?? config.extra_args ?? []),
suggestion,
],
})
}
/>
),
skip_secret_interp: true,
components: {
extra_args: (value, set) => (
<ExtraArgs args={value ?? []} set={set} disabled={disabled} />
),
},
},
},
termination: {
termination: {
termination_signal: (value, set) => (
<DefaultTerminationSignal
arg={value}
set={set}
disabled={disabled}
/>
),
termination_timeout: (value, set) => (
<TerminationTimeout arg={value} set={set} disabled={disabled} />
),
term_signal_labels: (value, set) => (
<TermSignalLabels args={value} set={set} disabled={disabled} />
{
label: "Labels",
actions: (
<Button
variant="secondary"
onClick={() =>
set({
...update,
labels: [
...(update.labels ?? config.labels ?? []),
{ variable: "", value: "" },
],
})
}
className="flex items-center gap-2 w-[200px]"
>
<PlusCircle className="w-4 h-4" />
Add Label
</Button>
),
components: {
labels: (l, set) => (
<LabelsConfig labels={l ?? []} set={set} disabled={disabled} />
),
},
},
},
{
label: "Settings",
components: {
send_alerts: true,
redeploy_on_build:
(update.image?.type || config.image?.type) === "Build",
},
},
],
environment: [
{
label: "Environment",
components: {
environment: (vars, set) => (
<EnvVars
vars={vars ?? []}
set={set}
server={update.server_id || config.server_id}
disabled={disabled}
/>
),
skip_secret_interp: true,
},
},
],
termination: [
{
label: "Termination",
components: {
termination_signal: (value, set) => (
<DefaultTerminationSignal
arg={value}
set={set}
disabled={disabled}
/>
),
termination_timeout: (value, set) => (
<TerminationTimeout arg={value} set={set} disabled={disabled} />
),
term_signal_labels: (value, set) => (
<TermSignalLabels args={value} set={set} disabled={disabled} />
),
},
},
],
}}
/>
);
@@ -33,60 +33,74 @@ export const RepoConfig = ({ id }: { id: string }) => {
await mutateAsync({ id, config: update });
}}
components={{
general: {
"": {
server_id: (value, set) => (
<ServerSelector selected={value} set={set} disabled={disabled} />
),
},
general: {
repo: { placeholder: "Enter repo" },
branch: { placeholder: "Enter branch" },
commit: { placeholder: "Enter specific commit hash. Optional." },
github_account: (value, set) => {
const server_id = update.server_id || config.server_id;
if (server_id) {
return (
<GithubAccount
server={server_id}
value={value}
set={set}
disabled={disabled}
/>
);
}
general: [
{
label: "Server Id",
labelHidden: true,
components: {
server_id: (value, set) => (
<ServerSelector
selected={value}
set={set}
disabled={disabled}
/>
),
},
on_clone: (value, set) => (
<SystemCommand
label="On Clone"
value={value}
set={(value) => set({ on_clone: value })}
disabled={disabled}
/>
),
on_pull: (value, set) => (
<SystemCommand
label="On Pull"
value={value}
set={(value) => set({ on_pull: value })}
disabled={disabled}
/>
),
},
github_webhooks: {
["clone" as any]: () => (
<ConfigItem label="Clone">
<CopyGithubWebhook path={`/repo/${id}/clone`} />
</ConfigItem>
),
["pull" as any]: () => (
<ConfigItem label="Pull">
<CopyGithubWebhook path={`/repo/${id}/pull`} />
</ConfigItem>
),
webhook_enabled: true,
{
label: "General",
components: {
repo: { placeholder: "Enter repo" },
branch: { placeholder: "Enter branch" },
commit: { placeholder: "Enter specific commit hash. Optional." },
github_account: (value, set) => {
const server_id = update.server_id || config.server_id;
if (server_id) {
return (
<GithubAccount
server={server_id}
value={value}
set={set}
disabled={disabled}
/>
);
}
},
on_clone: (value, set) => (
<SystemCommand
label="On Clone"
value={value}
set={(value) => set({ on_clone: value })}
disabled={disabled}
/>
),
on_pull: (value, set) => (
<SystemCommand
label="On Pull"
value={value}
set={(value) => set({ on_pull: value })}
disabled={disabled}
/>
),
},
},
},
{
label: "Github Webhooks",
components: {
["clone" as any]: () => (
<ConfigItem label="Clone">
<CopyGithubWebhook path={`/repo/${id}/clone`} />
</ConfigItem>
),
["pull" as any]: () => (
<ConfigItem label="Pull">
<CopyGithubWebhook path={`/repo/${id}/pull`} />
</ConfigItem>
),
webhook_enabled: true,
},
},
],
}}
/>
);
@@ -46,86 +46,91 @@ export const AwsServerTemplateConfig = ({ id }: { id: string }) => {
await mutateAsync({ id, config: { type: "Aws", params: update } });
}}
components={{
general: {
general: {
region: true,
instance_type: true,
ami_id: true,
subnet_id: true,
key_pair_name: true,
assign_public_ip: true,
use_public_ip: true,
port: true,
security_group_ids: (values, set) => (
<InputList
field="security_group_ids"
values={values!}
set={set}
disabled={disabled}
/>
),
volumes: (volumes, set) => {
return (
<ConfigItem
label="EBS Volumes"
className={volumes.length > 0 ? "items-start" : undefined}
>
<div className="flex flex-col gap-4 w-full max-w-[400px]">
{volumes.map((_, index) => (
<div
key={index}
className="flex items-center justify-between gap-4"
>
<AwsVolumeDialog
volumes={volumes}
index={index}
set={set}
disabled={disabled}
/>
{!disabled && (
<Button
variant="secondary"
disabled={disabled}
onClick={() =>
set({
volumes: volumes.filter((_, i) => i !== index),
})
}
>
<MinusCircle className="w-4 h-4" />
</Button>
)}
</div>
))}
{!disabled && (
<Button
variant="secondary"
onClick={() =>
set({
volumes: [...volumes, newVolume(volumes.length)],
})
}
>
Add Volume
</Button>
)}
</div>
</ConfigItem>
);
},
user_data: (user_data, set) => (
<ConfigItem label="User Data">
<TextUpdateMenu
title="Update User Data"
placeholder="Set User Data"
value={user_data}
onUpdate={(user_data) => set({ user_data })}
triggerClassName="min-w-[300px] max-w-[400px]"
general: [
{
label: "General",
components: {
region: true,
instance_type: true,
ami_id: true,
subnet_id: true,
key_pair_name: true,
assign_public_ip: true,
use_public_ip: true,
port: true,
security_group_ids: (values, set) => (
<InputList
field="security_group_ids"
values={values!}
set={set}
disabled={disabled}
/>
</ConfigItem>
),
),
volumes: (volumes, set) => {
return (
<ConfigItem
label="EBS Volumes"
className={volumes.length > 0 ? "items-start" : undefined}
>
<div className="flex flex-col gap-4 w-full max-w-[400px]">
{volumes.map((_, index) => (
<div
key={index}
className="flex items-center justify-between gap-4"
>
<AwsVolumeDialog
volumes={volumes}
index={index}
set={set}
disabled={disabled}
/>
{!disabled && (
<Button
variant="secondary"
disabled={disabled}
onClick={() =>
set({
volumes: volumes.filter(
(_, i) => i !== index
),
})
}
>
<MinusCircle className="w-4 h-4" />
</Button>
)}
</div>
))}
{!disabled && (
<Button
variant="secondary"
onClick={() =>
set({
volumes: [...volumes, newVolume(volumes.length)],
})
}
>
Add Volume
</Button>
)}
</div>
</ConfigItem>
);
},
user_data: (user_data, set) => (
<ConfigItem label="User Data">
<TextUpdateMenu
title="Update User Data"
placeholder="Set User Data"
value={user_data}
onUpdate={(user_data) => set({ user_data })}
triggerClassName="min-w-[300px] max-w-[400px]"
/>
</ConfigItem>
),
},
},
},
],
}}
/>
);
@@ -30,37 +30,52 @@ export const ServerConfig = ({ id }: { id: string }) => {
await mutateAsync({ id, config: update });
}}
components={{
general: {
general: {
address: true,
region: true,
enabled: true,
stats_monitoring: true,
auto_prune: true,
general: [
{
label: "General",
components: {
address: true,
region: true,
enabled: true,
stats_monitoring: true,
auto_prune: true,
},
},
},
alerts: {
alerts: {
send_unreachable_alerts: true,
send_cpu_alerts: true,
send_disk_alerts: true,
send_mem_alerts: true,
],
alerts: [
{
label: "Alerts",
components: {
send_unreachable_alerts: true,
send_cpu_alerts: true,
send_disk_alerts: true,
send_mem_alerts: true,
},
},
},
warnings: {
cpu: {
cpu_warning: true,
cpu_critical: true,
],
warnings: [
{
label: "Cpu",
components: {
cpu_warning: true,
cpu_critical: true,
},
},
memory: {
mem_warning: true,
mem_critical: true,
{
label: "Memory",
components: {
mem_warning: true,
mem_critical: true,
},
},
disk: {
disk_warning: true,
disk_critical: true,
{
label: "Disk",
components: {
disk_warning: true,
disk_critical: true,
},
},
},
],
}}
/>
);