Skip to content
This repository was archived by the owner on Oct 26, 2019. It is now read-only.

Commit

Permalink
Fixes issues found by Parente
Browse files Browse the repository at this point in the history
Asks before overwriting existing files/dirs
Better help text.

(c) Copyright IBM Corp. 2016
  • Loading branch information
jhpedemonte committed Jul 28, 2016
1 parent 22c192c commit 5212523
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 33 deletions.
41 changes: 28 additions & 13 deletions app/public-link.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,40 @@
* Copyright (c) Jupyter Development Team.
* Distributed under the terms of the Modified BSD License.
*/
var config = require('./config');

var PUBLIC_LINK_PATTERN = config.get('PUBLIC_LINK_PATTERN');
var PROTOCOL = /\{protocol\}/g;
var HOST = /\{host\}/g;
var PORT = /\{port\}/g;

/**
* Generates a base URL that can be used to build public links. Uses the pattern
* specified by the PUBLIC_LINK_PATTERN config parameter.
* @param {Request} req - Express request used to get protocol and hostname
* Generates a base URL that can be used to build public links. Uses a pattern, such as that
* specified by the PUBLIC_LINK_PATTERN config parameter. Eg:
*
* var url = require('public-link')(req, config.get('PUBLIC_LINK_PATTERN'))
*
* @param {Request|Object} req - Express request used to get protocol and hostname; or object
* containing `host` and `port` (both optional) properties
* @param {String} pattern - URL pattern
* @return {String} public base URL
* @return {undefined} if PUBLIC_LINK_PATTERN is not set
* @return {undefined} if `pattern` is not set
*/
module.exports = function(req) {
if (PUBLIC_LINK_PATTERN) {
return PUBLIC_LINK_PATTERN
.replace(PROTOCOL, req.protocol)
.replace(HOST, req.headers.host)
.replace(PORT, req.headers.host.split(':')[1] || '');
module.exports = function(req, pattern) {
if (pattern) {
var protocol;
var host;
var port;
if (!!req.headers) { // is Express request
protocol = req.protocol;
host = req.headers.host;
port = req.headers.host.split(':')[1] || '';
} else {
protocol = 'http';
host = req.host || '127.0.0.1';
port = req.port || '';
}

return pattern
.replace(PROTOCOL, protocol)
.replace(HOST, host)
.replace(PORT, port);
}
};
121 changes: 102 additions & 19 deletions bin/jupyter-dashboard-admin
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ var fs = require('fs');
var hjson = require('hjson');
var nconf = require('nconf');
var path = require('path');
var prompt = require('prompt');
var publicLink = require('../app/public-link');
var request = require('request');
var urljoin = require('url-join');
var yargs = require('yargs');
Expand All @@ -20,17 +22,35 @@ var yargs = require('yargs');
var defaultConfig = path.join(__dirname, '..', 'config.json');
var config = nconf.file({ file: defaultConfig, format: hjson });

var DEFAULT_HOST = 'http://localhost:3000';
// create default host from config options
var defaultHost = publicLink({
host: config.get('IP'),
port: config.get('PORT')
}, config.get('PUBLIC_LINK_PATTERN'));

// prompt settings
prompt.message = '';
prompt.delimiter = '';
prompt.start();
var uploadPromptSettings = {
name: 'yesno',
message: 'Destination [{dest}] already exists. Do you want to overwrite?',
validator: /y[es]*|n[o]?/,
warning: 'Respond with yes or no',
default: 'no'
};

// common options shared by all commands
var commonOpts = {
'host': {
default: defaultHost,
describe: 'dashboard server host and port',
default: DEFAULT_HOST,
global: true,
type: 'string'
},
'auth-token': {
describe: 'authorization token for admin privelages, defaults to reading from config file',
global: true,
type: 'string'
}
};
Expand All @@ -39,15 +59,14 @@ var commonOpts = {
var clear_cache_command = {
command: 'clear-cache [options]',
describe: 'Clear dashboard server cache',
builder: commonOpts,
handler: function(argv) {
var opts = {
url: urljoin(argv.host, '/_api/cache')
};

sendRequest('delete', argv, opts, 200)
.then(function() {
console.log('Cache reset!');
console.log('Cache reset');
process.exit(0);
})
.catch(function() {
Expand All @@ -61,15 +80,17 @@ var clear_cache_command = {
var delete_command = {
command: 'delete [options] <path>',
describe: 'Delete dashboard from server',
builder: commonOpts,
builder: function(yargs) {
return yargs.epilogue(' path path of dashboard to delete');
},
handler: function(argv) {
var opts = {
url: urljoin(argv.host, '/_api/notebooks', argv.path)
};

sendRequest('delete', argv, opts, 204)
.then(function() {
console.log('Dashboard deleted!');
console.log('Dashboard deleted');
process.exit(0);
})
.catch(function() {
Expand All @@ -81,27 +102,85 @@ var delete_command = {

// command to upload a dashboard
var upload_command = {
command: 'upload [options] <file> <dest>',
command: 'upload [options] <file> <pathname>',
describe: 'Upload a dashboard to the dashboard server',
builder: commonOpts,
builder: function(yargs) {
return yargs.option({
overwrite: {
default: false,
describe: 'overwrite existing dashboard/dir/file at <pathname>',
type: 'boolean'
}
})
.epilogue(' file path to a local notebook\n' +
' pathname path & name of destination dashboard;\n' +
' should not include ".ipynb" extension');
},
handler: function(argv) {
var datapath = path.resolve(argv.file);
var opts = {
url: urljoin(argv.host, '/_api/notebooks', argv.dest),
formData: {
file: fs.createReadStream(datapath)
// ensure that destination doesn't still include notebook extension
var dest = argv.pathname;
if (dest.endsWith('.ipynb')) {
yargs.showHelp();
console.error('ERROR: <pathname> should be the (optional) path and name of the destination dashboard -- it should not have an ".ipynb" extension.');
process.exit(1);
}
var destUrl = urljoin(argv.host, '/dashboards', dest);

// check is destination already exists
new Promise(function(resolve, reject) {
if (argv.overwrite) {
// user has already specified that he/she wishes to overwrite destination pathname
resolve();
return;
}
};

sendRequest('post', argv, opts, 201)
request.get(destUrl, function(err, res, body) {
if (err) {
console.error('ERROR: failure contacting server: ' + err);
process.exit(1);
}

if (res.statusCode === 404) {
// nothing at destination, so we can safely upload
resolve();
return;
}

// ask user if he/she wants to overwrite destination
uploadPromptSettings.message = uploadPromptSettings.message.replace('{dest}', destUrl);
prompt.get(uploadPromptSettings, function(err, result) {
if (err) {
console.error('ERROR: unknown error: ' + err);
process.exit(1);
}
if (result.yesno === 'y' || result.yesno === 'yes') {
resolve();
} else {
reject();
}
});
});
})
.then(function() {
// we are good to upload
var datapath = path.resolve(argv.file);
var opts = {
url: urljoin(argv.host, '/_api/notebooks', dest),
formData: {
file: fs.createReadStream(datapath)
}
};

sendRequest('post', argv, opts, 201)
.then(function() {
console.log('Dashboard successfully uploaded!');
console.log('Dashboard uploaded to: ' + destUrl);
process.exit(0);
})
.catch(function(err) {
console.error('ERROR:', 'could not upload dashboard.', err || '');
process.exit(1);
});
});
}
};

Expand Down Expand Up @@ -131,9 +210,13 @@ function sendRequest(method, argv, opts, expectedResponseCode) {
});
}

yargs.command(clear_cache_command)
var yargs = yargs.usage('Usage: $0 <command> [options]')
.options(commonOpts)
.command(clear_cache_command)
.command(delete_command)
.command(upload_command)
.demand(1)
.strict()
.help()
.argv;
.help('h')
.alias('h', 'help');
var argv = yargs.argv;
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"nconf": "^0.8.2",
"passport": "^0.3.2",
"passport-local": "^1.0.0",
"prompt": "^1.0.0",
"request": "^2.67.0",
"requirejs": "^2.1.21",
"rimraf": "^2.5.2",
Expand Down
3 changes: 2 additions & 1 deletion routes/auth-routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ var upload = require('../app/notebook-upload');
var router = require('express').Router();
var urljoin = require('url-join');

var PUBLIC_LINK_PATTERN = config.get('PUBLIC_LINK_PATTERN');
var UPLOAD_MESSAGE = 'Notebook successfully uploaded';

/* POST /notebooks/* - upload a dashboard notebook */
router.post('/notebooks(/*)', authToken, upload, function(req, res) {
var publicLink = link(req);
var publicLink = link(req, PUBLIC_LINK_PATTERN);
var resBody = {
message: UPLOAD_MESSAGE,
status: 201,
Expand Down

0 comments on commit 5212523

Please sign in to comment.