+ {handleClose && (
+
+ )}
{title}
{lines.map((item, idx) => (
diff --git a/pkgs/ui/src/components/join/vmBuildLogs.tsx b/pkgs/ui/src/components/join/vmBuildLogs.tsx
index eb62802cc..f5bab1219 100644
--- a/pkgs/ui/src/components/join/vmBuildLogs.tsx
+++ b/pkgs/ui/src/components/join/vmBuildLogs.tsx
@@ -1,29 +1,63 @@
"use client";
-import { useGetVmLogs } from "@/api/vm/vm";
+
+import { getGetVmLogsKey } from "@/api/vm/vm";
+import axios from "axios";
+import { Dispatch, SetStateAction, useEffect, useState } from "react";
import { Log } from "./log";
-import { LoadingOverlay } from "./loadingOverlay";
interface VmBuildLogsProps {
vmUuid: string;
+ handleClose: () => void;
}
-export const VmBuildLogs = (props: VmBuildLogsProps) => {
- const { vmUuid } = props;
- const { data: logs, isLoading } = useGetVmLogs(vmUuid as string, {
- swr: {
- enabled: vmUuid !== null,
- },
- axios: {
- responseType: "stream",
- },
- });
+const streamLogs = async (
+ uuid: string,
+ setter: Dispatch>,
+ onFinish: () => void,
+) => {
+ const apiPath = getGetVmLogsKey(uuid);
+ const baseUrl = axios.defaults.baseURL;
+
+ const response = await fetch(`${baseUrl}${apiPath}`);
+ const reader = response?.body?.getReader();
+ if (!reader) {
+ console.log("could not get reader");
+ }
+ while (true) {
+ const stream = await reader?.read();
+ if (!stream || stream.done) {
+ console.log("stream done");
+ onFinish();
+ break;
+ }
+
+ const text = new TextDecoder().decode(stream.value);
+ setter((s) => `${s}${text}`);
+ console.log("Received", stream.value);
+ console.log("String:", text);
+ }
+};
+
+export const VmBuildLogs = (props: VmBuildLogsProps) => {
+ const { vmUuid, handleClose } = props;
+ const [logs, setLogs] = useState("");
+ const [done, setDone] = useState(false);
+
+ // Reset the logs if uuid changes
+ useEffect(() => {
+ setLogs("");
+ setDone(false);
+ }, [vmUuid]);
+
+ !done && streamLogs(vmUuid, setLogs, () => setDone(true));
return (
- {isLoading && }
+ {/* {isLoading && } */}
);
diff --git a/pkgs/ui/src/views/createForm.tsx b/pkgs/ui/src/views/createForm.tsx
new file mode 100644
index 000000000..8c80dee73
--- /dev/null
+++ b/pkgs/ui/src/views/createForm.tsx
@@ -0,0 +1,56 @@
+import { Input, InputAdornment, LinearProgress } from "@mui/material";
+import { Controller, useFormContext } from "react-hook-form";
+
+interface CreateFormProps {
+ confirmAdornment?: React.ReactNode;
+}
+
+export const CreateForm = (props: CreateFormProps) => {
+ const { confirmAdornment } = props;
+ const {
+ control,
+ formState: { isSubmitting },
+ } = useFormContext();
+ return (
+
+ (
+ 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 && (
-
+
)}