diff --git a/pkgs/webview-ui/app/src/Form/fields/Select.tsx b/pkgs/webview-ui/app/src/Form/fields/Select.tsx index 18a98534b..8a149e9b3 100644 --- a/pkgs/webview-ui/app/src/Form/fields/Select.tsx +++ b/pkgs/webview-ui/app/src/Form/fields/Select.tsx @@ -5,6 +5,7 @@ import { type JSX, For, createMemo, + Accessor, } from "solid-js"; import { Portal } from "solid-js/web"; import { useFloating } from "../base"; @@ -46,11 +47,12 @@ interface SelectInputpProps { placeholder?: string; multiple?: boolean; loading?: boolean; - dialogContextId?: string; + portalRef?: Accessor; } export function SelectInput(props: SelectInputpProps) { - const dialogContext = useContext(props.dialogContextId); + const dialogContext = (dialogContextId?: string) => + useContext(dialogContextId); const _id = createUniqueId(); @@ -228,7 +230,11 @@ export function SelectInput(props: SelectInputpProps) { } /> - +
{ const handleMouseMove = (e: MouseEvent) => { if (dragging()) { - const newTop = e.clientY - startOffset().y; - const newLeft = e.clientX - startOffset().x; + let newTop = e.clientY - startOffset().y; + let newLeft = e.clientX - startOffset().x; + if (newTop < 0) { + newTop = 0; + } + if (newLeft < 0) { + newLeft = 0; + } dialogRef.style.top = `${newTop}px`; dialogRef.style.left = `${newLeft}px`; + + // dialogRef.style.maxHeight = `calc(100vh - ${newTop}px - 2rem)`; + // dialogRef.style.maxHeight = `calc(100vh - ${newTop}px - 2rem)`; } }; @@ -53,7 +62,7 @@ export const Modal = (props: ModalProps) => { )} classList={{ "!cursor-grabbing": dragging(), - [cx("scale-105 transition-transform")]: dragging(), + [cx("scale-[101%] transition-transform")]: dragging(), }} ref={(el) => { dialogRef = el; @@ -112,7 +121,10 @@ export const Modal = (props: ModalProps) => { />
- + {props.children} diff --git a/pkgs/webview-ui/app/src/routes/machines/details.tsx b/pkgs/webview-ui/app/src/routes/machines/details.tsx index 1f3c3dfaf..06097b3b6 100644 --- a/pkgs/webview-ui/app/src/routes/machines/details.tsx +++ b/pkgs/webview-ui/app/src/routes/machines/details.tsx @@ -25,7 +25,7 @@ import { HardwareValues, HWStep } from "./install/hardware-step"; import { DiskStep, DiskValues } from "./install/disk-step"; import { SummaryStep } from "./install/summary-step"; import cx from "classnames"; -import { SectionHeader } from "@/src/components/group"; +import { VarsStep, VarsValues } from "./install/vars-step"; type MachineFormInterface = MachineData & { sshKey?: File; @@ -37,7 +37,8 @@ type MachineData = SuccessData<"get_inventory_machine_details">; const steps: Record = { "1": "Hardware detection", "2": "Disk schema", - "3": "Installation", + "3": "Credentials & Data", + "4": "Installation", }; type StepIdx = keyof AllStepsValues; @@ -45,7 +46,8 @@ type StepIdx = keyof AllStepsValues; export interface AllStepsValues extends FieldValues { "1": HardwareValues; "2": DiskValues; - "3": NonNullable; + "3": VarsValues; + "4": NonNullable; } const LoadingBar = () => ( @@ -190,7 +192,7 @@ const InstallMachine = (props: InstallMachineProps) => { }; const Footer = () => ( -
+
- -
- - - { - const prev = getValue(formStore, "1"); - setValue(formStore, "1", { ...prev, ...data }); - handleNext(); - }} - initial={ - getValue(formStore, "1") || { - target: props.targetHost || "", - report: false, - } + + + { + 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(); - }} - // @ts-expect-error: The placeholder type is to wide - initial={{ - ...props.machine.disk_schema, - ...getValue(formStore, "2"), - initialized: !!props.machine.disk_schema, - }} - /> - - - handleNext()} - // @ts-expect-error: This cannot be known. - initial={getValues(formStore)} - footer={ -
- - -
- } - /> -
- -
+ } + footer={
} + /> + + + } + handleNext={(data) => { + const prev = getValue(formStore, "2"); + setValue(formStore, "2", { ...prev, ...data }); + handleNext(); + }} + // @ts-expect-error: The placeholder type is to wide + initial={{ + ...props.machine.disk_schema, + ...getValue(formStore, "2"), + initialized: !!props.machine.disk_schema, + }} + /> + + + } + handleNext={(data) => { + // const prev = getValue(formStore, "2"); + // setValue(formStore, "2", { ...prev, ...data }); + handleNext(); + }} + initial={{ + ...getValue(formStore, "3"), + }} + /> + + + handleNext()} + // @ts-expect-error: This cannot be known. + initial={getValues(formStore)} + footer={ +
+ + +
+ } + /> +
+ } > 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 index 78b1bf3b3..29b07c959 100644 --- a/pkgs/webview-ui/app/src/routes/machines/install/disk-step.tsx +++ b/pkgs/webview-ui/app/src/routes/machines/install/disk-step.tsx @@ -11,6 +11,7 @@ import { StepProps } from "./hardware-step"; import { SelectInput } from "@/src/Form/fields/Select"; import { Typography } from "@/src/components/Typography"; import { Group } from "@/src/components/group"; +import { useContext } from "corvu/dialog"; export interface DiskValues extends FieldValues { placeholders: { @@ -44,67 +45,82 @@ export const DiskStep = (props: StepProps) => { }, })); + const modalContext = useContext(); + return ( -
- - - {(field, fieldProps) => ( - <> - + +
+
+ + - {(field.value || "No schema selected").split("-").join(" ")} - - ( + <> + + {(field.value || "No schema selected") + .split("-") + .join(" ")} + + + Change schema + + + )} + + + + {props.initial?.initialized && + "Disk has been initialized already"} + - Change schema - - - )} - - - - {props.initial?.initialized && "Disk has been initialized already"} - - {(field, fieldProps) => ( - ({ label: o, value: o })) || [ - { label: "No options", value: "" }, - ] - } - error={field.error} - label="Main Disk" - value={field.value || ""} - placeholder="Select a disk" - selectProps={fieldProps} - required={!props.initial?.initialized} - /> - )} - - - {props.footer} - + {(field, fieldProps) => ( + ({ label: o, value: o })) || [ + { label: "No options", value: "" }, + ] + } + error={field.error} + label="Main Disk" + value={field.value || ""} + placeholder="Select a disk" + selectProps={fieldProps} + required={!props.initial?.initialized} + portalRef={modalContext.contentRef} + /> + )} + + +
+
+ {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 e29577020..80110e35c 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 @@ -105,101 +105,105 @@ export const HWStep = (props: StepProps) => { return (
- - - {(field, fieldProps) => ( - - )} - - - - - {(field, fieldProps) => ( - } - label={ - +
+ + + {(field, fieldProps) => ( + - Hardware report - - } - field={ - - -
Loading...
-
- -
Error...
-
- - {(data) => ( - <> - - - - No report - - - - - - Report detected - - - - - - Legacy Report detected - - - - - - )} - -
- } - /> - )} -
-
+ /> + )} + + + + + {(field, fieldProps) => ( + } + label={ + + Hardware report + + } + field={ + + +
Loading...
+
+ +
Error...
+
+ + {(data) => ( + <> + + + + No report + + + + + + Report detected + + + + + + Legacy Report detected + + + + + + )} + +
+ } + /> + )} +
+
+
+
{props.footer} ); 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 index 01dc27518..540a0ab62 100644 --- a/pkgs/webview-ui/app/src/routes/machines/install/summary-step.tsx +++ b/pkgs/webview-ui/app/src/routes/machines/install/summary-step.tsx @@ -12,87 +12,96 @@ export const SummaryStep = (props: StepProps) => { 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. + 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} ); diff --git a/pkgs/webview-ui/app/src/routes/machines/install/vars-step.tsx b/pkgs/webview-ui/app/src/routes/machines/install/vars-step.tsx new file mode 100644 index 000000000..0b7330dc4 --- /dev/null +++ b/pkgs/webview-ui/app/src/routes/machines/install/vars-step.tsx @@ -0,0 +1,86 @@ +import { callApi } from "@/src/api"; +import { + createForm, + SubmitHandler, + validate, + FieldValues, +} from "@modular-forms/solid"; +import { createQuery } from "@tanstack/solid-query"; +import { StepProps } from "./hardware-step"; +import { Typography } from "@/src/components/Typography"; +import { Group } from "@/src/components/group"; +import { For, Match, Show, Switch } from "solid-js"; + +export type VarsValues = FieldValues & Record; + +export const VarsStep = (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 generatorsQuery = createQuery(() => ({ + queryKey: [props.dir, props.machine_id, "generators"], + queryFn: async () => { + const result = await callApi("get_generators", { + base_dir: props.dir, + machine_name: props.machine_id, + }); + if (result.status === "error") throw new Error("Failed to fetch data"); + return result.data; + }, + })); + + return ( +
+
+
+ + Loading ... + + {(generators) => ( + + {(generator) => ( + + + {generator.name} + +
+ Bound to module (shared):{" "} + {generator.share ? "True" : "False"} +
+ + {(f) => ( + + + {!f.previous_value ? "Required" : "Optional"} + + + {f.name} + + + )} + +
+ )} +
+ )} +
+
+
+
+ {props.footer} +
+ ); +};