Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/dashboard/src/admin/WorkspaceDetail.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 { User, WorkspaceAndInstance, ContextURL, WorkspaceInstance } from "@gitpod/gitpod-protocol";
8
import { GitpodHostUrl } from "@gitpod/gitpod-protocol/lib/util/gitpod-host-url";
9
import dayjs from "dayjs";
10
import { useEffect, useState } from "react";
11
import { Link } from "react-router-dom";
12
import { Heading2, Subheading } from "../components/typography/headings";
13
import { getGitpodService } from "../service/service";
14
import { getProjectPath } from "../workspaces/WorkspaceEntry";
15
import { WorkspaceStatusIndicator } from "../workspaces/WorkspaceStatusIndicator";
16
import Property from "./Property";
17
import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution";
18
import { converter } from "../service/public-api";
19
import { Button } from "@podkit/buttons/Button";
20
21
export default function WorkspaceDetail(props: { workspace: WorkspaceAndInstance }) {
22
const [workspace, setWorkspace] = useState(props.workspace);
23
const [workspaceInstances, setWorkspaceInstances] = useState<WorkspaceInstance[]>([]);
24
const [activity, setActivity] = useState(false);
25
const [user, setUser] = useState<User>();
26
useEffect(() => {
27
getGitpodService().server.adminGetUser(props.workspace.ownerId).then(setUser);
28
getGitpodService().server.adminGetWorkspaceInstances(props.workspace.workspaceId).then(setWorkspaceInstances);
29
}, [props.workspace, workspace.workspaceId]);
30
31
const stopWorkspace = async () => {
32
try {
33
setActivity(true);
34
await getGitpodService().server.adminForceStopWorkspace(workspace.workspaceId);
35
// let's reload in a sec
36
setTimeout(reload, 2000);
37
} finally {
38
setActivity(false);
39
}
40
};
41
42
const reload = async () => {
43
try {
44
setActivity(true);
45
const [ws, workspaceInstances] = await Promise.all([
46
await getGitpodService().server.adminGetWorkspace(workspace.workspaceId),
47
await getGitpodService().server.adminGetWorkspaceInstances(workspace.workspaceId),
48
]);
49
setWorkspace(ws);
50
setWorkspaceInstances(workspaceInstances);
51
} finally {
52
setActivity(false);
53
}
54
};
55
56
return (
57
<div className="app-container">
58
<div className="flex mt-8">
59
<div className="flex-1">
60
<div className="flex">
61
<Heading2>{workspace.workspaceId}</Heading2>
62
<span className="my-auto ml-3">
63
<WorkspaceStatusIndicator
64
status={
65
converter.toWorkspace({
66
workspace: WorkspaceAndInstance.toWorkspace(workspace),
67
latestInstance: WorkspaceAndInstance.toInstance(workspace),
68
}).status
69
}
70
/>
71
</span>
72
</div>
73
<Subheading>
74
{getProjectPath(
75
converter.toWorkspace({
76
workspace: WorkspaceAndInstance.toWorkspace(workspace),
77
latestInstance: WorkspaceAndInstance.toInstance(workspace),
78
}),
79
)}
80
</Subheading>
81
</div>
82
<Button
83
variant="secondary"
84
className="ml-3"
85
onClick={() => {
86
window.location.href = new GitpodHostUrl(window.location.href)
87
.with({
88
pathname: `/workspace-download/get/${workspace.workspaceId}`,
89
})
90
.toString();
91
}}
92
>
93
Download Workspace
94
</Button>
95
<Button
96
variant="destructive"
97
className="ml-3"
98
disabled={activity || workspace.phase === "stopped"}
99
onClick={stopWorkspace}
100
>
101
Stop Workspace
102
</Button>
103
</div>
104
<div className="flex mt-6">
105
<div className="flex flex-col w-full">
106
<div className="flex w-full mt-6">
107
<Property name="Created">
108
{dayjs(workspace.workspaceCreationTime).format("MMM D, YYYY")}
109
</Property>
110
<Property name="User">
111
<Link
112
className="text-blue-400 dark:text-blue-600 hover:text-blue-600 dark:hover:text-blue-400"
113
to={"/admin/users/" + props.workspace.ownerId}
114
>
115
{user?.name || props.workspace.ownerId}
116
</Link>
117
</Property>
118
<Property name="Context">
119
<a
120
className="text-blue-400 dark:text-blue-600 hover:text-blue-600 dark:hover:text-blue-400"
121
href={ContextURL.getNormalizedURL(workspace)?.toString()}
122
>
123
{workspace.context.title}
124
</a>
125
</Property>
126
</div>
127
<div className="flex w-full mt-6">
128
<Property name="Sharing">{workspace.shareable ? "Enabled" : "Disabled"}</Property>
129
<Property
130
name="Soft Deleted"
131
actions={
132
!!workspace.softDeleted && !workspace.contentDeletedTime
133
? [
134
{
135
label: "Restore & Pin",
136
onClick: async () => {
137
await getGitpodService().server.adminRestoreSoftDeletedWorkspace(
138
workspace.workspaceId,
139
);
140
await reload();
141
},
142
},
143
]
144
: undefined
145
}
146
>
147
{workspace.softDeleted
148
? `'${workspace.softDeleted}' ${dayjs(workspace.softDeletedTime).fromNow()}`
149
: "No"}
150
</Property>
151
<Property name="Pinned">{workspace.pinned ? "Yes" : "No"}</Property>
152
</div>
153
<div className="flex w-full mt-12">
154
<Property name="Organization">
155
<Link
156
className="text-blue-400 dark:text-blue-600 hover:text-blue-600 dark:hover:text-blue-400"
157
to={"/admin/orgs/" + workspace.organizationId}
158
>
159
{workspace.organizationId}
160
</Link>
161
</Property>
162
<Property name="Node">
163
<div className="overflow-scroll">{workspace.status.nodeName ?? "not assigned"}</div>
164
</Property>
165
<Property name="Class">
166
<div>{workspace.workspaceClass ?? "unknown"}</div>
167
</Property>
168
</div>
169
</div>
170
</div>
171
<div className="flex mt-20">
172
<div className="flex-1">
173
<div className="flex">
174
<Heading2>Workspace Instances</Heading2>
175
</div>
176
</div>
177
</div>
178
<div className="flex flex-col space-y-2">
179
<div className="px-6 py-3 flex justify-between text-sm text-gray-400 border-b border-gray-200 dark:border-gray-800 mb-2">
180
<span className="my-auto ml-3"></span>
181
<div className="w-4/12">InstanceId</div>
182
<div className="w-2/12">Started</div>
183
<div className="w-2/12">Duration</div>
184
<div className="w-2/12">Attributed</div>
185
</div>
186
{workspaceInstances
187
.sort((a, b) => a.creationTime.localeCompare(b.creationTime) * -1)
188
.map((wsi) => {
189
const attributionId = wsi.usageAttributionId && AttributionId.parse(wsi.usageAttributionId);
190
return (
191
<div className="px-6 py-3 flex justify-between text-sm text-gray-400 mb-2">
192
<span className="my-1 ml-3">
193
<WorkspaceStatusIndicator status={converter.toWorkspace(wsi).status} />
194
</span>
195
<div className="w-4/12">{wsi.id}</div>
196
<div className="w-2/12">{dayjs(wsi.startedTime).fromNow()}</div>
197
<div className="w-2/12">
198
{dayjs.duration(dayjs(wsi.stoppingTime).diff(wsi.startedTime)).humanize()}
199
</div>
200
<div className="w-2/12">
201
{attributionId && attributionId?.kind === "team" ? (
202
<Link
203
className="text-blue-400 dark:text-blue-600 hover:text-blue-600 dark:hover:text-blue-400"
204
to={"/admin/orgs/" + attributionId.teamId}
205
>
206
{attributionId.teamId}
207
</Link>
208
) : (
209
"personal"
210
)}
211
</div>
212
</div>
213
);
214
})}
215
<div className="py-20"></div>
216
</div>
217
</div>
218
);
219
}
220
221