UI: update gitignore
This commit is contained in:
2
pkgs/webview-ui/.gitignore
vendored
2
pkgs/webview-ui/.gitignore
vendored
@@ -1,3 +1,3 @@
|
|||||||
api
|
app/api
|
||||||
|
|
||||||
.vite
|
.vite
|
||||||
31
pkgs/webview-ui/app/src/api/disk.ts
Normal file
31
pkgs/webview-ui/app/src/api/disk.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { get_inventory } from "./inventory";
|
||||||
|
|
||||||
|
export const instance_name = (machine_name: string) =>
|
||||||
|
`${machine_name}-single-disk` as const;
|
||||||
|
|
||||||
|
export async function set_single_disk_id(
|
||||||
|
base_path: string,
|
||||||
|
machine_name: string,
|
||||||
|
disk_id: string,
|
||||||
|
) {
|
||||||
|
const inventory = await get_inventory(base_path);
|
||||||
|
if (!inventory.services) {
|
||||||
|
return new Error("No services found in inventory");
|
||||||
|
}
|
||||||
|
if (!inventory.services["single-disk"]) {
|
||||||
|
inventory.services["single-disk"] = {};
|
||||||
|
}
|
||||||
|
inventory.services["single-disk"][instance_name(machine_name)] = {
|
||||||
|
meta: {
|
||||||
|
name: instance_name(machine_name),
|
||||||
|
},
|
||||||
|
roles: {
|
||||||
|
default: {
|
||||||
|
machines: [machine_name],
|
||||||
|
config: {
|
||||||
|
device: `/dev/disk/by-id/${disk_id}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
168
pkgs/webview-ui/app/src/api/index.ts
Normal file
168
pkgs/webview-ui/app/src/api/index.ts
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
import schema from "@/api/API.json" assert { type: "json" };
|
||||||
|
import { API } from "@/api/API";
|
||||||
|
import { nanoid } from "nanoid";
|
||||||
|
import { Schema as Inventory } from "@/api/Inventory";
|
||||||
|
|
||||||
|
export type OperationNames = keyof API;
|
||||||
|
export type OperationArgs<T extends OperationNames> = API[T]["arguments"];
|
||||||
|
export type OperationResponse<T extends OperationNames> = API[T]["return"];
|
||||||
|
|
||||||
|
export type Services = NonNullable<Inventory["services"]>;
|
||||||
|
export type ServiceNames = keyof Services;
|
||||||
|
export type ClanService<T extends ServiceNames> = Services[T];
|
||||||
|
export type ClanServiceInstance<T extends ServiceNames> = NonNullable<
|
||||||
|
Services[T]
|
||||||
|
>[string];
|
||||||
|
|
||||||
|
export type SuccessQuery<T extends OperationNames> = Extract<
|
||||||
|
OperationResponse<T>,
|
||||||
|
{ status: "success" }
|
||||||
|
>;
|
||||||
|
export type SuccessData<T extends OperationNames> = SuccessQuery<T>["data"];
|
||||||
|
|
||||||
|
export type ErrorQuery<T extends OperationNames> = Extract<
|
||||||
|
OperationResponse<T>,
|
||||||
|
{ status: "error" }
|
||||||
|
>;
|
||||||
|
export type ErrorData<T extends OperationNames> = ErrorQuery<T>["errors"];
|
||||||
|
|
||||||
|
export type ClanOperations = {
|
||||||
|
[K in OperationNames]: (str: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface GtkResponse<T> {
|
||||||
|
result: T;
|
||||||
|
op_key: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
clan: ClanOperations;
|
||||||
|
webkit: {
|
||||||
|
messageHandlers: {
|
||||||
|
gtk: {
|
||||||
|
postMessage: (message: {
|
||||||
|
method: OperationNames;
|
||||||
|
data: OperationArgs<OperationNames>;
|
||||||
|
}) => 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<K>) => void
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
const registry: ObserverRegistry = operationNames.reduce(
|
||||||
|
(acc, opName) => ({
|
||||||
|
...acc,
|
||||||
|
[opName]: {},
|
||||||
|
}),
|
||||||
|
{} as ObserverRegistry,
|
||||||
|
);
|
||||||
|
|
||||||
|
function createFunctions<K extends OperationNames>(
|
||||||
|
operationName: K,
|
||||||
|
): {
|
||||||
|
dispatch: (args: OperationArgs<K>) => void;
|
||||||
|
receive: (fn: (response: OperationResponse<K>) => void, id: string) => void;
|
||||||
|
} {
|
||||||
|
window.clan[operationName] = (s: string) => {
|
||||||
|
const f = (response: OperationResponse<K>) => {
|
||||||
|
// 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<K>) => {
|
||||||
|
// Send the data to the gtk app
|
||||||
|
window.webkit.messageHandlers.gtk.postMessage({
|
||||||
|
method: operationName,
|
||||||
|
data: args,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
receive: (fn: (response: OperationResponse<K>) => 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<K>) => void;
|
||||||
|
receive: (fn: (response: OperationResponse<K>) => 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 = <K extends OperationNames>(
|
||||||
|
method: K,
|
||||||
|
args: OperationArgs<K>,
|
||||||
|
) => {
|
||||||
|
return new Promise<OperationResponse<K>>((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 =
|
||||||
|
<T>(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 };
|
||||||
40
pkgs/webview-ui/app/src/api/inventory.ts
Normal file
40
pkgs/webview-ui/app/src/api/inventory.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { callApi, ClanService, ServiceNames, Services } from ".";
|
||||||
|
import { Schema as Inventory } from "@/api/Inventory";
|
||||||
|
|
||||||
|
export async function get_inventory(base_path: string) {
|
||||||
|
const r = await callApi("get_inventory", {
|
||||||
|
base_path,
|
||||||
|
});
|
||||||
|
if (r.status == "error") {
|
||||||
|
throw new Error("Failed to get inventory");
|
||||||
|
}
|
||||||
|
const inventory: Inventory = r.data;
|
||||||
|
return inventory;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const single_instance_name = <T extends keyof Services>(
|
||||||
|
machine_name: string,
|
||||||
|
service_name: T,
|
||||||
|
) => `${machine_name}_${service_name}_0` as const;
|
||||||
|
|
||||||
|
function get_service<T extends ServiceNames>(base_path: string, service: T) {
|
||||||
|
return callApi("get_inventory", { base_path }).then((r) => {
|
||||||
|
if (r.status == "error") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const inventory: Inventory = r.data;
|
||||||
|
|
||||||
|
const serviceInstance = inventory.services?.[service];
|
||||||
|
return serviceInstance;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function get_single_service<T extends keyof Services>(
|
||||||
|
base_path: string,
|
||||||
|
machine_name: string,
|
||||||
|
service_name: T,
|
||||||
|
) {
|
||||||
|
const instance_key = single_instance_name(machine_name, service_name);
|
||||||
|
const service = await get_service(base_path, "admin");
|
||||||
|
return service?.[instance_key];
|
||||||
|
}
|
||||||
18
pkgs/webview-ui/app/src/api/wifi.ts
Normal file
18
pkgs/webview-ui/app/src/api/wifi.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { callApi } from ".";
|
||||||
|
import { Schema as Inventory } from "@/api/Inventory";
|
||||||
|
|
||||||
|
export const instance_name = (machine_name: string) =>
|
||||||
|
`${machine_name}_wifi_0` as const;
|
||||||
|
|
||||||
|
export async function get_iwd_service(base_path: string, machine_name: string) {
|
||||||
|
const r = await callApi("get_inventory", {
|
||||||
|
base_path,
|
||||||
|
});
|
||||||
|
if (r.status == "error") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const inventory: Inventory = r.data;
|
||||||
|
|
||||||
|
const instance_key = instance_name(machine_name);
|
||||||
|
return inventory.services?.iwd?.[instance_key] || null;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user