diff --git a/pkgs/clan-app/clan_app/views/webview.py b/pkgs/clan-app/clan_app/views/webview.py index 99674737d..40835723b 100644 --- a/pkgs/clan-app/clan_app/views/webview.py +++ b/pkgs/clan-app/clan_app/views/webview.py @@ -1,4 +1,3 @@ -import dataclasses import json import logging from typing import Any diff --git a/pkgs/clan-cli/clan_cli/api/serde.py b/pkgs/clan-cli/clan_cli/api/serde.py index 34325cf5b..a3ff265d5 100644 --- a/pkgs/clan-cli/clan_cli/api/serde.py +++ b/pkgs/clan-cli/clan_cli/api/serde.py @@ -249,6 +249,6 @@ def from_dict(t: type[G], data: dict[str, Any] | Any, path: list[str] = []) -> G if is_dataclass(t): if not isinstance(data, dict): raise ClanError(f"{data} is not a dict. Expected {t}") - return construct_dataclass(t, data, path) + return construct_dataclass(t, data, path) # type: ignore else: return construct_value(t, data, path) diff --git a/pkgs/clan-cli/clan_cli/machines/update.py b/pkgs/clan-cli/clan_cli/machines/update.py index 6151e4d81..c0beae8b3 100644 --- a/pkgs/clan-cli/clan_cli/machines/update.py +++ b/pkgs/clan-cli/clan_cli/machines/update.py @@ -5,6 +5,7 @@ import os import shlex import sys +from clan_cli.api import API from clan_cli.clan_uri import FlakeId from ..cmd import run @@ -12,15 +13,13 @@ from ..completions import add_dynamic_completer, complete_machines from ..errors import ClanError from ..facts.generate import generate_facts from ..facts.upload import upload_secrets -from ..machines.machines import Machine -from clan_cli.inventory import from_dict from ..inventory import Machine as InventoryMachine +from ..machines.machines import Machine from ..nix import nix_command, nix_metadata from ..ssh import HostKeyCheck from ..vars.generate import generate_vars from .inventory import get_all_machines, get_selected_machines from .machine_group import MachineGroup -from clan_cli.api import API log = logging.getLogger(__name__) diff --git a/pkgs/webview-ui/app/src/components/MachineListItem.tsx b/pkgs/webview-ui/app/src/components/MachineListItem.tsx index e241d49ce..6a41b23b6 100644 --- a/pkgs/webview-ui/app/src/components/MachineListItem.tsx +++ b/pkgs/webview-ui/app/src/components/MachineListItem.tsx @@ -16,8 +16,91 @@ interface MachineListItemProps { export const MachineListItem = (props: MachineListItemProps) => { const { name, info, nixOnly } = props; - const [deploying, setDeploying] = createSignal(false); + // Bootstrapping + const [installing, setInstalling] = createSignal(false); + + // Later only updates + const [updating, setUpdating] = createSignal(false); + const navigate = useNavigate(); + + const handleInstall = async () => { + if (!info?.deploy.targetHost || installing()) { + return; + } + + const active_clan = activeURI(); + if (!active_clan) { + toast.error("No active clan selected"); + return; + } + if (!info?.deploy.targetHost) { + toast.error( + "Machine does not have a target host. Specify where the machine should be deployed.", + ); + return; + } + setInstalling(true); + await toast.promise( + callApi("install_machine", { + opts: { + machine: name, + flake: { + loc: active_clan, + }, + no_reboot: true, + target_host: info?.deploy.targetHost, + debug: true, + nix_options: [], + }, + password: null, + }), + { + loading: "Installing...", + success: "Installed", + error: "Failed to install", + }, + ); + setInstalling(false); + }; + + const handleUpdate = async () => { + if (!info?.deploy.targetHost || installing()) { + return; + } + + const active_clan = activeURI(); + if (!active_clan) { + toast.error("No active clan selected"); + return; + } + if (!info?.deploy.targetHost) { + toast.error( + "Machine does not have a target host. Specify where the machine should be deployed.", + ); + return; + } + setUpdating(true); + await toast.promise( + callApi("update_machines", { + base_path: active_clan, + machines: [ + { + name: name, + deploy: { + targetHost: info?.deploy.targetHost, + }, + }, + ], + }), + { + loading: "Updating...", + success: "Updated", + error: "Failed to update", + }, + ); + setUpdating(false); + }; return (
  • @@ -73,51 +156,25 @@ export const MachineListItem = (props: MachineListItemProps) => {
  • { - if (!info?.deploy.targetHost || deploying()) { - return; - } - - const active_clan = activeURI(); - if (!active_clan) { - toast.error("No active clan selected"); - return; - } - if (!info?.deploy.targetHost) { - toast.error( - "Machine does not have a target host. Specify where the machine should be deployed.", - ); - return; - } - setDeploying(true); - await toast.promise( - callApi("install_machine", { - opts: { - machine: name, - flake: { - loc: active_clan, - }, - no_reboot: true, - target_host: info?.deploy.targetHost, - debug: true, - nix_options: [], - }, - password: null, - }), - { - loading: "Deploying...", - success: "Deployed", - error: "Failed to deploy", - }, - ); - setDeploying(false); + disabled: !info?.deploy.targetHost || installing(), }} + onClick={handleInstall} > - {(d) => `Deploy to ${d()}`} + {(d) => `Install to ${d()}`} + + +
  • +
  • + + + {(d) => `Update (${d()})`}