Merge pull request 'api: improve message serialisation' (#1440) from hsjobeki-feat/api-improvements into main
All checks were successful
deploy / deploy-docs (push) Successful in 20s
buildbot/nix-build .#checks.aarch64-darwin.nixos-test-backup Build done.
buildbot/nix-build .#checks.aarch64-darwin.nixos-iso-installer Build done.
buildbot/nix-build .#checks.aarch64-linux.nixos-test_install_machine Build done.
buildbot/nix-build .#checks.aarch64-darwin.nixos-test_install_machine Build done.
buildbot/nix-build .#checks.aarch64-darwin.nixos-flash-installer Build done.
buildbot/nix-build .#checks.aarch64-linux.nixos-test-backup Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-bash Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-e2fsprogs Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-fakeroot Build done.
buildbot/nix-build .#checks.x86_64-linux."clan-dep-python3.11-mypy" Build done.
buildbot/nix-build .#checks.x86_64-linux."clan-dep-python3.11-qemu" Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-rsync Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-sops Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-zbar Build done.
buildbot/nix-build .#checks.aarch64-linux.nixos-flash-installer Build done.
buildbot/nix-build .#checks.x86_64-linux.deltachat Build done.
buildbot/nix-build .#checks.x86_64-linux.matrix-synapse Build done.
buildbot/nix-build .#checks.x86_64-linux.devShell-docs Build done.
buildbot/nix-build .#checks.x86_64-linux.nixos-test-backup Build done.
buildbot/nix-build .#checks.x86_64-linux.check-for-breakpoints Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-age Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-git Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-nix Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-openssh Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-sshpass Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-dep-tor Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-pytest-with-core Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-pytest-without-core Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-vm-manager-no-breakpoints Build done.
buildbot/nix-build .#checks.x86_64-linux.clan-vm-manager-pytest Build done.
buildbot/nix-build .#checks.aarch64-linux.nixos-iso-installer Build done.
buildbot/nix-build .#checks.x86_64-linux.devShell-default Build done.
buildbot/nix-build .#checks.x86_64-linux.devShell-clan-vm-manager Build done.
buildbot/nix-build .#checks.x86_64-linux.devShell-webview-ui Build done.
buildbot/nix-build .#checks.x86_64-linux.lib-jsonschema-example-valid Build done.
buildbot/nix-build .#checks.x86_64-linux.devShell-clan-cli Build done.
buildbot/nix-build .#checks.x86_64-linux.container Build done.
buildbot/nix-build .#checks.x86_64-linux.borgbackup Build done.
buildbot/nix-build .#checks.x86_64-linux.nixos-test_install_machine Build done.
buildbot/nix-build .#checks.x86_64-linux.package-default Build done.
buildbot/nix-build .#checks.x86_64-linux.package-clan-vm-manager Build done.
buildbot/nix-build .#checks.x86_64-linux.package-clan-cli-docs Build done.
buildbot/nix-build .#checks.x86_64-linux.package-clan-ts-api Build done.
buildbot/nix-build .#checks.x86_64-linux.package-clan-cli Build done.
buildbot/nix-build .#checks.x86_64-linux.package-module-schema Build done.
buildbot/nix-build .#checks.x86_64-linux.package-impure-checks Build done.
buildbot/nix-build .#checks.x86_64-linux.lib-jsonschema-nix-unit-tests Build done.
buildbot/nix-build .#checks.x86_64-linux.package-merge-after-ci Build done.
buildbot/nix-build .#checks.x86_64-linux.package-moonlight-sunshine-accept Build done.
buildbot/nix-build .#checks.x86_64-linux.package-pending-reviews Build done.
buildbot/nix-build .#checks.x86_64-linux.package-tea-create-pr Build done.
buildbot/nix-build .#checks.x86_64-linux.package-wayland-proxy-virtwl Build done.
buildbot/nix-build .#checks.x86_64-linux.package-webview-ui Build done.
buildbot/nix-build .#checks.x86_64-linux.package-zerotier-members Build done.
buildbot/nix-build .#checks.x86_64-linux.package-zerotierone Build done.
buildbot/nix-build .#checks.x86_64-linux.package-zt-tcp-relay Build done.
buildbot/nix-build .#checks.x86_64-linux.module-schema Build done.
buildbot/nix-build .#checks.x86_64-linux.renderClanOptions Build done.
buildbot/nix-build .#checks.x86_64-linux.package-docs Build done.
buildbot/nix-build .#checks.x86_64-linux.package-deploy-docs Build done.
buildbot/nix-build .#checks.x86_64-linux.package-function-schema Build done.
buildbot/nix-build .#checks.x86_64-linux.package-module-docs Build done.
buildbot/nix-build .#checks.x86_64-linux.nixos-iso-installer Build done.
buildbot/nix-build .#checks.x86_64-linux.test-backups Build done.
buildbot/nix-build .#checks.x86_64-linux.syncthing Build done.
buildbot/nix-build .#checks.x86_64-linux.test-installation Build done.
buildbot/nix-eval Build done.
checks / checks-impure (push) Successful in 2m15s
buildbot/nix-build .#checks.x86_64-linux.nixos-flash-installer Build done.
buildbot/nix-build .#checks.x86_64-linux.secrets Build done.
buildbot/nix-build .#checks.x86_64-linux.treefmt Build done.
buildbot/nix-build .#checks.x86_64-linux.zt-tcp-relay Build done.
buildbot/nix-build .#checks.x86_64-linux.wayland-proxy-virtwl Build done.
buildbot/nix-build .#checks.x86_64-linux.package-iso-installer Build done.

This commit is contained in:
clan-bot 2024-05-26 12:20:11 +00:00
commit 7da6826344
11 changed files with 114 additions and 32 deletions

View File

@ -1,12 +1,14 @@
from collections.abc import Callable
from typing import Any
from typing import Any, TypeVar
T = TypeVar("T")
class _MethodRegistry:
def __init__(self) -> None:
self._registry: dict[str, Callable] = {}
def register(self, fn: Callable) -> Callable:
def register(self, fn: Callable[..., T]) -> Callable[..., T]:
self._registry[fn.__name__] = fn
return fn

View File

@ -44,6 +44,7 @@ def type_to_dict(t: Any, scope: str = "") -> dict:
elif issubclass(origin, dict):
return {
"type": "object",
"additionalProperties": type_to_dict(t.__args__[1], scope),
}
raise BaseException(f"Error api type not yet supported {t!s}")

View File

@ -1,4 +1,5 @@
import argparse
import dataclasses
import json
import logging
from pathlib import Path
@ -11,18 +12,24 @@ from ..nix import nix_config, nix_eval
log = logging.getLogger(__name__)
@dataclasses.dataclass
class MachineInfo:
machine_name: str
machine_description: str | None
machine_icon: str | None
@API.register
def list_machines(
debug: bool,
flake_url: Path | str,
) -> list[str]:
def list_machines(debug: bool, flake_url: Path | str) -> dict[str, MachineInfo]:
config = nix_config()
system = config["system"]
cmd = nix_eval(
[
f"{flake_url}#clanInternals.machines.{system}",
"--apply",
"builtins.attrNames",
"""builtins.mapAttrs (name: attrs: {
inherit (attrs.config.clanCore) machineDescription machineIcon machineName;
})""",
"--json",
]
)
@ -33,12 +40,27 @@ def list_machines(
proc = run(cmd)
res = proc.stdout.strip()
return json.loads(res)
machines_dict = json.loads(res)
return {
k: MachineInfo(
machine_name=v.get("machineName"),
machine_description=v.get("machineDescription", None),
machine_icon=v.get("machineIcon", None),
)
for k, v in machines_dict.items()
}
def list_command(args: argparse.Namespace) -> None:
for machine in list_machines(args.debug, Path(args.flake)):
print(machine)
flake_path = Path(args.flake).resolve()
print("Listing all machines:\n")
print("Source: ", flake_path)
print("-" * 40)
for name, machine in list_machines(args.debug, flake_path).items():
description = machine.machine_description or "[no description]"
print(f"{name}\n: {description}\n")
print("-" * 40)
def register_list_parser(parser: argparse.ArgumentParser) -> None:

View File

@ -26,6 +26,14 @@ def test_create_flake(
cli.run(["machines", "create", "machine1"])
capsys.readouterr() # flush cache
# create a hardware-configuration.nix that doesn't throw an eval error
for patch_machine in ["jon", "sara"]:
with open(
flake_dir / "machines" / f"{patch_machine}/hardware-configuration.nix", "w"
) as hw_config_nix:
hw_config_nix.write("{}")
cli.run(["machines", "list"])
assert "machine1" in capsys.readouterr().out
flake_show = subprocess.run(

View File

@ -16,7 +16,10 @@ def test_machine_subcommands(
cli.run(["--flake", str(test_flake_with_core.path), "machines", "list"])
out = capsys.readouterr()
assert "machine1\nvm1\nvm2\n" == out.out
assert "machine1" in out.out
assert "vm1" in out.out
assert "vm2" in out.out
cli.run(
["--flake", str(test_flake_with_core.path), "machines", "delete", "machine1"]
@ -25,4 +28,7 @@ def test_machine_subcommands(
capsys.readouterr()
cli.run(["--flake", str(test_flake_with_core.path), "machines", "list"])
out = capsys.readouterr()
assert "vm1\nvm2\n" == out.out
assert "machine1" not in out.out
assert "vm1" in out.out
assert "vm2" in out.out

View File

@ -106,7 +106,7 @@ class MainApplication(Adw.Application):
def on_activate(self, source: "MainApplication") -> None:
if not self.window:
self.init_style()
self.window = MainWindow(config=ClanConfig(initial_view="webview"))
self.window = MainWindow(config=ClanConfig(initial_view="list"))
self.window.set_application(self)
self.window.show()

View File

@ -1,3 +1,4 @@
import dataclasses
import json
import logging
import sys
@ -22,6 +23,23 @@ site_index: Path = (
log = logging.getLogger(__name__)
def dataclass_to_dict(obj: Any) -> Any:
"""
Utility function to convert dataclasses to dictionaries
It converts all nested dataclasses, lists, tuples, and dictionaries to dictionaries
It does NOT convert member functions.
"""
if dataclasses.is_dataclass(obj):
return {k: dataclass_to_dict(v) for k, v in dataclasses.asdict(obj).items()}
elif isinstance(obj, list | tuple):
return [dataclass_to_dict(item) for item in obj]
elif isinstance(obj, dict):
return {k: dataclass_to_dict(v) for k, v in obj.items()}
else:
return obj
class WebView:
def __init__(self, methods: dict[str, Callable]) -> None:
self.method_registry: dict[str, Callable] = methods
@ -82,7 +100,7 @@ class WebView:
with self.mutex_lock:
log.debug("Executing... ", method_name)
result = handler_fn(data)
serialized = json.dumps(result)
serialized = json.dumps(dataclass_to_dict(result))
# Use idle_add to queue the response call to js on the main GTK thread
GLib.idle_add(self.return_data_to_js, method_name, serialized)

View File

@ -62,8 +62,7 @@ class MainWindow(Adw.ApplicationWindow):
stack_view.add_named(Logs(), "logs")
webview = WebView(methods=API._registry)
stack_view.add_named(webview.get_webview(), "list")
stack_view.add_named(webview.get_webview(), "webview")
stack_view.set_visible_child_name(config.initial_view)

View File

@ -1,8 +1,16 @@
import { createSignal, createContext, useContext, JSXElement } from "solid-js";
import { pyApi } from "./message";
import {
createSignal,
createContext,
useContext,
JSXElement,
createEffect,
} from "solid-js";
import { OperationResponse, pyApi } from "./message";
export const makeCountContext = () => {
const [machines, setMachines] = createSignal<string[]>([]);
const [machines, setMachines] = createSignal<
OperationResponse<"list_machines">
>({});
const [loading, setLoading] = createSignal(false);
pyApi.list_machines.receive((machines) => {
@ -10,6 +18,10 @@ export const makeCountContext = () => {
setMachines(machines);
});
createEffect(() => {
console.log("The count is now", machines());
});
return [
{ loading, machines },
{
@ -25,7 +37,10 @@ export const makeCountContext = () => {
type CountContextType = ReturnType<typeof makeCountContext>;
export const CountContext = createContext<CountContextType>([
{ loading: () => false, machines: () => [] },
{
loading: () => false,
machines: () => ({}),
},
{
getMachines: () => {},
},

View File

@ -1,11 +1,11 @@
import { FromSchema } from "json-schema-to-ts";
import { schema } from "@/api";
type API = FromSchema<typeof schema>;
export type API = FromSchema<typeof schema>;
type OperationNames = keyof API;
type OperationArgs<T extends OperationNames> = API[T]["argument"];
type OperationResponse<T extends OperationNames> = API[T]["return"];
export type OperationNames = keyof API;
export type OperationArgs<T extends OperationNames> = API[T]["argument"];
export type OperationResponse<T extends OperationNames> = API[T]["return"];
declare global {
interface Window {
@ -40,7 +40,7 @@ function createFunctions<K extends OperationNames>(
});
},
receive: (fn: (response: OperationResponse<K>) => void) => {
window.clan.list_machines = deserialize(fn);
window.clan[operationName] = deserialize(fn);
},
};
}
@ -59,6 +59,7 @@ const deserialize =
<T>(fn: (response: T) => void) =>
(str: string) => {
try {
console.debug("Received data: ", str);
fn(JSON.parse(str) as T);
} catch (e) {
alert(`Error parsing JSON: ${e}`);

View File

@ -1,24 +1,34 @@
import { For, Match, Switch, type Component } from "solid-js";
import { For, Match, Switch, createEffect, type Component } from "solid-js";
import { useCountContext } from "./Config";
export const Nested: Component = () => {
const [{ machines, loading }, { getMachines }] = useCountContext();
const list = () => Object.values(machines());
createEffect(() => {
console.log("1", list());
});
createEffect(() => {
console.log("2", machines());
});
return (
<div>
<button onClick={() => getMachines()} class="btn btn-primary">
Get machines
</button>
<hr />
<div></div>
<Switch>
<Match when={loading()}>Loading...</Match>
<Match when={!loading() && machines().length === 0}>
<Match when={!loading() && Object.entries(machines()).length === 0}>
No machines found
</Match>
<Match when={!loading() && machines().length}>
<For each={machines()}>
{(machine, i) => (
<Match when={!loading()}>
<For each={list()}>
{(entry, i) => (
<li>
{i() + 1}: {machine}
{i() + 1}: {entry.machine_name}{" "}
{entry.machine_description || "No description"}
</li>
)}
</For>