Skip to content

Commit

Permalink
Finishing design for deploy agent modal
Browse files Browse the repository at this point in the history
  • Loading branch information
clarkmcc committed Nov 23, 2023
1 parent 04900d9 commit 2feb2e9
Show file tree
Hide file tree
Showing 12 changed files with 909 additions and 89 deletions.
6 changes: 5 additions & 1 deletion app/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@auth0/auth0-react": "^2.2.3",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@hookform/resolvers": "^3.3.2",
"@mui/material": "^5.14.18",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
Expand All @@ -23,6 +24,8 @@
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-toast": "^1.1.5",
"@tanstack/react-router": "^0.0.1-beta.212",
"@tanstack/react-table": "^8.10.7",
"class-variance-authority": "^0.7.0",
Expand All @@ -41,7 +44,8 @@
"react-usage-bar": "^1.1.22",
"sort-by": "^1.2.0",
"tailwind-merge": "^2.0.0",
"tailwindcss-animate": "^1.0.7"
"tailwindcss-animate": "^1.0.7",
"zod": "^3.22.4"
},
"devDependencies": {
"@types/node": "^20.9.0",
Expand Down
129 changes: 50 additions & 79 deletions app/frontend/src/components/deploy-an-agent-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,13 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog.tsx";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group.tsx";
import { Label } from "@radix-ui/react-dropdown-menu";
import { AppleIcon } from "@/components/icons/apple-icon.tsx";
import { LinuxIcon } from "@/components/icons/linux-icon.tsx";
import { WindowsIcon } from "@/components/icons/windows-icon.tsx";
import { DeployAgentForm } from "@/components/forms/deploy-agent-form.tsx";

// type DeployAnAgentButtonProps = ButtonProps & {};

export const DeployAnAgentButton = forwardRef<HTMLButtonElement, ButtonProps>(
(props, ref) => {
const [open, setOpen] = useState(false);
const [os] = useState("macos");
return (
<>
<Button ref={ref} {...props} onClick={() => setOpen(true)}>
Expand All @@ -36,79 +31,55 @@ export const DeployAnAgentButton = forwardRef<HTMLButtonElement, ButtonProps>(
</DialogDescription>
</DialogHeader>

<div>
<RadioGroup value={os} className="grid grid-cols-3 gap-4">
<div>
<RadioGroupItem
value="macos"
id="macos"
className="peer sr-only"
/>
<Label className="flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary [&:has([data-state=checked])]:border-primary">
<AppleIcon className="mb-3 h-6 w-6" />
macOS
</Label>
</div>
<div>
<RadioGroupItem
value="linux"
id="linux"
className="peer sr-only"
/>
<Label className="flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary [&:has([data-state=checked])]:border-primary">
<LinuxIcon className="mb-3 h-6 w-6" />
Linux
</Label>
</div>
<div>
<RadioGroupItem
disabled
value="windows"
id="windows"
className="peer sr-only"
/>
<Label className="flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary [&:has([data-state=checked])]:border-primary">
<WindowsIcon className="mb-3 h-6 w-6" />
Windows
</Label>
</div>
</RadioGroup>
</div>

<div>
<RadioGroup value={os} className="grid grid-cols-3 gap-4">
<div>
<RadioGroupItem
value="macos"
id="macos"
className="peer sr-only"
/>
<Label className="flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary [&:has([data-state=checked])]:border-primary">
64-bit ARM
</Label>
</div>
<div>
<RadioGroupItem
value="linux"
id="linux"
className="peer sr-only"
/>
<Label className="flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary [&:has([data-state=checked])]:border-primary">
32-bit ARM
</Label>
</div>
<div>
<RadioGroupItem
value="linux"
id="linux"
className="peer sr-only"
/>
<Label className="flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary [&:has([data-state=checked])]:border-primary">
64-bit x86
</Label>
</div>
</RadioGroup>
</div>
<DeployAgentForm
onSubmit={(v) => console.log(v)}
downloads={[
{
goos: {
display: "Linux",
value: "linux",
},
goarch: [
{
display: "64-bit x86",
value: "amd64",
},
{
display: "64-bit ARM",
value: "arm64",
},
],
},
{
goos: {
display: "macOS",
value: "darwin",
},
goarch: [
{
display: "64-bit x86",
value: "amd64",
},
{
display: "64-bit ARM",
value: "arm64",
},
],
},
{
goos: {
display: "Windows",
value: "windows",
},
goarch: [
{
display: "64-bit x86",
value: "amd64",
},
],
},
]}
/>
</DialogContent>
</Dialog>
</>
Expand Down
211 changes: 211 additions & 0 deletions app/frontend/src/components/forms/deploy-agent-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import { z } from "zod";
import { ControllerRenderProps, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form.tsx";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group.tsx";
import { Label } from "@radix-ui/react-dropdown-menu";
import { Button } from "@/components/ui/button.tsx";
import { cn, goosToIcon } from "@/lib/utils.ts";
import React, { useCallback, useEffect } from "react";
import { AgentPlatformDownload, DisplayableValue, GOARCH } from "@/types";
import { Switch } from "@/components/ui/switch.tsx";

const FormSchema = z.object({
os: z.enum(["linux", "windows", "darwin"], {
required_error: "Please select an operating system",
}),
arch: z.enum(["amd64", "arm64", "386"]),
generate_psk: z.boolean(),
});

type DeployAgentFormProps = {
downloads: AgentPlatformDownload[];
onSubmit: (values: z.infer<typeof FormSchema>) => void;
};

export function DeployAgentForm({ onSubmit, downloads }: DeployAgentFormProps) {
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
});

const getArchs = useCallback(
(os: string): DisplayableValue<GOARCH>[] => {
const download = downloads.find((d) => d.goos.value === os);
if (!download) return [];
return download.goarch;
},
[downloads],
);

const os = form.watch("os");
const arch = form.watch("arch");

// Watch the os and arch and make sure that when the OS changes, the arch that
// is selected is compatible with the OS. If it is not, then select the first
// compatible arch.
useEffect(() => {
const arches = getArchs(os);
const selectedIncompatibleArch =
arches.find((a) => a.value === arch) === undefined;
if (arches.length > 0 && selectedIncompatibleArch) {
form.setValue("arch", arches[0].value);
}
}, [os, arch]);

return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="os"
render={({ field }) => (
<FormItem>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
defaultValue={field.value}
className={`grid grid-cols-${downloads.length} gap-4`}
>
{downloads.map((download) => (
<OsOption
key={download.goos.value}
os={download.goos.value}
label={download.goos.display}
icon={goosToIcon(download.goos.value)}
field={field}
/>
))}
</RadioGroup>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{os && (
<FormField
control={form.control}
name="arch"
render={({ field }) => (
<FormItem>
<FormControl>
<RadioGroup
onValueChange={field.onChange}
defaultValue={field.value}
className={`grid grid-cols-${getArchs(os).length} gap-4`}
>
{getArchs(os).map((download) => (
<ArchOption
key={download.value}
arch={download.value}
label={download.display}
field={field}
/>
))}
</RadioGroup>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}

<FormField
control={form.control}
name="generate_psk"
render={({ field }) => (
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
<div className="space-y-0.5">
<FormLabel>Generate pre-shared key</FormLabel>
<FormDescription>
Pre-shared keys are used to authenticate agents. If you do not
generate one now, you will need to generate one manually
later.
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>

<div className="flex flex-row-reverse space-x-2">
<Button type="submit">Next</Button>
</div>
</form>
</Form>
);
}

type OsOptionProps = {
os: string;
icon: React.ElementType;
label: string;
field: ControllerRenderProps<z.infer<typeof FormSchema>, "os">;
className?: string;
};

function OsOption(props: OsOptionProps) {
const Icon = props.icon;
return (
<FormItem className={props.className}>
<FormControl>
<RadioGroupItem className="sr-only" value={props.os} />
</FormControl>
<FormLabel className="font-normal">
<Label
className={cn(
"flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground",
{
"border-primary bg-accent": props.field.value === props.os,
},
)}
>
<Icon className="mb-3 h-6 w-6" />
{props.label}
</Label>
</FormLabel>
</FormItem>
);
}

type ArchOptionProps = {
arch: string;
// icon: React.ElementType;
label: string;
field: ControllerRenderProps<z.infer<typeof FormSchema>, "arch">;
className?: string;
};

function ArchOption(props: ArchOptionProps) {
return (
<FormItem className={props.className}>
<FormControl>
<RadioGroupItem className="sr-only" value={props.arch} />
</FormControl>
<FormLabel className="font-normal">
<Label
className={cn(
"flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground",
{
"border-primary bg-accent": props.field.value === props.arch,
},
)}
>
{props.label}
</Label>
</FormLabel>
</FormItem>
);
}
Loading

0 comments on commit 2feb2e9

Please sign in to comment.