mirror of
https://github.com/docker/docs.git
synced 2026-03-27 22:38:54 +07:00
Adding metrics
This commit is contained in:
30
gulpfile.js
30
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 () {
|
||||
|
||||
15
package.json
15
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",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 = <span><RetinaImage src="official.png"/>{r.name}</span>;
|
||||
} else {
|
||||
name = <span>{r.name}</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={r.name}>
|
||||
<div className="info">
|
||||
<div className="name">
|
||||
{name}
|
||||
</div>
|
||||
<div className="properties">
|
||||
<div className="icon icon-star-9"></div>
|
||||
<div className="star-count">{r.star_count}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="action">
|
||||
<div className="btn-group">
|
||||
<button type="button" className="btn btn-primary" onClick={self.handleClick.bind(self, r.name)}>Create</button>
|
||||
<button type="button" className="btn btn-primary dropdown-toggle" onClick={self.handleDropdownClick.bind(self, r.name)} data-name={r.name}>
|
||||
<span className="icon-dropdown icon icon-arrow-37"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
|
||||
results = (
|
||||
<div className="result-list">
|
||||
<ul>
|
||||
{items}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
results = (
|
||||
<div className="no-results">
|
||||
<h3>
|
||||
No Results
|
||||
</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
var title = this.state.query ? 'Results' : 'Recommended';
|
||||
var loadingClasses = React.addons.classSet({
|
||||
hidden: !this.state.loading,
|
||||
loading: true
|
||||
});
|
||||
var magnifierClasses = React.addons.classSet({
|
||||
hidden: this.state.loading,
|
||||
icon: true,
|
||||
'icon-magnifier': true,
|
||||
'search-icon': true
|
||||
});
|
||||
|
||||
var question = (
|
||||
<div className="question">
|
||||
<OverlayTrigger trigger="hover" placement="bottom" overlay={<Popover>An image is a template for a container.</Popover>}>
|
||||
<span>What's an image?</span>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
);
|
||||
|
||||
var tagData = self.state.tags[this.state.active];
|
||||
var tags;
|
||||
if (tagData) {
|
||||
var list = tagData.map(function (t) {
|
||||
return <li key={t.name} onClick={self.handleTagClick.bind(self, t.name, self.state.active)}>{t.name}</li>;
|
||||
});
|
||||
tags = (
|
||||
<ul>
|
||||
{list}
|
||||
</ul>
|
||||
);
|
||||
} else {
|
||||
tags = <RetinaImage className="tags-loading" src="loading.png"/>;
|
||||
}
|
||||
|
||||
var popoverClasses = React.addons.classSet({
|
||||
popover: true,
|
||||
hidden: !this.state.active
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal {...this.props} animation={false} className="create-modal">
|
||||
<div className="modal-body" onClick={this.handleModalClick}>
|
||||
<section className="search">
|
||||
<div className="search-bar">
|
||||
<input type="search" ref="searchInput" className="form-control" placeholder="Find an existing image" onChange={this.handleChange}/>
|
||||
<div className={magnifierClasses}></div>
|
||||
<RetinaImage className={loadingClasses} src="loading.png"/>
|
||||
</div>
|
||||
{question}
|
||||
<div className="results">
|
||||
<div className="title">{title}</div>
|
||||
{results}
|
||||
</div>
|
||||
</section>
|
||||
<Popover placement="bottom" className={popoverClasses}>
|
||||
{tags}
|
||||
</Popover>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = ContainerModal;
|
||||
@@ -6,6 +6,7 @@ var path = require('path');
|
||||
var remote = require('remote');
|
||||
var rimraf = require('rimraf');
|
||||
var fs = require('fs');
|
||||
var metrics = require('./Metrics');
|
||||
var dialog = remote.require('dialog');
|
||||
var ContainerStore = require('./ContainerStore');
|
||||
var ContainerUtil = require('./ContainerUtil');
|
||||
@@ -91,8 +92,12 @@ var ContainerSettingsGeneral = React.createClass({
|
||||
}
|
||||
ContainerStore.rename(oldName, newName, err => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
this.setState({
|
||||
nameError: err.message
|
||||
});
|
||||
return;
|
||||
}
|
||||
metrics.track('Changed Container Name');
|
||||
this.transitionTo('containerSettingsGeneral', {name: newName});
|
||||
var oldPath = path.join(process.env.HOME, 'Kitematic', oldName);
|
||||
var newPath = path.join(process.env.HOME, 'Kitematic', newName);
|
||||
@@ -127,6 +132,7 @@ var ContainerSettingsGeneral = React.createClass({
|
||||
envVarList.push(key + '=' + val);
|
||||
});
|
||||
var self = this;
|
||||
metrics.track('Saved Environment Variables');
|
||||
ContainerStore.updateContainer(self.props.container.Name, {
|
||||
Env: envVarList
|
||||
}, function (err) {
|
||||
@@ -151,18 +157,21 @@ var ContainerSettingsGeneral = React.createClass({
|
||||
});
|
||||
$('#new-env-key').val('');
|
||||
$('#new-env-val').val('');
|
||||
metrics.track('Added Pending Environment Variable');
|
||||
},
|
||||
handleRemoveEnvVar: function (key) {
|
||||
var newEnv = _.omit(this.state.env, key);
|
||||
this.setState({
|
||||
env: newEnv
|
||||
});
|
||||
metrics.track('Removed Environment Variable');
|
||||
},
|
||||
handleRemovePendingEnvVar: function (key) {
|
||||
var newEnv = _.omit(this.state.pendingEnv, key);
|
||||
this.setState({
|
||||
pendingEnv: newEnv
|
||||
});
|
||||
metrics.track('Removed Pending Environment Variable');
|
||||
},
|
||||
handleDeleteContainer: function () {
|
||||
dialog.showMessageBox({
|
||||
@@ -176,6 +185,10 @@ var ContainerSettingsGeneral = React.createClass({
|
||||
});
|
||||
}
|
||||
if (index === 0) {
|
||||
metrics.track('Deleted Container', {
|
||||
from: 'settings',
|
||||
type: 'existing'
|
||||
});
|
||||
ContainerStore.remove(this.props.container.Name, function (err) {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ var Router = require('react-router');
|
||||
var exec = require('exec');
|
||||
var ContainerStore = require('./ContainerStore');
|
||||
var ContainerUtil = require('./ContainerUtil');
|
||||
var metrics = require('./Metrics');
|
||||
|
||||
var ContainerSettingsPorts = React.createClass({
|
||||
mixins: [Router.State, Router.Navigation],
|
||||
@@ -34,6 +35,9 @@ var ContainerSettingsPorts = React.createClass({
|
||||
});
|
||||
},
|
||||
handleViewLink: function (url) {
|
||||
metrics.track('Opened In Browser', {
|
||||
from: 'settings'
|
||||
});
|
||||
exec(['open', url], function (err) {
|
||||
if (err) { throw err; }
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ var Router = require('react-router');
|
||||
var remote = require('remote');
|
||||
var exec = require('exec');
|
||||
var dialog = remote.require('dialog');
|
||||
var metrics = require('./Metrics');
|
||||
var ContainerStore = require('./ContainerStore');
|
||||
|
||||
var ContainerSettingsVolumes = React.createClass({
|
||||
@@ -16,6 +17,7 @@ var ContainerSettingsVolumes = React.createClass({
|
||||
}
|
||||
var directory = filenames[0];
|
||||
if (directory) {
|
||||
metrics.track('Chose Directory for Volume');
|
||||
var volumes = _.clone(self.props.container.Volumes);
|
||||
volumes[dockerVol] = directory;
|
||||
var binds = _.pairs(volumes).map(function (pair) {
|
||||
@@ -30,6 +32,9 @@ var ContainerSettingsVolumes = React.createClass({
|
||||
});
|
||||
},
|
||||
handleOpenVolumeClick: function (path) {
|
||||
metrics.track('Opened Volume Directory', {
|
||||
from: 'settings'
|
||||
});
|
||||
exec(['open', path], function (err) {
|
||||
if (err) { throw err; }
|
||||
});
|
||||
@@ -43,7 +48,7 @@ var ContainerSettingsVolumes = React.createClass({
|
||||
if (!val || val.indexOf(process.env.HOME) === -1) {
|
||||
val = (
|
||||
<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>
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@ var async = require('async');
|
||||
var path = require('path');
|
||||
var assign = require('object-assign');
|
||||
var docker = require('./Docker');
|
||||
var metrics = require('./Metrics');
|
||||
var registry = require('./Registry');
|
||||
var LogStore = require('./LogStore');
|
||||
|
||||
@@ -277,6 +278,7 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
||||
_muted[containerName] = true;
|
||||
_progress[containerName] = 0;
|
||||
self._pullImage(repository, tag, function () {
|
||||
metrics.track('Container Finished Creating');
|
||||
delete _placeholders[containerName];
|
||||
localStorage.setItem('store.placeholders', JSON.stringify(_placeholders));
|
||||
self._createContainer(containerName, {Image: imageName}, function () {
|
||||
@@ -372,7 +374,19 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
||||
},
|
||||
sorted: function () {
|
||||
return _.values(this.containers()).sort(function (a, b) {
|
||||
return a.Name.localeCompare(b.Name);
|
||||
if (a.State.Downloading && !b.State.Downloading) {
|
||||
return -1;
|
||||
} else if (!a.State.Downloading && b.State.Downloading) {
|
||||
return 1;
|
||||
} else {
|
||||
if (a.State.Running && !b.State.Running) {
|
||||
return -1;
|
||||
} else if (!a.State.Running && b.State.Running) {
|
||||
return 1;
|
||||
} else {
|
||||
return a.Name.localeCompare(b.Name);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
progress: function (name) {
|
||||
|
||||
@@ -6,6 +6,7 @@ var ContainerList = require('./ContainerList.react');
|
||||
var Header = require('./Header.react');
|
||||
var ipc = require('ipc');
|
||||
var remote = require('remote');
|
||||
var metrics = require('./Metrics');
|
||||
var autoUpdater = remote.require('auto-updater');
|
||||
|
||||
var Containers = React.createClass({
|
||||
@@ -76,9 +77,10 @@ var Containers = React.createClass({
|
||||
handleNewContainer: function () {
|
||||
$(this.getDOMNode()).find('.new-container-item').parent().fadeIn();
|
||||
this.transitionTo('new');
|
||||
metrics.track('Pressed New Container');
|
||||
},
|
||||
handleAutoUpdateClick: function () {
|
||||
console.log('CLICKED UPDATE');
|
||||
metrics.track('Restarted to Update');
|
||||
ipc.send('command', 'application:quit-install');
|
||||
},
|
||||
render: function () {
|
||||
|
||||
@@ -2,6 +2,7 @@ var $ = require('jquery');
|
||||
var React = require('react/addons');
|
||||
var RetinaImage = require('react-retina-image');
|
||||
var ContainerStore = require('./ContainerStore');
|
||||
var metrics = require('./Metrics');
|
||||
|
||||
var ImageCard = React.createClass({
|
||||
getInitialState: function () {
|
||||
@@ -16,8 +17,10 @@ var ImageCard = React.createClass({
|
||||
});
|
||||
var $tagOverlay = $(this.getDOMNode()).find('.tag-overlay');
|
||||
$tagOverlay.fadeOut(300);
|
||||
metrics.track('Selected Image Tag');
|
||||
},
|
||||
handleClick: function (name) {
|
||||
metrics.track('Created Container');
|
||||
ContainerStore.create(name, this.state.chosenTag, function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
@@ -33,7 +36,6 @@ var ImageCard = React.createClass({
|
||||
tags: result
|
||||
});
|
||||
}.bind(this));
|
||||
|
||||
},
|
||||
handleCloseTagOverlay: function () {
|
||||
var $tagOverlay = $(this.getDOMNode()).find('.tag-overlay');
|
||||
|
||||
@@ -11,6 +11,7 @@ var ContainerStore = require('./ContainerStore');
|
||||
var SetupStore = require('./SetupStore');
|
||||
var MenuTemplate = require('./MenuTemplate');
|
||||
var Menu = remote.require('menu');
|
||||
var metrics = require('./Metrics');
|
||||
var settingsjson;
|
||||
|
||||
try {
|
||||
@@ -37,6 +38,10 @@ bugsnag.appVersion = app.getVersion();
|
||||
var menu = Menu.buildFromTemplate(MenuTemplate);
|
||||
Menu.setApplicationMenu(menu);
|
||||
|
||||
setInterval(function () {
|
||||
metrics.track('app heartbeat');
|
||||
}, 14400000);
|
||||
|
||||
router.run(Handler => React.render(<Handler/>, document.body));
|
||||
SetupStore.run().then(boot2docker.ip).then(ip => {
|
||||
console.log(ip);
|
||||
|
||||
@@ -5,6 +5,7 @@ var docker = require('./Docker');
|
||||
var BrowserWindow = remote.require('browser-window');
|
||||
var router = require('./Router');
|
||||
var util = require('./Util');
|
||||
var metrics = require('./Metrics');
|
||||
|
||||
// main.js
|
||||
var MenuTemplate = [
|
||||
@@ -71,6 +72,7 @@ var MenuTemplate = [
|
||||
label: 'Open Docker Terminal',
|
||||
accelerator: 'Command+Shift+T',
|
||||
click: function() {
|
||||
metrics.track('Opened Docker Terminal');
|
||||
var terminal = path.join(process.cwd(), 'resources', 'terminal');
|
||||
var cmd = [terminal, `DOCKER_HOST=${'tcp://' + docker.host + ':2376'} DOCKER_CERT_PATH=${path.join(process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'], '.boot2docker/certs/boot2docker-vm')} DOCKER_TLS_VERIFY=1 $SHELL`];
|
||||
util.exec(cmd).then(() => {});
|
||||
|
||||
58
src/Metrics.js
Normal file
58
src/Metrics.js
Normal file
@@ -0,0 +1,58 @@
|
||||
var app = require('remote').require('app');
|
||||
var assign = require('object-assign');
|
||||
var Mixpanel = require('mixpanel');
|
||||
var uuid = require('node-uuid');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var settings;
|
||||
|
||||
try {
|
||||
settings = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'settings.json'), 'utf8'));
|
||||
} catch (err) {
|
||||
settings = {};
|
||||
}
|
||||
|
||||
var token = process.env.NODE_ENV === 'development' ? settings['mixpanel-dev'] : settings.mixpanel;
|
||||
if (!token) {
|
||||
token = 'none';
|
||||
}
|
||||
|
||||
var mixpanel = Mixpanel.init(token);
|
||||
|
||||
if (localStorage.getItem('metrics.enabled') === null) {
|
||||
localStorage.setItem('metrics.enabled', true);
|
||||
}
|
||||
|
||||
var Metrics = {
|
||||
enabled: function () {
|
||||
return localStorage.getItem('metrics.enabled') === 'true';
|
||||
},
|
||||
setEnabled: function (enabled) {
|
||||
localStorage.setItem('metrics.enabled', !!enabled);
|
||||
},
|
||||
track: function (name, data) {
|
||||
data = data || {};
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (localStorage.getItem('metrics.enabled') !== 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
var id = localStorage.getItem('metrics.id');
|
||||
if (!id) {
|
||||
localStorage.setItem('metrics.id', uuid.v4());
|
||||
}
|
||||
|
||||
var os = navigator.userAgent.match(/Mac OS X (\d+_\d+_\d+)/)[1].replace(/_/g, '.');
|
||||
mixpanel.track(name, assign({
|
||||
distinct_id: id,
|
||||
version: app.getVersion(),
|
||||
'Operating System Version': os,
|
||||
beta: !!settings.beta
|
||||
}, data));
|
||||
},
|
||||
|
||||
};
|
||||
module.exports = Metrics;
|
||||
@@ -5,6 +5,7 @@ var RetinaImage = require('react-retina-image');
|
||||
var Radial = require('./Radial.react');
|
||||
var ImageCard = require('./ImageCard.react');
|
||||
var Promise = require('bluebird');
|
||||
var metrics = require('./Metrics');
|
||||
|
||||
var _recommended = [];
|
||||
var _searchPromise = null;
|
||||
@@ -47,6 +48,7 @@ var NewContainer = React.createClass({
|
||||
});
|
||||
|
||||
_searchPromise = Promise.delay(200).then(() => Promise.resolve($.get('https://registry.hub.docker.com/v1/search?q=' + query))).cancellable().then(data => {
|
||||
metrics.track('Searched for Images');
|
||||
this.setState({
|
||||
results: data.results,
|
||||
query: query,
|
||||
|
||||
@@ -1,53 +1,58 @@
|
||||
var React = require('react/addons');
|
||||
var assign = require('object-assign');
|
||||
var ipc = require('ipc');
|
||||
var metrics = require('./Metrics');
|
||||
var Router = require('react-router');
|
||||
|
||||
// TODO: move this somewhere else
|
||||
if (localStorage.getItem('options')) {
|
||||
ipc.send('vm', JSON.parse(localStorage.getItem('options')).save_vm_on_quit);
|
||||
if (localStorage.getItem('settings.saveVMOnQuit') === 'true') {
|
||||
ipc.send('vm', true);
|
||||
} else {
|
||||
ipc.send('vm', false);
|
||||
}
|
||||
|
||||
var Preferences = React.createClass({
|
||||
mixins: [Router.Navigation],
|
||||
getInitialState: function () {
|
||||
var data = JSON.parse(localStorage.getItem('options'));
|
||||
return assign({
|
||||
save_vm_on_quit: true,
|
||||
report_analytics: true
|
||||
}, data || {});
|
||||
},
|
||||
handleChange: function (key) {
|
||||
var change = {};
|
||||
change[key] = !this.state[key];
|
||||
console.log(change);
|
||||
this.setState(change);
|
||||
},
|
||||
saveState: function () {
|
||||
ipc.send('vm', this.state.save_vm_on_quit);
|
||||
localStorage.setItem('options', JSON.stringify(this.state));
|
||||
},
|
||||
componentDidMount: function () {
|
||||
this.saveState();
|
||||
},
|
||||
componentDidUpdate: function () {
|
||||
this.saveState();
|
||||
return {
|
||||
saveVMOnQuit: localStorage.getItem('settings.saveVMOnQuit') === 'true',
|
||||
metricsEnabled: metrics.enabled()
|
||||
};
|
||||
},
|
||||
handleGoBackClick: function () {
|
||||
this.goBack();
|
||||
metrics.track('Went Back From Preferences');
|
||||
},
|
||||
handleChangeSaveVMOnQuit: function (e) {
|
||||
var checked = e.target.checked;
|
||||
this.setState({
|
||||
saveVMOnQuit: checked
|
||||
});
|
||||
ipc.send('vm', checked);
|
||||
metrics.track('Toggled Save VM On Quit', {
|
||||
save: checked
|
||||
});
|
||||
},
|
||||
handleChangeMetricsEnabled: function (e) {
|
||||
var checked = e.target.checked;
|
||||
this.setState({
|
||||
metricsEnabled: checked
|
||||
});
|
||||
metrics.setEnabled(checked);
|
||||
metrics.track('Toggled Metrics', {
|
||||
enabled: checked
|
||||
});
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div className="preferences">
|
||||
<div className="preferences-content">
|
||||
<a href="#" onClick={this.handleGoBackClick}>Go Back</a>
|
||||
<a onClick={this.handleGoBackClick}>Go Back</a>
|
||||
<div className="title">VM Settings</div>
|
||||
<div className="option">
|
||||
<div className="option-name">
|
||||
Save Linux VM state on closing Kitematic
|
||||
</div>
|
||||
<div className="option-value">
|
||||
<input type="checkbox" checked={this.state.save_vm_on_quit} onChange={this.handleChange.bind(this, 'save_vm_on_quit')}/>
|
||||
<input type="checkbox" checked={this.state.saveVMOnQuit} onChange={this.handleChangeSaveVMOnQuit}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="title">App Settings</div>
|
||||
@@ -56,10 +61,9 @@ var Preferences = React.createClass({
|
||||
Report anonymous usage analytics
|
||||
</div>
|
||||
<div className="option-value">
|
||||
<input type="checkbox" checked={this.state.report_analytics} onChange={this.handleChange.bind(this, 'report_analytics')}/>
|
||||
<input type="checkbox" checked={this.state.metricsEnabled} onChange={this.handleChangeMetricsEnabled}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ var SetupStore = require('./SetupStore');
|
||||
var RetinaImage = require('react-retina-image');
|
||||
var Header = require('./Header.react');
|
||||
var Util = require('./Util');
|
||||
var metrics = require('./Metrics');
|
||||
|
||||
var Setup = React.createClass({
|
||||
mixins: [ Router.Navigation ],
|
||||
@@ -28,6 +29,7 @@ var Setup = React.createClass({
|
||||
SetupStore.removeListener(SetupStore.ERROR_EVENT, this.update);
|
||||
},
|
||||
handleRetry: function () {
|
||||
metrics.track('Retried Setup');
|
||||
SetupStore.retry();
|
||||
},
|
||||
handleOpenWebsite: function () {
|
||||
|
||||
@@ -8,6 +8,7 @@ var virtualBox = require('./VirtualBox');
|
||||
var setupUtil = require('./SetupUtil');
|
||||
var util = require('./Util');
|
||||
var assign = require('object-assign');
|
||||
var metrics = require('./Metrics');
|
||||
|
||||
var _currentStep = null;
|
||||
var _error = null;
|
||||
@@ -167,6 +168,7 @@ var SetupStore = assign(Object.create(EventEmitter.prototype), {
|
||||
return Promise.resolve();
|
||||
},
|
||||
run: Promise.coroutine(function* () {
|
||||
metrics.track('Started Setup');
|
||||
yield this.updateBinaries();
|
||||
var steps = yield this.requiredSteps();
|
||||
for (let step of steps) {
|
||||
@@ -182,9 +184,15 @@ var SetupStore = assign(Object.create(EventEmitter.prototype), {
|
||||
this.emit(this.PROGRESS_EVENT);
|
||||
}
|
||||
});
|
||||
metrics.track('Completed Step', {
|
||||
name: step.name
|
||||
});
|
||||
step.percent = 100;
|
||||
break;
|
||||
} catch (err) {
|
||||
metrics.track('Setup Failed', {
|
||||
step: step.name
|
||||
});
|
||||
console.log('Setup encountered an error.');
|
||||
console.log(err);
|
||||
if (err) {
|
||||
@@ -198,6 +206,7 @@ var SetupStore = assign(Object.create(EventEmitter.prototype), {
|
||||
}
|
||||
}
|
||||
}
|
||||
metrics.track('Finished Setup');
|
||||
_currentStep = null;
|
||||
})
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user