diff --git a/package.json b/package.json index e70a8c4cc8..9695288c7f 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "classnames": "^1.2.0", "coveralls": "^2.11.2", "deep-extend": "^0.4.0", - "dockerode": "^2.1.1", + "dockerode": "^2.1.4", "exec": "0.2.0", "install": "^0.1.8", "jquery": "^2.1.3", diff --git a/src/actions/AccountActions.js b/src/actions/AccountActions.js index 938aa52e91..fce7e9ebd5 100644 --- a/src/actions/AccountActions.js +++ b/src/actions/AccountActions.js @@ -19,7 +19,7 @@ class AccountActions { skip () { this.dispatch({}); - hub.prompted(true); + hub.setPrompted(true); } verify () { diff --git a/src/actions/ContainerActions.js b/src/actions/ContainerActions.js index fbb33a966d..e5fd4e1dae 100644 --- a/src/actions/ContainerActions.js +++ b/src/actions/ContainerActions.js @@ -1,6 +1,5 @@ import alt from '../alt'; import dockerUtil from '../utils/DockerUtil'; -import hubUtil from '../utils/HubUtil'; class ContainerActions { start (name) { @@ -33,7 +32,7 @@ class ContainerActions { } run (name, repo, tag) { - dockerUtil.run(hubUtil.config(), name, repo, tag); + dockerUtil.run(name, repo, tag); } } diff --git a/src/actions/RepositoryActions.js b/src/actions/RepositoryActions.js index cd51da12d5..e1c7989b34 100644 --- a/src/actions/RepositoryActions.js +++ b/src/actions/RepositoryActions.js @@ -1,6 +1,5 @@ import alt from '../alt'; import regHubUtil from '../utils/RegHubUtil'; -import hubUtil from '../utils/HubUtil'; class RepositoryActions { recommended () { @@ -15,7 +14,7 @@ class RepositoryActions { repos () { this.dispatch({}); - regHubUtil.repos(hubUtil.jwt()); + regHubUtil.repos(); } tags () { diff --git a/src/actions/TagActions.js b/src/actions/TagActions.js index 21da864666..36ffd843e1 100644 --- a/src/actions/TagActions.js +++ b/src/actions/TagActions.js @@ -1,11 +1,10 @@ import alt from '../alt'; import regHubUtil from '../utils/RegHubUtil'; -import hubUtil from '../utils/HubUtil'; class TagActions { tags (repo) { this.dispatch({repo}); - regHubUtil.tags(hubUtil.jwt(), repo); + regHubUtil.tags(repo); } } diff --git a/src/app.js b/src/app.js index e9eb0d4bf4..fa6d1c840d 100644 --- a/src/app.js +++ b/src/app.js @@ -21,8 +21,12 @@ var routerContainer = require('./router'); var repositoryActions = require('./actions/RepositoryActions'); hubUtil.init(); + +if (hubUtil.loggedin()) { + repositoryActions.repos(); +} + repositoryActions.recommended(); -repositoryActions.repos(); webUtil.addWindowSizeSaving(); webUtil.addLiveReload(); @@ -46,8 +50,8 @@ routerContainer.set(router); SetupStore.setup().then(() => { Menu.setApplicationMenu(Menu.buildFromTemplate(template())); docker.init(); - if (!hub.isPrompted() && !hub.loggedin()) { - router.transitionTo('signup'); + if (!hub.prompted() && !hub.loggedin()) { + router.transitionTo('login'); } else { router.transitionTo('search'); } diff --git a/src/browser.js b/src/browser.js index b2a10b38d5..b731e338ef 100644 --- a/src/browser.js +++ b/src/browser.js @@ -33,7 +33,7 @@ app.on('ready', function () { 'standard-window': false, resizable: true, frame: false, - show: true, + show: false, }); mainWindow.loadUrl(path.normalize('file://' + path.join(__dirname, '..', 'build/index.html'))); diff --git a/src/components/Account.react.js b/src/components/Account.react.js index 2dcfd07990..522412d2c2 100644 --- a/src/components/Account.react.js +++ b/src/components/Account.react.js @@ -23,6 +23,12 @@ module.exports = React.createClass({ accountStore.unlisten(this.update); }, + componentWillUpdate: function (nextProps, nextState) { + if (nextState.username) { + this.goBack(); + } + }, + handleSkip: function () { accountActions.skip(); this.transitionTo('search'); @@ -35,12 +41,7 @@ module.exports = React.createClass({ }, update: function () { - let state = accountStore.getState(); - this.setState(state); - - if (state.username) { - this.goBack(); - } + this.setState(accountStore.getState()); }, render: function () { diff --git a/src/utils/DockerUtil.js b/src/utils/DockerUtil.js index 75667bfc84..5260359909 100644 --- a/src/utils/DockerUtil.js +++ b/src/utils/DockerUtil.js @@ -4,6 +4,7 @@ import path from 'path'; import dockerode from 'dockerode'; import _ from 'underscore'; import util from './Util'; +import hubUtil from './HubUtil'; import metrics from '../utils/MetricsUtil'; import containerServerActions from '../actions/ContainerServerActions'; import Promise from 'bluebird'; @@ -144,7 +145,7 @@ export default { }); }, - run (auth, name, repository, tag) { + run (name, repository, tag) { tag = tag || 'latest'; let imageName = repository + ':' + tag; @@ -164,7 +165,7 @@ export default { this.placeholders[name] = placeholderData; localStorage.setItem('placeholders', JSON.stringify(this.placeholders)); - this.pullImage(auth, repository, tag, error => { + this.pullImage(repository, tag, error => { if (error) { containerServerActions.error({name, error}); return; @@ -333,8 +334,22 @@ export default { }); }, - pullImage (auth, repository, tag, callback, progressCallback, blockedCallback) { - this.client.pull(repository + ':' + tag, (err, stream) => { + pullImage (repository, tag, callback, progressCallback, blockedCallback) { + let opts = {}, config = hubUtil.config(); + if (!hubUtil.config()) { + opts = {}; + } else { + let [username, password] = hubUtil.creds(config); + opts = { + authconfig: { + username, + password, + auth: '' + } + }; + } + + this.client.pull(repository + ':' + tag, opts, (err, stream) => { if (err) { callback(err); return; @@ -354,8 +369,10 @@ export default { // data is associated with one layer only (can be identified with id) stream.on('data', str => { var data = JSON.parse(str); + console.log(data); if (data.error) { + callback(data.error); return; } @@ -405,8 +422,6 @@ export default { tick = null; for (let i = 0; i < columns.amount; i++) { columns.progress[i].value = 0.0; - - // Start only if the column has accurate values for all layers if (columns.progress[i].nbLayers > 0) { let layer; let totalSum = 0; @@ -419,7 +434,7 @@ export default { } if (totalSum > 0) { - columns.progress[i].value = 100.0 * currentSum / totalSum; + columns.progress[i].value = Math.min(100.0 * currentSum / totalSum, 100); } else { columns.progress[i].value = 0.0; } diff --git a/src/utils/HubUtil.js b/src/utils/HubUtil.js index e1830d661e..87a21a4094 100644 --- a/src/utils/HubUtil.js +++ b/src/utils/HubUtil.js @@ -1,13 +1,13 @@ +var _ = require('underscore'); var request = require('request'); var accountServerActions = require('../actions/AccountServerActions'); -var regHubUtil = require('./RegHubUtil'); module.exports = { init: function () { accountServerActions.prompted({prompted: localStorage.getItem('auth.prompted')}); let username = localStorage.getItem('auth.username'); let verified = localStorage.getItem('auth.verified') === 'true'; - if (username) { // TODO: check for config too + if (username) { accountServerActions.loggedin({username, verified}); } }, @@ -21,15 +21,6 @@ module.exports = { return config; }, - isPrompted: function () { - return localStorage.getItem('auth.prompted'); - }, - - prompted: function (prompted) { - localStorage.setItem('auth.prompted', true); - accountServerActions.prompted({prompted}); - }, - // Retrives the current jwt hub token or null if no token exists jwt: function () { let jwt = localStorage.getItem('auth.jwt'); @@ -39,12 +30,53 @@ module.exports = { return jwt; }, - post: function (req) { - + prompted: function () { + return localStorage.getItem('auth.prompted'); }, - get: function (req) { - // TODO: implement me and wrap all jwt calls + setPrompted: function (prompted) { + localStorage.setItem('auth.prompted', true); + accountServerActions.prompted({prompted}); + }, + + request: function (req, callback) { + let jwt = this.jwt(); + + if (jwt) { + _.extend(req, { + headers: { + Authorization: `JWT ${jwt}` + } + }); + } + + // First attempt with existing JWT + request(req, (error, response, body) => { + let data = JSON.parse(body); + + // If the JWT has expired, then log in again to get a new JWT + if (data && data.detail === 'Signature has expired.') { + let config = this.config(); + if (!this.config()) { + this.logout(); + return; + } + + let [username, password] = this.creds(config); + this.auth(username, password, (error, response, body) => { + let data = JSON.parse(body); + if (response.statusCode === 200 && data && data.token) { + localStorage.setItem('auth.jwt', data.token); + } else { + this.logout(); + } + + this.request(req, callback); + }); + } else { + callback(error, response, body); + } + }); }, loggedin: function () { @@ -59,10 +91,15 @@ module.exports = { localStorage.removeItem('auth.config'); }, - // Places a token under ~/.dockercfg and saves a jwt to localstore login: function (username, password) { - request.post('https://hub.docker.com/v2/users/login/', {form: {username, password}}, (err, response, body) => { + this.auth(username, password, (error, response, body) => { + if (error) { + accountServerActions.errors({errors: {detail: error.message}}); + return; + } + let data = JSON.parse(body); + if (response.statusCode === 200) { if (data.token) { localStorage.setItem('auth.jwt', data.token); @@ -70,9 +107,9 @@ module.exports = { localStorage.setItem('auth.verified', true); localStorage.setItem('auth.config', new Buffer(username + ':' + password).toString('base64')); accountServerActions.loggedin({username, verified: true}); - regHubUtil.repos(data.token); + require('./RegHubUtil').repos(); } else { - accountServerActions.errors({errors: {details: new Error('Did not receive login token.')}}); + accountServerActions.errors({errors: {detail: 'Did not receive login token.'}}); } } else if (response.statusCode === 401) { if (data && data.detail && data.detail.indexOf('Account not active yet') !== -1) { @@ -84,8 +121,12 @@ module.exports = { accountServerActions.errors({errors: data}); } } + }); + }, - + auth: function (username, password, callback) { + request.post('https://hub.docker.com/v2/users/login/', {form: {username, password}}, (error, response, body) => { + callback(error, response, body); }); }, @@ -96,10 +137,14 @@ module.exports = { return; } - let [username, password] = new Buffer(config, 'base64').toString().split(/:(.+)?/).slice(0, 2); + let [username, password] = this.creds(config); this.login(username, password); }, + creds: function (config) { + return new Buffer(config, 'base64').toString().split(/:(.+)?/).slice(0, 2); + }, + // Signs up and places a token under ~/.dockercfg and saves a jwt to localstore signup: function (username, password, email, subscribe) { request.post('https://hub.docker.com/v2/users/signup/', { diff --git a/src/utils/RegHubUtil.js b/src/utils/RegHubUtil.js index 0ecb9add7b..3975ce96aa 100644 --- a/src/utils/RegHubUtil.js +++ b/src/utils/RegHubUtil.js @@ -2,6 +2,7 @@ var _ = require('underscore'); var request = require('request'); var async = require('async'); var util = require('../utils/Util'); +var hubUtil = require('../utils/HubUtil'); var repositoryServerActions = require('../actions/RepositoryServerActions'); var tagServerActions = require('../actions/TagServerActions'); @@ -63,6 +64,7 @@ module.exports = { if (util.isOfficialRepo(name)) { name = 'library/' + name; } + request.get({ url: `https://registry.hub.docker.com/v2/repositories/${name}`, }, (error, response, body) => { @@ -84,14 +86,9 @@ module.exports = { }); }, - tags: function (jwt, repo) { - let headers = jwt ? { - Authorization: `JWT ${jwt}` - } : null; - - request.get({ - url: `https://registry.hub.docker.com/v2/repositories/${repo}/tags`, - headers + tags: function (repo) { + hubUtil.request({ + url: `https://registry.hub.docker.com/v2/repositories/${repo}/tags` }, (error, response, body) => { if (response.statusCode === 200) { let data = JSON.parse(body); @@ -103,20 +100,11 @@ module.exports = { }, // Returns the base64 encoded index token or null if no token exists - repos: function (jwt) { - if (!jwt) { - repositoryServerActions.reposUpdated({repos: []}); - return; - } - + repos: function () { repositoryServerActions.reposLoading({repos: []}); - // TODO: provide jwt - request.get({ + hubUtil.request({ url: 'https://registry.hub.docker.com/v2/namespaces/', - headers: { - Authorization: `JWT ${jwt}` - } }, (error, response, body) => { if (error) { repositoryServerActions.reposError({error}); @@ -126,11 +114,8 @@ module.exports = { let data = JSON.parse(body); let namespaces = data.namespaces; async.map(namespaces, (namespace, cb) => { - request.get({ - url: `https://registry.hub.docker.com/v2/repositories/${namespace}`, - headers: { - Authorization: `JWT ${jwt}` - } + hubUtil.request({ + url: `https://registry.hub.docker.com/v2/repositories/${namespace}` }, (error, response, body) => { if (error) { repositoryServerActions.reposError({error}); diff --git a/styles/container-home.less b/styles/container-home.less index bc808ea860..8a11e5d833 100644 --- a/styles/container-home.less +++ b/styles/container-home.less @@ -17,7 +17,7 @@ } .subtext { text-align: right; - color: @gray-lightest; + color: @gray-lighter; margin-top: 2px; transition: all 0.25s; &:hover {