Merge pull request 'Clan-cli: remove unused show machine' (#1888) from hsjobeki/clan-core:hsjobeki-main into main
This commit is contained in:
@@ -6,7 +6,6 @@ from .delete import register_delete_parser
|
||||
from .hardware import register_hw_generate
|
||||
from .install import register_install_parser
|
||||
from .list import register_list_parser
|
||||
from .show import register_show_parser
|
||||
from .update import register_update_parser
|
||||
|
||||
|
||||
@@ -86,17 +85,6 @@ For more detailed information, visit: https://docs.clan.lol/getting-started/conf
|
||||
)
|
||||
register_hw_generate(generate_hw_parser)
|
||||
|
||||
show_parser = subparser.add_parser(
|
||||
"show",
|
||||
help="Show a machine",
|
||||
epilog=(
|
||||
"""
|
||||
This subcommand shows the details of a machine managed by this clan like icon, description, etc
|
||||
"""
|
||||
),
|
||||
)
|
||||
register_show_parser(show_parser)
|
||||
|
||||
install_parser = subparser.add_parser(
|
||||
"install",
|
||||
help="Install a machine",
|
||||
|
||||
@@ -114,7 +114,7 @@ def generate_machine_hardware_info(
|
||||
"-o StrictHostKeyChecking=no",
|
||||
# Disable known hosts file
|
||||
"-o UserKnownHostsFile=/dev/null",
|
||||
f"root@{hostname}",
|
||||
f"{hostname}",
|
||||
"nixos-generate-config",
|
||||
# Filesystems are managed by disko
|
||||
"--no-filesystems",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
from clan_cli.api import API
|
||||
@@ -18,6 +19,33 @@ def list_inventory_machines(flake_url: str | Path) -> dict[str, Machine]:
|
||||
return inventory.machines
|
||||
|
||||
|
||||
@dataclass
|
||||
class MachineDetails:
|
||||
machine: Machine
|
||||
has_hw_specs: bool = False
|
||||
# TODO:
|
||||
# has_disk_specs: bool = False
|
||||
|
||||
|
||||
@API.register
|
||||
def get_inventory_machine_details(
|
||||
flake_url: str | Path, machine_name: str
|
||||
) -> MachineDetails:
|
||||
inventory = load_inventory_eval(flake_url)
|
||||
machine = inventory.machines.get(machine_name)
|
||||
if machine is None:
|
||||
raise ClanError(f"Machine {machine_name} not found in inventory")
|
||||
|
||||
hw_config_path = (
|
||||
Path(flake_url) / "machines" / Path(machine_name) / "hardware-configuration.nix"
|
||||
)
|
||||
|
||||
return MachineDetails(
|
||||
machine=machine,
|
||||
has_hw_specs=hw_config_path.exists(),
|
||||
)
|
||||
|
||||
|
||||
@API.register
|
||||
def list_nixos_machines(flake_url: str | Path) -> list[str]:
|
||||
cmd = nix_eval(
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
import argparse
|
||||
import dataclasses
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from clan_cli.api import API
|
||||
|
||||
from ..cmd import run_no_stdout
|
||||
from ..completions import add_dynamic_completer, complete_machines
|
||||
from ..nix import nix_config, nix_eval
|
||||
from .types import machine_name_type
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class MachineInfo:
|
||||
machine_name: str
|
||||
machine_description: str | None
|
||||
machine_icon: str | None
|
||||
|
||||
|
||||
@API.register
|
||||
def show_machine(flake_url: str | Path, machine_name: str) -> MachineInfo:
|
||||
config = nix_config()
|
||||
system = config["system"]
|
||||
cmd = nix_eval(
|
||||
[
|
||||
f"{flake_url}#clanInternals.machines.{system}.{machine_name}",
|
||||
"--apply",
|
||||
"machine: { inherit (machine.config.clan.core) machineDescription machineIcon machineName; }",
|
||||
"--json",
|
||||
]
|
||||
)
|
||||
proc = run_no_stdout(cmd)
|
||||
res = proc.stdout.strip()
|
||||
machine = json.loads(res)
|
||||
|
||||
return MachineInfo(
|
||||
machine_name=machine.get("machineName"),
|
||||
machine_description=machine.get("machineDescription", None),
|
||||
machine_icon=machine.get("machineIcon", None),
|
||||
)
|
||||
|
||||
|
||||
def show_command(args: argparse.Namespace) -> None:
|
||||
machine = show_machine(args.flake.path, args.machine)
|
||||
print(f"Name: {machine.machine_name}")
|
||||
print(f"Description: {machine.machine_description or ''}")
|
||||
print(f"Icon: {machine.machine_icon or ''}")
|
||||
|
||||
|
||||
def register_show_parser(parser: argparse.ArgumentParser) -> None:
|
||||
parser.set_defaults(func=show_command)
|
||||
machine_parser = parser.add_argument(
|
||||
"machine", help="the name of the machine", type=machine_name_type
|
||||
)
|
||||
add_dynamic_completer(machine_parser, complete_machines)
|
||||
@@ -21,10 +21,6 @@ def test_machine_subcommands(
|
||||
assert "vm2" in out.out
|
||||
|
||||
capsys.readouterr()
|
||||
cli.run(["machines", "show", "--flake", str(test_flake_with_core.path), "machine1"])
|
||||
out = capsys.readouterr()
|
||||
assert "machine1" in out.out
|
||||
assert "Description" in out.out
|
||||
print(out)
|
||||
|
||||
cli.run(
|
||||
|
||||
@@ -3,6 +3,7 @@ import { callApi, SuccessData } from "../api";
|
||||
import { Menu } from "./Menu";
|
||||
import { activeURI } from "../App";
|
||||
import toast from "solid-toast";
|
||||
import { useNavigate } from "@solidjs/router";
|
||||
|
||||
type MachineDetails = SuccessData<"list_inventory_machines">["data"][string];
|
||||
|
||||
@@ -16,7 +17,7 @@ export const MachineListItem = (props: MachineListItemProps) => {
|
||||
const { name, info, nixOnly } = props;
|
||||
|
||||
const [deploying, setDeploying] = createSignal<boolean>(false);
|
||||
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<li>
|
||||
<div class="card card-side m-2 bg-base-200">
|
||||
@@ -63,11 +64,11 @@ export const MachineListItem = (props: MachineListItemProps) => {
|
||||
<ul class="menu z-[1] w-52 rounded-box bg-base-100 p-2 shadow">
|
||||
<li>
|
||||
<a
|
||||
// onClick={() => {
|
||||
// setRoute("machines/edit");
|
||||
// }}
|
||||
onClick={() => {
|
||||
navigate("/machines/" + name);
|
||||
}}
|
||||
>
|
||||
Edit
|
||||
Details
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
|
||||
@@ -3,10 +3,15 @@ import { Header } from "./header";
|
||||
import { Sidebar } from "../Sidebar";
|
||||
import { activeURI, clanList } from "../App";
|
||||
import { RouteSectionProps } from "@solidjs/router";
|
||||
import { Toaster } from "solid-toast";
|
||||
|
||||
export const Layout: Component<RouteSectionProps> = (props) => {
|
||||
createEffect(() => {
|
||||
console.log("Layout props", props.location);
|
||||
});
|
||||
return (
|
||||
<div class="h-screen bg-gradient-to-b from-white to-base-100 p-4">
|
||||
<Toaster position="top-right" />
|
||||
<div class="drawer lg:drawer-open ">
|
||||
<input
|
||||
id="toplevel-drawer"
|
||||
|
||||
@@ -1,6 +1,110 @@
|
||||
import { callApi } from "@/src/api";
|
||||
import { activeURI } from "@/src/App";
|
||||
import { useParams } from "@solidjs/router";
|
||||
import { createQuery } from "@tanstack/solid-query";
|
||||
import { createSignal, Show } from "solid-js";
|
||||
import toast from "solid-toast";
|
||||
|
||||
export const MachineDetails = () => {
|
||||
const params = useParams();
|
||||
return <div>Machine Details: {params.id}</div>;
|
||||
const query = createQuery(() => ({
|
||||
queryKey: [activeURI(), "machine", params.id],
|
||||
queryFn: async () => {
|
||||
const curr = activeURI();
|
||||
if (curr) {
|
||||
const result = await callApi("get_inventory_machine_details", {
|
||||
flake_url: curr,
|
||||
machine_name: params.id,
|
||||
});
|
||||
if (result.status === "error") throw new Error("Failed to fetch data");
|
||||
return result.data;
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
const [sshKey, setSshKey] = createSignal<string>();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{query.isLoading && <span class="loading loading-bars" />}
|
||||
<Show when={!query.isLoading && query.data}>
|
||||
{(data) => (
|
||||
<div class="grid grid-cols-2 gap-2 text-lg">
|
||||
<label class="justify-self-end font-light">Name</label>
|
||||
<span>{data().machine.name}</span>
|
||||
<span class="justify-self-end font-light">description</span>
|
||||
<span>{data().machine.description}</span>
|
||||
<span class="justify-self-end font-light">targetHost</span>
|
||||
<span>{data().machine.deploy.targetHost}</span>
|
||||
<div class="join col-span-2 justify-self-center">
|
||||
<button
|
||||
class="btn join-item btn-sm"
|
||||
onClick={async () => {
|
||||
const response = await callApi("open_file", {
|
||||
file_request: {
|
||||
title: "Select SSH Key",
|
||||
mode: "open_file",
|
||||
initial_folder: "~/.ssh",
|
||||
},
|
||||
});
|
||||
if (response.status === "success" && response.data) {
|
||||
setSshKey(response.data[0]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span>{sshKey() || "Default ssh key"}</span>
|
||||
</button>
|
||||
<button
|
||||
disabled={!sshKey()}
|
||||
class="btn btn-accent join-item btn-sm"
|
||||
onClick={() => setSshKey(undefined)}
|
||||
>
|
||||
<span class="material-icons">close</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<span class="justify-self-end font-light">has hw spec</span>
|
||||
<span>{data().has_hw_specs ? "Yes" : "Not yet"}</span>
|
||||
<div class="col-span-2 justify-self-center">
|
||||
<button
|
||||
class="btn btn-primary join-item btn-sm"
|
||||
onClick={async () => {
|
||||
const curr_uri = activeURI();
|
||||
if (!curr_uri) {
|
||||
return;
|
||||
}
|
||||
if (!query.data?.machine.deploy.targetHost) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lt = toast.loading("Generating HW spec ...");
|
||||
const response = await callApi(
|
||||
"generate_machine_hardware_info",
|
||||
{
|
||||
machine_name: params.id,
|
||||
clan_dir: curr_uri,
|
||||
hostname: query.data.machine.deploy.targetHost,
|
||||
},
|
||||
);
|
||||
toast.dismiss(lt);
|
||||
|
||||
if (response.status === "success") {
|
||||
toast.success("HW specification processed successfully");
|
||||
}
|
||||
if (response.status === "error") {
|
||||
toast.error(
|
||||
"Failed to generate. " + response.errors[0].message,
|
||||
);
|
||||
}
|
||||
query.refetch();
|
||||
}}
|
||||
>
|
||||
Generate HW Spec
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { callApi, OperationArgs } from "@/src/api";
|
||||
import { activeURI } from "@/src/App";
|
||||
import { TextInput } from "@/src/components/TextInput";
|
||||
import { createForm, required, reset } from "@modular-forms/solid";
|
||||
import { useNavigate } from "@solidjs/router";
|
||||
import { createQuery, useQueryClient } from "@tanstack/solid-query";
|
||||
import { Match, Switch } from "solid-js";
|
||||
import toast from "solid-toast";
|
||||
@@ -9,6 +10,7 @@ import toast from "solid-toast";
|
||||
type CreateMachineForm = OperationArgs<"create_machine">;
|
||||
|
||||
export function CreateMachine() {
|
||||
const navigate = useNavigate();
|
||||
const [formStore, { Form, Field }] = createForm<CreateMachineForm>({
|
||||
initialValues: {
|
||||
flake: {
|
||||
@@ -49,7 +51,7 @@ export function CreateMachine() {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [activeURI(), "list_machines"],
|
||||
});
|
||||
// setRoute("machines");
|
||||
navigate("/machines");
|
||||
} else {
|
||||
toast.error(
|
||||
`Error: ${response.errors[0].message}. Machine ${values.machine.name} could not be created`,
|
||||
|
||||
@@ -37,21 +37,4 @@ describe.concurrent("API types work properly", () => {
|
||||
| { status: "error"; errors: any }
|
||||
>();
|
||||
});
|
||||
|
||||
it("Machine show receives an object with at least: machine_name, machine_description and machine_icon", async () => {
|
||||
expectTypeOf(pyApi.show_machine.receive)
|
||||
.parameter(0)
|
||||
.parameter(0)
|
||||
.toMatchTypeOf<
|
||||
| {
|
||||
status: "success";
|
||||
data: {
|
||||
machine_name: string;
|
||||
machine_icon?: string | null;
|
||||
machine_description?: string | null;
|
||||
};
|
||||
}
|
||||
| { status: "error"; errors: any }
|
||||
>();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user