Skip to content

Commit 6a368f0

Browse files
committed
[TB] allow to config launcher from dashboard
1 parent 1feecdb commit 6a368f0

File tree

28 files changed

+356
-33
lines changed

28 files changed

+356
-33
lines changed

Diff for: components/dashboard/src/service/service.tsx

+8-1
Original file line numberDiff line numberDiff line change
@@ -344,8 +344,15 @@ export class IDEFrontendService implements IDEFrontendDashboardService.IServer {
344344
const desktopLink = new URL(url);
345345
// allow to redirect only for whitelisted trusted protocols
346346
// IDE-69
347-
const trustedProtocols = ["vscode:", "vscode-insiders:", "jetbrains-gateway:"];
347+
const trustedProtocols = ["vscode:", "vscode-insiders:", "jetbrains-gateway:", "jetbrains:"];
348348
redirect = trustedProtocols.includes(desktopLink.protocol);
349+
if (
350+
redirect &&
351+
desktopLink.protocol === "jetbrains:" &&
352+
!desktopLink.href.startsWith("jetbrains://gateway/io.gitpod.toolbox.gateway/")
353+
) {
354+
redirect = false;
355+
}
349356
} catch (e) {
350357
console.error("invalid desktop link:", e);
351358
}

Diff for: components/dashboard/src/user-settings/SelectIDE.tsx

+33-6
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,20 @@ export default function SelectIDE(props: SelectIDEProps) {
2525

2626
const [defaultIde, setDefaultIde] = useState<string>(user?.editorSettings?.name || "code");
2727
const [useLatestVersion, setUseLatestVersion] = useState<boolean>(user?.editorSettings?.version === "latest");
28+
const [preferToolbox, setPreferToolbox] = useState<boolean>(user?.editorSettings?.preferToolbox || false);
2829
const [ideWarning, setIdeWarning] = useState<ReactNode | undefined>(undefined);
2930

3031
const isOrgOwnedUser = user && isOrganizationOwned(user);
3132

3233
const actualUpdateUserIDEInfo = useCallback(
33-
async (selectedIde: string, useLatestVersion: boolean) => {
34+
async (selectedIde: string, useLatestVersion: boolean, preferToolbox: boolean) => {
3435
// update stored autostart options to match useLatestVersion value set here
3536
const workspaceAutostartOptions = user?.workspaceAutostartOptions?.map((o) => {
3637
const option = converter.fromWorkspaceAutostartOption(o);
3738

3839
if (option.ideSettings) {
3940
option.ideSettings.useLatestVersion = useLatestVersion;
41+
option.ideSettings.preferToolbox = preferToolbox;
4042
}
4143

4244
return option;
@@ -46,9 +48,10 @@ export default function SelectIDE(props: SelectIDEProps) {
4648
additionalData: {
4749
workspaceAutostartOptions,
4850
ideSettings: {
49-
settingVersion: "2.0",
51+
settingVersion: "2.1",
5052
defaultIde: selectedIde,
5153
useLatestVersion: useLatestVersion,
54+
preferToolbox: preferToolbox,
5255
},
5356
},
5457
});
@@ -59,18 +62,26 @@ export default function SelectIDE(props: SelectIDEProps) {
5962

6063
const actuallySetDefaultIde = useCallback(
6164
async (value: string) => {
62-
await actualUpdateUserIDEInfo(value, useLatestVersion);
65+
await actualUpdateUserIDEInfo(value, useLatestVersion, preferToolbox);
6366
setDefaultIde(value);
6467
},
65-
[actualUpdateUserIDEInfo, useLatestVersion],
68+
[actualUpdateUserIDEInfo, useLatestVersion, preferToolbox],
6669
);
6770

6871
const actuallySetUseLatestVersion = useCallback(
6972
async (value: boolean) => {
70-
await actualUpdateUserIDEInfo(defaultIde, value);
73+
await actualUpdateUserIDEInfo(defaultIde, value, preferToolbox);
7174
setUseLatestVersion(value);
7275
},
73-
[actualUpdateUserIDEInfo, defaultIde],
76+
[actualUpdateUserIDEInfo, defaultIde, preferToolbox],
77+
);
78+
79+
const actuallySetPreferToolbox = useCallback(
80+
async (value: boolean) => {
81+
await actualUpdateUserIDEInfo(defaultIde, useLatestVersion, value);
82+
setPreferToolbox(value);
83+
},
84+
[actualUpdateUserIDEInfo, defaultIde, useLatestVersion],
7485
);
7586

7687
const shouldShowJetbrainsNotice = isJetbrains(defaultIde);
@@ -142,6 +153,22 @@ export default function SelectIDE(props: SelectIDEProps) {
142153
checked={useLatestVersion}
143154
onChange={(checked) => actuallySetUseLatestVersion(checked)}
144155
/>
156+
157+
<CheckboxInputField
158+
label={
159+
<span className="flex items-center gap-2">
160+
Launch in JetBrains Toolbox{" "}
161+
<PillLabel type="warn">
162+
<a href="https://www.gitpod.io/docs/references/gitpod-releases">
163+
<span className="text-xs">BETA</span>
164+
</a>
165+
</PillLabel>
166+
</span>
167+
}
168+
hint={<span>Launch JetBrains IDEs in the JetBrains Toolbox.</span>}
169+
checked={preferToolbox}
170+
onChange={(checked) => actuallySetPreferToolbox(checked)}
171+
/>
145172
</>
146173
);
147174
}

Diff for: components/dashboard/src/workspaces/CreateWorkspacePage.tsx

+18-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ export function CreateWorkspacePage() {
8181
props.ideSettings?.useLatestVersion !== undefined
8282
? props.ideSettings.useLatestVersion
8383
: user?.editorSettings?.version === "latest";
84+
const defaultPreferToolbox = props.ideSettings?.preferToolbox ?? user?.editorSettings?.preferToolbox ?? false;
8485
const [useLatestIde, setUseLatestIde] = useState(defaultLatestIde);
86+
const [preferToolbox, setPreferToolbox] = useState(defaultPreferToolbox);
8587
// Note: it has data fetching and UI rendering race between the updating of `selectedProjectId` and `selectedIde`
8688
// We have to stored the using repositoryId locally so that we can know selectedIde is updated because if which repo
8789
// so that it doesn't show ide error messages in middle state
@@ -145,6 +147,7 @@ export function CreateWorkspacePage() {
145147
editorSettings: new EditorReference({
146148
name: selectedIde,
147149
version: useLatestIde ? "latest" : "stable",
150+
preferToolbox: preferToolbox,
148151
}),
149152
}),
150153
);
@@ -156,7 +159,17 @@ export function CreateWorkspacePage() {
156159
},
157160
});
158161
setUser(updatedUser);
159-
}, [updateUser, currentOrg, selectedIde, selectedWsClass, setUser, useLatestIde, user, workspaceContext.data]);
162+
}, [
163+
updateUser,
164+
currentOrg,
165+
selectedIde,
166+
selectedWsClass,
167+
setUser,
168+
useLatestIde,
169+
preferToolbox,
170+
user,
171+
workspaceContext.data,
172+
]);
160173

