Merge pull request #370 from kitematic/jeffdm-windows

Updating Windows Branch with latest changes
This commit is contained in:
Jeffrey Morgan 2015-04-08 10:20:43 -04:00
commit 5884b9201b
29 changed files with 191 additions and 118 deletions

3
.gitignore vendored
View File

@ -3,6 +3,7 @@
build build
dist dist
node_modules node_modules
coverage
npm-debug.log npm-debug.log
# Signing Identity # Signing Identity
@ -19,4 +20,4 @@ cache
settings.json settings.json
# IDEs # IDEs
.idea .idea

View File

@ -27,5 +27,5 @@
"jest": true, "jest": true,
"pit": true "pit": true
}, },
"predef": [ "-Promise" ] "predef": [ "Promise" ]
} }

13
.travis.yml Normal file
View File

@ -0,0 +1,13 @@
language: node_js
node_js:
- "0.10"
sudo: false
cache:
directories:
- resources
- node_modules
after_success:
- which ./node_modules/coveralls/bin/coveralls.js && cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js

View File

@ -53,6 +53,10 @@ We're thrilled to receive pull requests of any kind. Anything from bug fix, test
That said, please let us know what you're planning to do! For large changes always create a proposal. Maintainers will love to give you advice on building it and it keeps the app's design coherent. That said, please let us know what you're planning to do! For large changes always create a proposal. Maintainers will love to give you advice on building it and it keeps the app's design coherent.
### Pull Request Requirements:
- Tests
- [Signed Off](https://github.com/docker/docker/blob/master/CONTRIBUTING.md#sign-your-work)
## Code Guidelines ## Code Guidelines
### Javascript ### Javascript

View File

@ -1,4 +1,5 @@
[![CircleCI](https://img.shields.io/circleci/project/kitematic/kitematic.svg)](https://circleci.com/gh/kitematic/kitematic/tree/master) [![Build Status](https://travis-ci.org/kitematic/kitematic.svg?branch=master)](https://travis-ci.org/kitematic/kitematic)
[![Coverage Status](https://coveralls.io/repos/kitematic/kitematic/badge.svg?branch=master)](https://coveralls.io/r/kitematic/kitematic?branch=master)
[![bitHound Score](https://app.bithound.io/kitematic/kitematic/badges/score.svg)](http://app.bithound.io/kitematic/kitematic) [![bitHound Score](https://app.bithound.io/kitematic/kitematic/badges/score.svg)](http://app.bithound.io/kitematic/kitematic)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/kitematic/kitematic?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/kitematic/kitematic?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)

View File

@ -5,3 +5,6 @@ dependencies:
cache_directories: cache_directories:
- "resources" - "resources"
- "node_modules" - "node_modules"
notify:
webhooks:
- url: https://coveralls.io/webhook

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -1,6 +1,6 @@
{ {
"name": "Kitematic", "name": "Kitematic",
"version": "0.5.11", "version": "0.5.13",
"author": "Kitematic", "author": "Kitematic",
"description": "Simple Docker Container management for Mac OS X.", "description": "Simple Docker Container management for Mac OS X.",
"homepage": "https://kitematic.com/", "homepage": "https://kitematic.com/",
@ -12,7 +12,7 @@
"bugs": "https://github.com/kitematic/kitematic/issues", "bugs": "https://github.com/kitematic/kitematic/issues",
"scripts": { "scripts": {
"start": "gulp", "start": "gulp",
"test": "jest", "test": "jest --coverage",
"release": "gulp release", "release": "gulp release",
"release:beta": "gulp release --beta", "release:beta": "gulp release --beta",
"lint": "jsxhint src && jsxhint browser", "lint": "jsxhint src && jsxhint browser",
@ -27,25 +27,30 @@
"jest": { "jest": {
"scriptPreprocessor": "<rootDir>/util/preprocessor.js", "scriptPreprocessor": "<rootDir>/util/preprocessor.js",
"setupEnvScriptFile": "<rootDir>/util/testenv.js", "setupEnvScriptFile": "<rootDir>/util/testenv.js",
"collectCoverage": true,
"testDirectoryName": "src",
"testPathIgnorePatterns": [
"/node_modules/",
"^((?!-test).)*$"
],
"unmockedModulePathPatterns": [ "unmockedModulePathPatterns": [
"stream",
"tty", "tty",
"net", "net",
"crypto", "crypto",
"stream", "<rootDir>/node_modules/.*JSONStream",
"<rootDir>/node_modules/object-assign", "<rootDir>/node_modules/object-assign",
"<rootDir>/node_modules/underscore", "<rootDir>/node_modules/underscore",
"<rootDir>/node_modules/react", "<rootDir>/node_modules/bluebird"
"<rootDir>/node_modules/bluebird",
"<rootDir>/node_modules/babel"
] ]
}, },
"docker-version": "1.5.0", "docker-version": "1.5.0",
"docker-machine-version": "0.1.0-kitematic-0.5.10", "docker-machine-version": "0.1.0-kitematic-0.5.10",
"atom-shell-version": "0.21.3", "atom-shell-version": "0.21.3",
"virtualbox-version": "4.3.24", "virtualbox-version": "4.3.26",
"virtualbox-filename": "VirtualBox-4.3.24.pkg", "virtualbox-filename": "VirtualBox-4.3.26.pkg",
"virtualbox-filename-win": "VirtualBox-4.3.26.exe", "virtualbox-filename-win": "VirtualBox-4.3.26.exe",
"virtualbox-checksum": "100eee21df3808fc1b1e461e86f10b6d2a748935c5f31bc665f4ff0777e44a0b", "virtualbox-checksum": "668f61c95efe37f8fc65cafe95b866fba64e37f2492dfc1e2b44a7ac3dcafa3b",
"virtualbox-checksum-win": "9cb265babf307d825f5178693af95ffca077f80ae22cf43868c3538c159123ff", "virtualbox-checksum-win": "9cb265babf307d825f5178693af95ffca077f80ae22cf43868c3538c159123ff",
"dependencies": { "dependencies": {
"ansi-to-html": "0.3.0", "ansi-to-html": "0.3.0",
@ -53,7 +58,8 @@
"async": "^0.9.0", "async": "^0.9.0",
"bluebird": "^2.9.12", "bluebird": "^2.9.12",
"bugsnag-js": "^2.4.7", "bugsnag-js": "^2.4.7",
"dockerode": "^2.0.7", "coveralls": "^2.11.2",
"dockerode": "^2.1.1",
"exec": "0.2.0", "exec": "0.2.0",
"fs-extra": "^0.17.0", "fs-extra": "^0.17.0",
"fs-promise": "^0.3.1", "fs-promise": "^0.3.1",
@ -81,6 +87,7 @@
"gulp-cssmin": "^0.1.6", "gulp-cssmin": "^0.1.6",
"gulp-download-atom-shell": "0.0.4", "gulp-download-atom-shell": "0.0.4",
"gulp-if": "^1.2.5", "gulp-if": "^1.2.5",
"gulp-insert": "^0.4.0",
"gulp-less": "^3.0.1", "gulp-less": "^3.0.1",
"gulp-livereload": "^3.8.0", "gulp-livereload": "^3.8.0",
"gulp-plumber": "^0.6.6", "gulp-plumber": "^0.6.6",
@ -89,7 +96,7 @@
"gulp-shell": "^0.3.0", "gulp-shell": "^0.3.0",
"gulp-sourcemaps": "^1.5.0", "gulp-sourcemaps": "^1.5.0",
"gulp-util": "^3.0.4", "gulp-util": "^3.0.4",
"jest-cli": "^0.4.0", "jest-cli": "kitematic/jest",
"jsxhint": "^0.12.1", "jsxhint": "^0.12.1",
"react-tools": "^0.12.2", "react-tools": "^0.12.2",
"run-sequence": "^1.0.2" "run-sequence": "^1.0.2"

View File

@ -74,7 +74,9 @@ var ContainerHome = React.createClass({
if (this.props.error) { if (this.props.error) {
body = ( body = (
<div className="details-progress"> <div className="details-progress">
<h3>There was a problem connecting to the Docker Engine in the VirtualBox VM.<br/>This could be caused because this Mac is currently connected to a VPN, blocking access to the VM. If the issue persists, please <a onClick={this.handleErrorClick}>file a ticket on our GitHub repo.</a></h3> <h3>An error occurred:</h3>
<h2>{this.props.error.statusCode} {this.props.error.reason} - {this.props.error.json}</h2>
<h3>If you feel that this error is invalid, please <a onClick={this.handleErrorClick}>file a ticket on our GitHub repo.</a></h3>
<Radial progress={100} error={true} thick={true} transparent={true}/> <Radial progress={100} error={true} thick={true} transparent={true}/>
</div> </div>
); );

View File

@ -2,20 +2,46 @@ var _ = require('underscore');
var React = require('react/addons'); var React = require('react/addons');
var RetinaImage = require('react-retina-image'); var RetinaImage = require('react-retina-image');
var path = require('path'); var path = require('path');
var exec = require('exec'); var shell = require('shell');
var util = require('./Util');
var metrics = require('./Metrics'); var metrics = require('./Metrics');
var Router = require('react-router'); var Router = require('react-router');
var util = require('./Util'); var ContainerStore = require('./ContainerStore');
var ContainerHomeFolder = React.createClass({ var ContainerHomeFolder = React.createClass({
mixins: [Router.State, Router.Navigation], mixins: [Router.State, Router.Navigation],
handleClickFolder: function (path) { handleClickFolder: function (hostVolume, containerVolume) {
metrics.track('Opened Volume Directory', { metrics.track('Opened Volume Directory', {
from: 'home' from: 'home'
}); });
util.openPathOrUrl(path, function (err) {
if (err) { throw err; } if (hostVolume.indexOf(process.env.HOME) === -1) {
}); var volumes = _.clone(this.props.container.Volumes);
var newHostVolume = path.join(util.home(), 'Kitematic', this.props.container.Name, containerVolume);
volumes[containerVolume] = newHostVolume;
var binds = _.pairs(volumes).map(function (pair) {
if(util.isWindows()) {
var home = util.home();
home = home.charAt(0).toLowerCase() + home.slice(1);
home = '/' + home.replace(':', '').replace(/\\/g, '/');
var fullPath = path.join(home, 'Kitematic', pair[1], pair[0]);
fullPath = fullPath.replace(/\\/g, '/');
return fullPath + ':' + pair[0];
}
return pair[1] + ':' + pair[0];
});
ContainerStore.updateContainer(this.props.container.Name, {
Binds: binds
}, function (err) {
if (err) {
console.log(err);
return;
}
shell.showItemInFolder(newHostVolume);
});
} else {
shell.showItemInFolder(hostVolume);
}
}, },
handleClickChangeFolders: function () { handleClickChangeFolders: function () {
metrics.track('Viewed Volume Settings', { metrics.track('Viewed Volume Settings', {
@ -24,24 +50,21 @@ var ContainerHomeFolder = React.createClass({
this.transitionTo('containerSettingsVolumes', {name: this.getParams().name}); this.transitionTo('containerSettingsVolumes', {name: this.getParams().name});
}, },
render: function () { render: function () {
var folders; if (!this.props.container) {
if (this.props.container) { return false;
var self = this;
folders = _.map(self.props.container.Volumes, function (val, key) {
var firstFolder = key.split(path.sep)[1];
if (!val || val.indexOf(process.env.HOME) === -1) {
return;
} else {
return (
<div key={key} className="folder" onClick={self.handleClickFolder.bind(self, val)}>
<RetinaImage src="folder.png" />
<div className="text">{firstFolder}</div>
</div>
);
}
});
} }
if (this.props.container && this.props.container.Volumes && _.keys(this.props.container.Volumes).length > 0 && this.props.container.State.Running) {
var folders = _.map(this.props.container.Volumes, (val, key) => {
var firstFolder = key.split(path.sep)[1];
return (
<div key={key} className="folder" onClick={this.handleClickFolder.bind(this, val, key)}>
<RetinaImage src="folder.png" />
<div className="text">{firstFolder}</div>
</div>
);
});
if (this.props.container.Volumes && _.keys(this.props.container.Volumes).length > 0 && this.props.container.State.Running) {
return ( return (
<div className="folders wrapper"> <div className="folders wrapper">
<h4>Edit Files</h4> <h4>Edit Files</h4>
@ -52,9 +75,7 @@ var ContainerHomeFolder = React.createClass({
</div> </div>
); );
} else { } else {
return ( return false;
<div></div>
);
} }
} }
}); });

View File

@ -52,6 +52,9 @@ var ContainerHomeLogs = React.createClass({
var logs = this.state.logs.map(function (l, i) { var logs = this.state.logs.map(function (l, i) {
return <p key={i} dangerouslySetInnerHTML={{__html: l}}></p>; return <p key={i} dangerouslySetInnerHTML={{__html: l}}></p>;
}); });
if (logs.length === 0) {
logs = "No logs for this container.";
}
return ( return (
<div className="mini-logs wrapper"> <div className="mini-logs wrapper">
<h4>Logs</h4> <h4>Logs</h4>

View File

@ -38,9 +38,6 @@ var ContainerHomePreview = React.createClass({
}); });
} }
}, },
componentDidUpdate: function () {
this.reload();
},
componentWillUnmount: function() { componentWillUnmount: function() {
clearInterval(this.timer); clearInterval(this.timer);
}, },

View File

@ -9,8 +9,14 @@ var ContainerList = React.createClass({
render: function () { render: function () {
var self = this; var self = this;
var containers = this.props.containers.map(function (container) { var containers = this.props.containers.map(function (container) {
var containerId = container.Id;
if (!containerId && container.State.Downloading) {
// Fall back to the container image name when there is no id. (when the
// image is downloading).
containerId = container.Image;
}
return ( return (
<ContainerListItem key={container.Id} container={container} start={self._start}/> <ContainerListItem key={containerId} container={container} start={self._start} />
); );
}); });
var newItem; var newItem;

View File

@ -45,6 +45,9 @@ var ContainerLogs = React.createClass({
var logs = this.state.logs.map(function (l, i) { var logs = this.state.logs.map(function (l, i) {
return <p key={i} dangerouslySetInnerHTML={{__html: l}}></p>; return <p key={i} dangerouslySetInnerHTML={{__html: l}}></p>;
}); });
if (logs.length === 0) {
logs = "No logs for this container.";
}
return ( return (
<div className="details-panel details-logs logs"> <div className="details-panel details-logs logs">
{logs} {logs}

View File

@ -2,7 +2,6 @@ var _ = require('underscore');
var React = require('react/addons'); var React = require('react/addons');
var Router = require('react-router'); var Router = require('react-router');
var remote = require('remote'); var remote = require('remote');
var exec = require('exec');
var dialog = remote.require('dialog'); var dialog = remote.require('dialog');
var metrics = require('./Metrics'); var metrics = require('./Metrics');
var ContainerStore = require('./ContainerStore'); var ContainerStore = require('./ContainerStore');
@ -32,6 +31,21 @@ var ContainerSettingsVolumes = React.createClass({
} }
}); });
}, },
handleRemoveVolumeClick: function (dockerVol) {
metrics.track('Removed Volume Directory', {
from: 'settings'
});
var volumes = _.clone(this.props.container.Volumes);
delete volumes[dockerVol];
var binds = _.pairs(volumes).map(function (pair) {
return pair[1] + ':' + pair[0];
});
ContainerStore.updateContainer(this.props.container.Name, {
Binds: binds
}, function (err) {
if (err) { console.log(err); }
});
},
handleOpenVolumeClick: function (path) { handleOpenVolumeClick: function (path) {
metrics.track('Opened Volume Directory', { metrics.track('Opened Volume Directory', {
from: 'settings' from: 'settings'
@ -51,6 +65,7 @@ var ContainerSettingsVolumes = React.createClass({
<span> <span>
<a className="value-right">No Folder</a> <a className="value-right">No Folder</a>
<a className="btn btn-action small" onClick={self.handleChooseVolumeClick.bind(self, key)}>Change</a> <a className="btn btn-action small" onClick={self.handleChooseVolumeClick.bind(self, key)}>Change</a>
<a className="btn btn-action small" onClick={self.handleRemoveVolumeClick.bind(self, key)}>Remove</a>
</span> </span>
); );
} else { } else {
@ -58,6 +73,7 @@ var ContainerSettingsVolumes = React.createClass({
<span> <span>
<a className="value-right" onClick={self.handleOpenVolumeClick.bind(self, val)}>{val.replace(process.env.HOME, '~')}</a> <a className="value-right" onClick={self.handleOpenVolumeClick.bind(self, val)}>{val.replace(process.env.HOME, '~')}</a>
<a className="btn btn-action small" onClick={self.handleChooseVolumeClick.bind(self, key)}>Change</a> <a className="btn btn-action small" onClick={self.handleChooseVolumeClick.bind(self, key)}>Change</a>
<a className="btn btn-action small" onClick={self.handleRemoveVolumeClick.bind(self, key)}>Remove</a>
</span> </span>
); );
} }

View File

@ -1,14 +1,12 @@
var _ = require('underscore'); var _ = require('underscore');
var EventEmitter = require('events').EventEmitter; var EventEmitter = require('events').EventEmitter;
var async = require('async'); var async = require('async');
var path = require('path');
var assign = require('object-assign'); var assign = require('object-assign');
var docker = require('./Docker'); var docker = require('./Docker');
var metrics = require('./Metrics'); var metrics = require('./Metrics');
var registry = require('./Registry'); var registry = require('./Registry');
var logstore = require('./LogStore'); var logstore = require('./LogStore');
var bugsnag = require('bugsnag-js'); var bugsnag = require('bugsnag-js');
var util = require('./Util');
var _placeholders = {}; var _placeholders = {};
var _containers = {}; var _containers = {};
@ -58,6 +56,12 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
var data = JSON.parse(str); var data = JSON.parse(str);
console.log(data); console.log(data);
if (data.error) {
_error = data.error;
callback(data.error);
return;
}
if (data.status && (data.status === 'Pulling dependent layers' || data.status.indexOf('already being pulled by another client') !== -1)) { if (data.status && (data.status === 'Pulling dependent layers' || data.status.indexOf('already being pulled by another client') !== -1)) {
blockedCallback(); blockedCallback();
return; return;
@ -84,7 +88,8 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
progressCallback(totalProgress); progressCallback(totalProgress);
}); });
stream.on('end', function () { stream.on('end', function () {
callback(); callback(_error);
_error = null;
}); });
}); });
}); });
@ -92,48 +97,22 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
}, },
_startContainer: function (name, containerData, callback) { _startContainer: function (name, containerData, callback) {
var self = this; var self = this;
docker.client().getImage(containerData.Image).inspect(function (err, data) { var binds = containerData.Binds || [];
var startopts = {
Binds: binds
};
if (containerData.NetworkSettings && containerData.NetworkSettings.Ports) {
startopts.PortBindings = containerData.NetworkSettings.Ports;
} else{
startopts.PublishAllPorts = true;
}
var container = docker.client().getContainer(name);
container.start(startopts, function (err) {
if (err) { if (err) {
callback(err); callback(err);
return; return;
} }
var binds = containerData.Binds || []; self.fetchContainer(name, callback);
if (data.Config.Volumes) {
_.each(data.Config.Volumes, function (value, key) {
var existingBind = _.find(binds, b => {
return b.indexOf(':' + key) !== -1;
});
if (!existingBind) {
var home = util.home();
if(util.isWindows()) {
home = home.charAt(0).toLowerCase() + home.slice(1);
home = "/" + home.replace(':', '').replace(/\\/g, '/');
var fullPath = path.join(home, 'Kitematic', name, key);
fullPath = fullPath.replace(/\\/g, '/');
binds.push(fullPath + ':' + key);
} else {
binds.push(path.join(home, 'Kitematic', name, key) + ':' + key);
}
}
});
}
var startopts = {
Binds: binds
};
if (containerData.NetworkSettings && containerData.NetworkSettings.Ports) {
startopts.PortBindings = containerData.NetworkSettings.Ports;
} else{
startopts.PublishAllPorts = true;
}
var container = docker.client().getContainer(name);
container.start(startopts, function (err) {
if (err) {
callback(err);
return;
}
self.fetchContainer(name, callback);
});
}); });
}, },
_createContainer: function (name, containerData, callback) { _createContainer: function (name, containerData, callback) {
@ -147,6 +126,9 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
if (containerData.Config && containerData.Config.Image) { if (containerData.Config && containerData.Config.Image) {
containerData.Image = containerData.Config.Image; containerData.Image = containerData.Config.Image;
} }
if (!containerData.Env && containerData.Config && containerData.Config.Env) {
containerData.Env = containerData.Config.Env;
}
existing.kill(function () { existing.kill(function () {
existing.remove(function () { existing.remove(function () {
docker.client().createContainer(containerData, function (err) { docker.client().createContainer(containerData, function (err) {
@ -154,11 +136,7 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
callback(err, null); callback(err, null);
return; return;
} }
if (containerData.State && !containerData.State.Running) { self._startContainer(name, containerData, callback);
self.fetchContainer(containerData.name, callback);
} else {
self._startContainer(name, containerData, callback);
}
}); });
}); });
}); });

View File

@ -5,6 +5,7 @@ var ContainerStore = require('./ContainerStore');
var metrics = require('./Metrics'); var metrics = require('./Metrics');
var OverlayTrigger = require('react-bootstrap').OverlayTrigger; var OverlayTrigger = require('react-bootstrap').OverlayTrigger;
var Tooltip = require('react-bootstrap').Tooltip; var Tooltip = require('react-bootstrap').Tooltip;
var util = require('./Util');
var ImageCard = React.createClass({ var ImageCard = React.createClass({
getInitialState: function () { getInitialState: function () {
@ -30,16 +31,33 @@ var ImageCard = React.createClass({
handleTagOverlayClick: function (name) { handleTagOverlayClick: function (name) {
var $tagOverlay = $(this.getDOMNode()).find('.tag-overlay'); var $tagOverlay = $(this.getDOMNode()).find('.tag-overlay');
$tagOverlay.fadeIn(300); $tagOverlay.fadeIn(300);
$.get('https://registry.hub.docker.com/v1/repositories/' + name + '/tags', function (result) { $.get('https://registry.hub.docker.com/v1/repositories/' + name + '/tags', result => {
this.setState({ this.setState({
tags: result tags: result
}); });
}.bind(this)); });
}, },
handleCloseTagOverlay: function () { handleCloseTagOverlay: function () {
var $tagOverlay = $(this.getDOMNode()).find('.tag-overlay'); var $tagOverlay = $(this.getDOMNode()).find('.tag-overlay');
$tagOverlay.fadeOut(300); $tagOverlay.fadeOut(300);
}, },
handleRepoClick: function () {
var $repoUri = 'https://registry.hub.docker.com/';
if (this.props.image.is_official) {
$repoUri = $repoUri + "_/";
} else {
$repoUri = $repoUri + "u/";
}
util.exec(['open', $repoUri + this.props.image.name]);
},
componentDidMount: function() {
$.get('https://registry.hub.docker.com/v1/repositories/' + this.props.image.name + '/tags', result => {
this.setState({
tags: result,
chosenTag: result[0].name
});
});
},
render: function () { render: function () {
var self = this; var self = this;
var name; var name;
@ -57,8 +75,8 @@ var ImageCard = React.createClass({
name = ( name = (
<div> <div>
<div className="namespace official">{namespace}</div> <div className="namespace official">{namespace}</div>
<OverlayTrigger placement="bottom" overlay={<Tooltip>{this.props.image.name}</Tooltip>}> <OverlayTrigger placement="bottom" overlay={<Tooltip>View on DockerHub</Tooltip>}>
<span className="repo">{repo}</span> <span className="repo" onClick={this.handleRepoClick}>{repo}</span>
</OverlayTrigger> </OverlayTrigger>
</div> </div>
); );
@ -66,8 +84,8 @@ var ImageCard = React.createClass({
name = ( name = (
<div> <div>
<div className="namespace">{namespace}</div> <div className="namespace">{namespace}</div>
<OverlayTrigger placement="bottom" overlay={<Tooltip>{this.props.image.name}</Tooltip>}> <OverlayTrigger placement="bottom" overlay={<Tooltip>View on DockerHub</Tooltip>}>
<span className="repo">{repo}</span> <span className="repo" onClick={this.handleRepoClick}>{repo}</span>
</OverlayTrigger> </OverlayTrigger>
</div> </div>
); );

View File

@ -1,10 +1,9 @@
jest.dontMock('../src/SetupStore'); jest.dontMock('./SetupStore');
var setupStore = require('../src/SetupStore'); var setupStore = require('./SetupStore');
var virtualBox = require('../src/VirtualBox'); var virtualBox = require('./VirtualBox');
var util = require('../src/Util'); var util = require('./Util');
var machine = require('../src/DockerMachine'); var machine = require('./DockerMachine');
var setupUtil = require('../src/SetupUtil'); var setupUtil = require('./SetupUtil');
var Promise = require('bluebird');
describe('SetupStore', function () { describe('SetupStore', function () {
describe('download step', function () { describe('download step', function () {

View File

@ -180,7 +180,7 @@ var SetupStore = assign(Object.create(EventEmitter.prototype), {
var required = {}; var required = {};
var vboxfile = path.join(util.supportDir(), setupUtil.virtualBoxFileName()); var vboxfile = path.join(util.supportDir(), setupUtil.virtualBoxFileName());
var vboxNeedsInstall = !virtualBox.installed(); var vboxNeedsInstall = !virtualBox.installed();
required.download = vboxNeedsInstall && (!fs.existsSync(vboxfile) || setupUtil.checksum(vboxfile) !== setupUtil.virtualBoxChecksum()); required.download = vboxNeedsInstall && (!fs.existsSync(vboxfile) || setupUtil.checksum(vboxfile) !== setupUtil.virtualBoxChecksum());
required.install = vboxNeedsInstall || setupUtil.needsBinaryFix(); required.install = vboxNeedsInstall || setupUtil.needsBinaryFix();
required.init = required.install || !(yield machine.exists()) || (yield machine.state()) !== 'Running' || !isoversion || setupUtil.compareVersions(isoversion, packagejson['docker-version']) < 0; required.init = required.install || !(yield machine.exists()) || (yield machine.state()) !== 'Running' || !isoversion || setupUtil.compareVersions(isoversion, packagejson['docker-version']) < 0;
@ -270,6 +270,8 @@ var SetupStore = assign(Object.create(EventEmitter.prototype), {
step: _currentStep, step: _currentStep,
message: err.message message: err.message
}); });
console.log(err);
console.log(err.stack);
bugsnag.notify('SetupError', err.message, { bugsnag.notify('SetupError', err.message, {
error: err, error: err,
step: _currentStep step: _currentStep

View File

@ -33,9 +33,9 @@ var SetupUtil = {
return; return;
} }
yield fs.chown(util.binsPath(), process.getuid(), '80'); yield fs.chown(util.binsPath(), process.getuid(), 80);
yield fs.chown(util.dockerBinPath(), process.getuid(), '80'); yield fs.chown(util.dockerBinPath(), process.getuid(), 80);
yield fs.chown(util.dockerMachineBinPath(), process.getuid(), '80'); yield fs.chown(util.dockerMachineBinPath(), process.getuid(), 80);
return Promise.resolve(); return Promise.resolve();
}), }),
installVirtualBoxCmd: Promise.coroutine(function* () { installVirtualBoxCmd: Promise.coroutine(function* () {

View File

@ -1,7 +1,6 @@
jest.dontMock('../src/VirtualBox'); jest.dontMock('./VirtualBox');
var virtualBox = require('../src/VirtualBox'); var virtualBox = require('./VirtualBox');
var util = require('../src/Util'); var util = require('./Util');
var Promise = require('bluebird');
describe('VirtualBox', function () { describe('VirtualBox', function () {
it('returns the right command', function () { it('returns the right command', function () {

View File

@ -201,7 +201,7 @@
left: -20px; left: -20px;
.at2x('runningwave.png', 20px, 20px); .at2x('runningwave.png', 20px, 20px);
-webkit-animation-name: translatewave; -webkit-animation-name: translatewave;
-webkit-animation-duration: 8.0s; -webkit-animation-duration: 7.0s;
-webkit-animation-iteration-count: infinite; -webkit-animation-iteration-count: infinite;
-webkit-animation-timing-function: linear; -webkit-animation-timing-function: linear;
} }

View File

@ -186,6 +186,7 @@
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
text-decoration: underline;
} }
} }
.description { .description {

View File

@ -25,7 +25,6 @@
.action { .action {
display: inline-block; display: inline-block;
position: relative; position: relative;
top: 10px;
&.disabled { &.disabled {
opacity: 0.3; opacity: 0.3;
} }
@ -36,11 +35,11 @@
.btn-label { .btn-label {
position: absolute; position: absolute;
color: @brand-action; color: @brand-action;
font-size: 10px; font-size: 9px;
width: 200px; width: 200px;
top: 30px; top: 45px;
&.view { &.view {
left: 6px; left: 7px;
//left: 0px; //left: 0px;
} }
&.restart { &.restart {