From 8e0036358490009369f5f084f9f5985884595487 Mon Sep 17 00:00:00 2001 From: Qubasa Date: Wed, 2 Jul 2025 15:59:50 +0700 Subject: [PATCH] ui-2d: Fix build errors --- pkgs/clan-app/ui-2d/package.json | 85 +---------- .../clan-app/ui-2d/src/Form/fields/Select.tsx | 4 - pkgs/clan-app/ui-2d/src/api/index.tsx | 68 +++++++-- pkgs/clan-app/ui-2d/src/api_test.tsx | 2 +- .../src/components/Button/Button-Dark.css | 2 +- .../src/components/Button/Button-Ghost.css | 2 +- .../src/components/Button/Button-Light.css | 2 +- .../src/components/RemoteForm.stories.tsx | 6 +- .../ui-2d/src/components/RemoteForm.tsx | 72 +++------- .../ui-2d/src/components/SimpleModal.tsx | 39 +++++ .../ui-2d/src/components/inputBase/index.tsx | 4 +- .../components/machine-list-item/index.tsx | 20 +-- .../ui-2d/src/components/modal/index.tsx | 134 ------------------ pkgs/clan-app/ui-2d/src/index.css | 2 +- pkgs/clan-app/ui-2d/src/routes/flash/view.tsx | 11 +- .../machines/components/InstallMachine.tsx | 1 - .../machines/components/MachineForm.tsx | 9 +- .../routes/machines/install/hardware-step.tsx | 2 +- .../src/routes/machines/install/vars-step.tsx | 4 +- .../src/routes/machines/machines-list.tsx | 2 + pkgs/clan-app/ui/src/api/index.tsx | 114 +++++++++------ 21 files changed, 220 insertions(+), 365 deletions(-) mode change 100644 => 120000 pkgs/clan-app/ui-2d/package.json create mode 100644 pkgs/clan-app/ui-2d/src/components/SimpleModal.tsx delete mode 100644 pkgs/clan-app/ui-2d/src/components/modal/index.tsx diff --git a/pkgs/clan-app/ui-2d/package.json b/pkgs/clan-app/ui-2d/package.json deleted file mode 100644 index d1d91551f..000000000 --- a/pkgs/clan-app/ui-2d/package.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "name": "@clan/ui", - "version": "0.0.1", - "description": "", - "type": "module", - "scripts": { - "start": "vite", - "dev": "vite", - "build": "npm run check && npm run test && vite build && npm run convert-html", - "convert-html": "node gtk.webview.js", - "serve": "vite preview", - "check": "tsc --noEmit --skipLibCheck && eslint ./src --fix", - "knip": "knip --fix", - "test": "vitest run --project unit --typecheck", - "storybook": "storybook", - "storybook-build": "storybook build", - "storybook-dev": "storybook dev -p 6006", - "test-storybook": "vitest run --project storybook", - "test-storybook-update-snapshots": "vitest run --project storybook --update", - "test-storybook-static": "npm run storybook-build && concurrently -k -s first -n 'SB,TEST' -c 'magenta,blue' 'npx http-server storybook-static --port 6006 --silent' 'npx wait-on tcp:127.0.0.1:6006 && npm run test-storybook'" - }, - "license": "MIT", - "devDependencies": { - "@eslint/js": "^9.3.0", - "@kachurun/storybook-solid": "^9.0.11", - "@kachurun/storybook-solid-vite": "^9.0.11", - "@storybook/addon-a11y": "^9.0.8", - "@storybook/addon-docs": "^9.0.8", - "@storybook/addon-links": "^9.0.8", - "@storybook/addon-onboarding": "^9.0.8", - "@storybook/addon-vitest": "^9.0.8", - "@tailwindcss/typography": "^0.5.13", - "@types/json-schema": "^7.0.15", - "@types/node": "^22.15.19", - "@vitest/browser": "^3.2.3", - "autoprefixer": "^10.4.19", - "classnames": "^2.5.1", - "concurrently": "^9.1.2", - "eslint": "^9.27.0", - "eslint-plugin-tailwindcss": "^3.17.0", - "jsdom": "^26.1.0", - "knip": "^5.61.2", - "postcss": "^8.4.38", - "prettier": "^3.2.5", - "solid-devtools": "^0.34.0", - "storybook": "^9.0.8", - "tailwindcss": "^4.0.0", - "typescript": "^5.4.5", - "typescript-eslint": "^8.32.1", - "vite": "^7.0.0", - "vite-plugin-solid": "^2.8.2", - "vite-plugin-solid-svg": "^0.8.1", - "vitest": "^3.2.3" - }, - "dependencies": { - "@floating-ui/dom": "^1.6.8", - "@kobalte/core": "^0.13.10", - "@kobalte/tailwindcss": "^0.9.0", - "@modular-forms/solid": "^0.25.1", - "@solid-primitives/storage": "^4.3.2", - "@solidjs/router": "^0.15.3", - "@tanstack/eslint-plugin-query": "^5.51.12", - "@tanstack/solid-query": "^5.76.0", - "corvu": "^0.7.1", - "nanoid": "^5.0.7", - "solid-js": "^1.9.7", - "solid-toast": "^0.5.0" - }, - "optionalDependencies": { - "@esbuild/darwin-arm64": "^0.25.4", - "@esbuild/darwin-x64": "^0.25.4", - "@esbuild/linux-arm64": "^0.25.4", - "@esbuild/linux-x64": "^0.25.4" - }, - "overrides": { - "vite": { - "rollup": "npm:@rollup/wasm-node@^4.34.9" - }, - "@rollup/rollup-darwin-x64": "npm:@rollup/wasm-node@^4.34.9", - "@rollup/rollup-linux-x64": "npm:@rollup/wasm-node@^4.34.9", - "@rollup/rollup-darwin-arm64": "npm:@rollup/wasm-node@^4.34.9", - "@rollup/rollup-linux-arm64": "npm:@rollup/wasm-node@^4.34.9" - } -} diff --git a/pkgs/clan-app/ui-2d/package.json b/pkgs/clan-app/ui-2d/package.json new file mode 120000 index 000000000..c606d535c --- /dev/null +++ b/pkgs/clan-app/ui-2d/package.json @@ -0,0 +1 @@ +../ui/package.json \ No newline at end of file diff --git a/pkgs/clan-app/ui-2d/src/Form/fields/Select.tsx b/pkgs/clan-app/ui-2d/src/Form/fields/Select.tsx index bc9d33c72..a6a58089c 100644 --- a/pkgs/clan-app/ui-2d/src/Form/fields/Select.tsx +++ b/pkgs/clan-app/ui-2d/src/Form/fields/Select.tsx @@ -19,7 +19,6 @@ import { } from "@/src/components/inputBase"; import { FieldLayout } from "./layout"; import Icon from "@/src/components/icon"; -import { useContext } from "corvu/dialog"; interface Option { value: string; @@ -51,9 +50,6 @@ interface SelectInputpProps { } export function SelectInput(props: SelectInputpProps) { - const dialogContext = (dialogContextId?: string) => - useContext(dialogContextId); - const _id = createUniqueId(); const [reference, setReference] = createSignal(); diff --git a/pkgs/clan-app/ui-2d/src/api/index.tsx b/pkgs/clan-app/ui-2d/src/api/index.tsx index ad429b585..73d9b8612 100644 --- a/pkgs/clan-app/ui-2d/src/api/index.tsx +++ b/pkgs/clan-app/ui-2d/src/api/index.tsx @@ -23,9 +23,37 @@ export type SuccessQuery = Extract< >; export type SuccessData = SuccessQuery["data"]; +function isMachine(obj: unknown): obj is Machine { + return ( + !!obj && + typeof obj === "object" && + typeof (obj as any).name === "string" && + typeof (obj as any).flake === "object" && + typeof (obj as any).flake.identifier === "string" + ); +} + +// Machine type with flake for API calls +interface Machine { + name: string; + flake: { + identifier: string; + }; +} + +interface BackendOpts { + logging?: { group: string | Machine }; +} + +interface BackendReturnType { + result: OperationResponse; + metadata: Record; +} + const _callApi = ( method: K, args: OperationArgs, + backendOpts?: BackendOpts, ): { promise: Promise>; op_key: string } => { // if window[method] does not exist, throw an error if (!(method in window)) { @@ -33,19 +61,30 @@ const _callApi = ( // return a rejected promise return { promise: Promise.resolve({ - status: "error", - errors: [ - { - message: `Method ${method} not found on window object`, - code: "method_not_found", - }, - ], - op_key: "noop", + status: "error", + errors: [ + { + message: `Method ${method} not found on window object`, + code: "method_not_found", + }, + ], + op_key: "noop", }), op_key: "noop", }; } + let metadata: BackendOpts | undefined = undefined; + if (backendOpts != undefined) { + metadata = { ...backendOpts }; + let group = backendOpts?.logging?.group; + if (group != undefined && isMachine(group)) { + metadata = { + logging: { group: group.flake.identifier + "#" + group.name }, + }; + } + } + const promise = ( window as unknown as Record< OperationNames, @@ -105,10 +144,11 @@ const handleCancel = async ( export const callApi = ( method: K, args: OperationArgs, + backendOpts?: BackendOpts, ): { promise: Promise>; op_key: string } => { - console.log("Calling API", method, args); + console.log("Calling API", method, args, backendOpts); - const { promise, op_key } = _callApi(method, args); + const { promise, op_key } = _callApi(method, args, backendOpts); promise.catch((error) => { toast.custom( (t) => ( @@ -146,13 +186,14 @@ export const callApi = ( console.log("Not printing toast because operation was cancelled"); } - if (response.status === "error" && !cancelled) { + const result = response; + if (result.status === "error" && !cancelled) { toast.remove(toastId); toast.custom( (t) => ( ), { @@ -162,7 +203,8 @@ export const callApi = ( } else { toast.remove(toastId); } - return response; + return result; }); + return { promise: new_promise, op_key: op_key }; }; diff --git a/pkgs/clan-app/ui-2d/src/api_test.tsx b/pkgs/clan-app/ui-2d/src/api_test.tsx index 49599f059..76ab8ac65 100644 --- a/pkgs/clan-app/ui-2d/src/api_test.tsx +++ b/pkgs/clan-app/ui-2d/src/api_test.tsx @@ -61,7 +61,7 @@ export const ApiTester = () => { return await callApi( values.endpoint as keyof API, JSON.parse(values.payload || "{}"), - ); + ).promise; }, staleTime: Infinity, enabled: false, diff --git a/pkgs/clan-app/ui-2d/src/components/Button/Button-Dark.css b/pkgs/clan-app/ui-2d/src/components/Button/Button-Dark.css index 07faf3f30..914fc74b9 100644 --- a/pkgs/clan-app/ui-2d/src/components/Button/Button-Dark.css +++ b/pkgs/clan-app/ui-2d/src/components/Button/Button-Dark.css @@ -27,5 +27,5 @@ } .button--dark-active:active { - @apply active:border-secondary-900 active:shadow-button-primary-active; + @apply active:border-secondary-900; } diff --git a/pkgs/clan-app/ui-2d/src/components/Button/Button-Ghost.css b/pkgs/clan-app/ui-2d/src/components/Button/Button-Ghost.css index dcbd77556..ad9881bb7 100644 --- a/pkgs/clan-app/ui-2d/src/components/Button/Button-Ghost.css +++ b/pkgs/clan-app/ui-2d/src/components/Button/Button-Ghost.css @@ -7,5 +7,5 @@ } .button--ghost-active:active { - @apply active:bg-secondary-200 active:text-secondary-900 active:shadow-button-primary-active; + @apply active:bg-secondary-200 active:text-secondary-900; } diff --git a/pkgs/clan-app/ui-2d/src/components/Button/Button-Light.css b/pkgs/clan-app/ui-2d/src/components/Button/Button-Light.css index dde186706..1ec847ac7 100644 --- a/pkgs/clan-app/ui-2d/src/components/Button/Button-Light.css +++ b/pkgs/clan-app/ui-2d/src/components/Button/Button-Light.css @@ -27,7 +27,7 @@ } .button--light-active:active { - @apply active:bg-secondary-200 border-secondary-600 active:text-secondary-900 active:shadow-button-primary-active; + @apply active:bg-secondary-200 border-secondary-600 active:text-secondary-900; box-shadow: inset 2px 2px theme(backgroundColor.secondary.300); diff --git a/pkgs/clan-app/ui-2d/src/components/RemoteForm.stories.tsx b/pkgs/clan-app/ui-2d/src/components/RemoteForm.stories.tsx index 9df499beb..7c6086de4 100644 --- a/pkgs/clan-app/ui-2d/src/components/RemoteForm.stories.tsx +++ b/pkgs/clan-app/ui-2d/src/components/RemoteForm.stories.tsx @@ -17,7 +17,7 @@ const defaultRemoteData: RemoteData = { private_key: undefined, password: "", forward_agent: true, - host_key_check: 0, + host_key_check: "strict", verbose_ssh: false, ssh_options: {}, tor_socks: false, @@ -32,7 +32,7 @@ const sampleRemoteData: RemoteData = { private_key: undefined, password: "", forward_agent: true, - host_key_check: 1, + host_key_check: "ask", verbose_ssh: false, ssh_options: { StrictHostKeyChecking: "no", @@ -238,7 +238,7 @@ const advancedRemoteData: RemoteData = { private_key: undefined, password: "", forward_agent: false, - host_key_check: 2, + host_key_check: "none", verbose_ssh: true, ssh_options: { ConnectTimeout: "10", diff --git a/pkgs/clan-app/ui-2d/src/components/RemoteForm.tsx b/pkgs/clan-app/ui-2d/src/components/RemoteForm.tsx index 4a751f478..fb54b1a69 100644 --- a/pkgs/clan-app/ui-2d/src/components/RemoteForm.tsx +++ b/pkgs/clan-app/ui-2d/src/components/RemoteForm.tsx @@ -11,13 +11,6 @@ import { Loader } from "@/src/components/v2/Loader/Loader"; import { Button } from "@/src/components/v2/Button/Button"; import Accordion from "@/src/components/accordion"; -// Define the HostKeyCheck enum values with proper API mapping -export enum HostKeyCheck { - ASK = 0, - TOFU = 1, - IGNORE = 2, -} - // Export the API types for use in other components export type { RemoteData, Machine, RemoteDataSource }; @@ -185,40 +178,6 @@ export function RemoteForm(props: RemoteFormProps) { const [formData, setFormData] = createSignal(null); const [isSaving, setIsSaving] = createSignal(false); - const hostKeyCheckOptions = [ - { value: "ASK", label: "Ask" }, - { value: "TOFU", label: "TOFU (Trust On First Use)" }, - { value: "IGNORE", label: "Ignore" }, - ]; - - // Helper function to convert enum name to numeric value - const getHostKeyCheckValue = (name: string): number => { - switch (name) { - case "ASK": - return HostKeyCheck.ASK; - case "TOFU": - return HostKeyCheck.TOFU; - case "IGNORE": - return HostKeyCheck.IGNORE; - default: - return HostKeyCheck.ASK; - } - }; - - // Helper function to convert numeric value to enum name - const getHostKeyCheckName = (value: number | undefined): string => { - switch (value) { - case HostKeyCheck.ASK: - return "ASK"; - case HostKeyCheck.TOFU: - return "TOFU"; - case HostKeyCheck.IGNORE: - return "IGNORE"; - default: - return "ASK"; - } - }; - // Query host data when machine is provided const hostQuery = useQuery(() => ({ queryKey: [ @@ -241,11 +200,15 @@ export function RemoteForm(props: RemoteFormProps) { }); } - const result = await callApi("get_host", { - name: props.machine.name, - flake: props.machine.flake, - field: props.field || "targetHost", - }).promise; + const result = await callApi( + "get_host", + { + name: props.machine.name, + flake: props.machine.flake, + field: props.field || "targetHost", + }, + {logging: { group: { name: props.machine.name, flake: props.machine.flake } },}, + ).promise; if (result.status === "error") throw new Error("Failed to fetch host data"); @@ -372,16 +335,13 @@ export function RemoteForm(props: RemoteFormProps) { - updateFormData({ - host_key_check: getHostKeyCheckValue( - e.currentTarget.value, - ) as 0 | 1 | 2 | 3, - }), - }} + value={formData()?.host_key_check || "ask"} + options={[ + { value: "ask", label: "Ask" }, + { value: "none", label: "None" }, + { value: "strict", label: "Strict" }, + { value: "tofu", label: "Trust on First Use" }, + ]} disabled={computedDisabled} helperText="How to handle host key verification" /> diff --git a/pkgs/clan-app/ui-2d/src/components/SimpleModal.tsx b/pkgs/clan-app/ui-2d/src/components/SimpleModal.tsx new file mode 100644 index 000000000..eb0395d43 --- /dev/null +++ b/pkgs/clan-app/ui-2d/src/components/SimpleModal.tsx @@ -0,0 +1,39 @@ +import { JSX, Show } from "solid-js"; + +interface SimpleModalProps { + open: boolean; + onClose: () => void; + title?: string; + children: JSX.Element; +} + +export const SimpleModal = (props: SimpleModalProps) => { + return ( + +
+ {/* Backdrop */} +
+ + {/* Modal Content */} +
+ {/* Header */} + +
+

{props.title}

+ +
+
+ + {/* Body */} +
{props.children}
+
+
+ + ); +}; diff --git a/pkgs/clan-app/ui-2d/src/components/inputBase/index.tsx b/pkgs/clan-app/ui-2d/src/components/inputBase/index.tsx index b0c1d50fe..cce3e5435 100644 --- a/pkgs/clan-app/ui-2d/src/components/inputBase/index.tsx +++ b/pkgs/clan-app/ui-2d/src/components/inputBase/index.tsx @@ -125,7 +125,7 @@ export const InputLabel = (props: InputLabelProps) => { weight="bold" class="inline-flex gap-1 align-middle !fg-def-1" classList={{ - [cx("!fg-semantic-1")]: !!props.error, + [cx("!text-red-600")]: !!props.error, }} aria-invalid={props.error} > @@ -185,7 +185,7 @@ export const InputError = (props: InputErrorProps) => { // @ts-expect-error: Dependent type is to complex to check how it is coupled to the override for now size="xxs" weight="medium" - class={cx("col-span-full px-1 !fg-semantic-4", typoClasses)} + class={cx("col-span-full px-1 !text-red-500", typoClasses)} {...rest} > {props.error} diff --git a/pkgs/clan-app/ui-2d/src/components/machine-list-item/index.tsx b/pkgs/clan-app/ui-2d/src/components/machine-list-item/index.tsx index b0881bf79..221ff81a6 100644 --- a/pkgs/clan-app/ui-2d/src/components/machine-list-item/index.tsx +++ b/pkgs/clan-app/ui-2d/src/components/machine-list-item/index.tsx @@ -47,11 +47,14 @@ export const MachineListItem = (props: MachineListItemProps) => { ); return; } - const target_host = await callApi("get_host", { - field: "targetHost", - flake: { identifier: active_clan }, - name: name, - }).promise; + const target_host = await callApi( + "get_host", + { + field: "targetHost", + flake: { identifier: active_clan }, + name: name, + }, {logging: { group: { name, flake: { identifier: active_clan } } }} + ).promise; if (target_host.status == "error") { console.error("No target host found for the machine"); @@ -79,7 +82,6 @@ export const MachineListItem = (props: MachineListItemProps) => { }, no_reboot: true, debug: true, - nix_options: [], password: null, }, target_host: target_host.data!.data, @@ -108,6 +110,8 @@ export const MachineListItem = (props: MachineListItemProps) => { field: "targetHost", flake: { identifier: active_clan }, name: name, + }, { + logging: { group: { name, flake: { identifier: active_clan } } }, }).promise; if (target_host.status == "error") { @@ -129,7 +133,7 @@ export const MachineListItem = (props: MachineListItemProps) => { field: "buildHost", flake: { identifier: active_clan }, name: name, - }).promise; + }, {logging: { group: { name, flake: { identifier: active_clan } } }}).promise; if (build_host.status == "error") { console.error("No target host found for the machine"); @@ -150,7 +154,7 @@ export const MachineListItem = (props: MachineListItemProps) => { }, target_host: target_host.data!.data, build_host: build_host.data?.data || null, - }).promise; + }, {logging: { group: { name, flake: { identifier: active_clan } } }}).promise; setUpdating(false); }; diff --git a/pkgs/clan-app/ui-2d/src/components/modal/index.tsx b/pkgs/clan-app/ui-2d/src/components/modal/index.tsx deleted file mode 100644 index b568acf9f..000000000 --- a/pkgs/clan-app/ui-2d/src/components/modal/index.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import Dialog from "corvu/dialog"; -import { createSignal, JSX } from "solid-js"; -import { Button } from "../Button/Button"; -import Icon from "../icon"; -import cx from "classnames"; - -interface ModalProps { - open: boolean | undefined; - handleClose: () => void; - title: string; - children: JSX.Element; - class?: string; -} -export const Modal = (props: ModalProps) => { - const [dragging, setDragging] = createSignal(false); - const [startOffset, setStartOffset] = createSignal({ x: 0, y: 0 }); - - let dialogRef: HTMLDivElement; - - const handleMouseDown = (e: MouseEvent) => { - setDragging(true); - const rect = dialogRef.getBoundingClientRect(); - setStartOffset({ - x: e.clientX - rect.left, - y: e.clientY - rect.top, - }); - }; - - const handleMouseMove = (e: MouseEvent) => { - if (dragging()) { - let newTop = e.clientY - startOffset().y; - let newLeft = e.clientX - startOffset().x; - - if (newTop < 0) { - newTop = 0; - } - if (newLeft < 0) { - newLeft = 0; - } - dialogRef.style.top = `${newTop}px`; - dialogRef.style.left = `${newLeft}px`; - - // dialogRef.style.maxHeight = `calc(100vh - ${newTop}px - 2rem)`; - // dialogRef.style.maxHeight = `calc(100vh - ${newTop}px - 2rem)`; - } - }; - - const handleMouseUp = () => setDragging(false); - - return ( - - - - - { - dialogRef = el; - }} - onMouseMove={handleMouseMove} - onMouseUp={handleMouseUp} - onMouseDown={(e: MouseEvent) => { - e.stopPropagation(); // Prevent backdrop drag conflict - }} - onClick={(e: MouseEvent) => e.stopPropagation()} // Prevent backdrop click closing - > - -
-
-
-
-
-
-
-
- - {props.title} - -
-
-
-
-
-
-
-
- -
-
-
- - {props.children} - -
-
-
- ); -}; diff --git a/pkgs/clan-app/ui-2d/src/index.css b/pkgs/clan-app/ui-2d/src/index.css index e645ad29d..909b198bf 100644 --- a/pkgs/clan-app/ui-2d/src/index.css +++ b/pkgs/clan-app/ui-2d/src/index.css @@ -1,4 +1,4 @@ -@import "material-icons/iconfont/filled.css"; +/* @import "material-icons/iconfont/filled.css"; */ /* List of icons: https://marella.me/material-icons/demo/ */ /* @import url(./components/Typography/css/typography.css); */ diff --git a/pkgs/clan-app/ui-2d/src/routes/flash/view.tsx b/pkgs/clan-app/ui-2d/src/routes/flash/view.tsx index ec91d3d51..c5d0a645d 100644 --- a/pkgs/clan-app/ui-2d/src/routes/flash/view.tsx +++ b/pkgs/clan-app/ui-2d/src/routes/flash/view.tsx @@ -19,10 +19,12 @@ import { createEffect, createSignal } from "solid-js"; // For, Show might not be 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"; // Still used for other fieldsets import Accordion from "@/src/components/accordion"; +import { SimpleModal } from "@/src/components/SimpleModal"; + // Import the new generic component import { FileSelectorField, @@ -192,12 +194,11 @@ export const Flash = () => { return ( <>
- !isFlashing() && setConfirmOpen(false)} + onClose={() => !isFlashing() && setConfirmOpen(false)} title="Confirm" > - {/* ... Modal content as before ... */}
{
-
+
{ + }, {logging: { group: { name: machine, flake: { identifier: curr_uri } } }}).promise.finally(() => { setIsUpdating(false); }); }; diff --git a/pkgs/clan-app/ui-2d/src/routes/machines/install/hardware-step.tsx b/pkgs/clan-app/ui-2d/src/routes/machines/install/hardware-step.tsx index c22691ac3..291937e21 100644 --- a/pkgs/clan-app/ui-2d/src/routes/machines/install/hardware-step.tsx +++ b/pkgs/clan-app/ui-2d/src/routes/machines/install/hardware-step.tsx @@ -44,7 +44,7 @@ export const HWStep = (props: StepProps) => { command_prefix: "sudo", port: 22, forward_agent: false, - host_key_check: 1, // 0 = ASK + host_key_check: "ask", // 0 = ASK verbose_ssh: false, ssh_options: {}, tor_socks: false, diff --git a/pkgs/clan-app/ui-2d/src/routes/machines/install/vars-step.tsx b/pkgs/clan-app/ui-2d/src/routes/machines/install/vars-step.tsx index 6045bfb78..0b64af9e0 100644 --- a/pkgs/clan-app/ui-2d/src/routes/machines/install/vars-step.tsx +++ b/pkgs/clan-app/ui-2d/src/routes/machines/install/vars-step.tsx @@ -153,10 +153,10 @@ export const VarsStep = (props: VarsStepProps) => { base_dir: props.dir, machine_name: props.machine_id, full_closure: props.fullClosure, - }).promise; + }, {logging: {group: { name: props.machine_id, flake: {identifier: props.dir} }}}).promise; if (result.status === "error") throw new Error("Failed to fetch data"); return result.data; - }, + }, })); const handleSubmit: SubmitHandler = async (values, event) => { diff --git a/pkgs/clan-app/ui-2d/src/routes/machines/machines-list.tsx b/pkgs/clan-app/ui-2d/src/routes/machines/machines-list.tsx index 88919659c..fd043494d 100644 --- a/pkgs/clan-app/ui-2d/src/routes/machines/machines-list.tsx +++ b/pkgs/clan-app/ui-2d/src/routes/machines/machines-list.tsx @@ -8,6 +8,7 @@ import Icon from "@/src/components/icon"; import { Header } from "@/src/layout/header"; import { makePersisted } from "@solid-primitives/storage"; import { useClanContext } from "@/src/contexts/clan"; +import { debug } from "console"; type MachinesModel = Extract< OperationResponse<"list_machines">, @@ -38,6 +39,7 @@ export const MachineListView: Component = () => { }, }).promise; console.log("response", response); + if (response.status === "error") { console.error("Failed to fetch data"); } else { diff --git a/pkgs/clan-app/ui/src/api/index.tsx b/pkgs/clan-app/ui/src/api/index.tsx index 59139290d..bbc40bd16 100644 --- a/pkgs/clan-app/ui/src/api/index.tsx +++ b/pkgs/clan-app/ui/src/api/index.tsx @@ -1,26 +1,19 @@ -import { API, Error as ApiError } from "@/api/API"; +import { API } from "@/api/API"; import { Schema as Inventory } from "@/api/Inventory"; import { toast } from "solid-toast"; import { ErrorToastComponent, CancelToastComponent, } from "@/src/components/toast"; + type OperationNames = keyof API; -type OperationArgs = API[T]["arguments"]; -export type OperationResponse = API[T]["return"]; - -type ApiEnvelope = - | { - status: "success"; - data: T; - op_key: string; - } - | ApiError; - type Services = NonNullable; type ServiceNames = keyof Services; -type ClanService = Services[T]; -type ClanServiceInstance = NonNullable< + +export type OperationArgs = API[T]["arguments"]; +export type OperationResponse = API[T]["return"]; + +export type ClanServiceInstance = NonNullable< Services[T] >[string]; @@ -28,51 +21,83 @@ export type SuccessQuery = Extract< OperationResponse, { status: "success" } >; -type SuccessData = SuccessQuery["data"]; +export type SuccessData = SuccessQuery["data"]; -type ErrorQuery = Extract< - OperationResponse, - { status: "error" } ->; -type ErrorData = ErrorQuery["errors"]; - -type ClanOperations = Record void>; - -interface GtkResponse { - result: T; - op_key: string; +function isMachine(obj: unknown): obj is Machine { + return ( + !!obj && + typeof obj === "object" && + typeof (obj as Machine).name === "string" && + typeof (obj as Machine).flake === "object" && + typeof (obj as Machine).flake.identifier === "string" + ); } + +// Machine type with flake for API calls +interface Machine { + name: string; + flake: { + identifier: string; + }; +} + +interface BackendOpts { + logging?: { group: string | Machine }; +} + +interface BackendReturnType { + result: OperationResponse; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + metadata: Record; +} + const _callApi = ( method: K, args: OperationArgs, -): { promise: Promise>; op_key: string } => { + backendOpts?: BackendOpts, +): { promise: Promise>; op_key: string } => { // if window[method] does not exist, throw an error if (!(method in window)) { console.error(`Method ${method} not found on window object`); // return a rejected promise return { promise: Promise.resolve({ - status: "error", - errors: [ - { - message: `Method ${method} not found on window object`, - code: "method_not_found", - }, - ], - op_key: "noop", + result: { + status: "error", + errors: [ + { + message: `Method ${method} not found on window object`, + code: "method_not_found", + }, + ], + op_key: "noop", + }, + metadata: {}, }), op_key: "noop", }; } + let metadata: BackendOpts | undefined = undefined; + if (backendOpts != undefined) { + metadata = { ...backendOpts }; + const group = backendOpts?.logging?.group; + if (group != undefined && isMachine(group)) { + metadata = { + logging: { group: group.flake.identifier + "#" + group.name }, + }; + } + } + const promise = ( window as unknown as Record< OperationNames, ( args: OperationArgs, - ) => Promise> + metadata?: BackendOpts, + ) => Promise> > - )[method](args) as Promise>; + )[method](args, metadata) as Promise>; // eslint-disable-next-line @typescript-eslint/no-explicit-any const op_key = (promise as any)._webviewMessageId as string; @@ -82,7 +107,7 @@ const _callApi = ( const handleCancel = async ( ops_key: string, - orig_task: Promise>, + orig_task: Promise>, ) => { console.log("Canceling operation: ", ops_key); const { promise, op_key } = _callApi("cancel_task", { task_id: ops_key }); @@ -102,7 +127,7 @@ const handleCancel = async ( }); const resp = await promise; - if (resp.status === "error") { + if (resp.result.status === "error") { toast.custom( (t) => ( ( export const callApi = ( method: K, args: OperationArgs, + backendOpts?: BackendOpts, ): { promise: Promise>; op_key: string } => { console.log("Calling API", method, args); - const { promise, op_key } = _callApi(method, args); + const { promise, op_key } = _callApi(method, args, backendOpts); promise.catch((error) => { toast.custom( (t) => ( @@ -165,13 +191,14 @@ export const callApi = ( console.log("Not printing toast because operation was cancelled"); } - if (response.status === "error" && !cancelled) { + const result = response.result; + if (result.status === "error" && !cancelled) { toast.remove(toastId); toast.custom( (t) => ( ), { @@ -181,7 +208,8 @@ export const callApi = ( } else { toast.remove(toastId); } - return response; + return result; }); + return { promise: new_promise, op_key: op_key }; };