Show provisioned users

This commit is contained in:
binwiederhier
2025-12-30 11:30:36 -05:00
parent 75b2ca7dec
commit 2a940ad289
4 changed files with 65 additions and 24 deletions

View File

@@ -24,15 +24,17 @@ func (s *Server) handleUsersGet(w http.ResponseWriter, r *http.Request, v *visit
userGrants := make([]*apiUserGrantResponse, len(grants[u.ID])) userGrants := make([]*apiUserGrantResponse, len(grants[u.ID]))
for i, g := range grants[u.ID] { for i, g := range grants[u.ID] {
userGrants[i] = &apiUserGrantResponse{ userGrants[i] = &apiUserGrantResponse{
Topic: g.TopicPattern, Topic: g.TopicPattern,
Permission: g.Permission.String(), Permission: g.Permission.String(),
Provisioned: g.Provisioned,
} }
} }
usersResponse[i] = &apiUserResponse{ usersResponse[i] = &apiUserResponse{
Username: u.Name, Username: u.Name,
Role: string(u.Role), Role: string(u.Role),
Tier: tier, Tier: tier,
Grants: userGrants, Grants: userGrants,
Provisioned: u.Provisioned,
} }
} }
return s.writeJSON(w, usersResponse) return s.writeJSON(w, usersResponse)

View File

