ui-2d: Fix build errors
This commit is contained in:
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1
pkgs/clan-app/ui-2d/package.json
Symbolic link
1
pkgs/clan-app/ui-2d/package.json
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../ui/package.json
|
||||||
@@ -19,7 +19,6 @@ import {
|
|||||||
} from "@/src/components/inputBase";
|
} from "@/src/components/inputBase";
|
||||||
import { FieldLayout } from "./layout";
|
import { FieldLayout } from "./layout";
|
||||||
import Icon from "@/src/components/icon";
|
import Icon from "@/src/components/icon";
|
||||||
import { useContext } from "corvu/dialog";
|
|
||||||
|
|
||||||
interface Option {
|
interface Option {
|
||||||
value: string;
|
value: string;
|
||||||
@@ -51,9 +50,6 @@ interface SelectInputpProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function SelectInput(props: SelectInputpProps) {
|
export function SelectInput(props: SelectInputpProps) {
|
||||||
const dialogContext = (dialogContextId?: string) =>
|
|
||||||
useContext(dialogContextId);
|
|
||||||
|
|
||||||
const _id = createUniqueId();
|
const _id = createUniqueId();
|
||||||
|
|
||||||
const [reference, setReference] = createSignal<HTMLElement>();
|
const [reference, setReference] = createSignal<HTMLElement>();
|
||||||
|
|||||||
@@ -23,9 +23,37 @@ export type SuccessQuery<T extends OperationNames> = Extract<
|
|||||||
>;
|
>;
|
||||||
export type SuccessData<T extends OperationNames> = SuccessQuery<T>["data"];
|
export type SuccessData<T extends OperationNames> = SuccessQuery<T>["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<K extends OperationNames> {
|
||||||
|
result: OperationResponse<K>;
|
||||||
|
metadata: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
const _callApi = <K extends OperationNames>(
|
const _callApi = <K extends OperationNames>(
|
||||||
method: K,
|
method: K,
|
||||||
args: OperationArgs<K>,
|
args: OperationArgs<K>,
|
||||||
|
backendOpts?: BackendOpts,
|
||||||
): { promise: Promise<OperationResponse<K>>; op_key: string } => {
|
): { promise: Promise<OperationResponse<K>>; op_key: string } => {
|
||||||
// if window[method] does not exist, throw an error
|
// if window[method] does not exist, throw an error
|
||||||
if (!(method in window)) {
|
if (!(method in window)) {
|
||||||
@@ -33,19 +61,30 @@ const _callApi = <K extends OperationNames>(
|
|||||||
// return a rejected promise
|
// return a rejected promise
|
||||||
return {
|
return {
|
||||||
promise: Promise.resolve({
|
promise: Promise.resolve({
|
||||||
status: "error",
|
status: "error",
|
||||||
errors: [
|
errors: [
|
||||||
{
|
{
|
||||||
message: `Method ${method} not found on window object`,
|
message: `Method ${method} not found on window object`,
|
||||||
code: "method_not_found",
|
code: "method_not_found",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
op_key: "noop",
|
op_key: "noop",
|
||||||
}),
|
}),
|
||||||
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 = (
|
const promise = (
|
||||||
window as unknown as Record<
|
window as unknown as Record<
|
||||||
OperationNames,
|
OperationNames,
|
||||||
@@ -105,10 +144,11 @@ const handleCancel = async <K extends OperationNames>(
|
|||||||
export const callApi = <K extends OperationNames>(
|
export const callApi = <K extends OperationNames>(
|
||||||
method: K,
|
method: K,
|
||||||
args: OperationArgs<K>,
|
args: OperationArgs<K>,
|
||||||
|
backendOpts?: BackendOpts,
|
||||||
): { promise: Promise<OperationResponse<K>>; op_key: string } => {
|
): { promise: Promise<OperationResponse<K>>; 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) => {
|
promise.catch((error) => {
|
||||||
toast.custom(
|
toast.custom(
|
||||||
(t) => (
|
(t) => (
|
||||||
@@ -146,13 +186,14 @@ export const callApi = <K extends OperationNames>(
|
|||||||
console.log("Not printing toast because operation was cancelled");
|
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.remove(toastId);
|
||||||
toast.custom(
|
toast.custom(
|
||||||
(t) => (
|
(t) => (
|
||||||
<ErrorToastComponent
|
<ErrorToastComponent
|
||||||
t={t}
|
t={t}
|
||||||
message={"Error: " + response.errors[0].message}
|
message={"Error: " + result.errors[0].message}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
@@ -162,7 +203,8 @@ export const callApi = <K extends OperationNames>(
|
|||||||
} else {
|
} else {
|
||||||
toast.remove(toastId);
|
toast.remove(toastId);
|
||||||
}
|
}
|
||||||
return response;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
return { promise: new_promise, op_key: op_key };
|
return { promise: new_promise, op_key: op_key };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export const ApiTester = () => {
|
|||||||
return await callApi(
|
return await callApi(
|
||||||
values.endpoint as keyof API,
|
values.endpoint as keyof API,
|
||||||
JSON.parse(values.payload || "{}"),
|
JSON.parse(values.payload || "{}"),
|
||||||
);
|
).promise;
|
||||||
},
|
},
|
||||||
staleTime: Infinity,
|
staleTime: Infinity,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
|||||||
@@ -27,5 +27,5 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.button--dark-active:active {
|
.button--dark-active:active {
|
||||||
@apply active:border-secondary-900 active:shadow-button-primary-active;
|
@apply active:border-secondary-900;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,5 +7,5 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.button--ghost-active:active {
|
.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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.button--light-active:active {
|
.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);
|
box-shadow: inset 2px 2px theme(backgroundColor.secondary.300);
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const defaultRemoteData: RemoteData = {
|
|||||||
private_key: undefined,
|
private_key: undefined,
|
||||||
password: "",
|
password: "",
|
||||||
forward_agent: true,
|
forward_agent: true,
|
||||||
host_key_check: 0,
|
host_key_check: "strict",
|
||||||
verbose_ssh: false,
|
verbose_ssh: false,
|
||||||
ssh_options: {},
|
ssh_options: {},
|
||||||
tor_socks: false,
|
tor_socks: false,
|
||||||
@@ -32,7 +32,7 @@ const sampleRemoteData: RemoteData = {
|
|||||||
private_key: undefined,
|
private_key: undefined,
|
||||||
password: "",
|
password: "",
|
||||||
forward_agent: true,
|
forward_agent: true,
|
||||||
host_key_check: 1,
|
host_key_check: "ask",
|
||||||
verbose_ssh: false,
|
verbose_ssh: false,
|
||||||
ssh_options: {
|
ssh_options: {
|
||||||
StrictHostKeyChecking: "no",
|
StrictHostKeyChecking: "no",
|
||||||
@@ -238,7 +238,7 @@ const advancedRemoteData: RemoteData = {
|
|||||||
private_key: undefined,
|
private_key: undefined,
|
||||||
password: "",
|
password: "",
|
||||||
forward_agent: false,
|
forward_agent: false,
|
||||||
host_key_check: 2,
|
host_key_check: "none",
|
||||||
verbose_ssh: true,
|
verbose_ssh: true,
|
||||||
ssh_options: {
|
ssh_options: {
|
||||||
ConnectTimeout: "10",
|
ConnectTimeout: "10",
|
||||||
|
|||||||
@@ -11,13 +11,6 @@ import { Loader } from "@/src/components/v2/Loader/Loader";
|
|||||||
import { Button } from "@/src/components/v2/Button/Button";
|
import { Button } from "@/src/components/v2/Button/Button";
|
||||||
import Accordion from "@/src/components/accordion";
|
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 the API types for use in other components
|
||||||
export type { RemoteData, Machine, RemoteDataSource };
|
export type { RemoteData, Machine, RemoteDataSource };
|
||||||
|
|
||||||
@@ -185,40 +178,6 @@ export function RemoteForm(props: RemoteFormProps) {
|
|||||||
const [formData, setFormData] = createSignal<RemoteData | null>(null);
|
const [formData, setFormData] = createSignal<RemoteData | null>(null);
|
||||||
const [isSaving, setIsSaving] = createSignal(false);
|
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
|
// Query host data when machine is provided
|
||||||
const hostQuery = useQuery(() => ({
|
const hostQuery = useQuery(() => ({
|
||||||
queryKey: [
|
queryKey: [
|
||||||
@@ -241,11 +200,15 @@ export function RemoteForm(props: RemoteFormProps) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await callApi("get_host", {
|
const result = await callApi(
|
||||||
name: props.machine.name,
|
"get_host",
|
||||||
flake: props.machine.flake,
|
{
|
||||||
field: props.field || "targetHost",
|
name: props.machine.name,
|
||||||
}).promise;
|
flake: props.machine.flake,
|
||||||
|
field: props.field || "targetHost",
|
||||||
|
},
|
||||||
|
{logging: { group: { name: props.machine.name, flake: props.machine.flake } },},
|
||||||
|
).promise;
|
||||||
|
|
||||||
if (result.status === "error")
|
if (result.status === "error")
|
||||||
throw new Error("Failed to fetch host data");
|
throw new Error("Failed to fetch host data");
|
||||||
@@ -372,16 +335,13 @@ export function RemoteForm(props: RemoteFormProps) {
|
|||||||
|
|
||||||
<SelectInput
|
<SelectInput
|
||||||
label="Host Key Check"
|
label="Host Key Check"
|
||||||
value={getHostKeyCheckName(formData()?.host_key_check)}
|
value={formData()?.host_key_check || "ask"}
|
||||||
options={hostKeyCheckOptions}
|
options={[
|
||||||
selectProps={{
|
{ value: "ask", label: "Ask" },
|
||||||
onInput: (e) =>
|
{ value: "none", label: "None" },
|
||||||
updateFormData({
|
{ value: "strict", label: "Strict" },
|
||||||
host_key_check: getHostKeyCheckValue(
|
{ value: "tofu", label: "Trust on First Use" },
|
||||||
e.currentTarget.value,
|
]}
|
||||||
) as 0 | 1 | 2 | 3,
|
|
||||||
}),
|
|
||||||
}}
|
|
||||||
disabled={computedDisabled}
|
disabled={computedDisabled}
|
||||||
helperText="How to handle host key verification"
|
helperText="How to handle host key verification"
|
||||||
/>
|
/>
|
||||||
|
|||||||
39
pkgs/clan-app/ui-2d/src/components/SimpleModal.tsx
Normal file
39
pkgs/clan-app/ui-2d/src/components/SimpleModal.tsx
Normal file
@@ -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 (
|
||||||
|
<Show when={props.open}>
|
||||||
|
<div class="fixed inset-0 z-50 flex items-center justify-center">
|
||||||
|
{/* Backdrop */}
|
||||||
|
<div class="fixed inset-0 bg-black/50" onClick={props.onClose} />
|
||||||
|
|
||||||
|
{/* Modal Content */}
|
||||||
|
<div class="relative mx-4 w-full max-w-md rounded-lg bg-white shadow-lg">
|
||||||
|
{/* Header */}
|
||||||
|
<Show when={props.title}>
|
||||||
|
<div class="flex items-center justify-between border-b p-4">
|
||||||
|
<h3 class="text-lg font-semibold">{props.title}</h3>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="text-gray-400 hover:text-gray-600"
|
||||||
|
onClick={props.onClose}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
{/* Body */}
|
||||||
|
<div>{props.children}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -125,7 +125,7 @@ export const InputLabel = (props: InputLabelProps) => {
|
|||||||
weight="bold"
|
weight="bold"
|
||||||
class="inline-flex gap-1 align-middle !fg-def-1"
|
class="inline-flex gap-1 align-middle !fg-def-1"
|
||||||
classList={{
|
classList={{
|
||||||
[cx("!fg-semantic-1")]: !!props.error,
|
[cx("!text-red-600")]: !!props.error,
|
||||||
}}
|
}}
|
||||||
aria-invalid={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
|
// @ts-expect-error: Dependent type is to complex to check how it is coupled to the override for now
|
||||||
size="xxs"
|
size="xxs"
|
||||||
weight="medium"
|
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}
|
{...rest}
|
||||||
>
|
>
|
||||||
{props.error}
|
{props.error}
|
||||||
|
|||||||
@@ -47,11 +47,14 @@ export const MachineListItem = (props: MachineListItemProps) => {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const target_host = await callApi("get_host", {
|
const target_host = await callApi(
|
||||||
field: "targetHost",
|
"get_host",
|
||||||
flake: { identifier: active_clan },
|
{
|
||||||
name: name,
|
field: "targetHost",
|
||||||
}).promise;
|
flake: { identifier: active_clan },
|
||||||
|
name: name,
|
||||||
|
}, {logging: { group: { name, flake: { identifier: active_clan } } }}
|
||||||
|
).promise;
|
||||||
|
|
||||||
if (target_host.status == "error") {
|
if (target_host.status == "error") {
|
||||||
console.error("No target host found for the machine");
|
console.error("No target host found for the machine");
|
||||||
@@ -79,7 +82,6 @@ export const MachineListItem = (props: MachineListItemProps) => {
|
|||||||
},
|
},
|
||||||
no_reboot: true,
|
no_reboot: true,
|
||||||
debug: true,
|
debug: true,
|
||||||
nix_options: [],
|
|
||||||
password: null,
|
password: null,
|
||||||
},
|
},
|
||||||
target_host: target_host.data!.data,
|
target_host: target_host.data!.data,
|
||||||
@@ -108,6 +110,8 @@ export const MachineListItem = (props: MachineListItemProps) => {
|
|||||||
field: "targetHost",
|
field: "targetHost",
|
||||||
flake: { identifier: active_clan },
|
flake: { identifier: active_clan },
|
||||||
name: name,
|
name: name,
|
||||||
|
}, {
|
||||||
|
logging: { group: { name, flake: { identifier: active_clan } } },
|
||||||
}).promise;
|
}).promise;
|
||||||
|
|
||||||
if (target_host.status == "error") {
|
if (target_host.status == "error") {
|
||||||
@@ -129,7 +133,7 @@ export const MachineListItem = (props: MachineListItemProps) => {
|
|||||||
field: "buildHost",
|
field: "buildHost",
|
||||||
flake: { identifier: active_clan },
|
flake: { identifier: active_clan },
|
||||||
name: name,
|
name: name,
|
||||||
}).promise;
|
}, {logging: { group: { name, flake: { identifier: active_clan } } }}).promise;
|
||||||
|
|
||||||
if (build_host.status == "error") {
|
if (build_host.status == "error") {
|
||||||
console.error("No target host found for the machine");
|
console.error("No target host found for the machine");
|
||||||
@@ -150,7 +154,7 @@ export const MachineListItem = (props: MachineListItemProps) => {
|
|||||||
},
|
},
|
||||||
target_host: target_host.data!.data,
|
target_host: target_host.data!.data,
|
||||||
build_host: build_host.data?.data || null,
|
build_host: build_host.data?.data || null,
|
||||||
}).promise;
|
}, {logging: { group: { name, flake: { identifier: active_clan } } }}).promise;
|
||||||
|
|
||||||
setUpdating(false);
|
setUpdating(false);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 (
|
|
||||||
<Dialog open={props.open} trapFocus={true}>
|
|
||||||
<Dialog.Portal>
|
|
||||||
<Dialog.Overlay
|
|
||||||
class="fixed inset-0 z-50 bg-black/50"
|
|
||||||
onMouseMove={handleMouseMove}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Dialog.Content
|
|
||||||
class={cx(
|
|
||||||
"overflow-hidden absolute left-1/3 top-1/3 z-50 min-w-[560px] rounded-md border border-def-4 focus-visible:outline-none",
|
|
||||||
props.class,
|
|
||||||
)}
|
|
||||||
classList={{
|
|
||||||
"!cursor-grabbing": dragging(),
|
|
||||||
[cx("scale-[101%] transition-transform")]: dragging(),
|
|
||||||
}}
|
|
||||||
ref={(el) => {
|
|
||||||
dialogRef = el;
|
|
||||||
}}
|
|
||||||
onMouseMove={handleMouseMove}
|
|
||||||
onMouseUp={handleMouseUp}
|
|
||||||
onMouseDown={(e: MouseEvent) => {
|
|
||||||
e.stopPropagation(); // Prevent backdrop drag conflict
|
|
||||||
}}
|
|
||||||
onClick={(e: MouseEvent) => e.stopPropagation()} // Prevent backdrop click closing
|
|
||||||
>
|
|
||||||
<Dialog.Label
|
|
||||||
as="div"
|
|
||||||
class="flex w-full justify-center border-b-2 px-4 py-2 align-middle bg-def-3 border-def-4"
|
|
||||||
onMouseDown={handleMouseDown}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex w-full cursor-move flex-col gap-px py-1 "
|
|
||||||
classList={{
|
|
||||||
"!cursor-grabbing": dragging(),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<hr class="h-px w-full border-none bg-secondary-300" />
|
|
||||||
<hr class="h-px w-full border-none bg-secondary-300" />
|
|
||||||
<hr class="h-px w-full border-none bg-secondary-300" />
|
|
||||||
<hr class="h-px w-full border-none bg-secondary-300" />
|
|
||||||
<hr class="h-px w-full border-none bg-secondary-300" />
|
|
||||||
<hr class="h-px w-full border-none bg-secondary-300" />
|
|
||||||
</div>
|
|
||||||
<span class="mx-2 select-none whitespace-nowrap">
|
|
||||||
{props.title}
|
|
||||||
</span>
|
|
||||||
<div
|
|
||||||
class="flex w-full cursor-move flex-col gap-px py-1 "
|
|
||||||
classList={{
|
|
||||||
"!cursor-grabbing": dragging(),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<hr class="h-px w-full border-none bg-secondary-300" />
|
|
||||||
<hr class="h-px w-full border-none bg-secondary-300" />
|
|
||||||
<hr class="h-px w-full border-none bg-secondary-300" />
|
|
||||||
<hr class="h-px w-full border-none bg-secondary-300" />
|
|
||||||
<hr class="h-px w-full border-none bg-secondary-300" />
|
|
||||||
<hr class="h-px w-full border-none bg-secondary-300" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="absolute right-1 top-2 pl-1 bg-def-3">
|
|
||||||
<Button
|
|
||||||
onMouseDown={(e) => e.stopPropagation()}
|
|
||||||
tabIndex={-1}
|
|
||||||
class="size-4"
|
|
||||||
variant="ghost"
|
|
||||||
onClick={() => props.handleClose()}
|
|
||||||
size="s"
|
|
||||||
startIcon={<Icon icon={"Close"} />}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Dialog.Label>
|
|
||||||
<Dialog.Description
|
|
||||||
class="flex max-h-[90vh] flex-col overflow-y-hidden bg-def-1"
|
|
||||||
as="div"
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</Dialog.Description>
|
|
||||||
</Dialog.Content>
|
|
||||||
</Dialog.Portal>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -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/ */
|
/* List of icons: https://marella.me/material-icons/demo/ */
|
||||||
/* @import url(./components/Typography/css/typography.css); */
|
/* @import url(./components/Typography/css/typography.css); */
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,12 @@ import { createEffect, createSignal } from "solid-js"; // For, Show might not be
|
|||||||
import toast from "solid-toast";
|
import toast from "solid-toast";
|
||||||
import { FieldLayout } from "@/src/Form/fields/layout";
|
import { FieldLayout } from "@/src/Form/fields/layout";
|
||||||
import { InputLabel } from "@/src/components/inputBase";
|
import { InputLabel } from "@/src/components/inputBase";
|
||||||
import { Modal } from "@/src/components/modal";
|
|
||||||
import Fieldset from "@/src/Form/fieldset"; // Still used for other fieldsets
|
import Fieldset from "@/src/Form/fieldset"; // Still used for other fieldsets
|
||||||
import Accordion from "@/src/components/accordion";
|
import Accordion from "@/src/components/accordion";
|
||||||
|
|
||||||
|
import { SimpleModal } from "@/src/components/SimpleModal";
|
||||||
|
|
||||||
// Import the new generic component
|
// Import the new generic component
|
||||||
import {
|
import {
|
||||||
FileSelectorField,
|
FileSelectorField,
|
||||||
@@ -192,12 +194,11 @@ export const Flash = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header title="Flash installer" />
|
<Header title="Flash installer" />
|
||||||
<Modal
|
<SimpleModal
|
||||||
open={confirmOpen() || isFlashing()}
|
open={confirmOpen() || isFlashing()}
|
||||||
handleClose={() => !isFlashing() && setConfirmOpen(false)}
|
onClose={() => !isFlashing() && setConfirmOpen(false)}
|
||||||
title="Confirm"
|
title="Confirm"
|
||||||
>
|
>
|
||||||
{/* ... Modal content as before ... */}
|
|
||||||
<div class="flex flex-col gap-4 p-4">
|
<div class="flex flex-col gap-4 p-4">
|
||||||
<div class="flex flex-col justify-between rounded-sm border p-4 align-middle text-red-900 border-def-2">
|
<div class="flex flex-col justify-between rounded-sm border p-4 align-middle text-red-900 border-def-2">
|
||||||
<Typography
|
<Typography
|
||||||
@@ -230,7 +231,7 @@ export const Flash = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</SimpleModal>
|
||||||
<div class="w-full self-stretch p-8">
|
<div class="w-full self-stretch p-8">
|
||||||
<Form
|
<Form
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
|
|||||||
@@ -125,7 +125,6 @@ export function InstallMachine(props: InstallMachineProps) {
|
|||||||
machine: {
|
machine: {
|
||||||
name: props.name,
|
name: props.name,
|
||||||
flake: { identifier: curr_uri },
|
flake: { identifier: curr_uri },
|
||||||
private_key: values.sshKey?.name,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
target_host: targetHostResponse.data.data,
|
target_host: targetHostResponse.data.data,
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export function MachineForm(props: MachineFormProps) {
|
|||||||
...values.machine,
|
...values.machine,
|
||||||
tags: Array.from(values.machine.tags || detailed.machine.tags || []),
|
tags: Array.from(values.machine.tags || detailed.machine.tags || []),
|
||||||
},
|
},
|
||||||
}).promise;
|
} ).promise;
|
||||||
|
|
||||||
await queryClient.invalidateQueries({
|
await queryClient.invalidateQueries({
|
||||||
queryKey: [
|
queryKey: [
|
||||||
@@ -80,7 +80,7 @@ export function MachineForm(props: MachineFormProps) {
|
|||||||
const result = await callApi("get_generators_closure", {
|
const result = await callApi("get_generators_closure", {
|
||||||
base_dir: base_dir,
|
base_dir: base_dir,
|
||||||
machine_name: machine_name,
|
machine_name: machine_name,
|
||||||
}).promise;
|
}, {logging: {group: { name: machine_name, flake: {identifier: base_dir} }}}).promise;
|
||||||
if (result.status === "error") throw new Error("Failed to fetch data");
|
if (result.status === "error") throw new Error("Failed to fetch data");
|
||||||
return result.data;
|
return result.data;
|
||||||
},
|
},
|
||||||
@@ -118,7 +118,8 @@ export function MachineForm(props: MachineFormProps) {
|
|||||||
flake: {
|
flake: {
|
||||||
identifier: curr_uri,
|
identifier: curr_uri,
|
||||||
},
|
},
|
||||||
}).promise;
|
}, {logging: { group: { name: machine, flake: { identifier: curr_uri } } }}
|
||||||
|
).promise;
|
||||||
|
|
||||||
if (target.status === "error") {
|
if (target.status === "error") {
|
||||||
toast.error("Failed to get target host");
|
toast.error("Failed to get target host");
|
||||||
@@ -143,7 +144,7 @@ export function MachineForm(props: MachineFormProps) {
|
|||||||
...target_host,
|
...target_host,
|
||||||
},
|
},
|
||||||
build_host: null,
|
build_host: null,
|
||||||
}).promise.finally(() => {
|
}, {logging: { group: { name: machine, flake: { identifier: curr_uri } } }}).promise.finally(() => {
|
||||||
setIsUpdating(false);
|
setIsUpdating(false);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export const HWStep = (props: StepProps<HardwareValues>) => {
|
|||||||
command_prefix: "sudo",
|
command_prefix: "sudo",
|
||||||
port: 22,
|
port: 22,
|
||||||
forward_agent: false,
|
forward_agent: false,
|
||||||
host_key_check: 1, // 0 = ASK
|
host_key_check: "ask", // 0 = ASK
|
||||||
verbose_ssh: false,
|
verbose_ssh: false,
|
||||||
ssh_options: {},
|
ssh_options: {},
|
||||||
tor_socks: false,
|
tor_socks: false,
|
||||||
|
|||||||
@@ -153,10 +153,10 @@ export const VarsStep = (props: VarsStepProps) => {
|
|||||||
base_dir: props.dir,
|
base_dir: props.dir,
|
||||||
machine_name: props.machine_id,
|
machine_name: props.machine_id,
|
||||||
full_closure: props.fullClosure,
|
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");
|
if (result.status === "error") throw new Error("Failed to fetch data");
|
||||||
return result.data;
|
return result.data;
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const handleSubmit: SubmitHandler<VarsValues> = async (values, event) => {
|
const handleSubmit: SubmitHandler<VarsValues> = async (values, event) => {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import Icon from "@/src/components/icon";
|
|||||||
import { Header } from "@/src/layout/header";
|
import { Header } from "@/src/layout/header";
|
||||||
import { makePersisted } from "@solid-primitives/storage";
|
import { makePersisted } from "@solid-primitives/storage";
|
||||||
import { useClanContext } from "@/src/contexts/clan";
|
import { useClanContext } from "@/src/contexts/clan";
|
||||||
|
import { debug } from "console";
|
||||||
|
|
||||||
type MachinesModel = Extract<
|
type MachinesModel = Extract<
|
||||||
OperationResponse<"list_machines">,
|
OperationResponse<"list_machines">,
|
||||||
@@ -38,6 +39,7 @@ export const MachineListView: Component = () => {
|
|||||||
},
|
},
|
||||||
}).promise;
|
}).promise;
|
||||||
console.log("response", response);
|
console.log("response", response);
|
||||||
|
|
||||||
if (response.status === "error") {
|
if (response.status === "error") {
|
||||||
console.error("Failed to fetch data");
|
console.error("Failed to fetch data");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -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 { Schema as Inventory } from "@/api/Inventory";
|
||||||
import { toast } from "solid-toast";
|
import { toast } from "solid-toast";
|
||||||
import {
|
import {
|
||||||
ErrorToastComponent,
|
ErrorToastComponent,
|
||||||
CancelToastComponent,
|
CancelToastComponent,
|
||||||
} from "@/src/components/toast";
|
} from "@/src/components/toast";
|
||||||
|
|
||||||
type OperationNames = keyof API;
|
type OperationNames = keyof API;
|
||||||
type OperationArgs<T extends OperationNames> = API[T]["arguments"];
|
|
||||||
export type OperationResponse<T extends OperationNames> = API[T]["return"];
|
|
||||||
|
|
||||||
type ApiEnvelope<T> =
|
|
||||||
| {
|
|
||||||
status: "success";
|
|
||||||
data: T;
|
|
||||||
op_key: string;
|
|
||||||
}
|
|
||||||
| ApiError;
|
|
||||||
|
|
||||||
type Services = NonNullable<Inventory["services"]>;
|
type Services = NonNullable<Inventory["services"]>;
|
||||||
type ServiceNames = keyof Services;
|
type ServiceNames = keyof Services;
|
||||||
type ClanService<T extends ServiceNames> = Services[T];
|
|
||||||
type ClanServiceInstance<T extends ServiceNames> = NonNullable<
|
export type OperationArgs<T extends OperationNames> = API[T]["arguments"];
|
||||||
|
export type OperationResponse<T extends OperationNames> = API[T]["return"];
|
||||||
|
|
||||||
|
export type ClanServiceInstance<T extends ServiceNames> = NonNullable<
|
||||||
Services[T]
|
Services[T]
|
||||||
>[string];
|
>[string];
|
||||||
|
|
||||||
@@ -28,51 +21,83 @@ export type SuccessQuery<T extends OperationNames> = Extract<
|
|||||||
OperationResponse<T>,
|
OperationResponse<T>,
|
||||||
{ status: "success" }
|
{ status: "success" }
|
||||||
>;
|
>;
|
||||||
type SuccessData<T extends OperationNames> = SuccessQuery<T>["data"];
|
export type SuccessData<T extends OperationNames> = SuccessQuery<T>["data"];
|
||||||
|
|
||||||
type ErrorQuery<T extends OperationNames> = Extract<
|
function isMachine(obj: unknown): obj is Machine {
|
||||||
OperationResponse<T>,
|
return (
|
||||||
{ status: "error" }
|
!!obj &&
|
||||||
>;
|
typeof obj === "object" &&
|
||||||
type ErrorData<T extends OperationNames> = ErrorQuery<T>["errors"];
|
typeof (obj as Machine).name === "string" &&
|
||||||
|
typeof (obj as Machine).flake === "object" &&
|
||||||
type ClanOperations = Record<OperationNames, (str: string) => void>;
|
typeof (obj as Machine).flake.identifier === "string"
|
||||||
|
);
|
||||||
interface GtkResponse<T> {
|
|
||||||
result: T;
|
|
||||||
op_key: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Machine type with flake for API calls
|
||||||
|
interface Machine {
|
||||||
|
name: string;
|
||||||
|
flake: {
|
||||||
|
identifier: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BackendOpts {
|
||||||
|
logging?: { group: string | Machine };
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BackendReturnType<K extends OperationNames> {
|
||||||
|
result: OperationResponse<K>;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
metadata: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
const _callApi = <K extends OperationNames>(
|
const _callApi = <K extends OperationNames>(
|
||||||
method: K,
|
method: K,
|
||||||
args: OperationArgs<K>,
|
args: OperationArgs<K>,
|
||||||
): { promise: Promise<OperationResponse<K>>; op_key: string } => {
|
backendOpts?: BackendOpts,
|
||||||
|
): { promise: Promise<BackendReturnType<K>>; op_key: string } => {
|
||||||
// if window[method] does not exist, throw an error
|
// if window[method] does not exist, throw an error
|
||||||
if (!(method in window)) {
|
if (!(method in window)) {
|
||||||
console.error(`Method ${method} not found on window object`);
|
console.error(`Method ${method} not found on window object`);
|
||||||
// return a rejected promise
|
// return a rejected promise
|
||||||
return {
|
return {
|
||||||
promise: Promise.resolve({
|
promise: Promise.resolve({
|
||||||
status: "error",
|
result: {
|
||||||
errors: [
|
status: "error",
|
||||||
{
|
errors: [
|
||||||
message: `Method ${method} not found on window object`,
|
{
|
||||||
code: "method_not_found",
|
message: `Method ${method} not found on window object`,
|
||||||
},
|
code: "method_not_found",
|
||||||
],
|
},
|
||||||
op_key: "noop",
|
],
|
||||||
|
op_key: "noop",
|
||||||
|
},
|
||||||
|
metadata: {},
|
||||||
}),
|
}),
|
||||||
op_key: "noop",
|
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 = (
|
const promise = (
|
||||||
window as unknown as Record<
|
window as unknown as Record<
|
||||||
OperationNames,
|
OperationNames,
|
||||||
(
|
(
|
||||||
args: OperationArgs<OperationNames>,
|
args: OperationArgs<OperationNames>,
|
||||||
) => Promise<OperationResponse<OperationNames>>
|
metadata?: BackendOpts,
|
||||||
|
) => Promise<BackendReturnType<OperationNames>>
|
||||||
>
|
>
|
||||||
)[method](args) as Promise<OperationResponse<K>>;
|
)[method](args, metadata) as Promise<BackendReturnType<K>>;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const op_key = (promise as any)._webviewMessageId as string;
|
const op_key = (promise as any)._webviewMessageId as string;
|
||||||
@@ -82,7 +107,7 @@ const _callApi = <K extends OperationNames>(
|
|||||||
|
|
||||||
const handleCancel = async <K extends OperationNames>(
|
const handleCancel = async <K extends OperationNames>(
|
||||||
ops_key: string,
|
ops_key: string,
|
||||||
orig_task: Promise<OperationResponse<K>>,
|
orig_task: Promise<BackendReturnType<K>>,
|
||||||
) => {
|
) => {
|
||||||
console.log("Canceling operation: ", ops_key);
|
console.log("Canceling operation: ", ops_key);
|
||||||
const { promise, op_key } = _callApi("cancel_task", { task_id: ops_key });
|
const { promise, op_key } = _callApi("cancel_task", { task_id: ops_key });
|
||||||
@@ -102,7 +127,7 @@ const handleCancel = async <K extends OperationNames>(
|
|||||||
});
|
});
|
||||||
const resp = await promise;
|
const resp = await promise;
|
||||||
|
|
||||||
if (resp.status === "error") {
|
if (resp.result.status === "error") {
|
||||||
toast.custom(
|
toast.custom(
|
||||||
(t) => (
|
(t) => (
|
||||||
<ErrorToastComponent
|
<ErrorToastComponent
|
||||||
@@ -124,10 +149,11 @@ const handleCancel = async <K extends OperationNames>(
|
|||||||
export const callApi = <K extends OperationNames>(
|
export const callApi = <K extends OperationNames>(
|
||||||
method: K,
|
method: K,
|
||||||
args: OperationArgs<K>,
|
args: OperationArgs<K>,
|
||||||
|
backendOpts?: BackendOpts,
|
||||||
): { promise: Promise<OperationResponse<K>>; op_key: string } => {
|
): { promise: Promise<OperationResponse<K>>; op_key: string } => {
|
||||||
console.log("Calling API", method, args);
|
console.log("Calling API", method, args);
|
||||||
|
|
||||||
const { promise, op_key } = _callApi(method, args);
|
const { promise, op_key } = _callApi(method, args, backendOpts);
|
||||||
promise.catch((error) => {
|
promise.catch((error) => {
|
||||||
toast.custom(
|
toast.custom(
|
||||||
(t) => (
|
(t) => (
|
||||||
@@ -165,13 +191,14 @@ export const callApi = <K extends OperationNames>(
|
|||||||
console.log("Not printing toast because operation was cancelled");
|
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.remove(toastId);
|
||||||
toast.custom(
|
toast.custom(
|
||||||
(t) => (
|
(t) => (
|
||||||
<ErrorToastComponent
|
<ErrorToastComponent
|
||||||
t={t}
|
t={t}
|
||||||
message={"Error: " + response.errors[0].message}
|
message={"Error: " + result.errors[0].message}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
@@ -181,7 +208,8 @@ export const callApi = <K extends OperationNames>(
|
|||||||
} else {
|
} else {
|
||||||
toast.remove(toastId);
|
toast.remove(toastId);
|
||||||
}
|
}
|
||||||
return response;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
return { promise: new_promise, op_key: op_key };
|
return { promise: new_promise, op_key: op_key };
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user