diff --git a/pkgs/webview-ui/app/src/Form/fields/Select.tsx b/pkgs/webview-ui/app/src/Form/fields/Select.tsx index 26990bbcf..5b92cf391 100644 --- a/pkgs/webview-ui/app/src/Form/fields/Select.tsx +++ b/pkgs/webview-ui/app/src/Form/fields/Select.tsx @@ -18,6 +18,7 @@ import { } from "@/src/components/inputBase"; import { FieldLayout } from "./layout"; import Icon from "@/src/components/icon"; +import { useContext } from "corvu/dialog"; export interface Option { value: string; @@ -45,9 +46,12 @@ interface SelectInputpProps { placeholder?: string; multiple?: boolean; loading?: boolean; + dialogContextId?: string; } export function SelectInput(props: SelectInputpProps) { + const dialogContext = useContext(props.dialogContextId); + const _id = createUniqueId(); const [reference, setReference] = createSignal(); @@ -223,7 +227,7 @@ export function SelectInput(props: SelectInputpProps) { } /> - +
); - return ( -
-
- - Install:{" "} - - - {props.name} - -
- {/* Stepper container */} -
- {/* A Step with a circle a number inside. Label is below */} - - {([idx, label]) => ( -
- - = step()} - fallback={} - > - {idx} - - - - {label} - -
- )} -
-
-
- - - handleNext()} - footer={
} - /> - - - - - Single Disk - - - Change schema - - - - - -
- - -
- - Hardware Report - - - Target} - field={ - - 192.157.124.81 - - } - > - -
-
- - Disk Configuration - - - Disk Layout} - field={ - - Single Disk - - } - > -
- Main Disk} - field={ - - Samsung evo 850 efkjhasd - - } - > -
-
- + return ( + + {/* Register each step as form field */} + {/* @ts-expect-error: object type is not statically supported */} + {(field, fieldProps) => <>} + {/* @ts-expect-error: object type is not statically supported */} + {(field, fieldProps) => <>} + + {/* Modal Header */} +
+ + Install:{" "} + + + {props.name} + +
+ {/* Stepper header */} +
+ + {([idx, label]) => ( +
- Setup your device. + = step()} + fallback={} + > + {idx} + - This will erase the disk and bootstrap fresh. + {label} - - } - /> -
- - - -
-
+
+ )} + +
+ +
+ + + { + const prev = getValue(formStore, "1"); + setValue(formStore, "1", { ...prev, ...data }); + handleNext(); + }} + initial={ + getValue(formStore, "1") || { + target: props.targetHost || "", + report: false, + } + } + footer={
} + /> + + + } + handleNext={(data) => { + const prev = getValue(formStore, "2"); + setValue(formStore, "2", { ...prev, ...data }); + handleNext(); + }} + initial={getValue(formStore, "2")} + /> + + + handleNext()} + // @ts-expect-error: This cannot be known. + initial={getValues(formStore)} + footer={ +
+ + +
+ } + /> +
+ +
+ + } + > + +
+
+ + Install: + + + {props.name} + +
+
+ +
+ + {progressText()} + + +
+
+
+ ); }; @@ -401,7 +366,6 @@ const MachineForm = (props: MachineDetailsProps) => { initialValues: props.initialData, }); - const sshKey = () => getValue(formStore, "sshKey"); const targetHost = () => getValue(formStore, "machine.deploy.targetHost"); const machineName = () => getValue(formStore, "machine.name") || props.initialData.machine.name; @@ -479,9 +443,10 @@ const MachineForm = (props: MachineDetailsProps) => { }; return ( <> -
- General - +
+ + +
{(field, props) => ( @@ -564,37 +529,6 @@ const MachineForm = (props: MachineDetailsProps) => { /> )} - - {(field, props) => ( - <> - { - event.preventDefault(); // Prevent the native file dialog from opening - const input = event.target; - const files = await selectSshKeys(); - - // Set the files - Object.defineProperty(input, "files", { - value: files, - writable: true, - }); - // Define the files property on the input element - const changeEvent = new Event("input", { - bubbles: true, - cancelable: true, - }); - input.dispatchEvent(changeEvent); - }} - placeholder={"When empty the default key(s) will be used"} - value={field.value} - error={field.error} - helperText="Provide the SSH key used to connect to the machine" - label="SSH Key" - /> - - )} -
@@ -641,9 +575,7 @@ const MachineForm = (props: MachineDetailsProps) => { > @@ -692,140 +624,16 @@ export const MachineDetails = () => { return ( <>
-
- } - > - {(data) => ( - <> - - - )} - -
+ } + > + {(data) => ( + <> + + + )} + ); }; - -interface Wifi extends FieldValues { - name: string; - ssid?: string; - password?: string; -} - -interface WifiForm extends FieldValues { - networks: Wifi[]; -} - -interface MachineWifiProps { - base_url: string; - machine_name: string; - initialData: Wifi[]; -} -function WifiModule(props: MachineWifiProps) { - // You can use formData to initialize your form fields: - // const initialFormState = formData(); - - const [formStore, { Form, Field }] = createForm({ - initialValues: { - networks: props.initialData, - }, - }); - - const [nets, setNets] = createSignal<1[]>( - new Array(props.initialData.length || 1).fill(1), - ); - - const handleSubmit = async (values: WifiForm) => { - const networks = values.networks - .filter((i) => i.ssid) - .reduce( - (acc, curr) => ({ - ...acc, - [curr.ssid || ""]: { ssid: curr.ssid, password: curr.password }, - }), - {}, - ); - - console.log("submitting", values, networks); - // const r = await callApi("set_iwd_service_for_machine", { - // base_url: props.base_url, - // machine_name: props.machine_name, - // networks: networks, - // }); - // if (r.status === "error") { - toast.error("Failed to set wifi. Feature disabled temporarily"); - // } - // if (r.status === "success") { - // toast.success("Wifi set successfully"); - // } - }; - - return ( - - Preconfigure wireless networks - - {(_, idx) => ( -
- - {(field, props) => ( - - )} - - - {(field, props) => ( - - )} - - -
- )} -
- - { -
- -
- } - - ); -} diff --git a/pkgs/webview-ui/app/src/routes/machines/install/disk-step.tsx b/pkgs/webview-ui/app/src/routes/machines/install/disk-step.tsx new file mode 100644 index 000000000..a23e9388b --- /dev/null +++ b/pkgs/webview-ui/app/src/routes/machines/install/disk-step.tsx @@ -0,0 +1,110 @@ +import { callApi } from "@/src/api"; +import { + createForm, + SubmitHandler, + validate, + required, + FieldValues, +} from "@modular-forms/solid"; +import { createQuery } from "@tanstack/solid-query"; +import { StepProps } from "./hardware-step"; +import { SelectInput } from "@/src/Form/fields/Select"; +import { Typography } from "@/src/components/Typography"; +import { Group } from "@/src/components/group"; + +export interface DiskValues extends FieldValues { + placeholders: { + mainDisk: string; + }; + schema: string; +} +export const DiskStep = (props: StepProps) => { + const [formStore, { Form, Field }] = createForm({ + initialValues: { ...props.initial, schema: "single-disk" }, + }); + + const handleSubmit: SubmitHandler = async (values, event) => { + console.log("Submit Disk", { values }); + const valid = await validate(formStore); + console.log("Valid", valid); + if (!valid) return; + props.handleNext(values); + }; + + const diskSchemaQuery = createQuery(() => ({ + queryKey: [props.dir, props.machine_id, "disk_schemas"], + queryFn: async () => { + const result = await callApi("get_disk_schemas", { + base_path: props.dir, + machine_name: props.machine_id, + }); + if (result.status === "error") throw new Error("Failed to fetch data"); + return result.data; + }, + })); + + return ( +
+ + + {(field, fieldProps) => ( + <> + + {(field.value || "No schema selected").split("-").join(" ")} + + + Change schema + + + )} + + + + + {(field, fieldProps) => ( + ({ label: o, value: o })) || [ + { label: "No options", value: "" }, + ] + } + // options={ + // deviceQuery.data?.blockdevices.map((d) => ({ + // value: d.path, + // label: `${d.path} -- ${d.size} bytes`, + // })) || [] + // } + error={field.error} + label="Main Disk" + value={field.value || ""} + placeholder="Select a disk" + selectProps={fieldProps} + required + /> + )} + + + {props.footer} +
+ ); +}; diff --git a/pkgs/webview-ui/app/src/routes/machines/install/hardware-step.tsx b/pkgs/webview-ui/app/src/routes/machines/install/hardware-step.tsx index 8ce0ab499..e29577020 100644 --- a/pkgs/webview-ui/app/src/routes/machines/install/hardware-step.tsx +++ b/pkgs/webview-ui/app/src/routes/machines/install/hardware-step.tsx @@ -16,33 +16,34 @@ import { } from "@modular-forms/solid"; import { createEffect, createSignal, JSX, Match, Switch } from "solid-js"; import toast from "solid-toast"; -import { Group } from "../details"; import { TextInput } from "@/src/Form/fields"; import { createQuery } from "@tanstack/solid-query"; +import { Badge } from "@/src/components/badge"; +import { Group } from "@/src/components/group"; -interface Hardware extends FieldValues { +export type HardwareValues = FieldValues & { report: boolean; target: string; -} +}; -export interface StepProps { +export interface StepProps { machine_id: string; dir: string; - handleNext: () => void; + handleNext: (data: T) => void; footer: JSX.Element; - initial?: Partial; + initial?: T; } -export const HWStep = (props: StepProps) => { - const [formStore, { Form, Field }] = createForm({ - initialValues: props.initial || {}, +export const HWStep = (props: StepProps) => { + const [formStore, { Form, Field }] = createForm({ + initialValues: (props.initial as HardwareValues) || {}, }); - const handleSubmit: SubmitHandler = async (values, event) => { + const handleSubmit: SubmitHandler = async (values, event) => { console.log("Submit Hardware", { values }); const valid = await validate(formStore); console.log("Valid", valid); if (!valid) return; - props.handleNext(); + props.handleNext(values); }; const [isGenerating, setIsGenerating] = createSignal(false); @@ -148,20 +149,46 @@ export const HWStep = (props: StepProps) => { <> + + No report + -
Detected
+ + Report detected + +
-
Nixos report Detected
+ + Legacy Report detected + +
diff --git a/pkgs/webview-ui/app/src/routes/machines/install/summary-step.tsx b/pkgs/webview-ui/app/src/routes/machines/install/summary-step.tsx new file mode 100644 index 000000000..01dc27518 --- /dev/null +++ b/pkgs/webview-ui/app/src/routes/machines/install/summary-step.tsx @@ -0,0 +1,99 @@ +import { StepProps } from "./hardware-step"; +import { Typography } from "@/src/components/Typography"; +import { FieldLayout } from "@/src/Form/fields/layout"; +import { InputLabel } from "@/src/components/inputBase"; +import { Group, Section, SectionHeader } from "@/src/components/group"; +import { AllStepsValues } from "../details"; +import { Badge } from "@/src/components/badge"; +import Icon from "@/src/components/icon"; + +export const SummaryStep = (props: StepProps) => { + const hwValues = () => props.initial?.["1"]; + const diskValues = () => props.initial?.["2"]; + return ( + <> +
+ + Hardware Report + + + Detected} + field={ + hwValues()?.report ? ( + + + + ) : ( + + + + ) + } + > + Target} + field={ + + {hwValues()?.target} + + } + > + +
+
+ + Disk Configuration + + + Disk Layout} + field={ + + {diskValues()?.schema} + + } + > +
+ Main Disk} + field={ + + {diskValues()?.placeholders.mainDisk} + + } + > +
+
+ + + Setup your device. + + + This will erase the disk and bootstrap fresh. + + + } + /> + {props.footer} + + ); +};