import { FormEvent, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { api, assignGroupMember, assignGroupRole, removeGroupMember, removeGroupRole, type Group, type MqttClient, type Role, } from "../api"; export function GroupsPage() { const queryClient = useQueryClient(); const [name, setName] = useState(""); const [error, setError] = useState(null); const groups = useQuery({ queryKey: ["groups"], queryFn: () => api.get("/mqtt/groups"), }); // For the per-group assignment dropdowns. const roles = useQuery({ queryKey: ["roles"], queryFn: () => api.get("/mqtt/roles"), }); const clients = useQuery({ queryKey: ["clients"], queryFn: () => api.get("/mqtt/clients"), }); const refresh = () => { queryClient.invalidateQueries({ queryKey: ["groups"] }); queryClient.invalidateQueries({ queryKey: ["clients"] }); }; const create = useMutation({ mutationFn: (groupname: string) => api.post("/mqtt/groups", { groupname }), onSuccess: () => { setName(""); refresh(); }, onError: (e: Error) => setError(e.message), }); const remove = useMutation({ mutationFn: (groupname: string) => api.delete(`/mqtt/groups/${groupname}`), onSuccess: refresh, onError: (e: Error) => setError(e.message), }); const addRole = useMutation({ mutationFn: ({ groupname, rolename }: { groupname: string; rolename: string }) => assignGroupRole(groupname, rolename), onSuccess: refresh, onError: (e: Error) => setError(e.message), }); const dropRole = useMutation({ mutationFn: ({ groupname, rolename }: { groupname: string; rolename: string }) => removeGroupRole(groupname, rolename), onSuccess: refresh, onError: (e: Error) => setError(e.message), }); const addMember = useMutation({ mutationFn: ({ groupname, username }: { groupname: string; username: string }) => assignGroupMember(groupname, username), onSuccess: refresh, onError: (e: Error) => setError(e.message), }); const dropMember = useMutation({ mutationFn: ({ groupname, username }: { groupname: string; username: string }) => removeGroupMember(groupname, username), onSuccess: refresh, onError: (e: Error) => setError(e.message), }); const submit = (e: FormEvent) => { e.preventDefault(); if (name) create.mutate(name); }; const roleNames = (roles.data ?? []).map((r) => r.rolename); const clientNames = (clients.data ?? []).map((c) => c.username); // Group membership lives on the client objects (c.groups), so derive // members from the clients query rather than the group list. const membersOf = (groupname: string) => (clients.data ?? []) .filter((c) => c.groups.some((g) => g.groupname === groupname)) .map((c) => ({ name: c.username })); return (

Groups

Assign roles to collections of clients (dynamic security groups). Each group manages its own roles and members.

{error &&
{error}
}
setName(e.target.value)} placeholder="e.g. sensors" />

{(groups.data ?? []).map((g) => { const groupRoles = (g.roles ?? []).map((r) => ({ name: r.rolename })); const members = membersOf(g.groupname); const freeRoles = roleNames.filter( (name) => !groupRoles.some((r) => r.name === name), ); const freeMembers = clientNames.filter( (name) => !members.some((m) => m.name === name), ); return ( ); })}
Group Roles Members Add
{g.groupname} {groupRoles.length === 0 ? ( "—" ) : ( groupRoles.map((r) => ( {r.name}{" "} )) )} {members.length === 0 ? ( "—" ) : ( members.map((m) => ( {m.name}{" "} )) )} {freeRoles.length > 0 ? ( ) : null}{" "} {freeMembers.length > 0 ? ( ) : null}
); }