1
0
mirror of https://github.com/docker/docs.git synced 2026-11-05 00:14:17 +03:00

Merge branch 'master' into restyle

Conflicts:
	src/ContainerDetails.react.js
This commit is contained in:
Sean Li
2015-02-03 17:32:11 -08:00
23 changed files with 428 additions and 133 deletions

2
.gitignore vendored
View File

@@ -12,3 +12,5 @@ resources/boot2docker*
# Cache
cache
resources/settings*

View File

@@ -14,7 +14,6 @@ Kitematic's documentation and other information can be found at [http://kitemati
### Development
- `sudo npm install -g less`
- `npm install`
To run the app in development:

View File

@@ -1,13 +1,13 @@
var path = require('path');
var exec = require('exec');
var fs = require('fs');
var autoUpdater = require('auto-updater');
var app = require('app');
var BrowserWindow = require('browser-window');
var ipc = require('ipc');
var argv = require('minimist')(process.argv);
var saveVMOnQuit = false;
var settingsjson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'settings.json'), 'utf8'));
process.env.NODE_PATH = __dirname + '/../node_modules';
process.env.RESOURCES_PATH = __dirname + '/../resources';
@@ -39,10 +39,11 @@ app.on('activate-with-no-open-windows', function () {
app.on('ready', function() {
var mainWindow = new BrowserWindow(windowOptions);
var saveVMOnQuit = false;
if (argv.test) {
mainWindow.loadUrl('file://' + __dirname + '/../tests/tests.html');
mainWindow.loadUrl(path.normalize('file://' + path.join(__dirname, '..', 'tests/tests.html')));
} else {
mainWindow.loadUrl('file://' + __dirname + '/../build/index.html');
mainWindow.loadUrl(path.normalize('file://' + path.join(__dirname, '..', 'build/index.html')));
app.on('will-quit', function () {
if (saveVMOnQuit) {
exec('VBoxManage controlvm boot2docker-vm savestate', function () {});
@@ -63,7 +64,7 @@ app.on('ready', function() {
// Auto Updates
if (process.env.NODE_ENV !== 'development' && !argv.test) {
autoUpdater.setFeedUrl('https://updates.kitematic.com/releases/latest?version=' + app.getVersion());
autoUpdater.setFeedUrl('https://updates.kitematic.com/releases/latest?version=' + app.getVersion() + '&beta=' + settingsjson.beta);
autoUpdater.on('checking-for-update', function () {
console.log('Checking for update...');

0
gulp
View File

View File

@@ -17,12 +17,15 @@ var packagejson = require('./package.json');
var dependencies = Object.keys(packagejson.dependencies);
var devDependencies = Object.keys(packagejson.devDependencies);
var isBeta = process.argv.indexOf('--beta') !== -1;
var options = {
dev: process.argv.indexOf('release') === -1 && process.argv.indexOf('test') === -1,
test: process.argv.indexOf('test') !== -1,
integration: process.argv.indexOf('--integration') !== -1,
filename: 'Kitematic.app',
name: 'Kitematic'
beta: isBeta,
filename: isBeta ? 'Kitematic (Beta).app' : 'Kitematic.app',
name: isBeta ? 'Kitematic (Beta)' : 'Kitematic',
icon: isBeta ? 'kitematic-beta.icns' : 'kitematic.icns'
};
gulp.task('js', function () {
@@ -83,11 +86,13 @@ gulp.task('dist', function (cb) {
'cp -R ./cache/Atom.app ./dist/osx/<%= filename %>',
'mv ./dist/osx/<%= filename %>/Contents/MacOS/Atom ./dist/osx/<%= filename %>/Contents/MacOS/<%= name %>',
'mkdir -p ./dist/osx/<%= filename %>/Contents/Resources/app',
'mkdir -p ./dist/osx/<%= filename %>/Contents/Resources/app/node_modules',
'cp -R browser dist/osx/<%= filename %>/Contents/Resources/app',
'cp package.json dist/osx/<%= filename %>/Contents/Resources/app/',
'cp settings.json dist/osx/<%= filename %>/Contents/Resources/app/',
'mkdir -p dist/osx/<%= filename %>/Contents/Resources/app/resources',
'cp -v resources/* dist/osx/<%= filename %>/Contents/Resources/app/resources/ || :',
'cp kitematic.icns dist/osx/<%= filename %>/Contents/Resources/atom.icns',
'cp <%= icon %> dist/osx/<%= filename %>/Contents/Resources/atom.icns',
'/usr/libexec/PlistBuddy -c "Set :CFBundleVersion <%= version %>" dist/osx/<%= filename %>/Contents/Info.plist',
'/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName <%= name %>" dist/osx/<%= filename %>/Contents/Info.plist',
'/usr/libexec/PlistBuddy -c "Set :CFBundleName <%= name %>" dist/osx/<%= filename %>/Contents/Info.plist',
@@ -95,10 +100,11 @@ gulp.task('dist', function (cb) {
'/usr/libexec/PlistBuddy -c "Set :CFBundleExecutable <%= name %>" dist/osx/<%= filename %>/Contents/Info.plist'
], {
templateData: {
filename: options.filename,
name: options.name,
filename: options.filename.replace(' ', '\\ ').replace('(','\\(').replace(')','\\)'),
name: options.name.replace(' ', '\\ ').replace('(','\\(').replace(')','\\)'),
version: packagejson.version,
bundle: 'com.kitematic.app'
bundle: 'com.kitematic.app',
icon: options.icon
}
}));
@@ -107,7 +113,7 @@ gulp.task('dist', function (cb) {
'cp -R node_modules/' + d + ' dist/osx/<%= filename %>/Contents/Resources/app/node_modules/'
], {
templateData: {
filename: options.filename
filename: options.filename.replace(' ', '\\ ').replace('(','\\(').replace(')','\\)')
}
}));
});
@@ -119,7 +125,7 @@ gulp.task('sign', function () {
try {
var signing_identity = fs.readFileSync('./identity', 'utf8').trim();
return gulp.src('').pipe(shell([
'codesign --deep --force --verbose --sign "' + signing_identity + '" ' + options.filename
'codesign --deep --force --verbose --sign "' + signing_identity + '" ' + options.filename.replace(' ', '\\ ').replace('(','\\(').replace(')','\\)')
], {
cwd: './dist/osx/'
}));
@@ -130,7 +136,7 @@ gulp.task('sign', function () {
gulp.task('zip', function () {
return gulp.src('').pipe(shell([
'ditto -c -k --sequesterRsrc --keepParent ' + options.filename + ' ' + options.name + '-' + packagejson.version + '.zip'
'ditto -c -k --sequesterRsrc --keepParent ' + options.filename.replace(' ', '\\ ').replace('(','\\(').replace(')','\\)') + ' ' + options.name.replace(' ', '\\ ').replace('(','\\(').replace(')','\\)') + '-' + packagejson.version + '.zip'
], {
cwd: './dist/osx/'
}));

BIN
images/boot2docker.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
images/boot2docker@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
images/virtualbox.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
images/virtualbox@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
kitematic-beta.icns Normal file

Binary file not shown.

View File

@@ -16,8 +16,9 @@
"test:integration": "gulp test --silent --integration",
"all-tests": "npm test && npm run integration-tests",
"release": "gulp run release",
"release:beta": "gulp release --beta",
"preinstall": "./deps",
"lint": "jsxhint src/**/*"
"lint": "jsxhint src/"
},
"licenses": [
{
@@ -33,7 +34,7 @@
]
},
"boot2docker-version": "1.4.1",
"atom-shell-version": "0.21.0",
"atom-shell-version": "0.20.0",
"virtualbox-version": "4.3.20",
"virtualbox-filename": "VirtualBox-4.3.20-96996-OSX.dmg",
"virtualbox-required-version": "4.3.18",
@@ -56,7 +57,8 @@
"request": "^2.51.0",
"request-progress": "0.3.1",
"retina.js": "^1.1.0",
"underscore": "^1.7.0"
"underscore": "^1.7.0",
"rimraf": "^2.2.8"
},
"devDependencies": {
"browserify": "^6.2.0",
@@ -81,7 +83,6 @@
"gulp-uglifyjs": "^0.5.0",
"gulp-util": "^3.0.0",
"reactify": "^0.15.2",
"rimraf": "^2.2.8",
"run-sequence": "^1.0.2",
"time-require": "^0.1.2",
"vinyl-source-stream": "^0.1.1",

5
settings.json Normal file
View File

@@ -0,0 +1,5 @@
{
"bugsnag": "fc51aab02ce9dd1bb6ebc9fe2f4d43d7",
"mixpanel": "114caa6b42de655c4671c4b1e69f1b81",
"beta": true
}

View File

@@ -5,6 +5,8 @@ var Router = require('react-router');
var exec = require('exec');
var path = require('path');
var remote = require('remote');
var rimraf = require('rimraf');
var fs = require('fs');
var dialog = remote.require('dialog');
var ContainerStore = require('./ContainerStore');
var ContainerUtil = require('./ContainerUtil');
@@ -63,17 +65,16 @@ var ContainerDetails = React.createClass({
this.setState({
progress: ContainerStore.progress(this.getParams().name),
env: ContainerUtil.env(container),
page: this.PAGE_LOGS
});
var ports = ContainerUtil.ports(container);
var webPorts = ['80', '8000', '8080', '3000', '5000', '2368'];
console.log(ports);
this.setState({
ports: ports,
defaultPort: _.find(_.keys(ports), function (port) {
return webPorts.indexOf(port) !== -1;
})
});
console.log(this.state);
this.updateLogs();
},
updateLogs: function (name) {
@@ -121,8 +122,6 @@ var ContainerDetails = React.createClass({
console.log(this.state.ports);
console.log(this.state.defaultPort);
if (this.state.defaultPort) {
console.log(this.state.defaultPort);
console.log(this.state.ports[this.state.defaultPort].url);
exec(['open', this.state.ports[this.state.defaultPort].url], function (err) {
if (err) { throw err; }
});
@@ -133,6 +132,43 @@ var ContainerDetails = React.createClass({
if (err) { throw err; }
});
},
handleChangeDefaultPort: function (port, e) {
if (e.target.checked) {
this.setState({
defaultPort: null
});
} else {
this.setState({
defaultPort: port
});
}
},
handleChooseVolumeClick: function (dockerVol) {
var self = this;
dialog.showOpenDialog({properties: ['openDirectory', 'createDirectory']}, function (filenames) {
if (!filenames) {
return;
}
var directory = filenames[0];
if (directory) {
var volumes = _.clone(self.props.container.Volumes);
volumes[dockerVol] = directory;
var binds = _.pairs(volumes).map(function (pair) {
return pair[1] + ':' + pair[0];
});
ContainerStore.updateContainer(self.props.container.Name, {
Binds: binds
}, function (err) {
if (err) { console.log(err); }
});
}
});
},
handleOpenVolumeClick: function (path) {
exec(['open', path], function (err) {
if (err) { throw err; }
});
},
handleRestart: function () {
ContainerStore.restart(this.props.container.Name, function (err) {
console.log(err);
@@ -140,9 +176,11 @@ var ContainerDetails = React.createClass({
},
handleTerminal: function () {
var container = this.props.container;
var terminal = path.join(process.cwd(), 'resources', 'terminal').replace(/ /g, '\\\\ ');
var cmd = [terminal, boot2docker.command().replace(/ /g, '\\\\ '), 'ssh', '-t', 'sudo', 'docker', 'exec', '-i', '-t', container.Name, 'bash'];
var terminal = path.join(process.cwd(), 'resources', 'terminal');
var cmd = [terminal, boot2docker.command().replace(/ /g, '\\\\\\\\ ').replace(/\(/g, '\\\\\\\\(').replace(/\)/g, '\\\\\\\\)'), 'ssh', '-t', 'sudo', 'docker', 'exec', '-i', '-t', container.Name, 'sh'];
exec(cmd, function (stderr, stdout, code) {
console.log(stderr);
console.log(stdout);
if (code) {
console.log(stderr);
}
@@ -150,6 +188,12 @@ var ContainerDetails = React.createClass({
},
handleSaveContainerName: function () {
var newName = $('#input-container-name').val();
if (newName === this.props.container.Name) {
return;
}
if (fs.existsSync(path.join(process.env.HOME, 'Kitematic', this.props.container.Name))) {
fs.renameSync(path.join(process.env.HOME, 'Kitematic', this.props.container.Name), path.join(process.env.HOME, 'Kitematic', newName));
}
ContainerStore.updateContainer(this.props.container.Name, {
name: newName
}, function (err) {
@@ -213,6 +257,12 @@ var ContainerDetails = React.createClass({
message: 'Are you sure you want to delete this container?',
buttons: ['Delete', 'Cancel']
}, function (index) {
var volumePath = path.join(process.env.HOME, 'Kitematic', this.props.container.Name);
if (fs.existsSync(volumePath)) {
rimraf(volumePath, function (err) {
console.log(err);
});
}
if (index === 0) {
ContainerStore.remove(this.props.container.Name, function (err) {
console.error(err);
@@ -277,7 +327,7 @@ var ContainerDetails = React.createClass({
btn: true,
'btn-action': true,
'with-icon': true,
disabled: this.props.container.State.Restarting
disabled: this.props.container.State.Downloading || this.props.container.State.Restarting
});
var viewButtonClass = React.addons.classSet({
@@ -287,6 +337,17 @@ var ContainerDetails = React.createClass({
disabled: !this.props.container.State.Running || !this.state.defaultPort
});
var kitematicVolumes = _.pairs(this.props.container.Volumes).filter(function (pair) {
return pair[1].indexOf(path.join(process.env.HOME, 'Kitematic')) !== -1;
});
var volumesButtonClass = React.addons.classSet({
btn: true,
'btn-action': true,
'with-icon': true,
disabled: !kitematicVolumes.length
});
var textButtonClasses = React.addons.classSet({
'btn': true,
'btn-action': true,
@@ -328,11 +389,14 @@ var ContainerDetails = React.createClass({
var body;
if (this.props.container.State.Downloading) {
body = (
<div className="details-progress">
<ProgressBar now={this.state.progress * 100} label="%(percent)s%" />
</div>
);
if (this.state.progress) {
body = (
<div className="details-progress">
<h3>Downloading</h3>
<ProgressBar now={this.state.progress * 100} label="%(percent)s%"/>
</div>
);
}
} else {
if (this.state.page === this.PAGE_HOME) {
body = (
@@ -377,16 +441,19 @@ var ContainerDetails = React.createClass({
</div>
);
} else {
var rename = (
<div className="settings-section">
<h3>Container Name</h3>
<div className="container-name">
<input id="input-container-name" type="text" className="line" placeholder="Container Name" defaultValue={this.props.container.Name}></input>
</div>
<a className="btn btn-action" onClick={this.handleSaveContainerName}>Save</a>
</div>
);
body = (
<div className="details-panel">
<div className="settings">
<div className="settings-section">
<h3>Container Name</h3>
<div className="container-name">
<input id="input-container-name" type="text" className="line" placeholder="Container Name" defaultValue={this.props.container.Name}></input>
</div>
<a className="btn btn-action" onClick={this.handleSaveContainerName}>Save</a>
</div>
{rename}
<div className="settings-section">
<h3>Environment Variables</h3>
<div className="env-vars-labels">
@@ -426,24 +493,54 @@ var ContainerDetails = React.createClass({
disabled: this.props.container.State.Downloading
});
/*var tabPortsClasses = React.addons.classSet({
'tab': true,
'active': this.state.page === this.PAGE_PORTS,
disabled: this.props.container.State.Downloading
});
var tabVolumesClasses = React.addons.classSet({
'tab': true,
'active': this.state.page === this.PAGE_VOLUMES,
disabled: this.props.container.State.Downloading
});*/
var tabSettingsClasses = React.addons.classSet({
'tab': true,
'active': this.state.page === this.PAGE_SETTINGS,
disabled: this.props.container.State.Downloading
});
/*var ports = _.map(_.pairs(self.state.ports), function (pair, index, list) {
var key = pair[0];
var val = pair[1];
return (
<div key={key} className="table-values">
<span className="value-left">{key}</span><span className="icon icon-arrow-right"></span>
<a className="value-right" onClick={self.handleViewLink.bind(self, val.url)}>{val.display}</a>
<input onChange={self.handleChangeDefaultPort.bind(self, key)} type="checkbox" checked={self.state.defaultPort === key}/> <label>Default</label>
</div>
);
});
var volumes = _.map(self.props.container.Volumes, function (val, key) {
if (!val || val.indexOf(process.env.HOME) === -1) {
val = <span>No folder<a className="btn btn-primary btn-xs" onClick={self.handleChooseVolumeClick.bind(self, key)}>Choose</a></span>;
} else {
val = <span><a className="value-right" onClick={self.handleOpenVolumeClick.bind(self, val)}>{val.replace(process.env.HOME, '~')}</a> <a className="btn btn-primary btn-xs" onClick={self.handleChooseVolumeClick.bind(self, key)}>Choose</a></span>;
}
return (
<div key={key} className="table-values">
<span className="value-left">{key}</span><span className="icon icon-arrow-right"></span>
{val}
</div>
);
});*/
/* var view;
if (this.state.defaultPort) {
view = (
<div className="action btn-group">
<a className={viewButtonClass} onClick={this.handleView}><span className="icon icon-preview-2"></span><span className="content">View</span></a>
<a className={dropdownViewButtonClass} onClick={this.handleViewDropdown}><span className="icon-dropdown icon icon-arrow-37"></span></a>
</div>
);
} else {
view = (
<div className="action">
<a className={dropdownViewButtonClass} onClick={this.handleViewDropdown}><span className="icon icon-preview-2"></span> <span className="content">Ports</span> <span className="icon-dropdown icon icon-arrow-37"></span></a>
</div>
);
}*/
return (
<div className="details">
<ContainerDetailsHeader container={this.props.container} />

View File

@@ -77,18 +77,17 @@ var ContainerModal = React.createClass({
}, 200);
}
},
handleClick: function (name) {
ContainerStore.create(name, 'latest', function (err) {
handleClick: function (name, event) {
this.props.onRequestHide();
ContainerStore.create(name, 'latest', function (err, containerName) {
if (err) {
throw err;
}
this.props.onRequestHide();
}.bind(this));
},
handleTagClick: function (tag, name) {
ContainerStore.create(name, tag, function () {
this.props.onRequestHide();
}.bind(this));
handleTagClick: function (tag, name, event) {
this.props.onRequestHide();
ContainerStore.create(name, tag, function (err, containerName) {});
},
handleDropdownClick: function (name) {
this.setState({

View File

@@ -18,11 +18,11 @@ var _streams = {};
var _muted = {};
var ContainerStore = assign(EventEmitter.prototype, {
CLIENT_CONTAINER_EVENT: 'client_container',
CLIENT_CONTAINER_EVENT: 'client_container_event',
CLIENT_RECOMMENDED_EVENT: 'client_recommended_event',
SERVER_CONTAINER_EVENT: 'server_container',
SERVER_PROGRESS_EVENT: 'server_progress',
SERVER_LOGS_EVENT: 'server_logs',
SERVER_CONTAINER_EVENT: 'server_container_event',
SERVER_PROGRESS_EVENT: 'server_progress_event',
SERVER_LOGS_EVENT: 'server_logs_event',
_pullScratchImage: function (callback) {
var image = docker.client().getImage('scratch:latest');
image.inspect(function (err, data) {
@@ -118,16 +118,25 @@ var ContainerStore = assign(EventEmitter.prototype, {
if (containerData.Config && containerData.Config.Image) {
containerData.Image = containerData.Config.Image;
}
existing.kill(function () {
existing.remove(function () {
existing.kill(function (err, data) {
if (err) {
console.log(err);
}
existing.remove(function (err, data) {
if (err) {
console.log(err);
}
docker.client().getImage(containerData.Image).inspect(function (err, data) {
if (err) {
callback(err);
return;
}
var binds = [];
if (data.Config.Volumes) {
_.each(data.Config.Volumes, function (value, key) {
binds.push(path.join(process.env.HOME, 'Kitematic', containerData.name, key)+ ':' + key);
});
}
docker.client().createContainer(containerData, function (err, container) {
if (err) {
callback(err, null);
@@ -308,11 +317,13 @@ var ContainerStore = assign(EventEmitter.prototype, {
}
$.ajax({
url: 'https://kitematic.com/recommended.json',
cache: false,
dataType: 'json',
success: function (res) {
var recommended = res.recommended;
async.map(recommended, function (repository, callback) {
$.get('https://registry.hub.docker.com/v1/search?q=' + repository, function (data) {
console.log(data);
var results = data.results;
callback(null, _.find(results, function (r) {
return r.name === repository;
@@ -376,38 +387,27 @@ var ContainerStore = assign(EventEmitter.prototype, {
var imageName = repository + ':' + tag;
var containerName = this._generateName(repository);
var image = docker.client().getImage(imageName);
image.inspect(function (err, data) {
if (!data) {
// Pull image
self._createPlaceholderContainer(imageName, containerName, function (err, container) {
if (err) {
callback(err);
return;
}
_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 () {
delete _progress[containerName];
_muted[containerName] = false;
self.emit(self.CLIENT_CONTAINER_EVENT, containerName);
});
}, function (progress) {
_progress[containerName] = progress;
self.emit(self.SERVER_PROGRESS_EVENT, containerName);
});
callback(null, containerName);
});
} else {
// If not then directly create the container
self._createContainer(containerName, {Image: imageName}, function () {
self.emit(ContainerStore.CLIENT_CONTAINER_EVENT, containerName, 'create');
callback(null, containerName);
});
// Pull image
self._createPlaceholderContainer(imageName, containerName, function (err, container) {
if (err) {
callback(err);
return;
}
_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;
self.emit(self.SERVER_PROGRESS_EVENT, containerName);
});
callback(null, containerName);
});
},
updateContainer: function (name, data, callback) {
@@ -416,10 +416,10 @@ var ContainerStore = assign(EventEmitter.prototype, {
data.name = data.Name;
}
var fullData = assign(_containers[name], data);
console.log(fullData);
this._createContainer(name, fullData, function (err) {
callback(err);
_muted[name] = false;
this.emit(this.CLIENT_CONTAINER_EVENT, name);
callback(err);
}.bind(this));
},
restart: function (name, callback) {

View File

@@ -12,7 +12,7 @@ var Docker = {
return;
}
this._client = new dockerode({
host: '192.168.59.103',
host: host,
port: 2376,
ca: fs.readFileSync(path.join(certDir, 'ca.pem')),
cert: fs.readFileSync(path.join(certDir, 'cert.pem')),

View File

@@ -1,9 +1,16 @@
require.main.paths.splice(0, 0, process.env.NODE_PATH);
var remote = require('remote');
var app = remote.require('app');
var React = require('react');
var fs = require('fs');
var path = require('path');
var docker = require('./Docker');
var router = require('./router');
var boot2docker = require('./boot2docker');
var ContainerStore = require('./ContainerStore');
var SetupStore = require('./ContainerStore');
var settingsjson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'settings.json'), 'utf8'));
if (process.env.NODE_ENV === 'development') {
var script = document.createElement('script');
@@ -11,13 +18,21 @@ if (process.env.NODE_ENV === 'development') {
script.src = 'http://localhost:35729/livereload.js';
var head = document.getElementsByTagName('head')[0];
head.appendChild(script);
} else {
var bugsnag = require('bugsnag-js');
bugsnag.apiKey = settingsjson.bugsnag;
bugsnag.autoNotify = true;
bugsnag.releaseStage = process.env.NODE_ENV === 'development' ? 'development' : 'production';
bugsnag.notifyReleaseStages = ['production'];
bugsnag.appVersion = app.getVersion();
}
if (!window.location.hash.length || window.location.hash === '#/') {
router.run(function (Handler) {
React.render(<Handler/>, document.body);
});
SetupStore.run(function () {
SetupStore.run(function (err) {
if (err) { console.log(err); }
boot2docker.ip(function (err, ip) {
if (err) { console.log(err); }
docker.setHost(ip);

View File

@@ -1,23 +1,133 @@
var React = require('react/addons');
var Router = require('react-router');
var Radial = require('./Radial.react.js');
var async = require('async');
var assign = require('object-assign');
var fs = require('fs');
var path = require('path');
var virtualbox = require('./Virtualbox');
var SetupStore = require('./SetupStore');
var RetinaImage = require('react-retina-image');
var Setup = React.createClass({
mixins: [ Router.Navigation ],
getInitialState: function () {
return {
message: '',
progress: 0
progress: 0,
name: ''
};
},
componentWillMount: function () {
SetupStore.on(SetupStore.PROGRESS_EVENT, this.update);
SetupStore.on(SetupStore.STEP_EVENT, this.update);
},
componentDidMount: function () {
},
update: function () {
this.setState({
progress: SetupStore.stepProgress(),
step: SetupStore.stepName()
});
},
renderDownloadingVirtualboxStep: function () {
var message = 'Kitematic needs VirtualBox to run containers. VirtualBox is being downloaded from Oracle\'s website.';
return (
<div className="setup">
<div className="image">
<div className="contents">
<RetinaImage img src="virtualbox.png"/>
<div className="detail">
<Radial progress={this.state.progress}/>
</div>
</div>
</div>
<div className="desc">
<div className="content">
<h1>Downloading VirtualBox</h1>
<p>{message}</p>
</div>
</div>
</div>
);
},
renderInstallingVirtualboxStep: function () {
var message = 'VirtualBox is being installed. Administrative privileges are required.';
return (
<div className="setup">
<div className="image">
<div className="contents">
<RetinaImage img src="virtualbox.png"/>
<div className="detail">
<Radial progress="90" spin="true"/>
</div>
</div>
</div>
<div className="desc">
<div className="content">
<h1>Installing VirtualBox</h1>
<p>{message}</p>
</div>
</div>
</div>
);
},
renderInitBoot2DockerStep: function () {
var message = 'Containers run in a virtual machine provided by Boot2Docker. Kitematic is setting up that Linux VM.';
return (
<div className="setup">
<div className="image">
<div className="contents">
<RetinaImage img src="boot2docker.png"/>
<div className="detail">
<Radial progress="90" spin="true"/>
</div>
</div>
</div>
<div className="desc">
<div className="content">
<h1>Setting up the Docker VM</h1>
<p>{message}</p>
</div>
</div>
</div>
);
},
renderStartBoot2DockerStep: function () {
var message = 'Kitematic is starting the Boot2Docker Linux VM.';
return (
<div className="setup">
<div className="image">
<div className="contents">
<RetinaImage img src="boot2docker.png"/>
<div className="detail">
<Radial progress="90" spin="true"/>
</div>
</div>
</div>
<div className="desc">
<div className="content">
<h1>Starting the Docker VM</h1>
<p>{message}</p>
</div>
</div>
</div>
);
},
renderStep: function () {
switch(this.state.step) {
case 'downloading_virtualbox':
return this.renderDownloadingVirtualboxStep();
case 'installing_virtualbox':
return this.renderInstallingVirtualboxStep();
case 'cleanup_kitematic':
return this.renderInitBoot2DockerStep();
case 'init_boot2docker':
return this.renderInitBoot2DockerStep();
case 'start_boot2docker':
return this.renderStartBoot2DockerStep();
default:
return false;
}
},
render: function () {
var radial;
@@ -28,6 +138,9 @@ var Setup = React.createClass({
} else {
radial = <Radial spin="true" progress="100"/>;
}
var step = this.renderStep();
if (this.state.error) {
return (
<div className="setup">
@@ -36,12 +149,7 @@ var Setup = React.createClass({
</div>
);
} else {
return (
<div className="setup">
{radial}
<p>{this.state.message}</p>
</div>
);
return step;
}
}
});

View File

@@ -11,7 +11,7 @@ var packagejson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package
var _currentStep = null;
var _error = null;
var _progress = null;
var _progress = 0;
var SetupStore = assign(EventEmitter.prototype, {
PROGRESS_EVENT: 'setup_progress',
@@ -48,26 +48,30 @@ var SetupStore = assign(EventEmitter.prototype, {
}
},
name: 'downloading_virtualbox',
message: 'Downloading Virtualbox',
},
installVirtualboxStep: {
_install: function (callback) {
exec(['hdiutil', 'attach', path.join(setupUtil.supportDir(), 'VirtualBox-4.3.20-96996-OSX.dmg')], function (stderr, stdout, code) {
console.log('attaching');
exec(['hdiutil', 'attach', path.join(setupUtil.supportDir(), packagejson['virtualbox-filename'])], function (stderr, stdout, code) {
if (code) {
callback(stderr);
return;
}
console.log('Attached.');
var iconPath = path.join(setupUtil.resourceDir(), 'kitematic.icns');
setupUtil.isSudo(function (err, isSudo) {
var sudoCmd = isSudo ? ['sudo'] : [path.join(setupUtil.resourceDir(), 'cocoasudo'), '--icon=' + iconPath, '--prompt=Kitematic requires administrative privileges to install VirtualBox and copy itself to the Applications folder.'];
sudoCmd.push.apply(sudoCmd, ['installer', '-pkg', '/Volumes/VirtualBox/VirtualBox.pkg', '-target', '/']);
exec(sudoCmd, function (stderr, stdout, code) {
console.log(stdout);
console.log('Ran installer.');
if (code) {
console.log(stderr);
console.log(stdout);
callback('Could not install virtualbox.');
} else {
exec(['hdiutil', 'detach', '/Volumes/VirtualBox'], function(stderr, stdout, code) {
console.log('detaching');
if (code) {
callback(stderr);
} else {
@@ -101,7 +105,6 @@ var SetupStore = assign(EventEmitter.prototype, {
}
},
name: 'installing_virtualbox',
message: 'Installing VirtualBox',
},
cleanupKitematicStep: {
run: function (callback) {
@@ -113,7 +116,6 @@ var SetupStore = assign(EventEmitter.prototype, {
});
},
name: 'cleanup_kitematic',
message: 'Cleaning up existing Kitematic install...'
},
initBoot2DockerStep: {
run: function (callback) {
@@ -143,15 +145,16 @@ var SetupStore = assign(EventEmitter.prototype, {
});
},
name: 'init_boot2docker',
message: 'Setting up the Docker VM...'
},
startBoot2DockerStep: {
run: function (callback) {
boot2docker.waitWhileStatus('saving', function () {
boot2docker.status(function (err, status) {
if (err) {callback(err); return;}
console.log(status);
if (status !== 'running') {
boot2docker.start(function (err) {
console.log('stated');
callback(err);
});
} else {
@@ -161,37 +164,37 @@ var SetupStore = assign(EventEmitter.prototype, {
});
},
name: 'start_boot2docker',
message: 'Starting the Docker VM...'
},
step: function () {
return _currentStep;
stepName: function () {
return _currentStep.name;
},
progress: function () {
stepProgress: function () {
return _progress;
},
run: function (callback) {
var self = this;
var steps = [this.downloadVirtualboxStep, this.installVirtualboxStep, this.cleanupKitematicStep, this.initBoot2DockerStep, this.startBoot2DockerStep];
async.eachSeries(steps, function (step, callback) {
console.log(step.name);
_currentStep = step;
_progress = null;
_progress = 0;
self.emit(self.STEP_EVENT);
step.run(function (err) {
if (err) {
callback(err);
} else {
self.emit(self.STEP_EVENT);
callback();
}
}, function (progress) {
self.emit(self.PROGRESS_EVENT, progress);
_progress = progress;
self.emit(self.PROGRESS_EVENT, progress);
});
}, function (err) {
if (err) {
self.emit(self.ERROR_EVENT, _error);
callback(err);
} else {
console.log('done');
callback();
}
});

View File

@@ -1,11 +1,11 @@
.popover {
&.popover-view {
min-width: 290px;
min-width: 364px;
}
&.popover-volume {
min-width: 400px;
min-width: 480px;
}
.popover-content {
@@ -165,8 +165,8 @@
flex-shrink: 0;
cursor: default;
outline: none;
&.active {
background: @brand-primary;
li {
border-bottom: none;
background-image: linear-gradient(-180deg, #24B8EB 4%, #24A3EB 100%);
@@ -488,6 +488,11 @@
border-bottom: 1px solid transparent;
transition: border-bottom 0.25s;
.action {
a {
-webkit-transition: none;
}
transition: none;
flex: 0 auto;
margin-right: 24px;
}
@@ -505,6 +510,7 @@
.details-progress {
margin: 26% auto 0;
text-align: center;
width: 300px;
}

View File

@@ -12,16 +12,16 @@
.radial-progress {
&.radial-spinner {
-webkit-animation: rotating 1.2s linear infinite;
-webkit-animation: rotating 2.4s linear infinite;
}
@circle-size: 96px;
@circle-background: transparent;
@inset-size: 92px;
@circle-size: 140px;
@circle-background: #F2F2F2;
@inset-size: 136px;
@inset-color: white;
@transition-length: 1s;
// @percentage-color: #3FD899;
@percentage-font-size: 14px;
@percentage-font-size: 24px;
@percentage-text-width: 57px;
margin: 0 auto;
@@ -70,7 +70,7 @@
line-height: 1;
text-align: center;
// color: @percentage-color;
color: @brand-primary;
font-weight: 500;
font-size: @percentage-font-size;
}

View File

@@ -1,6 +1,52 @@
.setup {
margin-top: 25%;
text-align: center;
display: flex;
height: 100%;
width: 100%;
flex-direction: row;
-webkit-app-region: drag;
.image {
display: flex;
width: 50%;
height: 100%;
flex: 0 auto;
align-items: center;
justify-content: flex-end;
padding-right: 40px;
.contents {
position: relative;
.detail {
position: absolute;
right: 0;
bottom: 0;
}
}
}
.desc {
display: flex;
width: 50%;
height: 100%;
align-items: center;
padding-left: 40px;
.content {
max-width: 320px;
h1 {
margin-top: -30px;
font-size: 24px;
}
p {
font-size: 13px;
color: @gray-normal;
}
}
}
p {
&.error {

View File

@@ -87,6 +87,13 @@ input[type="text"] {
&:disabled,
&[disabled] {
opacity: 0.5;
background: none;
&.active {
background: none;
color: white;
box-shadow: none;
box-shadow: none;
}
}
}