UI: improve disk workflow when already initialized

This commit is contained in:
Johannes Kirschbauer
2025-01-07 09:20:00 +01:00
parent 9fa322af93
commit 3a3ab37a18
2 changed files with 87 additions and 59 deletions

View File

@@ -21,10 +21,10 @@ import { InputLabel } from "@/src/components/inputBase";
import { FieldLayout } from "@/src/Form/fields/layout"; import { FieldLayout } from "@/src/Form/fields/layout";
import { Modal } from "@/src/components/modal"; import { Modal } from "@/src/components/modal";
import { Typography } from "@/src/components/Typography"; import { Typography } from "@/src/components/Typography";
import cx from "classnames";
import { HardwareValues, HWStep } from "./install/hardware-step"; import { HardwareValues, HWStep } from "./install/hardware-step";
import { DiskStep, DiskValues } from "./install/disk-step"; import { DiskStep, DiskValues } from "./install/disk-step";
import { SummaryStep } from "./install/summary-step"; import { SummaryStep } from "./install/summary-step";
import cx from "classnames";
import { SectionHeader } from "@/src/components/group"; import { SectionHeader } from "@/src/components/group";
type MachineFormInterface = MachineData & { type MachineFormInterface = MachineData & {
@@ -48,12 +48,30 @@ export interface AllStepsValues extends FieldValues {
"3": NonNullable<unknown>; "3": NonNullable<unknown>;
} }
const LoadingBar = () => (
<div
class="h-3 w-80 overflow-hidden rounded-[3px] border-2 border-def-1"
style={{
background: `repeating-linear-gradient(
45deg,
#ccc,
#ccc 8px,
#eee 8px,
#eee 16px
)`,
animation: "slide 25s linear infinite",
"background-size": "200% 100%",
}}
></div>
);
function sleep(ms: number) { function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
} }
interface InstallMachineProps { interface InstallMachineProps {
name?: string; name?: string;
targetHost?: string | null; targetHost?: string | null;
machine: MachineData;
} }
const InstallMachine = (props: InstallMachineProps) => { const InstallMachine = (props: InstallMachineProps) => {
const curr = activeURI(); const curr = activeURI();
@@ -64,9 +82,9 @@ const InstallMachine = (props: InstallMachineProps) => {
const [formStore, { Form, Field }] = createForm<AllStepsValues>(); const [formStore, { Form, Field }] = createForm<AllStepsValues>();
const [isDone, setIsDone] = createSignal<boolean>(false);
const [isInstalling, setIsInstalling] = createSignal<boolean>(false); const [isInstalling, setIsInstalling] = createSignal<boolean>(false);
const [progressText, setProgressText] = createSignal<string>(); const [progressText, setProgressText] = createSignal<string>();
const [installError, setInstallError] = createSignal<string>();
const handleInstall = async (values: AllStepsValues) => { const handleInstall = async (values: AllStepsValues) => {
console.log("Installing", values); console.log("Installing", values);
@@ -86,42 +104,36 @@ const InstallMachine = (props: InstallMachineProps) => {
"Installing machine. Grab coffee (15min)...", "Installing machine. Grab coffee (15min)...",
); );
setIsInstalling(true); setIsInstalling(true);
setProgressText("Setting up disk ... (1/5)");
const disk_response = await callApi("set_machine_disk_schema", { // props.machine.disk_
base_path: curr_uri, const shouldRunDisk =
machine_name: props.name, JSON.stringify(props.machine.disk_schema?.placeholders) !==
placeholders: diskValues.placeholders, JSON.stringify(diskValues.placeholders);
schema_name: diskValues.schema,
force: true,
});
if (disk_response.status === "error") { if (shouldRunDisk) {
toast.error( setProgressText("Setting up disk ... (1/5)");
`Failed to set disk schema: ${disk_response.errors[0].message}`, const disk_response = await callApi("set_machine_disk_schema", {
); base_path: curr_uri,
setProgressText( machine_name: props.name,
"Failed to set disk schema. \n" + disk_response.errors[0].message, placeholders: diskValues.placeholders,
); schema_name: diskValues.schema,
return; force: true,
});
if (disk_response.status === "error") {
toast.error(
`Failed to set disk schema: ${disk_response.errors[0].message}`,
);
setProgressText(
"Failed to set disk schema. \n" + disk_response.errors[0].message,
);
return;
}
} }
// Next step setProgressText("Installing machine ... (2/5)");
if (disk_response.status === "success") {
setProgressText("Evaluate configuration ... (2/5)");
}
// Next step
await sleep(2000);
setProgressText("Building machine ... (3/5)");
await sleep(2000);
setProgressText("Formatting remote disk ... (4/5)");
await sleep(2000);
setProgressText("Copying system ... (5/5)");
await sleep(2000);
setProgressText("Rebooting remote system ... ");
await sleep(2000);
const r = await callApi("install_machine", { const installPromise = callApi("install_machine", {
opts: { opts: {
machine: { machine: {
name: props.name, name: props.name,
@@ -133,13 +145,36 @@ const InstallMachine = (props: InstallMachineProps) => {
password: "", password: "",
}, },
}); });
// Next step
await sleep(10 * 1000);
setProgressText("Building machine ... (3/5)");
await sleep(10 * 1000);
setProgressText("Formatting remote disk ... (4/5)");
await sleep(10 * 1000);
setProgressText("Copying system ... (5/5)");
await sleep(20 * 1000);
setProgressText("Rebooting remote system ... ");
await sleep(10 * 1000);
const installResponse = await installPromise;
toast.dismiss(loading_toast); toast.dismiss(loading_toast);
if (r.status === "error") { if (installResponse.status === "error") {
toast.error("Failed to install machine"); toast.error("Failed to install machine");
setIsDone(true);
setProgressText(
"Failed to install machine. \n" + installResponse.errors[0].message,
);
} }
if (r.status === "success") {
if (installResponse.status === "success") {
toast.success("Machine installed successfully"); toast.success("Machine installed successfully");
setIsDone(true);
setProgressText(
"Machine installed successfully. Please unplug the usb stick and reboot the system.",
);
} }
}; };
@@ -271,7 +306,12 @@ const InstallMachine = (props: InstallMachineProps) => {
setValue(formStore, "2", { ...prev, ...data }); setValue(formStore, "2", { ...prev, ...data });
handleNext(); handleNext();
}} }}
initial={getValue(formStore, "2")} // @ts-expect-error: The placeholder type is to wide
initial={{
...props.machine.disk_schema,
...getValue(formStore, "2"),
initialized: !!props.machine.disk_schema,
}}
/> />
</Match> </Match>
<Match when={step() === "3"}> <Match when={step() === "3"}>
@@ -326,20 +366,7 @@ const InstallMachine = (props: InstallMachineProps) => {
</div> </div>
<div class="flex h-full flex-col items-center gap-3 px-4 py-8 bg-inv-4 fg-inv-1"> <div class="flex h-full flex-col items-center gap-3 px-4 py-8 bg-inv-4 fg-inv-1">
<Icon icon="ClanIcon" viewBox="0 0 72 89" class="size-20" /> <Icon icon="ClanIcon" viewBox="0 0 72 89" class="size-20" />
<div {isDone() && <LoadingBar />}
class="h-3 w-80 overflow-hidden rounded-[3px] border-2 border-def-1"
style={{
background: `repeating-linear-gradient(
45deg,
#ccc,
#ccc 8px,
#eee 8px,
#eee 16px
)`,
animation: "slide 25s linear infinite",
"background-size": "200% 100%",
}}
></div>
<Typography <Typography
hierarchy="label" hierarchy="label"
size="default" size="default"
@@ -500,7 +527,7 @@ const MachineForm = (props: MachineDetailsProps) => {
/> />
)} )}
</Field> </Field>
<Field name="disk_schema"> <Field name="disk_schema.schema_name">
{(field, props) => ( {(field, props) => (
<> <>
<FieldLayout <FieldLayout
@@ -576,6 +603,7 @@ const MachineForm = (props: MachineDetailsProps) => {
<InstallMachine <InstallMachine
name={machineName()} name={machineName()}
targetHost={getValue(formStore, "machine.deploy.targetHost")} targetHost={getValue(formStore, "machine.deploy.targetHost")}
machine={props.initialData}
/> />
</Modal> </Modal>

View File

@@ -17,6 +17,7 @@ export interface DiskValues extends FieldValues {
mainDisk: string; mainDisk: string;
}; };
schema: string; schema: string;
initialized: boolean;
} }
export const DiskStep = (props: StepProps<DiskValues>) => { export const DiskStep = (props: StepProps<DiskValues>) => {
const [formStore, { Form, Field }] = createForm<DiskValues>({ const [formStore, { Form, Field }] = createForm<DiskValues>({
@@ -74,9 +75,14 @@ export const DiskStep = (props: StepProps<DiskValues>) => {
</Field> </Field>
</span> </span>
<Group> <Group>
{props.initial?.initialized && "Disk has been initialized already"}
<Field <Field
name="placeholders.mainDisk" name="placeholders.mainDisk"
validate={required("Dsik must be provided")} validate={
!props.initial?.initialized
? required("Disk must be provided")
: undefined
}
> >
{(field, fieldProps) => ( {(field, fieldProps) => (
<SelectInput <SelectInput
@@ -88,18 +94,12 @@ export const DiskStep = (props: StepProps<DiskValues>) => {
{ label: "No options", value: "" }, { label: "No options", value: "" },
] ]
} }
// options={
// deviceQuery.data?.blockdevices.map((d) => ({
// value: d.path,
// label: `${d.path} -- ${d.size} bytes`,
// })) || []
// }
error={field.error} error={field.error}
label="Main Disk" label="Main Disk"
value={field.value || ""} value={field.value || ""}
placeholder="Select a disk" placeholder="Select a disk"
selectProps={fieldProps} selectProps={fieldProps}
required required={!props.initial?.initialized}
/> />
)} )}
</Field> </Field>