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