diff --git a/client/core/ts/src/types.ts b/client/core/ts/src/types.ts index d9043b17f..e269c44aa 100644 --- a/client/core/ts/src/types.ts +++ b/client/core/ts/src/types.ts @@ -3029,6 +3029,13 @@ export interface ServerHealth { disks: Record; } +export enum AwsVolumeType { + Gp2 = "gp2", + Gp3 = "gp3", + Io1 = "io1", + Io2 = "io2", +} + /** * For information on AWS volumes, see * ``. @@ -3038,12 +3045,12 @@ export interface AwsVolume { device_name: string; /** The size of the volume in GB */ size_gb: number; - /** The type of volume, eg gp2, gp3, io1 */ - volume_type?: string; - /** The iops of the volume, or AWS default. */ - iops?: number; - /** The throughput of the volume, or AWS default. */ - throughput?: number; + /** The type of volume. Options: gp2, gp3, io1, io2. */ + volume_type: AwsVolumeType; + /** The iops of the volume, or 0 for AWS default. */ + iops: number; + /** The throughput of the volume, or 0 for AWS default. */ + throughput: number; } /** Aws EC2 instance config. */ @@ -3052,14 +3059,10 @@ export interface AwsServerTemplateConfig { region: string; /** The instance type to launch, eg. c5.2xlarge */ instance_type: string; - /** Specify the EBS volumes to attach. */ - volumes: AwsVolume[]; /** Specify the ami id to use. Must be set up to start the periphery binary on startup. */ ami_id: string; /** The subnet to assign to the instance. */ subnet_id: string; - /** The security groups to give to the instance. */ - security_group_ids: string[]; /** The key pair name to give to the instance in case SSH access required. */ key_pair_name: string; /** @@ -3077,6 +3080,12 @@ export interface AwsServerTemplateConfig { * Default: `8120` */ port: number; + /** The user data to deploy the instance with. */ + user_data?: string; + /** The security groups to give to the instance. */ + security_group_ids?: string[]; + /** Specify the EBS volumes to attach. */ + volumes: AwsVolume[]; } export type AuthRequest = diff --git a/frontend/src/components/resources/server-template/config.tsx b/frontend/src/components/resources/server-template/config.tsx deleted file mode 100644 index 61b0bf880..000000000 --- a/frontend/src/components/resources/server-template/config.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { Config } from "@components/config"; -import { InputList } from "@components/config/util"; -import { useRead, useWrite } from "@lib/hooks"; -import { Types } from "@monitor/client"; -import { useState } from "react"; - -export const ServerTemplateConfig = ({ id }: { id: string }) => { - const config = useRead("GetServerTemplate", { server_template: id }).data - ?.config; - if (config?.type === "Aws") return ; -}; - -export const AwsServerTemplateConfig = ({ id }: { id: string }) => { - const perms = useRead("GetPermissionLevel", { - target: { type: "ServerTemplate", id }, - }).data; - const config = useRead("GetServerTemplate", { server_template: id }).data - ?.config; - const [update, set] = useState>({}); - const { mutateAsync } = useWrite("UpdateServerTemplate"); - if (!config) return null; - - const disabled = perms !== Types.PermissionLevel.Write; - - return ( - { - 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, - security_group_ids: (values, set) => ( - - ), - port: true, - }, - }, - }} - /> - ); -}; diff --git a/frontend/src/components/resources/server-template/config/aws.tsx b/frontend/src/components/resources/server-template/config/aws.tsx new file mode 100644 index 000000000..9a670df10 --- /dev/null +++ b/frontend/src/components/resources/server-template/config/aws.tsx @@ -0,0 +1,300 @@ +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 { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTrigger, +} from "@ui/dialog"; +import { Input } from "@ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@ui/select"; +import { MinusCircle } from "lucide-react"; +import { useState } from "react"; + +export const AwsServerTemplateConfig = ({ id }: { id: string }) => { + const perms = useRead("GetPermissionLevel", { + target: { type: "ServerTemplate", id }, + }).data; + const config = useRead("GetServerTemplate", { server_template: id }).data + ?.config; + const [update, set] = useState>({}); + const { mutateAsync } = useWrite("UpdateServerTemplate"); + if (!config) return null; + + const disabled = perms !== Types.PermissionLevel.Write; + + return ( + { + 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) => ( + + ), + volumes: (volumes, set) => { + return ( + +
+ {volumes.map((_, index) => ( +
+ + {!disabled && ( + + )} +
+ ))} + {!disabled && ( + + )} +
+
+ ); + }, + user_data: (user_data, set) => ( + + set({ user_data })} + triggerClassName="min-w-[300px] max-w-[400px]" + /> + + ), + }, + }, + }} + /> + ); +}; + +const AwsVolumeDialog = ({ + volumes, + index, + set, + disabled, +}: { + volumes: Types.AwsVolume[]; + index: number; + set: (value: Partial) => void; + disabled?: boolean; +}) => { + const volume = volumes[index]; + const [open, setOpen] = useState(false); + return ( + + + +
+
+
device:
{" "} + {volume.device_name} +
+
+
size:
{" "} + {volume.size_gb} GB +
+
+
type:
{" "} + {volume.volume_type} +
+
+
+
+ + AWS Volume +
+ {/* Device Name */} +
+
Device Name
+ + set({ + volumes: volumes.map((volume, i) => + i === index + ? { ...volume, device_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 Type
+ +
+ + {/* IOPS */} +
+
Iops
+ + set({ + volumes: volumes.map((volume, i) => + i === index + ? { ...volume, iops: Number(e.target.value || 0) } + : volume + ), + }) + } + disabled={disabled} + className="w-[300px]" + /> +
+ + {/* Throughput */} +
+
Throughput
+ + set({ + volumes: volumes.map((volume, i) => + i === index + ? { ...volume, throughput: Number(e.target.value || 0) } + : volume + ), + }) + } + disabled={disabled} + className="w-[300px]" + /> +
+
+ + + +
+
+ ); +}; + +const device_letters = ["b", "c", "d", "e", "f"]; + +const newVolume: (index: number) => Types.AwsVolume = (index) => { + const device_name = + index === 0 ? "/dev/sda1" : `/dev/sd${device_letters[index - 1]}`; + return { + device_name, + size_gb: 20, + volume_type: Types.AwsVolumeType.Gp2, + iops: 0, + throughput: 0, + }; +}; diff --git a/frontend/src/components/resources/server-template/config/index.tsx b/frontend/src/components/resources/server-template/config/index.tsx new file mode 100644 index 000000000..cd80abb97 --- /dev/null +++ b/frontend/src/components/resources/server-template/config/index.tsx @@ -0,0 +1,8 @@ +import { useRead } from "@lib/hooks"; +import { AwsServerTemplateConfig } from "./aws"; + +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