Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start jupyter server in child process and launch render process with token. #26

Merged
merged 3 commits into from
Jun 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ MANIFEST
build
dist
lib

src/main/*.js
*.d.ts
node_modules
.cache
.vscode
Expand Down
20 changes: 11 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"name": "jupyterlab_app",
"version": "0.0.1",
"description": "A native app for JupyterLab, based on electron.",
"main": "src/main/main.ts",
"main": "src/main/main.js",
"scripts": {
"start": "electron .",
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"build": "tsc && webpack",
"build:all": "npm run build && npm start"
},
"repository": {
Expand All @@ -19,30 +19,31 @@
},
"homepage": "https://github.com/jupyterlab/jupyterlab_app#readme",
"devDependencies": {
"electron-prebuilt-compile": "^1.6.11",
"typescript": "^2.2.1",
"css-loader": "^0.27.3",
"file-loader": "^0.10.1",
"fs-extra": "^2.1.2",
"handlebars": "^4.0.6",
"json-loader": "^0.5.4",
"style-loader": "^0.13.1",
"typescript": "^2.2.1",
"url-loader": "^0.5.7",
"webpack": "^2.2.1"
},
"dependencies": {
"@jupyterlab/about-extension": "^0.5.0",
"@jupyterlab/application": "^0.5.0",
"@jupyterlab/apputils-extension": "^0.5.0",
"@jupyterlab/codemirror-extension": "^0.5.0",
"@jupyterlab/completer-extension": "^0.5.1",
"@jupyterlab/coreutils": "^0.5.0",
"@jupyterlab/console-extension": "^0.5.1",
"@jupyterlab/coreutils": "^0.5.0",
"@jupyterlab/csvviewer-extension": "^0.5.0",
"@jupyterlab/default-theme": "^0.5.1",
"@jupyterlab/docmanager-extension": "^0.5.0",
"@jupyterlab/docregistry-extension": "^0.5.0",
"@jupyterlab/fileeditor-extension": "^0.5.0",
"@jupyterlab/faq-extension": "^0.5.0",
"@jupyterlab/filebrowser-extension": "^0.5.1",
"@jupyterlab/fileeditor-extension": "^0.5.0",
"@jupyterlab/help-extension": "^0.5.0",
"@jupyterlab/imageviewer-extension": "^0.5.0",
"@jupyterlab/inspector-extension": "^0.5.1",
Expand All @@ -58,10 +59,11 @@
"@jupyterlab/tabmanager-extension": "^0.5.0",
"@jupyterlab/terminal-extension": "^0.5.0",
"@jupyterlab/tooltip-extension": "^0.5.1",
"@jupyterlab/application": "^0.5.0",
"@jupyterlab/default-theme": "^0.5.1",
"@types/handlebars": "^4.0.33",
"electron": "^1.6.11",
"es6-promise": "^4.1.0",
"font-awesome": "^4.6.3"
"font-awesome": "^4.6.3",
"handlebars": "^4.0.10"
},
"jupyterlab": {
"extensions": [
Expand Down
30 changes: 2 additions & 28 deletions src/browser/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
"appNamespace": "",
"appName": "JupyterLab",
"baseUrl": "http://localhost:8888/",
"token": "2efc305ee8fc1e41f7632d09c35830c0ada40909ba829351",
"wsUrl": "",
"publicUrl": "../build/"
"publicUrl": "../../build/",
"token": "{{ token }}"
}</script>

<script>
Expand All @@ -35,32 +35,6 @@

<body>

<script type="text/javascript">
function _remove_token_from_url() {
if (window.location.search.length <= 1) {
return;
}
var search_parameters = window.location.search.slice(1).split('&');
for (var i = 0; i < search_parameters.length; i++) {
if (search_parameters[i].split('=')[0] === 'token') {
// remote token from search parameters
search_parameters.splice(i, 1);
var new_search = '';
if (search_parameters.length) {
new_search = '?' + search_parameters.join('&');
}
var new_url = window.location.origin +
window.location.pathname +
new_search +
window.location.hash;
window.history.replaceState({}, "", new_url);
return;
}
}
}
_remove_token_from_url();
</script>

</body>

</html>
24 changes: 19 additions & 5 deletions src/browser/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
require('es6-promise/auto'); // polyfill Promise on IE


require('es6-promise/auto'); // polyfill Promise on IE'

var app = require('@jupyterlab/application').JupyterLab;
var PageConfig = require('@jupyterlab/coreutils').PageConfig;
__webpack_public_path__ = PageConfig.getOption('publicUrl');

// This needs to come after __webpack_public_path__ is set.
require('font-awesome/css/font-awesome.min.css');
// Load the core theming before any other package.
require('@jupyterlab/default-theme/style/index.css');

// Use window.require to prevent webpack from expanding electron
var ipcRenderer = window.require('electron').ipcRenderer;
var app = require('@jupyterlab/application').JupyterLab;

function main() {
var version = PageConfig.getOption('appVersion') || 'unknown';
var name = PageConfig.getOption('appName') || 'JupyterLab';
Expand Down Expand Up @@ -41,9 +44,20 @@ function main() {
var option = PageConfig.getOption('ignorePlugins');
ignorePlugins = JSON.parse(option);
} catch (e) {
console.error("Invalid ignorePlugins config:", option);
// No-op
}
lab.start({ "ignorePlugins": ignorePlugins });
ipcRenderer.on('server-data', (evt, arg) => {
/*var serverData = JSON.parse(String(arg));
var el = document.getElementById('jupyter-config-data');
var data = JSON.parse(el.innerText);
data.token = serverData.token;
el.innerText = JSON.stringify(data);
console.log(JSON.parse(el.innerText));*/
//lab.start({ "ignorePlugins": ignorePlugins });
});

