flash install: fixes form layout
This commit is contained in:
committed by
Johannes Kirschbauer
parent
698a39fafb
commit
7d39d49b30
@@ -142,91 +142,87 @@ export function SelectInput(props: SelectInputpProps) {
|
||||
</InputLabel>
|
||||
}
|
||||
field={
|
||||
<>
|
||||
<InputBase
|
||||
error={!!props.error}
|
||||
disabled={props.disabled}
|
||||
required={props.required}
|
||||
class="!justify-start"
|
||||
divRef={setReference}
|
||||
inputElem={
|
||||
<button
|
||||
// TODO: Keyboard acessibililty
|
||||
// Currently the popover only opens with onClick
|
||||
// Options are not selectable with keyboard
|
||||
tabIndex={-1}
|
||||
disabled={props.disabled}
|
||||
onClick={() => {
|
||||
const popover = document.getElementById(_id);
|
||||
if (popover) {
|
||||
popover.togglePopover(); // Show or hide the popover
|
||||
}
|
||||
}}
|
||||
type="button"
|
||||
class="flex w-full items-center gap-2"
|
||||
formnovalidate
|
||||
// TODO: Use native popover once Webkit supports it within <form>
|
||||
// popovertarget={_id}
|
||||
// popovertargetaction="toggle"
|
||||
<InputBase
|
||||
error={!!props.error}
|
||||
disabled={props.disabled}
|
||||
required={props.required}
|
||||
class="!justify-start"
|
||||
divRef={setReference}
|
||||
inputElem={
|
||||
<button
|
||||
// TODO: Keyboard acessibililty
|
||||
// Currently the popover only opens with onClick
|
||||
// Options are not selectable with keyboard
|
||||
tabIndex={-1}
|
||||
disabled={props.disabled}
|
||||
onClick={() => {
|
||||
const popover = document.getElementById(_id);
|
||||
if (popover) {
|
||||
popover.togglePopover(); // Show or hide the popover
|
||||
}
|
||||
}}
|
||||
type="button"
|
||||
class="flex w-full items-center gap-2"
|
||||
formnovalidate
|
||||
// TODO: Use native popover once Webkit supports it within <form>
|
||||
// popovertarget={_id}
|
||||
// popovertargetaction="toggle"
|
||||
>
|
||||
<Show
|
||||
when={props.adornment && props.adornment.position === "start"}
|
||||
>
|
||||
{props.adornment?.content}
|
||||
</Show>
|
||||
{props.inlineLabel}
|
||||
<div class="flex cursor-default flex-row gap-2">
|
||||
<Show
|
||||
when={
|
||||
props.adornment && props.adornment.position === "start"
|
||||
getValues() &&
|
||||
getValues.length !== 1 &&
|
||||
getValues()[0] !== ""
|
||||
}
|
||||
fallback={props.placeholder}
|
||||
>
|
||||
{props.adornment?.content}
|
||||
<For each={getValues()} fallback={"Select"}>
|
||||
{(item) => (
|
||||
<div class="rounded-xl bg-slate-800 px-4 py-1 text-sm text-white">
|
||||
{item}
|
||||
<Show when={props.multiple}>
|
||||
<button
|
||||
class=""
|
||||
type="button"
|
||||
onClick={(_e) => {
|
||||
// @ts-expect-error: fieldName is not known ahead of time
|
||||
props.selectProps.onInput({
|
||||
currentTarget: {
|
||||
options: getValues()
|
||||
.filter((o) => o !== item)
|
||||
.map((value) => ({
|
||||
value,
|
||||
selected: true,
|
||||
disabled: false,
|
||||
})),
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
X
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
{props.inlineLabel}
|
||||
<div class="flex cursor-default flex-row gap-2">
|
||||
<Show
|
||||
when={
|
||||
getValues() &&
|
||||
getValues.length !== 1 &&
|
||||
getValues()[0] !== ""
|
||||
}
|
||||
fallback={props.placeholder}
|
||||
>
|
||||
<For each={getValues()} fallback={"Select"}>
|
||||
{(item) => (
|
||||
<div class="rounded-xl bg-slate-800 px-4 py-1 text-sm text-white">
|
||||
{item}
|
||||
<Show when={props.multiple}>
|
||||
<button
|
||||
class=""
|
||||
type="button"
|
||||
onClick={(_e) => {
|
||||
// @ts-expect-error: fieldName is not known ahead of time
|
||||
props.selectProps.onInput({
|
||||
currentTarget: {
|
||||
options: getValues()
|
||||
.filter((o) => o !== item)
|
||||
.map((value) => ({
|
||||
value,
|
||||
selected: true,
|
||||
disabled: false,
|
||||
})),
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
X
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</div>
|
||||
<Show
|
||||
when={props.adornment && props.adornment.position === "end"}
|
||||
>
|
||||
{props.adornment?.content}
|
||||
</Show>
|
||||
<Icon icon="CaretDown" class="ml-auto mr-2"></Icon>
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
<Show
|
||||
when={props.adornment && props.adornment.position === "end"}
|
||||
>
|
||||
{props.adornment?.content}
|
||||
</Show>
|
||||
<Icon size={12} icon="CaretDown" class="ml-auto mr-2" />
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ export const FieldLayout = (props: LayoutProps) => {
|
||||
class={cx("grid grid-cols-10 items-center", intern.class)}
|
||||
{...divProps}
|
||||
>
|
||||
<label class="col-span-5">{props.label}</label>
|
||||
<div class="col-span-5 flex items-center">{props.label}</div>
|
||||
<div class="col-span-5">{props.field}</div>
|
||||
{props.error && <span class="col-span-full">{props.error}</span>}
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,7 @@ export default function Accordion(props: AccordionProps) {
|
||||
fallback={
|
||||
<Button
|
||||
endIcon={<Icon size={12} icon={"CaretDown"} />}
|
||||
variant="light"
|
||||
variant="ghost"
|
||||
size="s"
|
||||
>
|
||||
{props.title}
|
||||
@@ -30,7 +30,7 @@ export default function Accordion(props: AccordionProps) {
|
||||
>
|
||||
<Button
|
||||
endIcon={<Icon size={12} icon={"CaretUp"} />}
|
||||
variant="dark"
|
||||
variant="ghost"
|
||||
size="s"
|
||||
>
|
||||
{props.title}
|
||||
|
||||
@@ -22,6 +22,8 @@ import toast from "solid-toast";
|
||||
import { FieldLayout } from "@/src/Form/fields/layout";
|
||||
import { InputLabel } from "@/src/components/inputBase";
|
||||
import { Modal } from "@/src/components/modal";
|
||||
import Fieldset from "@/src/Form/fieldset";
|
||||
import Accordion from "@/src/components/accordion";
|
||||
|
||||
interface Wifi extends FieldValues {
|
||||
ssid: string;
|
||||
@@ -233,16 +235,23 @@ export const Flash = () => {
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<div class="p-4">
|
||||
<Typography tag="p" hierarchy="body" size="default" color="primary">
|
||||
<div class="w-full self-stretch p-8">
|
||||
{/* <Typography tag="p" hierarchy="body" size="default" color="primary">
|
||||
USB Utility image.
|
||||
</Typography>
|
||||
<Typography tag="p" hierarchy="body" size="default" color="secondary">
|
||||
Will make bootstrapping new machines easier by providing secure remote
|
||||
connection to any machine when plugged in.
|
||||
</Typography>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<div class="my-4">
|
||||
</Typography> */}
|
||||
<Form
|
||||
onSubmit={handleSubmit}
|
||||
class="mx-auto flex w-full max-w-2xl flex-col gap-y-6"
|
||||
>
|
||||
<Fieldset legend="Authorized SSH Keys">
|
||||
<Typography hierarchy="body" size="s" weight="medium">
|
||||
Provide your SSH public key. For secure and passwordless SSH
|
||||
connections.
|
||||
</Typography>
|
||||
<Field name="sshKeys" type="File[]">
|
||||
{(field, props) => (
|
||||
<>
|
||||
@@ -267,146 +276,72 @@ export const Flash = () => {
|
||||
}}
|
||||
value={field.value}
|
||||
error={field.error}
|
||||
helperText="Provide your SSH public key. For secure and passwordless SSH connections."
|
||||
label="Authorized SSH Keys"
|
||||
//helperText="Provide your SSH public key. For secure and passwordless SSH connections."
|
||||
//label="Authorized SSH Keys"
|
||||
multiple
|
||||
required
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<Field name="disk" validate={[required("This field is required")]}>
|
||||
{(field, props) => (
|
||||
<SelectInput
|
||||
loading={deviceQuery.isFetching}
|
||||
selectProps={props}
|
||||
label="Flash Disk"
|
||||
labelProps={{
|
||||
labelAction: (
|
||||
<Button
|
||||
disabled={isFlashing()}
|
||||
class="ml-auto"
|
||||
variant="ghost"
|
||||
size="s"
|
||||
type="button"
|
||||
startIcon={<Icon icon="Update" />}
|
||||
onClick={() => deviceQuery.refetch()}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
value={field.value || ""}
|
||||
error={field.error}
|
||||
required
|
||||
placeholder="Select a drive where the clan-installer will be flashed to"
|
||||
options={
|
||||
deviceQuery.data?.blockdevices.map((d) => ({
|
||||
value: d.path,
|
||||
label: `${d.path} -- ${d.size} bytes`,
|
||||
})) || []
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
{/* WiFi Networks */}
|
||||
<div class="my-4 py-2">
|
||||
<FieldLayout
|
||||
label={<InputLabel class="mb-4">Networks</InputLabel>}
|
||||
field={
|
||||
</Fieldset>
|
||||
<Fieldset legend="General">
|
||||
<Field name="disk" validate={[required("This field is required")]}>
|
||||
{(field, props) => (
|
||||
<>
|
||||
<SelectInput
|
||||
loading={deviceQuery.isFetching}
|
||||
selectProps={props}
|
||||
label="Flash Disk"
|
||||
labelProps={{
|
||||
labelAction: (
|
||||
<Button
|
||||
disabled={isFlashing()}
|
||||
class="ml-auto"
|
||||
variant="ghost"
|
||||
size="s"
|
||||
type="button"
|
||||
startIcon={<Icon icon="Update" />}
|
||||
onClick={() => deviceQuery.refetch()}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
value={field.value || ""}
|
||||
error={field.error}
|
||||
required
|
||||
placeholder="Select a drive"
|
||||
options={
|
||||
deviceQuery.data?.blockdevices.map((d) => ({
|
||||
value: d.path,
|
||||
label: `${d.path} -- ${d.size} bytes`,
|
||||
})) || []
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</Fieldset>
|
||||
|
||||
<Fieldset legend="Network Settings">
|
||||
<FieldLayout
|
||||
label={<InputLabel>Networks</InputLabel>}
|
||||
field={
|
||||
<div class="w-full flex justify-end">
|
||||
<Button
|
||||
type="button"
|
||||
size="s"
|
||||
variant="light"
|
||||
onClick={addWifiNetwork}
|
||||
startIcon={<Icon icon="Plus" />}
|
||||
startIcon={<Icon size={12} icon="Plus" />}
|
||||
>
|
||||
WiFi Network
|
||||
</Button>
|
||||
</>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<For each={wifiNetworks()}>
|
||||
{(network, index) => (
|
||||
<div class="flex w-full gap-2">
|
||||
<div class="mb-2 grid w-full grid-cols-6 gap-2 align-middle">
|
||||
<Field
|
||||
name={`wifi.${index()}.ssid`}
|
||||
validate={[required("SSID is required")]}
|
||||
>
|
||||
{(field, props) => (
|
||||
<TextInput
|
||||
inputProps={props}
|
||||
label="SSID"
|
||||
value={field.value ?? ""}
|
||||
error={field.error}
|
||||
class="col-span-full "
|
||||
required
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
<Field
|
||||
name={`wifi.${index()}.password`}
|
||||
validate={[required("Password is required")]}
|
||||
>
|
||||
{(field, props) => (
|
||||
<TextInput
|
||||
class="col-span-full"
|
||||
inputProps={{
|
||||
...props,
|
||||
type: passwordVisibility()[index()]
|
||||
? "text"
|
||||
: "password",
|
||||
}}
|
||||
label="Password"
|
||||
value={field.value ?? ""}
|
||||
error={field.error}
|
||||
// adornment={{
|
||||
// position: "end",
|
||||
// content: (
|
||||
// <Button
|
||||
// variant="light"
|
||||
// type="button"
|
||||
// class="flex justify-center opacity-70"
|
||||
// onClick={() =>
|
||||
// togglePasswordVisibility(index())
|
||||
// }
|
||||
// startIcon={
|
||||
// passwordVisibility()[index()] ? (
|
||||
// <Icon icon="EyeClose" />
|
||||
// ) : (
|
||||
// <Icon icon="EyeOpen" />
|
||||
// )
|
||||
// }
|
||||
// ></Button>
|
||||
// ),
|
||||
// }}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="light"
|
||||
class="h-10"
|
||||
size="s"
|
||||
onClick={() => removeWifiNetwork(index())}
|
||||
startIcon={<Icon icon="Trash" />}
|
||||
></Button>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Fieldset>
|
||||
|
||||
<div class=" " tabindex="0">
|
||||
<input type="checkbox" />
|
||||
<div class=" px-0">
|
||||
<InputLabel class="mb-4">Advanced</InputLabel>
|
||||
</div>
|
||||
<div class="">
|
||||
<Accordion title="Advanced">
|
||||
<Fieldset>
|
||||
<Field
|
||||
name="machine.flake"
|
||||
validate={[required("This field is required")]}
|
||||
@@ -508,10 +443,8 @@ export const Flash = () => {
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr></hr>
|
||||
</Fieldset>
|
||||
</Accordion>
|
||||
<div class="mt-2 flex justify-end pt-2">
|
||||
<Button
|
||||
class="self-end"
|
||||
|
||||
@@ -11,7 +11,6 @@ import { Match, Switch } from "solid-js";
|
||||
import toast from "solid-toast";
|
||||
import { MachineAvatar } from "./avatar";
|
||||
import { DynForm } from "@/src/Form/form";
|
||||
import { Typography } from "@/src/components/Typography";
|
||||
import Fieldset from "@/src/Form/fieldset";
|
||||
import Accordion from "@/src/components/accordion";
|
||||
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import { callApi, SuccessData } from "@/src/api";
|
||||
import { activeURI } from "@/src/App";
|
||||
import { Button } from "@/src/components/button";
|
||||
import Icon from "@/src/components/icon";
|
||||
import { TextInput } from "@/src/Form/fields/TextInput";
|
||||
|
||||
import {
|
||||
createForm,
|
||||
FieldValues,
|
||||
@@ -14,6 +9,12 @@ import {
|
||||
import { useNavigate, useParams, useSearchParams } from "@solidjs/router";
|
||||
import { createQuery, useQueryClient } from "@tanstack/solid-query";
|
||||
import { createEffect, createSignal, For, Match, Show, Switch } from "solid-js";
|
||||
|
||||
import { activeURI } from "@/src/App";
|
||||
import { Button } from "@/src/components/button";
|
||||
import Icon from "@/src/components/icon";
|
||||
import { TextInput } from "@/src/Form/fields/TextInput";
|
||||
import Accordion from "@/src/components/accordion";
|
||||
import toast from "solid-toast";
|
||||
import { MachineAvatar } from "./avatar";
|
||||
import { Header } from "@/src/layout/header";
|
||||
@@ -550,7 +551,7 @@ const MachineForm = (props: MachineDetailsProps) => {
|
||||
onClick={() => {
|
||||
setInstallModalOpen(true);
|
||||
}}
|
||||
endIcon={<Icon icon="Flash" />}
|
||||
endIcon={<Icon size={14} icon="Flash" />}
|
||||
>
|
||||
Install
|
||||
</Button>
|
||||
@@ -559,7 +560,7 @@ const MachineForm = (props: MachineDetailsProps) => {
|
||||
class="w-full"
|
||||
size="s"
|
||||
onClick={() => handleUpdate()}
|
||||
endIcon={<Icon icon="Update" />}
|
||||
endIcon={<Icon size={12} icon="Update" />}
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
@@ -636,6 +637,7 @@ const MachineForm = (props: MachineDetailsProps) => {
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
<hr />
|
||||
<Field name="disk_schema.schema_name">
|
||||
{(field, props) => (
|
||||
<>
|
||||
@@ -646,27 +648,25 @@ const MachineForm = (props: MachineDetailsProps) => {
|
||||
</>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
<div class=" col-span-full" tabindex="0">
|
||||
<input type="checkbox" />
|
||||
<div class=" px-0 text-xl ">Connection Settings</div>
|
||||
<div class="">
|
||||
<Field name="machine.deploy.targetHost">
|
||||
{(field, props) => (
|
||||
<TextInput
|
||||
inputProps={props}
|
||||
label="Target Host"
|
||||
value={field.value ?? ""}
|
||||
error={field.error}
|
||||
class="col-span-2"
|
||||
required
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
</Fieldset>
|
||||
|
||||
<Accordion title="Connection Settings">
|
||||
<Fieldset>
|
||||
<Field name="machine.deploy.targetHost">
|
||||
{(field, props) => (
|
||||
<TextInput
|
||||
inputProps={props}
|
||||
label="Target Host"
|
||||
value={field.value ?? ""}
|
||||
error={field.error}
|
||||
class="col-span-2"
|
||||
required
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
</Fieldset>
|
||||
</Accordion>
|
||||
|
||||
{
|
||||
<footer class="flex justify-end gap-y-3 border-t border-secondary-200 pt-5">
|
||||
<Button
|
||||
|
||||
Reference in New Issue
Block a user