UI: AdminSettings page
This commit is contained in:
@@ -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"]
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user