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