diff --git a/components/dashboard/package.json b/components/dashboard/package.json
index 85e18012274d84..964659327f970b 100644
--- a/components/dashboard/package.json
+++ b/components/dashboard/package.json
@@ -9,6 +9,7 @@
"js-cookie": "^3.0.1",
"moment": "^2.29.1",
"monaco-editor": "^0.25.2",
+ "query-string": "^7.1.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-router-dom": "^5.2.0",
diff --git a/components/dashboard/src/App.tsx b/components/dashboard/src/App.tsx
index 7c0cf3aa5857d8..348e39944fcb7e 100644
--- a/components/dashboard/src/App.tsx
+++ b/components/dashboard/src/App.tsx
@@ -27,6 +27,7 @@ import { settingsPathAccount, settingsPathIntegrations, settingsPathMain, settin
import { projectsPathInstallGitHubApp, projectsPathMain, projectsPathMainWithParams, projectsPathNew } from './projects/projects.routes';
import { refreshSearchData } from './components/RepositoryFinder';
import { StartWorkspaceModal } from './workspaces/StartWorkspaceModal';
+import { parseProps } from './start/StartWorkspace';
const Setup = React.lazy(() => import(/* webpackPrefetch: true */ './Setup'));
const Workspaces = React.lazy(() => import(/* webpackPrefetch: true */ './workspaces/Workspaces'));
@@ -412,7 +413,7 @@ function App() {
} else if (isCreation) {
toRender = ;
} else if (isWsStart) {
- toRender = ;
+ toRender = ;
} else if (/^(github|gitlab)\.com\/.+?/i.test(window.location.pathname)) {
let url = new URL(window.location.href)
url.hash = url.pathname
diff --git a/components/dashboard/src/start/CreateWorkspace.tsx b/components/dashboard/src/start/CreateWorkspace.tsx
index 072c1cf08a6ab9..b8fc5ff75a4956 100644
--- a/components/dashboard/src/start/CreateWorkspace.tsx
+++ b/components/dashboard/src/start/CreateWorkspace.tsx
@@ -12,7 +12,7 @@ import Modal from "../components/Modal";
import { getGitpodService, gitpodHostUrl } from "../service/service";
import { UserContext } from "../user-context";
import { StartPage, StartPhase, StartWorkspaceError } from "./StartPage";
-import StartWorkspace from "./StartWorkspace";
+import StartWorkspace, { parseProps } from "./StartWorkspace";
import { openAuthorizeWindow } from "../provider-utils";
import { SelectAccountPayload } from "@gitpod/gitpod-protocol/lib/auth";
import { SelectAccountModal } from "../settings/SelectAccountModal";
@@ -154,7 +154,7 @@ export default class CreateWorkspace extends React.Component;
+ return ;
}
else if (result?.existingWorkspaces) {
diff --git a/components/dashboard/src/start/StartWorkspace.tsx b/components/dashboard/src/start/StartWorkspace.tsx
index 7739818f192e65..79f1892bc1311c 100644
--- a/components/dashboard/src/start/StartWorkspace.tsx
+++ b/components/dashboard/src/start/StartWorkspace.tsx
@@ -8,6 +8,7 @@ import { ContextURL, DisposableCollection, GitpodServer, RateLimiterError, Start
import { IDEOptions } from "@gitpod/gitpod-protocol/lib/ide-protocol";
import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
import EventEmitter from "events";
+import * as queryString from "query-string";
import React, { Suspense, useEffect } from "react";
import { v4 } from 'uuid';
import Arrow from "../components/Arrow";
@@ -21,10 +22,54 @@ const sessionId = v4();
const WorkspaceLogs = React.lazy(() => import('../components/WorkspaceLogs'));
export interface StartWorkspaceProps {
- workspaceId: string;
+ workspaceId: string,
+ runsInIFrame: boolean,
+ /**
+ * This flag is used to break the autostart-cycle explained in https://github.com/gitpod-io/gitpod/issues/8043
+ */
+ dontAutostart: boolean,
+}
+
+export function parseProps(workspaceId: string, search?: string): StartWorkspaceProps {
+ const params = parseParameters(search);
+ const runsInIFrame = window.top !== window.self;
+ return {
+ workspaceId,
+ runsInIFrame: window.top !== window.self,
+ // Either:
+ // - not_found: we were sent back from a workspace cluster/IDE URL where we expected a workspace to be running but it wasn't because either:
+ // - this is a (very) old tab and the workspace already timed out
+ // - due to a start error our workspace terminated very quickly between:
+ // a) us being redirected to that IDEUrl (based on the first ws-manager update) and
+ // b) our requests being validated by ws-proxy
+ // - runsInIFrame (IDE case):
+ // - we assume the workspace has already been started for us
+ // - we don't know it's instanceId
+ dontAutostart: params.notFound || runsInIFrame,
+ }
+}
+
+function parseParameters(search?: string): { notFound?: boolean } {
+ try {
+ if (search === undefined) {
+ return {};
+ }
+ const params = queryString.parse(search, {parseBooleans: true});
+ const notFound = !!(params && params["not_found"]);
+ return {
+ notFound,
+ };
+ } catch (err) {
+ console.error("/start: error parsing search params", err);
+ return {};
+ }
}
export interface StartWorkspaceState {
+ /**
+ * This is set to the istanceId we started (think we started on).
+ * We only receive updates for this particular instance, or none if not set.
+ */
startedInstanceId?: string;
workspaceInstance?: WorkspaceInstance;
workspace?: Workspace;
@@ -34,8 +79,8 @@ export interface StartWorkspaceState {
link: string
label: string
clientID?: string
- }
- ideOptions?: IDEOptions
+ };
+ ideOptions?: IDEOptions;
}
export default class StartWorkspace extends React.Component {
@@ -47,7 +92,7 @@ export default class StartWorkspace extends React.Component {
if (event.data.type === 'setState' && 'state' in event.data && typeof event.data['state'] === 'object') {
@@ -75,8 +120,15 @@ export default class StartWorkspace extends React.Component this.setState({ ideOptions }))
+ // query IDE options so we can show them if necessary once the workspace is running
+ getGitpodService().server.getIDEOptions().then(ideOptions => this.setState({ ideOptions }));
}
componentWillUnmount() {
@@ -130,14 +182,13 @@ export default class StartWorkspace extends React.Component ({
+ workspace: info.workspace,
+ startedInstanceId: s.startedInstanceId || instance.id, // note: here's a potential mismatch between startedInstanceId and instance.id. TODO(gpl) How to handle this?
+ }));
+ this.onInstanceUpdate(instance);
}
} catch (error) {
console.error(error);
@@ -197,20 +261,35 @@ export default class StartWorkspace extends React.ComponentPreparing workspace …
;
const contextURL = ContextURL.getNormalizedURL(this.state.workspace)?.toString();
@@ -317,7 +392,29 @@ export default class StartWorkspace extends React.ComponentOpening Workspace …;
+
+ if (this.props.dontAutostart) {
+ // hide the progress bar, as we're already running
+ phase = undefined;
+ title = 'Running';
+
+ // in case we dontAutostart the IDE we have to provide controls to do so
+ statusMessage =
+
+
+
+
{this.state.workspaceInstance.workspaceId}
+
{contextURL}
+
+
+
+
;
+ } else {
+ statusMessage = Opening Workspace …
;
+ }
} else {
phase = StartPhase.IdeReady;
const openLink = this.state.desktopIde.link;
diff --git a/components/supervisor/frontend/src/index.ts b/components/supervisor/frontend/src/index.ts
index ffa333f2c497d3..d96ba1908455ac 100644
--- a/components/supervisor/frontend/src/index.ts
+++ b/components/supervisor/frontend/src/index.ts
@@ -106,7 +106,6 @@ const toStop = new DisposableCollection();
//#region current-frame
let current: HTMLElement = loading.frame;
- let stopped = false;
let desktopRedirected = false;
const nextFrame = () => {
const instance = gitpodServiceClient.info.latestInstance;
@@ -142,19 +141,6 @@ const toStop = new DisposableCollection();
return document.body;
}
}
- if (instance.status.phase === 'stopped') {
- stopped = true;
- }
- if (stopped && (
- instance.status.phase === 'preparing' ||
- instance.status.phase === 'pending' ||
- instance.status.phase === 'creating' ||
- instance.status.phase === 'initializing')) {
- // reload the page if the workspace was restarted to ensure:
- // - graceful reconnection of IDEs
- // - new owner token is set
- window.location.href = startUrl.toString();
- }
}
return loading.frame;
}
diff --git a/components/ws-proxy/pkg/proxy/routes.go b/components/ws-proxy/pkg/proxy/routes.go
index 0d576258929f1c..1ea27970246a3f 100644
--- a/components/ws-proxy/pkg/proxy/routes.go
+++ b/components/ws-proxy/pkg/proxy/routes.go
@@ -550,7 +550,7 @@ func workspaceMustExistHandler(config *Config, infoProvider WorkspaceInfoProvide
info := infoProvider.WorkspaceInfo(coords.ID)
if info == nil {
log.WithFields(log.OWI("", coords.ID, "")).Info("no workspace info found - redirecting to start")
- redirectURL := fmt.Sprintf("%s://%s/start/#%s", config.GitpodInstallation.Scheme, config.GitpodInstallation.HostName, coords.ID)
+ redirectURL := fmt.Sprintf("%s://%s/start/?not_found=true#%s", config.GitpodInstallation.Scheme, config.GitpodInstallation.HostName, coords.ID)
http.Redirect(resp, req, redirectURL, http.StatusFound)
return
}
diff --git a/components/ws-proxy/pkg/proxy/routes_test.go b/components/ws-proxy/pkg/proxy/routes_test.go
index 763c4f5e80a3fc..f506c21baae6e7 100644
--- a/components/ws-proxy/pkg/proxy/routes_test.go
+++ b/components/ws-proxy/pkg/proxy/routes_test.go
@@ -414,10 +414,10 @@ func TestRoutes(t *testing.T) {
Status: http.StatusFound,
Header: http.Header{
"Content-Type": {"text/html; charset=utf-8"},
- "Location": {"https://test-domain.com/start/#blabla-smelt-9ba20cc1"},
+ "Location": {"https://test-domain.com/start/?not_found=true#blabla-smelt-9ba20cc1"},
"Vary": {"Accept-Encoding"},
},
- Body: ("Found.\n\n"),
+ Body: ("Found.\n\n"),
},
},
{
@@ -429,10 +429,10 @@ func TestRoutes(t *testing.T) {
Status: http.StatusFound,
Header: http.Header{
"Content-Type": {"text/html; charset=utf-8"},
- "Location": {"https://test-domain.com/start/#blabla-smelt-9ba20cc1"},
+ "Location": {"https://test-domain.com/start/?not_found=true#blabla-smelt-9ba20cc1"},
"Vary": {"Accept-Encoding"},
},
- Body: ("Found.\n\n"),
+ Body: ("Found.\n\n"),
},
},
{
diff --git a/yarn.lock b/yarn.lock
index 5e395feecdcd9b..6faa6a3cf4591f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -14397,6 +14397,16 @@ query-string@^6.13.3:
split-on-first "^1.0.0"
strict-uri-encode "^2.0.0"
+query-string@^7.1.1:
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.1.tgz#754620669db978625a90f635f12617c271a088e1"
+ integrity sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w==
+ dependencies:
+ decode-uri-component "^0.2.0"
+ filter-obj "^1.1.0"
+ split-on-first "^1.0.0"
+ strict-uri-encode "^2.0.0"
+
querystring-es3@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"