Path: blob/main/components/dashboard/src/admin/UserDetail.tsx
2500 views
/**1* Copyright (c) 2021 Gitpod GmbH. All rights reserved.2* Licensed under the GNU Affero General Public License (AGPL).3* See License.AGPL.txt in the project root for license information.4*/56import {7NamedWorkspaceFeatureFlag,8Permissions,9RoleOrPermission,10Roles,11User,12WorkspaceFeatureFlags,13} from "@gitpod/gitpod-protocol";14import dayjs from "dayjs";15import { useEffect, useRef, useState } from "react";16import Modal from "../components/Modal";17import { getGitpodService } from "../service/service";18import { WorkspaceSearch } from "./WorkspacesSearch";19import Property from "./Property";20import { AdminPageHeader } from "./AdminPageHeader";21import { CheckboxInputField, CheckboxListField } from "../components/forms/CheckboxInputField";22import { Heading2, Subheading } from "../components/typography/headings";23import { Button } from "@podkit/buttons/Button";2425export default function UserDetail(p: { user: User }) {26const [activity, setActivity] = useState(false);27const [user, setUser] = useState(p.user);28const [editFeatureFlags, setEditFeatureFlags] = useState(false);29const [editRoles, setEditRoles] = useState(false);30const userRef = useRef(user);3132const initialize = () => {33setUser(user);34};35useEffect(initialize, [user]);3637const updateUser: UpdateUserFunction = async (fun) => {38setActivity(true);39try {40setUser(await fun(userRef.current));41} finally {42setActivity(false);43}44};4546const verifyUser = async () => {47await updateUser(async (u) => {48return await getGitpodService().server.adminVerifyUser(u.id);49});50};5152const toggleBlockUser = async () => {53await updateUser(async (u) => {54u.blocked = !u.blocked;55await getGitpodService().server.adminBlockUser({56blocked: u.blocked,57id: u.id,58});59return u;60});61};6263const deleteUser = async () => {64await updateUser(async (u) => {65u.markedDeleted = !u.markedDeleted;66await getGitpodService().server.adminDeleteUser(u.id);67return u;68});69};7071const flags = getFlags(user, updateUser);72const rop = getRopEntries(user, updateUser);7374return (75<>76<AdminPageHeader title="Admin" subtitle="Configure and manage instance settings.">77<div className="app-container">78<div className="flex mt-8">79<div className="flex-1">80<div className="flex">81<Heading2>{user.fullName}</Heading2>82{user.blocked ? <Label text="Blocked" color="red" /> : null}{" "}83{user.markedDeleted ? <Label text="Deleted" color="red" /> : null}84{user.lastVerificationTime ? <Label text="Verified" color="green" /> : null}85</div>86<Subheading>87{user.identities88.map((i) => i.primaryEmail)89.filter((e) => !!e)90.join(", ")}91{user.verificationPhoneNumber ? ` — ${user.verificationPhoneNumber}` : null}92</Subheading>93</div>94{!user.lastVerificationTime ? (95<Button variant="secondary" className="ml-3" disabled={activity} onClick={verifyUser}>96Verify User97</Button>98) : null}99<Button variant="destructive" className="ml-3" disabled={activity} onClick={toggleBlockUser}>100{user.blocked ? "Unblock" : "Block"} User101</Button>102<Button variant="destructive" className="ml-3" disabled={activity} onClick={deleteUser}>103Delete User104</Button>105</div>106<div className="flex mt-6">107<div className="w-40">108<img className="rounded-full h-28 w-28" alt={user.fullName} src={user.avatarUrl} />109</div>110<div className="flex flex-col w-full">111<div className="flex w-full mt-6">112<Property name="Sign Up Date">113{dayjs(user.creationDate).format("MMM D, YYYY")}114</Property>115<Property116name="Feature Flags"117actions={[118{119label: "Edit Feature Flags",120onClick: () => {121setEditFeatureFlags(true);122},123},124]}125>126{user.featureFlags?.permanentWSFeatureFlags?.join(", ") || "---"}127</Property>128<Property129name="Roles"130actions={[131{132label: "Edit Roles",133onClick: () => {134setEditRoles(true);135},136},137]}138>139{user.rolesOrPermissions?.join(", ") || "---"}140</Property>141</div>142</div>143</div>144</div>145146<WorkspaceSearch user={user} />147</AdminPageHeader>148149<Modal150visible={editFeatureFlags}151onClose={() => setEditFeatureFlags(false)}152title="Edit Feature Flags"153buttons={[154<Button variant="secondary" onClick={() => setEditFeatureFlags(false)}>155Done156</Button>,157]}158>159<CheckboxListField160label="Edit feature access by adding or removing feature flags for this user."161className="mt-0"162>163{flags.map((e) => (164<CheckboxInputField165key={e.title}166label={e.title}167checked={!!e.checked}168topMargin={false}169onChange={e.onClick}170/>171))}172</CheckboxListField>173</Modal>174<Modal175visible={editRoles}176onClose={() => setEditRoles(false)}177title="Edit Roles"178buttons={[179<Button variant="secondary" onClick={() => setEditRoles(false)}>180Done181</Button>,182]}183>184<CheckboxListField185label="Edit user permissions by adding or removing roles for this user."186className="mt-0"187>188{rop.map((e) => (189<CheckboxInputField190key={e.title}191label={e.title}192checked={!!e.checked}193topMargin={false}194onChange={e.onClick}195/>196))}197</CheckboxListField>198</Modal>199</>200);201}202203function Label(p: { text: string; color: string }) {204return (205<div className={`ml-3 text-sm text-${p.color}-600 truncate bg-${p.color}-100 px-1.5 py-0.5 rounded-md my-auto`}>206{p.text}207</div>208);209}210211interface Entry {212title: string;213checked: boolean;214onClick: () => void;215}216217type UpdateUserFunction = (fun: (u: User) => Promise<User>) => Promise<void>;218219function getFlags(user: User, updateUser: UpdateUserFunction): Entry[] {220return Object.entries(WorkspaceFeatureFlags)221.map((e) => e[0] as NamedWorkspaceFeatureFlag)222.map((name) => {223const checked = !!user.featureFlags?.permanentWSFeatureFlags?.includes(name);224return {225title: name,226checked,227onClick: async () => {228await updateUser(async (u) => {229return await getGitpodService().server.adminModifyPermanentWorkspaceFeatureFlag({230id: user.id,231changes: [232{233featureFlag: name,234add: !checked,235},236],237});238});239},240};241});242}243244function getRopEntries(user: User, updateUser: UpdateUserFunction): Entry[] {245const createRopEntry = (name: RoleOrPermission, role?: boolean) => {246const checked = user.rolesOrPermissions?.includes(name)!!;247return {248title: (role ? "Role: " : "Permission: ") + name,249checked,250onClick: async () => {251await updateUser(async (u) => {252return await getGitpodService().server.adminModifyRoleOrPermission({253id: user.id,254rpp: [255{256r: name,257add: !checked,258},259],260});261});262},263};264};265return [266...Object.entries(Permissions).map((e) => createRopEntry(e[0] as RoleOrPermission)),267...Object.entries(Roles).map((e) => createRopEntry(e[0] as RoleOrPermission, true)),268];269}270271272