Merge pull request 'Add vars step to UI' (#2710) from hsjobeki/clan-core:hsjobeki-main into main
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
|||||||
type JSX,
|
type JSX,
|
||||||
For,
|
For,
|
||||||
createMemo,
|
createMemo,
|
||||||
|
Accessor,
|
||||||
} from "solid-js";
|
} from "solid-js";
|
||||||
import { Portal } from "solid-js/web";
|
import { Portal } from "solid-js/web";
|
||||||
import { useFloating } from "../base";
|
import { useFloating } from "../base";
|
||||||
@@ -46,11 +47,12 @@ interface SelectInputpProps {
|
|||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
multiple?: boolean;
|
multiple?: boolean;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
dialogContextId?: string;
|
portalRef?: Accessor<HTMLElement | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SelectInput(props: SelectInputpProps) {
|
export function SelectInput(props: SelectInputpProps) {
|
||||||
const dialogContext = useContext(props.dialogContextId);
|
const dialogContext = (dialogContextId?: string) =>
|
||||||
|
useContext(dialogContextId);
|
||||||
|
|
||||||
const _id = createUniqueId();
|
const _id = createUniqueId();
|
||||||
|
|
||||||
@@ -228,7 +230,11 @@ export function SelectInput(props: SelectInputpProps) {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Portal mount={dialogContext.contentRef?.() || document.body}>
|
<Portal
|
||||||
|
mount={
|
||||||
|
props.portalRef ? props.portalRef() || document.body : document.body
|
||||||
|
}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
id={_id}
|
id={_id}
|
||||||
popover
|
popover
|
||||||
|
|||||||
@@ -28,11 +28,20 @@ export const Modal = (props: ModalProps) => {
|
|||||||
|
|
||||||
const handleMouseMove = (e: MouseEvent) => {
|
const handleMouseMove = (e: MouseEvent) => {
|
||||||
if (dragging()) {
|
if (dragging()) {
|
||||||
const newTop = e.clientY - startOffset().y;
|
let newTop = e.clientY - startOffset().y;
|
||||||
const newLeft = e.clientX - startOffset().x;
|
let newLeft = e.clientX - startOffset().x;
|
||||||
|
|
||||||
|
if (newTop < 0) {
|
||||||
|
newTop = 0;
|
||||||
|
}
|
||||||
|
if (newLeft < 0) {
|
||||||
|
newLeft = 0;
|
||||||
|
}
|
||||||
dialogRef.style.top = `${newTop}px`;
|
dialogRef.style.top = `${newTop}px`;
|
||||||
dialogRef.style.left = `${newLeft}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={{
|
classList={{
|
||||||
"!cursor-grabbing": dragging(),
|
"!cursor-grabbing": dragging(),
|
||||||
[cx("scale-105 transition-transform")]: dragging(),
|
[cx("scale-[101%] transition-transform")]: dragging(),
|
||||||
}}
|
}}
|
||||||
ref={(el) => {
|
ref={(el) => {
|
||||||
dialogRef = el;
|
dialogRef = el;
|
||||||
@@ -112,7 +121,10 @@ export const Modal = (props: ModalProps) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Dialog.Label>
|
</Dialog.Label>
|
||||||
<Dialog.Description class="flex flex-col bg-def-1" as="div">
|
<Dialog.Description
|
||||||
|
class="flex max-h-[90vh] flex-col overflow-y-hidden bg-def-1"
|
||||||
|
as="div"
|
||||||
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</Dialog.Description>
|
</Dialog.Description>
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ 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 cx from "classnames";
|
||||||
import { SectionHeader } from "@/src/components/group";
|
import { VarsStep, VarsValues } from "./install/vars-step";
|
||||||
|
|
||||||
type MachineFormInterface = MachineData & {
|
type MachineFormInterface = MachineData & {
|
||||||
sshKey?: File;
|
sshKey?: File;
|
||||||
@@ -37,7 +37,8 @@ type MachineData = SuccessData<"get_inventory_machine_details">;
|
|||||||
const steps: Record<StepIdx, string> = {
|
const steps: Record<StepIdx, string> = {
|
||||||
"1": "Hardware detection",
|
"1": "Hardware detection",
|
||||||
"2": "Disk schema",
|
"2": "Disk schema",
|
||||||
"3": "Installation",
|
"3": "Credentials & Data",
|
||||||
|
"4": "Installation",
|
||||||
};
|
};
|
||||||
|
|
||||||
type StepIdx = keyof AllStepsValues;
|
type StepIdx = keyof AllStepsValues;
|
||||||
@@ -45,7 +46,8 @@ type StepIdx = keyof AllStepsValues;
|
|||||||
export interface AllStepsValues extends FieldValues {
|
export interface AllStepsValues extends FieldValues {
|
||||||
"1": HardwareValues;
|
"1": HardwareValues;
|
||||||
"2": DiskValues;
|
"2": DiskValues;
|
||||||
"3": NonNullable<unknown>;
|
"3": VarsValues;
|
||||||
|
"4": NonNullable<unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LoadingBar = () => (
|
const LoadingBar = () => (
|
||||||
@@ -190,7 +192,7 @@ const InstallMachine = (props: InstallMachineProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Footer = () => (
|
const Footer = () => (
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between p-4">
|
||||||
<Button
|
<Button
|
||||||
startIcon={<Icon icon="ArrowLeft" />}
|
startIcon={<Icon icon="ArrowLeft" />}
|
||||||
variant="light"
|
variant="light"
|
||||||
@@ -214,7 +216,10 @@ const InstallMachine = (props: InstallMachineProps) => {
|
|||||||
return (
|
return (
|
||||||
<Switch
|
<Switch
|
||||||
fallback={
|
fallback={
|
||||||
<Form onSubmit={handleInstall}>
|
<Form
|
||||||
|
onSubmit={handleInstall}
|
||||||
|
class="relative top-0 flex h-full flex-col gap-0"
|
||||||
|
>
|
||||||
{/* Register each step as form field */}
|
{/* Register each step as form field */}
|
||||||
{/* @ts-expect-error: object type is not statically supported */}
|
{/* @ts-expect-error: object type is not statically supported */}
|
||||||
<Field name="1">{(field, fieldProps) => <></>}</Field>
|
<Field name="1">{(field, fieldProps) => <></>}</Field>
|
||||||
@@ -269,78 +274,90 @@ const InstallMachine = (props: InstallMachineProps) => {
|
|||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
|
<Switch fallback={"Undefined content. This Step seems to not exist."}>
|
||||||
<div class="flex flex-col gap-6 p-6">
|
<Match when={step() === "1"}>
|
||||||
<Switch
|
<HWStep
|
||||||
fallback={"Undefined content. This Step seems to not exist."}
|
// @ts-expect-error: This cannot be undefined in this context.
|
||||||
>
|
machine_id={props.name}
|
||||||
<Match when={step() === "1"}>
|
// @ts-expect-error: This cannot be undefined in this context.
|
||||||
<HWStep
|
dir={activeURI()}
|
||||||
// @ts-expect-error: This cannot be undefined in this context.
|
handleNext={(data) => {
|
||||||
machine_id={props.name}
|
const prev = getValue(formStore, "1");
|
||||||
// @ts-expect-error: This cannot be undefined in this context.
|
setValue(formStore, "1", { ...prev, ...data });
|
||||||
dir={activeURI()}
|
handleNext();
|
||||||
handleNext={(data) => {
|
}}
|
||||||
const prev = getValue(formStore, "1");
|
initial={
|
||||||
setValue(formStore, "1", { ...prev, ...data });
|
getValue(formStore, "1") || {
|
||||||
handleNext();
|
target: props.targetHost || "",
|
||||||
}}
|
report: false,
|
||||||
initial={
|
|
||||||
getValue(formStore, "1") || {
|
|
||||||
target: props.targetHost || "",
|
|
||||||
report: false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
footer={<Footer />}
|
}
|
||||||
/>
|
footer={<Footer />}
|
||||||
</Match>
|
/>
|
||||||
<Match when={step() === "2"}>
|
</Match>
|
||||||
<DiskStep
|
<Match when={step() === "2"}>
|
||||||
// @ts-expect-error: This cannot be undefined in this context.
|
<DiskStep
|
||||||
machine_id={props.name}
|
// @ts-expect-error: This cannot be undefined in this context.
|
||||||
// @ts-expect-error: This cannot be undefined in this context.
|
machine_id={props.name}
|
||||||
dir={activeURI()}
|
// @ts-expect-error: This cannot be undefined in this context.
|
||||||
footer={<Footer />}
|
dir={activeURI()}
|
||||||
handleNext={(data) => {
|
footer={<Footer />}
|
||||||
const prev = getValue(formStore, "2");
|
handleNext={(data) => {
|
||||||
setValue(formStore, "2", { ...prev, ...data });
|
const prev = getValue(formStore, "2");
|
||||||
handleNext();
|
setValue(formStore, "2", { ...prev, ...data });
|
||||||
}}
|
handleNext();
|
||||||
// @ts-expect-error: The placeholder type is to wide
|
}}
|
||||||
initial={{
|
// @ts-expect-error: The placeholder type is to wide
|
||||||
...props.machine.disk_schema,
|
initial={{
|
||||||
...getValue(formStore, "2"),
|
...props.machine.disk_schema,
|
||||||
initialized: !!props.machine.disk_schema,
|
...getValue(formStore, "2"),
|
||||||
}}
|
initialized: !!props.machine.disk_schema,
|
||||||
/>
|
}}
|
||||||
</Match>
|
/>
|
||||||
<Match when={step() === "3"}>
|
</Match>
|
||||||
<SummaryStep
|
<Match when={step() === "3"}>
|
||||||
// @ts-expect-error: This cannot be undefined in this context.
|
<VarsStep
|
||||||
machine_id={props.name}
|
// @ts-expect-error: This cannot be undefined in this context.
|
||||||
// @ts-expect-error: This cannot be undefined in this context.
|
machine_id={props.name}
|
||||||
dir={activeURI()}
|
// @ts-expect-error: This cannot be undefined in this context.
|
||||||
handleNext={() => handleNext()}
|
dir={activeURI()}
|
||||||
// @ts-expect-error: This cannot be known.
|
footer={<Footer />}
|
||||||
initial={getValues(formStore)}
|
handleNext={(data) => {
|
||||||
footer={
|
// const prev = getValue(formStore, "2");
|
||||||
<div class="flex justify-between">
|
// setValue(formStore, "2", { ...prev, ...data });
|
||||||
<Button
|
handleNext();
|
||||||
startIcon={<Icon icon="ArrowLeft" />}
|
}}
|
||||||
variant="light"
|
initial={{
|
||||||
type="button"
|
...getValue(formStore, "3"),
|
||||||
onClick={handlePrev}
|
}}
|
||||||
disabled={step() === "1"}
|
/>
|
||||||
>
|
</Match>
|
||||||
Previous
|
<Match when={step() === "4"}>
|
||||||
</Button>
|
<SummaryStep
|
||||||
<Button startIcon={<Icon icon="Flash" />}>Install</Button>
|
// @ts-expect-error: This cannot be undefined in this context.
|
||||||
</div>
|
machine_id={props.name}
|
||||||
}
|
// @ts-expect-error: This cannot be undefined in this context.
|
||||||
/>
|
dir={activeURI()}
|
||||||
</Match>
|
handleNext={() => handleNext()}
|
||||||
</Switch>
|
// @ts-expect-error: This cannot be known.
|
||||||
</div>
|
initial={getValues(formStore)}
|
||||||
|
footer={
|
||||||
|
<div class="flex justify-between p-4">
|
||||||
|
<Button
|
||||||
|
startIcon={<Icon icon="ArrowLeft" />}
|
||||||
|
variant="light"
|
||||||
|
type="button"
|
||||||
|
onClick={handlePrev}
|
||||||
|
disabled={step() === "1"}
|
||||||
|
>
|
||||||
|
Previous
|
||||||
|
</Button>
|
||||||
|
<Button startIcon={<Icon icon="Flash" />}>Install</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
</Form>
|
</Form>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { StepProps } from "./hardware-step";
|
|||||||
import { SelectInput } from "@/src/Form/fields/Select";
|
import { SelectInput } from "@/src/Form/fields/Select";
|
||||||
import { Typography } from "@/src/components/Typography";
|
import { Typography } from "@/src/components/Typography";
|
||||||
import { Group } from "@/src/components/group";
|
import { Group } from "@/src/components/group";
|
||||||
|
import { useContext } from "corvu/dialog";
|
||||||
|
|
||||||
export interface DiskValues extends FieldValues {
|
export interface DiskValues extends FieldValues {
|
||||||
placeholders: {
|
placeholders: {
|
||||||
@@ -44,67 +45,82 @@ export const DiskStep = (props: StepProps<DiskValues>) => {
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const modalContext = useContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<>
|
||||||
onSubmit={handleSubmit}
|
<Form
|
||||||
class="flex flex-col gap-6"
|
onSubmit={handleSubmit}
|
||||||
noValidate={false}
|
class="flex flex-col gap-6"
|
||||||
>
|
noValidate={false}
|
||||||
<span class="flex flex-col gap-4">
|
>
|
||||||
<Field name="schema" validate={required("Schema must be provided")}>
|
<div class="max-h-[calc(100vh-20rem)] overflow-y-scroll">
|
||||||
{(field, fieldProps) => (
|
<div class="flex h-full flex-col gap-6 p-4">
|
||||||
<>
|
<span class="flex flex-col gap-4">
|
||||||
<Typography
|
<Field
|
||||||
hierarchy="body"
|
name="schema"
|
||||||
size="default"
|
validate={required("Schema must be provided")}
|
||||||
weight="bold"
|
|
||||||
class="capitalize"
|
|
||||||
>
|
>
|
||||||
{(field.value || "No schema selected").split("-").join(" ")}
|
{(field, fieldProps) => (
|
||||||
</Typography>
|
<>
|
||||||
<Typography
|
<Typography
|
||||||
hierarchy="body"
|
hierarchy="body"
|
||||||
size="xs"
|
size="default"
|
||||||
weight="medium"
|
weight="bold"
|
||||||
class="underline"
|
class="capitalize"
|
||||||
|
>
|
||||||
|
{(field.value || "No schema selected")
|
||||||
|
.split("-")
|
||||||
|
.join(" ")}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
hierarchy="body"
|
||||||
|
size="xs"
|
||||||
|
weight="medium"
|
||||||
|
class="underline"
|
||||||
|
>
|
||||||
|
Change schema
|
||||||
|
</Typography>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</span>
|
||||||
|
<Group>
|
||||||
|
{props.initial?.initialized &&
|
||||||
|
"Disk has been initialized already"}
|
||||||
|
<Field
|
||||||
|
name="placeholders.mainDisk"
|
||||||
|
validate={
|
||||||
|
!props.initial?.initialized
|
||||||
|
? required("Disk must be provided")
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Change schema
|
{(field, fieldProps) => (
|
||||||
</Typography>
|
<SelectInput
|
||||||
</>
|
loading={diskSchemaQuery.isFetching}
|
||||||
)}
|
options={
|
||||||
</Field>
|
diskSchemaQuery.data?.["single-disk"].placeholders[
|
||||||
</span>
|
"mainDisk"
|
||||||
<Group>
|
].options?.map((o) => ({ label: o, value: o })) || [
|
||||||
{props.initial?.initialized && "Disk has been initialized already"}
|
{ label: "No options", value: "" },
|
||||||
<Field
|
]
|
||||||
name="placeholders.mainDisk"
|
}
|
||||||
validate={
|
error={field.error}
|
||||||
!props.initial?.initialized
|
label="Main Disk"
|
||||||
? required("Disk must be provided")
|
value={field.value || ""}
|
||||||
: undefined
|
placeholder="Select a disk"
|
||||||
}
|
selectProps={fieldProps}
|
||||||
>
|
required={!props.initial?.initialized}
|
||||||
{(field, fieldProps) => (
|
portalRef={modalContext.contentRef}
|
||||||
<SelectInput
|
/>
|
||||||
loading={diskSchemaQuery.isFetching}
|
)}
|
||||||
options={
|
</Field>
|
||||||
diskSchemaQuery.data?.["single-disk"].placeholders[
|
</Group>
|
||||||
"mainDisk"
|
</div>
|
||||||
].options?.map((o) => ({ label: o, value: o })) || [
|
</div>
|
||||||
{ label: "No options", value: "" },
|
{props.footer}
|
||||||
]
|
</Form>
|
||||||
}
|
</>
|
||||||
error={field.error}
|
|
||||||
label="Main Disk"
|
|
||||||
value={field.value || ""}
|
|
||||||
placeholder="Select a disk"
|
|
||||||
selectProps={fieldProps}
|
|
||||||
required={!props.initial?.initialized}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
</Group>
|
|
||||||
{props.footer}
|
|
||||||
</Form>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -105,101 +105,105 @@ export const HWStep = (props: StepProps<HardwareValues>) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Form onSubmit={handleSubmit} class="flex flex-col gap-6">
|
<Form onSubmit={handleSubmit} class="flex flex-col gap-6">
|
||||||
<Group>
|
<div class="max-h-[calc(100vh-20rem)] overflow-y-scroll">
|
||||||
<Field name="target" validate={required("Target must be provided")}>
|
<div class="flex h-full flex-col gap-6 p-4">
|
||||||
{(field, fieldProps) => (
|
<Group>
|
||||||
<TextInput
|
<Field name="target" validate={required("Target must be provided")}>
|
||||||
error={field.error}
|
{(field, fieldProps) => (
|
||||||
variant="ghost"
|
<TextInput
|
||||||
label="Target ip"
|
error={field.error}
|
||||||
value={field.value || ""}
|
variant="ghost"
|
||||||
inputProps={fieldProps}
|
label="Target ip"
|
||||||
required
|
value={field.value || ""}
|
||||||
/>
|
inputProps={fieldProps}
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
</Group>
|
|
||||||
<Group>
|
|
||||||
<Field
|
|
||||||
name="report"
|
|
||||||
type="boolean"
|
|
||||||
validate={required("Report must be generated")}
|
|
||||||
>
|
|
||||||
{(field, fieldProps) => (
|
|
||||||
<FieldLayout
|
|
||||||
error={field.error && <InputError error={field.error} />}
|
|
||||||
label={
|
|
||||||
<InputLabel
|
|
||||||
required
|
required
|
||||||
help="Detect hardware specific drivers from target ip"
|
/>
|
||||||
>
|
)}
|
||||||
Hardware report
|
</Field>
|
||||||
</InputLabel>
|
</Group>
|
||||||
}
|
<Group>
|
||||||
field={
|
<Field
|
||||||
<Switch>
|
name="report"
|
||||||
<Match when={hwReportQuery.isLoading}>
|
type="boolean"
|
||||||
<div>Loading...</div>
|
validate={required("Report must be generated")}
|
||||||
</Match>
|
>
|
||||||
<Match when={hwReportQuery.error}>
|
{(field, fieldProps) => (
|
||||||
<div>Error...</div>
|
<FieldLayout
|
||||||
</Match>
|
error={field.error && <InputError error={field.error} />}
|
||||||
<Match when={hwReportQuery.data}>
|
label={
|
||||||
{(data) => (
|
<InputLabel
|
||||||
<>
|
required
|
||||||
<Switch>
|
help="Detect hardware specific drivers from target ip"
|
||||||
<Match when={data() === "none"}>
|
>
|
||||||
<Badge color="red" icon="Attention">
|
Hardware report
|
||||||
No report
|
</InputLabel>
|
||||||
</Badge>
|
}
|
||||||
<Button
|
field={
|
||||||
variant="ghost"
|
<Switch>
|
||||||
disabled={isGenerating()}
|
<Match when={hwReportQuery.isLoading}>
|
||||||
startIcon={<Icon icon="Report" />}
|
<div>Loading...</div>
|
||||||
class="w-full"
|
</Match>
|
||||||
onClick={generateReport}
|
<Match when={hwReportQuery.error}>
|
||||||
>
|
<div>Error...</div>
|
||||||
Run hardware detection
|
</Match>
|
||||||
</Button>
|
<Match when={hwReportQuery.data}>
|
||||||
</Match>
|
{(data) => (
|
||||||
<Match when={data() === "nixos-facter"}>
|
<>
|
||||||
<Badge color="primary" icon="Checkmark">
|
<Switch>
|
||||||
Report detected
|
<Match when={data() === "none"}>
|
||||||
</Badge>
|
<Badge color="red" icon="Attention">
|
||||||
<Button
|
No report
|
||||||
variant="ghost"
|
</Badge>
|
||||||
disabled={isGenerating()}
|
<Button
|
||||||
startIcon={<Icon icon="Report" />}
|
variant="ghost"
|
||||||
class="w-full"
|
disabled={isGenerating()}
|
||||||
onClick={generateReport}
|
startIcon={<Icon icon="Report" />}
|
||||||
>
|
class="w-full"
|
||||||
Re-run hardware detection
|
onClick={generateReport}
|
||||||
</Button>
|
>
|
||||||
</Match>
|
Run hardware detection
|
||||||
<Match when={data() === "nixos-generate-config"}>
|
</Button>
|
||||||
<Badge color="primary" icon="Checkmark">
|
</Match>
|
||||||
Legacy Report detected
|
<Match when={data() === "nixos-facter"}>
|
||||||
</Badge>
|
<Badge color="primary" icon="Checkmark">
|
||||||
<Button
|
Report detected
|
||||||
variant="ghost"
|
</Badge>
|
||||||
disabled={isGenerating()}
|
<Button
|
||||||
startIcon={<Icon icon="Report" />}
|
variant="ghost"
|
||||||
class="w-full"
|
disabled={isGenerating()}
|
||||||
onClick={generateReport}
|
startIcon={<Icon icon="Report" />}
|
||||||
>
|
class="w-full"
|
||||||
Replace hardware detection
|
onClick={generateReport}
|
||||||
</Button>
|
>
|
||||||
</Match>
|
Re-run hardware detection
|
||||||
</Switch>
|
</Button>
|
||||||
</>
|
</Match>
|
||||||
)}
|
<Match when={data() === "nixos-generate-config"}>
|
||||||
</Match>
|
<Badge color="primary" icon="Checkmark">
|
||||||
</Switch>
|
Legacy Report detected
|
||||||
}
|
</Badge>
|
||||||
/>
|
<Button
|
||||||
)}
|
variant="ghost"
|
||||||
</Field>
|
disabled={isGenerating()}
|
||||||
</Group>
|
startIcon={<Icon icon="Report" />}
|
||||||
|
class="w-full"
|
||||||
|
onClick={generateReport}
|
||||||
|
>
|
||||||
|
Replace hardware detection
|
||||||
|
</Button>
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</Group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{props.footer}
|
{props.footer}
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,87 +12,96 @@ export const SummaryStep = (props: StepProps<AllStepsValues>) => {
|
|||||||
const diskValues = () => props.initial?.["2"];
|
const diskValues = () => props.initial?.["2"];
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Section>
|
<div class="max-h-[calc(100vh-20rem)] overflow-y-scroll">
|
||||||
<Typography
|
<div class="flex h-full flex-col gap-6 p-4">
|
||||||
hierarchy="label"
|
<Section>
|
||||||
size="xs"
|
|
||||||
weight="medium"
|
|
||||||
class="uppercase"
|
|
||||||
>
|
|
||||||
Hardware Report
|
|
||||||
</Typography>
|
|
||||||
<Group>
|
|
||||||
<FieldLayout
|
|
||||||
label={<InputLabel>Detected</InputLabel>}
|
|
||||||
field={
|
|
||||||
hwValues()?.report ? (
|
|
||||||
<Badge color="green" class="w-fit">
|
|
||||||
<Icon icon="Checkmark" color="inherit" />
|
|
||||||
</Badge>
|
|
||||||
) : (
|
|
||||||
<Badge color="red" class="w-fit">
|
|
||||||
<Icon icon="Warning" color="inherit" />
|
|
||||||
</Badge>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
></FieldLayout>
|
|
||||||
<FieldLayout
|
|
||||||
label={<InputLabel>Target</InputLabel>}
|
|
||||||
field={
|
|
||||||
<Typography hierarchy="body" size="xs" weight="bold">
|
|
||||||
{hwValues()?.target}
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
></FieldLayout>
|
|
||||||
</Group>
|
|
||||||
</Section>
|
|
||||||
<Section>
|
|
||||||
<Typography
|
|
||||||
hierarchy="label"
|
|
||||||
size="xs"
|
|
||||||
weight="medium"
|
|
||||||
class="uppercase"
|
|
||||||
>
|
|
||||||
Disk Configuration
|
|
||||||
</Typography>
|
|
||||||
<Group>
|
|
||||||
<FieldLayout
|
|
||||||
label={<InputLabel>Disk Layout</InputLabel>}
|
|
||||||
field={
|
|
||||||
<Typography hierarchy="body" size="xs" weight="bold">
|
|
||||||
{diskValues()?.schema}
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
></FieldLayout>
|
|
||||||
<hr class="h-px w-full border-none bg-acc-3"></hr>
|
|
||||||
<FieldLayout
|
|
||||||
label={<InputLabel>Main Disk</InputLabel>}
|
|
||||||
field={
|
|
||||||
<Typography hierarchy="body" size="xs" weight="bold">
|
|
||||||
{diskValues()?.placeholders.mainDisk}
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
></FieldLayout>
|
|
||||||
</Group>
|
|
||||||
</Section>
|
|
||||||
<SectionHeader
|
|
||||||
variant="danger"
|
|
||||||
headline={
|
|
||||||
<span>
|
|
||||||
<Typography hierarchy="body" size="s" weight="bold" color="inherit">
|
|
||||||
Setup your device.
|
|
||||||
</Typography>
|
|
||||||
<Typography
|
<Typography
|
||||||
hierarchy="body"
|
hierarchy="label"
|
||||||
size="s"
|
size="xs"
|
||||||
weight="medium"
|
weight="medium"
|
||||||
color="inherit"
|
class="uppercase"
|
||||||
>
|
>
|
||||||
This will erase the disk and bootstrap fresh.
|
Hardware Report
|
||||||
</Typography>
|
</Typography>
|
||||||
</span>
|
<Group>
|
||||||
}
|
<FieldLayout
|
||||||
/>
|
label={<InputLabel>Detected</InputLabel>}
|
||||||
|
field={
|
||||||
|
hwValues()?.report ? (
|
||||||
|
<Badge color="green" class="w-fit">
|
||||||
|
<Icon icon="Checkmark" color="inherit" />
|
||||||
|
</Badge>
|
||||||
|
) : (
|
||||||
|
<Badge color="red" class="w-fit">
|
||||||
|
<Icon icon="Warning" color="inherit" />
|
||||||
|
</Badge>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
></FieldLayout>
|
||||||
|
<FieldLayout
|
||||||
|
label={<InputLabel>Target</InputLabel>}
|
||||||
|
field={
|
||||||
|
<Typography hierarchy="body" size="xs" weight="bold">
|
||||||
|
{hwValues()?.target}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
></FieldLayout>
|
||||||
|
</Group>
|
||||||
|
</Section>
|
||||||
|
<Section>
|
||||||
|
<Typography
|
||||||
|
hierarchy="label"
|
||||||
|
size="xs"
|
||||||
|
weight="medium"
|
||||||
|
class="uppercase"
|
||||||
|
>
|
||||||
|
Disk Configuration
|
||||||
|
</Typography>
|
||||||
|
<Group>
|
||||||
|
<FieldLayout
|
||||||
|
label={<InputLabel>Disk Layout</InputLabel>}
|
||||||
|
field={
|
||||||
|
<Typography hierarchy="body" size="xs" weight="bold">
|
||||||
|
{diskValues()?.schema}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
></FieldLayout>
|
||||||
|
<hr class="h-px w-full border-none bg-acc-3"></hr>
|
||||||
|
<FieldLayout
|
||||||
|
label={<InputLabel>Main Disk</InputLabel>}
|
||||||
|
field={
|
||||||
|
<Typography hierarchy="body" size="xs" weight="bold">
|
||||||
|
{diskValues()?.placeholders.mainDisk}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
></FieldLayout>
|
||||||
|
</Group>
|
||||||
|
</Section>
|
||||||
|
<SectionHeader
|
||||||
|
variant="danger"
|
||||||
|
headline={
|
||||||
|
<span>
|
||||||
|
<Typography
|
||||||
|
hierarchy="body"
|
||||||
|
size="s"
|
||||||
|
weight="bold"
|
||||||
|
color="inherit"
|
||||||
|
>
|
||||||
|
Setup your device.
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
hierarchy="body"
|
||||||
|
size="s"
|
||||||
|
weight="medium"
|
||||||
|
color="inherit"
|
||||||
|
>
|
||||||
|
This will erase the disk and bootstrap fresh.
|
||||||
|
</Typography>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{props.footer}
|
{props.footer}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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<string, string>;
|
||||||
|
|
||||||
|
export const VarsStep = (props: StepProps<VarsValues>) => {
|
||||||
|
const [formStore, { Form, Field }] = createForm<VarsValues>({
|
||||||
|
initialValues: { ...props.initial, schema: "single-disk" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSubmit: SubmitHandler<VarsValues> = 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 (
|
||||||
|
<Form
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
class="flex h-full flex-col gap-6"
|
||||||
|
noValidate={false}
|
||||||
|
>
|
||||||
|
<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) => (
|
||||||
|
<Group>
|
||||||
|
<Typography hierarchy="label" size="default">
|
||||||
|
{generator.name}
|
||||||
|
</Typography>
|
||||||
|
<div>
|
||||||
|
Bound to module (shared):{" "}
|
||||||
|
{generator.share ? "True" : "False"}
|
||||||
|
</div>
|
||||||
|
<For each={generator.prompts}>
|
||||||
|
{(f) => (
|
||||||
|
<Group>
|
||||||
|
<Typography hierarchy="label" size="s">
|
||||||
|
{!f.previous_value ? "Required" : "Optional"}
|
||||||
|
</Typography>
|
||||||
|
<Typography hierarchy="label" size="s">
|
||||||
|
{f.name}
|
||||||
|
</Typography>
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
)}
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Show when={generatorsQuery.isFetched}>{props.footer}</Show>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user