Merge pull request 'UI/install: add configure disk' (#4653) from feat-ui into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4653
This commit is contained in:
@@ -22,6 +22,15 @@
|
||||
pkgs.xkcdpass
|
||||
];
|
||||
|
||||
prompts.password.display = {
|
||||
group = "Root User";
|
||||
label = "Password";
|
||||
required = false;
|
||||
helperText = ''
|
||||
Your password will be encrypted and stored securely using the secret store you've configured.
|
||||
'';
|
||||
};
|
||||
|
||||
prompts.password.type = "hidden";
|
||||
prompts.password.persist = true;
|
||||
prompts.password.description = "Leave empty to generate automatically";
|
||||
|
||||
@@ -91,7 +91,10 @@ export const callApi = <K extends OperationNames>(
|
||||
|
||||
return {
|
||||
uuid: op_key,
|
||||
result: result.then(({ body }) => body),
|
||||
result: result.then(({ body }) => {
|
||||
console.debug(`API call ${method}`, body);
|
||||
return body;
|
||||
}),
|
||||
cancel: async () => {
|
||||
console.log("Cancelling api call: ", op_key);
|
||||
await callApi("delete_task", { task_id: op_key }).result;
|
||||
|
||||
@@ -222,3 +222,41 @@ export const useMachineHardwareSummary = (
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
export type MachineDiskSchema = SuccessData<"get_machine_disk_schemas">;
|
||||
export type MachineDiskSchemaQuery = UseQueryResult<MachineDiskSchema>;
|
||||
|
||||
export const useMachineDiskSchemas = (
|
||||
clanUri: string,
|
||||
machineName: string,
|
||||
): MachineDiskSchemaQuery => {
|
||||
const client = useApiClient();
|
||||
return useQuery<MachineDiskSchema>(() => ({
|
||||
queryKey: [
|
||||
"clans",
|
||||
encodeBase64(clanUri),
|
||||
"machines",
|
||||
machineName,
|
||||
"disk_schemas",
|
||||
],
|
||||
queryFn: async () => {
|
||||
const call = client.fetch("get_machine_disk_schemas", {
|
||||
machine: {
|
||||
flake: {
|
||||
identifier: clanUri,
|
||||
},
|
||||
name: machineName,
|
||||
},
|
||||
});
|
||||
const result = await call.result;
|
||||
|
||||
if (result.status === "error") {
|
||||
// todo should we create some specific error types?
|
||||
console.error("Error fetching clan details:", result.errors);
|
||||
throw new Error(result.errors[0].message);
|
||||
}
|
||||
|
||||
return result.data;
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -23,13 +23,13 @@ export const Machine = (props: RouteSectionProps) => {
|
||||
<Button
|
||||
hierarchy="primary"
|
||||
onClick={() => setShowModal(true)}
|
||||
class="absolute top-0 right-0 m-4"
|
||||
class="absolute right-0 top-0 m-4"
|
||||
>
|
||||
Install me!
|
||||
</Button>
|
||||
<Show when={showInstall()}>
|
||||
<div
|
||||
class="absolute top-0 left-0 w-full h-full flex justify-center items-center z-50 bg-white/90"
|
||||
class="absolute left-0 top-0 z-50 flex size-full items-center justify-center bg-white/90"
|
||||
ref={(el) => (container = el)}
|
||||
>
|
||||
<InstallModal
|
||||
|
||||
@@ -63,6 +63,9 @@ export interface InstallStoreType {
|
||||
install: {
|
||||
targetHost: string;
|
||||
machineName: string;
|
||||
mainDisk: string;
|
||||
// ...TODO Vars
|
||||
progress: ApiCall<"run_machine_install">;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -21,8 +21,7 @@ import {
|
||||
useSystemStorageOptions,
|
||||
} from "@/src/hooks/queries";
|
||||
import { useApiClient } from "@/src/hooks/ApiClient";
|
||||
import { createEffect, onMount } from "solid-js";
|
||||
import { create } from "storybook/internal/theming";
|
||||
import { onMount } from "solid-js";
|
||||
|
||||
const Prose = () => (
|
||||
<StepLayout
|
||||
|
||||
@@ -12,14 +12,17 @@ import { getStepStore, useStepper } from "@/src/hooks/stepper";
|
||||
import { InstallSteps, InstallStoreType } from "../install";
|
||||
import { TextInput } from "@/src/components/Form/TextInput";
|
||||
import { Alert } from "@/src/components/Alert/Alert";
|
||||
import { createSignal, Show } from "solid-js";
|
||||
import { onMount, Show } from "solid-js";
|
||||
import { Divider } from "@/src/components/Divider/Divider";
|
||||
import { Orienter } from "@/src/components/Form/Orienter";
|
||||
import { Button } from "@/src/components/Button/Button";
|
||||
import { Select } from "@/src/components/Select/Select";
|
||||
import { LoadingBar } from "@/src/components/LoadingBar/LoadingBar";
|
||||
import Icon from "@/src/components/Icon/Icon";
|
||||
import { useMachineHardwareSummary } from "@/src/hooks/queries";
|
||||
import {
|
||||
useMachineDiskSchemas,
|
||||
useMachineHardwareSummary,
|
||||
} from "@/src/hooks/queries";
|
||||
import { useClanURI } from "@/src/hooks/clan";
|
||||
import { useApiClient } from "@/src/hooks/ApiClient";
|
||||
|
||||
@@ -120,10 +123,8 @@ const CheckHardware = () => {
|
||||
const call = client.fetch("run_machine_hardware_info", {
|
||||
target_host: {
|
||||
address: store.install.targetHost,
|
||||
command_prefix: "D0 YOU SEE ME LEAKING?",
|
||||
},
|
||||
opts: {
|
||||
backend: "nixos-facter",
|
||||
machine: {
|
||||
flake: {
|
||||
identifier: clanUri,
|
||||
@@ -199,17 +200,28 @@ const DiskSchema = v.object({
|
||||
type DiskForm = v.InferInput<typeof DiskSchema>;
|
||||
|
||||
const ConfigureDisk = () => {
|
||||
const stepSignal = useStepper<InstallSteps>();
|
||||
const [store, set] = getStepStore<InstallStoreType>(stepSignal);
|
||||
|
||||
const [formStore, { Form, Field }] = createForm<DiskForm>({
|
||||
validate: valiForm(DiskSchema),
|
||||
initialValues: {
|
||||
mainDisk: store.install?.mainDisk,
|
||||
},
|
||||
});
|
||||
const stepSignal = useStepper<InstallSteps>();
|
||||
|
||||
const handleSubmit: SubmitHandler<DiskForm> = (values, event) => {
|
||||
console.log("submitted", values);
|
||||
// Here you would typically trigger the ISO creation process
|
||||
console.log("disk submitted", values);
|
||||
|
||||
set("install", (s) => ({ ...s, mainDisk: values.mainDisk }));
|
||||
stepSignal.next();
|
||||
};
|
||||
|
||||
const diskSchemasQuery = useMachineDiskSchemas(
|
||||
useClanURI(),
|
||||
store.install.machineName,
|
||||
);
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<StepLayout
|
||||
@@ -219,6 +231,7 @@ const ConfigureDisk = () => {
|
||||
<Field name="mainDisk">
|
||||
{(field, props) => (
|
||||
<Select
|
||||
zIndex={100}
|
||||
{...props}
|
||||
value={field.value}
|
||||
error={field.error}
|
||||
@@ -227,8 +240,21 @@ const ConfigureDisk = () => {
|
||||
label: "Main disk",
|
||||
description: "Select the disk to install the system on",
|
||||
}}
|
||||
// TODO: Get from api
|
||||
options={[{ value: "disk", label: "Disk0" }]}
|
||||
getOptions={async () => {
|
||||
if (!diskSchemasQuery.data) {
|
||||
await diskSchemasQuery.refetch();
|
||||
}
|
||||
const placeholders =
|
||||
diskSchemasQuery.data?.["single-disk"].placeholders;
|
||||
const mainDiskOptions = placeholders?.["mainDisk"];
|
||||
|
||||
return (
|
||||
mainDiskOptions?.options?.map((disk) => ({
|
||||
value: disk,
|
||||
label: disk,
|
||||
})) || []
|
||||
);
|
||||
}}
|
||||
placeholder="Select a disk"
|
||||
name={field.name}
|
||||
/>
|
||||
@@ -357,17 +383,59 @@ const Display = (props: { value: string; label: string }) => {
|
||||
|
||||
const InstallSummary = () => {
|
||||
const stepSignal = useStepper<InstallSteps>();
|
||||
const [store, set] = getStepStore<InstallStoreType>(stepSignal);
|
||||
|
||||
const handleInstall = () => {
|
||||
const client = useApiClient();
|
||||
|
||||
const clanUri = useClanURI();
|
||||
const handleInstall = async () => {
|
||||
// Here you would typically trigger the installation process
|
||||
console.log("Installation started");
|
||||
|
||||
const setDisk = client.fetch("set_machine_disk_schema", {
|
||||
machine: {
|
||||
flake: {
|
||||
identifier: clanUri,
|
||||
},
|
||||
name: store.install.machineName,
|
||||
},
|
||||
schema_name: "single-disk",
|
||||
placeholders: {
|
||||
mainDisk: store.install.mainDisk,
|
||||
},
|
||||
});
|
||||
|
||||
const diskResult = await setDisk.result; // Wait for the disk schema to be set
|
||||
if (diskResult.status === "error") {
|
||||
console.error("Error setting disk schema:", diskResult.errors);
|
||||
return;
|
||||
}
|
||||
|
||||
const runInstall = client.fetch("run_machine_install", {
|
||||
opts: {
|
||||
machine: {
|
||||
name: store.install.machineName,
|
||||
flake: {
|
||||
identifier: clanUri,
|
||||
},
|
||||
},
|
||||
},
|
||||
target_host: {
|
||||
address: store.install.targetHost,
|
||||
},
|
||||
});
|
||||
set("install", (s) => ({
|
||||
...s,
|
||||
progress: runInstall,
|
||||
}));
|
||||
|
||||
stepSignal.setActiveStep("install:progress");
|
||||
};
|
||||
return (
|
||||
<StepLayout
|
||||
body={
|
||||
<div class="flex flex-col gap-4">
|
||||
<Fieldset legend="Deploy to">
|
||||
<Fieldset legend="Address Configuration">
|
||||
<Orienter orientation="horizontal">
|
||||
{/* TOOD: Display the values emited from previous steps */}
|
||||
<Display label="Target" value="flash-installer.local" />
|
||||
@@ -379,10 +447,7 @@ const InstallSummary = () => {
|
||||
</Orienter>
|
||||
<Divider orientation="horizontal" />
|
||||
<Orienter orientation="horizontal">
|
||||
<Display
|
||||
label="Main Disk"
|
||||
value="nvme-WD_PC_SN740_SDDQNQD-512G"
|
||||
/>
|
||||
<Display label="Main Disk" value={store.install.mainDisk} />
|
||||
</Orienter>
|
||||
</Fieldset>
|
||||
</div>
|
||||
@@ -400,6 +465,20 @@ const InstallSummary = () => {
|
||||
};
|
||||
|
||||
const InstallProgress = () => {
|
||||
const stepSignal = useStepper<InstallSteps>();
|
||||
const [store, get] = getStepStore<InstallStoreType>(stepSignal);
|
||||
|
||||
onMount(async () => {
|
||||
if (store.install.progress) {
|
||||
const result = await store.install.progress.result;
|
||||
|
||||
if (result.status === "error") {
|
||||
console.error("Error during installation:", result.errors);
|
||||
}
|
||||
|
||||
stepSignal.setActiveStep("install:done");
|
||||
}
|
||||
});
|
||||
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">
|
||||
|
||||
Reference in New Issue
Block a user