UI: migrate admin service api bindings

This commit is contained in:
Johannes Kirschbauer
2024-09-13 16:11:01 +02:00
parent 813172119e
commit 60e4196bb6
3 changed files with 122 additions and 46 deletions

View File

@@ -1,40 +1,101 @@
import { callApi, ClanService, ServiceNames, Services } from "."; import { QueryClient } from "@tanstack/solid-query";
import { Schema as Inventory } from "@/api/Inventory"; import { callApi, ClanServiceInstance, ServiceNames, Services } from ".";
export async function get_inventory(base_path: string) { export async function get_inventory(client: QueryClient, base_path: string) {
const r = await callApi("get_inventory", { const data = await client.ensureQueryData({
base_path, queryKey: [base_path, "inventory"],
queryFn: () => {
console.log("Refreshing inventory");
return callApi("get_inventory", { base_path });
},
revalidateIfStale: true,
staleTime: 60 * 1000,
}); });
if (r.status == "error") {
throw new Error("Failed to get inventory"); return data;
}
const inventory: Inventory = r.data;
return inventory;
} }
export const single_instance_name = <T extends keyof Services>( export const generate_instance_name = <T extends keyof Services>(
machine_name: string, machine_name: string,
service_name: T, service_name: T,
) => `${machine_name}_${service_name}_0` as const; ) => [machine_name, service_name, 1].filter(Boolean).join("_");
function get_service<T extends ServiceNames>(base_path: string, service: T) { export const get_first_instance_name = async <T extends keyof Services>(
return callApi("get_inventory", { base_path }).then((r) => { client: QueryClient,
if (r.status == "error") { base_path: string,
return null; service_name: T,
} ): Promise<string | null> => {
const inventory: Inventory = r.data; 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;
};
const serviceInstance = inventory.services?.[service]; async function get_service<T extends ServiceNames>(
return serviceInstance; 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>( export async function get_single_service<T extends keyof Services>(
client: QueryClient,
base_path: string,
service_name: T,
) {
const instance_key = await get_first_instance_name(
client,
base_path,
service_name,
);
if (!instance_key) {
return {};
}
const service: Services[T] | null = await get_service(
client,
base_path,
service_name,
);
if (service) {
const clanServiceInstance = service[instance_key];
return clanServiceInstance || {};
}
return {};
}
export async function set_single_service<T extends keyof Services>(
client: QueryClient,
base_path: string, base_path: string,
machine_name: string, machine_name: string,
service_name: T, service_name: T,
service_config: ClanServiceInstance<T>,
) { ) {
const instance_key = single_instance_name(machine_name, service_name); const instance_key =
const service = await get_service(base_path, "admin"); (await get_first_instance_name(client, base_path, service_name)) ||
return service?.[instance_key]; 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] || {};
// @ts-expect-error: This doesn't check
inventory.services[service_name][instance_key] = service_config;
console.log("saving inventory", inventory);
return callApi("set_inventory", {
inventory,
message: `update_single_service ${service_name}`,
flake_dir: base_path,
});
}
return r;
} }

View File

@@ -16,7 +16,7 @@ import { HostList } from "./routes/hosts/view";
import { Welcome } from "./routes/welcome"; import { Welcome } from "./routes/welcome";
import { Toaster } from "solid-toast"; import { Toaster } from "solid-toast";
const client = new QueryClient(); export const client = new QueryClient();
const root = document.getElementById("app"); const root = document.getElementById("app");

View File

@@ -24,7 +24,7 @@ import {
} from "@modular-forms/solid"; } from "@modular-forms/solid";
import { TextInput } from "@/src/components/TextInput"; import { TextInput } from "@/src/components/TextInput";
import toast from "solid-toast"; import toast from "solid-toast";
import { get_single_service } from "@/src/api/inventory"; import { get_single_service, set_single_service } from "@/src/api/inventory";
interface AdminModuleFormProps { interface AdminModuleFormProps {
admin: AdminData; admin: AdminData;
@@ -190,20 +190,34 @@ const AdminModuleForm = (props: AdminModuleFormProps) => {
const handleSubmit = async (values: AdminSettings) => { const handleSubmit = async (values: AdminSettings) => {
console.log("submitting", values, getValues(formStore)); console.log("submitting", values, getValues(formStore));
// const r = await callApi("set_admin_service", { const r = await set_single_service(
// base_url: props.base_url, queryClient,
// allowed_keys: values.allowedKeys.reduce( props.base_url,
// (acc, curr) => ({ ...acc, [curr.name]: curr.value }), "",
// {} "admin",
// ), {
// }); meta: {
// if (r.status === "success") { name: "admin",
// toast.success("Successfully updated admin settings"); },
// } roles: {
// if (r.status === "error") { default: {
// toast.error(`Failed to update admin settings: ${r.errors[0].message}`); tags: ["all"],
toast.error(`Failed to update admin settings: feature disabled`); },
// } },
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({ queryClient.invalidateQueries({
queryKey: [props.base_url, "get_admin_service"], queryKey: [props.base_url, "get_admin_service"],
}); });
@@ -340,10 +354,11 @@ type AdminData = ClanServiceInstance<"admin">;
export const ClanDetails = () => { export const ClanDetails = () => {
const params = useParams(); const params = useParams();
const queryClient = useQueryClient();
const clan_dir = window.atob(params.id); const clan_dir = window.atob(params.id);
// Fetch general meta data // Fetch general meta data
const clanQuery = createQuery(() => ({ const clanQuery = createQuery(() => ({
queryKey: [clan_dir, "meta"], queryKey: [clan_dir, "inventory", "meta"],
queryFn: async () => { queryFn: async () => {
const result = await callApi("show_clan_meta", { uri: clan_dir }); const result = await callApi("show_clan_meta", { uri: clan_dir });
if (result.status === "error") throw new Error("Failed to fetch data"); if (result.status === "error") throw new Error("Failed to fetch data");
@@ -352,11 +367,11 @@ export const ClanDetails = () => {
})); }));
// Fetch admin settings // Fetch admin settings
const adminQuery = createQuery(() => ({ const adminQuery = createQuery(() => ({
queryKey: [clan_dir, "get_admin_service"], queryKey: [clan_dir, "inventory", "services", "admin"],
queryFn: async () => { queryFn: async () => {
const result = await get_single_service(clan_dir, "", "admin"); const result = await get_single_service(queryClient, clan_dir, "admin");
if (!result) throw new Error("Failed to fetch data"); if (!result) throw new Error("Failed to fetch data");
return result || null; return result;
}, },
})); }));
@@ -371,7 +386,7 @@ export const ClanDetails = () => {
</div> </div>
} }
> >
<Switch> <Switch fallback={<>General data not available</>}>
<Match when={clanQuery.data}> <Match when={clanQuery.data}>
{(d) => <EditClanForm initial={d()} directory={clan_dir} />} {(d) => <EditClanForm initial={d()} directory={clan_dir} />}
</Match> </Match>
@@ -386,7 +401,7 @@ export const ClanDetails = () => {
</div> </div>
} }
> >
<Switch> <Switch fallback={<>Admin data not available</>}>
<Match when={adminQuery.data}> <Match when={adminQuery.data}>
{(d) => <AdminModuleForm admin={d()} base_url={clan_dir} />} {(d) => <AdminModuleForm admin={d()} base_url={clan_dir} />}
</Match> </Match>