161174
// see if we have a matching project based on context url and project's repo url
162175
const project = useMemo(() => {
@@ -271,6 +284,7 @@ export function CreateWorkspacePage() {
271284
contextUrlSource.editor = {
272285
name: selectedIde,
273286
version: useLatestIde ? "latest" : undefined,
287+
preferToolbox: preferToolbox,
274288
};
275289
}
276290
opts.source = {
@@ -301,6 +315,7 @@ export function CreateWorkspacePage() {
301315
selectedWsClass,
302316
selectedIde,
303317
useLatestIde,
318+
preferToolbox,
304319
createWorkspaceMutation,
305320
selectedProjectID,
306321
storeAutoStartOptions,
@@ -358,6 +373,7 @@ export function CreateWorkspacePage() {
358373
}
359374
setSelectedIde(rememberedOptions.editorSettings?.name, false);
360375
setUseLatestIde(rememberedOptions.editorSettings?.version === "latest");
376+
setPreferToolbox(rememberedOptions.editorSettings?.preferToolbox || false);
361377
}
362378

363379
if (!selectedWsClassIsDirty) {
@@ -374,6 +390,7 @@ export function CreateWorkspacePage() {
374390
if (!selectedIdeIsDirty) {
375391
setSelectedIde(defaultIde, false);
376392
setUseLatestIde(defaultLatestIde);
393+
setPreferToolbox(defaultPreferToolbox);
377394
}
378395
if (!selectedWsClassIsDirty) {
379396
const projectWsClass = project?.settings?.workspaceClasses?.regular;

Diff for: components/gitpod-protocol/go/gitpod-service.go

+1
Original file line numberDiff line numberDiff line change
@@ -2058,6 +2058,7 @@ type IDESettings struct {
20582058
UseDesktopIde bool `json:"useDesktopIde,omitempty"`
20592059
DefaultDesktopIde string `json:"defaultDesktopIde,omitempty"`
20602060
UseLatestVersion bool `json:"useLatestVersion"`
2061+
PreferToolbox bool `json:"preferToolbox"`
20612062
}
20622063

20632064
// EmailNotificationSettings is the EmailNotificationSettings message type

Diff for: components/gitpod-protocol/src/protocol.ts

+1
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ export type IDESettings = {
171171
settingVersion?: string;
172172
defaultIde?: string;
173173
useLatestVersion?: boolean;
174+
preferToolbox?: boolean;
174175
// DEPRECATED: Use defaultIde after `settingVersion: 2.0`, no more specialify desktop or browser.
175176
useDesktopIde?: boolean;
176177
// DEPRECATED: Same with useDesktopIde.

Diff for: components/gitpod-protocol/src/workspace-instance.ts

+1
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ export namespace WorkspaceInstanceRepoStatus {
269269
export interface ConfigurationIdeConfig {
270270
useLatest?: boolean;
271271
ide?: string;
272+
preferToolbox?: boolean;
272273
}
273274

274275
export interface IdeSetup {

Diff for: components/ide-service/example-ide-config.json

+12
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,18 @@
129129
"If you don't see an open dialog in your browser, make sure you have the \u003ca target='_blank' class='gp-link' href='https://www.gitpod.io/docs/ides-and-editors/jetbrains-gateway#getting-started-jetbrains-gateway'\u003eJetBrains Gateway with Gitpod Plugin\u003c/a\u003e installed on your machine, and then click \u003cb\u003e${OPEN_LINK_LABEL}\u003c/b\u003e below."
130130
]
131131
},
132+
"jetbrains-toolbox": {
133+
"defaultDesktopIDE": "intellij",
134+
"desktopIDEs": [
135+
"intellij",
136+
"goland",
137+
"pycharm",
138+
"phpstorm"
139+
],
140+
"installationSteps": [
141+
"If you don't see an open dialog in your browser, make sure you have the \u003ca target='_blank' class='gp-link' href='https://www.gitpod.io/docs/ides-and-editors/jetbrains-gateway#getting-started-jetbrains-gateway'\u003eJetBrains Toolbox with Gitpod Plugin\u003c/a\u003e installed on your machine, and then click \u003cb\u003e${OPEN_LINK_LABEL}\u003c/b\u003e below."
142+
]
143+
},
132144
"vscode": {
133145
"defaultDesktopIDE": "code-desktop",
134146
"desktopIDEs": [

Diff for: components/ide-service/pkg/server/server.go

+12
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ func grpcProbe(cfg baseserver.ServerConfiguration) func() error {
303303
type IDESettings struct {
304304
DefaultIde string `json:"defaultIde,omitempty"`
305305
UseLatestVersion bool `json:"useLatestVersion,omitempty"`
306+
PreferToolbox bool `json:"preferToolbox,omitempty"`
306307
PinnedIDEversions map[string]string `json:"pinnedIDEversions,omitempty"`
307308
}
308309

@@ -440,12 +441,22 @@ func (s *IDEServiceServer) ResolveWorkspaceConfig(ctx context.Context, req *api.
440441

441442
userIdeName := ""
442443
useLatest := false
444+
preferToolbox := false
443445
resultingIdeName := ideConfig.IdeOptions.DefaultIde
444446
chosenIDE := ideConfig.IdeOptions.Options[resultingIdeName]
445447

446448
if ideSettings != nil {
447449
userIdeName = ideSettings.DefaultIde
448450
useLatest = ideSettings.UseLatestVersion
451+
preferToolbox = ideSettings.PreferToolbox
452+
}
453+
454+
if preferToolbox {
455+
preferToolboxEnv := api.EnvironmentVariable{
456+
Name: "GITPOD_PREFER_TOOLBOX",
457+
Value: "true",
458+
}
459+
resp.Envvars = append(resp.Envvars, &preferToolboxEnv)
449460
}
450461

451462
if userIdeName != "" {
@@ -499,6 +510,7 @@ func (s *IDEServiceServer) ResolveWorkspaceConfig(ctx context.Context, req *api.
499510
resultingIdeSettings := &IDESettings{
500511
DefaultIde: resultingIdeName,
501512
UseLatestVersion: useLatest,
513+
PreferToolbox: preferToolbox,
502514
}
503515

504516
err = enc.Encode(resultingIdeSettings)

Diff for: components/ide/jetbrains/launcher/main.go

+80
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ type LaunchContext struct {
5757
label string
5858
warmup bool
5959

60+
preferToolbox bool
6061
qualifier string
6162
productDir string
6263
backendDir string
@@ -98,6 +99,7 @@ func main() {
9899
// supervisor refer see https://github.com/gitpod-io/gitpod/blob/main/components/supervisor/pkg/supervisor/supervisor.go#L961
99100
shouldWaitBackendPlugin := os.Getenv("GITPOD_WAIT_IDE_BACKEND") == "true"
100101
debugEnabled := os.Getenv("SUPERVISOR_DEBUG_ENABLE") == "true"
102+
preferToolbox := os.Getenv("GITPOD_PREFER_TOOLBOX") == "true"
101103
log.Init(ServiceName, constant.Version, true, debugEnabled)
102104
log.Info(ServiceName + ": " + constant.Version)
103105
startTime := time.Now()
@@ -165,6 +167,7 @@ func main() {
165167
alias: alias,
166168
label: label,
167169

170+
preferToolbox: preferToolbox,
168171
qualifier: qualifier,
169172
productDir: productDir,
170173
backendDir: backendDir,
@@ -178,6 +181,15 @@ func main() {
178181
launch(launchCtx)
179182
return
180183
}
184+
185+
if preferToolbox {
186+
err = configureToolboxCliProperties(backendDir)
187+
if err != nil {
188+
log.WithError(err).Error("failed to write toolbox cli config file")
189+
return
190+
}
191+
}
192+
181193
// we should start serving immediately and postpone launch
182194
// in order to enable a JB Gateway to connect as soon as possible
183195
go launch(launchCtx)
@@ -277,6 +289,22 @@ func serve(launchCtx *LaunchContext) {
277289
fmt.Fprint(w, jsonLink)
278290
})
279291
http.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {
292+
if launchCtx.preferToolbox {
293+
response := make(map[string]string)
294+
toolboxLink, err := resolveToolboxLink(launchCtx.wsInfo)
295+
if err != nil {
296+
log.WithError(err).Error("cannot resolve toolbox link")
297+
http.Error(w, err.Error(), http.StatusServiceUnavailable)
298+
return
299+
}
300+
response["link"] = toolboxLink
301+
response["label"] = launchCtx.label
302+
response["clientID"] = "jetbrains-toolbox"
303+
response["kind"] = launchCtx.alias
304+
w.Header().Set("Content-Type", "application/json")
305+
_ = json.NewEncoder(w).Encode(response)
306+
return
307+
}
280308
backendPort := r.URL.Query().Get("backendPort")
281309
if backendPort == "" {
282310
backendPort = defaultBackendPort
@@ -359,6 +387,21 @@ type JoinLinkResponse struct {
359387
JoinLink string `json:"joinLink"`
360388
}
361389

390+
func resolveToolboxLink(wsInfo *supervisor.WorkspaceInfoResponse) (string, error) {
391+
gitpodUrl, err := url.Parse(wsInfo.GitpodHost)
392+
if err != nil {
393+
return "", err
394+
}
395+
debugWorkspace := wsInfo.DebugWorkspaceType != supervisor.DebugWorkspaceType_noDebug
396+
link := url.URL{
397+
Scheme: "jetbrains",
398+
Host: "gateway",
399+
Path: "io.gitpod.toolbox.gateway/open-in-toolbox",
400+
RawQuery: fmt.Sprintf("host=%s&workspaceId=%s&debugWorkspace=%t", gitpodUrl.Hostname(), wsInfo.WorkspaceId, debugWorkspace),
401+
}
402+
return link.String(), nil
403+
}
404+
362405
func resolveGatewayLink(backendPort string, wsInfo *supervisor.WorkspaceInfoResponse) (string, error) {
363406
gitpodUrl, err := url.Parse(wsInfo.GitpodHost)
364407
if err != nil {
@@ -554,6 +597,8 @@ func run(launchCtx *LaunchContext) {
554597
var args []string
555598
if launchCtx.warmup {
556599
args = append(args, "warmup")
600+
} else if launchCtx.preferToolbox {
601+
args = append(args, "serverMode")
557602
} else {
558603
args = append(args, "run")
559604
}
@@ -1184,3 +1229,38 @@ func resolveProjectContextDir(launchCtx *LaunchContext) string {
11841229

11851230
return launchCtx.projectDir
11861231
}
1232+
1233+
func configureToolboxCliProperties(backendDir string) error {
1234+
userHomeDir, err := os.UserHomeDir()
1235+
if err != nil {
1236+
return err
1237+
}
1238+
1239+
toolboxCliPropertiesDir := fmt.Sprintf("%s/.local/share/JetBrains/Toolbox", userHomeDir)
1240+
_, err = os.Stat(toolboxCliPropertiesDir)
1241+
if !os.IsNotExist(err) {
1242+
return err
1243+
}
1244+
err = os.MkdirAll(toolboxCliPropertiesDir, os.ModePerm)
1245+
if err != nil {
1246+
return err
1247+
}
1248+
1249+
toolboxCliPropertiesFilePath := fmt.Sprintf("%s/environment.json", toolboxCliPropertiesDir)
1250+
1251+
// TODO(hw): restrict IDE installation
1252+
content := fmt.Sprintf(`{
1253+
"tools": {
1254+
"allowInstallation": true,
1255+
"allowUpdate": false,
1256+
"allowUninstallation": true,
1257+
"location": [
1258+
{
1259+
"path": "%s"
1260+
}
1261+
]
1262+
}
1263+
}`, backendDir)
1264+
1265+
return os.WriteFile(toolboxCliPropertiesFilePath, []byte(content), 0o644)
1266+
}

0 commit comments

Comments
 (0)