diff --git a/pkgs/clan-cli/flake-module.nix b/pkgs/clan-cli/flake-module.nix index b9d1a4d2c..28fcab023 100644 --- a/pkgs/clan-cli/flake-module.nix +++ b/pkgs/clan-cli/flake-module.nix @@ -97,6 +97,8 @@ mkdir -p $out python api.py > $out/API.json ${self'.packages.json2ts}/bin/json2ts --input $out/API.json > $out/API.ts + ${self'.packages.json2ts}/bin/json2ts --input ${self'.packages.inventory-schema}/schema.json > $out/Inventory.ts + cp ${self'.packages.inventory-schema}/schema.json $out/inventory-schema.json ''; }; json2ts = pkgs.buildNpmPackage { diff --git a/pkgs/webview-ui/app/src/api.ts b/pkgs/webview-ui/app/src/api.ts deleted file mode 100644 index d3ae83c27..000000000 --- a/pkgs/webview-ui/app/src/api.ts +++ /dev/null @@ -1,160 +0,0 @@ -import schema from "@/api/API.json" assert { type: "json" }; -import { API } from "@/api/API"; -import { nanoid } from "nanoid"; - -export type OperationNames = keyof API; -export type OperationArgs = API[T]["arguments"]; -export type OperationResponse = API[T]["return"]; - -export type SuccessQuery = Extract< - OperationResponse, - { status: "success" } ->; -export type SuccessData = SuccessQuery["data"]; - -export type ErrorQuery = Extract< - OperationResponse, - { status: "error" } ->; -export type ErrorData = ErrorQuery["errors"]; - -export type ClanOperations = { - [K in OperationNames]: (str: string) => void; -}; - -export interface GtkResponse { - result: T; - op_key: string; -} - -declare global { - interface Window { - clan: ClanOperations; - webkit: { - messageHandlers: { - gtk: { - postMessage: (message: { - method: OperationNames; - data: OperationArgs; - }) => void; - }; - }; - }; - } -} -// Make sure window.webkit is defined although the type is not correctly filled yet. -window.clan = {} as ClanOperations; - -const operations = schema.properties; -const operationNames = Object.keys(operations) as OperationNames[]; - -type ObserverRegistry = { - [K in OperationNames]: Record< - string, - (response: OperationResponse) => void - >; -}; -const registry: ObserverRegistry = operationNames.reduce( - (acc, opName) => ({ - ...acc, - [opName]: {}, - }), - {} as ObserverRegistry, -); - -function createFunctions( - operationName: K, -): { - dispatch: (args: OperationArgs) => void; - receive: (fn: (response: OperationResponse) => void, id: string) => void; -} { - window.clan[operationName] = (s: string) => { - const f = (response: OperationResponse) => { - // Get the correct receiver function for the op_key - const receiver = registry[operationName][response.op_key]; - if (receiver) { - receiver(response); - } - }; - deserialize(f)(s); - }; - - return { - dispatch: (args: OperationArgs) => { - // Send the data to the gtk app - window.webkit.messageHandlers.gtk.postMessage({ - method: operationName, - data: args, - }); - }, - receive: (fn: (response: OperationResponse) => void, id: string) => { - // @ts-expect-error: This should work although typescript doesn't let us write - registry[operationName][id] = fn; - }, - }; -} - -type PyApi = { - [K in OperationNames]: { - dispatch: (args: OperationArgs) => void; - receive: (fn: (response: OperationResponse) => void, id: string) => void; - }; -}; - -function download(filename: string, text: string) { - const element = document.createElement("a"); - element.setAttribute( - "href", - "data:text/plain;charset=utf-8," + encodeURIComponent(text), - ); - element.setAttribute("download", filename); - - element.style.display = "none"; - document.body.appendChild(element); - - element.click(); - - document.body.removeChild(element); -} - -export const callApi = ( - method: K, - args: OperationArgs, -) => { - return new Promise>((resolve) => { - const id = nanoid(); - pyApi[method].receive((response) => { - console.log(method, "Received response: ", { response }); - resolve(response); - }, id); - - pyApi[method].dispatch({ ...args, op_key: id }); - }); -}; - -const deserialize = - (fn: (response: T) => void) => - (str: string) => { - try { - const r = JSON.parse(str) as T; - fn(r); - } catch (e) { - console.log("Error parsing JSON: ", e); - window.localStorage.setItem("error", str); - console.error(str); - console.error("See localStorage 'error'"); - alert(`Error parsing JSON: ${e}`); - } - }; - -// Create the API object - -const pyApi: PyApi = {} as PyApi; - -operationNames.forEach((opName) => { - const name = opName as OperationNames; - // @ts-expect-error - TODO: Fix this. Typescript is not recognizing the receive function correctly - pyApi[name] = createFunctions(name); -}); - -export { pyApi }; diff --git a/pkgs/webview-ui/app/src/routes/clans/details.tsx b/pkgs/webview-ui/app/src/routes/clans/details.tsx index 1620763de..62e73726e 100644 --- a/pkgs/webview-ui/app/src/routes/clans/details.tsx +++ b/pkgs/webview-ui/app/src/routes/clans/details.tsx @@ -1,4 +1,9 @@ -import { callApi, SuccessQuery } from "@/src/api"; +import { + callApi, + ClanService, + ClanServiceInstance, + SuccessQuery, +} from "@/src/api"; import { BackButton } from "@/src/components/BackButton"; import { useParams } from "@solidjs/router"; import { @@ -19,6 +24,7 @@ import { } from "@modular-forms/solid"; import { TextInput } from "@/src/components/TextInput"; import toast from "solid-toast"; +import { get_single_service } from "@/src/api/inventory"; interface AdminModuleFormProps { admin: AdminData; @@ -184,19 +190,20 @@ const AdminModuleForm = (props: AdminModuleFormProps) => { const handleSubmit = async (values: AdminSettings) => { console.log("submitting", values, getValues(formStore)); - const r = await callApi("set_admin_service", { - base_url: props.base_url, - allowed_keys: values.allowedKeys.reduce( - (acc, curr) => ({ ...acc, [curr.name]: curr.value }), - {}, - ), - }); - if (r.status === "success") { - toast.success("Successfully updated admin settings"); - } - if (r.status === "error") { - toast.error(`Failed to update admin settings: ${r.errors[0].message}`); - } + // const r = await callApi("set_admin_service", { + // base_url: props.base_url, + // allowed_keys: values.allowedKeys.reduce( + // (acc, curr) => ({ ...acc, [curr.name]: curr.value }), + // {} + // ), + // }); + // if (r.status === "success") { + // toast.success("Successfully updated admin settings"); + // } + // if (r.status === "error") { + // toast.error(`Failed to update admin settings: ${r.errors[0].message}`); + toast.error(`Failed to update admin settings: feature disabled`); + // } queryClient.invalidateQueries({ queryKey: [props.base_url, "get_admin_service"], }); @@ -329,7 +336,7 @@ const AdminModuleForm = (props: AdminModuleFormProps) => { }; type GeneralData = SuccessQuery<"show_clan_meta">["data"]; -type AdminData = SuccessQuery<"get_admin_service">["data"]; +type AdminData = ClanServiceInstance<"admin">; export const ClanDetails = () => { const params = useParams(); @@ -347,11 +354,9 @@ export const ClanDetails = () => { const adminQuery = createQuery(() => ({ queryKey: [clan_dir, "get_admin_service"], queryFn: async () => { - const result = await callApi("get_admin_service", { - base_url: clan_dir, - }); - if (result.status === "error") throw new Error("Failed to fetch data"); - return result.data || null; + const result = await get_single_service(clan_dir, "", "admin"); + if (!result) throw new Error("Failed to fetch data"); + return result || null; }, })); diff --git a/pkgs/webview-ui/app/src/routes/machines/details.tsx b/pkgs/webview-ui/app/src/routes/machines/details.tsx index 821e9cea6..6e5499735 100644 --- a/pkgs/webview-ui/app/src/routes/machines/details.tsx +++ b/pkgs/webview-ui/app/src/routes/machines/details.tsx @@ -1,4 +1,12 @@ -import { callApi, SuccessData, SuccessQuery } from "@/src/api"; +import { + callApi, + ClanService, + Services, + SuccessData, + SuccessQuery, +} from "@/src/api"; +import { set_single_disk_id } from "@/src/api/disk"; +import { get_iwd_service } from "@/src/api/wifi"; import { activeURI } from "@/src/App"; import { BackButton } from "@/src/components/BackButton"; import { FileInput } from "@/src/components/FileInput"; @@ -117,17 +125,12 @@ const InstallMachine = (props: InstallMachineProps) => { return; } - const r = await callApi("set_single_disk_uuid", { - base_path: curr_uri, - machine_name: props.name, - disk_uuid: disk_id, - }); - if (r.status === "error") { - toast.error("Failed to set disk"); - } - if (r.status === "success") { + const r = await set_single_disk_id(curr_uri, props.name, disk_id); + if (!r) { toast.success("Disk set successfully"); setConfirmDisk(true); + } else { + toast.error("Failed to set disk"); } }; @@ -600,7 +603,7 @@ const MachineForm = (props: MachineDetailsProps) => { ); }; -type WifiData = SuccessData<"get_iwd_service">; +type WifiData = ClanService<"iwd">; export const MachineDetails = () => { const params = useParams(); @@ -629,12 +632,9 @@ export const MachineDetails = () => { queryFn: async () => { const curr = activeURI(); if (curr) { - const result = await callApi("get_iwd_service", { - base_url: curr, - machine_name: params.id, - }); - if (result.status === "error") throw new Error("Failed to fetch data"); - return Object.entries(result.data?.config?.networks || {}).map( + const result = await get_iwd_service(curr, params.id); + if (!result) throw new Error("Failed to fetch data"); + return Object.entries(result?.config?.networks || {}).map( ([name, value]) => ({ name, ssid: value.ssid }), ); } @@ -728,17 +728,17 @@ function WifiModule(props: MachineWifiProps) { ); console.log("submitting", values, networks); - const r = await callApi("set_iwd_service_for_machine", { - base_url: props.base_url, - machine_name: props.machine_name, - networks: networks, - }); - if (r.status === "error") { - toast.error("Failed to set wifi"); - } - if (r.status === "success") { - toast.success("Wifi set successfully"); - } + // const r = await callApi("set_iwd_service_for_machine", { + // base_url: props.base_url, + // machine_name: props.machine_name, + // networks: networks, + // }); + // if (r.status === "error") { + toast.error("Failed to set wifi. Feature disabled temporarily"); + // } + // if (r.status === "success") { + // toast.success("Wifi set successfully"); + // } }; return (