diff --git a/pkgs/ui/public/favicon.ico b/pkgs/ui/public/favicon.ico index 05acd55ee..94364cd20 100644 Binary files a/pkgs/ui/public/favicon.ico and b/pkgs/ui/public/favicon.ico differ diff --git a/pkgs/ui/src/app/favicon.ico b/pkgs/ui/src/app/favicon.ico deleted file mode 100644 index 718d6fea4..000000000 Binary files a/pkgs/ui/src/app/favicon.ico and /dev/null differ diff --git a/pkgs/ui/src/app/layout.tsx b/pkgs/ui/src/app/layout.tsx index b9d3fa532..0dec9b405 100644 --- a/pkgs/ui/src/app/layout.tsx +++ b/pkgs/ui/src/app/layout.tsx @@ -53,7 +53,7 @@ export default function RootLayout({ Clan.lol - + diff --git a/pkgs/ui/src/components/createMachineForm/clanModules.tsx b/pkgs/ui/src/components/createMachineForm/clanModules.tsx index 664b0dd37..2b8df35a3 100644 --- a/pkgs/ui/src/components/createMachineForm/clanModules.tsx +++ b/pkgs/ui/src/components/createMachineForm/clanModules.tsx @@ -115,7 +115,7 @@ export default function ClanModules(props: ClanModulesProps) { const isSchemaLoading = formHooks.watch("isSchemaLoading"); const handleChange = ( - event: SelectChangeEvent + event: SelectChangeEvent, ) => { const { target: { value }, diff --git a/pkgs/ui/src/components/createMachineForm/customConfig.tsx b/pkgs/ui/src/components/createMachineForm/customConfig.tsx index a0f84c91e..c9086e604 100644 --- a/pkgs/ui/src/components/createMachineForm/customConfig.tsx +++ b/pkgs/ui/src/components/createMachineForm/customConfig.tsx @@ -44,7 +44,7 @@ export function CustomConfig(props: FormStepContentProps) { } return acc; }, {}), - [schema] + [schema], ); return ( @@ -110,7 +110,7 @@ function PureCustomConfig(props: PureCustomConfigProps) { message: "invalid config", }); toast.error( - "Configuration is invalid. Please check the highlighted fields for details." + "Configuration is invalid. Please check the highlighted fields for details.", ); } else { formHooks.clearErrors("config"); diff --git a/pkgs/ui/src/components/hooks/useVms.tsx b/pkgs/ui/src/components/hooks/useVms.tsx index b385a72d5..6eda02897 100644 --- a/pkgs/ui/src/components/hooks/useVms.tsx +++ b/pkgs/ui/src/components/hooks/useVms.tsx @@ -1,5 +1,5 @@ -import { inspectVm } from "@/api/vm/vm"; import { HTTPValidationError, VmConfig } from "@/api/model"; +import { inspectVm } from "@/api/vm/vm"; import { AxiosError } from "axios"; import { useEffect, useState } from "react"; import { toast } from "react-hot-toast"; diff --git a/pkgs/ui/src/components/join/configureVM.tsx b/pkgs/ui/src/components/join/configureVM.tsx index 28b56fc62..27ab0526e 100644 --- a/pkgs/ui/src/components/join/configureVM.tsx +++ b/pkgs/ui/src/components/join/configureVM.tsx @@ -1,3 +1,5 @@ +import { useInspectFlakeAttrs } from "@/api/flake/flake"; +import { FormValues } from "@/views/joinPrequel"; import { Button, InputAdornment, @@ -8,14 +10,10 @@ import { Switch, TextField, } from "@mui/material"; -import { Controller, SubmitHandler, UseFormReturn } from "react-hook-form"; -import { FlakeBadge } from "../flakeBadge/flakeBadge"; -import { createVm } from "@/api/vm/vm"; -import { useInspectFlakeAttrs } from "@/api/flake/flake"; -import { VmConfig } from "@/api/model"; -import { Dispatch, SetStateAction, useEffect, useState } from "react"; +import { useEffect } from "react"; +import { Controller, UseFormReturn } from "react-hook-form"; import { toast } from "react-hot-toast"; -import { useAppState } from "../hooks/useAppContext"; +import { FlakeBadge } from "../flakeBadge/flakeBadge"; interface VmPropLabelProps { children: React.ReactNode; @@ -34,44 +32,41 @@ const VmPropContent = (props: VmPropContentProps) => ( ); interface VmDetailsProps { - formHooks: UseFormReturn; - setVmUuid: Dispatch>; + formHooks: UseFormReturn; } +type ClanError = { + detail: { + msg: string; + loc: []; + }[]; +}; + export const ConfigureVM = (props: VmDetailsProps) => { - const { formHooks, setVmUuid } = props; - const { control, handleSubmit, watch, setValue } = formHooks; - const [isStarting, setStarting] = useState(false); - const { setAppState } = useAppState(); - const { isLoading, data } = useInspectFlakeAttrs({ url: watch("flake_url") }); + const { formHooks } = props; + const { control, watch, setValue, formState } = formHooks; + + const { isLoading, data, error } = useInspectFlakeAttrs({ + url: watch("flakeUrl"), + }); useEffect(() => { if (!isLoading && data?.data) { setValue("flake_attr", data.data.flake_attrs[0] || ""); } }, [isLoading, setValue, data]); + if (error) { + const msg = + (error?.response?.data as unknown as ClanError)?.detail?.[0]?.msg || + error.message; - const onSubmit: SubmitHandler = async (data) => { - setStarting(true); - console.log(data); - const response = await createVm(data); - const { uuid } = response?.data || null; - - setVmUuid(() => uuid); - setStarting(false); - if (response.statusText === "OK") { - toast.success(("Joined @ " + uuid) as string); - setAppState((s) => ({ ...s, isJoined: true })); - } else { - toast.error("Could not join"); - } - }; - + toast.error(msg, { + id: error.name, + }); + return
{msg}
; + } return ( -
+
General
@@ -79,7 +74,7 @@ export const ConfigureVM = (props: VmDetailsProps) => { Machine @@ -88,6 +83,7 @@ export const ConfigureVM = (props: VmDetailsProps) => { ( Clan + } + /> + )} + /> + ( + Name + } + endAdornment={confirmAdornment} + /> + )} + /> + {isSubmitting && } +
+ ); +}; diff --git a/pkgs/ui/src/views/joinForm.tsx b/pkgs/ui/src/views/joinForm.tsx new file mode 100644 index 000000000..83f3edef8 --- /dev/null +++ b/pkgs/ui/src/views/joinForm.tsx @@ -0,0 +1,51 @@ +import { Confirm } from "@/components/join/confirm"; +import { Input, InputAdornment } from "@mui/material"; +import { Controller, useFormContext } from "react-hook-form"; + +interface JoinFormProps { + confirmAdornment?: React.ReactNode; + initialParams: { + flakeUrl: string; + flakeAttr: string; + }; +} +export const JoinForm = (props: JoinFormProps) => { + const { initialParams, confirmAdornment } = props; + const { control, formState, reset, getValues, watch } = useFormContext(); + + return ( +
+ {watch("flakeUrl") || initialParams.flakeUrl ? ( + reset()} + flakeUrl={ + formState.isSubmitted + ? getValues("flakeUrl") + : initialParams.flakeUrl + } + flakeAttr={initialParams.flakeAttr} + /> + ) : ( + ( + Clan + } + endAdornment={confirmAdornment} + /> + )} + /> + )} +
+ ); +}; diff --git a/pkgs/ui/src/views/joinPrequel.tsx b/pkgs/ui/src/views/joinPrequel.tsx index eb039a667..294a6d506 100644 --- a/pkgs/ui/src/views/joinPrequel.tsx +++ b/pkgs/ui/src/views/joinPrequel.tsx @@ -1,23 +1,28 @@ "use client"; import { IconButton, - Input, InputAdornment, - LinearProgress, MenuItem, Select, + Typography, } from "@mui/material"; import { useSearchParams } from "next/navigation"; import { Suspense, useState } from "react"; import { createFlake } from "@/api/flake/flake"; +import { VmConfig } from "@/api/model"; +import { createVm } from "@/api/vm/vm"; import { useAppState } from "@/components/hooks/useAppContext"; -import { Confirm } from "@/components/join/confirm"; import { Layout } from "@/components/join/layout"; +import { VmBuildLogs } from "@/components/join/vmBuildLogs"; import { ChevronRight } from "@mui/icons-material"; -import { Controller, useForm } from "react-hook-form"; +import { AxiosError } from "axios"; +import { Controller, FormProvider, useForm } from "react-hook-form"; +import { toast } from "react-hot-toast"; +import { CreateForm } from "./createForm"; +import { JoinForm } from "./joinForm"; -type FormValues = { +export type FormValues = VmConfig & { workflow: "join" | "create"; flakeUrl: string; dest?: string; @@ -27,13 +32,25 @@ export default function JoinPrequel() { const queryParams = useSearchParams(); const flakeUrl = queryParams.get("flake") || ""; const flakeAttr = queryParams.get("attr") || "default"; - const [, setForkInProgress] = useState(false); + const initialParams = { flakeUrl, flakeAttr }; + const { setAppState } = useAppState(); - const { control, formState, getValues, reset, watch, handleSubmit } = - useForm({ - defaultValues: { flakeUrl: "", dest: undefined, workflow: "join" }, - }); + const methods = useForm({ + defaultValues: { + flakeUrl: "", + dest: undefined, + workflow: "join", + cores: 4, + graphics: true, + memory_size: 2048, + }, + }); + + const { control, watch, handleSubmit } = methods; + + const [vmUuid, setVmUuid] = useState(null); + const [showLogs, setShowLogs] = useState(false); const workflow = watch("workflow"); @@ -54,88 +71,85 @@ export default function JoinPrequel() { )} /> - + ); return ( - + + {workflow}{" "} + + Clan.lol + + + } + > - {!formState.isSubmitted && !flakeUrl && ( - { - console.log("submitted", { values }); - if (workflow === "create") { - setForkInProgress(true); - createFlake({ - flake_name: values.dest || "default", - url: values.flakeUrl, - }).then(() => { - setForkInProgress(false); - setAppState((s) => ({ ...s, isJoined: true })); - }); - } - })} - className="w-full max-w-2xl justify-self-center" - > - ( - Clan + {vmUuid && showLogs ? ( + setShowLogs(false)} /> + ) : ( + + { + if (workflow === "create") { + try { + await createFlake({ + flake_name: values.dest || "default", + url: values.flakeUrl, + }); + setAppState((s) => ({ ...s, isJoined: true })); + } catch (error) { + toast.error( + `Error: ${(error as AxiosError).message || ""}`, + ); } - endAdornment={ - workflow == "join" ? WorkflowAdornment : undefined + } + if (workflow === "join") { + console.log("JOINING"); + console.log(values); + try { + const response = await createVm({ + cores: values.cores, + flake_attr: values.flake_attr, + flake_url: values.flakeUrl, + graphics: values.graphics, + memory_size: values.memory_size, + }); + const { uuid } = response?.data || null; + setShowLogs(true); + setVmUuid(() => uuid); + if (response.statusText === "OK") { + toast.success(("Joined @ " + uuid) as string); + } else { + toast.error("Could not join"); + } + } catch (error) { + toast.error( + `Error: ${(error as AxiosError).message || ""}`, + ); } + } + })} + className="w-full max-w-2xl justify-self-center" + > + {workflow == "join" && ( + )} - /> - {workflow === "create" && ( - ( - Name - } - endAdornment={ - workflow == "create" ? WorkflowAdornment : undefined - } - /> - )} - /> - )} - - )} - {formState.isSubmitted && workflow == "create" && ( -
- -
- )} - {(formState.isSubmitted || flakeUrl) && workflow == "join" && ( - reset()} - flakeUrl={formState.isSubmitted ? getValues("flakeUrl") : flakeUrl} - flakeAttr={flakeAttr} - /> + {workflow == "create" && ( + + )} + +
)}