/** * Admin Panel: User Management. Lists real accounts (GET /admin/users), * creates them (POST, with a bcrypt-hashed password server-side -- this * page only ever sends plaintext over the wire once, at creation/reset * time, and never displays a password hash anywhere), edits profiles, * enables/disables accounts, and assigns roles. * * Every action below is a real network call to a real, backend-enforced * endpoint (see azure-functions/api_blueprint.py's require_permission() * calls on each admin/users* route) -- this page itself only decides what * to SHOW, never what's actually ALLOWED; a disabled button here is a * convenience, not the security boundary (the server would refuse the * call either way, see the 403s surfaced below when it does). */ function UserManagement(props) { var api = props.api; var currentUser = props.currentUser; // Snapshot-cached, per the "no loading flash on revisit" requirement -- // every mutation handler below (create/edit/status/roles) still calls // `load` (now snapshot.refresh) on success, so your own change is // never hidden behind a stale cached list; only an idle REvisit with // no changes skips the reload. var snapshot = CDPI_REPORT_SNAPSHOT.useReportSnapshot("user_management", function () { return Promise.all([api.listUsers(), api.listRoles()]) .then(function (results) { return { users: results[0].users, roles: results[1].roles }; }); }); var data = { loading: snapshot.state.loading && !snapshot.state.data, error: snapshot.state.error, users: (snapshot.state.data && snapshot.state.data.users) || [], roles: (snapshot.state.data && snapshot.state.data.roles) || [], }; var load = snapshot.refresh; var formState = React.useState(null); // { mode: "create"|"edit"|"roles", user } | null var form = formState[0]; var setForm = formState[1]; var actionError = React.useState(null); var errorMsg = actionError[0]; var setErrorMsg = actionError[1]; function roleLabel(roleKey) { var role = data.roles.filter(function (r) { return r.role_key === roleKey; })[0]; return role ? role.display_name : roleKey; } function handleToggleStatus(user) { var next = user.status === "active" ? "disabled" : "active"; setErrorMsg(null); api.setUserStatus(user.id, next) .then(load) .catch(function (err) { setErrorMsg(err.message); }); } return (

User Management

Real accounts, real bcrypt-hashed passwords, real backend-enforced permissions -- not the old client-side demo login. See auth.py and azure-functions/api_blueprint.py.

Snapshot from {snapshot.state.lastUpdated ? new Date(snapshot.state.lastUpdated).toLocaleString() : "just now"}
{errorMsg &&
{errorMsg}
} {data.error &&
Couldn't load users: {data.error}
} {data.loading &&

Loading…

} {!data.loading && !data.error && (
{data.users.map(function (u) { var isSelf = u.id === currentUser.id; return ( ); })}
UsernameDisplay nameEmailRolesStatusActions
{u.username} {u.display_name} {u.email || "—"} {(u.role_keys || []).length ? u.role_keys.map(function (k) { return {roleLabel(k)}; }) : No role assigned} {u.status}
)} {form && form.mode === "create" && ( )} {form && form.mode === "edit" && ( )} {form && form.mode === "roles" && ( )}
); } function UserFormModal(props) { var isEdit = !!props.user; var roles = props.roles; var onClose = props.onClose; var onSave = props.onSave; var fieldsState = React.useState({ username: props.user ? props.user.username : "", password: "", display_name: props.user ? props.user.display_name : "", email: props.user ? props.user.email : "", role_keys: props.user ? (props.user.role_keys || []) : [], }); var fields = fieldsState[0]; var setFields = fieldsState[1]; var saveState = React.useState({ saving: false, error: null }); var save = saveState[0]; var setSave = saveState[1]; function setField(name, value) { setFields(function (f) { return Object.assign({}, f, { [name]: value }); }); } function toggleRole(roleKey) { setFields(function (f) { var next = f.role_keys.indexOf(roleKey) === -1 ? f.role_keys.concat([roleKey]) : f.role_keys.filter(function (k) { return k !== roleKey; }); return Object.assign({}, f, { role_keys: next }); }); } function handleSubmit(e) { e.preventDefault(); setSave({ saving: true, error: null }); var payload = isEdit ? { display_name: fields.display_name, email: fields.email, password: fields.password || undefined } : { username: fields.username, password: fields.password, display_name: fields.display_name, email: fields.email, role_keys: fields.role_keys }; onSave(payload).catch(function (err) { setSave({ saving: false, error: err.message }); }); } return (

{isEdit ? "Edit User" : "New User"}

{!isEdit && ( )} {!isEdit && (
Roles
{roles.map(function (r) { return ( ); })}
)} {save.error &&
{save.error}
}
); } function UserRolesModal(props) { var user = props.user; var roles = props.roles; var onClose = props.onClose; var onSave = props.onSave; var selectedState = React.useState((user.role_keys || []).slice()); var selected = selectedState[0]; var setSelected = selectedState[1]; var saveState = React.useState({ saving: false, error: null }); var save = saveState[0]; var setSave = saveState[1]; function toggle(roleKey) { setSelected(function (s) { return s.indexOf(roleKey) === -1 ? s.concat([roleKey]) : s.filter(function (k) { return k !== roleKey; }); }); } function handleSave() { setSave({ saving: true, error: null }); onSave(selected).catch(function (err) { setSave({ saving: false, error: err.message }); }); } return (

Roles for {user.display_name}

{roles.map(function (r) { return ( ); })}
{save.error &&
{save.error}
}
); }