From 30efb52bcee96f175784c1f4416bdbb09cc39c2d Mon Sep 17 00:00:00 2001 From: Vitalii Parfonov Date: Wed, 30 Jan 2019 15:09:31 +0200 Subject: [PATCH 1/9] Set URL for IDE in workspace config which will navigate to the Workspace Loader. It will help solve problem with serving different kind of IDE Signed-off-by: Vitalii Parfonov --- workspace-loader/src/index.ts | 3 ++- .../che/api/workspace/server/WorkspaceLinksGenerator.java | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/workspace-loader/src/index.ts b/workspace-loader/src/index.ts index c04155df73e..460a77f244d 100644 --- a/workspace-loader/src/index.ts +++ b/workspace-loader/src/index.ts @@ -358,7 +358,8 @@ export class WorkspaceLoader { } } } - this.openURL(workspace.links.ide + this.getQueryString()); + //fall back to GWT IDE behavior + this.openURL(workspace.links.ide.replace('/workspace-loader','') + this.getQueryString()); }); } diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceLinksGenerator.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceLinksGenerator.java index bbd17c9d247..973fcb5b92e 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceLinksGenerator.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceLinksGenerator.java @@ -69,6 +69,7 @@ public Map genLinks(Workspace workspace, ServiceContext serviceC uriBuilder .clone() .replacePath("") + .path("workspace-loader") .path(workspace.getNamespace()) .path(workspace.getConfig().getName()) .build() From 2aa2975a2db4195821e3e157cacdc834231e0cf4 Mon Sep 17 00:00:00 2001 From: Vitalii Parfonov Date: Wed, 30 Jan 2019 16:17:18 +0200 Subject: [PATCH 2/9] Fix test Signed-off-by: Vitalii Parfonov --- .../api/workspace/server/WorkspaceLinksGeneratorTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceLinksGeneratorTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceLinksGeneratorTest.java index 72cdfd4b251..35259af581c 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceLinksGeneratorTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceLinksGeneratorTest.java @@ -75,7 +75,8 @@ public void setUp() throws Exception { expectedStoppedLinks = new HashMap<>(); expectedStoppedLinks.put(LINK_REL_SELF, "http://localhost/api/workspace/wside-123877234580"); - expectedStoppedLinks.put(LINK_REL_IDE_URL, "http://localhost/my-namespace/my-name"); + expectedStoppedLinks.put( + LINK_REL_IDE_URL, "http://localhost/workspace-loader/my-namespace/my-name"); expectedRunningLinks = new HashMap<>(expectedStoppedLinks); expectedRunningLinks.put(LINK_REL_ENVIRONMENT_STATUS_CHANNEL, "ws://localhost"); @@ -113,7 +114,7 @@ public void genOfDifferentUrl() throws Exception { LINK_REL_SELF, "https://mydomain:7345/api/workspace/wside-123877234580", LINK_REL_IDE_URL, - "https://mydomain:7345/my-namespace/my-name", + "https://mydomain:7345/workspace-loader/my-namespace/my-name", LINK_REL_ENVIRONMENT_STATUS_CHANNEL, "wss://mydomain:7345", LINK_REL_ENVIRONMENT_OUTPUT_CHANNEL, From 59910d2cad86682b7788171fe6fb9672d1876b68 Mon Sep 17 00:00:00 2001 From: Vitalii Parfonov Date: Fri, 1 Feb 2019 17:10:53 +0200 Subject: [PATCH 3/9] Rework IDE loader. Remove odd files Signed-off-by: Vitalii Parfonov --- .../org/eclipse/che/ide/public/IDE.html | 1 + .../org/eclipse/che/ide/public/bundle.js | 1302 +++++++++++++++++ .../org/eclipse/che/ide/public/loader.css | 161 -- .../org/eclipse/che/ide/public/loader.html | 42 - .../org/eclipse/che/ide/public/loader.js | 364 ----- .../server/WorkspaceLinksGenerator.java | 1 - .../server/WorkspaceLinksGeneratorTest.java | 2 +- 7 files changed, 1304 insertions(+), 569 deletions(-) create mode 100644 ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/bundle.js delete mode 100644 ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/loader.css delete mode 100644 ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/loader.html delete mode 100644 ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/loader.js diff --git a/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/IDE.html b/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/IDE.html index 3f4ebb7326b..d7f5094424a 100644 --- a/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/IDE.html +++ b/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/IDE.html @@ -19,6 +19,7 @@ Eclipse Che + - - - - -
-
Loading a runtime token...
-
-
-
-
-
-
Press F5 or click here to try again.
-
-
-
-
- - - - diff --git a/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/loader.js b/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/loader.js deleted file mode 100644 index bf3e2f7d59f..00000000000 --- a/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/loader.js +++ /dev/null @@ -1,364 +0,0 @@ -/* - * Copyright (c) 2012-2018 Red Hat, Inc. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ -class KeycloakLoader { - /** - * Load keycloak settings - */ - loadKeycloakSettings() { - const msg = "Cannot load keycloak settings. This is normal for single-user mode."; - - return new Promise((resolve, reject) => { - if (window.parent && window.parent['_keycloak']) { - window['_keycloak'] = window.parent['_keycloak']; - resolve(window['_keycloak']); - return; - } - try { - const request = new XMLHttpRequest(); - - request.onerror = request.onabort = function () { - reject(new Error(msg)); - }; - - request.onload = () => { - if (request.status == 200) { - resolve(this.injectKeycloakScript(JSON.parse(request.responseText))); - } else { - reject(new Error(msg)); - } - }; - - const url = "/api/keycloak/settings"; - request.open("GET", url, true); - request.send(); - } catch (e) { - reject(new Error(msg + e.message)); - } - }); - } - - /** - * Injects keycloak javascript - */ - injectKeycloakScript(keycloakSettings) { - return new Promise((resolve, reject) => { - const script = document.createElement('script'); - script.type = 'text/javascript'; - script.language = 'javascript'; - script.async = true; - script.src = keycloakSettings['che.keycloak.js_adapter_url']; - - script.onload = () => { - resolve(this.initKeycloak(keycloakSettings)); - }; - - script.onerror = script.onabort = () => { - reject(new Error('cannot load ' + script.src)); - }; - - document.head.appendChild(script); - }); - } - - /** - * Initialize keycloak - */ - initKeycloak(keycloakSettings) { - return new Promise((resolve, reject) => { - - function keycloakConfig() { - const theOidcProvider = keycloakSettings['che.keycloak.oidc_provider']; - if (!theOidcProvider) { - return { - url: keycloakSettings['che.keycloak.auth_server_url'], - realm: keycloakSettings['che.keycloak.realm'], - clientId: keycloakSettings['che.keycloak.client_id'] - }; - } else { - return { - oidcProvider: theOidcProvider, - clientId: keycloakSettings['che.keycloak.client_id'] - }; - } - } - const keycloak = Keycloak(keycloakConfig()); - - window['_keycloak'] = keycloak; - - var useNonce; - if (typeof keycloakSettings['che.keycloak.use_nonce'] === 'string') { - useNonce = keycloakSettings['che.keycloak.use_nonce'].toLowerCase() === 'true'; - } - window.sessionStorage.setItem('oidcIdeRedirectUrl', location.href); - keycloak - .init({ - onLoad: 'login-required', - checkLoginIframe: false, - useNonce: useNonce, - scope: 'email profile', - redirectUri: keycloakSettings['che.keycloak.redirect_url.ide'] - }) - .success(() => { - resolve(keycloak); - }) - .error(() => { - reject(new Error('[Keycloak] Failed to initialize Keycloak')); - }); - }); - } - -} - -class Loader { - - constructor() { - document.getElementById('workspace-loader-reload').onclick = () => this.onclickReload(); - } - - /** - * Hides progress bar and displays reloading prompt - */ - hideLoader() { - document.getElementById('workspace-loader-label').style.display = 'none'; - document.getElementById('workspace-loader-progress').style.display = 'none'; - - document.getElementById('workspace-loader-reload').style.display = 'block'; - } - - /** - * Displays error message - * @param {string} message an error message to show - */ - error(message) { - const container = document.getElementById("workspace-console-container"); - if (container.childElementCount > 500) { - container.removeChild(container.firstChild) - } - - const element = document.createElement("pre"); - element.innerHTML = message; - container.appendChild(element); - if (element.scrollIntoView) { - element.scrollIntoView(); - } - element.className = "error"; - return element; - } - - /** - * Reloads the page - */ - onclickReload() { - window.location.reload(); - return false; - } - - /** - * Returns query parameter value if it is present - * @param {string} name a query parameter name - */ - getQueryParam(name) { - const params = window.location.search.substr(1), - paramEntries = params.split('&'); - const entry = paramEntries.find(_entry => { - return _entry.startsWith(name + '='); - }); - if (!entry) { - return; - } - const [_, value] = entry.split('='); - return decodeURIComponent(value); - } - - /** - * Fetches workspace details by ID - * @param {string} workspaceId a workspace ID - */ - asyncGetWorkspace(workspaceId) { - return new Promise((resolve, reject) => { - const request = new XMLHttpRequest(); - request.open("GET", '/api/workspace/' + workspaceId); - this.setAuthorizationHeader(request).then((xhr) => { - xhr.send(); - xhr.onreadystatechange = () => { - if (xhr.readyState !== 4) { - return; - } - if (xhr.status !== 200) { - const errorMessage = 'Failed to get the workspace: "' + this.getRequestErrorMessage(xhr) + '"'; - reject(new Error(errorMessage)); - return; - } - resolve(JSON.parse(xhr.responseText)); - }; - }); - }); - } - - /** - * Sets authorization header for a request - * @param {XMLHttpRequest} xhr - */ - setAuthorizationHeader(xhr) { - return new Promise((resolve, reject) => { - if (window._keycloak && window._keycloak.token) { - window._keycloak.updateToken(5).success(() => { - xhr.setRequestHeader('Authorization', 'Bearer ' + window._keycloak.token); - resolve(xhr); - }).error(() => { - window.sessionStorage.setItem('oidcIdeRedirectUrl', location.href); - window._keycloak.login(); - reject(new Error('Failed to refresh token')); - }); - } - - resolve(xhr); - }); - } - - /** - * Returns `true` if any machine in workspace contains a server which matches with `redirectUrl` - * @param {*} workspace a workspace - * @param {string} redirectUrl a redirect URL - */ - asyncCheckServiceLink(workspace, redirectUrl) { - return new Promise((resolve, reject) => { - if (!workspace.runtime) { - reject(new Error("Can't check service link: Workspace isn't RUNNING at the moment.")); - return; - } - - var machines = Object.keys(workspace.runtime.machines) - .map(machineName => workspace.runtime.machines[machineName]); - var servers = machines.map(machine => { - const servers = Object.keys(machine.servers) - .map(serverName => machine.servers[serverName]); - return servers; - }).reduce((servers, machineServers) => { - return servers.concat(...machineServers); - }, []); - var server = servers.find(_server => { - return _server.url && redirectUrl.startsWith(_server.url); - }); - - if (server) { - resolve(server); - } else { - reject(new Error("Workspace doesn't have a server which matches with URL: " + redirectUrl)); - } - }); - } - - /** - * Returns resolved promise if `workspace` has the `runtime` property - * @param {*} workspace a workspace - */ - asyncGetWsToken(workspace) { - return new Promise((resolve, reject) => { - if (workspace.runtime) { - resolve(workspace.runtime.machineToken); - } else { - reject(new Error("Can't get ws-token: Workspace isn't RUNNING at the moment.")); - } - }); - } - - /** - * @param {string} redirectUrl a redirect URL - * @param {string} token - */ - asyncAuthenticate(redirectUrl, token) { - const re = new RegExp(/(https?:\/\/[^\/]+?)(?:$|\/).*/), - // \ / \ / - // scheme host:port - url = redirectUrl.replace(re, "$1" + "/jwt/auth"); - return new Promise((resolve, reject) => { - const request = new XMLHttpRequest(); - request.open('GET', url); - request.setRequestHeader('Authorization', 'Bearer ' + token); - request.withCredentials = true; - request.send(); - request.onreadystatechange = () => { - if (request.readyState !== 4) { - return; - } - if (request.status !== 204) { - const errorMessage = 'Failed to authenticate: "' + this.getRequestErrorMessage(xhr) + '"'; - reject(new Error(errorMessage)); - return; - } - resolve(); - }; - }); - } - - getRequestErrorMessage(xhr) { - let errorMessage; - try { - const response = JSON.parse(xhr.responseText); - errorMessage = response.message; - } catch (e) { } - - if (errorMessage) { - return errorMessage; - } - - if (xhr.statusText) { - return xhr.statusText; - } - - return "Unknown error"; - } - -} - -(function () { - const keycloackAuthenticationPromise = new KeycloakLoader().loadKeycloakSettings(); - - const loader = new Loader(); - - const workspaceId = loader.getQueryParam('workspaceId'), - redirectUrl = loader.getQueryParam('redirectUrl'); - const getWorkspacePromise = new Promise((resolve, reject) => { - if (!workspaceId) { - reject(new Error("Workspace ID isn't found in query parameters.")); - } - if (!redirectUrl) { - reject(new Error("Redirect URL isn't found in query parameters.")); - } - resolve(); - }).then(_ => { - return keycloackAuthenticationPromise; - }).then(_ => { - return loader.asyncGetWorkspace(workspaceId); - }); - - const checkServiceUrlPromise = getWorkspacePromise.then(workspace => { - return loader.asyncCheckServiceLink(workspace, redirectUrl); - }) - - const tokenAuthenticationPromise = getWorkspacePromise.then(workspace => { - return loader.asyncGetWsToken(workspace); - }).then(token => { - return loader.asyncAuthenticate(redirectUrl, token); - }); - - Promise.all([checkServiceUrlPromise, tokenAuthenticationPromise]) - .then(_ => { - window.location.replace(redirectUrl); - }) - .catch(errorMessage => { - console.error(errorMessage); - loader.hideLoader(); - loader.error(errorMessage); - }); -})(); diff --git a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceLinksGenerator.java b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceLinksGenerator.java index 973fcb5b92e..bbd17c9d247 100644 --- a/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceLinksGenerator.java +++ b/wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/WorkspaceLinksGenerator.java @@ -69,7 +69,6 @@ public Map genLinks(Workspace workspace, ServiceContext serviceC uriBuilder .clone() .replacePath("") - .path("workspace-loader") .path(workspace.getNamespace()) .path(workspace.getConfig().getName()) .build() diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceLinksGeneratorTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceLinksGeneratorTest.java index 35259af581c..25bd1d32a5d 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceLinksGeneratorTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceLinksGeneratorTest.java @@ -114,7 +114,7 @@ public void genOfDifferentUrl() throws Exception { LINK_REL_SELF, "https://mydomain:7345/api/workspace/wside-123877234580", LINK_REL_IDE_URL, - "https://mydomain:7345/workspace-loader/my-namespace/my-name", + "https://mydomain:7345/my-namespace/my-name", LINK_REL_ENVIRONMENT_STATUS_CHANNEL, "wss://mydomain:7345", LINK_REL_ENVIRONMENT_OUTPUT_CHANNEL, From 7f9e41dd455bb34eba2a2121e6115c975cdb5fe6 Mon Sep 17 00:00:00 2001 From: Vitalii Parfonov Date: Fri, 1 Feb 2019 17:52:55 +0200 Subject: [PATCH 4/9] Fix tests and license header Signed-off-by: Vitalii Parfonov --- .../resources/org/eclipse/che/ide/public/bundle.js | 11 +++++++++++ .../workspace/server/WorkspaceLinksGeneratorTest.java | 3 +-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/bundle.js b/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/bundle.js index 81f4cd03dea..c5c60c2c5a9 100644 --- a/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/bundle.js +++ b/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/bundle.js @@ -1,3 +1,14 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; diff --git a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceLinksGeneratorTest.java b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceLinksGeneratorTest.java index 25bd1d32a5d..72cdfd4b251 100644 --- a/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceLinksGeneratorTest.java +++ b/wsmaster/che-core-api-workspace/src/test/java/org/eclipse/che/api/workspace/server/WorkspaceLinksGeneratorTest.java @@ -75,8 +75,7 @@ public void setUp() throws Exception { expectedStoppedLinks = new HashMap<>(); expectedStoppedLinks.put(LINK_REL_SELF, "http://localhost/api/workspace/wside-123877234580"); - expectedStoppedLinks.put( - LINK_REL_IDE_URL, "http://localhost/workspace-loader/my-namespace/my-name"); + expectedStoppedLinks.put(LINK_REL_IDE_URL, "http://localhost/my-namespace/my-name"); expectedRunningLinks = new HashMap<>(expectedStoppedLinks); expectedRunningLinks.put(LINK_REL_ENVIRONMENT_STATUS_CHANNEL, "ws://localhost"); From b6c9bc0f49f7bd5ce6908754cdb2afede1c094ad Mon Sep 17 00:00:00 2001 From: Vitalii Parfonov Date: Mon, 4 Feb 2019 12:15:36 +0200 Subject: [PATCH 5/9] Restore loder.* files from master Signed-off-by: Vitalii Parfonov --- .../org/eclipse/che/ide/public/loader.css | 161 ++++++++ .../org/eclipse/che/ide/public/loader.html | 42 ++ .../org/eclipse/che/ide/public/loader.js | 364 ++++++++++++++++++ 3 files changed, 567 insertions(+) create mode 100644 ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/loader.css create mode 100644 ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/loader.html create mode 100644 ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/loader.js diff --git a/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/loader.css b/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/loader.css new file mode 100644 index 00000000000..6894a5f7716 --- /dev/null +++ b/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/loader.css @@ -0,0 +1,161 @@ +/** + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +#workspace-loader { + position: fixed; + width: 300px; + height: 45px; + left: 50%; + top: 30%; + margin-left: -150px; + transition: all 0.2s ease-in-out; +} + +#workspace-loader-label { + position: absolute; + left: 0px; + top: 0px; + width: 300px; + height: 30px; + font-family: sans-serif; + font-size: 14px; + line-height: 30px; + text-align: center; + color: #a0a9b7; +} + +#workspace-loader-reload { + display: none; + position: absolute; + left: 0px; + top: 0px; + width: 300px; + height: 30px; + font-family: sans-serif; + font-size: 14px; + line-height: 30px; + text-align: center; + color: #a0a9b7; +} + +#workspace-loader-reload a { + color: #a0a9b7; +} + +#workspace-loader-progress { + position: absolute; + width: 300px; + height: 15px; + left: 0px; + bottom: 0px; + background-color: #202325; + border: 1px solid #456594; + box-sizing: border-box; +} + +#workspace-loader-progress>div { + position: absolute; + left: 1px; + right: 1px; + top: 1px; + bottom: 1px; + overflow: hidden; +} + +#workspace-loader-progress-bar { + box-sizing: border-box; + height: 100%; + width: 0%; + background-color: #498fe1; + transition: all 0.2s linear; + animation-name: dancing; + animation-duration: 3s; + animation-delay: 1s; + animation-timing-function: linear; + animation-iteration-count: infinite; +} + +#workspace-console { + position: fixed; + left: 30px; + right: 30px; + bottom: 25px; + height: 30%; + background-color: transparent; + overflow: auto; + color: #e6e6e6; + left: 2px; + right: 2px; + bottom: 2px; + transition: all 0.2s ease-in-out; +} + +#workspace-console>div { + position: relative; + width: 100%; +} + +#workspace-console pre { + font-family: "Droid Sans Mono", monospace; + font-size: 9pt; + line-height: 13px; + padding: 0; + margin: 0; + white-space: pre-wrap; + word-wrap: break-word; + cursor: text; +} + +#workspace-console pre.error { + color: #e22812; +} + +@-webkit-keyframes dancing { + 0% { + width: 0%; + margin-left: 0%; + } + 30% { + width: 30%; + margin-left: 0%; + } + 70% { + width: 30%; + margin-left: 70%; + } + 100% { + width: 0%; + margin-left: 100%; + } +} + +@keyframes dancing { + 0% { + width: 0%; + margin-left: 0%; + } + 6% { + width: 30%; + margin-left: 0%; + } + 14% { + width: 30%; + margin-left: 70%; + } + 20% { + width: 0%; + margin-left: 100%; + } + 100% { + width: 0%; + margin-left: 100%; + } +} diff --git a/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/loader.html b/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/loader.html new file mode 100644 index 00000000000..f3291aeaf67 --- /dev/null +++ b/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/loader.html @@ -0,0 +1,42 @@ + + + + + + + Workspace token loader + + + + + + + +
+
Loading a runtime token...
+
+
+
+
+
+
Press F5 or click here to try again.
+
+
+
+
+ + + + diff --git a/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/loader.js b/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/loader.js new file mode 100644 index 00000000000..bf3e2f7d59f --- /dev/null +++ b/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/loader.js @@ -0,0 +1,364 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +class KeycloakLoader { + /** + * Load keycloak settings + */ + loadKeycloakSettings() { + const msg = "Cannot load keycloak settings. This is normal for single-user mode."; + + return new Promise((resolve, reject) => { + if (window.parent && window.parent['_keycloak']) { + window['_keycloak'] = window.parent['_keycloak']; + resolve(window['_keycloak']); + return; + } + try { + const request = new XMLHttpRequest(); + + request.onerror = request.onabort = function () { + reject(new Error(msg)); + }; + + request.onload = () => { + if (request.status == 200) { + resolve(this.injectKeycloakScript(JSON.parse(request.responseText))); + } else { + reject(new Error(msg)); + } + }; + + const url = "/api/keycloak/settings"; + request.open("GET", url, true); + request.send(); + } catch (e) { + reject(new Error(msg + e.message)); + } + }); + } + + /** + * Injects keycloak javascript + */ + injectKeycloakScript(keycloakSettings) { + return new Promise((resolve, reject) => { + const script = document.createElement('script'); + script.type = 'text/javascript'; + script.language = 'javascript'; + script.async = true; + script.src = keycloakSettings['che.keycloak.js_adapter_url']; + + script.onload = () => { + resolve(this.initKeycloak(keycloakSettings)); + }; + + script.onerror = script.onabort = () => { + reject(new Error('cannot load ' + script.src)); + }; + + document.head.appendChild(script); + }); + } + + /** + * Initialize keycloak + */ + initKeycloak(keycloakSettings) { + return new Promise((resolve, reject) => { + + function keycloakConfig() { + const theOidcProvider = keycloakSettings['che.keycloak.oidc_provider']; + if (!theOidcProvider) { + return { + url: keycloakSettings['che.keycloak.auth_server_url'], + realm: keycloakSettings['che.keycloak.realm'], + clientId: keycloakSettings['che.keycloak.client_id'] + }; + } else { + return { + oidcProvider: theOidcProvider, + clientId: keycloakSettings['che.keycloak.client_id'] + }; + } + } + const keycloak = Keycloak(keycloakConfig()); + + window['_keycloak'] = keycloak; + + var useNonce; + if (typeof keycloakSettings['che.keycloak.use_nonce'] === 'string') { + useNonce = keycloakSettings['che.keycloak.use_nonce'].toLowerCase() === 'true'; + } + window.sessionStorage.setItem('oidcIdeRedirectUrl', location.href); + keycloak + .init({ + onLoad: 'login-required', + checkLoginIframe: false, + useNonce: useNonce, + scope: 'email profile', + redirectUri: keycloakSettings['che.keycloak.redirect_url.ide'] + }) + .success(() => { + resolve(keycloak); + }) + .error(() => { + reject(new Error('[Keycloak] Failed to initialize Keycloak')); + }); + }); + } + +} + +class Loader { + + constructor() { + document.getElementById('workspace-loader-reload').onclick = () => this.onclickReload(); + } + + /** + * Hides progress bar and displays reloading prompt + */ + hideLoader() { + document.getElementById('workspace-loader-label').style.display = 'none'; + document.getElementById('workspace-loader-progress').style.display = 'none'; + + document.getElementById('workspace-loader-reload').style.display = 'block'; + } + + /** + * Displays error message + * @param {string} message an error message to show + */ + error(message) { + const container = document.getElementById("workspace-console-container"); + if (container.childElementCount > 500) { + container.removeChild(container.firstChild) + } + + const element = document.createElement("pre"); + element.innerHTML = message; + container.appendChild(element); + if (element.scrollIntoView) { + element.scrollIntoView(); + } + element.className = "error"; + return element; + } + + /** + * Reloads the page + */ + onclickReload() { + window.location.reload(); + return false; + } + + /** + * Returns query parameter value if it is present + * @param {string} name a query parameter name + */ + getQueryParam(name) { + const params = window.location.search.substr(1), + paramEntries = params.split('&'); + const entry = paramEntries.find(_entry => { + return _entry.startsWith(name + '='); + }); + if (!entry) { + return; + } + const [_, value] = entry.split('='); + return decodeURIComponent(value); + } + + /** + * Fetches workspace details by ID + * @param {string} workspaceId a workspace ID + */ + asyncGetWorkspace(workspaceId) { + return new Promise((resolve, reject) => { + const request = new XMLHttpRequest(); + request.open("GET", '/api/workspace/' + workspaceId); + this.setAuthorizationHeader(request).then((xhr) => { + xhr.send(); + xhr.onreadystatechange = () => { + if (xhr.readyState !== 4) { + return; + } + if (xhr.status !== 200) { + const errorMessage = 'Failed to get the workspace: "' + this.getRequestErrorMessage(xhr) + '"'; + reject(new Error(errorMessage)); + return; + } + resolve(JSON.parse(xhr.responseText)); + }; + }); + }); + } + + /** + * Sets authorization header for a request + * @param {XMLHttpRequest} xhr + */ + setAuthorizationHeader(xhr) { + return new Promise((resolve, reject) => { + if (window._keycloak && window._keycloak.token) { + window._keycloak.updateToken(5).success(() => { + xhr.setRequestHeader('Authorization', 'Bearer ' + window._keycloak.token); + resolve(xhr); + }).error(() => { + window.sessionStorage.setItem('oidcIdeRedirectUrl', location.href); + window._keycloak.login(); + reject(new Error('Failed to refresh token')); + }); + } + + resolve(xhr); + }); + } + + /** + * Returns `true` if any machine in workspace contains a server which matches with `redirectUrl` + * @param {*} workspace a workspace + * @param {string} redirectUrl a redirect URL + */ + asyncCheckServiceLink(workspace, redirectUrl) { + return new Promise((resolve, reject) => { + if (!workspace.runtime) { + reject(new Error("Can't check service link: Workspace isn't RUNNING at the moment.")); + return; + } + + var machines = Object.keys(workspace.runtime.machines) + .map(machineName => workspace.runtime.machines[machineName]); + var servers = machines.map(machine => { + const servers = Object.keys(machine.servers) + .map(serverName => machine.servers[serverName]); + return servers; + }).reduce((servers, machineServers) => { + return servers.concat(...machineServers); + }, []); + var server = servers.find(_server => { + return _server.url && redirectUrl.startsWith(_server.url); + }); + + if (server) { + resolve(server); + } else { + reject(new Error("Workspace doesn't have a server which matches with URL: " + redirectUrl)); + } + }); + } + + /** + * Returns resolved promise if `workspace` has the `runtime` property + * @param {*} workspace a workspace + */ + asyncGetWsToken(workspace) { + return new Promise((resolve, reject) => { + if (workspace.runtime) { + resolve(workspace.runtime.machineToken); + } else { + reject(new Error("Can't get ws-token: Workspace isn't RUNNING at the moment.")); + } + }); + } + + /** + * @param {string} redirectUrl a redirect URL + * @param {string} token + */ + asyncAuthenticate(redirectUrl, token) { + const re = new RegExp(/(https?:\/\/[^\/]+?)(?:$|\/).*/), + // \ / \ / + // scheme host:port + url = redirectUrl.replace(re, "$1" + "/jwt/auth"); + return new Promise((resolve, reject) => { + const request = new XMLHttpRequest(); + request.open('GET', url); + request.setRequestHeader('Authorization', 'Bearer ' + token); + request.withCredentials = true; + request.send(); + request.onreadystatechange = () => { + if (request.readyState !== 4) { + return; + } + if (request.status !== 204) { + const errorMessage = 'Failed to authenticate: "' + this.getRequestErrorMessage(xhr) + '"'; + reject(new Error(errorMessage)); + return; + } + resolve(); + }; + }); + } + + getRequestErrorMessage(xhr) { + let errorMessage; + try { + const response = JSON.parse(xhr.responseText); + errorMessage = response.message; + } catch (e) { } + + if (errorMessage) { + return errorMessage; + } + + if (xhr.statusText) { + return xhr.statusText; + } + + return "Unknown error"; + } + +} + +(function () { + const keycloackAuthenticationPromise = new KeycloakLoader().loadKeycloakSettings(); + + const loader = new Loader(); + + const workspaceId = loader.getQueryParam('workspaceId'), + redirectUrl = loader.getQueryParam('redirectUrl'); + const getWorkspacePromise = new Promise((resolve, reject) => { + if (!workspaceId) { + reject(new Error("Workspace ID isn't found in query parameters.")); + } + if (!redirectUrl) { + reject(new Error("Redirect URL isn't found in query parameters.")); + } + resolve(); + }).then(_ => { + return keycloackAuthenticationPromise; + }).then(_ => { + return loader.asyncGetWorkspace(workspaceId); + }); + + const checkServiceUrlPromise = getWorkspacePromise.then(workspace => { + return loader.asyncCheckServiceLink(workspace, redirectUrl); + }) + + const tokenAuthenticationPromise = getWorkspacePromise.then(workspace => { + return loader.asyncGetWsToken(workspace); + }).then(token => { + return loader.asyncAuthenticate(redirectUrl, token); + }); + + Promise.all([checkServiceUrlPromise, tokenAuthenticationPromise]) + .then(_ => { + window.location.replace(redirectUrl); + }) + .catch(errorMessage => { + console.error(errorMessage); + loader.hideLoader(); + loader.error(errorMessage); + }); +})(); From 4e82934489b110ed414d1cfe8e6343ab8e7133ea Mon Sep 17 00:00:00 2001 From: Vitalii Parfonov Date: Tue, 5 Feb 2019 00:23:36 +0200 Subject: [PATCH 6/9] Temporary rename loder.html Signed-off-by: Vitalii Parfonov --- .../org/eclipse/che/ide/public/{loader.html => _loader.html} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/{loader.html => _loader.html} (100%) diff --git a/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/loader.html b/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/_loader.html similarity index 100% rename from ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/loader.html rename to ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/_loader.html From 8c31527b8ec80651e9af408f8d1452b80dbbd12a Mon Sep 17 00:00:00 2001 From: Vitalii Parfonov Date: Tue, 5 Feb 2019 17:43:26 +0200 Subject: [PATCH 7/9] Restore loader.html. Try to understand why Selenium test fail Signed-off-by: Vitalii Parfonov --- .../org/eclipse/che/ide/public/_loader.html | 42 ------------------- 1 file changed, 42 deletions(-) delete mode 100644 ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/_loader.html diff --git a/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/_loader.html b/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/_loader.html deleted file mode 100644 index f3291aeaf67..00000000000 --- a/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/_loader.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - Workspace token loader - - - - - - - -
-
Loading a runtime token...
-
-
-
-
-
-
Press F5 or click here to try again.
-
-
-
-
- - - - From 641caf5dcf4fa4902cc5203e8ed5db566caf039a Mon Sep 17 00:00:00 2001 From: Vitalii Parfonov Date: Tue, 5 Feb 2019 17:44:02 +0200 Subject: [PATCH 8/9] Restore loader.html. Try to understand why Selenium test fail Signed-off-by: Vitalii Parfonov --- .../org/eclipse/che/ide/public/loader.html | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/loader.html diff --git a/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/loader.html b/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/loader.html new file mode 100644 index 00000000000..f3291aeaf67 --- /dev/null +++ b/ide/che-ide-gwt-app/src/main/resources/org/eclipse/che/ide/public/loader.html @@ -0,0 +1,42 @@ + + + + + + + Workspace token loader + + + + + + + +
+
Loading a runtime token...
+
+
+
+
+
+
Press F5 or click here to try again.
+
+
+
+
+ + + + From 698bb4617568975b30b923088d3766db1b7f4118 Mon Sep 17 00:00:00 2001 From: Vitalii Parfonov Date: Tue, 5 Feb 2019 17:51:00 +0200 Subject: [PATCH 9/9] Restore indext.ts from master Signed-off-by: Vitalii Parfonov --- workspace-loader/src/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/workspace-loader/src/index.ts b/workspace-loader/src/index.ts index 38e47fe0646..c5e7e79207b 100644 --- a/workspace-loader/src/index.ts +++ b/workspace-loader/src/index.ts @@ -358,8 +358,7 @@ export class WorkspaceLoader { } } } - //fall back to GWT IDE behavior - this.openURL(workspace.links.ide.replace('/workspace-loader','') + this.getQueryString()); + this.openURL(workspace.links.ide + this.getQueryString()); }); }