Path: blob/main/components/dashboard/src/workspaces/WorkspaceOverflowMenu.tsx
2500 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 { GitpodHostUrl } from "@gitpod/gitpod-protocol/lib/util/gitpod-host-url";7import { FunctionComponent, useCallback, useMemo, useState } from "react";8import { ContextMenuEntry } from "../components/ContextMenu";9import { ItemFieldContextMenu } from "../components/ItemsList";10import { useStopWorkspaceMutation } from "../data/workspaces/stop-workspace-mutation";11import ConnectToSSHModal from "./ConnectToSSHModal";12import { DeleteWorkspaceModal } from "./DeleteWorkspaceModal";13import { useToast } from "../components/toasts/Toasts";14import { RenameWorkspaceModal } from "./RenameWorkspaceModal";15import { AdmissionLevel, Workspace, WorkspacePhase_Phase } from "@gitpod/public-api/lib/gitpod/v1/workspace_pb";16import { workspaceClient } from "../service/public-api";17import { useOrgSettingsQuery } from "../data/organizations/org-settings-query";18import { useUpdateWorkspaceMutation } from "../data/workspaces/update-workspace-mutation";1920type WorkspaceEntryOverflowMenuProps = {21info: Workspace;22changeMenuState: (state: boolean) => void;23};2425export const WorkspaceEntryOverflowMenu: FunctionComponent<WorkspaceEntryOverflowMenuProps> = ({26info,27changeMenuState,28}) => {29const [isDeleteModalVisible, setDeleteModalVisible] = useState(false);30const [isRenameModalVisible, setRenameModalVisible] = useState(false);31const [isSSHModalVisible, setSSHModalVisible] = useState(false);32const [ownerToken, setOwnerToken] = useState("");33const { toast } = useToast();34const { data: settings } = useOrgSettingsQuery();3536const stopWorkspace = useStopWorkspaceMutation();37const updateWorkspace = useUpdateWorkspaceMutation();3839const workspace = info;40const state: WorkspacePhase_Phase = info?.status?.phase?.name || WorkspacePhase_Phase.STOPPED;4142//TODO: shift this into ConnectToSSHModal43const handleConnectViaSSHClick = useCallback(async () => {44const response = await workspaceClient.getWorkspaceOwnerToken({ workspaceId: workspace.id });45setOwnerToken(response.ownerToken);46setSSHModalVisible(true);47}, [workspace.id]);4849const handleStopWorkspace = useCallback(() => {50stopWorkspace.mutate(51{ workspaceId: workspace.id },52{53onError: (error: any) => {54toast(error.message || "Failed to stop workspace");55},56},57);58}, [toast, stopWorkspace, workspace.id]);5960const toggleShared = useCallback(() => {61const newLevel =62workspace.spec?.admission === AdmissionLevel.EVERYONE ? AdmissionLevel.OWNER_ONLY : AdmissionLevel.EVERYONE;6364updateWorkspace.mutate({65workspaceId: workspace.id,66spec: {67admission: newLevel,68},69});70}, [updateWorkspace, workspace.id, workspace.spec?.admission]);7172const togglePinned = useCallback(() => {73updateWorkspace.mutate({74workspaceId: workspace.id,75metadata: {76pinned: !workspace.metadata?.pinned,77},78});79}, [updateWorkspace, workspace.id, workspace.metadata?.pinned]);8081// Can we use `/start#${workspace.id}` instead?82const startUrl = useMemo(83() =>84new GitpodHostUrl(window.location.href).with({85pathname: "/start/",86hash: "#" + workspace.id,87}),88[workspace.id],89);9091// Can we use `/workspace-download/get/${workspace.id}` instead?92const downloadURL = useMemo(93() =>94new GitpodHostUrl(window.location.href)95.with({96pathname: `/workspace-download/get/${workspace.id}`,97})98.toString(),99[workspace.id],100);101102const menuEntries: ContextMenuEntry[] = [103{104title: "Open",105href: startUrl.toString(),106},107{108title: "Rename",109href: "",110onClick: () => setRenameModalVisible(true),111},112];113114if (state === WorkspacePhase_Phase.RUNNING) {115menuEntries.push({116title: "Stop",117onClick: handleStopWorkspace,118});119menuEntries.splice(1, 0, {120title: "Connect via SSH",121onClick: handleConnectViaSSHClick,122});123}124125menuEntries.push({126title: "Download",127href: downloadURL,128download: `${workspace.id}.tar`,129});130131// Push the Workspace share menu entry based on the current organization settings for workspace sharing132if (settings && !settings.workspaceSharingDisabled) {133menuEntries.push({134title: "Share",135active: workspace.spec?.admission === AdmissionLevel.EVERYONE,136onClick: toggleShared,137});138} else {139menuEntries.push({140title: "Workspace sharing is disabled for this organization. Contact your org. owner to enable it.",141active: false,142customContent: "Share",143customFontStyle: "text-gray-400 dark:text-gray-500 opacity-50 cursor-not-allowed",144});145}146147menuEntries.push(148{149title: "Pin",150active: !!workspace.metadata?.pinned,151separator: true,152onClick: togglePinned,153},154{155title: "Delete",156customFontStyle: "text-red-600 dark:text-red-400 hover:text-red-800 dark:hover:text-red-300",157onClick: () => setDeleteModalVisible(true),158},159);160161return (162<>163<ItemFieldContextMenu changeMenuState={changeMenuState} menuEntries={menuEntries} />164{isDeleteModalVisible && (165<DeleteWorkspaceModal workspace={workspace} onClose={() => setDeleteModalVisible(false)} />166)}167{isRenameModalVisible && (168<RenameWorkspaceModal workspace={workspace} onClose={() => setRenameModalVisible(false)} />169)}170{isSSHModalVisible && info.status && ownerToken !== "" && (171<ConnectToSSHModal172workspaceId={workspace.id}173ownerToken={ownerToken}174ideUrl={info.status.workspaceUrl.replaceAll("https://", "")}175onClose={() => {176setSSHModalVisible(false);177setOwnerToken("");178}}179/>180)}181</>182);183};184185186