import { FormEvent, useState } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { api, assignClientRole, assignGroupMember, removeClientRole, removeGroupMember, type MqttClient, type PasswordReset, type Role, type Group, } from "../api"; /** Small inline "chip + remove" editor for one client row. */ function AssignmentChips({ items, onRemove, }: { items: { name: string }[]; onRemove: (name: string) => void; }) { if (items.length === 0) return <>—; return ( <> {items.map((item) => ( {item.name}{" "} ))} ); } export function ClientsPage() { const queryClient = useQueryClient(); const [newUsername, setNewUsername] = useState(""); const [oncePassword, setOncePassword] = useState(null); const [error, setError] = useState(null); const clients = useQuery({ queryKey: ["clients"], queryFn: () => api.get("/mqtt/clients"), }); // Roles and groups are needed for the assignment dropdowns. const roles = useQuery({ queryKey: ["roles"], queryFn: () => api.get("/mqtt/roles"), }); const groups = useQuery({ queryKey: ["groups"], queryFn: () => api.get("/mqtt/groups"), }); const refresh = () => { queryClient.invalidateQueries({ queryKey: ["clients"] }); }; const create = useMutation({ mutationFn: (username: string) => api.post("/mqtt/clients", { username }), onSuccess: (result) => { setOncePassword(result); setNewUsername(""); refresh(); }, onError: (e: Error) => setError(e.message), }); const disable = useMutation({ mutationFn: (username: string) => api.post(`/mqtt/clients/${username}/disable`), onSuccess: refresh, }); const enable = useMutation({ mutationFn: (username: string) => api.post(`/mqtt/clients/${username}/enable`), onSuccess: refresh, }); const remove = useMutation({ mutationFn: (username: string) => api.delete(`/mqtt/clients/${username}`), onSuccess: refresh, onError: (e: Error) => setError(e.message), }); const assignRole = useMutation({ mutationFn: ({ username, rolename }: { username: string; rolename: string }) => assignClientRole(username, rolename), onSuccess: refresh, onError: (e: Error) => setError(e.message), }); const unassignRole = useMutation({ mutationFn: ({ username, rolename }: { username: string; rolename: string }) => removeClientRole(username, rolename), onSuccess: refresh, onError: (e: Error) => setError(e.message), }); const joinGroup = useMutation({ mutationFn: ({ groupname, username }: { groupname: string; username: string }) => assignGroupMember(groupname, username), onSuccess: refresh, onError: (e: Error) => setError(e.message), }); const leaveGroup = 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(); setError(null); if (newUsername) create.mutate(newUsername); }; const roleNames = (roles.data ?? []).map((r) => r.rolename); const groupNames = (groups.data ?? []).map((g) => g.groupname); return (

Clients

MQTT identities managed by the broker's dynamic security plugin. Passwords are shown exactly once on creation or reset. Use the selectors to assign roles or group memberships directly.

{oncePassword && (
Password for {oncePassword.username} — copy it now, it is shown only once:
{oncePassword.password}
)} {error &&
{error}
}
setNewUsername(e.target.value)} placeholder="e.g. ps042" />

{clients.isLoading ? (

Loading clients…

) : ( {(clients.data ?? []).map((c) => { const assignedRoles = c.roles.map((r) => ({ name: r.rolename })); const assignedGroups = c.groups.map((g) => ({ name: g.groupname })); const freeRoles = roleNames.filter( (name) => !assignedRoles.some((a) => a.name === name), ); const freeGroups = groupNames.filter( (name) => !assignedGroups.some((a) => a.name === name), ); return ( ); })}
Username Client ID Enabled Roles Groups Assign Actions
{c.username} {c.clientid ?? "—"} {c.enabled ? "yes" : "no"} unassignRole.mutate({ username: c.username, rolename }) } /> leaveGroup.mutate({ groupname, username: c.username }) } /> {freeRoles.length > 0 ? ( ) : null}{" "} {freeGroups.length > 0 ? ( ) : null} {c.enabled ? ( ) : ( )}{" "}
)}
); }