mirror of
https://github.com/moghtech/komodo.git
synced 2026-09-10 16:01:20 +00:00
finish configure server templates
This commit is contained in:
+19
-10
@@ -3029,6 +3029,13 @@ export interface ServerHealth {
|
||||
disks: Record<string, SeverityLevel>;
|
||||
}
|
||||
|
||||
export enum AwsVolumeType {
|
||||
Gp2 = "gp2",
|
||||
Gp3 = "gp3",
|
||||
Io1 = "io1",
|
||||
Io2 = "io2",
|
||||
}
|
||||
|
||||
/**
|
||||
* For information on AWS volumes, see
|
||||
* `<https://docs.aws.amazon.com/ebs/latest/userguide/ebs-volume-types.html>`.
|
||||
@@ -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 =
|
||||
|
||||
@@ -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 <AwsServerTemplateConfig id={id} />;
|
||||
};
|
||||
|
||||
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<Partial<Types.AwsServerTemplateConfig>>({});
|
||||
const { mutateAsync } = useWrite("UpdateServerTemplate");
|
||||
if (!config) return null;
|
||||
|
||||
const disabled = perms !== Types.PermissionLevel.Write;
|
||||
|
||||
return (
|
||||
<Config
|
||||
disabled={disabled}
|
||||
config={config.params as Types.AwsServerTemplateConfig}
|
||||
update={update}
|
||||
set={set}
|
||||
onSave={async () => {
|
||||
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) => (
|
||||
<InputList
|
||||
field="security_group_ids"
|
||||
values={values}
|
||||
set={set}
|
||||
disabled={disabled}
|
||||
/>
|
||||
),
|
||||
port: true,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -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<Partial<Types.AwsServerTemplateConfig>>({});
|
||||
const { mutateAsync } = useWrite("UpdateServerTemplate");
|
||||
if (!config) return null;
|
||||
|
||||
const disabled = perms !== Types.PermissionLevel.Write;
|
||||
|
||||
return (
|
||||
<Config
|
||||
disabled={disabled}
|
||||
config={config.params as Types.AwsServerTemplateConfig}
|
||||
update={update}
|
||||
set={set}
|
||||
onSave={async () => {
|
||||
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="items-start">
|
||||
<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="outline"
|
||||
disabled={disabled}
|
||||
onClick={() =>
|
||||
set({
|
||||
volumes: volumes.filter((_, i) => i !== index),
|
||||
})
|
||||
}
|
||||
>
|
||||
<MinusCircle className="w-4 h-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{!disabled && (
|
||||
<Button
|
||||
variant="outline"
|
||||
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>
|
||||
),
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const AwsVolumeDialog = ({
|
||||
volumes,
|
||||
index,
|
||||
set,
|
||||
disabled,
|
||||
}: {
|
||||
volumes: Types.AwsVolume[];
|
||||
index: number;
|
||||
set: (value: Partial<Types.AwsServerTemplateConfig>) => void;
|
||||
disabled?: boolean;
|
||||
}) => {
|
||||
const volume = volumes[index];
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Card className="px-3 py-2 hover:bg-accent/50 transition-colors cursor-pointer w-full">
|
||||
<div
|
||||
className={cn(
|
||||
"flex gap-2 text-sm text-nowrap overflow-hidden overflow-ellipsis"
|
||||
)}
|
||||
>
|
||||
<div className="flex gap-2">
|
||||
<div className="text-muted-foreground">device:</div>{" "}
|
||||
{volume.device_name}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="text-muted-foreground">size:</div>{" "}
|
||||
{volume.size_gb} GB
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="text-muted-foreground">type:</div>{" "}
|
||||
{volume.volume_type}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>AWS Volume</DialogHeader>
|
||||
<div className="flex flex-col gap-4">
|
||||
{/* Device Name */}
|
||||
<div className="flex items-center justify-between w-fill">
|
||||
<div className="text-nowrap">Device Name</div>
|
||||
<Input
|
||||
value={volume.device_name}
|
||||
onChange={(e) =>
|
||||
set({
|
||||
volumes: volumes.map((volume, i) =>
|
||||
i === index
|
||||
? { ...volume, device_name: e.target.value }
|
||||
: volume
|
||||
),
|
||||
})
|
||||
}
|
||||
disabled={disabled}
|
||||
className="w-[300px]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Size GB */}
|
||||
<div className="flex items-center justify-between w-fill">
|
||||
<div className="text-nowrap">Size (GB)</div>
|
||||
<Input
|
||||
type="number"
|
||||
value={volume.size_gb}
|
||||
onChange={(e) =>
|
||||
set({
|
||||
volumes: volumes.map((volume, i) =>
|
||||
i === index
|
||||
? { ...volume, size_gb: Number(e.target.value || 0) }
|
||||
: volume
|
||||
),
|
||||
})
|
||||
}
|
||||
disabled={disabled}
|
||||
className="w-[300px]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Volume Type */}
|
||||
<div className="flex items-center justify-between w-fill">
|
||||
<div className="text-nowrap">Volume Type</div>
|
||||
<Select
|
||||
value={volume.volume_type}
|
||||
onValueChange={(value) =>
|
||||
set({
|
||||
volumes: volumes.map((volume, i) =>
|
||||
i === index
|
||||
? { ...volume, volume_type: value as Types.AwsVolumeType }
|
||||
: volume
|
||||
),
|
||||
})
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="w-[300px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Object.entries(Types.AwsVolumeType).map(([name, item]) => (
|
||||
<SelectItem key={name} value={item}>
|
||||
{name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* IOPS */}
|
||||
<div className="flex items-center justify-between w-fill">
|
||||
<div className="text-nowrap">Iops</div>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="Set Custom Iops"
|
||||
value={volume.iops ? volume.iops : undefined}
|
||||
onChange={(e) =>
|
||||
set({
|
||||
volumes: volumes.map((volume, i) =>
|
||||
i === index
|
||||
? { ...volume, iops: Number(e.target.value || 0) }
|
||||
: volume
|
||||
),
|
||||
})
|
||||
}
|
||||
disabled={disabled}
|
||||
className="w-[300px]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Throughput */}
|
||||
<div className="flex items-center justify-between w-fill">
|
||||
<div className="text-nowrap">Throughput</div>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="Set Custom Throughput"
|
||||
value={volume.throughput ? volume.throughput : undefined}
|
||||
onChange={(e) =>
|
||||
set({
|
||||
volumes: volumes.map((volume, i) =>
|
||||
i === index
|
||||
? { ...volume, throughput: Number(e.target.value || 0) }
|
||||
: volume
|
||||
),
|
||||
})
|
||||
}
|
||||
disabled={disabled}
|
||||
className="w-[300px]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button onClick={() => setOpen(false)}>Confirm</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
};
|
||||
@@ -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 <AwsServerTemplateConfig id={id} />;
|
||||
};
|
||||
Reference in New Issue
Block a user