Merge pull request 'api: init notification queue' (#4678) from ui-notify into main

Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4678
This commit is contained in:
hsjobeki
2025-08-11 15:13:42 +00:00
13 changed files with 270 additions and 54 deletions

View File

@@ -5,11 +5,13 @@ from collections.abc import Callable
from dataclasses import dataclass
from functools import wraps
from inspect import Parameter, Signature, signature
from queue import Queue
from types import ModuleType
from typing import (
Annotated,
Any,
Literal,
TypedDict,
TypeVar,
get_type_hints,
)
@@ -31,6 +33,30 @@ T = TypeVar("T")
ResponseDataType = TypeVar("ResponseDataType")
class ProcessMessage(TypedDict):
"""
Represents a message to be sent to the UI.
Attributes:
- topic: The topic of the message, used to identify the type of message.
- data: The data to be sent with the message.
- origin: The API operation that this message is related to, if applicable.
"""
topic: str
data: Any
origin: str | None
message_queue: Queue[ProcessMessage] = Queue()
"""
A global message queue for sending messages to the UI
This can be used to send notifications or messages to the UI. Before returning a response.
The clan-app imports the queue as clan_lib.api.message_queue and subscribes to it.
"""
@dataclass
class ApiError:
message: str

View File

@@ -9,7 +9,7 @@ from clan_cli.facts.generate import generate_facts
from clan_cli.machines.hardware import HardwareConfig
from clan_cli.vars.generate import generate_vars
from clan_lib.api import API
from clan_lib.api import API, message_queue
from clan_lib.cmd import Log, RunOpts, run
from clan_lib.machines.machines import Machine
from clan_lib.nix import nix_config, nix_shell
@@ -22,6 +22,20 @@ log = logging.getLogger(__name__)
BuildOn = Literal["auto", "local", "remote"]
Step = Literal["generators", "upload-secrets", "nixos-anywhere"]
def notify_install_step(current: Step) -> None:
message_queue.put(
{
"topic": current,
"data": None,
# MUST be set the to api function name, while technically you can set any origin, this is a bad idea.
"origin": "run_machine_install",
}
)
@dataclass
class InstallOptions:
machine: Machine
@@ -64,6 +78,8 @@ def run_machine_install(opts: InstallOptions, target_host: Remote) -> None:
]
)
# Notify the UI about what we are doing
notify_install_step("generators")
generate_facts([machine])
generate_vars([machine])
@@ -74,6 +90,9 @@ def run_machine_install(opts: InstallOptions, target_host: Remote) -> None:
activation_secrets = base_directory / "activation_secrets"
upload_dir = activation_secrets / machine.secrets_upload_directory.lstrip("/")
upload_dir.mkdir(parents=True)
# Notify the UI about what we are doing
notify_install_step("upload-secrets")
machine.secret_facts_store.upload(upload_dir)
machine.secret_vars_store.populate_dir(
machine.name, upload_dir, phases=["activation", "users", "services"]
@@ -174,4 +193,6 @@ def run_machine_install(opts: InstallOptions, target_host: Remote) -> None:
["nixos-anywhere"],
cmd,
)
notify_install_step("nixos-anywhere")
run(cmd, RunOpts(log=Log.BOTH, prefix=machine.name, needs_user_terminal=True))