Merge branch 'style-polish'

Conflicts:
	src/components/ContainerHomeFolders.react.js
	src/components/Containers.react.js
	styles/right-panel.less
	styles/variables.less
This commit is contained in:
Sean Li 2015-06-11 14:48:25 -07:00
commit ccb1c5f214
27 changed files with 173 additions and 167 deletions

View File

@ -18,13 +18,14 @@ Before you fil an issue or a pull request, quickly read of the following tips on
### Prerequisites
Most of the time, you'll have installed Kitematic before contibuting, but for the
sake of completeness, you can also install [Node.js](https://nodejs.org/) and the latest Xcode from the Apple App Store and then run from your Git clone.
sake of completeness, you can also install [Node.js 0.10.38](https://nodejs.org/dist/v0.10.38/).
Running `npm start` will download and install the OS X Docker client,
[Docker machine](https://github.com/docker/machine),
the [Boot2Docker iso](https://github.com/boot2docker/boot2docker),
[Electron](http://electron.atom.io/), and [VirtualBox](https://www.virtualbox.org/)
if needed.
### Other Prerequisites (Mac)
- The latest Xcode from the Apple App Store.
### Other Prerequisites (Windows)
- [Visual Studio 2013 Community](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx) (or similar)
- [Python](https://www.python.org/downloads/release/python-2710/)
### Getting Started
@ -34,6 +35,11 @@ To run the app in development:
- `npm start`
Running `npm start` will download and install the OS X Docker client,
[Docker Machine](https://github.com/docker/machine), [Docker Compose](https://github.com/docker/compose)
the [Boot2Docker iso](https://github.com/boot2docker/boot2docker),
[Electron](http://electron.atom.io/).
### Building & Release
- `npm run release`

4
__mocks__/remote.js Normal file
View File

@ -0,0 +1,4 @@
module.exports = {
require: jest.genMockFunction(),
match: jest.genMockFunction()
};

View File

@ -28,10 +28,9 @@ container!
## Technical Details
Kitematic is a self-contained .app, with a two exceptions:
Kitematic is a self-contained .app, with an exception:
- It will install VirtualBox if it's not already installed.
- It copies the `docker` and `docker-machine` binaries to `/usr/local/bin` for
convenience.
### Why does Kitematic need my root password?

View File

@ -14,6 +14,9 @@ var plumber = require('gulp-plumber');
var runSequence = require('run-sequence');
var shell = require('gulp-shell');
var sourcemaps = require('gulp-sourcemaps');
var execFile = require('child_process').execFile;
var request = require('request');
var path = require('path');
var dependencies = Object.keys(packagejson.dependencies);
var argv = require('minimist')(process.argv.slice(2));
@ -175,15 +178,95 @@ gulp.task('settings', function () {
string_src('settings.json', JSON.stringify(settings)).pipe(gulp.dest('dist/osx/' + options.appFilename.replace(' ', '\ ').replace('(','\(').replace(')','\)') + '/Contents/Resources/app'));
});
gulp.task('download-deps', function () {
if(process.platform === 'win32') {
return gulp.src('').pipe(
shell(['powershell.exe -ExecutionPolicy unrestricted -File util\\deps.ps1'])
);
var version = function (str) {
var match = str.match(/(\d+\.\d+\.\d+)/);
if (match) {
return match[1];
} else {
return gulp.src('').pipe(
shell(['./util/deps'])
);
return null;
}
};
gulp.task('download-docker', function (cb) {
if (process.platform === 'win32' && fs.existsSync(path.join('resources', 'docker.exe'))) {
cb();
return;
}
execFile(path.join('resources', 'docker'), ['-v'], function (err, stdout, stderr) {
var currentVersion = version(stdout);
if (currentVersion && currentVersion === packagejson['docker-version']) {
cb();
return;
}
gutil.log(gutil.colors.green('Downloading Docker'));
if(process.platform === 'win32') {
request('https://master.dockerproject.com/windows/amd64/docker-1.7.0-dev.exe').pipe(fs.createWriteStream('./resources/docker.exe')).on('finish', cb);
} else {
request('https://get.docker.com/builds/Darwin/x86_64/docker-' + packagejson['docker-version'])
.pipe(fs.createWriteStream('./resources/docker')).on('finish', function () {
fs.chmodSync('./resources/docker', 0755);
cb();
});
}
});
});
gulp.task('download-docker-machine', function (cb) {
execFile(path.join('resources', 'docker-machine'), ['-v'], function (err, stdout, stderr) {
var currentVersion = version(stdout);
if (currentVersion && currentVersion === version(packagejson['docker-machine-version'])) {
cb();
return;
}
gutil.log(gutil.colors.green('Downloading Docker Machine'));
if(process.platform === 'win32') {
request('https://github.com/docker/machine/releases/download/v' + packagejson['docker-machine-version'] + '/docker-machine_windows-amd64.exe')
.pipe(fs.createWriteStream('./resources/docker-machine.exe')).on('finish', function () {cb()});
} else {
request('https://github.com/docker/machine/releases/download/v' + packagejson['docker-machine-version'] + '/docker-machine_darwin-amd64')
.pipe(fs.createWriteStream('./resources/docker-machine')).on('finish', function () {
fs.chmodSync('./resources/docker-machine', 0755);
cb();
});
}
});
});
gulp.task('download-docker-compose', function (cb) {
execFile(path.join('resources', 'docker-compose'), ['--version'], function (err, stdout, stderr) {
var currentVersion = version(stdout);
if (currentVersion && currentVersion === packagejson['docker-compose-version']) {
cb();
return;
}
if(process.platform === 'win32') {
// Todo: install windows version of compose
cb();
} else {
gutil.log(gutil.colors.green('Downloading Docker Compose'));
request('https://github.com/docker/compose/releases/download/' + packagejson['docker-compose-version'] + '/docker-compose-Darwin-x86_64')
.pipe(fs.createWriteStream('./resources/docker-compose')).on('finish', function () {
fs.chmodSync('./resources/docker-compose', 0755);
cb();
});
}
});
});
gulp.task('download-boot2docker-iso', function (cb) {
var b2dFile = path.join('resources', 'boot2docker-' + packagejson['docker-version'] + '.iso');
if (!fs.existsSync(b2dFile)) {
gutil.log(gutil.colors.green('Downloading Boot2Docker iso'));
request('https://github.com/boot2docker/boot2docker/releases/download/v' + packagejson['docker-version'] + '/boot2docker.iso')
.pipe(fs.createWriteStream(b2dFile)).on('finish', cb);
} else {
cb();
}
});
@ -200,10 +283,10 @@ gulp.task('reset', function () {
});
gulp.task('release', function () {
runSequence('download-deps', 'download', 'dist', ['copy', 'images', 'js', 'styles', 'settings'], 'sign', 'zip');
runSequence('download-docker', 'download-docker-machine', 'download-docker-compose', 'download-boot2docker-iso', 'download', 'dist', ['copy', 'images', 'js', 'styles', 'settings'], 'sign', 'zip');
});
gulp.task('default', ['download-deps', 'download', 'copy', 'js', 'images', 'styles'], function () {
gulp.task('default', ['download-docker', 'download-docker-machine', 'download-docker-compose', 'download-boot2docker-iso', 'download', 'copy', 'js', 'images', 'styles'], function () {
gulp.watch('src/**/*.js', ['js']);
gulp.watch('index.html', ['copy']);
gulp.watch('styles/**/*.less', ['styles']);

View File

@ -6,6 +6,6 @@
<title>Kitematic</title>
</head>
<body>
<script src="app.js"></script>
<script src="main.js"></script>
</body>
</html>

View File

@ -1,6 +1,6 @@
{
"testDirectoryName": "__integration__",
"scriptPreprocessor": "<rootDir>/util/preprocessor.js",
"scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
"setupEnvScriptFile": "<rootDir>/util/testenv.js",
"setupTestFrameworkScriptFile": "<rootDir>/util/prepare.js",
"unmockedModulePathPatterns": [

View File

@ -1,5 +1,5 @@
{
"scriptPreprocessor": "<rootDir>/util/preprocessor.js",
"scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
"setupEnvScriptFile": "<rootDir>/util/testenv.js",
"setupTestFrameworkScriptFile": "<rootDir>/util/prepare.js",
"unmockedModulePathPatterns": [
@ -9,10 +9,10 @@
"net",
"crypto",
"babel",
"<rootDir>/node_modules/.*JSONStream",
"<rootDir>/node_modules/object-assign",
"<rootDir>/node_modules/underscore",
"<rootDir>/node_modules/bluebird",
"<rootDir>/node_modules/source-map-support"
"bluebird",
"object-assign",
"underscore",
"source-map-support",
"<rootDir>/node_modules/.*JSONStream"
]
}

View File

@ -1,6 +1,6 @@
{
"name": "Kitematic",
"version": "0.6.3",
"version": "0.6.5",
"author": "Kitematic",
"description": "Simple Docker Container management for Mac OS X.",
"homepage": "https://kitematic.com/",
@ -26,9 +26,9 @@
}
],
"docker-version": "1.6.2",
"docker-machine-version": "0.2.0",
"docker-machine-version": "0.3.0-rc2",
"docker-compose-version": "1.2.0",
"electron-version": "0.26.0",
"electron-version": "0.27.2",
"virtualbox-version": "4.3.28",
"virtualbox-filename": "VirtualBox-4.3.28.pkg",
"virtualbox-filename-win": "VirtualBox-4.3.28.exe",
@ -57,7 +57,7 @@
"react": "^0.13.1",
"react-bootstrap": "^0.20.3",
"react-retina-image": "^1.1.2",
"react-router": "^0.13.2",
"react-router": "^0.13.3",
"request": "^2.55.0",
"request-progress": "^0.3.1",
"rimraf": "^2.3.2",
@ -66,6 +66,7 @@
},
"devDependencies": {
"babel": "^5.1.10",
"babel-jest": "^5.2.0",
"gulp": "^3.8.11",
"gulp-babel": "^5.1.0",
"gulp-changed": "^1.2.1",

View File

@ -93,8 +93,12 @@ var ContainerDetailsSubheader = React.createClass({
if (!this.disableTerminal()) {
metrics.track('Terminaled Into Container');
var container = this.props.container;
var shell = ContainerUtil.env(container).SHELL;
if(typeof shell === 'undefined') {
var shell = ContainerUtil.env(container).reduce((envs, env) => {
envs[env[0]] = env[1];
return envs;
}, {}).SHELL;
if(!shell) {
shell = 'sh';
}
machine.ip().then(ip => {

View File

@ -54,7 +54,7 @@ var ContainerHome = React.createClass({
body = (
<div className="details-progress">
<h3>An error occurred:</h3>
<h2>{this.props.container.Error.message}</h2>
<h2>{this.props.container.Error}</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}/>
</div>

View File

@ -25,7 +25,7 @@ var ContainerHomeFolder = React.createClass({
}, (index) => {
if (index === 0) {
var volumes = _.clone(this.props.container.Volumes);
var newHostVolume = path.join(util.home(), 'Kitematic', this.props.container.Name, containerVolume);
var newHostVolume = path.join(util.home(), util.documents(), 'Kitematic', this.props.container.Name, containerVolume);
volumes[containerVolume] = newHostVolume;
var binds = _.pairs(volumes).map(function (pair) {
if(util.isWindows()) {
@ -63,7 +63,8 @@ var ContainerHomeFolder = React.createClass({
return false;
}
var folders = _.map(this.props.container.Volumes, (val, key) => {
console.log(this.props.container.Volumes);
var folders = _.map(_.omit(this.props.container.Volumes, (v, k) => k.indexOf('/Users/') !== -1), (val, key) => {
var firstFolder = key;
return (
<div key={key} className="folder" onClick={this.handleClickFolder.bind(this, val, key)}>

View File

@ -160,7 +160,7 @@ var Containers = React.createClass({
<section className={sidebarHeaderClass}>
<h4>Containers</h4>
<div className="create">
<Router.Link to="new">
<Router.Link tabIndex="-1" to="new">
<span className="btn btn-new btn-action has-icon btn-hollow"><span className="icon icon-add"></span>New</span>
</Router.Link>
</div>

1
src/main.js Normal file
View File

@ -0,0 +1 @@
import './app';

View File

@ -42,25 +42,30 @@ var MenuTemplate = function () {
click: function () {
metrics.track('Installed Docker Commands');
if (!setupUtil.shouldUpdateBinaries()) {
dialog.showMessageBox({
message: 'Docker binaries are already installed in /usr/local/bin',
buttons: ['OK']
});
return;
}
let copy = setupUtil.needsBinaryFix() ?
util.exec(setupUtil.copyBinariesCmd() + ' && ' + setupUtil.fixBinariesCmd()) :
util.exec(setupUtil.macSudoCmd(setupUtil.copyBinariesCmd() + ' && ' + setupUtil.fixBinariesCmd())) :
util.exec(setupUtil.copyBinariesCmd());
copy.then(() => {
dialog.showMessageBox({
message: 'Docker binaries have been copied to /usr/local/bin',
message: 'Docker binaries have been installed under /usr/local/bin',
buttons: ['OK']
});
}).catch(() => {});
}).catch((err) => {
console.log(err);
});
},
},
{
type: 'separator'
},
{
}, {
label: 'Hide Kitematic',
accelerator: util.CommandOrCtrl() + '+H',
selector: 'hide:'

View File

@ -41,7 +41,11 @@ var _steps = [{
yield virtualBox.killall();
progressCallback(50); // TODO: detect when the installation has started so we can simulate progress
try {
if (util.isWindows()) {
yield util.exec([path.join(util.supportDir(), virtualBox.filename())]);
} else {
yield util.exec(setupUtil.macSudoCmd(setupUtil.installVirtualBoxCmd()));
}
} catch (err) {
throw null;
}

View File

@ -57,7 +57,11 @@ var DockerMachine = {
},
create: function () {
var dockerversion = util.packagejson()['docker-version'];
if (util.isWindows()) {
return util.exec([this.command(), '-D', 'create', '-d', 'virtualbox', '--virtualbox-memory', '2048', NAME]);
} else {
return util.exec([this.command(), '-D', 'create', '-d', 'virtualbox', '--virtualbox-boot2docker-url', path.join(process.cwd(), 'resources', 'boot2docker-' + dockerversion + '.iso'), '--virtualbox-memory', '2048', NAME]);
}
},
start: function () {
return util.exec([this.command(), '-D', 'start', NAME]);

View File

@ -103,6 +103,8 @@ export default {
containerData.Env = containerData.Config.Env;
}
containerData.Volumes = _.mapObject(containerData.Volumes, () => {return {};});
let existing = this.client.getContainer(name);
existing.kill(() => {
existing.remove(() => {
@ -203,7 +205,6 @@ export default {
containerServerActions.error({name, error});
return;
}
existingData.name = existingData.Name || name;
if (existingData.Config && existingData.Config.Image) {
existingData.Image = existingData.Config.Image;

View File

@ -62,7 +62,7 @@ module.exports = {
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.') {
if (data && data.detail && data.detail.indexOf('expired') !== -1) {
let config = this.config();
if (!this.config()) {
this.logout();

View File

@ -55,7 +55,7 @@ module.exports = {
recommended: function () {
request.get('https://kitematic.com/recommended.json', (error, response, body) => {
if (error) {
repositoryServerActions.recommendedError({error});
repositoryServerActions.error({error});
}
let data = JSON.parse(body);

View File

@ -3,6 +3,8 @@ var Promise = require('bluebird');
var fs = require('fs');
var path = require('path');
var crypto = require('crypto');
var remote = require('remote');
var app = remote.require('app');
module.exports = {
exec: function (args, options) {
@ -11,7 +13,7 @@ module.exports = {
exec(args, options, (stderr, stdout, code) => {
if (code) {
var cmd = Array.isArray(args) ? args.join(' ') : args;
reject(new Error(cmd + ' returned non zero exit code. Stderr: ' + stderr));
reject(new Error(cmd + ' returned non zero exit code.\n===== Stderr =====\n ' + stderr + '\n===== Stdout =====\n' + stdout));
} else {
resolve(stdout);
}
@ -37,18 +39,13 @@ module.exports = {
return str.replace(/ /g, '\\ ').replace(/\(/g, '\\(').replace(/\)/g, '\\)');
},
home: function () {
return process.env[this.isWindows() ? 'USERPROFILE' : 'HOME'];
return app.getPath('home');
},
documents: function () {
return this.isWindows() ? 'My\ Documents' : 'Documents';
},
supportDir: function () {
var dirs = ['Library', 'Application\ Support', 'Kitematic'];
var acc = this.home();
dirs.forEach(function (d) {
acc = path.join(acc, d);
if (!fs.existsSync(acc)) {
fs.mkdirSync(acc);
}
});
return acc;
return app.getPath('userData');
},
CommandOrCtrl: function () {
return this.isWindows() ? 'Ctrl' : 'Command';

View File

@ -17,11 +17,7 @@ var VirtualBox = {
return util.isWindows() ? util.packagejson()['virtualbox-checksum-win'] : util.packagejson()['virtualbox-checksum'];
},
url: function () {
if(util.isWindows()) {
return 'http://download.virtualbox.org/virtualbox/4.3.26/VirtualBox-4.3.26-98988-Win.exe';
} else {
return `https://github.com/kitematic/virtualbox/releases/download/${util.packagejson()['virtualbox-version']}/${this.filename()}`;
}
},
installed: function () {
if(util.isWindows()) {

View File

@ -3,7 +3,7 @@
margin: 0;
padding: 0;
box-sizing: border-box;
flex: 1;
flex: 1 auto;
display: flex;
flex-direction: column;
.header-section {
@ -43,7 +43,6 @@
}
}
.details-subheader {
//margin: 1rem 1rem 0 1rem;
flex: 0 auto;
display: flex;
flex-direction: row;
@ -181,7 +180,7 @@
}
}
.details-panel {
flex: 1;
flex: 1 auto;
overflow: auto;
}
}

View File

@ -23,8 +23,8 @@
@color-box-button: lighten(@gray-lightest, 5%);
@color-background: lighten(@gray-lightest, 4.5%);
@font-regular: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
@font-code: Menlo;
@font-regular: "Helvetica Neue", Segoe UI, Arial, "Lucida Grande", sans-serif;
@font-code: Menlo, Consolas;
@border-radius: 0.2rem;

View File

@ -1,46 +0,0 @@
#!/bin/bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
BASE=$DIR/..
DOCKER_VERSION=$(node -pe "JSON.parse(process.argv[1])['docker-version']" "$(cat $BASE/package.json)")
DOCKER_MACHINE_VERSION=$(node -pe "JSON.parse(process.argv[1])['docker-machine-version']" "$(cat $BASE/package.json)")
DOCKER_COMPOSE_VERSION=$(node -pe "JSON.parse(process.argv[1])['docker-compose-version']" "$(cat $BASE/package.json)")
CURRENT_DOCKER_VERSION=$($BASE/resources/docker -v | cut -d ',' -f1 | awk '{print $3}' | cut -d '-' -f1)
CURRENT_DOCKER_MACHINE_VERSION=$($BASE/resources/docker-machine -v | cut -d ',' -f1 | awk '{print $3}' | cut -d '-' -f1)
CURRENT_DOCKER_COMPOSE_VERSION=$($BASE/resources/docker-compose --version | cut -d ',' -f1 | awk '{print $2}' | cut -d '-' -f1)
BOOT2DOCKER_FILE=boot2docker-$DOCKER_VERSION.iso
pushd $BASE/resources > /dev/null
if [ "$DOCKER_VERSION" != "$CURRENT_DOCKER_VERSION" ]; then
echo "-----> Downloading Docker CLI..."
rm -rf docker
rm -rf docker-*
curl -L -o docker-$DOCKER_VERSION.tgz https://get.docker.com/builds/Darwin/x86_64/docker-$DOCKER_VERSION.tgz
tar xvzf docker-$DOCKER_VERSION.tgz --strip=3
rm docker-$DOCKER_VERSION.tgz
chmod +x docker
fi
if [ "$DOCKER_MACHINE_VERSION" != "$CURRENT_DOCKER_MACHINE_VERSION" ]; then
echo "-----> Downloading Docker Machine CLI..."
rm -rf docker-machine
rm -rf docker-machine-*
curl -L -o docker-machine https://github.com/docker/machine/releases/download/v$DOCKER_MACHINE_VERSION/docker-machine_darwin-amd64
chmod +x docker-machine
fi
if [ "$DOCKER_COMPOSE_VERSION" != "$CURRENT_DOCKER_COMPOSE_VERSION" ]; then
echo "-----> Downloading Docker Compose CLI..."
rm -rf docker-compose
curl -L -o docker-compose https://github.com/docker/compose/releases/download/$DOCKER_COMPOSE_VERSION/docker-compose-Darwin-x86_64
chmod +x docker-compose
fi
if [ ! -f $BOOT2DOCKER_FILE ]; then
echo "-----> Downloading Boot2Docker iso..."
rm -rf boot2docker-*
curl -L -o $BOOT2DOCKER_FILE https://github.com/boot2docker/boot2docker/releases/download/v$DOCKER_VERSION/boot2docker.iso
fi
popd > /dev/null

View File

@ -1,26 +0,0 @@
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
$BasePath = $dir + '\..\'
$packageJson = get-content ($BasePath + 'package.json')
[System.Reflection.Assembly]::LoadWithPartialName("System.Web.Extensions") > $null
$serializer = New-Object System.Web.Script.Serialization.JavaScriptSerializer
$packageJsonContent = $serializer.DeserializeObject($packageJson)
$webclient = New-Object System.Net.WebClient
$DOCKER_MACHINE_CLI_VERSION = $packageJsonContent['docker-machine-version']
$DOCKER_CLI_VERSION = $packageJsonContent['docker-version']
if(-Not (test-path ($BasePath + '\resources\docker'))) {
echo "-----> Downloading Docker CLI..."
$source = "https://master.dockerproject.com/windows/amd64/docker.exe"
$destination = $BasePath + "\resources\docker"
$webclient.DownloadFile($source, $destination)
}
if(-Not (test-path ($BasePath + '\resources\docker-machine'))) {
echo "-----> Downloading Docker Machine CLI..."
$source = "https://github.com/docker/machine/releases/download/v" + $DOCKER_MACHINE_VERSION+ "/docker-machine_windows-amd64.exe"
$destination = $BasePath + "\resources\docker-machine"
$webclient.DownloadFile($source, $destination)
}

View File

@ -1,14 +1 @@
require.requireActual('babel/polyfill');
require.requireActual('source-map-support').install({
retrieveSourceMap: function(filename) {
if (filename.indexOf('node_modules') === -1) {
try {
return {
map: require.requireActual('fs').readFileSync('/tmp/' + require('crypto').createHash('md5').update(filename).digest('hex') + '.map', 'utf8')
};
} catch (err) {
return undefined;
}
}
}
});

View File

@ -1,14 +0,0 @@
var babel = require('babel');
var fs = require('fs');
var crypto = require('crypto');
module.exports = {
process: function(src, filename) {
if (filename.indexOf('node_modules') !== -1) {
return src;
}
var compiled = babel.transform(src, {filename: filename, sourceMap: true});
fs.writeFileSync('/tmp/' + crypto.createHash('md5').update(filename).digest('hex') + '.map', JSON.stringify(compiled.map));
return compiled.code;
}
};