@@ -308,15 +308,17 @@ type apiUserAddOrUpdateRequest struct {
} }
type apiUserResponse struct { type apiUserResponse struct {
Username string `json:"username"` Username string `json:"username"`
Role string `json:"role"` Role string `json:"role"`
Tier string `json:"tier,omitempty"` Tier string `json:"tier,omitempty"`
Grants []*apiUserGrantResponse `json:"grants,omitempty"` Grants []*apiUserGrantResponse `json:"grants,omitempty"`
Provisioned bool `json:"provisioned,omitempty"`
} }
type apiUserGrantResponse struct { type apiUserGrantResponse struct {
Topic string `json:"topic"` // This may be a pattern Topic string `json:"topic"` // This may be a pattern
Permission string `json:"permission"` Permission string `json:"permission"`
Provisioned bool `json:"provisioned,omitempty"`
} }
type apiUserDeleteRequest struct { type apiUserDeleteRequest struct {

View File

@@ -415,10 +415,13 @@
"admin_users_table_grants_header": "Access grants", "admin_users_table_grants_header": "Access grants",
"admin_users_table_actions_header": "Actions", "admin_users_table_actions_header": "Actions",
"admin_users_table_grant_tooltip": "Permission: {{permission}}", "admin_users_table_grant_tooltip": "Permission: {{permission}}",
"admin_users_table_grant_provisioned_tooltip": "Permission: {{permission}} (provisioned, cannot be changed)",
"admin_users_table_add_access_tooltip": "Add access grant", "admin_users_table_add_access_tooltip": "Add access grant",
"admin_users_table_edit_tooltip": "Edit user", "admin_users_table_edit_tooltip": "Edit user",
"admin_users_table_delete_tooltip": "Delete user", "admin_users_table_delete_tooltip": "Delete user",
"admin_users_table_admin_no_actions": "Cannot modify admin users", "admin_users_table_admin_no_actions": "Cannot modify admin users",
"admin_users_provisioned_tooltip": "Provisioned user (defined in server config)",
"admin_users_provisioned_cannot_edit": "Provisioned users cannot be edited or deleted",
"admin_users_role_admin": "Admin", "admin_users_role_admin": "Admin",
"admin_users_role_user": "User", "admin_users_role_user": "User",
"admin_users_add_button": "Add user", "admin_users_add_button": "Add user",

View File

@@ -35,6 +35,7 @@ import { useTranslation } from "react-i18next";
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
import AddIcon from "@mui/icons-material/Add"; import AddIcon from "@mui/icons-material/Add";
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
import LockIcon from "@mui/icons-material/Lock";
import routes from "./routes"; import routes from "./routes";
import { AccountContext } from "./App"; import { AccountContext } from "./App";
import DialogFooter from "./DialogFooter"; import DialogFooter from "./DialogFooter";
@@ -206,7 +207,14 @@ const UsersTable = (props) => {
{users.map((user) => ( {users.map((user) => (
<TableRow key={user.username} sx={{ "&:last-child td, &:last-child th": { border: 0 } }}> <TableRow key={user.username} sx={{ "&:last-child td, &:last-child th": { border: 0 } }}>
<TableCell component="th" scope="row" sx={{ paddingLeft: 0 }}> <TableCell component="th" scope="row" sx={{ paddingLeft: 0 }}>
{user.username} <Stack direction="row" spacing={0.5} alignItems="center">
<span>{user.username}</span>
{user.provisioned && (
<Tooltip title={t("admin_users_provisioned_tooltip")}>
<LockIcon fontSize="small" color="disabled" />
</Tooltip>
)}
</Stack>
</TableCell> </TableCell>
<TableCell> <TableCell>
<RoleChip role={user.role} /> <RoleChip role={user.role} />
@@ -215,23 +223,31 @@ const UsersTable = (props) => {
<TableCell> <TableCell>
{user.grants && user.grants.length > 0 ? ( {user.grants && user.grants.length > 0 ? (
<Stack direction="row" spacing={0.5} flexWrap="wrap" useFlexGap> <Stack direction="row" spacing={0.5} flexWrap="wrap" useFlexGap>
{user.grants.map((grant, idx) => ( {user.grants.map((grant, idx) => {
<Tooltip key={idx} title={t("admin_users_table_grant_tooltip", { permission: grant.permission })}> const canDelete = user.role !== "admin" && !grant.provisioned;
<Chip const tooltipText = grant.provisioned
label={grant.topic} ? t("admin_users_table_grant_provisioned_tooltip", { permission: grant.permission })
size="small" : t("admin_users_table_grant_tooltip", { permission: grant.permission });
variant="outlined" return (
onDelete={user.role !== "admin" ? () => handleDeleteAccessClick(user, grant) : undefined} <Tooltip key={idx} title={tooltipText}>
/> <Chip
</Tooltip> label={grant.topic}
))} size="small"
variant={grant.provisioned ? "filled" : "outlined"}
color={grant.provisioned ? "default" : "default"}
icon={grant.provisioned ? <LockIcon fontSize="small" /> : undefined}
onDelete={canDelete ? () => handleDeleteAccessClick(user, grant) : undefined}
/>
</Tooltip>
);
})}
</Stack> </Stack>
) : ( ) : (
"-" "-"
)} )}
</TableCell> </TableCell>
<TableCell align="right" sx={{ whiteSpace: "nowrap" }}> <TableCell align="right" sx={{ whiteSpace: "nowrap" }}>
{user.role !== "admin" ? ( {user.role !== "admin" && !user.provisioned ? (
<> <>
<Tooltip title={t("admin_users_table_add_access_tooltip")}> <Tooltip title={t("admin_users_table_add_access_tooltip")}>
<IconButton onClick={() => handleAddAccessClick(user)} size="small"> <IconButton onClick={() => handleAddAccessClick(user)} size="small">
@@ -249,6 +265,24 @@ const UsersTable = (props) => {
</IconButton> </IconButton>
</Tooltip> </Tooltip>
</> </>
) : user.role !== "admin" && user.provisioned ? (
<>
<Tooltip title={t("admin_users_table_add_access_tooltip")}>
<IconButton onClick={() => handleAddAccessClick(user)} size="small">
<AddIcon />
</IconButton>
</Tooltip>
<Tooltip title={t("admin_users_provisioned_cannot_edit")}>
<span>
<IconButton disabled size="small">
<EditIcon />
</IconButton>
<IconButton disabled size="small">
<DeleteOutlineIcon />
</IconButton>
</span>
</Tooltip>
</>
) : ( ) : (
<Tooltip title={t("admin_users_table_admin_no_actions")}> <Tooltip title={t("admin_users_table_admin_no_actions")}>
<span> <span>