Merge pull request #292 from resin-io/feat/ux-improvements

UX CLI improvements
This commit is contained in:
Juan Cruz Viotti 2016-01-12 14:28:47 -04:00
commit ef7e39450c
12 changed files with 259 additions and 97 deletions

View File

@ -27,12 +27,6 @@ This might require elevated privileges in some environments.
$ npm install --global --production resin-cli
```
### Login
```sh
$ resin login
```
### List available commands
```sh

View File

@ -19,13 +19,18 @@ limitations under the License.
exports.login = {
signature: 'login',
description: 'login to resin.io',
help: 'Use this command to login to your resin.io account.\n\nThis command will open your web browser and prompt you to authorize the CLI\nfrom the dashboard.\n\nIf you don\'t have access to a web browser (e.g: running in a headless server),\nyou can fetch your authentication token from the preferences page and pass\nthe token option.\n\nAlternatively, you can pass the `--credentials` boolean option to perform\na credential-based authentication, with optional `--email` and `--password`\noptions to avoid interactive behaviour (unless you have 2FA enabled).\n\nExamples:\n\n $ resin login\n $ resin login --token "..."\n $ resin login --credentials\n $ resin login --credentials --email johndoe@gmail.com --password secret',
help: 'Use this command to login to your resin.io account.\n\nThis command will prompt you to login using the following login types:\n\n- Web authorization: open your web browser and prompt you to authorize the CLI\nfrom the dashboard.\n\n- Credentials: using email/password and 2FA.\n\n- Token: using the authentication token from the preferences page.\n\nExamples:\n\n $ resin login\n $ resin login --web\n $ resin login --token "..."\n $ resin login --credentials\n $ resin login --credentials --email johndoe@gmail.com --password secret',
options: [
{
signature: 'token',
description: 'auth token',
parameter: 'token',
alias: 't'
}, {
signature: 'web',
description: 'web-based login',
boolean: true,
alias: 'w'
}, {
signature: 'credentials',
description: 'credential-based login',
@ -45,51 +50,46 @@ limitations under the License.
],
primary: true,
action: function(params, options, done) {
var Promise, auth, events, form, resin, validation;
var Promise, _, auth, events, form, login, messages, patterns, resin;
_ = require('lodash');
Promise = require('bluebird');
resin = require('resin-sdk');
events = require('resin-cli-events');
form = require('resin-cli-form');
auth = require('resin-cli-auth');
validation = require('../utils/validation');
return resin.settings.get('resinUrl').then(function(resinUrl) {
console.log("Logging in to " + resinUrl);
form = require('resin-cli-form');
patterns = require('../utils/patterns');
messages = require('../utils/messages');
login = function(options) {
if (options.token != null) {
return resin.auth.loginWithToken(options.token);
} else if (options.credentials) {
return form.run([
{
message: 'Email:',
name: 'email',
type: 'input',
validate: validation.validateEmail
}, {
message: 'Password:',
name: 'password',
type: 'password'
}
], {
override: options
}).then(resin.auth.login).then(resin.auth.twoFactor.isPassed).then(function(isTwoFactorAuthPassed) {
if (isTwoFactorAuthPassed) {
return;
return Promise["try"](function() {
if (_.isString(options.token)) {
return options.token;
}
return form.ask({
message: 'Two factor auth challenge:',
name: 'code',
message: 'Token (from the preferences page)',
name: 'token',
type: 'input'
}).then(resin.auth.twoFactor.challenge)["catch"](function() {
return resin.auth.logout().then(function() {
throw new Error('Invalid two factor authentication code');
});
});
});
}).then(resin.auth.loginWithToken);
} else if (options.credentials) {
return patterns.authenticate(options);
} else if (options.web) {
console.info('Connecting to the web dashboard');
return auth.login();
}
console.info('Connecting to the web dashboard');
return auth.login();
return patterns.askLoginType().then(function(loginType) {
options[loginType] = true;
return login(options);
});
};
return resin.settings.get('resinUrl').then(function(resinUrl) {
console.log(messages.resinAsciiArt);
console.log("\nLogging in to " + resinUrl);
return login(options);
}).then(resin.auth.whoami).tap(function(username) {
events.send('user.login');
console.info("Successfully logged in as: " + username);
return events.send('user.login');
return console.info("\nNow what?\n\n" + messages.gettingStarted + "\n\nFind out about more super powers by running:\n\n $ resin help\n\n" + messages.reachingOut);
}).nodeify(done);
}
};

View File

@ -16,7 +16,7 @@ limitations under the License.
*/
(function() {
var _, capitano, columnify, command, general, indent, parse, print;
var _, capitano, columnify, command, general, indent, messages, parse, print;
_ = require('lodash');
@ -26,6 +26,8 @@ limitations under the License.
columnify = require('columnify');
messages = require('../utils/messages');
parse = function(object) {
return _.object(_.map(object, function(item) {
var signature;
@ -55,7 +57,9 @@ limitations under the License.
general = function(params, options, done) {
var commands, groupedCommands;
console.log('Usage: resin [COMMAND] [OPTIONS]\n');
console.log('Primary commands:\n');
console.log(messages.gettingStarted + "\n");
console.log(messages.reachingOut);
console.log('\nPrimary commands:\n');
commands = _.reject(capitano.state.commands, function(command) {
return command.isWildcard();
});

View File

@ -20,7 +20,6 @@ limitations under the License.
signature: 'quickstart [name]',
description: 'getting started with resin.io',
help: 'Use this command to run a friendly wizard to get started with resin.io.\n\nThe wizard will guide you through:\n\n - Create an application.\n - Initialise an SDCard with the resin.io operating system.\n - Associate an existing project directory with your resin.io application.\n - Push your project to your devices.\n\nExamples:\n\n $ resin quickstart\n $ resin quickstart MyApp',
permission: 'user',
primary: true,
action: function(params, options, done) {
var Promise, capitano, patterns, resin;
@ -28,7 +27,16 @@ limitations under the License.
capitano = Promise.promisifyAll(require('capitano'));
resin = require('resin-sdk');
patterns = require('../utils/patterns');
return Promise["try"](function() {
return resin.auth.isLoggedIn().then(function(isLoggedIn) {
if (isLoggedIn) {
return;
}
console.info('Looks like you\'re not logged in yet!');
console.info('Lets go through a quick wizard to get you started.\n');
return capitano.runAsync('login').then(function() {
return require('fs').readdirSync('/Users/jviotti/.resin');
});
}).then(function() {
if (params.name != null) {
return;
}

10
build/utils/messages.js Normal file
View File

@ -0,0 +1,10 @@
(function() {
exports.gettingStarted = 'Run the following command to get a device started with Resin.io\n\n $ resin quickstart';
exports.reachingOut = 'If you need help, or just want to say hi, don\'t hesitate in reaching out at:\n\n GitHub: https://github.com/resin-io/resin-cli/issues/new\n Gitter: https://gitter.im/resin-io/chat';
exports.getHelp = 'If you need help, don\'t hesitate in contacting us at:\n\n GitHub: https://github.com/resin-io/resin-cli/issues/new\n Gitter: https://gitter.im/resin-io/chat';
exports.resinAsciiArt = '______ _ _\n| ___ \\ (_) (_)\n| |_/ /___ ___ _ _ __ _ ___\n| // _ \\/ __| | \'_ \\ | |/ _ \\\n| |\\ \\ __/\\__ \\ | | | |_| | (_) |\n\\_| \\_\\___||___/_|_| |_(_)_|\\___/';
}).call(this);

View File

@ -16,7 +16,7 @@ limitations under the License.
*/
(function() {
var Promise, _, chalk, form, resin, validation, visuals;
var Promise, _, chalk, form, messages, resin, validation, visuals;
_ = require('lodash');
@ -32,6 +32,58 @@ limitations under the License.
validation = require('./validation');
messages = require('./messages');
exports.authenticate = function(options) {
return form.run([
{
message: 'Email:',
name: 'email',
type: 'input',
validate: validation.validateEmail
}, {
message: 'Password:',
name: 'password',
type: 'password'
}
], {
override: options
}).then(resin.auth.login).then(resin.auth.twoFactor.isPassed).then(function(isTwoFactorAuthPassed) {
if (isTwoFactorAuthPassed) {
return;
}
return form.ask({
message: 'Two factor auth challenge:',
name: 'code',
type: 'input'
}).then(resin.auth.twoFactor.challenge)["catch"](function() {
return resin.auth.logout().then(function() {
throw new Error('Invalid two factor authentication code');
});
});
});
};
exports.askLoginType = function() {
return form.ask({
message: 'How would you like to login?',
name: 'loginType',
type: 'list',
choices: [
{
name: 'Web authorization (recommended)',
value: 'web'
}, {
name: 'Credentials',
value: 'credentials'
}, {
name: 'Authentication token',
value: 'token'
}
]
});
};
exports.selectDeviceType = function() {
return resin.models.device.getSupportedDeviceTypes().then(function(deviceTypes) {
return form.ask({
@ -134,7 +186,8 @@ limitations under the License.
};
exports.printErrorMessage = function(message) {
return console.error(chalk.red(message));
console.error(chalk.red(message));
return console.error(chalk.red("\n" + messages.getHelp + "\n"));
};
}).call(this);

View File

@ -165,20 +165,19 @@ confirm non interactively
Use this command to login to your resin.io account.
This command will open your web browser and prompt you to authorize the CLI
This command will prompt you to login using the following login types:
- Web authorization: open your web browser and prompt you to authorize the CLI
from the dashboard.
If you don't have access to a web browser (e.g: running in a headless server),
you can fetch your authentication token from the preferences page and pass
the token option.
- Credentials: using email/password and 2FA.
Alternatively, you can pass the `--credentials` boolean option to perform
a credential-based authentication, with optional `--email` and `--password`
options to avoid interactive behaviour (unless you have 2FA enabled).
- Token: using the authentication token from the preferences page.
Examples:
$ resin login
$ resin login --web
$ resin login --token "..."
$ resin login --credentials
$ resin login --credentials --email johndoe@gmail.com --password secret
@ -189,6 +188,10 @@ Examples:
auth token
#### --web, -w
web-based login
#### --credentials, -c
credential-based login

View File

@ -20,20 +20,19 @@ exports.login =
help: '''
Use this command to login to your resin.io account.
This command will open your web browser and prompt you to authorize the CLI
This command will prompt you to login using the following login types:
- Web authorization: open your web browser and prompt you to authorize the CLI
from the dashboard.
If you don't have access to a web browser (e.g: running in a headless server),
you can fetch your authentication token from the preferences page and pass
the token option.
- Credentials: using email/password and 2FA.
Alternatively, you can pass the `--credentials` boolean option to perform
a credential-based authentication, with optional `--email` and `--password`
options to avoid interactive behaviour (unless you have 2FA enabled).
- Token: using the authentication token from the preferences page.
Examples:
$ resin login
$ resin login --web
$ resin login --token "..."
$ resin login --credentials
$ resin login --credentials --email johndoe@gmail.com --password secret
@ -45,6 +44,12 @@ exports.login =
parameter: 'token'
alias: 't'
}
{
signature: 'web'
description: 'web-based login'
boolean: true
alias: 'w'
}
{
signature: 'credentials'
description: 'credential-based login'
@ -66,49 +71,55 @@ exports.login =
]
primary: true
action: (params, options, done) ->
_ = require('lodash')
Promise = require('bluebird')
resin = require('resin-sdk')
events = require('resin-cli-events')
form = require('resin-cli-form')
auth = require('resin-cli-auth')
validation = require('../utils/validation')
form = require('resin-cli-form')
patterns = require('../utils/patterns')
messages = require('../utils/messages')
login = (options) ->
if options.token?
return Promise.try ->
return options.token if _.isString(options.token)
return form.ask
message: 'Token (from the preferences page)'
name: 'token'
type: 'input'
.then(resin.auth.loginWithToken)
else if options.credentials
return patterns.authenticate(options)
else if options.web
console.info('Connecting to the web dashboard')
return auth.login()
return patterns.askLoginType().then (loginType) ->
options[loginType] = true
return login(options)
resin.settings.get('resinUrl').then (resinUrl) ->
console.log("Logging in to #{resinUrl}")
if options.token?
return resin.auth.loginWithToken(options.token)
else if options.credentials
return form.run [
message: 'Email:'
name: 'email'
type: 'input'
validate: validation.validateEmail
,
message: 'Password:'
name: 'password'
type: 'password'
],
override: options
.then(resin.auth.login)
.then(resin.auth.twoFactor.isPassed)
.then (isTwoFactorAuthPassed) ->
return if isTwoFactorAuthPassed
return form.ask
message: 'Two factor auth challenge:'
name: 'code'
type: 'input'
.then(resin.auth.twoFactor.challenge)
.catch ->
resin.auth.logout().then ->
throw new Error('Invalid two factor authentication code')
console.info('Connecting to the web dashboard')
return auth.login()
console.log(messages.resinAsciiArt)
console.log("\nLogging in to #{resinUrl}")
return login(options)
.then(resin.auth.whoami)
.tap (username) ->
console.info("Successfully logged in as: #{username}")
events.send('user.login')
console.info("Successfully logged in as: #{username}")
console.info """
Now what?
#{messages.gettingStarted}
Find out about more super powers by running:
$ resin help
#{messages.reachingOut}
"""
.nodeify(done)
exports.logout =

View File

@ -18,6 +18,7 @@ _ = require('lodash')
_.str = require('underscore.string')
capitano = require('capitano')
columnify = require('columnify')
messages = require('../utils/messages')
parse = (object) ->
return _.object _.map object, (item) ->
@ -46,7 +47,9 @@ print = (data) ->
general = (params, options, done) ->
console.log('Usage: resin [COMMAND] [OPTIONS]\n')
console.log('Primary commands:\n')
console.log("#{messages.gettingStarted}\n")
console.log(messages.reachingOut)
console.log('\nPrimary commands:\n')
# We do not want the wildcard command
# to be printed in the help screen.

View File

@ -32,7 +32,6 @@ exports.wizard =
$ resin quickstart
$ resin quickstart MyApp
'''
permission: 'user'
primary: true
action: (params, options, done) ->
Promise = require('bluebird')
@ -40,7 +39,13 @@ exports.wizard =
resin = require('resin-sdk')
patterns = require('../utils/patterns')
Promise.try ->
resin.auth.isLoggedIn().then (isLoggedIn) ->
return if isLoggedIn
console.info('Looks like you\'re not logged in yet!')
console.info('Lets go through a quick wizard to get you started.\n')
return capitano.runAsync('login').then ->
require('fs').readdirSync('/Users/jviotti/.resin')
.then ->
return if params.name?
patterns.selectOrCreateApplication().tap (applicationName) ->
resin.models.application.has(applicationName).then (hasApplication) ->

28
lib/utils/messages.coffee Normal file
View File

@ -0,0 +1,28 @@
exports.gettingStarted = '''
Run the following command to get a device started with Resin.io
$ resin quickstart
'''
exports.reachingOut = '''
If you need help, or just want to say hi, don't hesitate in reaching out at:
GitHub: https://github.com/resin-io/resin-cli/issues/new
Gitter: https://gitter.im/resin-io/chat
'''
exports.getHelp = '''
If you need help, don't hesitate in contacting us at:
GitHub: https://github.com/resin-io/resin-cli/issues/new
Gitter: https://gitter.im/resin-io/chat
'''
exports.resinAsciiArt = '''
______ _ _
| ___ \\ (_) (_)
| |_/ /___ ___ _ _ __ _ ___
| // _ \\/ __| | '_ \\ | |/ _ \\
| |\\ \\ __/\\__ \\ | | | |_| | (_) |
\\_| \\_\\___||___/_|_| |_(_)_|\\___/
'''

View File

@ -21,6 +21,48 @@ visuals = require('resin-cli-visuals')
resin = require('resin-sdk')
chalk = require('chalk')
validation = require('./validation')
messages = require('./messages')
exports.authenticate = (options) ->
return form.run [
message: 'Email:'
name: 'email'
type: 'input'
validate: validation.validateEmail
,
message: 'Password:'
name: 'password'
type: 'password'
],
override: options
.then(resin.auth.login)
.then(resin.auth.twoFactor.isPassed)
.then (isTwoFactorAuthPassed) ->
return if isTwoFactorAuthPassed
return form.ask
message: 'Two factor auth challenge:'
name: 'code'
type: 'input'
.then(resin.auth.twoFactor.challenge)
.catch ->
resin.auth.logout().then ->
throw new Error('Invalid two factor authentication code')
exports.askLoginType = ->
return form.ask
message: 'How would you like to login?'
name: 'loginType'
type: 'list'
choices: [
name: 'Web authorization (recommended)'
value: 'web'
,
name: 'Credentials'
value: 'credentials'
,
name: 'Authentication token'
value: 'token'
]
exports.selectDeviceType = ->
resin.models.device.getSupportedDeviceTypes().then (deviceTypes) ->
@ -105,3 +147,4 @@ exports.awaitDevice = (uuid) ->
exports.printErrorMessage = (message) ->
console.error(chalk.red(message))
console.error(chalk.red("\n#{messages.getHelp}\n"))