Path: blob/main/components/dashboard/src/usage/UsageEntry.tsx
2499 views
/**1* Copyright (c) 2023 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 { WorkspaceType } from "@gitpod/gitpod-protocol";7import { Usage, WorkspaceInstanceUsageData } from "@gitpod/gitpod-protocol/lib/usage";8import { FC } from "react";9import { useWorkspaceClasses } from "../data/workspaces/workspace-classes-query";10import { ReactComponent as UsageIcon } from "../images/usage-default.svg";11import { toRemoteURL } from "../projects/render-utils";12// TODO: shift these into a DatePicker component that wraps react-datepicker13import "react-datepicker/dist/react-datepicker.css";14import "../components/react-datepicker.css";1516type Props = {17usage: Usage;18};19export const UsageEntry: FC<Props> = ({ usage }) => {20// We shouldn't be delivering these to the client, but just in case, don't try to render them21if (usage.kind !== "workspaceinstance") {22return null;23}2425const metadata = usage.metadata as WorkspaceInstanceUsageData;2627return (28<div29key={usage.workspaceInstanceId}30className="flex p-3 grid grid-cols-12 gap-x-3 justify-between transition ease-in-out rounded-xl"31>32<div className="flex flex-col col-span-2 my-auto">33<span className="text-gray-600 dark:text-gray-100 text-md font-medium">34{getType(metadata.workspaceType)}35</span>36<span className="text-sm text-gray-400 dark:text-gray-500">37<DisplayName workspaceClass={metadata.workspaceClass} />38</span>39</div>40<div className="flex flex-col col-span-5 my-auto">41<div className="flex">42{isRunning(usage) && (43<div44className="rounded-full w-2 h-2 text-sm align-middle bg-green-500 my-auto mx-1"45title="Still running"46/>47)}48<span className="truncate text-gray-600 dark:text-gray-100 text-md font-medium">49{metadata.workspaceId}50</span>51</div>52<span className="text-sm truncate text-gray-400 dark:text-gray-500">53{metadata.contextURL && toRemoteURL(metadata.contextURL)}54</span>55</div>56<div className="flex flex-col my-auto">57<span className="text-right text-gray-500 dark:text-gray-400 font-medium">{usage.credits}</span>58<span className="text-right text-sm text-gray-400 dark:text-gray-500">{getMinutes(usage)}</span>59</div>60<div className="my-auto" />61<div className="flex flex-col col-span-3 my-auto">62<span className="text-gray-400 dark:text-gray-500 truncate font-medium">63{displayTime(usage.effectiveTime!)}64</span>65<div className="flex">66{metadata.workspaceType === "prebuild" ? <UsageIcon className="my-auto w-4 h-4 mr-1" /> : ""}67{metadata.workspaceType === "prebuild" ? (68<span className="text-sm text-gray-400 dark:text-gray-500">Gitpod</span>69) : (70<div className="flex">71<img72className="my-auto rounded-full w-4 h-4 inline-block align-text-bottom mr-1 overflow-hidden"73src={metadata.userAvatarURL || ""}74alt="user avatar"75/>76<span className="text-sm text-gray-400 dark:text-gray-500">{metadata.userName || ""}</span>77</div>78)}79</div>80</div>81</div>82);83};8485export const DisplayName: FC<{ workspaceClass: string }> = ({ workspaceClass }) => {86const supportedClasses = useWorkspaceClasses();8788const workspaceDisplayName = supportedClasses.data?.find((wc) => wc.id === workspaceClass)?.displayName;8990return <span>{workspaceDisplayName || workspaceClass}</span>;91};9293const getType = (type: WorkspaceType) => {94if (type === "regular") {95return "Workspace";96}97return "Prebuild";98};99100const isRunning = (usage: Usage) => {101if (usage.kind !== "workspaceinstance") {102return false;103}104const metaData = usage.metadata as WorkspaceInstanceUsageData;105return metaData.endTime === "" || metaData.endTime === undefined;106};107108const getMinutes = (usage: Usage) => {109if (usage.kind !== "workspaceinstance") {110return "";111}112const metaData = usage.metadata as WorkspaceInstanceUsageData;113const end = metaData.endTime ? new Date(metaData.endTime).getTime() : Date.now();114const start = new Date(metaData.startTime).getTime();115const lengthOfUsage = Math.floor(end - start);116const inMinutes = (lengthOfUsage / (1000 * 60)).toFixed(1);117return inMinutes + " min";118};119120export const displayTime = (time: string | number) => {121const options: Intl.DateTimeFormatOptions = {122day: "numeric",123month: "short",124year: "numeric",125hour: "numeric",126minute: "numeric",127};128return new Date(time).toLocaleDateString(undefined, options).replace("at ", "");129};130131132