UI: add vars step to installation flow
This commit is contained in:
@@ -22,7 +22,7 @@ import { ApiTester } from "./api_test";
|
||||
import { IconVariant } from "./components/icon";
|
||||
import { Components } from "./routes/components";
|
||||
import { activeURI } from "./App";
|
||||
import { VarsForMachine, VarsStep } from "./routes/machines/install/vars-step";
|
||||
import { VarsPage, VarsForm } from "./routes/machines/install/vars-step";
|
||||
import { ThreePlayground } from "./three";
|
||||
|
||||
export const client = new QueryClient();
|
||||
@@ -79,7 +79,7 @@ export const routes: AppRoute[] = [
|
||||
path: "/:id/vars",
|
||||
label: "Vars",
|
||||
hidden: true,
|
||||
component: () => <VarsForMachine />,
|
||||
component: () => <VarsPage />,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -293,7 +293,19 @@ const InstallMachine = (props: InstallMachineProps) => {
|
||||
/>
|
||||
</Match>
|
||||
<Match when={step() === "3"}>
|
||||
<div>TODO: vars</div>
|
||||
<VarsStep
|
||||
// @ts-expect-error: This cannot be undefined in this context.
|
||||
machine_id={props.name}
|
||||
// @ts-expect-error: This cannot be undefined in this context.
|
||||
dir={activeURI()}
|
||||
handleNext={(data) => {
|
||||
const prev = getValue(formStore, "3");
|
||||
setValue(formStore, "3", { ...prev, ...data });
|
||||
handleNext();
|
||||
}}
|
||||
initial={getValue(formStore, "3") || {}}
|
||||
footer={<Footer />}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={step() === "4"}>
|
||||
<SummaryStep
|
||||
@@ -433,8 +445,10 @@ const MachineForm = (props: MachineDetailsProps) => {
|
||||
const handleUpdateButton = async () => {
|
||||
await generatorsQuery.refetch();
|
||||
|
||||
if (generatorsQuery.data?.length !== 0) {
|
||||
navigate(`/machines/${machineName()}/vars`);
|
||||
if (
|
||||
generatorsQuery.data?.some((generator) => generator.prompts?.length !== 0)
|
||||
) {
|
||||
navigate(`/machines/${machineName()}/vars?action=update`);
|
||||
} else {
|
||||
handleUpdate();
|
||||
}
|
||||
@@ -472,6 +486,7 @@ const MachineForm = (props: MachineDetailsProps) => {
|
||||
|
||||
createEffect(() => {
|
||||
const action = searchParams.action;
|
||||
console.log({ action });
|
||||
if (action === "update") {
|
||||
setSearchParams({ action: undefined });
|
||||
handleUpdate();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { callApi } from "@/src/api";
|
||||
import { callApi, SuccessData } from "@/src/api";
|
||||
import {
|
||||
createForm,
|
||||
FieldValues,
|
||||
@@ -11,24 +11,82 @@ import { Group } from "@/src/components/group";
|
||||
import { For, Match, Show, Switch } from "solid-js";
|
||||
import { TextInput } from "@/src/Form/fields";
|
||||
import toast from "solid-toast";
|
||||
import { useNavigate, useParams } from "@solidjs/router";
|
||||
import { useNavigate, useParams, useSearchParams } from "@solidjs/router";
|
||||
import { activeURI } from "@/src/App";
|
||||
import { StepProps } from "./hardware-step";
|
||||
|
||||
export type VarsValues = FieldValues & Record<string, Record<string, string>>;
|
||||
|
||||
export interface VarsStepProps {
|
||||
machine_id: string;
|
||||
dir: string;
|
||||
}
|
||||
|
||||
export const VarsStep = (props: VarsStepProps) => {
|
||||
const [formStore, { Form, Field }] = createForm<VarsValues>({});
|
||||
|
||||
const navigate = useNavigate();
|
||||
export const VarsStep = (props: StepProps<VarsValues>) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const generatorsQuery = createQuery(() => ({
|
||||
queryKey: [props.dir, props.machine_id, "generators"],
|
||||
queryFn: async () => {
|
||||
const result = await callApi("get_generators_closure", {
|
||||
base_dir: props.dir,
|
||||
machine_name: props.machine_id,
|
||||
});
|
||||
if (result.status === "error") throw new Error("Failed to fetch data");
|
||||
return result.data;
|
||||
},
|
||||
}));
|
||||
|
||||
const handleSubmit: SubmitHandler<VarsValues> = async (values, event) => {
|
||||
console.log("Submit Disk", { values });
|
||||
const loading_toast = toast.loading("Generating vars...");
|
||||
if (generatorsQuery.data === undefined) {
|
||||
toast.error("Error fetching data");
|
||||
return;
|
||||
}
|
||||
const result = await callApi("generate_vars_for_machine", {
|
||||
machine_name: props.machine_id,
|
||||
base_dir: props.dir,
|
||||
generators: generatorsQuery.data.map((generator) => generator.name),
|
||||
all_prompt_values: values,
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [props.dir, props.machine_id, "generators"],
|
||||
});
|
||||
toast.dismiss(loading_toast);
|
||||
if (result.status === "error") {
|
||||
toast.error(result.errors[0].message);
|
||||
return;
|
||||
}
|
||||
if (result.status === "success") {
|
||||
toast.success("Vars saved successfully");
|
||||
}
|
||||
props.handleNext(values);
|
||||
};
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Match when={generatorsQuery.isLoading}>Loading ...</Match>
|
||||
<Match when={generatorsQuery.data}>
|
||||
{(generators) => (
|
||||
<VarsForm
|
||||
machine_id={props.machine_id}
|
||||
dir={props.dir}
|
||||
handleSubmit={handleSubmit}
|
||||
generators={generators()}
|
||||
/>
|
||||
)}
|
||||
</Match>
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
|
||||
export interface VarsFormProps {
|
||||
machine_id: string;
|
||||
dir: string;
|
||||
handleSubmit: SubmitHandler<VarsValues>;
|
||||
generators: SuccessData<"get_generators_closure">;
|
||||
}
|
||||
|
||||
export const VarsForm = (props: VarsFormProps) => {
|
||||
const [formStore, { Form, Field }] = createForm<VarsValues>({});
|
||||
|
||||
const handleSubmit: SubmitHandler<VarsValues> = async (values, event) => {
|
||||
console.log("Submit Vars", { values });
|
||||
// sanitize the values back (replace __dot__)
|
||||
// This hack is needed because we are using "." in the keys of the form
|
||||
const sanitizedValues = Object.fromEntries(
|
||||
@@ -43,43 +101,13 @@ export const VarsStep = (props: VarsStepProps) => {
|
||||
]),
|
||||
) as VarsValues;
|
||||
const valid = await validate(formStore);
|
||||
if (generatorsQuery.data === undefined) {
|
||||
toast.error("Error fetching data");
|
||||
if (!valid) {
|
||||
toast.error("Please fill all required fields");
|
||||
return;
|
||||
}
|
||||
const loading_toast = toast.loading("Generating vars...");
|
||||
const result = await callApi("generate_vars_for_machine", {
|
||||
machine_name: props.machine_id,
|
||||
base_dir: props.dir,
|
||||
generators: generatorsQuery.data.map((generator) => generator.name),
|
||||
all_prompt_values: sanitizedValues,
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [props.dir, props.machine_id, "generators"],
|
||||
});
|
||||
toast.dismiss(loading_toast);
|
||||
if (result.status === "error") {
|
||||
toast.error(result.errors[0].message);
|
||||
return;
|
||||
}
|
||||
if (result.status === "success") {
|
||||
toast.success("Vars saved successfully");
|
||||
navigate(`/machines/${props.machine_id}?action=update`);
|
||||
}
|
||||
props.handleSubmit(sanitizedValues, event);
|
||||
};
|
||||
|
||||
const generatorsQuery = createQuery(() => ({
|
||||
queryKey: [props.dir, props.machine_id, "generators"],
|
||||
queryFn: async () => {
|
||||
const result = await callApi("get_generators_closure", {
|
||||
base_dir: props.dir,
|
||||
machine_name: props.machine_id,
|
||||
});
|
||||
if (result.status === "error") throw new Error("Failed to fetch data");
|
||||
return result.data;
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<Form
|
||||
onSubmit={handleSubmit}
|
||||
@@ -88,79 +116,71 @@ export const VarsStep = (props: VarsStepProps) => {
|
||||
>
|
||||
<div class="max-h-[calc(100vh-20rem)] overflow-y-scroll">
|
||||
<div class="flex h-full flex-col gap-6 p-4">
|
||||
<Switch>
|
||||
<Match when={generatorsQuery.isLoading}>Loading ...</Match>
|
||||
<Match when={generatorsQuery.data}>
|
||||
{(generators) => (
|
||||
<For each={generators()}>
|
||||
{(generator) => (
|
||||
<For each={props.generators}>
|
||||
{(generator) => (
|
||||
<Group>
|
||||
<Typography hierarchy="label" size="default">
|
||||
{generator.name}
|
||||
</Typography>
|
||||
<div>
|
||||
Bound to module (shared): {generator.share ? "True" : "False"}
|
||||
</div>
|
||||
<For each={generator.prompts}>
|
||||
{(prompt) => (
|
||||
<Group>
|
||||
<Typography hierarchy="label" size="default">
|
||||
{generator.name}
|
||||
<Typography hierarchy="label" size="s">
|
||||
{!prompt.previous_value ? "Required" : "Optional"}
|
||||
</Typography>
|
||||
<div>
|
||||
Bound to module (shared):{" "}
|
||||
{generator.share ? "True" : "False"}
|
||||
</div>
|
||||
<For each={generator.prompts}>
|
||||
{(prompt) => (
|
||||
<Group>
|
||||
<Typography hierarchy="label" size="s">
|
||||
{!prompt.previous_value ? "Required" : "Optional"}
|
||||
</Typography>
|
||||
<Typography hierarchy="label" size="s">
|
||||
{prompt.name}
|
||||
</Typography>
|
||||
{/* Avoid nesting issue in case of a "." */}
|
||||
<Field
|
||||
name={`${generator.name.replaceAll(
|
||||
".",
|
||||
"__dot__",
|
||||
)}.${prompt.name.replaceAll(".", "__dot__")}`}
|
||||
<Typography hierarchy="label" size="s">
|
||||
{prompt.name}
|
||||
</Typography>
|
||||
<Field
|
||||
// Avoid nesting issue in case of a "."
|
||||
name={`${generator.name.replaceAll(
|
||||
".",
|
||||
"__dot__",
|
||||
)}.${prompt.name.replaceAll(".", "__dot__")}`}
|
||||
>
|
||||
{(field, props) => (
|
||||
<Switch
|
||||
fallback={
|
||||
<TextInput
|
||||
inputProps={{
|
||||
...props,
|
||||
type:
|
||||
prompt.prompt_type === "hidden"
|
||||
? "password"
|
||||
: "text",
|
||||
}}
|
||||
label={prompt.description}
|
||||
value={prompt.previous_value ?? ""}
|
||||
error={field.error}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Match
|
||||
when={
|
||||
prompt.prompt_type === "multiline" ||
|
||||
prompt.prompt_type === "multiline-hidden"
|
||||
}
|
||||
>
|
||||
{(field, props) => (
|
||||
<Switch
|
||||
fallback={
|
||||
<TextInput
|
||||
inputProps={{
|
||||
...props,
|
||||
type:
|
||||
prompt.prompt_type === "hidden"
|
||||
? "password"
|
||||
: "text",
|
||||
}}
|
||||
label={prompt.description}
|
||||
value={prompt.previous_value ?? ""}
|
||||
error={field.error}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Match
|
||||
when={
|
||||
prompt.prompt_type === "multiline" ||
|
||||
prompt.prompt_type === "multiline-hidden"
|
||||
}
|
||||
>
|
||||
<textarea
|
||||
{...props}
|
||||
class="w-full h-32 border border-gray-300 rounded-md p-2"
|
||||
placeholder={prompt.description}
|
||||
value={prompt.previous_value ?? ""}
|
||||
name={prompt.description}
|
||||
/>
|
||||
</Match>
|
||||
</Switch>
|
||||
)}
|
||||
</Field>
|
||||
</Group>
|
||||
<textarea
|
||||
{...props}
|
||||
class="w-full h-32 border border-gray-300 rounded-md p-2"
|
||||
placeholder={prompt.description}
|
||||
value={prompt.previous_value ?? ""}
|
||||
name={prompt.description}
|
||||
/>
|
||||
</Match>
|
||||
</Switch>
|
||||
)}
|
||||
</For>
|
||||
</Field>
|
||||
</Group>
|
||||
)}
|
||||
</For>
|
||||
)}
|
||||
</Match>
|
||||
</Switch>
|
||||
</Group>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit">Submit</button>
|
||||
@@ -168,12 +188,27 @@ export const VarsStep = (props: VarsStepProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const VarsForMachine = () => {
|
||||
export const VarsPage = () => {
|
||||
const params = useParams();
|
||||
|
||||
const navigate = useNavigate();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const handleNext = (values: VarsValues) => {
|
||||
if (searchParams?.action === "update") {
|
||||
navigate(`/machines/${params.id}?action=update`);
|
||||
} else {
|
||||
toast.error("Invalid action for vars page");
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Show when={activeURI()}>
|
||||
{(uri) => <VarsStep machine_id={params.id} dir={uri()} />}
|
||||
{(uri) => (
|
||||
<VarsStep
|
||||
machine_id={params.id}
|
||||
dir={uri()}
|
||||
handleNext={handleNext}
|
||||
footer
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user