Merge pull request 'API: init iwd clanModule inventory' (#2016) from hsjobeki/clan-core:hsjobeki-main into main

This commit is contained in:
clan-bot
2024-09-02 13:14:13 +00:00
5 changed files with 141 additions and 22 deletions

View File

@@ -0,0 +1 @@
{ }

View File

@@ -117,6 +117,43 @@ class ServiceBorgbackup:
machines: dict[str, ServiceBorgbackupMachine] = field(default_factory = dict) machines: dict[str, ServiceBorgbackupMachine] = field(default_factory = dict)
@dataclass
class IwdConfigNetwork:
ssid: str
@dataclass
class IwdConfig:
networks: dict[str, IwdConfigNetwork] = field(default_factory = dict)
@dataclass
class ServiceIwdMachine:
config: IwdConfig = field(default_factory = IwdConfig)
imports: list[str] = field(default_factory = list)
@dataclass
class ServiceIwdRoleDefault:
config: IwdConfig = field(default_factory = IwdConfig)
imports: list[str] = field(default_factory = list)
machines: list[str] = field(default_factory = list)
tags: list[str] = field(default_factory = list)
@dataclass
class ServiceIwdRole:
default: ServiceIwdRoleDefault
@dataclass
class ServiceIwd:
meta: ServiceMeta
roles: ServiceIwdRole
config: IwdConfig = field(default_factory = IwdConfig)
machines: dict[str, ServiceIwdMachine] = field(default_factory = dict)
@dataclass @dataclass
class PackagesConfig: class PackagesConfig:
packages: list[str] = field(default_factory = list) packages: list[str] = field(default_factory = list)
@@ -185,6 +222,7 @@ class ServiceSingleDisk:
class Service: class Service:
admin: dict[str, ServiceAdmin] = field(default_factory = dict) admin: dict[str, ServiceAdmin] = field(default_factory = dict)
borgbackup: dict[str, ServiceBorgbackup] = field(default_factory = dict) borgbackup: dict[str, ServiceBorgbackup] = field(default_factory = dict)
iwd: dict[str, ServiceIwd] = field(default_factory = dict)
packages: dict[str, ServicePackage] = field(default_factory = dict) packages: dict[str, ServicePackage] = field(default_factory = dict)
single_disk: dict[str, ServiceSingleDisk] = field(default_factory = dict, metadata = {"alias": "single-disk"}) single_disk: dict[str, ServiceSingleDisk] = field(default_factory = dict, metadata = {"alias": "single-disk"})

View File

@@ -3,7 +3,7 @@ import logging
from dataclasses import dataclass, field from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from typing import Any from typing import Any, Literal
from clan_cli.clan_uri import FlakeId from clan_cli.clan_uri import FlakeId
from clan_cli.cmd import run_no_stdout from clan_cli.cmd import run_no_stdout
@@ -64,13 +64,24 @@ class Machine:
self.deployment["targetHost"] = value self.deployment["targetHost"] = value
@property @property
def secret_facts_module(self) -> str: def secret_facts_module(
self,
) -> Literal[
"clan_cli.facts.secret_modules.sops",
"clan_cli.facts.secret_modules.vm",
"clan_cli.facts.secret_modules.password_store",
]:
return self.deployment["facts"]["secretModule"] return self.deployment["facts"]["secretModule"]
@property @property
def public_facts_module(self) -> str: def public_facts_module(
self,
) -> Literal[
"clan_cli.facts.public_modules.in_repo", "clan_cli.facts.public_modules.vm"
]:
return self.deployment["facts"]["publicModule"] return self.deployment["facts"]["publicModule"]
# WIP: Vars module is not ready yet.
@property @property
def secret_vars_module(self) -> str: def secret_vars_module(self) -> str:
return self.deployment["vars"]["secretModule"] return self.deployment["vars"]["secretModule"]

View File

@@ -2,34 +2,105 @@ import { callApi, SuccessData } from "@/src/api";
import { BackButton } from "@/src/components/BackButton"; import { BackButton } from "@/src/components/BackButton";
import { useParams } from "@solidjs/router"; import { useParams } from "@solidjs/router";
import { createQuery } from "@tanstack/solid-query"; import { createQuery } from "@tanstack/solid-query";
import { createEffect, For, Match, Switch } from "solid-js"; import { createEffect, createSignal, For, Match, Switch } from "solid-js";
import { Show } from "solid-js"; import { Show } from "solid-js";
import { DiskView } from "../disk/view"; import { DiskView } from "../disk/view";
import { Accessor } from "solid-js"; import { Accessor } from "solid-js";
import {
createForm,
FieldArray,
FieldValues,
getValue,
getValues,
insert,
setValue,
} from "@modular-forms/solid";
import { TextInput } from "@/src/components/TextInput";
type AdminData = SuccessData<"get_admin_service">["data"]; type AdminData = SuccessData<"get_admin_service">["data"];
interface ClanDetailsProps { interface ClanDetailsProps {
admin: AdminData; admin: AdminData;
} }
interface AdminSettings extends FieldValues {
allowedKeys: { name: string; value: string }[];
}
const ClanDetails = (props: ClanDetailsProps) => { const ClanDetails = (props: ClanDetailsProps) => {
const items = () => const items = () =>
Object.entries<string>( Object.entries<string>(
(props.admin?.config?.allowedKeys as Record<string, string>) || {}, (props.admin?.config?.allowedKeys as Record<string, string>) || {},
); );
const [formStore, { Form, Field }] = createForm<AdminSettings>({
initialValues: {
allowedKeys: items().map(([name, value]) => ({ name, value })),
},
});
const [keys, setKeys] = createSignal<1[]>(new Array(items().length).fill(1));
const handleSubmit = async (values: AdminSettings) => {
console.log("submitting", values, getValues(formStore));
};
return ( return (
<div> <div>
<h1>Clan Details </h1> <span class="text-xl text-primary">Clan Settings</span>
<For each={items()}> <br></br>
{([name, key]) => ( <span class="text-lg text-neutral">
<div> Each of the following keys can be used to authenticate on any machine
<span>{name}</span> </span>
<span>{key}</span> <Form onSubmit={handleSubmit}>
</div> <div class="grid grid-cols-12 gap-2">
)} <For each={keys()}>
</For> {(name, idx) => (
<>
<Field name={`allowedKeys.${idx()}.name`}>
{(field, props) => (
<TextInput
formStore={formStore}
inputProps={props}
label={`allowedKeys.${idx()}.name-` + items().length}
value={field.value ?? ""}
error={field.error}
class="col-span-4"
required
/>
)}
</Field>
<Field name={`allowedKeys.${idx()}.value`}>
{(field, props) => (
<TextInput
formStore={formStore}
inputProps={props}
label="Value"
value={field.value ?? ""}
error={field.error}
class="col-span-7"
required
/>
)}
</Field>
<button class="btn btn-ghost col-span-1 self-end">
<span class="material-icons">delete</span>
</button>
</>
)}
</For>
</div>
<div class="flex w-full my-2 gap-2">
<button
class="btn btn-ghost btn-square"
onClick={(e) => {
e.preventDefault();
setKeys((c) => [...c, 1]);
console.log(keys());
}}
>
<span class="material-icons">add</span>
</button>
<button class="btn">Submit</button>
</div>
</Form>
</div> </div>
); );
}; };
@@ -60,7 +131,6 @@ export const Details = () => {
{(d) => <ClanDetails admin={query.data} />} {(d) => <ClanDetails admin={query.data} />}
</Match> </Match>
</Switch> </Switch>
{clan_dir}
</Show> </Show>
</div> </div>
); );

View File

@@ -57,10 +57,12 @@ export const Flash = () => {
}); });
const addWifiNetwork = () => { const addWifiNetwork = () => {
const updatedNetworks = [...wifiNetworks(), { ssid: "", password: "" }]; setWifiNetworks((c) => {
setWifiNetworks(updatedNetworks); const res = [...c, { ssid: "", password: "" }];
setPasswordVisibility([...passwordVisibility(), false]); setValue(formStore, "wifi", res);
setValue(formStore, "wifi", updatedNetworks); return res;
});
setPasswordVisibility((c) => [...c, false]);
}; };
const removeWifiNetwork = (index: number) => { const removeWifiNetwork = (index: number) => {
@@ -153,10 +155,7 @@ export const Flash = () => {
language: values.language, language: values.language,
keymap: values.keymap, keymap: values.keymap,
ssh_keys_path: values.sshKeys.map((file) => file.name), ssh_keys_path: values.sshKeys.map((file) => file.name),
wifi_settings: values.wifi.map((network) => ({ wifi_settings: values.wifi,
ssid: network.ssid,
password: network.password,
})),
}, },
dry_run: false, dry_run: false,
write_efi_boot_entries: false, write_efi_boot_entries: false,