Merge pull request 'Fix: remove unused service endpoints from UI' (#3579) from hsjobeki/clan-core:ui-fixes-1 into main

Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3579
This commit is contained in:
Mic92
2025-05-12 14:04:01 +00:00
2 changed files with 1 additions and 277 deletions

View File

@@ -1,11 +1,5 @@
import { QueryClient } from "@tanstack/solid-query";
import {
ApiEnvelope,
callApi,
ClanServiceInstance,
ServiceNames,
Services,
} from ".";
import { ApiEnvelope, callApi } from ".";
import { Schema as Inventory } from "@/api/Inventory";
export async function get_inventory(client: QueryClient, base_path: string) {
@@ -23,89 +17,3 @@ export async function get_inventory(client: QueryClient, base_path: string) {
return data;
}
export const generate_instance_name = <T extends keyof Services>(
machine_name: string,
service_name: T,
) => [machine_name, service_name, 1].filter(Boolean).join("_");
export const get_first_instance_name = async <T extends keyof Services>(
client: QueryClient,
base_path: string,
service_name: T,
): Promise<string | null> => {
const r = await get_inventory(client, base_path);
if (r.status === "success") {
const service = r.data.services?.[service_name];
if (!service) return null;
return Object.keys(service)[0] || null;
}
return null;
};
async function get_service<T extends ServiceNames>(
client: QueryClient,
base_path: string,
service_name: T,
) {
const r = await get_inventory(client, base_path);
if (r.status === "success") {
const service = r.data.services?.[service_name];
return service as Services[T];
}
return null;
}
export async function get_single_service<T extends keyof Services>(
client: QueryClient,
base_path: string,
service_name: T,
): Promise<ClanServiceInstance<T>> {
const instance_key = await get_first_instance_name(
client,
base_path,
service_name,
);
if (!instance_key) {
throw new Error("No instance found");
}
const service: Services[T] | null = await get_service(
client,
base_path,
service_name,
);
if (service) {
const clanServiceInstance = service[instance_key] as ClanServiceInstance<T>;
return clanServiceInstance;
}
throw new Error("No service found");
}
export async function set_single_service<T extends keyof Services>(
client: QueryClient,
base_path: string,
machine_name: string,
service_name: T,
service_config: ClanServiceInstance<T>,
) {
const instance_key =
(await get_first_instance_name(client, base_path, service_name)) ||
generate_instance_name(machine_name, service_name);
const r = await get_inventory(client, base_path);
if (r.status === "success") {
const inventory = r.data;
inventory.services = inventory.services || {};
inventory.services[service_name] = inventory.services[service_name] || {};
inventory.services[service_name][instance_key] = service_config;
console.log("saving inventory", inventory);
return callApi("set_inventory", {
// @ts-expect-error: This doesn't check
inventory,
message: `update_single_service ${service_name}`,
flake_dir: base_path,
});
}
return r;
}

View File

@@ -13,19 +13,10 @@ import {
} from "@modular-forms/solid";
import { TextInput } from "@/src/Form/fields/TextInput";
import toast from "solid-toast";
import { set_single_service } from "@/src/api/inventory";
import { Button } from "@/src/components/button";
import Icon from "@/src/components/icon";
import { Header } from "@/src/layout/header";
interface AdminModuleFormProps {
admin: AdminData;
base_url: string;
}
interface AdminSettings extends FieldValues {
allowedKeys: { name: string; value: string }[];
}
interface EditClanFormProps {
initial: GeneralData;
directory: string;
@@ -145,182 +136,7 @@ const EditClanForm = (props: EditClanFormProps) => {
);
};
const AdminModuleForm = (props: AdminModuleFormProps) => {
const items = () =>
Object.entries<string>(
(props.admin?.config?.allowedKeys as Record<string, string>) || {},
);
const [formStore, { Form, Field }] = createForm<AdminSettings>({
initialValues: {
allowedKeys: items().map(([name, value]) => ({ name, value })),
},
});
const queryClient = useQueryClient();
const [keys, setKeys] = createSignal<1[]>(
new Array(items().length || 1).fill(1),
);
const handleSubmit = async (values: AdminSettings) => {
console.log("submitting", values, getValues(formStore));
const r = await set_single_service(
queryClient,
props.base_url,
"",
"admin",
{
meta: {
name: "admin",
},
roles: {
default: {
tags: ["all"],
},
},
config: {
allowedKeys: values.allowedKeys.reduce(
(acc, curr) => ({ ...acc, [curr.name]: curr.value }),
{},
),
},
},
);
if (r.status === "success") {
toast.success("Successfully updated admin settings");
}
if (r.status === "error") {
toast.error(`Failed to update admin settings: ${r.errors[0].message}`);
}
queryClient.invalidateQueries({
queryKey: [props.base_url, "get_admin_service"],
});
};
return (
<Form onSubmit={handleSubmit}>
<div class="">
<span class="text-xl text-primary-800">Administration</span>
<div class="grid grid-cols-12 gap-2">
<span class="col-span-12 text-lg text-neutral-800">
Each of the following keys can be used to authenticate on machines
</span>
<For each={keys()}>
{(name, idx) => (
<>
<Field name={`allowedKeys.${idx()}.name`}>
{(field, props) => (
<TextInput
inputProps={props}
label={"Name"}
// adornment={{
// position: "start",
// content: (
// <span class="material-icons text-gray-400">key</span>
// ),
// }}
value={field.value ?? ""}
error={field.error}
class="col-span-4"
required
/>
)}
</Field>
<Field name={`allowedKeys.${idx()}.value`}>
{(field, props) => (
<>
<TextInput
inputProps={props}
label={"Value"}
value={field.value ?? ""}
error={field.error}
class="col-span-6"
required
/>
<span class=" col-span-12 mt-auto" data-tip="Select file">
<label
class={"w-full"}
aria-disabled={formStore.submitting}
>
<div class="relative flex items-center justify-center">
<input
value=""
// Disable drag n drop
onDrop={(e) => e.preventDefault()}
class="absolute -ml-4 size-full cursor-pointer opacity-0"
type="file"
onInput={async (e) => {
if (!e.target.files) return;
const content = await e.target.files[0].text();
setValue(
formStore,
`allowedKeys.${idx()}.value`,
content,
);
if (
!getValue(
formStore,
`allowedKeys.${idx()}.name`,
)
) {
setValue(
formStore,
`allowedKeys.${idx()}.name`,
e.target.files[0].name,
);
}
}}
/>
<span class="material-icons">file_open</span>
</div>
</label>
</span>
</>
)}
</Field>
<Button
variant="light"
class="col-span-1 self-end"
startIcon={<Icon icon="Trash" />}
onClick={(e) => {
e.preventDefault();
setKeys((c) => c.filter((_, i) => i !== idx()));
setValue(formStore, `allowedKeys.${idx()}.name`, "");
setValue(formStore, `allowedKeys.${idx()}.value`, "");
}}
></Button>
</>
)}
</For>
<div class="my-2 flex w-full gap-2">
<Button
variant="light"
onClick={(e) => {
e.preventDefault();
setKeys((c) => [...c, 1]);
}}
startIcon={<Icon icon="Plus" />}
></Button>
</div>
</div>
{
<div class=" justify-end">
<Button
type="submit"
disabled={formStore.submitting || !formStore.dirty}
>
Save
</Button>
</div>
}
</div>
</Form>
);
};
type GeneralData = SuccessQuery<"show_clan_meta">["data"];
type AdminData = ClanServiceInstance<"admin">;
export const ClanDetails = () => {
const params = useParams();