UI: AdminSettings page

This commit is contained in:
Johannes Kirschbauer
2024-09-02 15:08:36 +02:00
parent 3b526955a2
commit 5cfa72edcc
3 changed files with 102 additions and 22 deletions

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,