import { useEffect, useRef, useState } from "react"; import { useFetcher } from "react-router"; import Dialog, { DialogPanel } from "~/components/dialog"; import Link from "~/components/link"; import Text from "~/components/text"; import Title from "~/components/title"; import TokenList from "~/components/token-list"; import { isValidGroupName } from "~/utils/acl-policy"; interface UserGroupsProps { isOpen: boolean; setIsOpen: (isOpen: boolean) => void; // The Headscale username, which is what the ACL policy references. userName: string; displayName: string; groups: string[]; availableGroups: string[]; // Whether the stored policy contains HuJSON comments, which saving drops. policyHasComments?: boolean; } export default function UserGroups({ isOpen, setIsOpen, userName, displayName, groups, availableGroups, policyHasComments, }: UserGroupsProps) { const fetcher = useFetcher<{ message?: string; error?: string }>(); const submittingRef = useRef(false); const [selected, setSelected] = useState([...groups]); const error = fetcher.data?.error; const isSubmitting = fetcher.state !== "idle"; useEffect(() => { if (isOpen) { setSelected([...groups]); } }, [isOpen, groups]); useEffect(() => { if (fetcher.state === "idle" && fetcher.data) { submittingRef.current = false; if (!fetcher.data.error) { setIsOpen(false); } } }, [fetcher.data, fetcher.state]); return ( { if (!open && submittingRef.current) { return; } setIsOpen(open); }} > { event.preventDefault(); submittingRef.current = true; const form = new FormData(); form.set("action_id", "update_user_groups"); form.set("user_name", userName); form.set("groups", selected.join(",")); fetcher.submit(form, { method: "POST" }); }} > Edit ACL groups for {displayName} Groups live in the ACL policy, not in Headscale. Changing them here rewrites the{" "} groups section of your policy. See the{" "} Tailscale ACL guide {" "} for details. {policyHasComments ? (

Your policy contains comments. Saving here rewrites the policy and drops them.

) : null} {error ? (

{error}

) : null}
); }