diff --git a/pkgs/clan-cli/clan_cli/machines/list.py b/pkgs/clan-cli/clan_cli/machines/list.py index b473a0c2f..a89504733 100644 --- a/pkgs/clan-cli/clan_cli/machines/list.py +++ b/pkgs/clan-cli/clan_cli/machines/list.py @@ -13,15 +13,13 @@ log = logging.getLogger(__name__) @API.register -def list_inventory_machines( - flake_url: str | Path, debug: bool = False -) -> dict[str, Machine]: +def list_inventory_machines(flake_url: str | Path) -> dict[str, Machine]: inventory = load_inventory_eval(flake_url) return inventory.machines @API.register -def list_nixos_machines(flake_url: str | Path, debug: bool = False) -> list[str]: +def list_nixos_machines(flake_url: str | Path) -> list[str]: cmd = nix_eval( [ f"{flake_url}#nixosConfigurations", @@ -42,7 +40,7 @@ def list_nixos_machines(flake_url: str | Path, debug: bool = False) -> list[str] def list_command(args: argparse.Namespace) -> None: flake_path = args.flake.path - for name in list_nixos_machines(flake_path, args.debug): + for name in list_nixos_machines(flake_path): print(name) diff --git a/pkgs/webview-ui/app/src/App.tsx b/pkgs/webview-ui/app/src/App.tsx index ea0b2742f..3aa1e369a 100644 --- a/pkgs/webview-ui/app/src/App.tsx +++ b/pkgs/webview-ui/app/src/App.tsx @@ -7,9 +7,6 @@ import { makePersisted } from "@solid-primitives/storage"; // Some global state const [route, setRoute] = createSignal("machines"); -createEffect(() => { - console.log(route()); -}); export { route, setRoute }; diff --git a/pkgs/webview-ui/app/src/Routes.tsx b/pkgs/webview-ui/app/src/Routes.tsx index a9a618fff..420502948 100644 --- a/pkgs/webview-ui/app/src/Routes.tsx +++ b/pkgs/webview-ui/app/src/Routes.tsx @@ -69,6 +69,11 @@ export const routes = { label: "diskConfig", icon: "disk", }, + "machines/edit": { + child: CreateMachine, + label: "Edit Machine", + icon: "edit", + }, }; interface RouterProps { diff --git a/pkgs/webview-ui/app/src/api.ts b/pkgs/webview-ui/app/src/api.ts index f12d95c8e..b7d48bcd9 100644 --- a/pkgs/webview-ui/app/src/api.ts +++ b/pkgs/webview-ui/app/src/api.ts @@ -123,7 +123,7 @@ export const callApi = ( return new Promise>((resolve) => { const id = nanoid(); pyApi[method].receive((response) => { - console.log("Received response: ", { response }); + console.log(method, "Received response: ", { response }); resolve(response); }, id); @@ -136,7 +136,6 @@ const deserialize = (str: string) => { try { const r = JSON.parse(str) as T; - console.log("Received: ", r); fn(r); } catch (e) { console.log("Error parsing JSON: ", e); diff --git a/pkgs/webview-ui/app/src/components/MachineListItem.tsx b/pkgs/webview-ui/app/src/components/MachineListItem.tsx index 749291f32..e5ba35e54 100644 --- a/pkgs/webview-ui/app/src/components/MachineListItem.tsx +++ b/pkgs/webview-ui/app/src/components/MachineListItem.tsx @@ -1,86 +1,67 @@ -import { createSignal, Match, Show, Switch } from "solid-js"; -import { ErrorData, pyApi, SuccessData } from "../api"; +import { Accessor, createEffect, Show } from "solid-js"; +import { SuccessData } from "../api"; +import { Menu } from "./Menu"; +import { setRoute } from "../App"; type MachineDetails = SuccessData<"list_inventory_machines">["data"][string]; interface MachineListItemProps { name: string; info?: MachineDetails; + nixOnly?: boolean; } -type HWInfo = Record["data"]>; -type DeploymentInfo = Record< - string, - SuccessData<"show_machine_deployment_target">["data"] ->; - -type MachineErrors = Record["errors"]>; - -const [hwInfo, setHwInfo] = createSignal({}); - -const [deploymentInfo, setDeploymentInfo] = createSignal({}); - -const [errors, setErrors] = createSignal({}); - -// pyApi.show_machine_hardware_info.receive((r) => { -// const { op_key } = r; -// if (r.status === "error") { -// console.error(r.errors); -// if (op_key) { -// setHwInfo((d) => ({ ...d, [op_key]: { system: null } })); -// } -// return; -// } -// if (op_key) { -// setHwInfo((d) => ({ ...d, [op_key]: r.data })); -// } -// }); - -// pyApi.show_machine_deployment_target.receive((r) => { -// const { op_key } = r; -// if (r.status === "error") { -// console.error(r.errors); -// if (op_key) { -// setDeploymentInfo((d) => ({ ...d, [op_key]: null })); -// } -// return; -// } -// if (op_key) { -// setDeploymentInfo((d) => ({ ...d, [op_key]: r.data })); -// } -// }); - export const MachineListItem = (props: MachineListItemProps) => { - const { name, info } = props; + const { name, info, nixOnly } = props; return (
  • -
    +
    - + devices_other
    -
    +
    -

    {name}

    +

    + {name} +

    {(d) => d()?.description}
    - {/* Show only the first error at the bottom */} - - {(error) => ( -
    - Error: {error().message}: {error().description} -
    - )} -
    - + more_vert} + > + +
    diff --git a/pkgs/webview-ui/app/src/components/Menu.tsx b/pkgs/webview-ui/app/src/components/Menu.tsx new file mode 100644 index 000000000..bf7f5922e --- /dev/null +++ b/pkgs/webview-ui/app/src/components/Menu.tsx @@ -0,0 +1,84 @@ +import { children, Component, createSignal, type JSX } from "solid-js"; +import { useFloating } from "@/src/floating"; +import { + autoUpdate, + flip, + hide, + offset, + Placement, + shift, +} from "@floating-ui/dom"; +import cx from "classnames"; + +interface MenuProps { + /** + * Used by the html API to associate the popover with the dispatcher button + */ + popoverid: string; + + label: JSX.Element; + + children?: JSX.Element; + buttonProps?: JSX.ButtonHTMLAttributes; + buttonClass?: string; + /** + * @default "bottom" + */ + placement?: Placement; +} +export const Menu = (props: MenuProps) => { + const c = children(() => props.children); + const [reference, setReference] = createSignal(); + const [floating, setFloating] = createSignal(); + + // `position` is a reactive object. + const position = useFloating(reference, floating, { + placement: "bottom", + + // pass options. Ensure the cleanup function is returned. + whileElementsMounted: (reference, floating, update) => + autoUpdate(reference, floating, update, { + animationFrame: true, + }), + middleware: [ + offset(5), + shift(), + flip(), + + hide({ + strategy: "referenceHidden", + }), + ], + }); + + return ( +
    + +
    + {c()} +
    +
    + ); +}; diff --git a/pkgs/webview-ui/app/src/layout/header.tsx b/pkgs/webview-ui/app/src/layout/header.tsx index 1bae01663..5fb258577 100644 --- a/pkgs/webview-ui/app/src/layout/header.tsx +++ b/pkgs/webview-ui/app/src/layout/header.tsx @@ -1,7 +1,7 @@ import { createQuery } from "@tanstack/solid-query"; import { activeURI, setRoute } from "../App"; import { callApi } from "../api"; -import { Accessor, createEffect, Show } from "solid-js"; +import { Accessor, Show } from "solid-js"; interface HeaderProps { clan_dir: Accessor; @@ -34,7 +34,14 @@ export const Header = (props: HeaderProps) => {
    - + +
    + +
    +
    +
    +
    + {(meta) => (
    @@ -46,7 +53,7 @@ export const Header = (props: HeaderProps) => { )} - + {(meta) => [ {meta().name}, {meta()?.description}, diff --git a/pkgs/webview-ui/app/src/routes/machines/create.tsx b/pkgs/webview-ui/app/src/routes/machines/create.tsx index 7ab203d6c..7f8cfd4f2 100644 --- a/pkgs/webview-ui/app/src/routes/machines/create.tsx +++ b/pkgs/webview-ui/app/src/routes/machines/create.tsx @@ -1,7 +1,7 @@ import { callApi, OperationArgs, pyApi, OperationResponse } from "@/src/api"; import { activeURI, setRoute } from "@/src/App"; import { createForm, required, reset } from "@modular-forms/solid"; -import { createQuery } from "@tanstack/solid-query"; +import { createQuery, useQueryClient } from "@tanstack/solid-query"; import { Match, Switch } from "solid-js"; import toast from "solid-toast"; @@ -23,9 +23,7 @@ export function CreateMachine() { }, }); - const { refetch: refetchMachines } = createQuery(() => ({ - queryKey: [activeURI(), "list_inventory_machines"], - })); + const queryClient = useQueryClient(); const handleSubmit = async (values: CreateMachineForm) => { const active_dir = activeURI(); @@ -46,7 +44,10 @@ export function CreateMachine() { if (response.status === "success") { toast.success(`Successfully created ${values.machine.name}`); reset(formStore); - refetchMachines(); + + queryClient.invalidateQueries({ + queryKey: [activeURI(), "list_machines"], + }); setRoute("machines"); } else { toast.error( diff --git a/pkgs/webview-ui/app/src/routes/machines/view.tsx b/pkgs/webview-ui/app/src/routes/machines/view.tsx index b9b49e8a3..466e546f9 100644 --- a/pkgs/webview-ui/app/src/routes/machines/view.tsx +++ b/pkgs/webview-ui/app/src/routes/machines/view.tsx @@ -3,43 +3,28 @@ import { activeURI, setRoute } from "@/src/App"; import { callApi, OperationResponse } from "@/src/api"; import toast from "solid-toast"; import { MachineListItem } from "@/src/components/MachineListItem"; -import { createQuery } from "@tanstack/solid-query"; +import { + createQueries, + createQuery, + useQueryClient, +} from "@tanstack/solid-query"; type MachinesModel = Extract< OperationResponse<"list_inventory_machines">, { status: "success" } >["data"]; +type ExtendedMachine = MachinesModel & { + nixOnly: boolean; +}; + export const MachineListView: Component = () => { - const { - data: nixosMachines, - isFetching: isLoadingNixos, - refetch: refetchNixos, - } = createQuery(() => ({ - queryKey: [activeURI(), "list_nixos_machines"], - queryFn: async () => { - const uri = activeURI(); - if (uri) { - const response = await callApi("list_nixos_machines", { - flake_url: uri, - }); - if (response.status === "error") { - toast.error("Failed to fetch data"); - } else { - return response.data; - } - } - return []; - }, - staleTime: 1000 * 60 * 5, - })); - const { - data: inventoryMachines, - isFetching: isLoadingInventory, - refetch: refetchInventory, - } = createQuery(() => ({ - queryKey: [activeURI(), "list_inventory_machines"], - initialData: {}, + const queryClient = useQueryClient(); + + const inventoryQuery = createQuery(() => ({ + queryKey: [activeURI(), "list_machines", "inventory"], + placeholderData: {}, + enabled: !!activeURI(), queryFn: async () => { const uri = activeURI(); if (uri) { @@ -54,26 +39,43 @@ export const MachineListView: Component = () => { } return {}; }, - staleTime: 1000 * 60 * 5, + })); + + const nixosQuery = createQuery(() => ({ + queryKey: [activeURI(), "list_machines", "nixos"], + enabled: !!activeURI(), + placeholderData: [], + queryFn: async () => { + const uri = activeURI(); + if (uri) { + const response = await callApi("list_nixos_machines", { + flake_url: uri, + }); + if (response.status === "error") { + toast.error("Failed to fetch data"); + } else { + return response.data; + } + } + return []; + }, })); const refresh = async () => { - refetchInventory(); - refetchNixos(); + queryClient.invalidateQueries({ + // Invalidates the cache for of all types of machine list at once + queryKey: [activeURI(), "list_machines"], + }); }; - const unpackedMachines = () => Object.entries(inventoryMachines); + const inventoryMachines = () => Object.entries(inventoryQuery.data || {}); const nixOnlyMachines = () => - nixosMachines?.filter( - (name) => !unpackedMachines().some(([key, machine]) => key === name), + nixosQuery.data?.filter( + (name) => !inventoryMachines().some(([key, machine]) => key === name), ); - createEffect(() => { - console.log(nixOnlyMachines(), unpackedMachines()); - }); - return ( -
    +
    - + {/* Loading skeleton */}
    @@ -104,20 +106,20 @@ export const MachineListView: Component = () => { No machines found - +
      - + {([name, info]) => } - {(name) => } + {(name) => }
    diff --git a/pkgs/webview-ui/app/src/routes/settings/index.tsx b/pkgs/webview-ui/app/src/routes/settings/index.tsx index 81eb22bdf..fba7b3b57 100644 --- a/pkgs/webview-ui/app/src/routes/settings/index.tsx +++ b/pkgs/webview-ui/app/src/routes/settings/index.tsx @@ -1,10 +1,4 @@ import { callApi } from "@/src/api"; -import { - createForm, - required, - setValue, - SubmitHandler, -} from "@modular-forms/solid"; import { activeURI, clanList, @@ -12,15 +6,7 @@ import { setClanList, setRoute, } from "@/src/App"; -import { - createEffect, - createSignal, - For, - Match, - Setter, - Show, - Switch, -} from "solid-js"; +import { createSignal, For, Match, Setter, Show, Switch } from "solid-js"; import { createQuery } from "@tanstack/solid-query"; import { useFloating } from "@/src/floating"; import { autoUpdate, flip, hide, offset, shift } from "@floating-ui/dom"; @@ -156,6 +142,9 @@ const ClanDetails = (props: ClanDetailsProps) => {
    {clan_dir}
    + +
    +
    {details.data?.name}