Workspace loader (#8838)

Adding workspace loader application.
6.19.x
Vitaliy Guliy 2018-03-05 14:04:49 +02:00 committed by Anna Shumilova
parent d39ca3e80b
commit 3da13d54b5
38 changed files with 2285 additions and 7 deletions

2
.gitignore vendored
View File

@ -17,6 +17,8 @@ maven-eclipse.xml
*.iws
.idea/
.vscode/
# Compiled source #
###################
!*.com/

View File

@ -27,6 +27,11 @@
<artifactId>assembly-ide-war</artifactId>
<type>war</type>
</dependency>
<dependency>
<groupId>org.eclipse.che</groupId>
<artifactId>assembly-workspace-loader-war</artifactId>
<type>war</type>
</dependency>
<dependency>
<groupId>org.eclipse.che</groupId>
<artifactId>assembly-wsagent-server</artifactId>

View File

@ -55,6 +55,15 @@
<include>org.eclipse.che.dashboard:che-dashboard-war</include>
</includes>
</dependencySet>
<dependencySet>
<useProjectArtifact>false</useProjectArtifact>
<unpack>false</unpack>
<outputDirectory>tomcat/webapps</outputDirectory>
<outputFileNameMapping>workspace-loader.war</outputFileNameMapping>
<includes>
<include>org.eclipse.che:assembly-workspace-loader-war</include>
</includes>
</dependencySet>
<dependencySet>
<useProjectArtifact>false</useProjectArtifact>
<unpack>false</unpack>

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2018-2018 Red Hat, Inc.
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
which accompanies this distribution, and is available at
http://www.eclipse.org/legal/epl-v10.html
Contributors:
Red Hat, Inc. - initial API and implementation
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>che-assembly-parent</artifactId>
<groupId>org.eclipse.che</groupId>
<version>6.2.0-SNAPSHOT</version>
</parent>
<artifactId>assembly-workspace-loader-war</artifactId>
<packaging>war</packaging>
<name>Che Workspace Loader :: War Packaging</name>
<description>Packages Che Workspace Loader application as a Java web app</description>
<inceptionYear>2018</inceptionYear>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.che.workspace.loader</groupId>
<artifactId>che-workspace-loader</artifactId>
<type>zip</type>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<overlays>
<overlay>
<groupId>org.eclipse.che.workspace.loader</groupId>
<artifactId>che-workspace-loader</artifactId>
<type>zip</type>
</overlay>
</overlays>
<packagingExcludes>/webapp/</packagingExcludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>analyze</id>
<configuration>
<ignoredUnusedDeclaredDependencies>
<!-- dependency is required just to overlay it's content -->
<dep>org.eclipse.che.workspace.loader:che-workspace-loader</dep>
</ignoredUnusedDeclaredDependencies>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2018-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Defines a controller that is serving the index.html page of a workspace loader.
*
* @author Florent Benoit
*/
public class WSLoaderController extends HttpServlet {
/** Use the default dispatcher to serve the resource. */
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
RequestDispatcher dispatcher = request.getRequestDispatcher("/loader/index.html");
dispatcher.forward(request, response);
}
}

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2018-2018 Red Hat, Inc.
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
which accompanies this distribution, and is available at
http://www.eclipse.org/legal/epl-v10.html
Contributors:
Red Hat, Inc. - initial API and implementation
-->
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0"
metadata-complete="true">
<absolute-ordering></absolute-ordering>
<servlet>
<servlet-name>WSLoader</servlet-name>
<servlet-class>org.eclipse.che.WSLoaderController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>WSLoader</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/loader/*</url-pattern>
</servlet-mapping>
</web-app>

View File

@ -29,6 +29,7 @@
<module>assembly-wsagent-server</module>
<module>assembly-ide-war</module>
<module>assembly-wsmaster-war</module>
<module>assembly-workspace-loader-war</module>
<module>assembly-main</module>
</modules>
</project>

View File

@ -22,7 +22,7 @@ var serverOptions = {
var options = minimist(process.argv.slice(2), serverOptions);
var patterns = ['/api', '/ext', '/ws', '/datasource', '/java-ca', '/im', '/che', '/admin'];
var patterns = ['/api', '/ext', '/ws', '/datasource', '/java-ca', '/im', '/che', '/admin', '/workspace-loader'];
var proxies = [];
@ -36,6 +36,8 @@ patterns.forEach(function(pattern) {
proxyOptions.route = '/admin';
} else if (pattern === '/ext') {
proxyOptions.route = '/ext';
} else if (pattern === '/workspace-loader') {
proxyOptions.route = '/workspace-loader';
} else {
proxyOptions.route = '/api';
}

View File

@ -141,11 +141,11 @@ class IdeSvc {
let inDevMode = this.userDashboardConfig.developmentMode;
let randVal = Math.floor((Math.random() * 1000000) + 1);
let appendUrl = '?uid=' + randVal;
let workspace = this.cheWorkspace.getWorkspaceById(workspaceId);
this.openedWorkspace = workspace;
let ideUrlLink = workspace.links.ide;
let workspaceLoaderUrl = this.cheWorkspace.getWorkspaceLoaderUrl(workspace.namespace, workspace.config.name);
let ideUrlLink = workspaceLoaderUrl || workspace.links.ide;
if (this.ideAction != null) {
appendUrl = appendUrl + '&action=' + this.ideAction;

View File

@ -514,7 +514,7 @@ export class CheOrganization implements che.api.ICheOrganization {
return this.cheResourcesDistribution.fetchAvailableOrganizationResources(organization.id).then(() => {
let resource = this.cheResourcesDistribution.getOrganizationAvailableResourceByType(organization.id, this.resourceLimits.RAM);
if (resource.amount === -1) {
return 'RAM is not limited'
return 'RAM is not limited';
}
return resource ? 'Available RAM: ' + (resource.amount / 1024) + 'GB' : null;

View File

@ -93,6 +93,7 @@ export class CheHttpBackend {
this.$httpBackend.when('GET', '/api/').respond(200, {rootResources: []});
this.$httpBackend.when('GET', '/api/keycloak/settings').respond(404);
this.$httpBackend.when('GET', '/workspace-loader/').respond(404);
// add the remote call
let workspaceReturn = [];

View File

@ -79,6 +79,7 @@ export class CheWorkspace {
private statusDefers: Object;
private workspaceSettings: any;
private jsonRpcApiLocation: string;
private workspaceLoaderUrl: string;
/**
* Map with instance of Observable by workspaceId.
*/
@ -164,6 +165,8 @@ export class CheWorkspace {
cheBranding.unregisterCallback(CONTEXT_FETCHER_ID);
};
cheBranding.registerCallback(CONTEXT_FETCHER_ID, callback.bind(this));
this.checkWorkspaceLoader(userDashboardConfig.developmentMode, proxySettings);
}
/**
@ -648,6 +651,10 @@ export class CheWorkspace {
return '/ide/' + namespace + '/' + workspaceName;
}
getWorkspaceLoaderUrl(namespace: string, workspaceName: string): string {
return this.workspaceLoaderUrl ? this.workspaceLoaderUrl + namespace + '/' + workspaceName : null;
}
/**
* Creates deferred object which will be resolved
* when workspace change it's status to given
@ -797,4 +804,17 @@ export class CheWorkspace {
}
return wsUrl;
}
private checkWorkspaceLoader(devmode: boolean, proxySettings: string): void {
let url = '/workspace-loader/';
let promise = this.$http.get(url);
promise.then((response: {data: any}) => {
this.workspaceLoaderUrl = devmode ? proxySettings + url : url;
}, (error: any) => {
if (error.status !== 304) {
this.workspaceLoaderUrl = null;
}
});
}
}

View File

@ -104,6 +104,7 @@
script.onerror = script.onabort = function() {
console.error("Cannot load " + script.src);
Loader.startLoading();
};
document.head.appendChild(script);
@ -135,10 +136,14 @@
* Show loader and load compilation-mapping.txt file to determine which IDE JavaScript file will be loaded
*/
this.startLoading = function() {
setTimeout(function() {
setTimeout(() => {
document.getElementById("ide-loader").style.opacity = 1;
}, 1);
setTimeout(() => {
window.parent.postMessage("show-ide", "*");
}, 250);
var msg = "Cannot load compilation mappings";
try {
@ -362,9 +367,8 @@
};
};
setTimeout(function() {
setTimeout(() => {
Loader.loadKeycloakSettings();
window.parent.postMessage("show-ide", "*");
}, 1);
</script>

13
pom.xml
View File

@ -36,6 +36,7 @@
<module>ide/che-ide-full</module>
<module>ide/che-ide-gwt-app</module>
<module>dashboard</module>
<module>workspace-loader</module>
<module>assembly</module>
<module>selenium</module>
</modules>
@ -89,6 +90,12 @@
<artifactId>assembly-main</artifactId>
<version>${che.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.che</groupId>
<artifactId>assembly-workspace-loader-war</artifactId>
<version>${che.version}</version>
<type>war</type>
</dependency>
<dependency>
<groupId>org.eclipse.che</groupId>
<artifactId>assembly-wsagent-server</artifactId>
@ -1876,6 +1883,12 @@
<artifactId>che-selenium-test</artifactId>
<version>${che.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.che.workspace.loader</groupId>
<artifactId>che-workspace-loader</artifactId>
<version>${che.version}</version>
<type>zip</type>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-test</artifactId>

View File

@ -0,0 +1,5 @@
Dockerfile
node_modules
target
dist
package-lock.json

3
workspace-loader/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules/
dist/
package-lock.json

View File

@ -0,0 +1,24 @@
# Copyright (c) 2018-2018 Red Hat, Inc
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
# This is a Dockerfile allowing to build workspace loader by using a docker container.
# Build step: $ docker build -t eclipse-che-workspace-loader .
# It builds an archive file that can be used by doing later
# $ docker run --rm eclipse-che-workspace-loader | tar -C target/ -zxf -
FROM node:6.11.2
COPY package.json /workspace-loader/
RUN cd /workspace-loader && npm install
COPY . /workspace-loader/
RUN cd /workspace-loader && \
npm run build && \
npm run test && \
cd target && \
tar zcf /tmp/workspace-loader.tar.gz dist
CMD zcat /tmp/workspace-loader.tar.gz

View File

@ -0,0 +1,24 @@
<!--
Copyright (c) 2018-2018 Red Hat, Inc.
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
which accompanies this distribution, and is available at
http://www.eclipse.org/legal/epl-v10.html
Contributors:
Red Hat, Inc. - initial API and implementation
-->
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
<id>workspace-loader-zip</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>${project.basedir}/target/dist</directory>
<outputDirectory>/loader</outputDirectory>
</fileSet>
</fileSets>
</assembly>

View File

@ -0,0 +1,44 @@
{
"name": "che-workspace-loader",
"version": "1.0.0",
"description": "Eclipse Che Workspace Loader App",
"main": "index.js",
"scripts": {
"test": "jest",
"build": "webpack --config webpack.prod.js",
"start": "webpack-dev-server --open --config webpack.dev.js"
},
"author": "",
"license": "EPL-1.0",
"devDependencies": {
"@types/jest": "^22.1.3",
"clean-webpack-plugin": "^0.1.18",
"css-loader": "^0.28.9",
"extract-text-webpack-plugin": "^3.0.2",
"html-webpack-plugin": "^2.30.1",
"jest": "^22.4.2",
"style-loader": "^0.20.1",
"ts-loader": "^3.4.0",
"typescript": "^2.7.1",
"uglifyjs-webpack-plugin": "^1.1.8",
"webpack": "^3.10.0",
"webpack-dev-server": "^2.11.1",
"webpack-merge": "^4.1.1"
},
"dependencies": {},
"jest": {
"moduleFileExtensions": [
"ts",
"js"
],
"transform": {
"^.+\\.(ts)$": "<rootDir>/test/preprocessor.js"
},
"moduleNameMapper": {
"\\.(css|less)$": "<rootDir>/test/mock.js"
},
"testMatch": [
"**/test/*.(ts)"
]
}
}

177
workspace-loader/pom.xml Normal file
View File

@ -0,0 +1,177 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2018-2018 Red Hat, Inc.
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
which accompanies this distribution, and is available at
http://www.eclipse.org/legal/epl-v10.html
Contributors:
Red Hat, Inc. - initial API and implementation
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>che-parent</artifactId>
<groupId>org.eclipse.che</groupId>
<version>6.2.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>org.eclipse.che.workspace.loader</groupId>
<artifactId>che-workspace-loader</artifactId>
<version>6.2.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Che Workspace Loader :: Web App</name>
<inceptionYear>2018</inceptionYear>
<build>
<finalName>workspace-loader</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
<configuration>
<tarLongFileMode>posix</tarLongFileMode>
<appendAssemblyId>false</appendAssemblyId>
<updateOnly>false</updateOnly>
<descriptors>
<descriptor>${project.basedir}/assembly.xml</descriptor>
</descriptors>
<finalName>workspace-loader</finalName>
</configuration>
</plugin>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<configuration>
<filesets>
<fileset>
<directory>${basedir}/node_modules</directory>
<followSymlinks>false</followSymlinks>
</fileset>
<fileset>
<directory>${basedir}/dist</directory>
<followSymlinks>false</followSymlinks>
</fileset>
<fileset>
<directory>${basedir}</directory>
<includes>
<include>**/package-lock.json</include>
</includes>
<followSymlinks>false</followSymlinks>
</fileset>
</filesets>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<!-- Docker build used by default. To use native build, use -Pnative -->
<id>docker</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>build-image</id>
<phase>generate-sources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<!-- build workspace loader with maven in docker -->
<exec dir="${basedir}" executable="docker" failonerror="true">
<arg value="build" />
<arg value="-t" />
<arg value="eclipse-che-workspace-loader" />
<arg value="." />
</exec>
</target>
</configuration>
</execution>
<execution>
<id>unpack-docker-build</id>
<phase>generate-sources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<!-- extract built workspace loader -->
<exec executable="bash">
<arg value="-c" />
<arg value="docker run --rm eclipse-che-workspace-loader | tar -C target/ -xf -" />
</exec>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>native</id>
<build>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>build-workspace-loader</id>
<phase>compile</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<!-- install node modules -->
<exec dir="${basedir}" executable="npm" failonerror="true">
<arg value="install" />
</exec>
<!-- build workspace loader -->
<exec dir="${basedir}" executable="npm" failonerror="true">
<arg value="run" />
<arg value="build" />
</exec>
</target>
</configuration>
</execution>
<execution>
<id>test-workspace-loader</id>
<phase>test</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<!-- test workspace loader -->
<exec dir="${basedir}" executable="npm" failonerror="true">
<arg value="run" />
<arg value="test" />
</exec>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

126
workspace-loader/src/custom.d.ts vendored Normal file
View File

@ -0,0 +1,126 @@
/*
* Copyright (c) 2018-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
declare module 'che' {
export = che;
}
declare function require(string: string): any;
declare namespace che {
export interface IWorkspace {
id?: string;
projects?: any;
links?: {
ide?: string
[rel: string]: string;
};
temporary?: boolean;
status?: string;
namespace?: string;
attributes?: IWorkspaceAttributes;
config: IWorkspaceConfig;
runtime?: IWorkspaceRuntime;
isLocked?: boolean;
usedResources?: string;
}
export interface IWorkspaceConfig {
name?: string;
defaultEnv?: string;
environments: {
[envName: string]: IWorkspaceEnvironment
};
projects?: Array<any>;
commands?: Array<any>;
}
export interface IWorkspaceEnvironment {
machines: {
[machineName: string]: IEnvironmentMachine
};
recipe: IRecipe;
}
export interface IRecipe {
id?: string;
content?: string;
location?: string;
contentType?: string;
type: string;
}
export interface IEnvironmentMachine {
installers?: string[];
attributes?: {
memoryLimitBytes?: string | number;
[attrName: string]: string | number;
};
servers?: {
[serverRef: string]: IEnvironmentMachineServer
};
volumes?: {
[volumeRef: string]: IEnvironmentMachineVolume
};
env?: { [envName: string]: string };
}
export interface IEnvironmentMachineServer {
port: string | number;
protocol: string;
path?: string;
properties?: any;
attributes?: {
[attrName: string]: string | number;
};
}
export interface IEnvironmentMachineVolume {
path: string;
}
export interface IWorkspaceAttributes {
created: number;
updated?: number;
stackId?: string;
errorMessage?: string;
[propName: string]: string | number;
}
export interface IWorkspaceRuntime {
activeEnv: string;
links: any[];
machines: {
[machineName: string]: IWorkspaceRuntimeMachine
};
owner: string;
warnings: IWorkspaceWarning[];
}
export interface IWorkspaceWarning {
code?: number;
message: string;
}
export interface IWorkspaceRuntimeMachine {
attributes: { [propName: string]: string };
servers: { [serverName: string]: IWorkspaceRuntimeMachineServer };
}
export interface IWorkspaceRuntimeMachineServer {
status: string;
port: string;
url: string;
ref: string;
protocol: string;
path: string;
attributes: { [propName: string]: string };
}
}

View File

@ -0,0 +1,38 @@
<!--
Copyright (c) 2018-2018 Red Hat, Inc.
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
which accompanies this distribution, and is available at
http://www.eclipse.org/legal/epl-v10.html
Contributors:
Red Hat, Inc. - initial API and implementation
-->
<!doctype html>
<html>
<head>
<title><%= htmlWebpackPlugin.options.title %></title>
<% if(htmlWebpackPlugin.options.cssName) { %>
<link href="<%= htmlWebpackPlugin.options.urlPrefix %><%= htmlWebpackPlugin.options.cssName %>" rel="stylesheet">
<% } %>
<script src="<%= htmlWebpackPlugin.options.urlPrefix %><%= htmlWebpackPlugin.files.js %>" type="text/javascript" async="true"></script>
</head>
<body style="background-color: #21252b; transition: background-color 0.5s ease;">
<div id="workspace-loader">
<div id="workspace-loader-label">Loading...</div>
<div id="workspace-loader-progress">
<div>
<div id="workspace-loader-progress-bar"></div>
</div>
</div>
</div>
<div id="workspace-console">
<div id="workspace-console-container"></div>
</div>
</body>
</html>

View File

@ -0,0 +1,247 @@
/*
* Copyright (c) 2018-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
'use strict';
require('./style.css');
import { WebsocketClient } from './json-rpc/websocket-client';
import { CheJsonRpcMasterApi } from './json-rpc/che-json-rpc-master-api';
import { Loader } from './loader/loader'
const WEBSOCKET_CONTEXT = '/api/websocket';
export class WorkspaceLoader {
loader: Loader;
workspace: che.IWorkspace;
startAfterStopping = false;
constructor(loader: Loader) {
this.loader = loader;
/** Ask dashboard to show the IDE. */
window.parent.postMessage("show-ide", "*");
}
/**
* Loads the workspace.
*/
load(): Promise<void> {
let workspaceKey = this.getWorkspaceKey();
if (!workspaceKey || workspaceKey === "") {
console.error("Workspace is not defined");
return;
}
return this.getWorkspace(workspaceKey)
.then((workspace) => {
this.workspace = workspace;
if (this.hasPreconfiguredIDE()) {
return this.handleWorkspace();
} else {
return new Promise<void>((resolve) => {
this.openURL(workspace.links.ide + this.getQueryString());
resolve();
});
}
})
.catch(err => {
console.error(err);
});
}
/**
* Determines whether the workspace has preconfigured IDE.
*/
hasPreconfiguredIDE() : boolean {
if (this.workspace.config.defaultEnv && this.workspace.config.environments) {
let defaultEnvironment = this.workspace.config.defaultEnv;
let environment = this.workspace.config.environments[defaultEnvironment];
let machines = environment.machines;
for (let machineName in machines) {
let servers = machines[machineName].servers;
for (let serverName in servers) {
let attributes = servers[serverName].attributes;
if (attributes['type'] === 'ide') {
return true;
}
}
}
}
return false;
}
/**
* Returns workspace key from current address or empty string when it is undefined.
*/
getWorkspaceKey(): string {
let result: string = window.location.pathname.substr(1);
return result.substr(result.indexOf('/') + 1, result.length);
}
/**
* Returns base websocket URL.
*/
websocketBaseURL(): string {
let wsProtocol = 'http:' === document.location.protocol ? 'ws' : 'wss';
return wsProtocol + '://' + document.location.host;
}
/**
* Returns query string.
*/
getQueryString(): string {
return location.search;
}
/**
* Get workspace by ID.
*
* @param workspaceId workspace id
*/
getWorkspace(workspaceId: string): Promise<che.IWorkspace> {
return new Promise((resolve, reject) => {
let request = new XMLHttpRequest();
request.open("GET", '/api/workspace/' + workspaceId);
request.send();
request.onreadystatechange = function () {
if (this.readyState !== 4) { return; }
if (this.status !== 200) {
reject(this.status ? this.statusText : "Unknown error");
return;
}
resolve(JSON.parse(this.responseText));
};
});
}
/**
* Start current workspace.
*/
startWorkspace(): Promise<che.IWorkspace> {
return new Promise((resolve, reject) => {
let request = new XMLHttpRequest();
request.open("POST", `/api/workspace/${this.workspace.id}/runtime`);
request.send();
request.onreadystatechange = function () {
if (this.readyState !== 4) { return; }
if (this.status !== 200) {
reject(this.status ? this.statusText : "Unknown error");
return;
}
resolve(JSON.parse(this.responseText));
};
});
}
/**
* Handles workspace status.
*/
handleWorkspace(): Promise<void> {
if (this.workspace.status === 'RUNNING') {
this.openIDE();
return;
}
return this.subscribeWorkspaceEvents().then(() => {
if (this.workspace.status === 'STOPPED') {
this.startWorkspace();
} else if (this.workspace.status === 'STOPPING') {
this.startAfterStopping = true;
}
});
}
/**
* Shows environment outputs.
*
* @param message output message
*/
onEnvironmentOutput(message) : void {
this.loader.log(message);
}
/**
* Handles changing of workspace status.
*
* @param status workspace status
*/
onWorkspaceStatusChanged(status) : void {
if (status === 'RUNNING') {
this.openIDE();
} else if (status === 'STOPPED' && this.startAfterStopping) {
this.startWorkspace();
}
}
/**
* Subscribes to the workspace events.
*/
subscribeWorkspaceEvents() : Promise<void> {
let master = new CheJsonRpcMasterApi(new WebsocketClient());
return new Promise((resolve) => {
master.connect(this.websocketBaseURL() + WEBSOCKET_CONTEXT).then(() => {
master.subscribeEnvironmentOutput(this.workspace.id,
(message: any) => this.onEnvironmentOutput(message.text));
master.subscribeWorkspaceStatus(this.workspace.id,
(message: any) => this.onWorkspaceStatusChanged(message.status));
resolve();
});
});
}
/**
* Opens IDE for the workspace.
*/
openIDE() : void {
this.getWorkspace(this.workspace.id).then((workspace) => {
let machines = workspace.runtime.machines;
for (let machineName in machines) {
let servers = machines[machineName].servers;
for (let serverId in servers) {
let attributes = servers[serverId].attributes;
if (attributes['type'] === 'ide') {
this.openURL(servers[serverId].url);
return;
}
}
}
this.openURL(workspace.links.ide);
});
}
/**
* Schedule opening URL.
* Scheduling prevents appearing an error net::ERR_CONNECTION_REFUSED instead opening the URL.
*
* @param url url to be opened
*/
openURL(url) : void {
// Preconfigured IDE may use dedicated port. In this case Chrome browser fails
// with error net::ERR_CONNECTION_REFUSED. Timer helps to open the URL without errors.
setTimeout(() => {
window.location.href = url;
}, 1000);
}
};
/** Initialize */
if (document.getElementById('workspace-console')) {
new WorkspaceLoader(new Loader()).load();
}

View File

@ -0,0 +1,86 @@
/*
* Copyright (c) 2018-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
'use strict';
import {ICommunicationClient, JsonRpcClient} from './json-rpc-client';
export class IChannel {
subscription: string;
unsubscription: string;
notification: string;
}
/**
* Class for basic CHE API communication methods.
*
* @author Ann Shumilova
*/
export class CheJsonRpcApiClient {
/**
* Client that implements JSON RPC protocol.
*/
private jsonRpcClient: JsonRpcClient;
/**
* Communication client (can be http, websocket).
*/
private client: ICommunicationClient;
constructor (client: ICommunicationClient) {
this.client = client;
this.jsonRpcClient = new JsonRpcClient(client);
}
/**
* Subscribe on the events from service.
*
* @param event event's name to subscribe
* @param notification notification name to handle
* @param handler event's handler
* @param params params (optional)
*/
subscribe(event: string, notification: string, handler: Function, params?: any): void {
this.jsonRpcClient.addNotificationHandler(notification, handler);
this.jsonRpcClient.notify(event, params);
}
/**
* Unsubscribe concrete handler from events from service.
*
* @param event event's name to unsubscribe
* @param notification notification name binded to the event
* @param handler handler to be removed
* @param params params (optional)
*/
unsubscribe(event: string, notification: string, handler: Function, params?: any): void {
this.jsonRpcClient.removeNotificationHandler(notification, handler);
this.jsonRpcClient.notify(event, params);
}
/**
* Connects to the pointed entrypoint
*
* @param entrypoint entrypoint to connect to
* @returns {Promise<any>} promise
*/
connect(entrypoint: string): Promise<any> {
return this.client.connect(entrypoint);
}
/**
* Makes request.
*
* @param method
* @param params
* @returns {ng.IPromise<any>}
*/
request(method: string, params?: any): Promise<any> {
return this.jsonRpcClient.request(method, params);
}
}

View File

@ -0,0 +1,181 @@
/*
* Copyright (c) 2018-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
'use strict';
import {CheJsonRpcApiClient} from './che-json-rpc-api-service';
import {ICommunicationClient} from './json-rpc-client';
enum MasterChannels {
ENVIRONMENT_OUTPUT = <any>'machine/log',
ENVIRONMENT_STATUS = <any>'machine/statusChanged',
WS_AGENT_OUTPUT = <any>'installer/log',
WORKSPACE_STATUS = <any>'workspace/statusChanged'
}
const SUBSCRIBE: string = 'subscribe';
const UNSUBSCRIBE: string = 'unsubscribe';
/**
* Client API for workspace master interactions.
*
* @author Ann Shumilova
*/
export class CheJsonRpcMasterApi {
private cheJsonRpcApi: CheJsonRpcApiClient;
private clientId: string;
constructor (client: ICommunicationClient) {
this.cheJsonRpcApi = new CheJsonRpcApiClient(client);
}
/**
* Opens connection to pointed entrypoint.
*
* @param entrypoint
* @returns {IPromise<IHttpPromiseCallbackArg<any>>}
*/
connect(entrypoint: string): Promise<any> {
return this.cheJsonRpcApi.connect(entrypoint).then(() => {
return this.fetchClientId();
});
}
/**
* Subscribes the environment output.
*
* @param workspaceId workspace's id
* @param machineName machine's name
* @param callback callback to process event
*/
subscribeEnvironmentOutput(workspaceId: string, callback: Function): void {
this.subscribe(MasterChannels.ENVIRONMENT_OUTPUT, workspaceId, callback);
}
/**
* Un-subscribes the pointed callback from the environment output.
*
* @param workspaceId workspace's id
* @param machineName machine's name
* @param callback callback to process event
*/
unSubscribeEnvironmentOutput(workspaceId: string, callback: Function): void {
this.unsubscribe(MasterChannels.ENVIRONMENT_OUTPUT, workspaceId, callback);
}
/**
* Subscribes the environment status changed.
*
* @param workspaceId workspace's id
* @param callback callback to process event
*/
subscribeEnvironmentStatus(workspaceId: string, callback: Function): void {
this.subscribe(MasterChannels.ENVIRONMENT_STATUS, workspaceId, callback);
}
/**
* Un-subscribes the pointed callback from environment status changed.
*
* @param workspaceId workspace's id
* @param callback callback to process event
*/
unSubscribeEnvironmentStatus(workspaceId: string, callback: Function): void {
this.unsubscribe(MasterChannels.ENVIRONMENT_STATUS, workspaceId, callback);
}
/**
* Subscribes on workspace agent output.
*
* @param workspaceId workspace's id
* @param callback callback to process event
*/
subscribeWsAgentOutput(workspaceId: string, callback: Function): void {
this.subscribe(MasterChannels.WS_AGENT_OUTPUT, workspaceId, callback);
}
/**
* Un-subscribes from workspace agent output.
*
* @param workspaceId workspace's id
* @param callback callback to process event
*/
unSubscribeWsAgentOutput(workspaceId: string, callback: Function): void {
this.unsubscribe(MasterChannels.WS_AGENT_OUTPUT, workspaceId, callback);
}
/**
* Subscribes to workspace's status.
*
* @param workspaceId workspace's id
* @param callback callback to process event
*/
subscribeWorkspaceStatus(workspaceId: string, callback: Function): void {
let statusHandler = (message: any) => {
if (workspaceId === message.workspaceId) {
callback(message);
}
};
this.subscribe(MasterChannels.WORKSPACE_STATUS, workspaceId, statusHandler);
}
/**
* Un-subscribes pointed callback from workspace's status.
*
* @param workspaceId
* @param callback
*/
unSubscribeWorkspaceStatus(workspaceId: string, callback: Function): void {
this.unsubscribe(MasterChannels.WORKSPACE_STATUS, workspaceId, callback);
}
/**
* Fetch client's id and strores it.
*
* @returns {IPromise<TResult>}
*/
fetchClientId(): Promise<any> {
return this.cheJsonRpcApi.request('websocketIdService/getId').then((data: any) => {
this.clientId = data[0];
});
}
/**
* Returns client's id.
*
* @returns {string} clinet connection identifier
*/
getClientId(): string {
return this.clientId;
}
/**
* Performs subscribe to the pointed channel for pointed workspace's ID and callback.
*
* @param channel channel to un-subscribe
* @param workspaceId workspace's id
* @param callback callback
*/
private subscribe(channel: MasterChannels, workspaceId: string, callback: Function): void {
let method: string = channel.toString();
let params = {method: method, scope: {workspaceId: workspaceId}};
this.cheJsonRpcApi.subscribe(SUBSCRIBE, method, callback, params);
}
/**
* Performs un-subscribe of the pointed channel by pointed workspace's ID and callback.
*
* @param channel channel to un-subscribe
* @param workspaceId workspace's id
* @param callback callback
*/
private unsubscribe(channel: MasterChannels, workspaceId: string, callback: Function): void {
let method: string = channel.toString();
let params = {method: method, scope: {workspaceId: workspaceId}};
this.cheJsonRpcApi.unsubscribe(UNSUBSCRIBE, method, callback, params);
}
}

View File

@ -0,0 +1,198 @@
/*
* Copyright (c) 2018-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
'use strict';
import { IDeffered, Deffered } from './util';
const JSON_RPC_VERSION: string = '2.0';
/**
* Interface for communication between two entrypoints.
* The implementation can be through websocket or http protocol.
*/
export interface ICommunicationClient {
/**
* Process responses.
*/
onResponse: Function;
/**
* Performs connections.
*
* @param entrypoint
*/
connect(entrypoint: string): Promise<any>;
/**
* Close the connection.
*/
disconnect(): void;
/**
* Send pointed data.
*
* @param data data to be sent
*/
send(data: any): void;
}
interface IRequest {
jsonrpc: string;
id: string;
method: string;
params: any;
}
interface INotification {
jsonrpc: string;
method: string;
params: any;
}
/**
* This client is handling the JSON RPC requests, responses and notifications.
*
* @author Ann Shumilova
*/
export class JsonRpcClient {
/**
* Client for performing communications.
*/
private client: ICommunicationClient;
/**
* The list of the pending requests by request id.
*/
private pendingRequests: Map<string, IDeffered<any>>;
/**
* The list of notification handlers by method name.
*/
private notificationHandlers: Map<string, Array<Function>>;
private counter: number = 100;
constructor(client: ICommunicationClient) {
this.client = client;
this.pendingRequests = new Map<string, IDeffered<any>>();
this.notificationHandlers = new Map<string, Array<Function>>();
this.client.onResponse = (message: any): void => {
this.processResponse(message);
};
}
/**
* Performs JSON RPC request.
*
* @param method method's name
* @param params params
* @returns {IPromise<any>}
*/
request(method: string, params?: any): Promise<any> {
let deferred = new Deffered();
let id: string = (this.counter++).toString();
this.pendingRequests.set(id, deferred);
let request: IRequest = {
jsonrpc: JSON_RPC_VERSION,
id: id,
method: method,
params: params
};
this.client.send(request);
return deferred.promise;
}
/**
* Sends JSON RPC notification.
*
* @param method method's name
* @param params params (optional)
*/
notify(method: string, params?: any): void {
let request: INotification = {
jsonrpc: JSON_RPC_VERSION,
method: method,
params: params
};
this.client.send(request);
}
/**
* Adds notification handler.
*
* @param method method's name
* @param handler handler to process notification
*/
public addNotificationHandler(method: string, handler: Function): void {
let handlers = this.notificationHandlers.get(method);
if (handlers) {
handlers.push(handler);
} else {
handlers = [handler];
this.notificationHandlers.set(method, handlers);
}
}
/**
* Removes notification handler.
*
* @param method method's name
* @param handler handler
*/
public removeNotificationHandler(method: string, handler: Function): void {
let handlers = this.notificationHandlers.get(method);
if (handlers) {
handlers.splice(handlers.indexOf(handler), 1);
}
}
/**
* Processes response - detects whether it is JSON RPC response or notification.
*
* @param message
*/
private processResponse(message: any): void {
if (message.id && this.pendingRequests.has(message.id)) {
this.processResponseMessage(message);
} else {
this.processNotification(message);
}
}
/**
* Processes JSON RPC notification.
*
* @param message message
*/
private processNotification(message: any): void {
let method = message.method;
let handlers = this.notificationHandlers.get(method);
if (handlers && handlers.length > 0) {
handlers.forEach((handler: Function) => {
handler(message.params);
});
}
}
/**
* Process JSON RPC response.
*
* @param message
*/
private processResponseMessage(message: any): void {
let promise = this.pendingRequests.get(message.id);
if (message.result) {
promise.resolve(message.result);
return;
}
if (message.error) {
promise.reject(message.error);
}
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2018-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
export interface IDeffered<T> {
resolve(value?: T): void;
reject(reason?: any): void;
promise: Promise<T>;
}
export class Deffered<T> implements IDeffered<T> {
promise: Promise<T>;
private resolveF;
private rejectF;
constructor() {
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
resolve(value?: T): void {
this.resolveF(value);
}
reject(reason?: any): void {
this.rejectF(reason);
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (c) 2018-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
'use strict';
import { ICommunicationClient } from './json-rpc-client';
/**
* The implementation for JSON RPC protocol communication through websocket.
*
* @author Ann Shumilova
*/
export class WebsocketClient implements ICommunicationClient {
onResponse: Function;
private websocketStream: WebSocket;
constructor() {
}
/**
* Performs connection to the pointed entrypoint.
*
* @param entrypoint the entrypoint to connect to
*/
connect(entrypoint: string): Promise<void> {
return new Promise((resolve, reject) => {
this.websocketStream = new WebSocket(entrypoint);
this.websocketStream.addEventListener("open", () => {
resolve();
});
this.websocketStream.addEventListener("error", () => {
reject();
});
this.websocketStream.addEventListener("message", (message) => {
let data = JSON.parse(message.data);
this.onResponse(data);
});
});
}
/**
* Performs closing the connection.
*/
disconnect(): void {
if (this.websocketStream) {
this.websocketStream.close();
}
}
/**
* Sends pointed data.
*
* @param data to be sent
*/
send(data: any): void {
this.websocketStream.send(JSON.stringify(data));
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright (c) 2018-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
'use strict';
export class Loader {
/**
* Initializes the Loader.
*/
constructor() {
/** Show the loader */
setTimeout(() => {
document.getElementById('workspace-loader').style.display = "block";
setTimeout(() => {
document.getElementById('workspace-loader').style.opacity = "1";
}, 1);
}, 1);
/** Add click handler to maximize output */
document.getElementById('workspace-console').onclick = () => this.onclick();
}
/**
* Adds a message to output console.
*
* @param message message to log
*/
log(message: string): void {
let element = document.createElement("pre");
element.innerHTML = message;
document.getElementById("workspace-console-container").appendChild(element);
element.scrollIntoView();
}
onclick(): void {
if (document.getElementById('workspace-loader').hasAttribute("max")) {
document.getElementById('workspace-loader').removeAttribute("max");
document.getElementById('workspace-console').removeAttribute("max");
} else {
document.getElementById('workspace-loader').setAttribute("max", "");
document.getElementById('workspace-console').setAttribute("max", "");
}
}
}

View File

@ -0,0 +1,195 @@
/**
* Copyright (c) 2018-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
#workspace-loader {
position: fixed;
width: 300px;
height: 45px;
left: 50%;
top: 30%;
margin-left: -150px;
opacity: 0;
display: none;
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-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;
cursor: pointer;
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;
cursor: pointer;
}
#workspace-loader[max] {
top: 5%;
}
#workspace-console[max] {
height: calc(100% - 110px);
}
@-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%;
}
}
/********************************************************************************************
*
* Styled scroll bars
*
********************************************************************************************/
::-webkit-scrollbar {
width: 7px;
height: 7px;
}
::-webkit-scrollbar-button {
width: 0px;
height: 0px;
display: none;
}
::-webkit-scrollbar-corner {
background-color: transparent;
display: none;
}
::-webkit-scrollbar-track, ::-webkit-scrollbar-track:hover, ::-webkit-scrollbar-track:active {
background-color: transparent;
-webkit-box-shadow: none;
box-shadow: none;
border: none;
}
::-webkit-scrollbar-thumb {
background-clip: padding-box;
background-color: rgba(215, 215, 215, 0.10);
;
border: 2px solid transparent;
border-radius: 4px;
-webkit-box-shadow: inset 0 0 2px rgba(235, 235, 235, 0.3);
box-shadow: inset 0 0 2px rgba(235, 235, 235, 0.3);
min-height: 32px;
}
::-webkit-scrollbar-thumb:hover {
background-color: rgba(215, 215, 215, 0.3);
}

View File

@ -0,0 +1,13 @@
/*
* Copyright (c) 2018-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
'use strict';
module.exports = {};

View File

@ -0,0 +1,18 @@
/*
* Copyright (c) 2018-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
const compiler = require('typescript');
module.exports = {
process(source, path) {
return compiler.transpile(source, {}, path, []);
}
};

View File

@ -0,0 +1,336 @@
/*
* Copyright (c) 2018-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
/// <reference path="../src/custom.d.ts" />
'use strict';
import {WorkspaceLoader} from '../src/index';
import { Loader } from '../src/loader/loader';
describe('Workspace Loader', () => {
let fakeWorkspaceConfig: che.IWorkspace;
beforeEach(function() {
document.body.innerHTML = `<div id="workspace-loader">
<div id="workspace-loader-label">Loading...</div>
<div id="workspace-loader-progress">
<div>
<div id="workspace-loader-progress-bar"></div>
</div>
</div>
</div>
<div id="workspace-console">
<div id="workspace-console-container"></div>
</div>`;
fakeWorkspaceConfig = {
status: 'STOPPED',
links: {
ide: "test url"
},
config: {
defaultEnv: "default",
"environments": {
"default": {
machines: {
machine: {
servers: {
server1: {
attributes: {
type: "ide"
},
port: 0,
protocol: ""
}
}
},
},
recipe: {
type: ""
}
}
}
}
} as che.IWorkspace;
});
it('must have "workspace-loader" in DOM', () => {
const loader = document.getElementById('workspace-loader');
expect(loader).toBeTruthy();
});
it('test when workspace key is not specified', () => {
let loader = new Loader();
let workspaceLoader = new WorkspaceLoader(loader);
spyOn(workspaceLoader, 'getWorkspaceKey');
spyOn(workspaceLoader, 'getWorkspace');
workspaceLoader.load();
expect(workspaceLoader.getWorkspaceKey).toHaveBeenCalled();
expect(workspaceLoader.getWorkspace).not.toHaveBeenCalled();
});
it('test getWorkspace with test value', () => {
let loader = new Loader();
let workspaceLoader = new WorkspaceLoader(loader);
spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue("foo/bar");
spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => {
return new Promise((resolve) => {
resolve(fakeWorkspaceConfig);
});
});
workspaceLoader.load();
expect(workspaceLoader.getWorkspaceKey).toHaveBeenCalled();
expect(workspaceLoader.getWorkspace).toHaveBeenCalledWith("foo/bar");
});
describe('must open IDE directly when workspace does not have IDE server', () => {
let workspaceLoader;
beforeEach((done) => {
let loader = new Loader();
workspaceLoader = new WorkspaceLoader(loader);
spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue("foo/bar");
spyOn(workspaceLoader, 'getQueryString').and.returnValue("");
spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => {
return new Promise((resolve) => {
fakeWorkspaceConfig.config.environments["default"].machines = {};
resolve(fakeWorkspaceConfig);
});
});
spyOn(workspaceLoader, "handleWorkspace");
spyOn(workspaceLoader, "openURL").and.callFake(() => {
done();
});
workspaceLoader.load();
});
it('basic workspace function must be called', () => {
expect(workspaceLoader.getWorkspaceKey).toHaveBeenCalled();
expect(workspaceLoader.getWorkspace).toHaveBeenCalledWith("foo/bar");
});
it('handleWorkspace must not be called', () => {
expect(workspaceLoader.handleWorkspace).not.toHaveBeenCalled();
});
it('must open IDE with `test url`', () => {
expect(workspaceLoader.openURL).toHaveBeenCalledWith("test url");
});
});
describe('must open default IDE with query parameters when workspace does not have IDE server', () => {
let workspaceLoader;
beforeEach((done) => {
let loader = new Loader();
workspaceLoader = new WorkspaceLoader(loader);
spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue("foo/bar");
spyOn(workspaceLoader, 'getQueryString').and.returnValue("?param=value");
spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => {
return new Promise((resolve) => {
fakeWorkspaceConfig.config.environments["default"].machines = {};
resolve(fakeWorkspaceConfig);
});
});
spyOn(workspaceLoader, "handleWorkspace");
spyOn(workspaceLoader, "openURL").and.callFake(() => {
done();
});
workspaceLoader.load();
});
it('must open IDE with `test url` and query param `param=value`', () => {
expect(workspaceLoader.openURL).toHaveBeenCalledWith("test url?param=value");
});
});
describe('must handle workspace when it has IDE server', () => {
let workspaceLoader;
beforeEach((done) => {
let loader = new Loader();
workspaceLoader = new WorkspaceLoader(loader);
spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue("foo/bar");
spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => {
return new Promise((resolve) => {
resolve(fakeWorkspaceConfig);
});
});
spyOn(workspaceLoader, "handleWorkspace").and.callFake(() => {
done();
});
workspaceLoader.load();
});
it('basic workspace function must be called', () => {
expect(workspaceLoader.getWorkspaceKey).toHaveBeenCalled();
expect(workspaceLoader.getWorkspace).toHaveBeenCalledWith("foo/bar");
});
it('must be called', () => {
expect(workspaceLoader.handleWorkspace).toHaveBeenCalled();
});
});
describe('must open IDE for RUNNING workspace', () => {
let workspaceLoader;
beforeEach((done) => {
let loader = new Loader();
workspaceLoader = new WorkspaceLoader(loader);
spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue("foo/bar");
spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => {
return new Promise((resolve) => {
fakeWorkspaceConfig.status = 'RUNNING';
resolve(fakeWorkspaceConfig);
});
});
spyOn(workspaceLoader, "subscribeWorkspaceEvents");
spyOn(workspaceLoader, "openIDE").and.callFake(() => {
done();
});
workspaceLoader.load();
});
it('must not subscribe to events', () => {
expect(workspaceLoader.subscribeWorkspaceEvents).not.toHaveBeenCalled();
});
it('must open IDE immediately', () => {
expect(workspaceLoader.openIDE).toHaveBeenCalled();
});
});
describe('> must start STOPPED workspace', () => {
let workspaceLoader;
beforeEach((done) => {
let loader = new Loader();
workspaceLoader = new WorkspaceLoader(loader);
spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue("foo/bar");
spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => {
return new Promise((resolve) => {
fakeWorkspaceConfig.status = 'STOPPED';
resolve(fakeWorkspaceConfig);
});
});
spyOn(workspaceLoader, "subscribeWorkspaceEvents").and.callFake(() => {
return new Promise((resolve) => {
resolve();
});
});
spyOn(workspaceLoader, "startWorkspace").and.callFake(() => {
done();
});
spyOn(workspaceLoader, "openIDE");
workspaceLoader.load();
});
it('openIDE must not be called if status is STOPPED', () => {
expect(workspaceLoader.openIDE).not.toHaveBeenCalled();
});
it('must subscribe to events', () => {
expect(workspaceLoader.subscribeWorkspaceEvents).toHaveBeenCalled();
});
it('must start the workspace', () => {
expect(workspaceLoader.startWorkspace).toHaveBeenCalled();
});
it('openIDE must be called when workspace become RUNNING', () => {
workspaceLoader.onWorkspaceStatusChanged("RUNNING");
expect(workspaceLoader.openIDE).toHaveBeenCalled();
});
});
describe('must restart STOPPING workspace', () => {
let workspaceLoader;
beforeEach((done) => {
let loader = new Loader();
workspaceLoader = new WorkspaceLoader(loader);
spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue("foo/bar");
spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => {
return new Promise((resolve) => {
fakeWorkspaceConfig.status = 'STOPPING';
resolve(fakeWorkspaceConfig);
});
});
spyOn(workspaceLoader, "subscribeWorkspaceEvents").and.callFake(() => {
return new Promise((resolve) => {
resolve();
});
});
spyOn(workspaceLoader, "startWorkspace");
spyOn(workspaceLoader, "openIDE");
workspaceLoader.load().then(() => {
done();
});
});
it('must start the workspace after stopping', () => {
expect(workspaceLoader.startAfterStopping).toEqual(true);
});
it('must start workspace when workspace status become STOPPED', () => {
workspaceLoader.onWorkspaceStatusChanged("STOPPED");
expect(workspaceLoader.startWorkspace).toHaveBeenCalled();
expect(workspaceLoader.openIDE).not.toHaveBeenCalled();
});
it('must open IDE when workspace become RUNNING', () => {
workspaceLoader.onWorkspaceStatusChanged("RUNNING");
expect(workspaceLoader.openIDE).toHaveBeenCalled();
});
});
});

View File

@ -0,0 +1,14 @@
{
"include": [
"src"
],
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"lib": ["dom","es6"],
"sourceMap": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
}
}

View File

@ -0,0 +1,67 @@
{
"defaultSeverity": "error",
"rules": {
"file-header": [
true,
"[\n\r]+ \\* Copyright \\(c\\) \\d{4}(-\\d{4})? .*[\n\r]+"
],
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"eofline": true,
"forin": true,
"indent": [
true,
"spaces"
],
"max-line-length": [
true,
180
],
"no-consecutive-blank-lines": true,
"no-trailing-whitespace": true,
"no-var-keyword": true,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-whitespace"
],
"radix": true,
"semicolon": [
true,
"always",
"ignore-interfaces"
],
"trailing-comma": [
false
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
]
}
}

View File

@ -0,0 +1,33 @@
/*******************************************************************************
* Copyright (c) 2018-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*******************************************************************************/
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: './src/index.ts',
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/
},
]
},
resolve: {
extensions: ['.ts', '.js']
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'target/dist')
}
};

View File

@ -0,0 +1,51 @@
/*******************************************************************************
* Copyright (c) 2018-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*******************************************************************************/
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = merge(common, {
devtool: 'inline-source-map',
module:{
rules:[
{
test: /\.css$/,
use: [{
loader: "style-loader" // creates style nodes from JS strings
}, {
loader: "css-loader" // translates CSS into CommonJS
}]
}
]
},
devServer: {
contentBase: './dist',
port: 3000,
index: 'index.html',
historyApiFallback: true,
proxy: {
'/api/websocket': {
target: 'http://localhost:8080',
ws: true,
},
'/api/workspace': "http://localhost:8080",
}
},
plugins:[
new HtmlWebpackPlugin({
inject: false,
title: "Che Workspace Loader",
template:"src/index.html",
urlPrefix:"/"
})
]
});

View File

@ -0,0 +1,44 @@
/*******************************************************************************
* Copyright (c) 2018-2018 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*******************************************************************************/
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = merge(common, {
devtool: 'source-map',
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
}
],
},
plugins: [
new UglifyJSPlugin({
sourceMap: true
}),
new HtmlWebpackPlugin({
inject: false,
template: 'src/index.html',
title: 'Che Workspace Loader',
urlPrefix: '/workspace-loader/loader/',
cssName: 'style.css'
}),
new ExtractTextPlugin('style.css')
]
});