UI: finish up create installer subflow

This commit is contained in:
Johannes Kirschbauer
2025-08-08 21:03:49 +02:00
parent 1eb567682c
commit 47c94c51b6
2 changed files with 51 additions and 80 deletions

View File

@@ -1,6 +1,7 @@
import { Modal } from "@/src/components/Modal/Modal";
import {
createStepper,
getStepStore,
StepperProvider,
useStepper,
} from "@/src/hooks/stepper";
@@ -10,6 +11,7 @@ import { Dynamic } from "solid-js/web";
import { initialSteps } from "./steps/Initial";
import { createInstallerSteps } from "./steps/createInstaller";
import { installSteps } from "./steps/installSteps";
import { ApiCall } from "@/src/hooks/api";
interface InstallForm extends FieldValues {
data_from_step_1: string;
@@ -54,10 +56,13 @@ const steps = [
export type InstallSteps = typeof steps;
export interface InstallStoreType {
flash: {
language: string;
keymap: string;
ssh_file: string;
device: string;
progress: ApiCall<"run_machine_flash">;
};
install: {
targetHost: string;
machineName: string;
};
}
@@ -78,6 +83,9 @@ export const InstallModal = (props: InstallModalProps) => {
</Show>
);
};
const [store, set] = getStepStore<InstallStoreType>(stepper);
set("install", { machineName: props.machineName });
return (
<StepperProvider stepper={stepper}>

View File

@@ -20,8 +20,9 @@ import {
useMachineFlashOptions,
useSystemStorageOptions,
} from "@/src/hooks/queries";
import { useClanURI } from "@/src/hooks/clan";
import { useApiClient } from "@/src/hooks/ApiClient";
import { createEffect, onMount } from "solid-js";
import { create } from "storybook/internal/theming";
const Prose = () => (
<StepLayout
@@ -94,26 +95,25 @@ const ConfigureImageSchema = v.object({
v.string("Please select a key."),
v.nonEmpty("Please select a key."),
),
language: v.pipe(v.string(), v.nonEmpty("Please choose a language.")),
keymap: v.pipe(v.string(), v.nonEmpty("Please select a keyboard layout.")),
});
type ConfigureImageForm = v.InferInput<typeof ConfigureImageSchema>;
const ConfigureImage = () => {
const stepSignal = useStepper<InstallSteps>();
const [store, set] = getStepStore<InstallStoreType>(stepSignal);
const [formStore, { Form, Field }] = createForm<ConfigureImageForm>({
validate: valiForm(ConfigureImageSchema),
initialValues: {
ssh_key: store.flash?.ssh_file,
},
});
const stepSignal = useStepper<InstallSteps>();
const [store, set] = getStepStore<InstallStoreType>(stepSignal);
const handleSubmit: SubmitHandler<ConfigureImageForm> = (values, event) => {
// Push values to the store
set("flash", (s) => ({
...s,
language: values.language,
keymap: values.keymap,
ssh_file: values.ssh_key,
}));
@@ -124,8 +124,9 @@ const ConfigureImage = () => {
const onSelectFile = async () => {
const req = client.fetch("get_system_file", {
file_request: {
mode: "select_folder",
mode: "get_system_file",
title: "Select a folder for you new Clan",
initial_folder: "~/.ssh",
},
});
@@ -144,14 +145,20 @@ const ConfigureImage = () => {
throw new Error("No data returned from api call");
};
const currClan = useClanURI();
const optionsQuery = useMachineFlashOptions(currClan);
const optionsQuery = useMachineFlashOptions();
let content: Node;
return (
<Form onSubmit={handleSubmit}>
<StepLayout
body={
<div class="flex flex-col gap-2">
<div
class="flex flex-col gap-2"
ref={(el) => {
content = el;
}}
>
<Fieldset>
<Field name="ssh_key">
{(field, input) => (
@@ -172,66 +179,6 @@ const ConfigureImage = () => {
)}
</Field>
</Fieldset>
<Fieldset>
<Field name="language">
{(field, props) => (
<Select
{...props}
value={field.value}
error={field.error}
required
label={{
label: "Language",
description: "Select your preferred language",
}}
getOptions={async () => {
if (!optionsQuery.data) {
await optionsQuery.refetch();
}
return (optionsQuery.data?.languages ?? []).map(
(lang) => ({
// TODO: Pretty label ?
value: lang,
label: lang,
}),
);
}}
placeholder="Language"
name={field.name}
/>
)}
</Field>
<Field name="keymap">
{(field, props) => (
<Select
{...props}
value={field.value}
error={field.error}
required
label={{
label: "Keymap",
description: "Select your keyboard layout",
}}
getOptions={async () => {
if (!optionsQuery.data) {
await optionsQuery.refetch();
}
return (optionsQuery.data?.keymaps ?? []).map(
(keymap) => ({
// TODO: Pretty label ?
value: keymap,
label: keymap,
}),
);
}}
placeholder="Keymap"
name={field.name}
/>
)}
</Field>
</Fieldset>
</div>
}
footer={
@@ -256,11 +203,14 @@ type ChooseDiskForm = v.InferInput<typeof ChooseDiskSchema>;
const ChooseDisk = () => {
const stepSignal = useStepper<InstallSteps>();
const [store, set] = getStepStore<InstallStoreType>(stepSignal);
const [formStore, { Form, Field }] = createForm<ChooseDiskForm>({
validate: valiForm(ChooseDiskSchema),
initialValues: {
disk: store.flash?.device,
},
});
const [store, set] = getStepStore<InstallStoreType>(stepSignal);
const client = useApiClient();
const systemStorageQuery = useSystemStorageOptions();
@@ -273,8 +223,6 @@ const ChooseDisk = () => {
}));
const call = client.fetch("run_machine_flash", {
system_config: {
keymap: store.flash.keymap,
language: store.flash.language,
ssh_keys_path: [store.flash.ssh_file],
},
disks: [
@@ -284,13 +232,15 @@ const ChooseDisk = () => {
},
],
});
// TOOD: Pass the "call" Promise to the "progress step"
set("flash", "progress", call);
console.log("Flashing", store.flash);
// Here you would typically trigger the disk selection process
stepSignal.next();
};
const stripId = (s: string) => s.split("-")[1] ?? s;
return (
<Form onSubmit={handleSubmit}>
<StepLayout
@@ -300,6 +250,7 @@ const ChooseDisk = () => {
<Field name="disk">
{(field, props) => (
<Select
zIndex={100}
{...props}
value={field.value}
error={field.error}
@@ -312,11 +263,12 @@ const ChooseDisk = () => {
if (!systemStorageQuery.data) {
await systemStorageQuery.refetch();
}
console.log(systemStorageQuery.data);
return (systemStorageQuery.data?.blockdevices ?? []).map(
(dev) => ({
value: dev.path,
label: dev.name,
label: stripId(dev.id_link),
}),
);
}}
@@ -346,6 +298,17 @@ const ChooseDisk = () => {
};
const FlashProgress = () => {
const stepSignal = useStepper<InstallSteps>();
const [store, set] = getStepStore<InstallStoreType>(stepSignal);
onMount(async () => {
const result = await store.flash.progress.result;
if (result.status == "success") {
console.log("Flashing Success");
}
stepSignal.next();
});
return (
<div class="flex h-60 w-full flex-col items-center justify-end bg-inv-4">
<div class="mb-6 flex w-full max-w-md flex-col items-center gap-3 fg-inv-1">