ipcRenderer.send('server-data', 'get');
}

window.onload = main;
209 changes: 209 additions & 0 deletions src/main/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

import { dialog, app, BrowserWindow } from 'electron'
import { ChildProcess, spawn } from 'child_process';
import * as Handlebars from 'handlebars';
import * as fs from 'fs';
import * as path from 'path';
import * as url from 'url'

class JupyterServer {
/**
* The child process object for the Jupyter server
*/
private nbServer: ChildProcess;

/**
* The Jupyter server authentication token
*/
public token: string;

/**
* The Jupyter server hostname
*/
public hostname: string;

/**
* The Jupyter server port number
*/
public port: number;

/**
* The Jupyter server url
*/
public url: string;

/**
* The jupyter server notebook directory
*/
public notebookDir: string;

/**
* Start a local Jupyer server on the specified port. Returns
* a promise that is fulfilled when the Jupyter server has
* started and all the required data (url, token, etc.) is
* collected. This data is collected using `jupyter notebook list`
*/
public start(port: number): Promise<any> {
return new Promise((resolve, reject) => {
this.nbServer = spawn('jupyter', ['notebook', '--no-browser', '--port', String(port)]);

this.nbServer.on('error', (err: Error) => {
this.nbServer.stderr.removeAllListeners();
reject(err);
});

this.nbServer.stderr.on('data', (serverData: string) => {
if (serverData.indexOf("The Jupyter Notebook is running at") == -1)
return;
this.nbServer.removeAllListeners();
this.nbServer.stderr.removeAllListeners();

/* Get server data */
let list = spawn('jupyter', ['notebook', 'list', '--json']);

list.on('error', (err: Error) => {
list.stdout.removeAllListeners();
reject(err);
});

list.stdout.on('data', (data: string) => {
let serverData = JSON.parse(data);
serverData.port = Number(serverData.port);
this.token = serverData.token;
this.hostname = serverData.hostname;
this.port = serverData.port;
this.url = serverData.url;
this.notebookDir = serverData.notebook_dir;
resolve(serverData);
});
});
});
}

/**
* Stop the currently executing Jupyter server
*/
public stop(): void {
if (this.nbServer !== undefined)
this.nbServer.kill();
}
}

export class JupyterApplication {
/**
* The JupyterServer the application will use
*/
private server: JupyterServer;

/**
* The JupyterLab window
*/
private mainWindow: any;

/**
* The file that stores index.html after it has been
* run through the templater. This is a hack that should
* be replaced when the JupyterLab application api is
* updated.
*/
private indexFile: string;

/**
* Construct the Jupyter application
*/
constructor() {
this.registerListeners();
this.server = new JupyterServer();
this.indexFile = path.join(__dirname, 'temp.index.html');
}

/**
* Register all application event listeners
*/
private registerListeners(): void {
// On OS X it is common for applications and their menu bar to stay
// active until the user quits explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});

// On OS X it's common to re-create a window in the app when the dock icon is clicked and there are no other
// windows open.
// Need to double check this code to ensure it has expected behaviour
app.on('activate', () => {
if (this.mainWindow === null) {
this.createWindow();
}
});

app.on('quit', () => {
this.server.stop();
fs.unlinkSync(this.indexFile);
});
}

/**
* Creates the primary application window and loads the
* html
*/
private createWindow(): void {
this.mainWindow = new BrowserWindow({
width: 800,
height: 600,
minWidth: 400,
minHeight: 300
});

this.mainWindow.loadURL(url.format({
pathname: this.indexFile,
protocol: 'file:',
slashes: true
}));

// Register dialog on window close
this.mainWindow.on('close', (event: Event) => {
let buttonClicked = dialog.showMessageBox({
type: 'warning',
message: 'Do you want to leave?',
detail: 'Changes you made may not be saved.',
buttons: ['Leave', 'Stay'],
defaultId: 0,
cancelId: 1
});
if (buttonClicked === 1) {
event.preventDefault();
}
});

this.mainWindow.on('closed', () => {
this.mainWindow = null;
});
}

/**
* Starts the Jupyter Server and launches the electron application.
* When the Jupyter Sevrer start promise is fulfilled, the Handlebars
* templater is run on index.html and the output is saved to a file.
* The electron render process is then started on that file by
* calling createWindow
*/
public start(): void {
this.server.start(8888)
.then((serverData) => {
console.log("Jupyter Server started at: " + serverData.url + "?token=" + serverData.token);
let source = fs.readFileSync(path.join(__dirname, '../browser/index.html')).toString();
let template = Handlebars.compile(source);
let html = template(serverData);
fs.writeFileSync(path.resolve(__dirname, this.indexFile), html);
this.createWindow();
})
.catch((err) => {
console.error("Failed to start Jupyter Server");
console.error(err);
});
}
}
Loading