Path: blob/main/components/dashboard/src/admin/UserSearch.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 { AdminGetListResult, User } from "@gitpod/gitpod-protocol";7import dayjs from "dayjs";8import { useEffect, useState } from "react";9import { useLocation } from "react-router";10import { Link } from "react-router-dom";11import { SpinnerLoader } from "../components/Loader";12import Pagination from "../Pagination/Pagination";13import { getGitpodService } from "../service/service";14import { AdminPageHeader } from "./AdminPageHeader";15import UserDetail from "./UserDetail";16import searchIcon from "../icons/search.svg";17import Tooltip from "../components/Tooltip";18import { getPrimaryEmail } from "@gitpod/public-api-common/lib/user-utils";1920export default function UserSearch() {21const location = useLocation();22const [searchResult, setSearchResult] = useState<AdminGetListResult<User>>({ rows: [], total: 0 });23const [searchTerm, setSearchTerm] = useState("");24const [searching, setSearching] = useState(false);25const [currentUser, setCurrentUserState] = useState<User | undefined>(undefined);26const pageLength = 50;27const [currentPage, setCurrentPage] = useState(1);2829useEffect(() => {30const userId = location.pathname.split("/")[3];31if (userId) {32let user = searchResult.rows.find((u) => u.id === userId);33if (user) {34setCurrentUserState(user);35} else {36getGitpodService()37.server.adminGetUser(userId)38.then((user) => setCurrentUserState(user))39.catch((e) => console.error(e));40}41} else {42setCurrentUserState(undefined);43}44// eslint-disable-next-line react-hooks/exhaustive-deps45}, [location]);4647if (currentUser) {48return <UserDetail user={currentUser} />;49}5051const search = async (page: number = 1) => {52setSearching(true);53try {54const result = await getGitpodService().server.adminGetUsers({55searchTerm,56limit: pageLength,57orderBy: "creationDate",58offset: (page - 1) * pageLength,59orderDir: "desc",60});61setSearchResult(result);62setCurrentPage(page);63} finally {64setSearching(false);65}66};67return (68<AdminPageHeader title="Admin" subtitle="Configure and manage instance settings.">69<div className="app-container">70<div className="mb-3 mt-3 flex">71<div className="flex justify-between w-full">72<div className="flex relative h-10 my-auto">73{searching ? (74<span className="filter-grayscale absolute top-3 left-3">75<SpinnerLoader small={true} />76</span>77) : (78<img79src={searchIcon}80title="Search"81className="filter-grayscale absolute top-3 left-3"82alt="search icon"83/>84)}85<input86className="w-64 pl-9 border-0"87type="search"88placeholder="Search Users"89onKeyDown={(ke) => ke.key === "Enter" && search()}90onChange={(v) => {91setSearchTerm(v.target.value.trim());92}}93/>94</div>95</div>96</div>97<div className="flex flex-col space-y-2">98<div className="px-6 py-3 flex justify-between space-x-2 text-sm text-gray-400 border-t border-b border-gray-200 dark:border-gray-800 mb-2">99<div className="w-7/12">Name</div>100<div className="w-5/12 flex items-center">101<span>Created</span>102<svg xmlns="http://www.w3.org/2000/svg" fill="none" className="h-4 w-4" viewBox="0 0 16 16">103<path104fill="#A8A29E"105fillRule="evenodd"106d="M13.366 8.234a.8.8 0 010 1.132l-4.8 4.8a.8.8 0 01-1.132 0l-4.8-4.8a.8.8 0 111.132-1.132L7.2 11.67V2.4a.8.8 0 111.6 0v9.269l3.434-3.435a.8.8 0 011.132 0z"107clipRule="evenodd"108/>109</svg>110</div>111</div>112{searchResult.rows113.filter((u) => u.identities.length > 0)114.map((u) => (115<UserEntry user={u} />116))}117</div>118<Pagination119currentPage={currentPage}120setPage={search}121totalNumberOfPages={Math.ceil(searchResult.total / pageLength)}122/>123</div>124</AdminPageHeader>125);126}127128function UserEntry(p: { user: User }) {129if (!p) {130return <></>;131}132const email = getPrimaryEmail(p.user) || "---";133return (134<Link key={p.user.id} to={"/admin/users/" + p.user.id} data-analytics='{"button_type":"sidebar_menu"}'>135<div className="rounded-xl whitespace-nowrap flex space-x-2 py-6 px-6 w-full justify-between hover:bg-gray-100 dark:hover:bg-gray-800 focus:bg-kumquat-light group">136<div className="pr-3 self-center w-1/12">137<img className="rounded-full" src={p.user.avatarUrl} alt={p.user.fullName || p.user.name} />138</div>139<div className="flex flex-col w-6/12">140<div className="font-medium text-gray-800 dark:text-gray-100 truncate hover:text-blue-600 dark:hover:text-blue-400">141{p.user.fullName}142</div>143<div className="text-sm overflow-ellipsis truncate text-gray-400 hover:text-blue-600 dark:hover:text-blue-400">144{email}145</div>146</div>147<div className="flex w-5/12 self-center">148<Tooltip content={dayjs(p.user.creationDate).format("MMM D, YYYY")}>149<div className="text-sm w-full text-gray-400 truncate">150{dayjs(p.user.creationDate).fromNow()}151</div>152</Tooltip>153</div>154</div>155</Link>156);157}158159160