clan-app: Propagate op_key to callApi callers.

This commit is contained in:
Qubasa
2025-06-13 12:41:50 +02:00
parent 371fe3181e
commit 2df223c7e8
19 changed files with 110 additions and 76 deletions

View File

@@ -88,6 +88,20 @@ const handleCancel = async <K extends OperationNames>(
) => { ) => {
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 });
promise.catch((error) => {
toast.custom(
(t) => (
<ErrorToastComponent
t={t}
message={"Unexpected error: " + (error?.message || String(error))}
/>
),
{
duration: 5000,
},
);
console.error("Unhandled promise rejection in callApi:", error);
});
const resp = await promise; const resp = await promise;
if (resp.status === "error") { if (resp.status === "error") {
@@ -109,13 +123,27 @@ const handleCancel = async <K extends OperationNames>(
console.log("Cancel response: ", resp); console.log("Cancel response: ", resp);
}; };
export const callApi = async <K extends OperationNames>( export const callApi = <K extends OperationNames>(
method: K, method: K,
args: OperationArgs<K>, args: OperationArgs<K>,
): Promise<OperationResponse<K>> => { ): { 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);
promise.catch((error) => {
toast.custom(
(t) => (
<ErrorToastComponent
t={t}
message={"Unexpected error: " + (error?.message || String(error))}
/>
),
{
duration: 5000,
},
);
console.error("Unhandled promise rejection in callApi:", error);
});
const toastId = toast.custom( const toastId = toast.custom(
( (
@@ -132,7 +160,7 @@ export const callApi = async <K extends OperationNames>(
}, },
); );
const response = await promise; const new_promise = promise.then((response) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const cancelled = (promise as any).cancelled; const cancelled = (promise as any).cancelled;
if (cancelled) { if (cancelled) {
@@ -155,5 +183,7 @@ export const callApi = async <K extends OperationNames>(
} else { } else {
toast.remove(toastId); toast.remove(toastId);
} }
return response as OperationResponse<K>; return response;
});
return { promise: new_promise, op_key: op_key };
}; };

View File

@@ -1,10 +0,0 @@
export interface Machine {
machine: {
name: string;
flake: {
identifier: string;
};
override_target_host: string | null;
private_key: string | null;
};
}

View File

@@ -7,7 +7,7 @@ export const instance_name = (machine_name: string) =>
export async function get_iwd_service(base_path: string, machine_name: string) { export async function get_iwd_service(base_path: string, machine_name: string) {
const r = await callApi("get_inventory", { const r = await callApi("get_inventory", {
flake: { identifier: base_path }, flake: { identifier: base_path },
}); }).promise;
if (r.status == "error") { if (r.status == "error") {
return null; return null;
} }

View File

@@ -142,11 +142,25 @@ export const ApiTester = () => {
</Field> </Field>
<Field name="payload"> <Field name="payload">
{(field, fieldProps) => ( {(field, fieldProps) => (
<TextInput <div class="flex flex-col my-2">
label={"payload"} <label class="mb-1 font-medium" for="payload-textarea">
payload
</label>
<textarea
id="payload-textarea"
class="border rounded p-2 text-sm min-h-[120px] resize-y focus:outline-none focus:ring-2 focus:ring-blue-400"
placeholder={`{\n "key": "value"\n}`}
value={field.value || ""} value={field.value || ""}
inputProps={fieldProps} {...fieldProps}
onInput={(e) => {
fieldProps.onInput?.(e);
}}
spellcheck={false}
autocomplete="off"
autocorrect="off"
autocapitalize="off"
/> />
</div>
)} )}
</Field> </Field>
<Button class="m-2" disabled={query.isFetching}> <Button class="m-2" disabled={query.isFetching}>

View File

@@ -72,7 +72,7 @@ export const FileSelectorField: Component<FileSelectorOpts<string>> = (
filters: fileDialogOptions.filters, filters: fileDialogOptions.filters,
initial_folder: fileDialogOptions.initial_folder, initial_folder: fileDialogOptions.initial_folder,
}, },
}); }).promise;
if ( if (
response.status === "success" && response.status === "success" &&

View File

@@ -62,7 +62,7 @@ export const MachineListItem = (props: MachineListItemProps) => {
nix_options: [], nix_options: [],
password: null, password: null,
}, },
}); }).promise;
setInstalling(false); setInstalling(false);
}; };
@@ -91,7 +91,7 @@ export const MachineListItem = (props: MachineListItemProps) => {
}, },
override_target_host: info?.deploy.targetHost, override_target_host: info?.deploy.targetHost,
}, },
}); }).promise;
setUpdating(false); setUpdating(false);
}; };

