From 99ccffbc383f545eeb5fef44fd945ad88eefe4a3 Mon Sep 17 00:00:00 2001 From: mbecker20 Date: Sat, 25 May 2024 03:09:31 -0700 Subject: [PATCH] configure hetzner template working --- .../server-template/config/hetzner.tsx | 507 ++++++++++++++++++ .../server-template/config/index.tsx | 9 +- .../resources/server-template/index.tsx | 1 + 3 files changed, 515 insertions(+), 2 deletions(-) create mode 100644 frontend/src/components/resources/server-template/config/hetzner.tsx diff --git a/frontend/src/components/resources/server-template/config/hetzner.tsx b/frontend/src/components/resources/server-template/config/hetzner.tsx new file mode 100644 index 000000000..fe8a018d4 --- /dev/null +++ b/frontend/src/components/resources/server-template/config/hetzner.tsx @@ -0,0 +1,507 @@ +import { Config } from "@components/config"; +import { ConfigItem, InputList } from "@components/config/util"; +import { TextUpdateMenu } from "@components/util"; +import { useRead, useWrite } from "@lib/hooks"; +import { cn } from "@lib/utils"; +import { Types } from "@monitor/client"; +import { Button } from "@ui/button"; +import { Card } from "@ui/card"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@ui/command"; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTrigger, +} from "@ui/dialog"; +import { Input } from "@ui/input"; +import { Popover, PopoverContent, PopoverTrigger } from "@ui/popover"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@ui/select"; +import { ChevronsUpDown, MinusCircle, PlusCircle, SearchX } from "lucide-react"; +import { useState } from "react"; + +export const HetznerServerTemplateConfig = ({ id }: { id: string }) => { + const perms = useRead("GetPermissionLevel", { + target: { type: "ServerTemplate", id }, + }).data; + const config = useRead("GetServerTemplate", { server_template: id }).data + ?.config.params as Types.HetznerServerTemplateConfig; + const [update, set] = useState>( + {} + ); + const { mutateAsync } = useWrite("UpdateServerTemplate"); + if (!config) return null; + + const disabled = perms !== Types.PermissionLevel.Write; + + return ( + { + await mutateAsync({ id, config: { type: "Hetzner", params: update } }); + }} + components={{ + general: [ + { + label: "General", + components: { + datacenter: (datacenter, set) => ( + + ), + server_type: (server_type, set) => ( + + ), + image: true, + automount: true, + port: true, + enable_public_ipv4: true, + enable_public_ipv6: true, + use_public_ip: + update.enable_public_ipv4 ?? config.enable_public_ipv4, + }, + }, + { + label: "Private Network Ids", + contentHidden: + (update.private_network_ids ?? config.private_network_ids) + ?.length === 0, + actions: !disabled && ( + + ), + components: { + private_network_ids: (values, set) => ( + val.toString()) ?? []} + set={({ private_network_ids }) => + set({ + private_network_ids: ( + private_network_ids as string[] + ).map((id) => Number(id)), + }) + } + disabled={disabled} + placeholder="Private Network Id" + /> + ), + }, + }, + { + label: "Firewall Ids", + contentHidden: + (update.firewall_ids ?? config.firewall_ids)?.length === 0, + actions: !disabled && ( + + ), + components: { + firewall_ids: (values, set) => ( + val.toString()) ?? []} + set={({ firewall_ids }) => + set({ + firewall_ids: (firewall_ids as string[]).map((id) => + Number(id) + ), + }) + } + disabled={disabled} + placeholder="Firewall Id" + /> + ), + }, + }, + { + label: "Volumes", + contentHidden: + ((update.volumes ?? config.volumes)?.length ?? 0) === 0, + actions: !disabled && ( + + ), + components: { + volumes: (volumes, set) => { + return ( +
+
+ {volumes?.map((_, index) => ( +
+ + {!disabled && ( + + )} +
+ ))} +
+
+ ); + }, + }, + }, + { + label: "User Data", + labelHidden: true, + components: { + user_data: (user_data, set) => ( + + set({ user_data })} + triggerClassName="min-w-[300px] max-w-[400px]" + disabled={disabled} + /> + + ), + }, + }, + { + label: "SSH Keys", + contentHidden: (update.ssh_keys ?? config.ssh_keys)?.length === 0, + actions: !disabled && ( + + ), + components: { + ssh_keys: (values, set) => ( + val.toString()) ?? []} + set={set} + disabled={disabled} + placeholder="SSH Key ID or Name" + /> + ), + }, + }, + ], + }} + /> + ); +}; + +const DatacenterSelector = ({ + disabled, + selected, + set, +}: { + disabled: boolean; + selected: Types.HetznerDatacenter | undefined; + set: (value: Partial) => void; +}) => { + return ( + + + + ); +}; + +const ServerTypeSelector = ({ + disabled, + selected, + set, +}: { + disabled: boolean; + selected: Types.HetznerServerType | undefined; + set: (value: Partial) => void; +}) => { + const [open, setOpen] = useState(false); + const [input, setInput] = useState(""); + return ( + + + + + + + + + + + No Server Types Found + + + + + {Object.values(Types.HetznerServerType).map((server_type) => ( + { + set({ server_type }); + setOpen(false); + }} + className="flex items-center justify-between cursor-pointer" + > +
{server_type}
+
+ ))} +
+
+
+
+
+
+ ); +}; + +const HetznerVolumeDialog = ({ + volumes, + index, + set, + disabled, +}: { + volumes: Types.HetznerVolumeSpecs[]; + index: number; + set: (value: Partial) => void; + disabled?: boolean; +}) => { + const volume = volumes[index]; + const [open, setOpen] = useState(false); + return ( + + + +
+
+
device:
{volume.name} +
+
+
size:
{" "} + {volume.size_gb} GB +
+
+
type:
{volume.format} +
+
+
+
+ + Hetzner Volume +
+ {/* Volume Name */} +
+
Volume Name
+ + set({ + volumes: volumes.map((volume, i) => + i === index ? { ...volume, name: e.target.value } : volume + ), + }) + } + disabled={disabled} + className="w-[300px]" + /> +
+ + {/* Size GB */} +
+
Size (GB)
+ + set({ + volumes: volumes.map((volume, i) => + i === index + ? { ...volume, size_gb: Number(e.target.value || 0) } + : volume + ), + }) + } + disabled={disabled} + className="w-[300px]" + /> +
+ + {/* Volume Type */} +
+
Volume Format
+ +
+
+ + + +
+
+ ); +}; + +const newVolume: (index: number) => Types.HetznerVolumeSpecs = (index) => { + return { + name: `volume-${index}`, + size_gb: 20, + format: Types.HetznerVolumeFormat.Xfs, + labels: {}, + }; +}; diff --git a/frontend/src/components/resources/server-template/config/index.tsx b/frontend/src/components/resources/server-template/config/index.tsx index cd80abb97..90635b279 100644 --- a/frontend/src/components/resources/server-template/config/index.tsx +++ b/frontend/src/components/resources/server-template/config/index.tsx @@ -1,8 +1,13 @@ import { useRead } from "@lib/hooks"; import { AwsServerTemplateConfig } from "./aws"; +import { HetznerServerTemplateConfig } from "./hetzner"; export const ServerTemplateConfig = ({ id }: { id: string }) => { const config = useRead("GetServerTemplate", { server_template: id }).data ?.config; - if (config?.type === "Aws") return ; -}; \ No newline at end of file + if (config?.type === "Aws") { + return ; + } else if (config?.type === "Hetzner") { + return ; + } +}; diff --git a/frontend/src/components/resources/server-template/index.tsx b/frontend/src/components/resources/server-template/index.tsx index 2440c56f9..b09c348c8 100644 --- a/frontend/src/components/resources/server-template/index.tsx +++ b/frontend/src/components/resources/server-template/index.tsx @@ -82,6 +82,7 @@ export const ServerTemplateComponents: RequiredResourceComponents = { Aws + Hetzner