diff --git a/pkgs/clan-cli/clan_cli/__init__.py b/pkgs/clan-cli/clan_cli/__init__.py index 1e01225c4..1c7880e79 100644 --- a/pkgs/clan-cli/clan_cli/__init__.py +++ b/pkgs/clan-cli/clan_cli/__init__.py @@ -6,20 +6,18 @@ from pathlib import Path from types import ModuleType # These imports are unused, but necessary for @API.register to run once. -from .api import admin, directory, disk, mdns_discovery, modules +from .api import admin, directory, disk, iwd, mdns_discovery, modules from .arg_actions import AppendOptionAction from .clan import show, update # API endpoints that are not used in the cli. -__all__ = ["directory", "mdns_discovery", "modules", "update", "disk", "admin"] +__all__ = ["directory", "mdns_discovery", "modules", "update", "disk", "admin", "iwd"] from . import ( backups, clan, - facts, flash, history, - machines, secrets, state, vars, @@ -29,7 +27,9 @@ from .clan_uri import FlakeId from .custom_logger import setup_logging from .dirs import get_clan_flake_toplevel_or_env from .errors import ClanCmdError, ClanError +from .facts import cli as facts from .hyperlink import help_hyperlink +from .machines import cli as machines from .profiler import profile from .ssh import cli as ssh_cli diff --git a/pkgs/clan-cli/clan_cli/api/iwd.py b/pkgs/clan-cli/clan_cli/api/iwd.py new file mode 100644 index 000000000..63501608e --- /dev/null +++ b/pkgs/clan-cli/clan_cli/api/iwd.py @@ -0,0 +1,100 @@ +from dataclasses import dataclass +from pathlib import Path + +from clan_cli.clan_uri import FlakeId +from clan_cli.errors import ClanError +from clan_cli.facts.generate import generate_facts +from clan_cli.inventory import ( + IwdConfig, + IwdConfigNetwork, + ServiceIwd, + ServiceIwdRole, + ServiceIwdRoleDefault, + ServiceMeta, + load_inventory_eval, + save_inventory, +) +from clan_cli.machines.machines import Machine +from clan_cli.secrets.sops import ( + maybe_get_public_key, + maybe_get_user_or_machine, +) + +from . import API + + +def instance_name(machine_name: str) -> str: + return f"{machine_name}_wifi_0_" + + +@API.register +def get_iwd_service(base_url: str, machine_name: str) -> ServiceIwd | None: + """ + Return the admin service of a clan. + + There is only one admin service. This might be changed in the future + """ + inventory = load_inventory_eval(base_url) + return inventory.services.iwd.get(instance_name(machine_name)) + + +@dataclass +class NetworkConfig: + ssid: str + password: str + + +@API.register +def set_iwd_service_for_machine( + base_url: str, machine_name: str, networks: dict[str, NetworkConfig] +) -> None: + """ + Set the admin service of a clan + Every machine is by default part of the admin service via the 'all' tag + """ + _instance_name = instance_name(machine_name) + + inventory = load_inventory_eval(base_url) + + instance = ServiceIwd( + meta=ServiceMeta(name="wifi_0"), + roles=ServiceIwdRole( + default=ServiceIwdRoleDefault( + machines=[machine_name], + ) + ), + config=IwdConfig( + networks={k: IwdConfigNetwork(v.ssid) for k, v in networks.items()} + ), + ) + + inventory.services.iwd[_instance_name] = instance + + save_inventory( + inventory, + base_url, + f"Set iwd service: '{_instance_name}'", + ) + + pubkey = maybe_get_public_key() + if not pubkey: + # TODO: do this automatically + # pubkey = generate_key() + raise ClanError(msg="No public key found. Please initialize your key.") + + registered_key = maybe_get_user_or_machine(Path(base_url), pubkey) + if not registered_key: + # TODO: do this automatically + # username = os.getlogin() + # add_user(Path(base_url), username, pubkey, force=False) + raise ClanError(msg="Your public key is not registered for use with this clan.") + + password_dict = {f"iwd.{net.ssid}": net.password for net in networks.values()} + for net in networks.values(): + generate_facts( + service=f"iwd.{net.ssid}", + machines=[Machine(machine_name, FlakeId(base_url))], + regenerate=True, + # Just returns the password + prompt=lambda service, _msg: password_dict[service], + ) diff --git a/pkgs/clan-cli/clan_cli/facts/__init__.py b/pkgs/clan-cli/clan_cli/facts/__init__.py index 836a4326d..e69de29bb 100644 --- a/pkgs/clan-cli/clan_cli/facts/__init__.py +++ b/pkgs/clan-cli/clan_cli/facts/__init__.py @@ -1,133 +0,0 @@ -# !/usr/bin/env python3 -import argparse - -from clan_cli.hyperlink import help_hyperlink - -from .check import register_check_parser -from .generate import register_generate_parser -from .list import register_list_parser -from .upload import register_upload_parser - - -# takes a (sub)parser and configures it -def register_parser(parser: argparse.ArgumentParser) -> None: - subparser = parser.add_subparsers( - title="command", - description="the command to run", - help="the command to run", - required=True, - ) - - check_parser = subparser.add_parser( - "check", - help="check if facts are up to date", - epilog=( - f""" -This subcommand allows checking if all facts are up to date. - -Examples: - - $ clan facts check [MACHINE] - Will check facts for the specified machine. - - -For more detailed information, visit: {help_hyperlink("secrets", "https://docs.clan.lol/getting-started/secrets")} - """ - ), - formatter_class=argparse.RawTextHelpFormatter, - ) - register_check_parser(check_parser) - - list_parser = subparser.add_parser( - "list", - help="list all facts", - epilog=( - f""" -This subcommand allows listing all public facts for a specific machine. - -The resulting list will be a json string with the name of the fact as its key -and the fact itself as it's value. - -This is how an example output might look like: -``` -\u007b -"[FACT_NAME]": "[FACT]" -\u007d -``` - -Examples: - - $ clan facts list [MACHINE] - Will list facts for the specified machine. - - -For more detailed information, visit: {help_hyperlink("secrets", "https://docs.clan.lol/getting-started/secrets")} - """ - ), - formatter_class=argparse.RawTextHelpFormatter, - ) - register_list_parser(list_parser) - - parser_generate = subparser.add_parser( - "generate", - help="generate public and secret facts for machines", - epilog=( - f""" -This subcommand allows control of the generation of facts. -Often this function will be invoked automatically on deploying machines, -but there are situations the user may want to have more granular control, -especially for the regeneration of certain services. - -A service is an included clan-module that implements facts generation functionality. -For example the zerotier module will generate private and public facts. -In this case the public fact will be the resulting zerotier-ip of the machine. -The secret fact will be the zerotier-identity-secret, which is used by zerotier -to prove the machine has control of the zerotier-ip. - - -Examples: - - $ clan facts generate - Will generate facts for all machines. - - $ clan facts generate [MACHINE] - Will generate facts for the specified machine. - - $ clan facts generate [MACHINE] --service [SERVICE] - Will generate facts for the specified machine for the specified service. - - $ clan facts generate --service [SERVICE] --regenerate - Will regenerate facts, if they are already generated for a specific service. - This is especially useful for resetting certain passwords while leaving the rest - of the facts for a machine in place. - -For more detailed information, visit: {help_hyperlink("secrets", "https://docs.clan.lol/getting-started/secrets")} - """ - ), - formatter_class=argparse.RawTextHelpFormatter, - ) - register_generate_parser(parser_generate) - - parser_upload = subparser.add_parser( - "upload", - help="upload secrets for machines", - epilog=( - f""" -This subcommand allows uploading secrets to remote machines. - -If using sops as a secret backend it will upload the private key to the machine. -If using password store it uploads all the secrets you manage to the machine. - -The default backend is sops. - -Examples: - - $ clan facts upload [MACHINE] - Will upload secrets to a specific machine. - -For more detailed information, visit: {help_hyperlink("secrets", "https://docs.clan.lol/getting-started/secrets")} - """ - ), - formatter_class=argparse.RawTextHelpFormatter, - ) - register_upload_parser(parser_upload) diff --git a/pkgs/clan-cli/clan_cli/facts/cli.py b/pkgs/clan-cli/clan_cli/facts/cli.py new file mode 100644 index 000000000..836a4326d --- /dev/null +++ b/pkgs/clan-cli/clan_cli/facts/cli.py @@ -0,0 +1,133 @@ +# !/usr/bin/env python3 +import argparse + +from clan_cli.hyperlink import help_hyperlink + +from .check import register_check_parser +from .generate import register_generate_parser +from .list import register_list_parser +from .upload import register_upload_parser + + +# takes a (sub)parser and configures it +def register_parser(parser: argparse.ArgumentParser) -> None: + subparser = parser.add_subparsers( + title="command", + description="the command to run", + help="the command to run", + required=True, + ) + + check_parser = subparser.add_parser( + "check", + help="check if facts are up to date", + epilog=( + f""" +This subcommand allows checking if all facts are up to date. + +Examples: + + $ clan facts check [MACHINE] + Will check facts for the specified machine. + + +For more detailed information, visit: {help_hyperlink("secrets", "https://docs.clan.lol/getting-started/secrets")} + """ + ), + formatter_class=argparse.RawTextHelpFormatter, + ) + register_check_parser(check_parser) + + list_parser = subparser.add_parser( + "list", + help="list all facts", + epilog=( + f""" +This subcommand allows listing all public facts for a specific machine. + +The resulting list will be a json string with the name of the fact as its key +and the fact itself as it's value. + +This is how an example output might look like: +``` +\u007b +"[FACT_NAME]": "[FACT]" +\u007d +``` + +Examples: + + $ clan facts list [MACHINE] + Will list facts for the specified machine. + + +For more detailed information, visit: {help_hyperlink("secrets", "https://docs.clan.lol/getting-started/secrets")} + """ + ), + formatter_class=argparse.RawTextHelpFormatter, + ) + register_list_parser(list_parser) + + parser_generate = subparser.add_parser( + "generate", + help="generate public and secret facts for machines", + epilog=( + f""" +This subcommand allows control of the generation of facts. +Often this function will be invoked automatically on deploying machines, +but there are situations the user may want to have more granular control, +especially for the regeneration of certain services. + +A service is an included clan-module that implements facts generation functionality. +For example the zerotier module will generate private and public facts. +In this case the public fact will be the resulting zerotier-ip of the machine. +The secret fact will be the zerotier-identity-secret, which is used by zerotier +to prove the machine has control of the zerotier-ip. + + +Examples: + + $ clan facts generate + Will generate facts for all machines. + + $ clan facts generate [MACHINE] + Will generate facts for the specified machine. + + $ clan facts generate [MACHINE] --service [SERVICE] + Will generate facts for the specified machine for the specified service. + + $ clan facts generate --service [SERVICE] --regenerate + Will regenerate facts, if they are already generated for a specific service. + This is especially useful for resetting certain passwords while leaving the rest + of the facts for a machine in place. + +For more detailed information, visit: {help_hyperlink("secrets", "https://docs.clan.lol/getting-started/secrets")} + """ + ), + formatter_class=argparse.RawTextHelpFormatter, + ) + register_generate_parser(parser_generate) + + parser_upload = subparser.add_parser( + "upload", + help="upload secrets for machines", + epilog=( + f""" +This subcommand allows uploading secrets to remote machines. + +If using sops as a secret backend it will upload the private key to the machine. +If using password store it uploads all the secrets you manage to the machine. + +The default backend is sops. + +Examples: + + $ clan facts upload [MACHINE] + Will upload secrets to a specific machine. + +For more detailed information, visit: {help_hyperlink("secrets", "https://docs.clan.lol/getting-started/secrets")} + """ + ), + formatter_class=argparse.RawTextHelpFormatter, + ) + register_upload_parser(parser_upload) diff --git a/pkgs/clan-cli/clan_cli/facts/generate.py b/pkgs/clan-cli/clan_cli/facts/generate.py index e0e28a506..74c073a2e 100644 --- a/pkgs/clan-cli/clan_cli/facts/generate.py +++ b/pkgs/clan-cli/clan_cli/facts/generate.py @@ -68,7 +68,7 @@ def generate_service_facts( secret_facts_store: SecretStoreBase, public_facts_store: FactStoreBase, tmpdir: Path, - prompt: Callable[[str], str], + prompt: Callable[[str, str], str], ) -> bool: service_dir = tmpdir / service # check if all secrets exist and generate them if at least one is missing @@ -93,7 +93,9 @@ def generate_service_facts( else: generator = machine.facts_data[service]["generator"]["finalScript"] if machine.facts_data[service]["generator"]["prompt"]: - prompt_value = prompt(machine.facts_data[service]["generator"]["prompt"]) + prompt_value = prompt( + service, machine.facts_data[service]["generator"]["prompt"] + ) env["prompt_value"] = prompt_value if sys.platform == "linux": cmd = bubblewrap_cmd(generator, facts_dir, secrets_dir) @@ -137,7 +139,7 @@ def generate_service_facts( return True -def prompt_func(text: str) -> str: +def prompt_func(service: str, text: str) -> str: print(f"{text}: ") return read_multiline_input() @@ -147,7 +149,7 @@ def _generate_facts_for_machine( service: str | None, regenerate: bool, tmpdir: Path, - prompt: Callable[[str], str] = prompt_func, + prompt: Callable[[str, str], str] = prompt_func, ) -> bool: local_temp = tmpdir / machine.name local_temp.mkdir() @@ -189,7 +191,7 @@ def generate_facts( machines: list[Machine], service: str | None, regenerate: bool, - prompt: Callable[[str], str] = prompt_func, + prompt: Callable[[str, str], str] = prompt_func, ) -> bool: was_regenerated = False with TemporaryDirectory() as tmp: diff --git a/pkgs/clan-cli/clan_cli/inventory/__init__.py b/pkgs/clan-cli/clan_cli/inventory/__init__.py index 17ab54d07..a79b29e10 100644 --- a/pkgs/clan-cli/clan_cli/inventory/__init__.py +++ b/pkgs/clan-cli/clan_cli/inventory/__init__.py @@ -25,6 +25,8 @@ from clan_cli.nix import nix_eval from .classes import ( AdminConfig, Inventory, + IwdConfig, + IwdConfigNetwork, # Machine classes Machine, MachineDeploy, @@ -40,6 +42,10 @@ from .classes import ( ServiceBorgbackupRole, ServiceBorgbackupRoleClient, ServiceBorgbackupRoleServer, + # IWD + ServiceIwd, + ServiceIwdRole, + ServiceIwdRoleDefault, ServiceMeta, # Single Disk service ServiceSingleDisk, @@ -73,6 +79,12 @@ __all__ = [ "ServiceAdminRole", "ServiceAdminRoleDefault", "AdminConfig", + # IWD service, + "ServiceIwd", + "ServiceIwdRole", + "ServiceIwdRoleDefault", + "IwdConfig", + "IwdConfigNetwork", ] diff --git a/pkgs/clan-cli/clan_cli/machines/__init__.py b/pkgs/clan-cli/clan_cli/machines/__init__.py index c6b0631e1..e69de29bb 100644 --- a/pkgs/clan-cli/clan_cli/machines/__init__.py +++ b/pkgs/clan-cli/clan_cli/machines/__init__.py @@ -1,115 +0,0 @@ -# !/usr/bin/env python3 -import argparse - -from .create import register_create_parser -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 .update import register_update_parser - - -# takes a (sub)parser and configures it -def register_parser(parser: argparse.ArgumentParser) -> None: - subparser = parser.add_subparsers( - title="command", - description="the command to run", - help="the command to run", - required=True, - ) - - update_parser = subparser.add_parser( - "update", - help="Update a machine", - epilog=( - """ -This subcommand provides an interface to update machines managed by clan. - -Examples: - - $ clan machines update [MACHINES] - Will update the specified machine [MACHINE], if [MACHINE] is omitted, the command - will attempt to update every configured machine. - To exclude machines being updated `clan.deployment.requireExplicitUpdate = true;` - can be set in the machine config. - -For more detailed information, visit: https://docs.clan.lol/getting-started/deploy - """ - ), - formatter_class=argparse.RawTextHelpFormatter, - ) - register_update_parser(update_parser) - - create_parser = subparser.add_parser("create", help="Create a machine") - register_create_parser(create_parser) - - delete_parser = subparser.add_parser("delete", help="Delete a machine") - register_delete_parser(delete_parser) - - list_parser = subparser.add_parser( - "list", - help="List machines", - epilog=( - """ -This subcommand lists all machines managed by this clan. - -Examples: - - $ clan machines list - Lists all the machines and their descriptions. - """ - ), - formatter_class=argparse.RawTextHelpFormatter, - ) - register_list_parser(list_parser) - - generate_hw_parser = subparser.add_parser( - "hw-generate", - help="Generate hardware specifics for a machine", - description=""" -Generates hardware specifics for a machine. Such as the host platform, available kernel modules, etc. - -The target must be a Linux based system reachable via SSH. - """, - epilog=( - """ -Examples: - - $ clan machines hw-generate [MACHINE] [TARGET_HOST] - Will generate hardware specifics for the the specified `[TARGET_HOST]` and place the result in hardware.nix for the given machine `[MACHINE]`. - -For more detailed information, visit: https://docs.clan.lol/getting-started/configure/#machine-configuration - -""" - ), - ) - register_hw_generate(generate_hw_parser) - - install_parser = subparser.add_parser( - "install", - help="Install a machine", - description=""" -Install a configured machine over the network. -The target must be a Linux based system reachable via SSH. -Installing a machine means overwriting the target's disk. - """, - epilog=( - """ -This subcommand provides an interface to install machines managed by clan. - -Examples: - - $ clan machines install [MACHINE] [TARGET_HOST] - Will install the specified machine [MACHINE], to the specified [TARGET_HOST]. - - $ clan machines install [MACHINE] --json [JSON] - Will install the specified machine [MACHINE] to the host exposed by - the deployment information of the [JSON] deployment string. - -For information on how to set up the installer see: https://docs.clan.lol/getting-started/installer/ -For more detailed information, visit: https://docs.clan.lol/getting-started/deploy - """ - ), - formatter_class=argparse.RawTextHelpFormatter, - ) - register_install_parser(install_parser) diff --git a/pkgs/clan-cli/clan_cli/machines/cli.py b/pkgs/clan-cli/clan_cli/machines/cli.py new file mode 100644 index 000000000..c6b0631e1 --- /dev/null +++ b/pkgs/clan-cli/clan_cli/machines/cli.py @@ -0,0 +1,115 @@ +# !/usr/bin/env python3 +import argparse + +from .create import register_create_parser +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 .update import register_update_parser + + +# takes a (sub)parser and configures it +def register_parser(parser: argparse.ArgumentParser) -> None: + subparser = parser.add_subparsers( + title="command", + description="the command to run", + help="the command to run", + required=True, + ) + + update_parser = subparser.add_parser( + "update", + help="Update a machine", + epilog=( + """ +This subcommand provides an interface to update machines managed by clan. + +Examples: + + $ clan machines update [MACHINES] + Will update the specified machine [MACHINE], if [MACHINE] is omitted, the command + will attempt to update every configured machine. + To exclude machines being updated `clan.deployment.requireExplicitUpdate = true;` + can be set in the machine config. + +For more detailed information, visit: https://docs.clan.lol/getting-started/deploy + """ + ), + formatter_class=argparse.RawTextHelpFormatter, + ) + register_update_parser(update_parser) + + create_parser = subparser.add_parser("create", help="Create a machine") + register_create_parser(create_parser) + + delete_parser = subparser.add_parser("delete", help="Delete a machine") + register_delete_parser(delete_parser) + + list_parser = subparser.add_parser( + "list", + help="List machines", + epilog=( + """ +This subcommand lists all machines managed by this clan. + +Examples: + + $ clan machines list + Lists all the machines and their descriptions. + """ + ), + formatter_class=argparse.RawTextHelpFormatter, + ) + register_list_parser(list_parser) + + generate_hw_parser = subparser.add_parser( + "hw-generate", + help="Generate hardware specifics for a machine", + description=""" +Generates hardware specifics for a machine. Such as the host platform, available kernel modules, etc. + +The target must be a Linux based system reachable via SSH. + """, + epilog=( + """ +Examples: + + $ clan machines hw-generate [MACHINE] [TARGET_HOST] + Will generate hardware specifics for the the specified `[TARGET_HOST]` and place the result in hardware.nix for the given machine `[MACHINE]`. + +For more detailed information, visit: https://docs.clan.lol/getting-started/configure/#machine-configuration + +""" + ), + ) + register_hw_generate(generate_hw_parser) + + install_parser = subparser.add_parser( + "install", + help="Install a machine", + description=""" +Install a configured machine over the network. +The target must be a Linux based system reachable via SSH. +Installing a machine means overwriting the target's disk. + """, + epilog=( + """ +This subcommand provides an interface to install machines managed by clan. + +Examples: + + $ clan machines install [MACHINE] [TARGET_HOST] + Will install the specified machine [MACHINE], to the specified [TARGET_HOST]. + + $ clan machines install [MACHINE] --json [JSON] + Will install the specified machine [MACHINE] to the host exposed by + the deployment information of the [JSON] deployment string. + +For information on how to set up the installer see: https://docs.clan.lol/getting-started/installer/ +For more detailed information, visit: https://docs.clan.lol/getting-started/deploy + """ + ), + formatter_class=argparse.RawTextHelpFormatter, + ) + register_install_parser(install_parser) diff --git a/pkgs/clan-cli/clan_cli/secrets/sops.py b/pkgs/clan-cli/clan_cli/secrets/sops.py index 281d2c46e..72640d967 100644 --- a/pkgs/clan-cli/clan_cli/secrets/sops.py +++ b/pkgs/clan-cli/clan_cli/secrets/sops.py @@ -75,7 +75,7 @@ def get_user_name(flake_dir: Path, user: str) -> str: print(f"{flake_dir / user} already exists") -def ensure_user_or_machine(flake_dir: Path, pub_key: str) -> SopsKey: +def maybe_get_user_or_machine(flake_dir: Path, pub_key: str) -> SopsKey | None: key = SopsKey(pub_key, username="") folders = [sops_users_folder(flake_dir), sops_machines_folder(flake_dir)] @@ -88,8 +88,15 @@ def ensure_user_or_machine(flake_dir: Path, pub_key: str) -> SopsKey: key.username = user.name return key - msg = f"Your sops key is not yet added to the repository. Please add it with 'clan secrets users add youruser {pub_key}' (replace youruser with your user name)" - raise ClanError(msg) + return None + + +def ensure_user_or_machine(flake_dir: Path, pub_key: str) -> SopsKey: + key = maybe_get_user_or_machine(flake_dir, pub_key) + if not key: + msg = f"Your sops key is not yet added to the repository. Please add it with 'clan secrets users add youruser {pub_key}' (replace youruser with your user name)" + raise ClanError(msg) + return key def default_sops_key_path() -> Path: @@ -99,15 +106,30 @@ def default_sops_key_path() -> Path: return user_config_dir() / "sops" / "age" / "keys.txt" -def ensure_sops_key(flake_dir: Path) -> SopsKey: +def maybe_get_public_key() -> str | None: key = os.environ.get("SOPS_AGE_KEY") if key: - return ensure_user_or_machine(flake_dir, get_public_key(key)) + return get_public_key(key) path = default_sops_key_path() if path.exists(): - return ensure_user_or_machine(flake_dir, get_public_key(path.read_text())) - msg = "No sops key found. Please generate one with 'clan secrets key generate'." - raise ClanError(msg) + return get_public_key(path.read_text()) + + return None + + +def maybe_get_sops_key(flake_dir: Path) -> SopsKey | None: + pub_key = maybe_get_public_key() + if pub_key: + return maybe_get_user_or_machine(flake_dir, pub_key) + return None + + +def ensure_sops_key(flake_dir: Path) -> SopsKey: + pub_key = maybe_get_public_key() + if not pub_key: + msg = "No sops key found. Please generate one with 'clan secrets key generate'." + raise ClanError(msg) + return ensure_user_or_machine(flake_dir, pub_key) @contextmanager diff --git a/pkgs/webview-ui/app/src/routes/machines/[name]/view.tsx b/pkgs/webview-ui/app/src/routes/machines/[name]/view.tsx index c83a21b41..b6bdb69ec 100644 --- a/pkgs/webview-ui/app/src/routes/machines/[name]/view.tsx +++ b/pkgs/webview-ui/app/src/routes/machines/[name]/view.tsx @@ -4,7 +4,7 @@ import { BackButton } from "@/src/components/BackButton"; import { FileInput } from "@/src/components/FileInput"; import { SelectInput } from "@/src/components/SelectInput"; import { TextInput } from "@/src/components/TextInput"; -import { createForm, getValue, reset } from "@modular-forms/solid"; +import { createForm, FieldValues, getValue, reset } from "@modular-forms/solid"; import { useParams } from "@solidjs/router"; import { createQuery } from "@tanstack/solid-query"; import { createSignal, For, Show, Switch, Match } from "solid-js"; @@ -608,8 +608,75 @@ export const MachineDetails = () => { when={query.data} fallback={} > - {(data) => } + {(data) => ( + <> + + + + )} ); }; + +interface WifiForm extends FieldValues { + ssid: string; + password: string; +} + +interface MachineWifiProps { + base_url: string; + machine_name: string; +} +function MachineWifi(props: MachineWifiProps) { + const [formStore, { Form, Field }] = createForm(); + + const handleSubmit = async (values: WifiForm) => { + console.log("submitting", values); + const r = await callApi("set_iwd_service_for_machine", { + base_url: props.base_url, + machine_name: props.machine_name, + networks: { + [values.ssid]: { ssid: values.ssid, password: values.password }, + }, + }); + }; + return ( +
+

MachineWifi

+
+ + {(field, props) => ( + + )} + + + {(field, props) => ( + + )} + + +
+
+ ); +}