View File

@@ -7,7 +7,7 @@ export const registerClan = async () => {
try { try {
const loc = await callApi("open_file", { const loc = await callApi("open_file", {
file_request: { mode: "select_folder" }, file_request: { mode: "select_folder" },
}); }).promise;
if (loc.status === "success" && loc.data) { if (loc.status === "success" && loc.data) {
const data = loc.data[0]; const data = loc.data[0];
addClanURI(data); addClanURI(data);
@@ -32,7 +32,7 @@ export const selectSshKeys = async (): Promise<FileList> => {
mode: "open_file", mode: "open_file",
initial_folder: "~/.ssh", initial_folder: "~/.ssh",
}, },
}); }).promise;
if (response.status === "success" && response.data) { if (response.status === "success" && response.data) {
// Add synthetic files to the DataTransfer object // Add synthetic files to the DataTransfer object
// FileList cannot be instantiated directly. // FileList cannot be instantiated directly.

View File

@@ -15,7 +15,7 @@ export const clanMetaQuery = (uri: string | undefined = undefined) =>
const result = await callApi("show_clan_meta", { const result = await callApi("show_clan_meta", {
flake: { identifier: clanURI! }, flake: { identifier: clanURI! },
}); }).promise;
console.log("result", result); console.log("result", result);

View File

@@ -1,4 +1,4 @@
import { createQuery } from "@tanstack/solid-query"; import { useQuery } from "@tanstack/solid-query";
import { callApi } from "../api"; import { callApi } from "../api";
import toast from "solid-toast"; import toast from "solid-toast";
@@ -9,7 +9,7 @@ export const createModulesQuery = (
uri: string | undefined, uri: string | undefined,
filter?: ModulesFilter, filter?: ModulesFilter,
) => ) =>
createQuery(() => ({ useQuery(() => ({
queryKey: [uri, "list_modules"], queryKey: [uri, "list_modules"],
placeholderData: { placeholderData: {
localModules: {}, localModules: {},
@@ -20,7 +20,7 @@ export const createModulesQuery = (
if (uri) { if (uri) {
const response = await callApi("list_modules", { const response = await callApi("list_modules", {
base_path: uri, base_path: uri,
}); }).promise;
if (response.status === "error") { if (response.status === "error") {
console.error("Failed to fetch data"); console.error("Failed to fetch data");
} else { } else {
@@ -35,7 +35,7 @@ export const createModulesQuery = (
})); }));
export const tagsQuery = (uri: string | undefined) => export const tagsQuery = (uri: string | undefined) =>
createQuery<string[]>(() => ({ useQuery<string[]>(() => ({
queryKey: [uri, "tags"], queryKey: [uri, "tags"],
placeholderData: [], placeholderData: [],
queryFn: async () => { queryFn: async () => {
@@ -43,7 +43,7 @@ export const tagsQuery = (uri: string | undefined) =>
const response = await callApi("get_inventory", { const response = await callApi("get_inventory", {
flake: { identifier: uri }, flake: { identifier: uri },
}); }).promise;
if (response.status === "error") { if (response.status === "error") {
console.error("Failed to fetch data"); console.error("Failed to fetch data");
} else { } else {
@@ -56,7 +56,7 @@ export const tagsQuery = (uri: string | undefined) =>
})); }));
export const machinesQuery = (uri: string | undefined) => export const machinesQuery = (uri: string | undefined) =>
createQuery<string[]>(() => ({ useQuery<string[]>(() => ({
queryKey: [uri, "machines"], queryKey: [uri, "machines"],
placeholderData: [], placeholderData: [],
queryFn: async () => { queryFn: async () => {
@@ -64,7 +64,7 @@ export const machinesQuery = (uri: string | undefined) =>
const response = await callApi("get_inventory", { const response = await callApi("get_inventory", {
flake: { identifier: uri }, flake: { identifier: uri },
}); }).promise;
if (response.status === "error") { if (response.status === "error") {
console.error("Failed to fetch data"); console.error("Failed to fetch data");
} else { } else {

View File

@@ -33,7 +33,7 @@ export const CreateClan = () => {
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" },
}); }).promise;
if (response.status !== "success") { if (response.status !== "success") {
toast.error("Cannot select clan directory"); toast.error("Cannot select clan directory");
@@ -56,7 +56,7 @@ export const CreateClan = () => {
machines: {}, machines: {},
}, },
}, },
}); }).promise;
toast.dismiss(loading_toast); toast.dismiss(loading_toast);
if (r.status === "error") { if (r.status === "error") {
@@ -67,7 +67,7 @@ export const CreateClan = () => {
// Will generate a key if it doesn't exist, and add a user to the clan // Will generate a key if it doesn't exist, and add a user to the clan
const k = await callApi("keygen", { const k = await callApi("keygen", {
flake_dir: target_dir[0], flake_dir: target_dir[0],
}); }).promise;
if (k.status === "error") { if (k.status === "error") {
toast.error("Failed to generate key"); toast.error("Failed to generate key");

View File

@@ -37,7 +37,7 @@ const EditClanForm = (props: EditClanFormProps) => {
flake: { identifier: props.directory }, flake: { identifier: props.directory },
meta: values, meta: values,
}, },
}); }).promise;
})(), })(),
{ {
loading: "Updating clan...", loading: "Updating clan...",

View File

@@ -13,7 +13,7 @@ export function DiskView() {
// Example of calling an API // Example of calling an API
const result = await callApi("get_inventory", { const result = await callApi("get_inventory", {
flake: { identifier: currUri }, flake: { identifier: currUri },
}); }).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;
} }

View File

@@ -98,7 +98,7 @@ export const Flash = () => {
const deviceQuery = createQuery(() => ({ const deviceQuery = createQuery(() => ({
queryKey: ["block_devices"], queryKey: ["block_devices"],
queryFn: async () => { queryFn: async () => {
const result = await callApi("show_block_devices", {}); const result = await callApi("show_block_devices", {}).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;
}, },
@@ -108,7 +108,7 @@ export const Flash = () => {
const keymapQuery = createQuery(() => ({ const keymapQuery = createQuery(() => ({
queryKey: ["list_keymaps"], queryKey: ["list_keymaps"],
queryFn: async () => { queryFn: async () => {
const result = await callApi("list_possible_keymaps", {}); const result = await callApi("list_possible_keymaps", {}).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,7 @@ export const Flash = () => {
const langQuery = createQuery(() => ({ const langQuery = createQuery(() => ({
queryKey: ["list_languages"], queryKey: ["list_languages"],
queryFn: async () => { queryFn: async () => {
const result = await callApi("list_possible_languages", {}); const result = await callApi("list_possible_languages", {}).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;
}, },
@@ -174,7 +174,7 @@ export const Flash = () => {
write_efi_boot_entries: false, write_efi_boot_entries: false,
debug: false, debug: false,
graphical: true, graphical: true,
}), }).promise,
{ {
error: (errors) => `Error flashing disk: ${errors}`, error: (errors) => `Error flashing disk: ${errors}`,
loading: "Flashing ... This may take up to 15minutes.", loading: "Flashing ... This may take up to 15minutes.",

View File

@@ -56,7 +56,7 @@ export function CreateMachine() {
identifier: active_dir, identifier: active_dir,
}, },
}, },
}); }).promise;
if (response.status === "success") { if (response.status === "success") {
toast.success(`Successfully created ${values.opts.machine.name}`); toast.success(`Successfully created ${values.opts.machine.name}`);

View File

@@ -7,7 +7,7 @@ import {
setValue, setValue,
} from "@modular-forms/solid"; } from "@modular-forms/solid";
import { useNavigate, useParams, useSearchParams } from "@solidjs/router"; import { useNavigate, useParams, useSearchParams } from "@solidjs/router";
import { createQuery, useQuery, useQueryClient } from "@tanstack/solid-query"; import { useQuery, useQueryClient } from "@tanstack/solid-query";
import { createEffect, createSignal, For, Match, Show, Switch } from "solid-js"; import { createEffect, createSignal, For, Match, Show, Switch } from "solid-js";
import { Button } from "../../components/Button/Button"; import { Button } from "../../components/Button/Button";
@@ -130,7 +130,7 @@ const InstallMachine = (props: InstallMachineProps) => {
placeholders: diskValues.placeholders, placeholders: diskValues.placeholders,
schema_name: diskValues.schema, schema_name: diskValues.schema,
force: true, force: true,
}); }).promise;
} }
setProgressText("Installing machine ... (2/5)"); setProgressText("Installing machine ... (2/5)");
@@ -425,7 +425,7 @@ const MachineForm = (props: MachineDetailsProps) => {
values.machine.tags || props.initialData.machine.tags || [], values.machine.tags || props.initialData.machine.tags || [],
), ),
}, },
}); }).promise;
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: [curr_uri, "machine", machineName(), "get_machine_details"], queryKey: [curr_uri, "machine", machineName(), "get_machine_details"],
}); });
@@ -433,7 +433,7 @@ const MachineForm = (props: MachineDetailsProps) => {
return null; return null;
}; };
const generatorsQuery = createQuery(() => ({ const generatorsQuery = useQuery(() => ({
queryKey: [activeClanURI(), machineName(), "generators"], queryKey: [activeClanURI(), machineName(), "generators"],
queryFn: async () => { queryFn: async () => {
const machine_name = machineName(); const machine_name = machineName();
@@ -444,7 +444,7 @@ const MachineForm = (props: MachineDetailsProps) => {
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;
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;
}, },
@@ -489,7 +489,7 @@ const MachineForm = (props: MachineDetailsProps) => {
}, },
override_target_host: target, override_target_host: target,
}, },
}); }).promise;
}; };
createEffect(() => { createEffect(() => {
@@ -717,7 +717,7 @@ export const MachineDetails = () => {
}, },
name: params.id, name: params.id,
}, },
}); }).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;
} }

View File

@@ -43,7 +43,7 @@ export const DiskStep = (props: StepProps<DiskValues>) => {
}, },
name: props.machine_id, name: props.machine_id,
}, },
}); }).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;
}, },

View File

@@ -62,7 +62,7 @@ export const HWStep = (props: StepProps<HardwareValues>) => {
}, },
name: props.machine_id, name: props.machine_id,
}, },
}); }).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;
}, },

View File

@@ -153,7 +153,7 @@ 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;
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;
}, },
@@ -170,7 +170,7 @@ export const VarsStep = (props: VarsStepProps) => {
base_dir: props.dir, base_dir: props.dir,
generators: generatorsQuery.data.map((generator) => generator.name), generators: generatorsQuery.data.map((generator) => generator.name),
all_prompt_values: values, all_prompt_values: values,
}); }).promise;
queryClient.invalidateQueries({ queryClient.invalidateQueries({
queryKey: [props.dir, props.machine_id, "generators"], queryKey: [props.dir, props.machine_id, "generators"],
}); });

View File

@@ -36,7 +36,7 @@ export const MachineListView: Component = () => {
flake: { flake: {
identifier: uri, identifier: uri,
}, },
}); }).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");