🚧 WIP
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
import { FormEvent, useState } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { api, type MqttClient, type PasswordReset } from "../api";
|
||||
|
||||
export function ClientsPage() {
|
||||
const queryClient = useQueryClient();
|
||||
const [newUsername, setNewUsername] = useState("");
|
||||
const [oncePassword, setOncePassword] = useState<PasswordReset | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const clients = useQuery({
|
||||
queryKey: ["clients"],
|
||||
queryFn: () => api.get<MqttClient[]>("/mqtt/clients"),
|
||||
});
|
||||
|
||||
const create = useMutation({
|
||||
mutationFn: (username: string) =>
|
||||
api.post<PasswordReset>("/mqtt/clients", { username }),
|
||||
onSuccess: (result) => {
|
||||
setOncePassword(result);
|
||||
setNewUsername("");
|
||||
queryClient.invalidateQueries({ queryKey: ["clients"] });
|
||||
},
|
||||
onError: (e: Error) => setError(e.message),
|
||||
});
|
||||
|
||||
const disable = useMutation({
|
||||
mutationFn: (username: string) => api.post(`/mqtt/clients/${username}/disable`),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["clients"] }),
|
||||
});
|
||||
|
||||
const enable = useMutation({
|
||||
mutationFn: (username: string) => api.post(`/mqtt/clients/${username}/enable`),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["clients"] }),
|
||||
});
|
||||
|
||||
const remove = useMutation({
|
||||
mutationFn: (username: string) => api.delete(`/mqtt/clients/${username}`),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["clients"] }),
|
||||
onError: (e: Error) => setError(e.message),
|
||||
});
|
||||
|
||||
const submit = (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
if (newUsername) create.mutate(newUsername);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Clients</h2>
|
||||
<p>
|
||||
MQTT identities managed by the broker's dynamic security plugin. Passwords
|
||||
are shown exactly once on creation or reset.
|
||||
</p>
|
||||
|
||||
{oncePassword && (
|
||||
<div className="banner warn">
|
||||
<strong>Password for {oncePassword.username} — copy it now, it is shown
|
||||
only once:</strong>
|
||||
<div className="password-once mono">{oncePassword.password}</div>
|
||||
</div>
|
||||
)}
|
||||
{error && <div className="banner error">{error}</div>}
|
||||
|
||||
<form className="stack" onSubmit={submit}>
|
||||
<label htmlFor="new-client">New client username</label>
|
||||
<input
|
||||
id="new-client"
|
||||
value={newUsername}
|
||||
onChange={(e) => setNewUsername(e.target.value)}
|
||||
placeholder="e.g. ps042"
|
||||
/>
|
||||
<p />
|
||||
<button className="primary" disabled={!newUsername || create.isPending}>
|
||||
{create.isPending ? "Creating…" : "Create client (random password)"}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{clients.isLoading ? (
|
||||
<p>Loading clients…</p>
|
||||
) : (
|
||||
<table style={{ marginTop: "1.5rem" }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Client ID</th>
|
||||
<th>Enabled</th>
|
||||
<th>Groups</th>
|
||||
<th>Roles</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{(clients.data ?? []).map((c) => (
|
||||
<tr key={c.username}>
|
||||
<td className="mono">{c.username}</td>
|
||||
<td className="mono">{c.clientid ?? "—"}</td>
|
||||
<td>{c.enabled ? "yes" : "no"}</td>
|
||||
<td>{c.groups.map((g) => g.groupname).join(", ") || "—"}</td>
|
||||
<td>{c.roles.map((r) => r.rolename).join(", ") || "—"}</td>
|
||||
<td>
|
||||
{c.enabled ? (
|
||||
<button onClick={() => disable.mutate(c.username)}>Disable</button>
|
||||
) : (
|
||||
<button onClick={() => enable.mutate(c.username)}>Enable</button>
|
||||
)}{" "}
|
||||
<button
|
||||
className="danger"
|
||||
onClick={() => {
|
||||
if (confirm(`Delete client ${c.username}?`))
|
||||
remove.mutate(c.username);
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user