Merge pull request 'fix/machine-detail-view' (#3777) from fix/machine-detail-view into main

Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3777
This commit is contained in:
hsjobeki
2025-06-03 12:10:21 +00:00
7 changed files with 117 additions and 37 deletions

View File

@@ -1,5 +1,7 @@
import type { Preview } from "@kachurun/storybook-solid"; import type { Preview } from "@kachurun/storybook-solid";
import "../src/index.css";
export const preview: Preview = { export const preview: Preview = {
tags: ["autodocs"], tags: ["autodocs"],
parameters: { parameters: {

View File

@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from "@kachurun/storybook-solid"; import type { Meta, StoryObj } from "@kachurun/storybook-solid";
import { Button, ButtonProps } from "./Button"; import { Button, ButtonProps } from "./Button";
import FlashIcon from "@/icons/flash.svg"; import Icon from "../icon";
const meta: Meta<ButtonProps> = { const meta: Meta<ButtonProps> = {
title: "Components/Button", title: "Components/Button",
@@ -12,12 +12,10 @@ export default meta;
type Story = StoryObj<ButtonProps>; type Story = StoryObj<ButtonProps>;
const children = "click me"; const children = "click me";
const startIcon = <FlashIcon width={16} height={16} viewBox="0 0 48 48" />;
export const Default: Story = { export const Default: Story = {
args: { args: {
children, children,
startIcon,
}, },
}; };
@@ -41,3 +39,17 @@ export const Ghost: Story = {
variant: "ghost", variant: "ghost",
}, },
}; };
export const StartIcon: Story = {
args: {
...Default.args,
startIcon: <Icon size={12} icon="Flash" />,
},
};
export const EndIcon: Story = {
args: {
...Default.args,
endIcon: <Icon size={12} icon="Flash" />,
},
};

View File

@@ -0,0 +1,7 @@
div.tag-list {
@apply flex flex-wrap gap-2;
span.tag {
@apply w-fit rounded-full px-3 py-2 bg-inv-4 fg-inv-1;
}
}

View File

@@ -0,0 +1,36 @@
import type { Meta, StoryObj } from "@kachurun/storybook-solid";
import { TagList, TagListProps } from "./TagList";
const meta: Meta<TagListProps> = {
title: "Components/TagList",
component: TagList,
decorators: [
// wrap in a fixed width div so we can check that it wraps
(Story) => {
return (
<div style={{ width: "20em" }}>
<Story />
</div>
);
},
],
};
export default meta;
type Story = StoryObj<TagListProps>;
export const Default: Story = {
args: {
values: [
"Titan",
"Enceladus",
"Mimas",
"Dione",
"Iapetus",
"Tethys",
"Hyperion",
"Epimetheus",
],
},
};

View File

@@ -0,0 +1,21 @@
import { Component, For } from "solid-js";
import { Typography } from "@/src/components/Typography";
import "./TagList.css";
export interface TagListProps {
values: string[];
}
export const TagList: Component<TagListProps> = (props) => {
return (
<div class="tag-list">
<For each={props.values}>
{(tag) => (
<Typography hierarchy="label" size="s" inverted={true} class="tag">
{tag}
</Typography>
)}
</For>
</div>
);
};

View File

@@ -137,7 +137,7 @@ export const InputLabel = (props: InputLabelProps) => {
weight="bold" weight="bold"
size="xs" size="xs"
> >
{""} <>&#42;</>
</Typography> </Typography>
)} )}
{props.help && ( {props.help && (

View File

@@ -32,6 +32,7 @@ import {
FileSelectorField, FileSelectorField,
} from "@/src/components/fileSelect"; } from "@/src/components/fileSelect";
import { useClanContext } from "@/src/contexts/clan"; import { useClanContext } from "@/src/contexts/clan";
import { TagList } from "@/src/components/TagList/TagList";
type MachineFormInterface = MachineData & { type MachineFormInterface = MachineData & {
sshKey?: File; sshKey?: File;
@@ -77,10 +78,12 @@ const LoadingBar = () => (
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;
machine: MachineData; machine: MachineData;
} }
const InstallMachine = (props: InstallMachineProps) => { const InstallMachine = (props: InstallMachineProps) => {
const { activeClanURI } = useClanContext(); const { activeClanURI } = useClanContext();
@@ -381,6 +384,7 @@ const InstallMachine = (props: InstallMachineProps) => {
interface MachineDetailsProps { interface MachineDetailsProps {
initialData: MachineData; initialData: MachineData;
} }
const MachineForm = (props: MachineDetailsProps) => { const MachineForm = (props: MachineDetailsProps) => {
const [formStore, { Form, Field }] = const [formStore, { Form, Field }] =
// TODO: retrieve the correct initial values from API // TODO: retrieve the correct initial values from API
@@ -595,49 +599,47 @@ const MachineForm = (props: MachineDetailsProps) => {
</Field> </Field>
<Field name="machine.tags" type="string[]"> <Field name="machine.tags" type="string[]">
{(field, props) => ( {(field, props) => (
<div class="flex items-center gap-4"> <div class="grid grid-cols-10 items-center">
<Typography hierarchy="label" size="default" weight="bold"> <Typography
hierarchy="label"
size="default"
weight="bold"
class="col-span-5"
>
Tags{" "} Tags{" "}
</Typography> </Typography>
<For each={field.value}> <div class="col-span-5 justify-self-end">
{(tag) => ( {/* alphabetically sort the tags */}
<span class="mx-2 w-fit rounded-full px-3 py-0.5 bg-inv-4 fg-inv-1"> <TagList values={[...(field.value || [])].sort()} />
<Typography </div>
hierarchy="label"
size="s"
inverted={true}
>
{tag}
</Typography>
</span>
)}
</For>
</div> </div>
)} )}
</Field> </Field>
</Fieldset> </Fieldset>
<Fieldset legend="Hardware"> <Typography hierarchy={"body"} size={"s"}>
<Field name="hw_config"> <Fieldset legend="Hardware">
{(field, props) => ( <Field name="hw_config">
<FieldLayout {(field, props) => (
label={<InputLabel>Hardware Configuration</InputLabel>}
field={<span>{field.value || "None"}</span>}
/>
)}
</Field>
<hr />
<Field name="disk_schema.schema_name">
{(field, props) => (
<>
<FieldLayout <FieldLayout
label={<InputLabel>Disk schema</InputLabel>} label={<InputLabel>Hardware Configuration</InputLabel>}
field={<span>{field.value || "None"}</span>} field={<span>{field.value || "None"}</span>}
/> />
</> )}
)} </Field>
</Field> <hr />
</Fieldset> <Field name="disk_schema.schema_name">
{(field, props) => (
<>
<FieldLayout
label={<InputLabel>Disk schema</InputLabel>}
field={<span>{field.value || "None"}</span>}
/>
</>
)}
</Field>
</Fieldset>
</Typography>
<Accordion title="Connection Settings"> <Accordion title="Connection Settings">
<Fieldset> <Fieldset>