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