Files
coder/site/src/components/UsersTable/UsersTableBody.tsx
T

233 lines
7.7 KiB
TypeScript

import Box from "@material-ui/core/Box"
import { makeStyles } from "@material-ui/core/styles"
import TableCell from "@material-ui/core/TableCell"
import TableRow from "@material-ui/core/TableRow"
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"
import { LastUsed } from "components/LastUsed/LastUsed"
import { Pill } from "components/Pill/Pill"
import { FC } from "react"
import { useTranslation } from "react-i18next"
import * as TypesGen from "../../api/typesGenerated"
import { combineClasses } from "../../util/combineClasses"
import { AvatarData } from "../AvatarData/AvatarData"
import { EmptyState } from "../EmptyState/EmptyState"
import { TableLoader } from "../TableLoader/TableLoader"
import { TableRowMenu } from "../TableRowMenu/TableRowMenu"
import { EditRolesButton } from "components/EditRolesButton/EditRolesButton"
import { Stack } from "components/Stack/Stack"
const isOwnerRole = (role: TypesGen.Role): boolean => {
return role.name === "owner"
}
const roleOrder = ["owner", "user-admin", "template-admin", "auditor"]
const sortRoles = (roles: TypesGen.Role[]) => {
return roles.slice(0).sort((a, b) => {
return roleOrder.indexOf(a.name) - roleOrder.indexOf(b.name)
})
}
interface UsersTableBodyProps {
users?: TypesGen.User[]
roles?: TypesGen.AssignableRoles[]
isUpdatingUserRoles?: boolean
canEditUsers?: boolean
isLoading?: boolean
onSuspendUser: (user: TypesGen.User) => void
onDeleteUser: (user: TypesGen.User) => void
onListWorkspaces: (user: TypesGen.User) => void
onActivateUser: (user: TypesGen.User) => void
onResetUserPassword: (user: TypesGen.User) => void
onUpdateUserRoles: (
user: TypesGen.User,
roles: TypesGen.Role["name"][],
) => void
isNonInitialPage: boolean
}
export const UsersTableBody: FC<
React.PropsWithChildren<UsersTableBodyProps>
> = ({
users,
roles,
onSuspendUser,
onDeleteUser,
onListWorkspaces,
onActivateUser,
onResetUserPassword,
onUpdateUserRoles,
isUpdatingUserRoles,
canEditUsers,
isLoading,
isNonInitialPage,
}) => {
const styles = useStyles()
const { t } = useTranslation("usersPage")
return (
<ChooseOne>
<Cond condition={Boolean(isLoading)}>
<TableLoader />
</Cond>
<Cond condition={!users || users.length === 0}>
<ChooseOne>
<Cond condition={isNonInitialPage}>
<TableRow>
<TableCell colSpan={999}>
<Box p={4}>
<EmptyState message={t("emptyPageMessage")} />
</Box>
</TableCell>
</TableRow>
</Cond>
<Cond>
<TableRow>
<TableCell colSpan={999}>
<Box p={4}>
<EmptyState message={t("emptyMessage")} />
</Box>
</TableCell>
</TableRow>
</Cond>
</ChooseOne>
</Cond>
<Cond>
<>
{users &&
users.map((user) => {
// When the user has no role we want to show they are a Member
const fallbackRole: TypesGen.Role = {
name: "member",
display_name: "Member",
}
const userRoles =
user.roles.length === 0 ? [fallbackRole] : sortRoles(user.roles)
return (
<TableRow key={user.id}>
<TableCell>
<AvatarData
title={user.username}
subtitle={user.email}
highlightTitle
avatar={
user.avatar_url ? (
<img
className={styles.avatar}
alt={`${user.username}'s Avatar`}
src={user.avatar_url}
/>
) : null
}
/>
</TableCell>
<TableCell>
<Stack direction="row" spacing={1}>
{canEditUsers && (
<EditRolesButton
roles={roles ? sortRoles(roles) : []}
selectedRoles={userRoles}
isLoading={Boolean(isUpdatingUserRoles)}
onChange={(roles) => {
// Remove the fallback role because it is only for the UI
const rolesWithoutFallback = roles.filter(
(role) => role !== fallbackRole.name,
)
onUpdateUserRoles(user, rolesWithoutFallback)
}}
/>
)}
{userRoles.map((role) => (
<Pill
key={role.name}
text={role.display_name}
className={combineClasses({
[styles.rolePill]: true,
[styles.rolePillOwner]: isOwnerRole(role),
})}
/>
))}
</Stack>
</TableCell>
<TableCell
className={combineClasses([
styles.status,
user.status === "suspended"
? styles.suspended
: undefined,
])}
>
{user.status}
</TableCell>
<TableCell>
<LastUsed lastUsedAt={user.last_seen_at} />
</TableCell>
{canEditUsers && (
<TableCell>
<TableRowMenu
data={user}
menuItems={
// Return either suspend or activate depending on status
(user.status === "active"
? [
{
label: t("suspendMenuItem"),
onClick: onSuspendUser,
},
]
: [
{
label: t("activateMenuItem"),
onClick: onActivateUser,
},
]
).concat(
{
label: t("deleteMenuItem"),
onClick: onDeleteUser,
},
{
label: t("listWorkspacesMenuItem"),
onClick: onListWorkspaces,
},
{
label: t("resetPasswordMenuItem"),
onClick: onResetUserPassword,
},
)
}
/>
</TableCell>
)}
</TableRow>
)
})}
</>
</Cond>
</ChooseOne>
)
}
const useStyles = makeStyles((theme) => ({
status: {
textTransform: "capitalize",
},
suspended: {
color: theme.palette.text.secondary,
},
avatar: {
width: theme.spacing(4.5),
height: theme.spacing(4.5),
borderRadius: "100%",
},
rolePill: {
backgroundColor: theme.palette.background.paperLight,
borderColor: theme.palette.divider,
},
rolePillOwner: {
backgroundColor: theme.palette.info.dark,
borderColor: theme.palette.info.light,
},
}))