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:
@@ -1,11 +1,5 @@
|
|||||||
import { QueryClient } from "@tanstack/solid-query";
|
import { QueryClient } from "@tanstack/solid-query";
|
||||||
import {
|
import { ApiEnvelope, callApi } from ".";
|
||||||
ApiEnvelope,
|
|
||||||
callApi,
|
|
||||||
ClanServiceInstance,
|
|
||||||
ServiceNames,
|
|
||||||
Services,
|
|
||||||
} from ".";
|
|
||||||
import { Schema as Inventory } from "@/api/Inventory";
|
import { Schema as Inventory } from "@/api/Inventory";
|
||||||
|
|
||||||
export async function get_inventory(client: QueryClient, base_path: string) {
|
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;
|
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;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -13,19 +13,10 @@ import {
|
|||||||
} from "@modular-forms/solid";
|
} from "@modular-forms/solid";
|
||||||
import { TextInput } from "@/src/Form/fields/TextInput";
|
import { TextInput } from "@/src/Form/fields/TextInput";
|
||||||
import toast from "solid-toast";
|
import toast from "solid-toast";
|
||||||
import { set_single_service } from "@/src/api/inventory";
|
|
||||||
import { Button } from "@/src/components/button";
|
import { Button } from "@/src/components/button";
|
||||||
import Icon from "@/src/components/icon";
|
import Icon from "@/src/components/icon";
|
||||||
import { Header } from "@/src/layout/header";
|
import { Header } from "@/src/layout/header";
|
||||||
|
|
||||||
interface AdminModuleFormProps {
|
|
||||||
admin: AdminData;
|
|
||||||
base_url: string;
|
|
||||||
}
|
|
||||||
interface AdminSettings extends FieldValues {
|
|
||||||
allowedKeys: { name: string; value: string }[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EditClanFormProps {
|
interface EditClanFormProps {
|
||||||
initial: GeneralData;
|
initial: GeneralData;
|
||||||
directory: string;
|
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 GeneralData = SuccessQuery<"show_clan_meta">["data"];
|
||||||
type AdminData = ClanServiceInstance<"admin">;
|
|
||||||
|
|
||||||
export const ClanDetails = () => {
|
export const ClanDetails = () => {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
|||||||
Reference in New Issue
Block a user