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