diff --git a/pkgs/clan-app/clan_app/deps/webview/_webview_ffi.py b/pkgs/clan-app/clan_app/deps/webview/_webview_ffi.py index d719aa15e..051e1e762 100644 --- a/pkgs/clan-app/clan_app/deps/webview/_webview_ffi.py +++ b/pkgs/clan-app/clan_app/deps/webview/_webview_ffi.py @@ -109,9 +109,7 @@ class _WebviewLibrary: self.webview_return = self.lib.webview_return self.webview_return.argtypes = [c_void_p, c_char_p, c_int, c_char_p] - self.binding_callback_t = CFUNCTYPE( - None, c_char_p, c_char_p, c_void_p - ) + self.binding_callback_t = CFUNCTYPE(None, c_char_p, c_char_p, c_void_p) self.CFUNCTYPE = CFUNCTYPE diff --git a/pkgs/clan-app/clan_app/deps/webview/webview.py b/pkgs/clan-app/clan_app/deps/webview/webview.py index ad24fcfd7..f5cd80fa9 100644 --- a/pkgs/clan-app/clan_app/deps/webview/webview.py +++ b/pkgs/clan-app/clan_app/deps/webview/webview.py @@ -66,13 +66,14 @@ class Webview: ) -> None: op_key = op_key_bytes.decode() args = json.loads(request_data.decode()) - log.debug(f"Calling {method_name}({args[0]})") - metadata: dict[str, Any] = {} + log.debug(f"Calling {method_name}({args})") + header: dict[str, Any] try: # Initialize dataclasses from the payload reconciled_arguments = {} - if len(args) > 0: + if len(args) > 1: + header = args[1] for k, v in args[0].items(): # Some functions expect to be called with dataclass instances # But the js api returns dictionaries. @@ -83,6 +84,8 @@ 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] reconciled_arguments["op_key"] = op_key except Exception as e: @@ -109,12 +112,12 @@ class Webview: ctx.should_cancel = lambda: stop_event.is_set() # If the API call has set log_group in metadata, # create the log file under that group. - log_group = metadata.get("logging", {}).get("group", None) + log_group = header.get("logging", {}).get("group", None) if log_group is not None: log.warning( f"Using log group {log_group} for {method_name} with op_key {op_key}" ) - breakpoint() + log_file = log_manager.create_log_file( wrap_method, op_key=op_key, group=log_group ).get_file_path() @@ -136,10 +139,11 @@ class Webview: try: # Original logic: call the wrapped API method. result = wrap_method(**reconciled_arguments) + wrapped_result = {"body": dataclass_to_dict(result), "header": {}} # Serialize the result to JSON. serialized = json.dumps( - dataclass_to_dict(result), indent=4, ensure_ascii=False + dataclass_to_dict(wrapped_result), indent=4, ensure_ascii=False ) # This log message will now also be written to log_f @@ -206,7 +210,6 @@ class Webview: _webview_lib.webview_set_title(self._handle, _encode_c_string(value)) self._title = value - def destroy(self) -> None: for name in list(self._callbacks.keys()): self.unbind(name) diff --git a/pkgs/clan-app/ui-2d/src/api/index.tsx b/pkgs/clan-app/ui-2d/src/api/index.tsx index 73d9b8612..5c72f2edd 100644 --- a/pkgs/clan-app/ui-2d/src/api/index.tsx +++ b/pkgs/clan-app/ui-2d/src/api/index.tsx @@ -46,21 +46,22 @@ interface BackendOpts { } interface BackendReturnType { - result: OperationResponse; - metadata: Record; + body: OperationResponse; + header: Record; } const _callApi = ( method: K, args: OperationArgs, backendOpts?: BackendOpts, -): { promise: Promise>; op_key: string } => { +): { promise: Promise>; op_key: string } => { // if window[method] does not exist, throw an error if (!(method in window)) { console.error(`Method ${method} not found on window object`); // return a rejected promise return { promise: Promise.resolve({ + body: { status: "error", errors: [ { @@ -69,17 +70,19 @@ const _callApi = ( }, ], op_key: "noop", + }, + header: {}, }), op_key: "noop", }; } - let metadata: BackendOpts | undefined = undefined; + let header: BackendOpts = {}; if (backendOpts != undefined) { - metadata = { ...backendOpts }; + header = { ...backendOpts }; let group = backendOpts?.logging?.group; if (group != undefined && isMachine(group)) { - metadata = { + header = { logging: { group: group.flake.identifier + "#" + group.name }, }; } @@ -90,9 +93,10 @@ const _callApi = ( OperationNames, ( args: OperationArgs, - ) => Promise> + metadata: BackendOpts, + ) => Promise> > - )[method](args) as Promise>; + )[method](args, header) as Promise>; // eslint-disable-next-line @typescript-eslint/no-explicit-any const op_key = (promise as any)._webviewMessageId as string; @@ -102,7 +106,7 @@ const _callApi = ( const handleCancel = async ( ops_key: string, - orig_task: Promise>, + orig_task: Promise>, ) => { console.log("Canceling operation: ", ops_key); const { promise, op_key } = _callApi("cancel_task", { task_id: ops_key }); @@ -122,7 +126,7 @@ const handleCancel = async ( }); const resp = await promise; - if (resp.status === "error") { + if (resp.body.status === "error") { toast.custom( (t) => ( ( console.log("Not printing toast because operation was cancelled"); } - const result = response; - if (result.status === "error" && !cancelled) { + const body = response.body; + if (body.status === "error" && !cancelled) { toast.remove(toastId); toast.custom( (t) => ( ), { @@ -203,7 +207,7 @@ export const callApi = ( } else { toast.remove(toastId); } - return result; + return body; }); return { promise: new_promise, op_key: op_key }; diff --git a/pkgs/clan-app/ui-2d/src/components/RemoteForm.tsx b/pkgs/clan-app/ui-2d/src/components/RemoteForm.tsx index fb54b1a69..6e4517798 100644 --- a/pkgs/clan-app/ui-2d/src/components/RemoteForm.tsx +++ b/pkgs/clan-app/ui-2d/src/components/RemoteForm.tsx @@ -207,7 +207,11 @@ export function RemoteForm(props: RemoteFormProps) { flake: props.machine.flake, field: props.field || "targetHost", }, - {logging: { group: { name: props.machine.name, flake: props.machine.flake } },}, + { + logging: { + group: { name: props.machine.name, flake: props.machine.flake }, + }, + }, ).promise; if (result.status === "error") 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 221ff81a6..78f0312a4 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 @@ -53,7 +53,8 @@ export const MachineListItem = (props: MachineListItemProps) => { field: "targetHost", flake: { identifier: active_clan }, name: name, - }, {logging: { group: { name, flake: { identifier: active_clan } } }} + }, + { logging: { group: { name, flake: { identifier: active_clan } } } }, ).promise; if (target_host.status == "error") { @@ -106,13 +107,17 @@ export const MachineListItem = (props: MachineListItemProps) => { } setUpdating(true); - const target_host = await callApi("get_host", { - field: "targetHost", - flake: { identifier: active_clan }, - name: name, - }, { - logging: { group: { name, flake: { identifier: active_clan } } }, - }).promise; + const target_host = await callApi( + "get_host", + { + field: "targetHost", + flake: { identifier: active_clan }, + name: name, + }, + { + logging: { group: { name, flake: { identifier: active_clan } } }, + }, + ).promise; if (target_host.status == "error") { console.error("No target host found for the machine"); @@ -129,11 +134,15 @@ export const MachineListItem = (props: MachineListItemProps) => { return; } - const build_host = await callApi("get_host", { - field: "buildHost", - flake: { identifier: active_clan }, - name: name, - }, {logging: { group: { name, flake: { identifier: active_clan } } }}).promise; + const build_host = await callApi( + "get_host", + { + field: "buildHost", + flake: { identifier: active_clan }, + name: name, + }, + { logging: { group: { name, flake: { identifier: active_clan } } } }, + ).promise; if (build_host.status == "error") { console.error("No target host found for the machine"); @@ -145,16 +154,20 @@ export const MachineListItem = (props: MachineListItemProps) => { return; } - await callApi("deploy_machine", { - machine: { - name: name, - flake: { - identifier: active_clan, + await callApi( + "deploy_machine", + { + machine: { + name: name, + flake: { + identifier: active_clan, + }, }, + target_host: target_host.data!.data, + build_host: build_host.data?.data || null, }, - target_host: target_host.data!.data, - build_host: build_host.data?.data || null, - }, {logging: { group: { name, flake: { identifier: active_clan } } }}).promise; + { logging: { group: { name, flake: { identifier: active_clan } } } }, + ).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 4f6059d4e..0ab716aa6 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 @@ -55,7 +55,7 @@ export function MachineForm(props: MachineFormProps) { ...values.machine, tags: Array.from(values.machine.tags || detailed.machine.tags || []), }, - } ).promise; + }).promise; await queryClient.invalidateQueries({ queryKey: [ @@ -77,10 +77,18 @@ export function MachineForm(props: MachineFormProps) { if (!machine_name || !base_dir) { return []; } - const result = await callApi("get_generators_closure", { - base_dir: base_dir, - machine_name: machine_name, - }, {logging: {group: { name: machine_name, flake: {identifier: base_dir} }}}).promise; + const result = await callApi( + "get_generators_closure", + { + base_dir: base_dir, + machine_name: machine_name, + }, + { + logging: { + group: { name: machine_name, flake: { identifier: base_dir } }, + }, + }, + ).promise; if (result.status === "error") throw new Error("Failed to fetch data"); return result.data; }, @@ -112,13 +120,18 @@ export function MachineForm(props: MachineFormProps) { return; } - const target = await callApi("get_host", { - field: "targetHost", - name: machine, - flake: { - identifier: curr_uri, + const target = await callApi( + "get_host", + { + field: "targetHost", + name: machine, + flake: { + identifier: curr_uri, + }, + }, + { + logging: { group: { name: machine, flake: { identifier: curr_uri } } }, }, - }, {logging: { group: { name: machine, flake: { identifier: curr_uri } } }} ).promise; if (target.status === "error") { @@ -133,18 +146,24 @@ export function MachineForm(props: MachineFormProps) { const target_host = target.data.data; setIsUpdating(true); - const r = await callApi("deploy_machine", { - machine: { - name: machine, - flake: { - identifier: curr_uri, + const r = await callApi( + "deploy_machine", + { + machine: { + name: machine, + flake: { + identifier: curr_uri, + }, }, + target_host: { + ...target_host, + }, + build_host: null, }, - target_host: { - ...target_host, + { + logging: { group: { name: machine, flake: { identifier: curr_uri } } }, }, - build_host: null, - }, {logging: { group: { name: machine, flake: { identifier: curr_uri } } }}).promise.finally(() => { + ).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 0b64af9e0..ddc136f59 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 @@ -149,14 +149,22 @@ export const VarsStep = (props: VarsStepProps) => { const generatorsQuery = createQuery(() => ({ queryKey: [props.dir, props.machine_id, "generators", props.fullClosure], queryFn: async () => { - const result = await callApi("get_generators_closure", { - base_dir: props.dir, - machine_name: props.machine_id, - full_closure: props.fullClosure, - }, {logging: {group: { name: props.machine_id, flake: {identifier: props.dir} }}}).promise; + const result = await callApi( + "get_generators_closure", + { + base_dir: props.dir, + machine_name: props.machine_id, + full_closure: props.fullClosure, + }, + { + logging: { + group: { name: props.machine_id, flake: { identifier: props.dir } }, + }, + }, + ).promise; if (result.status === "error") throw new Error("Failed to fetch data"); return result.data; - }, + }, })); const handleSubmit: SubmitHandler = async (values, event) => { diff --git a/pkgs/clan-app/ui/src/api/index.tsx b/pkgs/clan-app/ui/src/api/index.tsx index bbc40bd16..5c72f2edd 100644 --- a/pkgs/clan-app/ui/src/api/index.tsx +++ b/pkgs/clan-app/ui/src/api/index.tsx @@ -27,9 +27,9 @@ function isMachine(obj: unknown): obj is Machine { return ( !!obj && typeof obj === "object" && - typeof (obj as Machine).name === "string" && - typeof (obj as Machine).flake === "object" && - typeof (obj as Machine).flake.identifier === "string" + typeof (obj as any).name === "string" && + typeof (obj as any).flake === "object" && + typeof (obj as any).flake.identifier === "string" ); } @@ -46,9 +46,8 @@ interface BackendOpts { } interface BackendReturnType { - result: OperationResponse; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - metadata: Record; + body: OperationResponse; + header: Record; } const _callApi = ( @@ -62,7 +61,7 @@ const _callApi = ( // return a rejected promise return { promise: Promise.resolve({ - result: { + body: { status: "error", errors: [ { @@ -72,18 +71,18 @@ const _callApi = ( ], op_key: "noop", }, - metadata: {}, + header: {}, }), op_key: "noop", }; } - let metadata: BackendOpts | undefined = undefined; + let header: BackendOpts = {}; if (backendOpts != undefined) { - metadata = { ...backendOpts }; - const group = backendOpts?.logging?.group; + header = { ...backendOpts }; + let group = backendOpts?.logging?.group; if (group != undefined && isMachine(group)) { - metadata = { + header = { logging: { group: group.flake.identifier + "#" + group.name }, }; } @@ -94,10 +93,10 @@ const _callApi = ( OperationNames, ( args: OperationArgs, - metadata?: BackendOpts, + metadata: BackendOpts, ) => Promise> > - )[method](args, metadata) as Promise>; + )[method](args, header) as Promise>; // eslint-disable-next-line @typescript-eslint/no-explicit-any const op_key = (promise as any)._webviewMessageId as string; @@ -127,7 +126,7 @@ const handleCancel = async ( }); const resp = await promise; - if (resp.result.status === "error") { + if (resp.body.status === "error") { toast.custom( (t) => ( ( args: OperationArgs, backendOpts?: BackendOpts, ): { promise: Promise>; op_key: string } => { - console.log("Calling API", method, args); + console.log("Calling API", method, args, backendOpts); const { promise, op_key } = _callApi(method, args, backendOpts); promise.catch((error) => { @@ -191,14 +190,14 @@ export const callApi = ( console.log("Not printing toast because operation was cancelled"); } - const result = response.result; - if (result.status === "error" && !cancelled) { + const body = response.body; + if (body.status === "error" && !cancelled) { toast.remove(toastId); toast.custom( (t) => ( ), { @@ -208,7 +207,7 @@ export const callApi = ( } else { toast.remove(toastId); } - return result; + return body; }); return { promise: new_promise, op_key: op_key }; diff --git a/pkgs/clan-cli/clan_lib/log_manager/__init__.py b/pkgs/clan-cli/clan_lib/log_manager/__init__.py index fcb7a9f9a..9f85b990e 100644 --- a/pkgs/clan-cli/clan_lib/log_manager/__init__.py +++ b/pkgs/clan-cli/clan_lib/log_manager/__init__.py @@ -310,10 +310,13 @@ class LogManager: base_dir: Path def create_log_file( - self, func: Callable, op_key: str, group: str = "default" + self, func: Callable, op_key: str, group: str | None = None ) -> LogFile: now_utc = datetime.datetime.now(tz=datetime.UTC) + if group is None: + group = "default" + log_file = LogFile( op_key=op_key, date_day=now_utc.strftime("%Y-%m-%d"),