Files
NexaFabric/frontend/src/pages/UsersRoles.tsx
T
nessi 3cd2c0a2f1
CI / backend (push) Failing after 3s
CI / frontend (push) Failing after 29s
feat: add initial setup wizard, workload insights, and policy audit mode
Add setup wizard with status tracking via SystemSetting model, implement /setup/status and /setup/complete endpoints to create initial admin user and optional cluster configuration, add workload insights endpoint with traffic analysis and policy matching including audit mode detection, implement enforcement_mode property on Policy model with audit/enforced states, add Modal component for dialogs, create SetupWizard page with multi
2026-07-09 12:47:08 +02:00

82 lines
4.9 KiB
TypeScript

import { FormEvent, useState } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { Plus, Users } from "lucide-react";
import { api, Role, User } from "../api/client";
import { DataTable } from "../components/DataTable";
import { buttonClass, Field, inputClass, selectClass } from "../components/FormControls";
import { Modal } from "../components/Modal";
import { PageHeader } from "../components/PageHeader";
export function UsersRoles() {
const queryClient = useQueryClient();
const users = useQuery({ queryKey: ["users"], queryFn: () => api<User[]>("/users") });
const roles = useQuery({ queryKey: ["roles"], queryFn: () => api<Role[]>("/roles") });
const [roleForm, setRoleForm] = useState({ name: "Helpdesk", permissions: "clusters:read,networks:read,audit:read" });
const [userForm, setUserForm] = useState({ email: "operator@nexafabric.local", display_name: "Operator", password: "ChangeMe_12345", role_id: "" });
const [roleOpen, setRoleOpen] = useState(false);
const [userOpen, setUserOpen] = useState(false);
const createRole = useMutation({
mutationFn: () => api<Role>("/roles", { method: "POST", body: JSON.stringify({ name: roleForm.name, permissions: roleForm.permissions.split(",").map((item) => item.trim()).filter(Boolean) }) }),
onSuccess: () => {
setRoleOpen(false);
queryClient.invalidateQueries({ queryKey: ["roles"] });
},
});
const createUser = useMutation({
mutationFn: () => api<User>("/users", { method: "POST", body: JSON.stringify({ email: userForm.email, display_name: userForm.display_name, password: userForm.password, role_ids: userForm.role_id ? [userForm.role_id] : [] }) }),
onSuccess: () => {
setUserOpen(false);
queryClient.invalidateQueries({ queryKey: ["users"] });
},
});
async function submitRole(event: FormEvent) {
event.preventDefault();
await createRole.mutateAsync();
}
async function submitUser(event: FormEvent) {
event.preventDefault();
await createUser.mutateAsync();
}
return (
<>
<PageHeader title="Users & Roles" subtitle="Manage local users, roles, and permission sets." />
<div className="space-y-4">
<div className="flex flex-wrap gap-2">
<button className={buttonClass} onClick={() => setRoleOpen(true)}><Plus size={16} /> Add Role</button>
<button className={buttonClass} onClick={() => setUserOpen(true)}><Plus size={16} /> Add User</button>
</div>
<Modal title="Add Role" open={roleOpen} onClose={() => setRoleOpen(false)}>
<form onSubmit={submitRole}>
<div className="mb-4 font-medium">Add Role</div>
<div className="grid gap-3">
<Field label="Name"><input className={inputClass} value={roleForm.name} onChange={(event) => setRoleForm({ ...roleForm, name: event.target.value })} /></Field>
<Field label="Permissions"><input className={inputClass} value={roleForm.permissions} onChange={(event) => setRoleForm({ ...roleForm, permissions: event.target.value })} /></Field>
<button className={buttonClass}><Plus size={16} /> Save Role</button>
</div>
</form>
</Modal>
<Modal title="Add User" open={userOpen} onClose={() => setUserOpen(false)}>
<form onSubmit={submitUser}>
<div className="mb-4 flex items-center gap-2 font-medium"><Users size={18} /> Add User</div>
<div className="grid gap-3">
<Field label="Email"><input className={inputClass} value={userForm.email} onChange={(event) => setUserForm({ ...userForm, email: event.target.value })} /></Field>
<Field label="Name"><input className={inputClass} value={userForm.display_name} onChange={(event) => setUserForm({ ...userForm, display_name: event.target.value })} /></Field>
<Field label="Password"><input className={inputClass} type="password" value={userForm.password} onChange={(event) => setUserForm({ ...userForm, password: event.target.value })} /></Field>
<Field label="Role"><select className={selectClass} value={userForm.role_id} onChange={(event) => setUserForm({ ...userForm, role_id: event.target.value })}><option value="">No role</option>{(roles.data ?? []).map((role) => <option key={role.id} value={role.id}>{role.name}</option>)}</select></Field>
<button className={buttonClass}><Plus size={16} /> Save User</button>
</div>
</form>
</Modal>
<section className="space-y-4">
<DataTable rows={(users.data ?? []) as unknown as Record<string, unknown>[]} columns={[{ key: "email", label: "Email" }, { key: "display_name", label: "Name" }, { key: "is_active", label: "Active" }]} />
<DataTable rows={(roles.data ?? []) as unknown as Record<string, unknown>[]} columns={[{ key: "name", label: "Role" }, { key: "permissions", label: "Permissions" }]} />
</section>
</div>
</>
);
}