Path: blob/main/components/dashboard/src/admin/ProjectsSearch.tsx
2500 views
/**1* Copyright (c) 2022 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 dayjs from "dayjs";7import { useLocation } from "react-router";8import { Link } from "react-router-dom";9import { useState, useEffect } from "react";1011import ProjectDetail from "./ProjectDetail";12import { getGitpodService } from "../service/service";13import { AdminGetListResult, Project } from "@gitpod/gitpod-protocol";14import { AdminPageHeader } from "./AdminPageHeader";15import Pagination from "../Pagination/Pagination";16import { SpinnerLoader } from "../components/Loader";17import searchIcon from "../icons/search.svg";18import Tooltip from "../components/Tooltip";1920export default function ProjectsSearchPage() {21return (22<AdminPageHeader title="Admin" subtitle="Configure and manage instance settings.">23<ProjectsSearch />24</AdminPageHeader>25);26}2728export function ProjectsSearch() {29const location = useLocation();30const [searchTerm, setSearchTerm] = useState("");31const [searching, setSearching] = useState(false);32const [searchResult, setSearchResult] = useState<AdminGetListResult<Project>>({ total: 0, rows: [] });33const [currentProject, setCurrentProject] = useState<Project | undefined>(undefined);34const [currentProjectOwner, setCurrentProjectOwner] = useState<string | undefined>("");35const pageLength = 50;36const [currentPage, setCurrentPage] = useState(1);3738useEffect(() => {39const projectId = location.pathname.split("/")[3];40if (projectId && searchResult) {41let currentProject = searchResult.rows.find((project) => project.id === projectId);42if (currentProject) {43setCurrentProject(currentProject);44} else {45getGitpodService()46.server.adminGetProjectById(projectId)47.then((project) => setCurrentProject(project))48.catch((e) => console.error(e));49}50} else {51setCurrentProject(undefined);52}53// eslint-disable-next-line react-hooks/exhaustive-deps54}, [location]);5556useEffect(() => {57(async () => {58if (currentProject) {59const owner = await getGitpodService().server.adminGetTeamById(currentProject.teamId);60if (owner) {61setCurrentProjectOwner(owner.name);62}63}64})();65}, [currentProject]);6667if (currentProject) {68return <ProjectDetail project={currentProject} owner={currentProjectOwner} />;69}7071const search = async (page: number = 1) => {72setSearching(true);73try {74const result = await getGitpodService().server.adminGetProjectsBySearchTerm({75searchTerm,76limit: pageLength,77orderBy: "creationTime",78offset: (page - 1) * pageLength,79orderDir: "desc",80});81setCurrentPage(page);82setSearchResult(result);83} finally {84setSearching(false);85}86};8788return (89<div className="app-container">90<div className="pt-3 mb-3 flex">91<div className="flex justify-between w-full">92<div className="flex relative h-10 my-auto">93{searching ? (94<span className="filter-grayscale absolute top-3 left-3">95<SpinnerLoader small={true} />96</span>97) : (98<img99src={searchIcon}100title="Search"101className="filter-grayscale absolute top-3 left-3"102alt="search icon"103/>104)}105<input106className="w-64 pl-9 border-0"107type="search"108placeholder="Search Projects"109onKeyDown={(k) => k.key === "Enter" && search()}110onChange={(v) => {111setSearchTerm(v.target.value.trim());112}}113/>114</div>115</div>116</div>117<div className="flex flex-col space-y-2">118<div className="px-6 py-3 flex justify-between text-sm text-gray-400 border-t border-b border-gray-200 dark:border-gray-800 mb-2">119<div className="w-4/12">Name</div>120<div className="w-6/12">Clone URL</div>121<div className="w-2/12">Created</div>122</div>123{searchResult.rows.map((project) => (124<ProjectResultItem project={project} />125))}126</div>127<Pagination128currentPage={currentPage}129setPage={search}130totalNumberOfPages={Math.ceil(searchResult.total / pageLength)}131/>132</div>133);134135function ProjectResultItem({ project }: { project: Project }) {136return (137<Link key={project.id} to={`/admin/projects/${project.id}`} data-analytics='{"button_type":"sidebar_menu"}'>138<div className="rounded-xl whitespace-nowrap flex py-6 px-6 w-full justify-between hover:bg-gray-100 dark:hover:bg-gray-800 focus:bg-kumquat-light group">139<div className="flex flex-col w-4/12 truncate">140<div className="font-medium text-gray-800 dark:text-gray-100 truncate">{project.name}</div>141</div>142<div className="flex flex-col w-6/12 truncate">143<div className="text-gray-500 dark:text-gray-100 truncate">{project.cloneUrl}</div>144</div>145<div className="flex w-2/12 self-center">146<Tooltip content={dayjs(project.creationTime).format("MMM D, YYYY")}>147<div className="text-sm w-full text-gray-400 truncate">148{dayjs(project.creationTime).fromNow()}149</div>150</Tooltip>151</div>152</div>153</Link>154);155}156}157158159