From 52aaad272f5c018073d6f6a518278a29398be69f Mon Sep 17 00:00:00 2001 From: Qubasa Date: Fri, 4 Jul 2025 17:46:29 +0700 Subject: [PATCH] clan-app: Implement dynamic log groups into javascript callApi nix fmt --- pkgs/clan-app/clan_app/app.py | 13 +++-- .../clan-app/clan_app/deps/webview/webview.py | 29 +++++++--- pkgs/clan-app/ui-2d/src/api/index.tsx | 58 ++++++------------- .../ui-2d/src/components/RemoteForm.tsx | 8 ++- .../components/machine-list-item/index.tsx | 20 +++++-- .../machines/components/MachineForm.tsx | 10 +++- .../src/routes/machines/install/vars-step.tsx | 2 +- .../src/routes/machines/machine-install.tsx | 20 +++++-- .../src/routes/machines/machines-list.tsx | 1 - pkgs/clan-app/ui/src/api/index.tsx | 58 ++++++------------- pkgs/clan-cli/clan_lib/log_manager/api.py | 4 +- 11 files changed, 111 insertions(+), 112 deletions(-) diff --git a/pkgs/clan-app/clan_app/app.py b/pkgs/clan-app/clan_app/app.py index 5654f0f4f..eef27a844 100644 --- a/pkgs/clan-app/clan_app/app.py +++ b/pkgs/clan-app/clan_app/app.py @@ -11,7 +11,7 @@ import clan_lib.machines.actions # noqa: F401 from clan_lib.api import API, load_in_all_api_functions, tasks from clan_lib.custom_logger import setup_logging from clan_lib.dirs import user_data_dir -from clan_lib.log_manager import LogManager +from clan_lib.log_manager import LogGroupConfig, LogManager from clan_lib.log_manager import api as log_manager_api from clan_app.api.file_gtk import open_file @@ -41,12 +41,15 @@ def app_run(app_opts: ClanAppOptions) -> int: webview = Webview(debug=app_opts.debug) webview.title = "Clan App" - # This seems to call the gtk api correctly but and gtk also seems to our icon, but somehow the icon is not loaded. - # Init LogManager global in log_manager_api module - log_manager_api.LOG_MANAGER_INSTANCE = LogManager( - base_dir=user_data_dir() / "clan-app" / "logs" + # Add a log group ["clans", , "machines", ] + log_manager = LogManager(base_dir=user_data_dir() / "clan-app" / "logs") + clan_log_group = LogGroupConfig("clans", "Clans").add_child( + LogGroupConfig("machines", "Machines") ) + log_manager = log_manager.add_root_group_config(clan_log_group) + # Init LogManager global in log_manager_api module + log_manager_api.LOG_MANAGER_INSTANCE = log_manager # Init BAKEND_THREADS global in tasks module tasks.BAKEND_THREADS = webview.threads diff --git a/pkgs/clan-app/clan_app/deps/webview/webview.py b/pkgs/clan-app/clan_app/deps/webview/webview.py index 6653b17b3..2c6ad5be0 100644 --- a/pkgs/clan-app/clan_app/deps/webview/webview.py +++ b/pkgs/clan-app/clan_app/deps/webview/webview.py @@ -1,3 +1,4 @@ +# ruff: noqa: TRY301 import functools import io import json @@ -66,15 +67,24 @@ class Webview: ) -> None: op_key = op_key_bytes.decode() args = json.loads(request_data.decode()) - log.debug(f"Calling {method_name}({args})") + log.debug(f"Calling {method_name}({json.dumps(args, indent=4)})") header: dict[str, Any] try: # Initialize dataclasses from the payload reconciled_arguments = {} - if len(args) > 1: - header = args[1] - for k, v in args[0].items(): + if len(args) == 1: + request = args[0] + header = request.get("header", {}) + msg = f"Expected header to be a dict, got {type(header)}" + if not isinstance(header, dict): + raise TypeError(msg) + body = request.get("body", {}) + msg = f"Expected body to be a dict, got {type(body)}" + if not isinstance(body, dict): + raise TypeError(msg) + + for k, v in body.items(): # Some functions expect to be called with dataclass instances # But the js api returns dictionaries. # Introspect the function and create the expected dataclass from dict dynamically @@ -84,8 +94,11 @@ class Webview: # TODO: rename from_dict into something like construct_checked_value # from_dict really takes Anything and returns an instance of the type/class reconciled_arguments[k] = from_dict(arg_class, v) - elif len(args) == 1: - header = args[0] + elif len(args) > 1: + msg = ( + "Expected a single argument, got multiple arguments to api_wrapper" + ) + raise ValueError(msg) reconciled_arguments["op_key"] = op_key except Exception as e: @@ -114,11 +127,11 @@ class Webview: try: # If the API call has set log_group in metadata, # create the log file under that group. - log_group: list[str] = header.get("logging", {}).get("group", None) + log_group: list[str] = header.get("logging", {}).get("group_path", None) if log_group is not None: if not isinstance(log_group, list): msg = f"Expected log_group to be a list, got {type(log_group)}" - raise TypeError(msg) # noqa: TRY301 + raise TypeError(msg) log.warning( f"Using log group {log_group} for {method_name} with op_key {op_key}" ) diff --git a/pkgs/clan-app/ui-2d/src/api/index.tsx b/pkgs/clan-app/ui-2d/src/api/index.tsx index f75bc5942..cc02db123 100644 --- a/pkgs/clan-app/ui-2d/src/api/index.tsx +++ b/pkgs/clan-app/ui-2d/src/api/index.tsx @@ -23,42 +23,25 @@ export type SuccessQuery = Extract< >; export type SuccessData = SuccessQuery["data"]; -function isMachine(obj: unknown): obj is Machine { - return ( - !!obj && - typeof obj === "object" && - // eslint-disable-next-line @typescript-eslint/no-explicit-any - typeof (obj as any).name === "string" && - // eslint-disable-next-line @typescript-eslint/no-explicit-any - typeof (obj as any).flake === "object" && - // eslint-disable-next-line @typescript-eslint/no-explicit-any - 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 SendHeaderType { + logging?: { group_path: string[] }; +} +interface BackendSendType { + body: OperationArgs; + header?: SendHeaderType; } +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +interface ReceiveHeaderType {} interface BackendReturnType { body: OperationResponse; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - header: Record; + header: ReceiveHeaderType; } const _callApi = ( method: K, args: OperationArgs, - backendOpts?: BackendOpts, + backendOpts?: SendHeaderType, ): { promise: Promise>; op_key: string } => { // if window[method] does not exist, throw an error if (!(method in window)) { @@ -82,26 +65,19 @@ const _callApi = ( }; } - let header: BackendOpts = {}; - if (backendOpts != undefined) { - header = { ...backendOpts }; - const group = backendOpts?.logging?.group; - if (group != undefined && isMachine(group)) { - header = { - logging: { group: group.flake.identifier + "#" + group.name }, - }; - } - } + const message: BackendSendType = { + body: args, + header: backendOpts, + }; const promise = ( window as unknown as Record< OperationNames, ( - args: OperationArgs, - metadata: BackendOpts, + args: BackendSendType, ) => Promise> > - )[method](args, header) as Promise>; + )[method](message) as Promise>; // eslint-disable-next-line @typescript-eslint/no-explicit-any const op_key = (promise as any)._webviewMessageId as string; @@ -153,7 +129,7 @@ const handleCancel = async ( export const callApi = ( method: K, args: OperationArgs, - backendOpts?: BackendOpts, + backendOpts?: SendHeaderType, ): { promise: Promise>; op_key: string } => { console.log("Calling API", method, args, backendOpts); diff --git a/pkgs/clan-app/ui-2d/src/components/RemoteForm.tsx b/pkgs/clan-app/ui-2d/src/components/RemoteForm.tsx index 6e4517798..486fe2598 100644 --- a/pkgs/clan-app/ui-2d/src/components/RemoteForm.tsx +++ b/pkgs/clan-app/ui-2d/src/components/RemoteForm.tsx @@ -186,6 +186,7 @@ export function RemoteForm(props: RemoteFormProps) { props.queryFn, props.machine?.name, props.machine?.flake, + props.machine?.flake.identifier, props.field || "targetHost", ], queryFn: async () => { @@ -209,7 +210,12 @@ export function RemoteForm(props: RemoteFormProps) { }, { logging: { - group: { name: props.machine.name, flake: props.machine.flake }, + group_path: [ + "clans", + props.machine.flake.identifier, + "machines", + props.machine.name, + ], }, }, ).promise; diff --git a/pkgs/clan-app/ui-2d/src/components/machine-list-item/index.tsx b/pkgs/clan-app/ui-2d/src/components/machine-list-item/index.tsx index 78f0312a4..e6c7010af 100644 --- a/pkgs/clan-app/ui-2d/src/components/machine-list-item/index.tsx +++ b/pkgs/clan-app/ui-2d/src/components/machine-list-item/index.tsx @@ -54,7 +54,9 @@ export const MachineListItem = (props: MachineListItemProps) => { flake: { identifier: active_clan }, name: name, }, - { logging: { group: { name, flake: { identifier: active_clan } } } }, + { + logging: { group_path: ["clans", active_clan, "machines", name] }, + }, ).promise; if (target_host.status == "error") { @@ -115,7 +117,9 @@ export const MachineListItem = (props: MachineListItemProps) => { name: name, }, { - logging: { group: { name, flake: { identifier: active_clan } } }, + logging: { + group_path: ["clans", active_clan, "machines", name], + }, }, ).promise; @@ -141,7 +145,11 @@ export const MachineListItem = (props: MachineListItemProps) => { flake: { identifier: active_clan }, name: name, }, - { logging: { group: { name, flake: { identifier: active_clan } } } }, + { + logging: { + group_path: ["clans", active_clan, "machines", name], + }, + }, ).promise; if (build_host.status == "error") { @@ -166,7 +174,11 @@ export const MachineListItem = (props: MachineListItemProps) => { target_host: target_host.data!.data, build_host: build_host.data?.data || null, }, - { logging: { group: { name, flake: { identifier: active_clan } } } }, + { + logging: { + group_path: ["clans", active_clan, "machines", name], + }, + }, ).promise; setUpdating(false); diff --git a/pkgs/clan-app/ui-2d/src/routes/machines/components/MachineForm.tsx b/pkgs/clan-app/ui-2d/src/routes/machines/components/MachineForm.tsx index 0ab716aa6..407321c82 100644 --- a/pkgs/clan-app/ui-2d/src/routes/machines/components/MachineForm.tsx +++ b/pkgs/clan-app/ui-2d/src/routes/machines/components/MachineForm.tsx @@ -85,7 +85,7 @@ export function MachineForm(props: MachineFormProps) { }, { logging: { - group: { name: machine_name, flake: { identifier: base_dir } }, + group_path: ["clans", base_dir, "machines", machine_name], }, }, ).promise; @@ -130,7 +130,9 @@ export function MachineForm(props: MachineFormProps) { }, }, { - logging: { group: { name: machine, flake: { identifier: curr_uri } } }, + logging: { + group_path: ["clans", curr_uri, "machines", machine], + }, }, ).promise; @@ -161,7 +163,9 @@ export function MachineForm(props: MachineFormProps) { build_host: null, }, { - logging: { group: { name: machine, flake: { identifier: curr_uri } } }, + logging: { + group_path: ["clans", curr_uri, "machines", machine], + }, }, ).promise.finally(() => { setIsUpdating(false); diff --git a/pkgs/clan-app/ui-2d/src/routes/machines/install/vars-step.tsx b/pkgs/clan-app/ui-2d/src/routes/machines/install/vars-step.tsx index ddc136f59..d257ebcb5 100644 --- a/pkgs/clan-app/ui-2d/src/routes/machines/install/vars-step.tsx +++ b/pkgs/clan-app/ui-2d/src/routes/machines/install/vars-step.tsx @@ -158,7 +158,7 @@ export const VarsStep = (props: VarsStepProps) => { }, { logging: { - group: { name: props.machine_id, flake: { identifier: props.dir } }, + group_path: ["clans", props.dir, "machines", props.machine_id], }, }, ).promise; diff --git a/pkgs/clan-app/ui-2d/src/routes/machines/machine-install.tsx b/pkgs/clan-app/ui-2d/src/routes/machines/machine-install.tsx index 8a3a91780..c60cb8d13 100644 --- a/pkgs/clan-app/ui-2d/src/routes/machines/machine-install.tsx +++ b/pkgs/clan-app/ui-2d/src/routes/machines/machine-install.tsx @@ -16,14 +16,22 @@ export const MachineInstall = () => { queryFn: async () => { const curr = activeClanURI(); if (curr) { - const result = await callApi("get_machine_details", { - machine: { - flake: { - identifier: curr, + const result = await callApi( + "get_machine_details", + { + machine: { + flake: { + identifier: curr, + }, + name: params.id, }, - name: params.id, }, - }).promise; + { + logging: { + group_path: ["clans", curr, "machines", params.id], + }, + }, + ).promise; if (result.status === "error") throw new Error("Failed to fetch data"); return result.data; } diff --git a/pkgs/clan-app/ui-2d/src/routes/machines/machines-list.tsx b/pkgs/clan-app/ui-2d/src/routes/machines/machines-list.tsx index fd043494d..27d9ba9a4 100644 --- a/pkgs/clan-app/ui-2d/src/routes/machines/machines-list.tsx +++ b/pkgs/clan-app/ui-2d/src/routes/machines/machines-list.tsx @@ -8,7 +8,6 @@ import Icon from "@/src/components/icon"; import { Header } from "@/src/layout/header"; import { makePersisted } from "@solid-primitives/storage"; import { useClanContext } from "@/src/contexts/clan"; -import { debug } from "console"; type MachinesModel = Extract< OperationResponse<"list_machines">, diff --git a/pkgs/clan-app/ui/src/api/index.tsx b/pkgs/clan-app/ui/src/api/index.tsx index f75bc5942..cc02db123 100644 --- a/pkgs/clan-app/ui/src/api/index.tsx +++ b/pkgs/clan-app/ui/src/api/index.tsx @@ -23,42 +23,25 @@ export type SuccessQuery = Extract< >; export type SuccessData = SuccessQuery["data"]; -function isMachine(obj: unknown): obj is Machine { - return ( - !!obj && - typeof obj === "object" && - // eslint-disable-next-line @typescript-eslint/no-explicit-any - typeof (obj as any).name === "string" && - // eslint-disable-next-line @typescript-eslint/no-explicit-any - typeof (obj as any).flake === "object" && - // eslint-disable-next-line @typescript-eslint/no-explicit-any - 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 SendHeaderType { + logging?: { group_path: string[] }; +} +interface BackendSendType { + body: OperationArgs; + header?: SendHeaderType; } +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +interface ReceiveHeaderType {} interface BackendReturnType { body: OperationResponse; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - header: Record; + header: ReceiveHeaderType; } const _callApi = ( method: K, args: OperationArgs, - backendOpts?: BackendOpts, + backendOpts?: SendHeaderType, ): { promise: Promise>; op_key: string } => { // if window[method] does not exist, throw an error if (!(method in window)) { @@ -82,26 +65,19 @@ const _callApi = ( }; } - let header: BackendOpts = {}; - if (backendOpts != undefined) { - header = { ...backendOpts }; - const group = backendOpts?.logging?.group; - if (group != undefined && isMachine(group)) { - header = { - logging: { group: group.flake.identifier + "#" + group.name }, - }; - } - } + const message: BackendSendType = { + body: args, + header: backendOpts, + }; const promise = ( window as unknown as Record< OperationNames, ( - args: OperationArgs, - metadata: BackendOpts, + args: BackendSendType, ) => Promise> > - )[method](args, header) as Promise>; + )[method](message) as Promise>; // eslint-disable-next-line @typescript-eslint/no-explicit-any const op_key = (promise as any)._webviewMessageId as string; @@ -153,7 +129,7 @@ const handleCancel = async ( export const callApi = ( method: K, args: OperationArgs, - backendOpts?: BackendOpts, + backendOpts?: SendHeaderType, ): { promise: Promise>; op_key: string } => { console.log("Calling API", method, args, backendOpts); diff --git a/pkgs/clan-cli/clan_lib/log_manager/api.py b/pkgs/clan-cli/clan_lib/log_manager/api.py index 9c6d57b1f..db51d3b3d 100644 --- a/pkgs/clan-cli/clan_lib/log_manager/api.py +++ b/pkgs/clan-cli/clan_lib/log_manager/api.py @@ -20,7 +20,9 @@ def list_log_days() -> list[str]: @API.register -def list_log_groups(selector: list[str], date_day: str | None = None) -> list[str]: +def list_log_groups( + selector: list[str] | None, date_day: str | None = None +) -> list[str]: """List all log groups at the specified hierarchical path. Args: