diff --git a/app/ContainerDetails.react.js b/app/ContainerDetails.react.js index 585e4a0b38..2b25261c54 100644 --- a/app/ContainerDetails.react.js +++ b/app/ContainerDetails.react.js @@ -2,10 +2,12 @@ var _ = require('underscore'); var $ = require('jquery'); var React = require('react/addons'); var Router = require('react-router'); +var exec = require('exec'); +var remote = require('remote'); +var dialog = remote.require('dialog'); var ContainerStore = require('./ContainerStore'); var ContainerUtil = require('./ContainerUtil'); var docker = require('./docker'); -var exec = require('exec'); var boot2docker = require('./boot2docker'); var ProgressBar = require('react-bootstrap/ProgressBar'); @@ -29,10 +31,6 @@ var ContainerDetails = React.createClass({ }; }, componentWillReceiveProps: function () { - if (this.state.page === this.PAGE_SETTINGS) { - - } - console.log(this.props.container); this.init(); }, componentWillMount: function () { @@ -155,11 +153,16 @@ var ContainerDetails = React.createClass({ }); }, handleDeleteContainer: function () { - var container = this.props.container; - var name = container.Name.replace('/', ''); - ContainerStore.remove(name, function (err) { - console.error(err); - }); + dialog.showMessageBox({ + message: 'Are you sure you want to delete this container?', + buttons: ['Delete', 'Cancel'] + }, function (index) { + if (index === 0) { + ContainerStore.remove(this.props.container.Name, function (err) { + console.error(err); + }); + } + }.bind(this)); }, render: function () { var self = this; @@ -223,7 +226,7 @@ var ContainerDetails = React.createClass({ } else { if (this.state.page === this.PAGE_LOGS) { body = ( -
+
{logs}
@@ -281,7 +284,7 @@ var ContainerDetails = React.createClass({ 'btn-action': true, 'only-icon': true, 'active': this.state.page === this.PAGE_LOGS, - disabled: !this.props.container.State.Running + disabled: this.props.container.State.Downloading }); var gearButtonClass = React.addons.classSet({ @@ -289,7 +292,7 @@ var ContainerDetails = React.createClass({ 'btn-action': true, 'only-icon': true, 'active': this.state.page === this.PAGE_SETTINGS, - disabled: !this.props.container.State.Running + disabled: this.props.container.State.Downloading }); return ( diff --git a/app/ContainerStore.js b/app/ContainerStore.js index bd07c2c69e..504bd658d7 100644 --- a/app/ContainerStore.js +++ b/app/ContainerStore.js @@ -3,13 +3,14 @@ var async = require('async'); var assign = require('object-assign'); var Stream = require('stream'); var Convert = require('ansi-to-html'); -var convert = new Convert(); var docker = require('./docker'); var registry = require('./registry'); var ContainerUtil = require('./ContainerUtil'); var $ = require('jquery'); var _ = require('underscore'); +var convert = new Convert(); + var _recommended = []; var _containers = {}; var _progress = {}; @@ -120,15 +121,19 @@ var ContainerStore = assign(EventEmitter.prototype, { callback(err, null); return; } - container.start({ - PublishAllPorts: true - }, function (err) { - if (err) { - callback(err); - return; - } + if (containerData.State && !containerData.State.Running) { self.fetchContainer(name, callback); - }); + } else { + container.start({ + PublishAllPorts: true + }, function (err) { + if (err) { + callback(err); + return; + } + self.fetchContainer(name, callback); + }); + } }); }); }); @@ -204,15 +209,18 @@ var ContainerStore = assign(EventEmitter.prototype, { // If the event is delete, remove the container if (data.status === 'destroy') { var container = _.findWhere(_.values(_containers), {Id: data.id}); - if (_muted[container.Name]) { + if (!container || _muted[container.Name]) { return; } delete _containers[container.Name]; this.emit(this.SERVER_CONTAINER_EVENT, container.Name, data.status); } else { this.fetchContainer(data.id, function (err) { + if (err) { + return; + } var container = _.findWhere(_.values(_containers), {Id: data.id}); - if (_muted[container.Name]) { + if (!container || _muted[container.Name]) { return; } this.emit(this.SERVER_CONTAINER_EVENT, container ? container.Name : null, data.status); @@ -236,6 +244,10 @@ var ContainerStore = assign(EventEmitter.prototype, { if (err) { callback(err); } else { + if (container.Config.Image === container.Image.slice(0, 12) || container.Config.Image === container.Image) { + callback(); + return; + } // Fix leading slash in container names container.Name = container.Name.replace('/', ''); @@ -278,7 +290,6 @@ var ContainerStore = assign(EventEmitter.prototype, { async.map(recommended, function (repository, callback) { $.get('https://registry.hub.docker.com/v1/search?q=' + repository, function (data) { var results = data.results; - console.log(repository, data); callback(null, _.find(results, function (r) { return r.name === repository; })); @@ -296,8 +307,9 @@ var ContainerStore = assign(EventEmitter.prototype, { fetchLogs: function (name, callback) { if (_logs[name]) { callback(); + } else { + _logs[name] = []; } - _logs[name] = []; var index = 0; var self = this; docker.client().getContainer(name).logs({ @@ -316,11 +328,11 @@ var ContainerStore = assign(EventEmitter.prototype, { var time = buf.substr(0,buf.indexOf(' ')); var msg = buf.substr(buf.indexOf(' ')+1); _logs[name].push(convert.toHtml(self._escapeHTML(msg))); - self.emit(self.SERVER_LOGS_EVENT, name); } index += 1; }); stream.on('end', function (buf) { + self.emit(self.SERVER_LOGS_EVENT, name); callback(); docker.client().getContainer(name).logs({ follow: true, @@ -329,6 +341,9 @@ var ContainerStore = assign(EventEmitter.prototype, { timestamps: true, tail: 0 }, function (err, stream) { + if (err) { + return; + } stream.setEncoding('utf8'); stream.on('data', function (buf) { // Every other message is a header @@ -357,10 +372,13 @@ var ContainerStore = assign(EventEmitter.prototype, { self._createPlaceholderContainer(imageName, containerName, function (err, container) { _containers[containerName] = container; self.emit(self.CLIENT_CONTAINER_EVENT, containerName, 'create'); + _muted[containerName] = true; _progress[containerName] = 0; self._pullImage(repository, tag, function () { self._createContainer(containerName, {Image: imageName}, function (err, container) { delete _progress[containerName]; + _muted[containerName] = false; + self.emit(self.CLIENT_CONTAINER_EVENT, containerName); }); }, function (progress) { _progress[containerName] = progress; @@ -382,7 +400,6 @@ var ContainerStore = assign(EventEmitter.prototype, { var fullData = assign(_containers[name], data); this._createContainer(name, fullData, function (err) { this.emit(this.CLIENT_CONTAINER_EVENT, name); - console.log(err); _muted[name] = false; }.bind(this)); }, diff --git a/app/Containers.react.js b/app/Containers.react.js index 45ab3f9fd5..45789f8db0 100644 --- a/app/Containers.react.js +++ b/app/Containers.react.js @@ -52,7 +52,6 @@ var Containers = React.createClass({ sorted: ContainerStore.sorted() }); if (status === 'create') { - console.log('transition'); this.transitionTo('container', {name: name}); } }, diff --git a/app/Radial.react.js b/app/Radial.react.js index c91c898445..3eda034f15 100644 --- a/app/Radial.react.js +++ b/app/Radial.react.js @@ -12,7 +12,8 @@ var Radial = React.createClass({ } var classes = React.addons.classSet({ 'radial-progress': true, - 'radial-spinner': this.props.spin + 'radial-spinner': this.props.spin, + 'radial-negative': this.props.error }); return (
diff --git a/app/Setup.react.js b/app/Setup.react.js index 9f8661eb9e..16f8928b31 100644 --- a/app/Setup.react.js +++ b/app/Setup.react.js @@ -14,9 +14,10 @@ var ContainerStore = require('./ContainerStore.js'); var setupSteps = [ { run: function (callback, progressCallback) { + console.log(util.supportDir()); var installed = virtualbox.installed(); if (!installed) { - util.download('https://s3.amazonaws.com/kite-installer/' + virtualbox.INSTALLER_FILENAME, path.join(process.cwd(), 'resources', virtualbox.INSTALLER_FILENAME), virtualbox.INSTALLER_CHECKSUM, function (err) { + util.download('https://s3.amazonaws.com/kite-installer/' + virtualbox.INSTALLER_FILENAME, path.join(util.supportDir(), virtualbox.INSTALLER_FILENAME), virtualbox.INSTALLER_CHECKSUM, function (err) { if (err) {callback(err); return;} virtualbox.install(function (err) { if (!virtualbox.installed()) { @@ -144,15 +145,24 @@ var Setup = React.createClass({ var radial; if (this.state.progress) { radial = ; - } else { - radial = ; + } else if (this.state.error) { + radial = ; + } + if (this.state.error) { + return ( +
+ {radial} +

Error: {this.state.error}

+
+ ); + } else { + return ( +
+ {radial} +

{this.state.message}

+
+ ); } - return ( -
- {radial} -

{this.state.message}

-
- ); }, componentWillMount: function () { this.setState({}); @@ -160,10 +170,12 @@ var Setup = React.createClass({ componentDidMount: function () { var self = this; this.setup(function (err) { - boot2docker.ip(function (err, ip) { - docker.setHost(ip); - self.transitionTo('containers'); - }); + if (!err) { + boot2docker.ip(function (err, ip) { + docker.setHost(ip); + self.transitionTo('containers'); + }); + } }); }, setup: function (callback) { @@ -188,7 +200,7 @@ var Setup = React.createClass({ // if any of the steps fail console.log('Kitematic setup failed at step ' + currentStep); console.log(err); - self.setState({error: err}); + self.setState({error: err.message}); callback(err); } else { // Setup Finished diff --git a/app/boot2docker.js b/app/boot2docker.js index 1c90bfe105..8e64baedc4 100644 --- a/app/boot2docker.js +++ b/app/boot2docker.js @@ -54,7 +54,13 @@ var Boot2Docker = { return path.join(process.cwd(), 'resources', 'boot2docker-' + this.version()); }, exists: function (callback) { - cmdExec([Boot2Docker.command(), 'info'], callback); + cmdExec([Boot2Docker.command(), 'info'], function (err, out) { + if (err) { + callback(null, false); + } else { + callback(null, true); + } + }); }, status: function (callback) { cmdExec([Boot2Docker.command(), 'status'], function (err, out) { diff --git a/app/main.js b/app/main.js index e508dc3261..52ec623ddc 100644 --- a/app/main.js +++ b/app/main.js @@ -24,7 +24,7 @@ Bugsnag.releaseStage = process.env.NODE_ENV === 'development' ? 'development' : Bugsnag.notifyReleaseStages = []; Bugsnag.appVersion = app.getVersion(); -if (window.location.hash === '#/') { +if (!window.location.hash.length || window.location.hash === '#/') { router.run(function (Handler) { React.render(, document.body); }); diff --git a/app/styles/containers.less b/app/styles/containers.less index ef62d65a91..6882fb166a 100644 --- a/app/styles/containers.less +++ b/app/styles/containers.less @@ -61,6 +61,7 @@ overflow-y: scroll; overflow-x: hidden; box-sizing: border-box; + max-width: 280px; &.sep { border-top: 1px solid #eee; diff --git a/app/styles/setup.less b/app/styles/setup.less index f1b5324c3b..8537b00717 100644 --- a/app/styles/setup.less +++ b/app/styles/setup.less @@ -3,6 +3,9 @@ text-align: center; p { + &.error { + color: @brand-danger; + } margin-top: 20px; } } diff --git a/app/util.js b/app/util.js index 6df25646c6..78a4c61035 100644 --- a/app/util.js +++ b/app/util.js @@ -6,9 +6,23 @@ var progress = require('request-progress'); var exec = require('exec'); var Util = { + supportDir: function (callback) { + var dirs = ['Application\ Support', 'Kitematic']; + var acc = process.env.HOME; + dirs.forEach(function (d) { + acc = path.join(acc, d); + if (!fs.existsSync(acc)) { + fs.mkdirSync(acc); + } + }); + return acc; + }, download: function (url, filename, checksum, callback, progressCallback) { var doDownload = function () { - progress(request(url), { + progress(request({ + uri: url, + rejectUnauthorized: false + }), { throttle: 250 }).on('progress', function (state) { progressCallback(state.percent); diff --git a/app/virtualbox.js b/app/virtualbox.js index 5516643348..a7192793de 100644 --- a/app/virtualbox.js +++ b/app/virtualbox.js @@ -2,6 +2,7 @@ var fs = require('fs'); var exec = require('exec'); var path = require('path'); var async = require('async'); +var util = require('./util'); var VirtualBox = { REQUIRED_VERSION: '4.3.18', @@ -16,7 +17,7 @@ var VirtualBox = { }, install: function (callback) { // -W waits for the process to close before finishing. - exec('open -W ' + path.join(process.cwd(), 'resources', this.INSTALLER_FILENAME).replace(' ', '\\ '), function (stderr, stdout, code) { + exec('open -W ' + path.join(util.supportDir(), this.INSTALLER_FILENAME).replace(' ', '\\ '), function (stderr, stdout, code) { if (code) { callback(stderr); return; diff --git a/resources/virtualbox-4.3.18.pkg b/resources/virtualbox-4.3.18.pkg new file mode 100644 index 0000000000..e69de29bb2