diff --git a/workspace-loader/package.json b/workspace-loader/package.json index 5c3c0cd8cee..57e5e72c078 100644 --- a/workspace-loader/package.json +++ b/workspace-loader/package.json @@ -7,7 +7,7 @@ "test": "jest", "test:watch": "jest --watch", "compile": "webpack --config webpack.prod.js", - "start": "webpack-dev-server --open --config webpack.dev.js", + "start": "webpack-dev-server --open --disable-host-check --config webpack.dev.js", "format": "tsfmt -r --useTsfmt tsfmt.json", "lint:fix": "tslint -c tslint.json --fix --project .", "build": "yarn run format && yarn run compile" diff --git a/workspace-loader/src/json-rpc/che-json-rpc-master-api.ts b/workspace-loader/src/json-rpc/che-json-rpc-master-api.ts index 77cc8af1dc3..9846395a192 100644 --- a/workspace-loader/src/json-rpc/che-json-rpc-master-api.ts +++ b/workspace-loader/src/json-rpc/che-json-rpc-master-api.ts @@ -16,7 +16,7 @@ import { CheJsonRpcApiClient } from './che-json-rpc-api-service'; import { ICommunicationClient, CODE_REQUEST_TIMEOUT, CommunicationClientEvent } from './json-rpc-client'; import { WorkspaceLoader } from '../workspace-loader'; -enum MasterChannels { +export enum MasterChannels { ENVIRONMENT_OUTPUT = 'runtime/log', ENVIRONMENT_STATUS = 'machine/statusChanged', INSTALLER_OUTPUT = 'installer/log', @@ -25,13 +25,6 @@ enum MasterChannels { const SUBSCRIBE: string = 'subscribe'; const UNSUBSCRIBE: string = 'unsubscribe'; -export interface WorkspaceStatusChangedEvent { - status: string; - prevStatus: string; - workspaceId: string; - error: string; -} - /** * Client API for workspace master interactions. * @@ -105,12 +98,12 @@ export class CheJsonRpcMasterApi { * * @returns {Promise} */ - connect(): Promise { + async connect(): Promise { const entryPointFunction = () => { const entryPoint = this.entryPoint + this.loader.getAuthenticationToken(); if (this.clientId) { let clientId = `clientId=${this.clientId}`; - // in case of reconnection + // in case of re-connection // we need to test entrypoint on existing query parameters // to add already gotten clientId if (/\?/.test(entryPoint) === false) { @@ -123,9 +116,8 @@ export class CheJsonRpcMasterApi { return entryPoint; }; - return this.cheJsonRpcApi.connect(entryPointFunction).then(() => - this.fetchClientId() - ); + await this.cheJsonRpcApi.connect(entryPointFunction); + return await this.fetchClientId(); } /** @@ -220,10 +212,9 @@ export class CheJsonRpcMasterApi { * * @returns {Promise} */ - fetchClientId(): Promise { - return this.cheJsonRpcApi.request('websocketIdService/getId').then((data: string[]) => { - this.clientId = data[0]; - }); + async fetchClientId(): Promise { + const data = await this.cheJsonRpcApi.request('websocketIdService/getId'); + this.clientId = data[0]; } /** diff --git a/workspace-loader/src/workspace-loader.ts b/workspace-loader/src/workspace-loader.ts index 06d4c151fb8..6d62dbdefb3 100644 --- a/workspace-loader/src/workspace-loader.ts +++ b/workspace-loader/src/workspace-loader.ts @@ -12,9 +12,10 @@ 'use strict'; import { WebsocketClient } from './json-rpc/websocket-client'; -import { CheJsonRpcMasterApi, WorkspaceStatusChangedEvent } from './json-rpc/che-json-rpc-master-api'; +import { CheJsonRpcMasterApi } from './json-rpc/che-json-rpc-master-api'; import { Loader } from './loader/loader'; import { che } from '@eclipse-che/api'; +import { Deferred } from './json-rpc/util'; // tslint:disable:no-any @@ -22,13 +23,17 @@ const WEBSOCKET_CONTEXT = '/api/websocket'; export class WorkspaceLoader { - workspace: che.workspace.Workspace; - startAfterStopping = false; + private workspace: che.workspace.Workspace; + private runtimeIsAccessible: Deferred; - constructor(private readonly loader: Loader, - private readonly keycloak?: any) { + constructor( + private readonly loader: Loader, + private readonly keycloak?: any + ) { /** Ask dashboard to show the IDE. */ window.parent.postMessage('show-ide', '*'); + + this.runtimeIsAccessible = new Deferred(); } async load(): Promise { @@ -45,7 +50,6 @@ export class WorkspaceLoader { await this.openIDE(); } catch (err) { if (err) { - console.error(err); this.loader.error(err); } else { this.loader.error('Unknown error has happened, try to reload page'); @@ -56,7 +60,7 @@ export class WorkspaceLoader { } /** - * Returns workspace key from current address or empty string when it is undefined. + * Returns workspace key from current location or empty string when it is undefined. */ getWorkspaceKey(): string { const result: string = window.location.pathname.substr(1); @@ -83,47 +87,49 @@ export class WorkspaceLoader { * * @param workspaceId workspace id */ - getWorkspace(workspaceId: string): Promise { + async getWorkspace(workspaceId: string): Promise { const request = new XMLHttpRequest(); request.open('GET', '/api/workspace/' + workspaceId); - return this.setAuthorizationHeader(request).then((xhr: XMLHttpRequest) => - new Promise((resolve, reject) => { - 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)); - }; - })); + const requestWithAuth = await this.setAuthorizationHeader(request); + return new Promise((resolve, reject) => { + requestWithAuth.send(); + requestWithAuth.onreadystatechange = () => { + if (requestWithAuth.readyState !== 4) { + return; + } + if (requestWithAuth.status !== 200) { + reject(new Error(`Failed to get the workspace: "${this.getRequestErrorMessage(requestWithAuth)}"`)); + return; + } + resolve(JSON.parse(requestWithAuth.responseText)); + }; + }); } /** * Start current workspace. */ - startWorkspace(): Promise { + async startWorkspace(): Promise { const request = new XMLHttpRequest(); request.open('POST', `/api/workspace/${this.workspace.id}/runtime`); - return this.setAuthorizationHeader(request).then((xhr: XMLHttpRequest) => - new Promise((resolve, reject) => { - xhr.send(); - xhr.onreadystatechange = () => { - if (xhr.readyState !== 4) { return; } - if (xhr.status !== 200) { - const errorMessage = 'Failed to start the workspace: "' + this.getRequestErrorMessage(xhr) + '"'; - reject(new Error(errorMessage)); - return; - } - resolve(JSON.parse(xhr.responseText)); - }; - })); + const requestWithAuth = await this.setAuthorizationHeader(request); + return new Promise((resolve, reject) => { + requestWithAuth.send(); + requestWithAuth.onreadystatechange = () => { + if (requestWithAuth.readyState !== 4) { + return; + } + if (requestWithAuth.status !== 200) { + reject(new Error(`Failed to start the workspace: "${this.getRequestErrorMessage(requestWithAuth)}"`)); + return; + } + resolve(JSON.parse(requestWithAuth.responseText)); + }; + }); } - getRequestErrorMessage(xhr: XMLHttpRequest): string { - let errorMessage; + private getRequestErrorMessage(xhr: XMLHttpRequest): string { + let errorMessage: string; try { const response = JSON.parse(xhr.responseText); errorMessage = response.message; @@ -143,118 +149,129 @@ export class WorkspaceLoader { /** * Handles workspace status. */ - handleWorkspace(): Promise { + private async handleWorkspace(): Promise { if (this.workspace.status === 'RUNNING') { - return new Promise((resolve, reject) => { - this.checkWorkspaceRuntime().then(resolve, reject); - }); - } else if (this.workspace.status === 'STOPPING') { - this.startAfterStopping = true; + return await this.checkWorkspaceRuntime(); } - const masterApiConnectionPromise = new Promise((resolve, reject) => { - if (this.workspace.status === 'STOPPED') { - this.startWorkspace().then(resolve, reject); - } else { - resolve(); - } - }).then(() => this.connectMasterApi()); + const masterApi = await this.connectMasterApi(); + this.subscribeWorkspaceEvents(masterApi); + masterApi.addListener('open', async () => { + try { + await this.checkWorkspaceRuntime(); + this.runtimeIsAccessible.resolve(); + } catch (e) { } + }); - const runningOnConnectionPromise = masterApiConnectionPromise - .then((masterApi: CheJsonRpcMasterApi) => - new Promise((resolve, reject) => { - masterApi.addListener('open', () => { - this.checkWorkspaceRuntime().then(resolve, reject); - }); - })); + if (this.workspace.status === 'STOPPED') { + try { + await this.startWorkspace(); + } catch (e) { + this.runtimeIsAccessible.reject(e); + } + } - const runningOnStatusChangePromise = masterApiConnectionPromise - .then((masterApi: CheJsonRpcMasterApi) => - this.subscribeWorkspaceEvents(masterApi)); + return this.runtimeIsAccessible.promise; + } - return Promise.race([runningOnConnectionPromise, runningOnStatusChangePromise]); + /** + * Shows environment and installer outputs. + * + * @param message + */ + private onLogOutput(message: che.workspace.event.RuntimeLogEvent | che.workspace.event.InstallerLogEvent): void { + this.loader.log(message.text); } /** - * Shows environment outputs. + * Resolves deferred when workspace is running and runtime is accessible. * - * @param message output message + * @param message */ - onEnvironmentOutput(message): void { - this.loader.log(message); + private async onWorkspaceStatus(message: che.workspace.event.WorkspaceStatusEvent): Promise { + if (message.error) { + this.runtimeIsAccessible.reject(new Error(`Failed to run the workspace: "${message.error}"`)); + return; + } + + if (message.status === 'RUNNING') { + try { + await this.checkWorkspaceRuntime(); + this.runtimeIsAccessible.resolve(); + } catch (e) { + this.runtimeIsAccessible.reject(e); + } + return; + } + + if (message.status === 'STOPPED') { + if (message.prevStatus === 'STARTING') { + this.loader.error('Workspace stopped.'); + this.runtimeIsAccessible.reject('Workspace stopped.'); + } + if (message.prevStatus === 'STOPPING') { + try { + await this.startWorkspace(); + } catch (e) { + this.runtimeIsAccessible.reject(e); + } + } + } } - connectMasterApi(): Promise { - return new Promise((resolve, reject) => { - const entryPoint = this.websocketBaseURL() + WEBSOCKET_CONTEXT; - const master = new CheJsonRpcMasterApi(new WebsocketClient(), entryPoint, this); - master.connect() - .then(() => resolve(master)) - .catch((error: any) => reject(error)); - }); + private async connectMasterApi(): Promise { + const entryPoint = this.websocketBaseURL() + WEBSOCKET_CONTEXT; + const master = new CheJsonRpcMasterApi(new WebsocketClient(), entryPoint, this); + await master.connect(); + return master; } /** * Subscribes to the workspace events. */ - subscribeWorkspaceEvents(masterApi: CheJsonRpcMasterApi): Promise { - return new Promise((resolve, reject) => { - masterApi.subscribeEnvironmentOutput(this.workspace.id, - (message: any) => this.onEnvironmentOutput(message.text)); - masterApi.subscribeInstallerOutput(this.workspace.id, - (message: any) => this.onEnvironmentOutput(message.text)); - masterApi.subscribeWorkspaceStatus(this.workspace.id, - (message: WorkspaceStatusChangedEvent) => { - if (message.error) { - reject(new Error(`Failed to run the workspace: "${message.error}"`)); - } else if (message.status === 'RUNNING') { - this.checkWorkspaceRuntime().then(resolve, reject); - } else if (message.status === 'STOPPED') { - if (message.prevStatus === 'STARTING') { - this.loader.error('Workspace stopped.'); - this.loader.hideLoader(); - this.loader.showReload(); - } - if (this.startAfterStopping) { - this.startWorkspace().catch((error: any) => reject(error)); - } - } - }); - }); + private subscribeWorkspaceEvents(masterApi: CheJsonRpcMasterApi): void { + masterApi.subscribeEnvironmentOutput( + this.workspace.id, + (message: che.workspace.event.RuntimeLogEvent) => this.onLogOutput(message) + ); + masterApi.subscribeInstallerOutput( + this.workspace.id, + (message: che.workspace.event.InstallerLogEvent) => this.onLogOutput(message) + ); + masterApi.subscribeWorkspaceStatus( + this.workspace.id, + (message: che.workspace.event.WorkspaceStatusEvent) => this.onWorkspaceStatus(message) + ); } - checkWorkspaceRuntime(): Promise { - return new Promise((resolve, reject) => { - this.getWorkspace(this.workspace.id).then(workspace => { - if (workspace.status === 'RUNNING') { - if (workspace.runtime) { - resolve(); - } else { - reject(new Error('You do not have permissions to access workspace runtime, in this case IDE cannot be loaded.')); - } - } - }); - }); + private async checkWorkspaceRuntime(): Promise { + const workspace = await this.getWorkspace(this.workspace.id); + + if (workspace.status !== 'RUNNING') { + throw new Error('Workspace is NOT RUNNING yet.'); + } + if (!workspace.runtime) { + throw new Error('You do not have permissions to access workspace runtime, in this case IDE cannot be loaded.'); + } } /** * Opens IDE for the workspace. */ - openIDE(): void { - this.getWorkspace(this.workspace.id).then(workspace => { - const machines = workspace.runtime.machines || []; - for (const machineName of Object.keys(machines)) { - const servers = machines[machineName].servers || []; - for (const serverId of Object.keys(servers)) { - const attributes = servers[serverId].attributes; - if (attributes['type'] === 'ide') { - this.openURL(servers[serverId].url + this.getQueryString()); - return; - } + async openIDE(): Promise { + const workspace = await this.getWorkspace(this.workspace.id); + const machines = workspace.runtime.machines || []; + for (const machineName of Object.keys(machines)) { + const servers = machines[machineName].servers || []; + for (const serverId of Object.keys(servers)) { + const attributes = servers[serverId].attributes; + if (attributes['type'] === 'ide') { + this.openURL(servers[serverId].url + this.getQueryString()); + return; } } - this.openURL(workspace.links.ide + this.getQueryString()); - }); + } + this.openURL(workspace.links.ide + this.getQueryString()); } /** @@ -278,7 +295,6 @@ export class WorkspaceLoader { xhr.setRequestHeader('Authorization', 'Bearer ' + this.keycloak.token); resolve(xhr); }).error(() => { - console.log('Failed to refresh token'); window.sessionStorage.setItem('oidcIdeRedirectUrl', location.href); this.keycloak.login(); reject(new Error('Failed to refresh token')); diff --git a/workspace-loader/test/test.spec.ts b/workspace-loader/test/test.spec.ts index 1fa07a39e53..44db4ec7e07 100644 --- a/workspace-loader/test/test.spec.ts +++ b/workspace-loader/test/test.spec.ts @@ -20,12 +20,11 @@ import { che } from '@eclipse-che/api'; // tslint:disable:no-any -describe('Workspace Loader', () => { - - let fakeWorkspaceConfig: che.workspace.Workspace; - - beforeEach(function () { - document.body.innerHTML = `
+let loader: Loader; +let workspaceLoader: WorkspaceLoader; +let workspaceConfig: che.workspace.Workspace; +beforeEach(function () { + document.body.innerHTML = `
Loading...
@@ -38,571 +37,547 @@ describe('Workspace Loader', () => {
`; - fakeWorkspaceConfig = { - status: 'STOPPED', - links: { - ide: 'test url' - }, - config: { - defaultEnv: 'default', - 'environments': { - 'default': { - machines: { - machine: { - servers: { - server1: { - attributes: { - type: 'ide' - }, - port: '0', - protocol: '' - } + workspaceConfig = { + status: 'STOPPED', + links: { + ide: 'test url' + }, + config: { + defaultEnv: 'default', + environments: { + default: { + machines: { + machine: { + servers: { + server1: { + attributes: { + type: 'ide' + }, + port: '0', + protocol: '' } - }, + } }, - recipe: { - type: '' - } + }, + recipe: { + type: '' } } } - } as che.workspace.Workspace; - }); + } + } as che.workspace.Workspace; - it('should have "workspace-loader" in DOM', () => { + loader = new Loader(); + workspaceLoader = new WorkspaceLoader(loader); +}); + +describe('workspace-loader', () => { + + it('should have "#workspace-loader" element in DOM', () => { const loaderElement = document.getElementById('workspace-loader'); expect(loaderElement).toBeTruthy(); }); +}); - it('should not get a workspace if workspace key is not specified', () => { - const loader = new Loader(); - const workspaceLoader = new WorkspaceLoader(loader); +describe('If workspace key is not specified then workspace-loader', () => { + it('should not get a workspace', done => { spyOn(workspaceLoader, 'getWorkspaceKey'); spyOn(workspaceLoader, 'getWorkspace'); - workspaceLoader.load(); + workspaceLoader.load().then(done); expect(workspaceLoader.getWorkspaceKey).toHaveBeenCalled(); expect(workspaceLoader.getWorkspace).not.toHaveBeenCalled(); }); - it('should get a workspace by its workspace key', () => { - const loader = new Loader(); - const workspaceLoader = new WorkspaceLoader(loader); +}); + +describe('If workspace key is specified then workspace-loader', () => { + it('should get a workspace', done => { spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue('foo/bar'); - spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => new Promise(resolve => { - resolve(fakeWorkspaceConfig); + resolve(workspaceConfig); })); - workspaceLoader.load(); + workspaceLoader.load().then(done); expect(workspaceLoader.getWorkspaceKey).toHaveBeenCalled(); expect(workspaceLoader.getWorkspace).toHaveBeenCalledWith('foo/bar'); }); - describe('if workspace has a preconfigured IDE with query parameters', () => { - const ideURL = 'ide URL'; - let workspaceLoader: WorkspaceLoader; - - beforeEach(done => { - const 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(() => - new Promise(resolve => { - fakeWorkspaceConfig.status = 'RUNNING'; - fakeWorkspaceConfig.runtime = { machines: { ide: { servers: { server1: { attributes: { type: 'ide' }, url: ideURL } } } } } as any; - resolve(fakeWorkspaceConfig); - })); +describe('If workspace has a pre-configured IDE with query parameters then workspace-loader', () => { + const ideURL = 'ide URL'; - spyOn(workspaceLoader, 'openIDE').and.callThrough(); - spyOn(workspaceLoader, 'openURL'); - workspaceLoader.load().then(done); - }); + beforeEach(done => { + spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue('foo/bar'); + spyOn(workspaceLoader, 'getQueryString').and.returnValue('?param=value'); + + workspaceConfig.status = 'RUNNING'; + workspaceConfig.runtime = { + machines: { + ide: { + servers: { + server1: { + attributes: { type: 'ide' }, + url: ideURL + } + } + } + } + } as che.workspace.Runtime; + spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => + new Promise(resolve => { + resolve(workspaceConfig); + })); - it('should call openURL method with correct parameter', () => { - expect(workspaceLoader.openURL).toHaveBeenCalledWith(ideURL + '?param=value'); - }); + spyOn(workspaceLoader, 'openIDE').and.callThrough(); + spyOn(workspaceLoader, 'openURL'); + workspaceLoader.load().then(done); }); - describe('if workspace does not have an IDE server', () => { - let workspaceLoader: WorkspaceLoader; - - beforeEach(done => { - const loader = new Loader(); - workspaceLoader = new WorkspaceLoader(loader); - - spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue('foo/bar'); - spyOn(workspaceLoader, 'getQueryString').and.returnValue(''); - - spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => - new Promise(resolve => { - fakeWorkspaceConfig.status = 'RUNNING'; - fakeWorkspaceConfig.config.environments['default'].machines = {}; - fakeWorkspaceConfig.runtime = {} as che.workspace.Runtime; - resolve(fakeWorkspaceConfig); - })); - - spyOn(workspaceLoader, 'openIDE').and.callThrough(); - spyOn(workspaceLoader, 'openURL'); - workspaceLoader.load().then(done); - }); - - it('should open IDE directly', () => { - expect(workspaceLoader.openURL).toHaveBeenCalledWith(fakeWorkspaceConfig.links.ide); - }); + it('should call openURL method with correct parameter', () => { + expect(workspaceLoader.openURL).toHaveBeenCalledWith(ideURL + '?param=value'); }); - describe('if workspace is RUNNING', () => { - let workspaceLoader: WorkspaceLoader; - - beforeEach(() => { - const loader = new Loader(); - workspaceLoader = new WorkspaceLoader(loader); - - spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue('foo/bar'); - - spyOn(workspaceLoader, 'connectMasterApi'); - - spyOn(workspaceLoader, 'subscribeWorkspaceEvents'); - - spyOn(workspaceLoader, 'openIDE'); - }); +}); - describe('and user owns the workspace or has been granted permissions for shared workspace', () => { +describe('If an IDE server is not defined in workspace config then workspace-loader', () => { - beforeEach(done => { - spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => - new Promise(resolve => { - fakeWorkspaceConfig.status = 'RUNNING'; - fakeWorkspaceConfig.runtime = {} as che.workspace.Runtime; - resolve(fakeWorkspaceConfig); - })); + beforeEach(done => { + spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue('foo/bar'); + spyOn(workspaceLoader, 'getQueryString').and.returnValue(''); + spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => + new Promise(resolve => { + workspaceConfig.status = 'RUNNING'; + workspaceConfig.config.environments['default'].machines = {}; + workspaceConfig.runtime = {} as che.workspace.Runtime; + resolve(workspaceConfig); + })); - workspaceLoader.load().then(done); - }); + spyOn(workspaceLoader, 'openIDE').and.callThrough(); + spyOn(workspaceLoader, 'openURL'); + workspaceLoader.load().then(done); + }); - it('should not connect to workspace master API', () => { - expect(workspaceLoader.connectMasterApi).not.toHaveBeenCalled(); - }); + it('should open IDE directly', () => { + expect(workspaceLoader.openURL).toHaveBeenCalledWith(workspaceConfig.links.ide); + }); +}); - it('should not subscribe to workspace events', () => { - expect(workspaceLoader.subscribeWorkspaceEvents).not.toHaveBeenCalled(); - }); +describe('If workspace status is STOPPING then workspace-loader', () => { - it('should open IDE immediately', () => { - expect(workspaceLoader.openIDE).toHaveBeenCalled(); + beforeEach(done => { + spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue('foo/bar'); + spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => + new Promise(resolve => { + workspaceConfig.status = 'STOPPING'; + resolve(workspaceConfig); + })); + spyOn(workspaceLoader, 'subscribeWorkspaceEvents').and.callThrough(); + spyOn(workspaceLoader, 'startWorkspace').and.callFake(() => Promise.resolve()); + spyOn(workspaceLoader, 'connectMasterApi').and.callFake(() => { + done(); + return Promise.resolve({ + addListener: () => { }, + subscribeEnvironmentOutput: () => { }, + subscribeInstallerOutput: () => { }, + subscribeWorkspaceStatus: () => { } }); - }); + spyOn(workspaceLoader, 'openIDE').and.callFake(() => Promise.resolve()); - describe('and user hasn\'t been granted permissions for shared workspace', () => { - - beforeEach(done => { - spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => - new Promise(resolve => { - fakeWorkspaceConfig.status = 'RUNNING'; - resolve(fakeWorkspaceConfig); - })); + workspaceLoader.load(); + }); - workspaceLoader.load().then(done); - }); + it('should not open an IDE', () => { + expect(workspaceLoader.openIDE).not.toHaveBeenCalled(); + }); - it('should not connect to workspace master API', () => { - expect(workspaceLoader.connectMasterApi).not.toHaveBeenCalled(); - }); +}); - it('should not subscribe to workspace events', () => { - expect(workspaceLoader.subscribeWorkspaceEvents).not.toHaveBeenCalled(); - }); +describe('If workspace status is STOPPED then workspace-loader', () => { - it('should not open an IDE', () => { - expect(workspaceLoader.openIDE).not.toHaveBeenCalled(); - }); + beforeEach(done => { + spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue('foo/bar'); + spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => + new Promise(resolve => { + workspaceConfig.status = 'STOPPED'; + resolve(workspaceConfig); + })); + spyOn((workspaceLoader), 'subscribeWorkspaceEvents').and.callThrough(); + spyOn(workspaceLoader, 'startWorkspace').and.callFake(() => { + done(); + return Promise.resolve(); }); - }); + spyOn((workspaceLoader), 'connectMasterApi').and.callFake(() => + Promise.resolve({ + addListener: () => { }, + subscribeEnvironmentOutput: () => { }, + subscribeInstallerOutput: () => { }, + subscribeWorkspaceStatus: () => { } + })); + spyOn(workspaceLoader, 'openIDE').and.callFake(() => Promise.resolve()); - describe('if workspace is STOPPED and then starts successfully', () => { - let workspaceLoader: WorkspaceLoader; - let statusChangeCallback: Function; - let workspaceLoadPromise: Promise; + workspaceLoader.load(); + }); - beforeEach(done => { - const loader = new Loader(); - workspaceLoader = new WorkspaceLoader(loader); + it('should not open an IDE', () => { + expect(workspaceLoader.openIDE).not.toHaveBeenCalled(); + }); - spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue('foo/bar'); + it('should subscribe to workspace events', () => { + expect((workspaceLoader).subscribeWorkspaceEvents).toHaveBeenCalled(); + }); - spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => - new Promise(resolve => { - fakeWorkspaceConfig.status = 'STOPPED'; - resolve(fakeWorkspaceConfig); - })); + it('should start the workspace', () => { + expect(workspaceLoader.startWorkspace).toHaveBeenCalled(); + }); - spyOn(workspaceLoader, 'subscribeWorkspaceEvents').and.callThrough(); +}); - spyOn(workspaceLoader, 'startWorkspace').and.callFake(() => Promise.resolve()); +describe('If workspace status is changed from STOPPING to STOPPED then workspace-loader', () => { + let statusChangeCallback: (event: che.workspace.event.WorkspaceStatusEvent) => {}; + const statusStoppedEvent: che.workspace.event.WorkspaceStatusEvent = { + status: 'STOPPED', + prevStatus: 'STOPPING' + }; - spyOn(workspaceLoader, 'connectMasterApi').and.callFake(() => { - done(); - return Promise.resolve({ - addListener: () => { }, - subscribeEnvironmentOutput: () => { }, - subscribeInstallerOutput: () => { }, - subscribeWorkspaceStatus: (_workspaceId, callback) => { - statusChangeCallback = callback; - } - }); + beforeEach(done => { + spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue('foo/bar'); + spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => + new Promise(resolve => { + workspaceConfig.status = 'STOPPING'; + resolve(workspaceConfig); + })); + spyOn(workspaceLoader, 'subscribeWorkspaceEvents').and.callThrough(); + spyOn(workspaceLoader, 'startWorkspace').and.callFake(() => Promise.resolve()); + spyOn(workspaceLoader, 'connectMasterApi').and.callFake(() => { + done(); + return Promise.resolve({ + addListener: () => { }, + subscribeEnvironmentOutput: () => { }, + subscribeInstallerOutput: () => { }, + subscribeWorkspaceStatus: (_workspaceId, callback) => { + statusChangeCallback = callback; + } }); - - spyOn(workspaceLoader, 'openIDE').and.callFake(() => Promise.resolve()); - - workspaceLoadPromise = workspaceLoader.load(); }); + spyOn(workspaceLoader, 'openIDE').and.callFake(() => Promise.resolve()); - it('should not open an IDE', () => { - expect(workspaceLoader.openIDE).not.toHaveBeenCalled(); - }); + workspaceLoader.load(); + }); - it('should subscribe to workspace events', () => { - expect(workspaceLoader.subscribeWorkspaceEvents).toHaveBeenCalled(); - }); + beforeEach(() => { + spyOn((workspaceLoader), 'onWorkspaceStatus').and.callThrough(); - it('should start the workspace', () => { - expect(workspaceLoader.startWorkspace).toHaveBeenCalled(); - }); + statusChangeCallback(statusStoppedEvent); + }); - describe('then becomes STARTING', () => { + it('should not open an IDE', () => { + expect(workspaceLoader.openIDE).not.toHaveBeenCalled(); + }); - beforeEach(() => { - statusChangeCallback({ status: 'STARTING' }); - }); + it('should handle workspace status change', () => { + expect((workspaceLoader).onWorkspaceStatus).toHaveBeenCalledWith(statusStoppedEvent); + }); - it('should not open an IDE', () => { - expect(workspaceLoader.openIDE).not.toHaveBeenCalled(); - }); + it('should start a workspace', () => { + expect(workspaceLoader.startWorkspace).toHaveBeenCalled(); + }); - describe('then becomes RUNNING', () => { +}); - describe('and user owns workspace or has been granted permissions for shared workspace', () => { +describe('If workspace status is STARTING then workspace-loader', () => { - beforeEach(done => { - (workspaceLoader.getWorkspace as any).and.callFake(() => - new Promise(resolve => { - fakeWorkspaceConfig.status = 'RUNNING'; - fakeWorkspaceConfig.runtime = {} as che.workspace.Runtime; - resolve(fakeWorkspaceConfig); - })); + beforeEach(done => { + spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue('foo/bar'); + spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => + new Promise(resolve => { + workspaceConfig.status = 'STARTING'; + resolve(workspaceConfig); + })); + spyOn(workspaceLoader, 'subscribeWorkspaceEvents').and.callThrough(); + spyOn(workspaceLoader, 'startWorkspace').and.callFake(() => Promise.resolve()); + spyOn(workspaceLoader, 'connectMasterApi').and.callFake(() => { + done(); + return Promise.resolve({ + addListener: () => { }, + subscribeEnvironmentOutput: () => { }, + subscribeInstallerOutput: () => { }, + subscribeWorkspaceStatus: () => { } + }); + }); + spyOn(workspaceLoader, 'openIDE').and.callFake(() => Promise.resolve()); - statusChangeCallback({ status: 'RUNNING' }); + workspaceLoader.load(); + }); - workspaceLoadPromise.then(done); - }); + it('should not open an IDE', () => { + expect(workspaceLoader.openIDE).not.toHaveBeenCalled(); + }); - it('should open an IDE', () => { - expect(workspaceLoader.openIDE).toHaveBeenCalled(); - }); +}); - }); +describe('If websocket connection to master API is established when workspace is not in RUNNING state then workspace-loader', () => { + let onOpenCallback: Function; - describe('and user hasn\'t been granted permissions for shared workspace', () => { + beforeEach(done => { + spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue('foo/bar'); + spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => + new Promise(resolve => { + workspaceConfig.status = 'STOPPED'; + resolve(workspaceConfig); + })); - beforeEach(done => { - (workspaceLoader.getWorkspace as any).and.callFake(() => - new Promise(resolve => { - fakeWorkspaceConfig.status = 'RUNNING'; - resolve(fakeWorkspaceConfig); - })); + spyOn((workspaceLoader), 'subscribeWorkspaceEvents').and.callThrough(); + spyOn(workspaceLoader, 'startWorkspace').and.callFake(() => { + done(); + return Promise.resolve(); + }); + spyOn((workspaceLoader), 'connectMasterApi').and.callFake(() => + Promise.resolve({ + addListener: (_eventName, callback) => { + onOpenCallback = callback; + }, + subscribeEnvironmentOutput: () => { }, + subscribeInstallerOutput: () => { }, + subscribeWorkspaceStatus: () => { } + })); + spyOn(workspaceLoader, 'openIDE').and.callFake(() => Promise.resolve()); - statusChangeCallback({ status: 'RUNNING' }); + workspaceLoader.load(); + }); - workspaceLoadPromise.then(done); - }); + beforeEach(done => { + onOpenCallback(); + setTimeout(done, 1000); + }); - it('should not open an IDE', () => { - expect(workspaceLoader.openIDE).not.toHaveBeenCalled(); - }); + it('should not open an IDE', () => { + expect(workspaceLoader.openIDE).not.toHaveBeenCalled(); + }); + +}); - testLoaderIsHidden(); - testProgressBarIsHidden(); - testPromptIsShown(); - }); +describe('If workspace status is changed to RUNNING then workspace-loader', () => { + let statusChangeCallback: (event: che.workspace.event.WorkspaceStatusEvent) => {}; + let workspaceLoaderPromise: Promise; + beforeEach(done => { + spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue('foo/bar'); + spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => + new Promise(resolve => { + workspaceConfig.status = 'STARTING'; + resolve(workspaceConfig); + })); + spyOn((workspaceLoader), 'connectMasterApi').and.callFake(() => { + done(); + return Promise.resolve({ + addListener: () => { }, + subscribeEnvironmentOutput: () => { }, + subscribeInstallerOutput: () => { }, + subscribeWorkspaceStatus: (_workspaceId, callback) => { + statusChangeCallback = callback; + } }); + }); + spyOn(workspaceLoader, 'openIDE').and.callFake(() => Promise.resolve()); - describe('then receives an error on websocket', () => { + workspaceLoaderPromise = workspaceLoader.load(); + }); - beforeEach(done => { - statusChangeCallback({ error: 'Something bad happened.' }); + beforeEach(done => { + spyOn((workspaceLoader), 'onWorkspaceStatus').and.callThrough(); - workspaceLoadPromise.then(done); - }); + (workspaceLoader.getWorkspace as any).and.callFake(() => + new Promise(resolve => { + workspaceConfig.status = 'RUNNING'; + workspaceConfig.runtime = {} as che.workspace.Runtime; + resolve(workspaceConfig); + })); - it('should not open an IDE', () => { - expect(workspaceLoader.openIDE).not.toHaveBeenCalled(); - }); + statusChangeCallback({ status: 'RUNNING' }); + workspaceLoaderPromise.then(done); + }); - testLoaderIsHidden(); - testProgressBarIsHidden(); - testPromptIsShown(); + it('should handle workspace status change', () => { + expect((workspaceLoader).onWorkspaceStatus).toHaveBeenCalledWith({ status: 'RUNNING' }); + }); - }); + it('should open an IDE', () => { + expect(workspaceLoader.openIDE).toHaveBeenCalled(); + }); - }); +}); +describe('If workspace status is RUNNING', () => { + + beforeEach(() => { + spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue('foo/bar'); + spyOn((workspaceLoader), 'connectMasterApi'); + spyOn((workspaceLoader), 'subscribeWorkspaceEvents'); + spyOn(workspaceLoader, 'openIDE'); }); - describe('if workspace is STOPPED and then fails to start', () => { - let workspaceLoader: WorkspaceLoader; - let workspaceLoadPromise: Promise; + describe('and user owns the workspace then workspace-loader', () => { beforeEach(done => { - const loader = new Loader(); - workspaceLoader = new WorkspaceLoader(loader); - - spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue('foo/bar'); - spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => new Promise(resolve => { - fakeWorkspaceConfig.status = 'STOPPED'; - resolve(fakeWorkspaceConfig); + workspaceConfig.status = 'RUNNING'; + workspaceConfig.runtime = {} as che.workspace.Runtime; + resolve(workspaceConfig); })); - spyOn(workspaceLoader, 'connectMasterApi').and.callFake(() => - Promise.resolve({ - addListener: () => { }, - subscribeEnvironmentOutput: () => { }, - subscribeInstallerOutput: () => { }, - subscribeWorkspaceStatus: () => { } - })); - - spyOn(workspaceLoader, 'subscribeWorkspaceEvents').and.callThrough(); - - spyOn(workspaceLoader, 'startWorkspace').and.callFake(() => { - done(); - return Promise.reject(); - }); - - spyOn(workspaceLoader, 'openIDE').and.callFake(() => Promise.resolve()); - - workspaceLoadPromise = workspaceLoader.load(); - }); - - it('should not open an IDE immediately', () => { - expect(workspaceLoader.openIDE).not.toHaveBeenCalled(); + workspaceLoader.load().then(done); }); - it('should start the workspace', () => { - expect(workspaceLoader.startWorkspace).toHaveBeenCalled(); + it('should not connect to workspace master API', () => { + expect((workspaceLoader).connectMasterApi).not.toHaveBeenCalled(); }); it('should not subscribe to workspace events', () => { - expect(workspaceLoader.subscribeWorkspaceEvents).not.toHaveBeenCalled(); + expect((workspaceLoader).subscribeWorkspaceEvents).not.toHaveBeenCalled(); }); - it('should not open an IDE', () => { - expect(workspaceLoader.openIDE).not.toHaveBeenCalled(); - }); - - describe('then the request for starting the workspace fails', () => { - - beforeEach(done => { - workspaceLoadPromise.then(done); - }); - - testLoaderIsHidden(); - testProgressBarIsHidden(); - testPromptIsShown(); + it('should open IDE immediately', () => { + expect(workspaceLoader.openIDE).toHaveBeenCalled(); }); }); - describe('show error if workspace became stopped on starting', () => { - let workspaceLoader: WorkspaceLoader; - const loader = { - log: () => undefined, - hideLoader: () => { }, - showReload: () => { }, - error: () => { }, - onclickConsole: () => { }, - onclickReload: () => true - }; - let statusChangeCallback: Function; + describe('and user has not been granted permissions for shared workspace then workspace-loader', () => { beforeEach(done => { - spyOn(loader, 'error').and.callThrough(); - spyOn(loader, 'log').and.callThrough(); - spyOn(loader, 'hideLoader').and.callThrough(); - spyOn(loader, 'showReload').and.callThrough(); - - workspaceLoader = new WorkspaceLoader(loader); - - spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue('foo/bar'); - spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => new Promise(resolve => { - fakeWorkspaceConfig.status = 'STARTING'; - resolve(fakeWorkspaceConfig); + workspaceConfig.status = 'RUNNING'; + resolve(workspaceConfig); })); - spyOn(workspaceLoader, 'subscribeWorkspaceEvents').and.callThrough(); - - spyOn(workspaceLoader, 'startWorkspace').and.callFake(() => Promise.resolve()); - - spyOn(workspaceLoader, 'connectMasterApi').and.callFake(() => { - done(); - return Promise.resolve({ - addListener: () => { }, - subscribeEnvironmentOutput: () => { }, - subscribeInstallerOutput: () => { }, - subscribeWorkspaceStatus: (_workspaceId, callback) => { - statusChangeCallback = callback; - } - }); - }); - - spyOn(workspaceLoader, 'openIDE').and.callFake(() => Promise.resolve()); - - workspaceLoader.load(); - }); - - it('should not open IDE', () => { - expect(workspaceLoader.openIDE).not.toHaveBeenCalled(); + workspaceLoader.load().then(done); }); - it('should subscribe to workspace events', () => { - expect(workspaceLoader.subscribeWorkspaceEvents).toHaveBeenCalled(); + it('should not connect to workspace master API', () => { + expect((workspaceLoader).connectMasterApi).not.toHaveBeenCalled(); }); - it('should not start the workspace', () => { - expect(workspaceLoader.startWorkspace).not.toHaveBeenCalled(); + it('should not subscribe to workspace events', () => { + expect((workspaceLoader).subscribeWorkspaceEvents).not.toHaveBeenCalled(); }); - it('should not log something', () => { - expect(loader.log).not.toHaveBeenCalled(); + it('should not open an IDE', () => { + expect(workspaceLoader.openIDE).not.toHaveBeenCalled(); }); - describe('then receives workspace stopped event on websocket, when workspace starting', () => { - beforeEach(() => { - statusChangeCallback({ status: 'STOPPED', prevStatus: 'STARTING', workspaceId: 'someID-bla-bla' }); - }); + }); +}); - it('should not open an IDE', () => { - expect(workspaceLoader.openIDE).not.toHaveBeenCalled(); - }); +describe('If workspace fails to start then workspace-loader', () => { - it('should log error', () => { - expect(loader.error).toBeCalledWith('Workspace stopped.'); - }); + beforeEach(done => { + spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue('foo/bar'); + spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => + new Promise(resolve => { + workspaceConfig.status = 'STOPPED'; + resolve(workspaceConfig); + })); + spyOn((workspaceLoader), 'connectMasterApi').and.callFake(() => + Promise.resolve({ + addListener: () => { }, + subscribeEnvironmentOutput: () => { }, + subscribeInstallerOutput: () => { }, + subscribeWorkspaceStatus: () => { } + })); + spyOn((workspaceLoader), 'subscribeWorkspaceEvents').and.callThrough(); + spyOn(workspaceLoader, 'startWorkspace').and.callFake(() => Promise.reject('Failed to start the workspace')); + spyOn(workspaceLoader, 'openIDE').and.callFake(() => Promise.resolve()); - it('should hide loader', () => { - expect(loader.hideLoader).toHaveBeenCalled(); - }); + spyOn(loader, 'error'); + spyOn(loader, 'hideLoader').and.callThrough(); + spyOn(loader, 'showReload').and.callThrough(); - it('should show reload', () => { - expect(loader.showReload).toHaveBeenCalled(); - }); - }); + workspaceLoader.load().then(done); }); - describe('if workspace is STOPPING', () => { - let workspaceLoader: WorkspaceLoader; - let statusChangeCallback: Function; - let workspaceLoadPromise: Promise; - - beforeEach(done => { - const loader = new Loader(); - workspaceLoader = new WorkspaceLoader(loader); + it('should not open an IDE', () => { + expect(workspaceLoader.openIDE).not.toHaveBeenCalled(); + }); - spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue('foo/bar'); + it('should log an error', () => { + expect(loader.error).toHaveBeenCalled(); + }); - spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => - new Promise(resolve => { - fakeWorkspaceConfig.status = 'STOPPING'; - resolve(fakeWorkspaceConfig); - })); + testLoaderIsHidden(); + testProgressBarIsHidden(); + testPromptIsShown(); - spyOn(workspaceLoader, 'subscribeWorkspaceEvents').and.callThrough(); +}); - spyOn(workspaceLoader, 'startWorkspace').and.callFake(() => - Promise.resolve()); +describe('If workspace gets an error status on start then workspace-loader', () => { + let statusChangeCallback: (event: che.workspace.event.WorkspaceStatusEvent) => {}; + let workspaceLoadPromise: Promise; + const statusErrorEvent: che.workspace.event.WorkspaceStatusEvent = { + error: 'Something bad happened.' + }; - spyOn(workspaceLoader, 'connectMasterApi').and.callFake(() => { - done(); - return Promise.resolve({ - addListener: () => { }, - subscribeEnvironmentOutput: () => { }, - subscribeInstallerOutput: () => { }, - subscribeWorkspaceStatus: (_workspaceId, callback) => { - statusChangeCallback = callback; - } - }); + beforeEach(done => { + spyOn(workspaceLoader, 'getWorkspaceKey').and.returnValue('foo/bar'); + spyOn(workspaceLoader, 'getWorkspace').and.callFake(() => + new Promise(resolve => { + workspaceConfig.status = 'STOPPED'; + resolve(workspaceConfig); + })); + spyOn(workspaceLoader, 'startWorkspace').and.callFake(() => Promise.resolve()); + spyOn((workspaceLoader), 'connectMasterApi').and.callFake(() => { + done(); + return Promise.resolve({ + addListener: () => { }, + subscribeEnvironmentOutput: () => { }, + subscribeInstallerOutput: () => { }, + subscribeWorkspaceStatus: (_workspaceId, callback) => { + statusChangeCallback = callback; + } }); - - spyOn(workspaceLoader, 'openIDE').and.callFake(() => Promise.resolve()); - - workspaceLoadPromise = workspaceLoader.load(); }); + spyOn((workspaceLoader), 'onWorkspaceStatus').and.callThrough(); + spyOn(workspaceLoader, 'openIDE').and.callFake(() => Promise.resolve()); - it('should not open an IDE immediately', () => { - expect(workspaceLoader.openIDE).not.toHaveBeenCalled(); - }); - - it('should set flag to restart a workspace', () => { - expect(workspaceLoader.startAfterStopping).toEqual(true); - }); + spyOn(loader, 'error'); + spyOn(loader, 'hideLoader').and.callThrough(); + spyOn(loader, 'showReload').and.callThrough(); - it('should not start a workspace immediately', () => { - expect(workspaceLoader.startWorkspace).not.toHaveBeenCalled(); - }); - - describe('then becomes STOPPED', () => { - - beforeEach(() => { - statusChangeCallback({ status: 'STOPPED' }); - }); - - it('should start a workspace', () => { - expect(workspaceLoader.startWorkspace).toHaveBeenCalled(); - }); - - it('should not open an IDE', () => { - expect(workspaceLoader.openIDE).not.toHaveBeenCalled(); - }); - - describe('then becomes RUNNING', () => { - - beforeEach(done => { - (workspaceLoader.getWorkspace as any).and.callFake(() => - new Promise(resolve => { - fakeWorkspaceConfig.status = 'RUNNING'; - fakeWorkspaceConfig.runtime = {} as che.workspace.Runtime; - resolve(fakeWorkspaceConfig); - })); - - statusChangeCallback({ status: 'RUNNING' }); + workspaceLoadPromise = workspaceLoader.load(); + }); - workspaceLoadPromise.then(done); - }); + beforeEach(done => { + statusChangeCallback(statusErrorEvent); - it('should open an IDE', () => { - expect(workspaceLoader.openIDE).toHaveBeenCalled(); - }); + workspaceLoadPromise.then(done); + }); - }); + it('should handle workspace status change', () => { + expect((workspaceLoader).onWorkspaceStatus).toHaveBeenCalledWith(statusErrorEvent); + }); - }); + it('should not open an IDE', () => { + expect(workspaceLoader.openIDE).not.toHaveBeenCalled(); + }); + it('should log an error', () => { + expect(loader.error).toHaveBeenCalled(); }); + testLoaderIsHidden(); + testProgressBarIsHidden(); + testPromptIsShown(); + }); function testLoaderIsHidden() { diff --git a/workspace-loader/webpack.dev.js b/workspace-loader/webpack.dev.js index d338110318e..bb804b7059f 100644 --- a/workspace-loader/webpack.dev.js +++ b/workspace-loader/webpack.dev.js @@ -10,48 +10,60 @@ * Red Hat, Inc. - initial API and implementation *******************************************************************************/ +const path = require('path'); const merge = require('webpack-merge'); const common = require('./webpack.common.js'); const HtmlWebpackPlugin = require('html-webpack-plugin'); -module.exports = merge(common, { - mode: 'development', - 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, - changeOrigin: true +module.exports = env => { + const proxyTarget = env && env.target ? env.target : 'http://localhost:8080'; + + return merge( + common, + { + mode: 'development', + 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 + }] + } + ] }, - '/api': { - target: 'http://localhost:8080', - changeOrigin: true + devServer: { + contentBase: path.resolve(__dirname, './target/dist'), + host: 'localhost', + port: 3000, + index: 'index.html', + historyApiFallback: true, + proxy: { + '/api/websocket': { + target: proxyTarget, + ws: true, + changeOrigin: true + }, + '/api': { + target: proxyTarget, + changeOrigin: true, + headers: { + origin: proxyTarget + } + }, + } }, + plugins: [ + new HtmlWebpackPlugin({ + inject: false, + title: "Che Workspace Loader", + template: "src/index.html", + urlPrefix: "/" + }) + ] } - }, - plugins: [ - new HtmlWebpackPlugin({ - inject: false, - title: "Che Workspace Loader", - template: "src/index.html", - urlPrefix: "/" - }) - ] -}); + ) +};