diff --git a/gulpfile.js b/gulpfile.js
index 98fd33f140..937b8a1cd8 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -17,10 +17,18 @@ var packagejson = require('./package.json');
var dependencies = Object.keys(packagejson.dependencies);
var isBeta = process.argv.indexOf('--beta') !== -1;
+
+var settings;
+try {
+ settings = JSON.parse(fs.readFileSync('settings.json'), 'utf8');
+} catch (err) {
+ settings = {};
+}
+settings.beta = isBeta;
+
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,
beta: isBeta,
filename: isBeta ? 'Kitematic (Beta).app' : 'Kitematic.app',
name: isBeta ? 'Kitematic (Beta)' : 'Kitematic',
@@ -29,6 +37,11 @@ var options = {
gulp.task('js', function () {
return gulp.src('src/**/*.js')
+ .pipe(plumber(function(error) {
+ gutil.log(gutil.colors.red('Error (' + error.plugin + '): ' + error.message));
+ // emit the end event, to properly end the task
+ this.emit('end');
+ }))
.pipe(gulpif(options.dev || options.test, sourcemaps.init()))
.pipe(react())
.pipe(babel({blacklist: ['regenerator']}))
@@ -86,7 +99,6 @@ gulp.task('dist', function () {
'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 <%= icon %> dist/osx/<%= filename %>/Contents/Resources/atom.icns',
@@ -139,8 +151,20 @@ gulp.task('zip', function () {
}));
});
+gulp.task('settings', function () {
+ var string_src = function (filename, string) {
+ var src = require('stream').Readable({ objectMode: true });
+ src._read = function () {
+ this.push(new gutil.File({ cwd: "", base: "", path: filename, contents: new Buffer(string) }));
+ this.push(null);
+ };
+ return src;
+ };
+ string_src('settings.json', JSON.stringify(settings)).pipe(gulp.dest('dist/osx/' + options.filename.replace(' ', '\ ').replace('(','\(').replace(')','\)') + '/Contents/Resources/app'));
+});
+
gulp.task('release', function () {
- runSequence('download', 'dist', ['copy', 'images', 'js', 'styles'], 'sign', 'zip');
+ runSequence('download', 'dist', ['copy', 'images', 'js', 'styles', 'settings'], 'sign', 'zip');
});
gulp.task('default', ['download', 'copy', 'js', 'images', 'styles'], function () {
diff --git a/package.json b/package.json
index 7a764c95b7..704e606c54 100644
--- a/package.json
+++ b/package.json
@@ -41,21 +41,20 @@
"docker-version": "1.5.0",
"boot2docker-version": "1.5.0",
"atom-shell-version": "0.21.1",
- "virtualbox-version": "4.3.20",
- "virtualbox-filename": "VirtualBox-4.3.20.pkg",
- "virtualbox-checksum": "89edac4cc7298c8a04fd4bb646ff2197e7673137c6566c7757f0e9cd6265d0c5",
- "virtualbox-required-version": "4.3.18",
+ "virtualbox-version": "4.3.22",
+ "virtualbox-filename": "VirtualBox-4.3.22.pkg",
+ "virtualbox-checksum": "4a7dff25bdeef0d112e16ac11bee6d52e856d36bb412aa75576036ba560082eb",
+ "virtualbox-required-version": "4.3.12",
"dependencies": {
"ansi-to-html": "0.2.0",
"async": "^0.9.0",
- "babel": "^4.0.1",
"bluebird": "^2.9.6",
"bugsnag-js": "^2.4.7",
"dockerode": "^2.0.7",
- "download": "^4.0.0",
"exec": "0.1.2",
"jquery": "^2.1.3",
"minimist": "^1.1.0",
+ "mixpanel": "0.0.20",
"node-uuid": "^1.4.2",
"object-assign": "^2.0.0",
"react": "^0.12.2",
@@ -69,13 +68,13 @@
"underscore": "^1.7.0"
},
"devDependencies": {
+ "babel": "^4.0.1",
"browserify": "^6.2.0",
"ecstatic": "^0.5.8",
"glob": "^4.0.6",
"gulp": "^3.8.10",
- "gulp-babel": "^3.0.0",
- "gulp-atom": "0.0.5",
"gulp-babel": "^4.0.0",
+ "gulp-atom": "0.0.5",
"gulp-concat": "^2.3.4",
"gulp-cssmin": "^0.1.6",
"gulp-download-atom-shell": "0.0.4",
diff --git a/src/ContainerDetailsSubheader.react.js b/src/ContainerDetailsSubheader.react.js
index 28bd6182fb..f40727ae49 100644
--- a/src/ContainerDetailsSubheader.react.js
+++ b/src/ContainerDetailsSubheader.react.js
@@ -3,6 +3,7 @@ var $ = require('jquery');
var React = require('react/addons');
var exec = require('exec');
var path = require('path');
+var metrics = require('./Metrics');
var ContainerStore = require('./ContainerStore');
var ContainerUtil = require('./ContainerUtil');
var boot2docker = require('./Boot2Docker');
@@ -65,21 +66,27 @@ var ContainerDetailsSubheader = React.createClass({
},
showHome: function () {
if (!this.disableTab()) {
+ metrics.track('Viewed Home');
this.transitionTo('containerHome', {name: this.getParams().name});
}
},
showLogs: function () {
if (!this.disableTab()) {
+ metrics.track('Viewed Logs');
this.transitionTo('containerLogs', {name: this.getParams().name});
}
},
showSettings: function () {
if (!this.disableTab()) {
+ metrics.track('Viewed Settings');
this.transitionTo('containerSettings', {name: this.getParams().name});
}
},
handleRun: function () {
if (this.state.defaultPort && !this.disableRun()) {
+ metrics.track('Opened In Browser', {
+ from: 'header'
+ });
exec(['open', this.state.ports[this.state.defaultPort].url], function (err) {
if (err) { throw err; }
});
@@ -87,6 +94,7 @@ var ContainerDetailsSubheader = React.createClass({
},
handleRestart: function () {
if (!this.disableRestart()) {
+ metrics.track('Restarted Container');
ContainerStore.restart(this.props.container.Name, function (err) {
console.log(err);
});
@@ -94,12 +102,11 @@ var ContainerDetailsSubheader = React.createClass({
},
handleTerminal: function () {
if (!this.disableTerminal()) {
+ metrics.track('Terminaled Into Container');
var container = this.props.container;
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);
}
diff --git a/src/ContainerHomeFolders.react.js b/src/ContainerHomeFolders.react.js
index 46b1bba7a4..513cc974b5 100644
--- a/src/ContainerHomeFolders.react.js
+++ b/src/ContainerHomeFolders.react.js
@@ -3,16 +3,23 @@ var React = require('react/addons');
var RetinaImage = require('react-retina-image');
var path = require('path');
var exec = require('exec');
+var metrics = require('./Metrics');
var Router = require('react-router');
var ContainerHomeFolder = React.createClass({
mixins: [Router.State, Router.Navigation],
handleClickFolder: function (path) {
+ metrics.track('Opened Volume Directory', {
+ from: 'home'
+ });
exec(['open', path], function (err) {
if (err) { throw err; }
});
},
handleClickChangeFolders: function () {
+ metrics.track('Viewed Volume Settings', {
+ from: 'preview'
+ });
this.transitionTo('containerSettingsVolumes', {name: this.getParams().name});
},
render: function () {
diff --git a/src/ContainerHomeLogs.react.js b/src/ContainerHomeLogs.react.js
index d64d8b75be..dcaaa95cff 100644
--- a/src/ContainerHomeLogs.react.js
+++ b/src/ContainerHomeLogs.react.js
@@ -2,6 +2,7 @@ var $ = require('jquery');
var React = require('react/addons');
var LogStore = require('./LogStore');
var Router = require('react-router');
+var metrics = require('./Metrics');
var _oldScrollTop = 0;
@@ -42,6 +43,9 @@ var ContainerHomeLogs = React.createClass({
});
},
handleClickLogs: function () {
+ metrics.track('Viewed Logs', {
+ from: 'preview'
+ });
this.transitionTo('containerLogs', {name: this.getParams().name});
},
render: function () {
diff --git a/src/ContainerHomePreview.react.js b/src/ContainerHomePreview.react.js
index 8d48f50cd0..9f0f58fc5c 100644
--- a/src/ContainerHomePreview.react.js
+++ b/src/ContainerHomePreview.react.js
@@ -5,6 +5,7 @@ var ContainerStore = require('./ContainerStore');
var ContainerUtil = require('./ContainerUtil');
var Router = require('react-router');
var request = require('request');
+var metrics = require('./Metrics');
var ContainerHomePreview = React.createClass({
mixins: [Router.State, Router.Navigation],
@@ -57,12 +58,18 @@ var ContainerHomePreview = React.createClass({
},
handleClickPreview: function () {
if (this.state.defaultPort) {
+ metrics.track('Opened In Browser', {
+ from: 'preview'
+ });
exec(['open', this.state.ports[this.state.defaultPort].url], function (err) {
if (err) { throw err; }
});
}
},
handleClickNotShowingCorrectly: function () {
+ metrics.track('Viewed Port Settings', {
+ from: 'preview'
+ });
this.transitionTo('containerSettingsPorts', {name: this.getParams().name});
},
render: function () {
diff --git a/src/ContainerListItem.react.js b/src/ContainerListItem.react.js
index 7cedd398b6..f065833aec 100644
--- a/src/ContainerListItem.react.js
+++ b/src/ContainerListItem.react.js
@@ -3,6 +3,7 @@ var React = require('react/addons');
var Router = require('react-router');
var remote = require('remote');
var dialog = remote.require('dialog');
+var metrics = require('./Metrics');
var ContainerStore = require('./ContainerStore');
var ContainerListItem = React.createClass({
@@ -20,6 +21,10 @@ var ContainerListItem = React.createClass({
buttons: ['Delete', 'Cancel']
}, function (index) {
if (index === 0) {
+ metrics.track('Deleted Container', {
+ from: 'list',
+ type: 'existing'
+ });
ContainerStore.remove(this.props.container.Name, function (err) {
console.error(err);
var containers = ContainerStore.sorted();
diff --git a/src/ContainerListNewItem.react.js b/src/ContainerListNewItem.react.js
index 9e88d2e514..21c3d53c45 100644
--- a/src/ContainerListNewItem.react.js
+++ b/src/ContainerListNewItem.react.js
@@ -2,6 +2,7 @@ var $ = require('jquery');
var React = require('react/addons');
var Router = require('react-router');
var ContainerStore = require('./ContainerStore');
+var metrics = require('./Metrics');
var ContainerListNewItem = React.createClass({
mixins: [Router.State, Router.Navigation],
@@ -15,6 +16,10 @@ var ContainerListNewItem = React.createClass({
},
handleDelete: function () {
var self = this;
+ metrics.track('Deleted Container', {
+ from: 'list',
+ type: 'new'
+ });
var containers = ContainerStore.sorted();
$(self.getDOMNode()).fadeOut(300, function () {
if (containers.length > 0) {
diff --git a/src/ContainerModal.react.js b/src/ContainerModal.react.js
deleted file mode 100644
index a78ef0edd0..0000000000
--- a/src/ContainerModal.react.js
+++ /dev/null
@@ -1,248 +0,0 @@
-var $ = require('jquery');
-var assign = require('object-assign');
-var React = require('react/addons');
-var Modal = require('react-bootstrap').Modal;
-var OverlayTrigger = require('react-bootstrap');
-var Popover = require('react-bootstrap/Popover');
-var RetinaImage = require('react-retina-image');
-var ContainerStore = require('./ContainerStore');
-var OverlayTrigger = require('react-bootstrap/OverlayTrigger');
-var Popover = require('react-bootstrap/Popover');
-
-var ContainerModal = React.createClass({
- _searchRequest: null,
- getInitialState: function () {
- return {
- query: '',
- results: ContainerStore.recommended(),
- loading: false,
- tags: {},
- active: null,
- };
- },
- componentDidMount: function () {
- this.refs.searchInput.getDOMNode().focus();
- ContainerStore.on(ContainerStore.CLIENT_RECOMMENDED_EVENT, this.update);
- },
- update: function () {
- if (!this.state.query.length) {
- this.setState({
- results: ContainerStore.recommended()
- });
- }
- },
- search: function (query) {
- if (this._searchRequest) {
- this._searchRequest.abort();
- this._searchRequest = null;
- }
-
- if (!query.length) {
- return;
- }
-
- this.setState({
- loading: true
- });
-
- var self = this;
- this._searchRequest = $.get('https://registry.hub.docker.com/v1/search?q=' + query, function (result) {
- self.setState({
- query: query,
- loading: false
- });
- self._searchRequest = null;
- if (self.isMounted()) {
- self.setState(result);
- }
- });
- },
- handleChange: function (e) {
- var query = e.target.value;
-
- if (query === this.state.query) {
- return;
- }
-
- clearTimeout(this.timeout);
- if (!query.length) {
- this.setState({
- query: query,
- results: ContainerStore.recommended()
- });
- } else {
- var self = this;
- this.timeout = setTimeout(function () {
- self.search(query);
- }, 200);
- }
- },
- handleClick: function (name) {
- this.props.onRequestHide();
- ContainerStore.create(name, 'latest', function (err) {
- if (err) {
- throw err;
- }
- }.bind(this));
- },
- handleTagClick: function (tag, name) {
- this.props.onRequestHide();
- ContainerStore.create(name, tag, function () {});
- },
- handleDropdownClick: function (name) {
- this.setState({
- active: name
- });
- if (this.state.tags[name]) {
- return;
- }
- $.get('https://registry.hub.docker.com/v1/repositories/' + name + '/tags', function (result) {
- var res = {};
- res[name] = result;
- console.log(assign(this.state.tags, res));
- this.setState({
- tags: assign(this.state.tags, res)
- });
- }.bind(this));
- },
- handleModalClick: function (event) {
- if (!this.state.active) {
- return;
- }
- if (!$('.popover').is(event.target)) {
- this.setState({
- active: null
- });
- }
- },
- componentDidUpdate: function () {
- if (!this.state.active) {
- return;
- }
- var $dropdown = $(this.getDOMNode()).find('[data-name="' + this.state.active + '"]');
- var $popover = $(this.getDOMNode()).find('.popover');
-
- $popover.offset({
- top: $dropdown.offset().top + 32,
- left: $dropdown.offset().left - $popover.width() / 2 + 11
- });
- },
- render: function () {
- var self = this;
- var data = this.state.results.slice(0, 7);
-
- var results;
- if (data.length) {
- var items = data.map(function (r) {
- var name;
- if (r.is_official) {
- name =