Merge pull request 'UI: improve welcome workflows' (#1975) from hsjobeki/clan-core:hsjobeki-main into main
This commit is contained in:
10
pkgs/webview-ui/app/src/components/BackButton.tsx
Normal file
10
pkgs/webview-ui/app/src/components/BackButton.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { useNavigate } from "@solidjs/router";
|
||||||
|
|
||||||
|
export const BackButton = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
return (
|
||||||
|
<button class="btn btn-square btn-ghost" onClick={() => navigate(-1)}>
|
||||||
|
<span class="material-icons ">arrow_back_ios</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -3,7 +3,7 @@ import { callApi, SuccessData } from "../api";
|
|||||||
import { Menu } from "./Menu";
|
import { Menu } from "./Menu";
|
||||||
import { activeURI } from "../App";
|
import { activeURI } from "../App";
|
||||||
import toast from "solid-toast";
|
import toast from "solid-toast";
|
||||||
import { useNavigate } from "@solidjs/router";
|
import { A, useNavigate } from "@solidjs/router";
|
||||||
|
|
||||||
type MachineDetails = SuccessData<"list_inventory_machines">["data"][string];
|
type MachineDetails = SuccessData<"list_inventory_machines">["data"][string];
|
||||||
|
|
||||||
@@ -116,14 +116,16 @@ export const MachineListItem = (props: MachineListItemProps) => {
|
|||||||
</figure>
|
</figure>
|
||||||
<div class="card-body flex-row justify-between ">
|
<div class="card-body flex-row justify-between ">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<h2
|
<A href={`/machines/${name}`}>
|
||||||
class="card-title"
|
<h2
|
||||||
classList={{
|
class="card-title underline"
|
||||||
"text-neutral-500": nixOnly,
|
classList={{
|
||||||
}}
|
"text-neutral-500": nixOnly,
|
||||||
>
|
}}
|
||||||
{name}
|
>
|
||||||
</h2>
|
{name}
|
||||||
|
</h2>
|
||||||
|
</A>
|
||||||
<div class="text-slate-600">
|
<div class="text-slate-600">
|
||||||
<Show when={info}>{(d) => d()?.description}</Show>
|
<Show when={info}>{(d) => d()?.description}</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import {
|
|||||||
SubmitHandler,
|
SubmitHandler,
|
||||||
} from "@modular-forms/solid";
|
} from "@modular-forms/solid";
|
||||||
import toast from "solid-toast";
|
import toast from "solid-toast";
|
||||||
import { setActiveURI } from "@/src/App";
|
import { setActiveURI, setClanList } from "@/src/App";
|
||||||
|
import { TextInput } from "@/src/components/TextInput";
|
||||||
|
import { useNavigate } from "@solidjs/router";
|
||||||
|
|
||||||
type CreateForm = Meta & {
|
type CreateForm = Meta & {
|
||||||
template: string;
|
template: string;
|
||||||
@@ -21,10 +23,10 @@ export const ClanForm = () => {
|
|||||||
template: "minimal",
|
template: "minimal",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleSubmit: SubmitHandler<CreateForm> = async (values, event) => {
|
const handleSubmit: SubmitHandler<CreateForm> = async (values, event) => {
|
||||||
const { template, ...meta } = values;
|
const { template, ...meta } = values;
|
||||||
|
|
||||||
const response = await callApi("open_file", {
|
const response = await callApi("open_file", {
|
||||||
file_request: { mode: "save" },
|
file_request: { mode: "save" },
|
||||||
});
|
});
|
||||||
@@ -39,29 +41,31 @@ export const ClanForm = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await toast.promise(
|
const loading_toast = toast.loading("Creating Clan....");
|
||||||
(async () => {
|
const r = await callApi("create_clan", {
|
||||||
await callApi("create_clan", {
|
options: {
|
||||||
options: {
|
directory: target_dir[0],
|
||||||
directory: target_dir[0],
|
template,
|
||||||
template,
|
initial: {
|
||||||
initial: {
|
meta,
|
||||||
meta,
|
services: {},
|
||||||
services: {},
|
machines: {},
|
||||||
machines: {},
|
},
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
setActiveURI(target_dir[0]);
|
|
||||||
// setRoute("machines");
|
|
||||||
})(),
|
|
||||||
{
|
|
||||||
loading: "Creating clan...",
|
|
||||||
success: "Clan Successfully Created",
|
|
||||||
error: "Failed to create clan",
|
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
reset(formStore);
|
toast.dismiss(loading_toast);
|
||||||
|
|
||||||
|
if (r.status === "error") {
|
||||||
|
toast.error("Failed to create clan");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (r.status === "success") {
|
||||||
|
toast.success("Clan Successfully Created");
|
||||||
|
setActiveURI(target_dir[0]);
|
||||||
|
setClanList((list) => [...list, target_dir[0]]);
|
||||||
|
navigate("/machines");
|
||||||
|
reset(formStore);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -108,7 +112,7 @@ export const ClanForm = () => {
|
|||||||
{...props}
|
{...props}
|
||||||
disabled={formStore.submitting}
|
disabled={formStore.submitting}
|
||||||
required
|
required
|
||||||
placeholder="Clan Name"
|
placeholder="Give your Clan a legendary name"
|
||||||
class="input input-bordered"
|
class="input input-bordered"
|
||||||
classList={{ "input-error": !!field.error }}
|
classList={{ "input-error": !!field.error }}
|
||||||
value={field.value}
|
value={field.value}
|
||||||
@@ -133,7 +137,7 @@ export const ClanForm = () => {
|
|||||||
disabled={formStore.submitting}
|
disabled={formStore.submitting}
|
||||||
required
|
required
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Some words about your clan"
|
placeholder="Tell us what makes your Clan legendary"
|
||||||
class="input input-bordered"
|
class="input input-bordered"
|
||||||
classList={{ "input-error": !!field.error }}
|
classList={{ "input-error": !!field.error }}
|
||||||
value={field.value || ""}
|
value={field.value || ""}
|
||||||
@@ -152,23 +156,20 @@ export const ClanForm = () => {
|
|||||||
<input type="checkbox" />
|
<input type="checkbox" />
|
||||||
<div class="collapse-title link font-medium ">Advanced</div>
|
<div class="collapse-title link font-medium ">Advanced</div>
|
||||||
<div class="collapse-content">
|
<div class="collapse-content">
|
||||||
<label class="form-control w-full">
|
<TextInput
|
||||||
<div class="label ">
|
adornment={{
|
||||||
<span class="label-text after:ml-0.5 after:text-primary after:content-['*']">
|
content: (
|
||||||
Template to use
|
<span class="-mr-1 text-neutral-500">clan-core #</span>
|
||||||
</span>
|
),
|
||||||
</div>
|
position: "start",
|
||||||
<input
|
}}
|
||||||
{...props}
|
formStore={formStore}
|
||||||
required
|
inputProps={props}
|
||||||
disabled={formStore.submitting}
|
label="Template to use"
|
||||||
type="text"
|
value={field.value ?? ""}
|
||||||
placeholder="Template to use"
|
error={field.error}
|
||||||
class="input input-bordered"
|
required
|
||||||
classList={{ "input-error": !!field.error }}
|
/>
|
||||||
value={field.value}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { callApi, SuccessData } from "@/src/api";
|
import { callApi, SuccessData } from "@/src/api";
|
||||||
import { activeURI } from "@/src/App";
|
import { activeURI } from "@/src/App";
|
||||||
|
import { BackButton } from "@/src/components/BackButton";
|
||||||
import { FileInput } from "@/src/components/FileInput";
|
import { FileInput } from "@/src/components/FileInput";
|
||||||
import { SelectInput } from "@/src/components/SelectInput";
|
import { SelectInput } from "@/src/components/SelectInput";
|
||||||
import { TextInput } from "@/src/components/TextInput";
|
import { TextInput } from "@/src/components/TextInput";
|
||||||
@@ -420,159 +421,163 @@ const MachineForm = (props: MachineDetailsProps) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div class="m-2 w-full max-w-xl">
|
<div class="flex w-full justify-center">
|
||||||
<Form onSubmit={handleSubmit}>
|
<div class="m-2 w-full max-w-xl">
|
||||||
<div class="flex w-full justify-center p-2">
|
<Form onSubmit={handleSubmit}>
|
||||||
<div
|
<div class="flex w-full justify-center p-2">
|
||||||
class="avatar placeholder"
|
<div
|
||||||
classList={{
|
class="avatar placeholder"
|
||||||
online: onlineStatusQuery.data === "Online",
|
classList={{
|
||||||
offline: onlineStatusQuery.data === "Offline",
|
online: onlineStatusQuery.data === "Online",
|
||||||
}}
|
offline: onlineStatusQuery.data === "Offline",
|
||||||
>
|
}}
|
||||||
<div class="w-24 rounded-full bg-neutral text-neutral-content">
|
>
|
||||||
<Show
|
<div class="w-24 rounded-full bg-neutral text-neutral-content">
|
||||||
when={onlineStatusQuery.isFetching}
|
<Show
|
||||||
fallback={<span class="material-icons text-4xl">devices</span>}
|
when={onlineStatusQuery.isFetching}
|
||||||
>
|
fallback={
|
||||||
<span class="loading loading-bars loading-sm justify-self-end"></span>
|
<span class="material-icons text-4xl">devices</span>
|
||||||
</Show>
|
}
|
||||||
|
>
|
||||||
|
<span class="loading loading-bars loading-sm justify-self-end"></span>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="my-2 w-full text-2xl">Details</div>
|
||||||
<div class="my-2 w-full text-2xl">Details</div>
|
<Field name="name">
|
||||||
<Field name="name">
|
{(field, props) => (
|
||||||
{(field, props) => (
|
<TextInput
|
||||||
<TextInput
|
formStore={formStore}
|
||||||
formStore={formStore}
|
inputProps={props}
|
||||||
inputProps={props}
|
label="Name"
|
||||||
label="Name"
|
value={field.value ?? ""}
|
||||||
value={field.value ?? ""}
|
error={field.error}
|
||||||
error={field.error}
|
class="col-span-2"
|
||||||
class="col-span-2"
|
required
|
||||||
required
|
/>
|
||||||
/>
|
)}
|
||||||
)}
|
</Field>
|
||||||
</Field>
|
<Field name="description">
|
||||||
<Field name="description">
|
{(field, props) => (
|
||||||
{(field, props) => (
|
<TextInput
|
||||||
<TextInput
|
formStore={formStore}
|
||||||
formStore={formStore}
|
inputProps={props}
|
||||||
inputProps={props}
|
label="Description"
|
||||||
label="Description"
|
value={field.value ?? ""}
|
||||||
value={field.value ?? ""}
|
error={field.error}
|
||||||
error={field.error}
|
class="col-span-2"
|
||||||
class="col-span-2"
|
required
|
||||||
required
|
/>
|
||||||
/>
|
)}
|
||||||
)}
|
</Field>
|
||||||
</Field>
|
|
||||||
|
|
||||||
<div class="collapse collapse-arrow" tabindex="0">
|
<div class="collapse collapse-arrow" tabindex="0">
|
||||||
<input type="checkbox" />
|
<input type="checkbox" />
|
||||||
<div class="collapse-title link px-0 text-xl ">
|
<div class="collapse-title link px-0 text-xl ">
|
||||||
Connection Settings
|
Connection Settings
|
||||||
</div>
|
</div>
|
||||||
<div class="collapse-content">
|
<div class="collapse-content">
|
||||||
<Field name="targetHost">
|
<Field name="targetHost">
|
||||||
{(field, props) => (
|
{(field, props) => (
|
||||||
<TextInput
|
<TextInput
|
||||||
formStore={formStore}
|
formStore={formStore}
|
||||||
inputProps={props}
|
inputProps={props}
|
||||||
label="Target Host"
|
label="Target Host"
|
||||||
value={field.value ?? ""}
|
value={field.value ?? ""}
|
||||||
error={field.error}
|
|
||||||
class="col-span-2"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
<Field name="sshKey" type="File">
|
|
||||||
{(field, props) => (
|
|
||||||
<>
|
|
||||||
<FileInput
|
|
||||||
{...props}
|
|
||||||
onClick={async (event) => {
|
|
||||||
event.preventDefault(); // Prevent the native file dialog from opening
|
|
||||||
const input = event.target;
|
|
||||||
const files = await selectSshKeys();
|
|
||||||
|
|
||||||
// Set the files
|
|
||||||
Object.defineProperty(input, "files", {
|
|
||||||
value: files,
|
|
||||||
writable: true,
|
|
||||||
});
|
|
||||||
// Define the files property on the input element
|
|
||||||
const changeEvent = new Event("input", {
|
|
||||||
bubbles: true,
|
|
||||||
cancelable: true,
|
|
||||||
});
|
|
||||||
input.dispatchEvent(changeEvent);
|
|
||||||
}}
|
|
||||||
placeholder={"When empty the default key(s) will be used"}
|
|
||||||
value={field.value}
|
|
||||||
error={field.error}
|
error={field.error}
|
||||||
helperText="Provide the SSH key used to connect to the machine"
|
class="col-span-2"
|
||||||
label="SSH Key"
|
required
|
||||||
/>
|
/>
|
||||||
</>
|
)}
|
||||||
)}
|
</Field>
|
||||||
</Field>
|
<Field name="sshKey" type="File">
|
||||||
|
{(field, props) => (
|
||||||
|
<>
|
||||||
|
<FileInput
|
||||||
|
{...props}
|
||||||
|
onClick={async (event) => {
|
||||||
|
event.preventDefault(); // Prevent the native file dialog from opening
|
||||||
|
const input = event.target;
|
||||||
|
const files = await selectSshKeys();
|
||||||
|
|
||||||
|
// Set the files
|
||||||
|
Object.defineProperty(input, "files", {
|
||||||
|
value: files,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
// Define the files property on the input element
|
||||||
|
const changeEvent = new Event("input", {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
});
|
||||||
|
input.dispatchEvent(changeEvent);
|
||||||
|
}}
|
||||||
|
placeholder={"When empty the default key(s) will be used"}
|
||||||
|
value={field.value}
|
||||||
|
error={field.error}
|
||||||
|
helperText="Provide the SSH key used to connect to the machine"
|
||||||
|
label="SSH Key"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="my-2 w-full">
|
<div class="my-2 w-full">
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary btn-wide"
|
class="btn btn-primary btn-wide"
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!formStore.dirty}
|
disabled={!formStore.dirty}
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
<div class="my-2 w-full text-2xl">Remote Interactions</div>
|
<div class="my-2 w-full text-2xl">Remote Interactions</div>
|
||||||
<div class="my-2 flex w-full flex-col gap-2">
|
<div class="my-2 flex w-full flex-col gap-2">
|
||||||
<span class="max-w-md text-neutral">
|
<span class="max-w-md text-neutral">
|
||||||
Installs the system for the first time. Used to bootstrap the remote
|
Installs the system for the first time. Used to bootstrap the remote
|
||||||
device.
|
device.
|
||||||
</span>
|
</span>
|
||||||
<div class="tooltip w-fit" data-tip="Machine must be online">
|
<div class="tooltip w-fit" data-tip="Machine must be online">
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary btn-sm btn-wide"
|
class="btn btn-primary btn-sm btn-wide"
|
||||||
disabled={!online()}
|
disabled={!online()}
|
||||||
// @ts-expect-error: This string method is not supported by ts
|
// @ts-expect-error: This string method is not supported by ts
|
||||||
onClick="install_modal.showModal()"
|
onClick="install_modal.showModal()"
|
||||||
>
|
>
|
||||||
<span class="material-icons">send_to_mobile</span>
|
<span class="material-icons">send_to_mobile</span>
|
||||||
Install
|
Install
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
|
|
||||||
<dialog id="install_modal" class="modal backdrop:bg-transparent">
|
|
||||||
<div class="modal-box w-11/12 max-w-5xl">
|
|
||||||
<InstallMachine
|
|
||||||
name={machineName()}
|
|
||||||
sshKey={sshKey()}
|
|
||||||
targetHost={getValue(formStore, "targetHost")}
|
|
||||||
disks={remoteDiskQuery.data?.blockdevices || []}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</dialog>
|
|
||||||
|
|
||||||
<span class="max-w-md text-neutral">
|
<dialog id="install_modal" class="modal backdrop:bg-transparent">
|
||||||
Update the system if changes should be synced after the installation
|
<div class="modal-box w-11/12 max-w-5xl">
|
||||||
process.
|
<InstallMachine
|
||||||
</span>
|
name={machineName()}
|
||||||
<div class="tooltip w-fit" data-tip="Machine must be online">
|
sshKey={sshKey()}
|
||||||
<button
|
targetHost={getValue(formStore, "targetHost")}
|
||||||
class="btn btn-primary btn-sm btn-wide"
|
disks={remoteDiskQuery.data?.blockdevices || []}
|
||||||
disabled={!online()}
|
/>
|
||||||
onClick={() => handleUpdate()}
|
</div>
|
||||||
>
|
</dialog>
|
||||||
<span class="material-icons">update</span>
|
|
||||||
Update
|
<span class="max-w-md text-neutral">
|
||||||
</button>
|
Update the system if changes should be synced after the installation
|
||||||
|
process.
|
||||||
|
</span>
|
||||||
|
<div class="tooltip w-fit" data-tip="Machine must be online">
|
||||||
|
<button
|
||||||
|
class="btn btn-primary btn-sm btn-wide"
|
||||||
|
disabled={!online()}
|
||||||
|
onClick={() => handleUpdate()}
|
||||||
|
>
|
||||||
|
<span class="material-icons">update</span>
|
||||||
|
Update
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -602,7 +607,8 @@ export const MachineDetails = () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div class="p-2">
|
||||||
|
<BackButton />
|
||||||
<Show
|
<Show
|
||||||
when={query.data}
|
when={query.data}
|
||||||
fallback={<span class="loading loading-lg"></span>}
|
fallback={<span class="loading loading-lg"></span>}
|
||||||
@@ -615,6 +621,6 @@ export const MachineDetails = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -59,68 +59,73 @@ export function CreateMachine() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div class="px-1">
|
<div class="flex w-full justify-center">
|
||||||
<span class="px-2">Create new Machine</span>
|
<div class="mt-4 w-full max-w-3xl self-stretch px-2">
|
||||||
<Form onSubmit={handleSubmit}>
|
<span class="px-2">Create new Machine</span>
|
||||||
<Field
|
<Form onSubmit={handleSubmit}>
|
||||||
name="machine.name"
|
<Field
|
||||||
validate={[required("This field is required")]}
|
name="machine.name"
|
||||||
>
|
validate={[required("This field is required")]}
|
||||||
{(field, props) => (
|
>
|
||||||
<TextInput
|
{(field, props) => (
|
||||||
inputProps={props}
|
|
||||||
formStore={formStore}
|
|
||||||
value={`${field.value}`}
|
|
||||||
label={"name"}
|
|
||||||
error={field.error}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
<Field name="machine.description">
|
|
||||||
{(field, props) => (
|
|
||||||
<TextInput
|
|
||||||
inputProps={props}
|
|
||||||
formStore={formStore}
|
|
||||||
value={`${field.value}`}
|
|
||||||
label={"description"}
|
|
||||||
error={field.error}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
<Field name="machine.deploy.targetHost">
|
|
||||||
{(field, props) => (
|
|
||||||
<>
|
|
||||||
<TextInput
|
<TextInput
|
||||||
inputProps={props}
|
inputProps={props}
|
||||||
formStore={formStore}
|
formStore={formStore}
|
||||||
value={`${field.value}`}
|
value={`${field.value}`}
|
||||||
label={"Deployment target"}
|
label={"name"}
|
||||||
|
error={field.error}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
<Field name="machine.description">
|
||||||
|
{(field, props) => (
|
||||||
|
<TextInput
|
||||||
|
inputProps={props}
|
||||||
|
formStore={formStore}
|
||||||
|
value={`${field.value}`}
|
||||||
|
label={"description"}
|
||||||
error={field.error}
|
error={field.error}
|
||||||
/>
|
/>
|
||||||
</>
|
)}
|
||||||
)}
|
</Field>
|
||||||
</Field>
|
<Field name="machine.deploy.targetHost">
|
||||||
<button
|
{(field, props) => (
|
||||||
class="btn btn-error float-right"
|
|
||||||
type="submit"
|
|
||||||
classList={{
|
|
||||||
"btn-disabled": formStore.submitting,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Switch
|
|
||||||
fallback={
|
|
||||||
<>
|
<>
|
||||||
<span class="loading loading-spinner loading-sm"></span>Creating
|
<TextInput
|
||||||
|
inputProps={props}
|
||||||
|
formStore={formStore}
|
||||||
|
value={`${field.value}`}
|
||||||
|
label={"Deployment target"}
|
||||||
|
error={field.error}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
}
|
)}
|
||||||
>
|
</Field>
|
||||||
<Match when={!formStore.submitting}>
|
<div class="mt-12 flex justify-end">
|
||||||
<span class="material-icons">add</span>Create
|
<button
|
||||||
</Match>
|
class="btn btn-primary"
|
||||||
</Switch>
|
type="submit"
|
||||||
</button>
|
classList={{
|
||||||
</Form>
|
"btn-disabled": formStore.submitting,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
fallback={
|
||||||
|
<>
|
||||||
|
<span class="loading loading-spinner loading-sm"></span>
|
||||||
|
Creating
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Match when={!formStore.submitting}>
|
||||||
|
<span class="material-icons">add</span>Create
|
||||||
|
</Match>
|
||||||
|
</Switch>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,17 @@ export const MachineListView: Component = () => {
|
|||||||
nixOnlyMachines()?.length === 0
|
nixOnlyMachines()?.length === 0
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
No machines found
|
<div class="mt-8 flex w-full flex-col items-center justify-center gap-2">
|
||||||
|
<span class="text-lg text-neutral">
|
||||||
|
No machines defined yet. Click below to define one.
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
class="btn btn-square btn-ghost size-28 overflow-hidden p-2"
|
||||||
|
onClick={() => navigate("/machines/create")}
|
||||||
|
>
|
||||||
|
<span class="material-icons text-6xl font-light">add</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={!inventoryQuery.isLoading}>
|
<Match when={!inventoryQuery.isLoading}>
|
||||||
<ul>
|
<ul>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const Welcome = () => {
|
|||||||
<div class="flex flex-col items-start gap-2">
|
<div class="flex flex-col items-start gap-2">
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary w-full"
|
class="btn btn-primary w-full"
|
||||||
// onClick={() => setRoute("createClan")}
|
onClick={() => navigate("/clan/create")}
|
||||||
>
|
>
|
||||||
Build your own
|
Build your own
|
||||||
</button>
|
</button>
|
||||||
@@ -29,14 +29,6 @@ export const Welcome = () => {
|
|||||||
>
|
>
|
||||||
Or select folder
|
Or select folder
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
class="link w-full text-right text-secondary"
|
|
||||||
onClick={async () => {
|
|
||||||
setClanList((c) => [...c, "debug"]);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Skip (Debug)
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user