Merging
|
@ -11,6 +11,7 @@ identity*
|
|||
|
||||
# Resources
|
||||
resources/docker-*
|
||||
resources/boot2docker-*
|
||||
|
||||
# Cache
|
||||
cache
|
||||
|
|
|
@ -11,7 +11,6 @@ var less = require('gulp-less');
|
|||
var livereload = require('gulp-livereload');
|
||||
var packagejson = require('./package.json');
|
||||
var plumber = require('gulp-plumber');
|
||||
var react = require('gulp-react');
|
||||
var runSequence = require('run-sequence');
|
||||
var shell = require('gulp-shell');
|
||||
var sourcemaps = require('gulp-sourcemaps');
|
||||
|
@ -45,7 +44,6 @@ gulp.task('js', function () {
|
|||
this.emit('end');
|
||||
}))
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(react())
|
||||
.pipe(babel({blacklist: ['regenerator']}))
|
||||
.pipe(sourcemaps.write())
|
||||
.pipe(gulp.dest(options.dev ? './build' : './dist/osx/' + options.appFilename + '/Contents/Resources/app/build'))
|
||||
|
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 85 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 844 B After Width: | Height: | Size: 856 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
BIN
images/logo.png
Before Width: | Height: | Size: 571 B After Width: | Height: | Size: 593 B |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 966 B After Width: | Height: | Size: 976 B |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 749 B After Width: | Height: | Size: 732 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 92 KiB |
|
@ -6,6 +6,6 @@
|
|||
<title>Kitematic</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="Startup.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
13
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "Kitematic",
|
||||
"version": "0.5.15",
|
||||
"version": "0.5.22",
|
||||
"author": "Kitematic",
|
||||
"description": "Simple Docker Container management for Mac OS X.",
|
||||
"homepage": "https://kitematic.com/",
|
||||
|
@ -15,7 +15,7 @@
|
|||
"test": "jest",
|
||||
"release": "gulp release",
|
||||
"release:beta": "gulp release --beta",
|
||||
"lint": "jsxhint src && jsxhint browser",
|
||||
"lint": "jsxhint src",
|
||||
"reset": "gulp reset"
|
||||
},
|
||||
"licenses": [
|
||||
|
@ -27,6 +27,7 @@
|
|||
"jest": {
|
||||
"scriptPreprocessor": "<rootDir>/util/preprocessor.js",
|
||||
"setupEnvScriptFile": "<rootDir>/util/testenv.js",
|
||||
"setupTestFrameworkScriptFile": "<rootDir>/util/prepare.js",
|
||||
"collectCoverage": true,
|
||||
"testDirectoryName": "src",
|
||||
"testPathIgnorePatterns": [
|
||||
|
@ -42,7 +43,8 @@
|
|||
"<rootDir>/node_modules/.*JSONStream",
|
||||
"<rootDir>/node_modules/object-assign",
|
||||
"<rootDir>/node_modules/underscore",
|
||||
"<rootDir>/node_modules/bluebird"
|
||||
"<rootDir>/node_modules/bluebird",
|
||||
"<rootDir>/node_modules/source-map-support"
|
||||
]
|
||||
},
|
||||
"docker-version": "1.6.0",
|
||||
|
@ -69,6 +71,7 @@
|
|||
"mixpanel": "0.2.0",
|
||||
"node-uuid": "^1.4.3",
|
||||
"object-assign": "^2.0.0",
|
||||
"parseUri": "^1.2.3-2",
|
||||
"react": "^0.13.1",
|
||||
"react-bootstrap": "^0.20.3",
|
||||
"react-retina-image": "^1.1.2",
|
||||
|
@ -91,7 +94,6 @@
|
|||
"gulp-less": "^3.0.2",
|
||||
"gulp-livereload": "^3.8.0",
|
||||
"gulp-plumber": "^1.0.0",
|
||||
"gulp-react": "^3.0.1",
|
||||
"gulp-shell": "^0.4.1",
|
||||
"gulp-sourcemaps": "^1.5.2",
|
||||
"gulp-util": "^3.0.4",
|
||||
|
@ -99,6 +101,7 @@
|
|||
"jsxhint": "^0.14.0",
|
||||
"minimist": "^1.1.1",
|
||||
"react-tools": "^0.13.1",
|
||||
"run-sequence": "^1.0.2"
|
||||
"run-sequence": "^1.0.2",
|
||||
"source-map-support": "^0.2.10"
|
||||
}
|
||||
}
|
||||
|
|
51
src/Main.js
|
@ -1,51 +0,0 @@
|
|||
require.main.paths.splice(0, 0, process.env.NODE_PATH);
|
||||
var remote = require('remote');
|
||||
var ContainerStore = require('./ContainerStore');
|
||||
var Menu = remote.require('menu');
|
||||
var React = require('react');
|
||||
var SetupStore = require('./SetupStore');
|
||||
var bugsnag = require('bugsnag-js');
|
||||
var ipc = require('ipc');
|
||||
var machine = require('./DockerMachine');
|
||||
var metrics = require('./Metrics');
|
||||
var router = require('./Router');
|
||||
var template = require('./MenuTemplate');
|
||||
var webUtil = require('./WebUtil');
|
||||
|
||||
webUtil.addWindowSizeSaving();
|
||||
webUtil.addLiveReload();
|
||||
webUtil.addBugReporting();
|
||||
webUtil.disableGlobalBackspace();
|
||||
|
||||
Menu.setApplicationMenu(Menu.buildFromTemplate(template()));
|
||||
|
||||
metrics.track('Started App');
|
||||
metrics.track('app heartbeat');
|
||||
setInterval(function () {
|
||||
metrics.track('app heartbeat');
|
||||
}, 14400000);
|
||||
|
||||
router.run(Handler => React.render(<Handler/>, document.body));
|
||||
|
||||
SetupStore.setup().then(() => {
|
||||
Menu.setApplicationMenu(Menu.buildFromTemplate(template()));
|
||||
ContainerStore.on(ContainerStore.SERVER_ERROR_EVENT, (err) => {
|
||||
bugsnag.notify(err);
|
||||
});
|
||||
ContainerStore.init(function () {
|
||||
router.transitionTo('containers');
|
||||
});
|
||||
}).catch(err => {
|
||||
metrics.track('Setup Failed', {
|
||||
step: 'catch',
|
||||
message: err.message
|
||||
});
|
||||
console.log(err);
|
||||
bugsnag.notify(err);
|
||||
});
|
||||
|
||||
ipc.on('application:quitting', opts => {
|
||||
if (!opts.updating && localStorage.getItem('settings.closeVMOnQuit') === 'true') {
|
||||
machine.stop();
|
||||
}
|
||||
});
|
|
@ -1,20 +0,0 @@
|
|||
var util = require('./Util');
|
||||
var path = require('path');
|
||||
|
||||
module.exports = {
|
||||
resourceDir() {
|
||||
return process.env.RESOURCES_PATH;
|
||||
},
|
||||
macsudo() {
|
||||
return path.join(this.resourceDir(), 'macsudo');
|
||||
},
|
||||
terminal() {
|
||||
return path.join(this.resourceDir(), 'terminal');
|
||||
},
|
||||
docker() {
|
||||
return path.join(this.resourceDir(), 'docker-' + util.packagejson()['docker-version'] + util.binsEnding());
|
||||
},
|
||||
docker_machine() {
|
||||
return path.join(this.resourceDir(), 'docker-machine-' + util.packagejson()['docker-machine-version'] + util.binsEnding());
|
||||
}
|
||||
};
|
|
@ -1,46 +0,0 @@
|
|||
var React = require('react/addons');
|
||||
var Setup = require('./Setup.react');
|
||||
var Containers = require('./Containers.react');
|
||||
var ContainerDetails = require('./ContainerDetails.react');
|
||||
var ContainerHome = require('./ContainerHome.react');
|
||||
var ContainerLogs = require('./ContainerLogs.react');
|
||||
var ContainerSettings = require('./ContainerSettings.react');
|
||||
var ContainerSettingsGeneral = require('./ContainerSettingsGeneral.react');
|
||||
var ContainerSettingsPorts = require('./ContainerSettingsPorts.react');
|
||||
var ContainerSettingsVolumes = require('./ContainerSettingsVolumes.react');
|
||||
var Preferences = require('./Preferences.react');
|
||||
var NewContainer = require('./NewContainer.react');
|
||||
var Router = require('react-router');
|
||||
|
||||
var Route = Router.Route;
|
||||
var DefaultRoute = Router.DefaultRoute;
|
||||
var RouteHandler = Router.RouteHandler;
|
||||
|
||||
var App = React.createClass({
|
||||
render: function () {
|
||||
return (
|
||||
<RouteHandler/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var routes = (
|
||||
<Route name="app" path="/" handler={App}>
|
||||
<Route name="containers" handler={Containers}>
|
||||
<Route name="containerDetails" path="containers/:name" handler={ContainerDetails}>
|
||||
<Route name="containerHome" path="containers/:name/home" handler={ContainerHome} />
|
||||
<Route name="containerLogs" path="containers/:name/logs" handler={ContainerLogs}/>
|
||||
<Route name="containerSettings" path="containers/:name/settings" handler={ContainerSettings}>
|
||||
<Route name="containerSettingsGeneral" path="containers/:name/settings/general" handler={ContainerSettingsGeneral}/>
|
||||
<Route name="containerSettingsPorts" path="containers/:name/settings/ports" handler={ContainerSettingsPorts}/>
|
||||
<Route name="containerSettingsVolumes" path="containers/:name/settings/volumes" handler={ContainerSettingsVolumes}/>
|
||||
</Route>
|
||||
</Route>
|
||||
<Route name="preferences" path="/preferences" handler={Preferences}/>
|
||||
<DefaultRoute name="new" handler={NewContainer}/>
|
||||
</Route>
|
||||
<DefaultRoute name="setup" handler={Setup}/>
|
||||
</Route>
|
||||
);
|
||||
|
||||
module.exports = routes;
|
|
@ -1 +0,0 @@
|
|||
require('./Main');
|
78
src/Util.js
|
@ -1,78 +0,0 @@
|
|||
var exec = require('exec');
|
||||
var execProper = require('child_process').exec;
|
||||
var Promise = require('bluebird');
|
||||
var fs = require('fs-promise');
|
||||
var path = require('path');
|
||||
|
||||
module.exports = {
|
||||
exec(args, options) {
|
||||
options = options || {};
|
||||
return new Promise((resolve, reject) => {
|
||||
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\nstdout:' + stdout + '\nstderr:' + stderr));
|
||||
} else {
|
||||
resolve(stdout);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
execProper(args, options) {
|
||||
options = options || {};
|
||||
var cmd = Array.isArray(args) ? args.join(' ') : args;
|
||||
return new Promise((resolve, reject) => {
|
||||
execProper(cmd, options, (stderr, stdout, code) => {
|
||||
if (code) {
|
||||
reject(new Error(cmd + ' returned non zero exit code\nstdout:' + stdout + '\nstderr:' + stderr));
|
||||
} else {
|
||||
resolve(stdout);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
home() {
|
||||
return process.env[this.isWindows() ? 'USERPROFILE' : 'HOME'];
|
||||
},
|
||||
binsPath() {
|
||||
return this.isWindows() ? path.join(this.home(), 'Kitematic-bins') : path.join('/usr/local/bin');
|
||||
},
|
||||
binsEnding() {
|
||||
return this.isWindows() ? '.exe' : '';
|
||||
},
|
||||
dockerBinPath() {
|
||||
return path.join(this.binsPath(), 'docker' + this.binsEnding());
|
||||
},
|
||||
dockerMachineBinPath() {
|
||||
return path.join(this.binsPath(), 'docker-machine' + this.binsEnding());
|
||||
},
|
||||
pathDoesNotExistOrDenied(path) {
|
||||
if(this.isWindows()) {
|
||||
return (!fs.existsSync(path));
|
||||
} else {
|
||||
return (!fs.existsSync(path) || fs.statSync(path).gid !== 80 || fs.statSync(path).uid !== process.getuid());
|
||||
}
|
||||
},
|
||||
supportDir() {
|
||||
var acc = path.join(this.home(), 'Library', 'Application\ Support', 'Kitematic');
|
||||
fs.mkdirsSync(acc);
|
||||
return acc;
|
||||
},
|
||||
packagejson() {
|
||||
return JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
|
||||
},
|
||||
settingsjson() {
|
||||
var settingsjson = {};
|
||||
try {
|
||||
settingsjson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'settings.json'), 'utf8'));
|
||||
} catch (err) {}
|
||||
return settingsjson;
|
||||
},
|
||||
isWindows() {
|
||||
return process.platform === 'win32';
|
||||
},
|
||||
CommandOrCtrl() {
|
||||
return this.isWindows() ? 'Ctrl' : 'Command';
|
||||
},
|
||||
webPorts: ['80', '8000', '8080', '3000', '5000', '2368', '9200', '8983']
|
||||
};
|
|
@ -0,0 +1,74 @@
|
|||
require.main.paths.splice(0, 0, process.env.NODE_PATH);
|
||||
var remote = require('remote');
|
||||
var ContainerStore = require('./stores/ContainerStore');
|
||||
var Menu = remote.require('menu');
|
||||
var React = require('react');
|
||||
var SetupStore = require('./stores/SetupStore');
|
||||
var bugsnag = require('bugsnag-js');
|
||||
var ipc = require('ipc');
|
||||
var machine = require('./utils/DockerMachineUtil');
|
||||
var metrics = require('./utils/MetricsUtil');
|
||||
var router = require('./router');
|
||||
var template = require('./menutemplate');
|
||||
var webUtil = require('./utils/WebUtil');
|
||||
var urlUtil = require ('./utils/URLUtil');
|
||||
var app = remote.require('app');
|
||||
var request = require('request');
|
||||
|
||||
webUtil.addWindowSizeSaving();
|
||||
webUtil.addLiveReload();
|
||||
webUtil.addBugReporting();
|
||||
webUtil.disableGlobalBackspace();
|
||||
|
||||
Menu.setApplicationMenu(Menu.buildFromTemplate(template()));
|
||||
|
||||
metrics.track('Started App');
|
||||
metrics.track('app heartbeat');
|
||||
setInterval(function () {
|
||||
metrics.track('app heartbeat');
|
||||
}, 14400000);
|
||||
|
||||
router.run(Handler => React.render(<Handler/>, document.body));
|
||||
|
||||
SetupStore.setup().then(() => {
|
||||
if (ContainerStore.pending()) {
|
||||
router.transitionTo('pull');
|
||||
} else {
|
||||
router.transitionTo('new');
|
||||
}
|
||||
Menu.setApplicationMenu(Menu.buildFromTemplate(template()));
|
||||
ContainerStore.on(ContainerStore.SERVER_ERROR_EVENT, (err) => {
|
||||
bugsnag.notify(err);
|
||||
});
|
||||
ContainerStore.init(function () {});
|
||||
}).catch(err => {
|
||||
metrics.track('Setup Failed', {
|
||||
step: 'catch',
|
||||
message: err.message
|
||||
});
|
||||
console.log(err);
|
||||
bugsnag.notify(err);
|
||||
});
|
||||
|
||||
ipc.on('application:quitting', () => {
|
||||
if (localStorage.getItem('settings.closeVMOnQuit') === 'true') {
|
||||
machine.stop();
|
||||
}
|
||||
});
|
||||
|
||||
// Event fires when the app receives a docker:// URL such as
|
||||
// docker://repository/run/redis
|
||||
ipc.on('application:open-url', opts => {
|
||||
request.get('https://kitematic.com/flags.json', (err, response, body) => {
|
||||
if (err || response.statusCode !== 200) {
|
||||
return;
|
||||
}
|
||||
|
||||
var flags = JSON.parse(body);
|
||||
if (!flags) {
|
||||
return;
|
||||
}
|
||||
|
||||
urlUtil.openUrl(opts.url, flags, app.getVersion());
|
||||
});
|
||||
});
|
|
@ -8,14 +8,7 @@ var path = require('path');
|
|||
process.env.NODE_PATH = path.join(__dirname, '/../node_modules');
|
||||
process.env.RESOURCES_PATH = path.join(__dirname, '/../resources');
|
||||
process.chdir(path.join(__dirname, '..'));
|
||||
|
||||
if(process.platform === 'win32') {
|
||||
process.env.PATH = process.env.PATH + ';' + process.env['USERPROFILE'] + '\\Kitematic-bins';
|
||||
} else {
|
||||
process.env.PATH = '/usr/local/bin:' + process.env.PATH;
|
||||
}
|
||||
|
||||
|
||||
process.env.PATH = '/usr/local/bin:' + process.env.PATH;
|
||||
|
||||
var size = {}, settingsjson = {};
|
||||
try {
|
||||
|
@ -25,6 +18,13 @@ try {
|
|||
settingsjson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'settings.json'), 'utf8'));
|
||||
} catch (err) {}
|
||||
|
||||
|
||||
var openURL = null;
|
||||
app.on('open-url', function (event, url) {
|
||||
event.preventDefault();
|
||||
openURL = url;
|
||||
});
|
||||
|
||||
app.on('ready', function () {
|
||||
var mainWindow = new BrowserWindow({
|
||||
width: size.width || 1000,
|
||||
|
@ -52,9 +52,9 @@ app.on('ready', function () {
|
|||
});
|
||||
|
||||
app.on('before-quit', function () {
|
||||
mainWindow.webContents.send('application:quitting', {
|
||||
updating: updating
|
||||
});
|
||||
if (!updating) {
|
||||
mainWindow.webContents.send('application:quitting');
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.webContents.on('new-window', function (e) {
|
||||
|
@ -72,6 +72,18 @@ app.on('ready', function () {
|
|||
mainWindow.show();
|
||||
mainWindow.focus();
|
||||
|
||||
if (openURL) {
|
||||
mainWindow.webContents.send('application:open-url', {
|
||||
url: openURL
|
||||
});
|
||||
}
|
||||
app.on('open-url', function (event, url) {
|
||||
event.preventDefault();
|
||||
mainWindow.webContents.send('application:open-url', {
|
||||
url: url
|
||||
});
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
autoUpdater.setFeedUrl('https://updates.kitematic.com/releases/latest?version=' + app.getVersion() + '&beta=' + !!settingsjson.beta);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ var ContainerDetailsHeader = React.createClass({
|
|||
if (!this.props.container) {
|
||||
return false;
|
||||
}
|
||||
if (this.props.container.State.Running && !this.props.container.State.Paused && !this.props.container.State.Restarting) {
|
||||
if (this.props.container.State.Running && !this.props.container.State.Paused && !this.props.container.State.ExitCode && !this.props.container.State.Restarting) {
|
||||
state = <span className="status running">RUNNING</span>;
|
||||
} else if (this.props.container.State.Restarting) {
|
||||
state = <span className="status restarting">RESTARTING</span>;
|
|
@ -2,15 +2,14 @@ var _ = require('underscore');
|
|||
var $ = require('jquery');
|
||||
var React = require('react');
|
||||
var exec = require('exec');
|
||||
var metrics = require('./Metrics');
|
||||
var ContainerStore = require('./ContainerStore');
|
||||
var ContainerUtil = require('./ContainerUtil');
|
||||
var machine = require('./DockerMachine');
|
||||
var RetinaImage = require('react-retina-image');
|
||||
var webPorts = require('./Util').webPorts;
|
||||
var shell = require('shell');
|
||||
var resources = require('./Resources');
|
||||
var classNames = require('classNames');
|
||||
var metrics = require('../utils/MetricsUtil');
|
||||
var ContainerStore = require('../stores/ContainerStore');
|
||||
var ContainerUtil = require('../utils/ContainerUtil');
|
||||
var machine = require('../utils/DockerMachineUtil');
|
||||
var RetinaImage = require('react-retina-image');
|
||||
var webPorts = require('../utils/Util').webPorts;
|
||||
var classNames = require('classnames');
|
||||
|
||||
var ContainerDetailsSubheader = React.createClass({
|
||||
contextTypes: {
|
||||
|
@ -55,6 +54,18 @@ var ContainerDetailsSubheader = React.createClass({
|
|||
}
|
||||
return (this.props.container.State.Downloading || this.props.container.State.Restarting);
|
||||
},
|
||||
disableStop: function () {
|
||||
if (!this.props.container) {
|
||||
return false;
|
||||
}
|
||||
return (this.props.container.State.Downloading || this.props.container.State.ExitCode);
|
||||
},
|
||||
disableStart: function () {
|
||||
if (!this.props.container) {
|
||||
return false;
|
||||
}
|
||||
return (this.props.container.State.Downloading || this.props.container.State.Running);
|
||||
},
|
||||
disableTerminal: function () {
|
||||
if (!this.props.container) {
|
||||
return false;
|
||||
|
@ -102,6 +113,20 @@ var ContainerDetailsSubheader = React.createClass({
|
|||
});
|
||||
}
|
||||
},
|
||||
handleStop: function () {
|
||||
if (!this.disableStop()) {
|
||||
metrics.track('Stopped Container');
|
||||
ContainerStore.stop(this.props.container.Name, function () {
|
||||
});
|
||||
}
|
||||
},
|
||||
handleStart: function () {
|
||||
if (!this.disableStart()) {
|
||||
metrics.track('Started Container');
|
||||
ContainerStore.start(this.props.container.Name, function () {
|
||||
});
|
||||
}
|
||||
},
|
||||
handleTerminal: function () {
|
||||
if (!this.disableTerminal()) {
|
||||
metrics.track('Terminaled Into Container');
|
||||
|
@ -132,6 +157,22 @@ var ContainerDetailsSubheader = React.createClass({
|
|||
var $action = $(this.getDOMNode()).find('.action .restart');
|
||||
$action.css("visibility", "hidden");
|
||||
},
|
||||
handleItemMouseEnterStop: function () {
|
||||
var $action = $(this.getDOMNode()).find('.action .stop');
|
||||
$action.css("visibility", "visible");
|
||||
},
|
||||
handleItemMouseLeaveStop: function () {
|
||||
var $action = $(this.getDOMNode()).find('.action .stop');
|
||||
$action.css("visibility", "hidden");
|
||||
},
|
||||
handleItemMouseEnterStart: function () {
|
||||
var $action = $(this.getDOMNode()).find('.action .start');
|
||||
$action.css("visibility", "visible");
|
||||
},
|
||||
handleItemMouseLeaveStart: function () {
|
||||
var $action = $(this.getDOMNode()).find('.action .start');
|
||||
$action.css("visibility", "hidden");
|
||||
},
|
||||
handleItemMouseEnterTerminal: function () {
|
||||
var $action = $(this.getDOMNode()).find('.action .terminal');
|
||||
$action.css("visibility", "visible");
|
||||
|
@ -149,6 +190,14 @@ var ContainerDetailsSubheader = React.createClass({
|
|||
action: true,
|
||||
disabled: this.disableRestart()
|
||||
});
|
||||
var stopActionClass = classNames({
|
||||
action: true,
|
||||
disabled: this.disableStop()
|
||||
});
|
||||
var startActionClass = classNames({
|
||||
action: true,
|
||||
disabled: this.disableStart()
|
||||
});
|
||||
var terminalActionClass = classNames({
|
||||
action: true,
|
||||
disabled: this.disableTerminal()
|
||||
|
@ -168,6 +217,22 @@ var ContainerDetailsSubheader = React.createClass({
|
|||
'active': this.state.currentRoute && (this.state.currentRoute.indexOf('containerSettings') >= 0),
|
||||
disabled: this.disableTab()
|
||||
});
|
||||
var startStopToggle;
|
||||
if (this.disableStop()) {
|
||||
startStopToggle = (
|
||||
<div className={startActionClass} onMouseEnter={this.handleItemMouseEnterStart} onMouseLeave={this.handleItemMouseLeaveStart}>
|
||||
<div className="action-icon" onClick={this.handleStart}><RetinaImage src="button-start.png" /></div>
|
||||
<span className="btn-label start">Start</span>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
startStopToggle = (
|
||||
<div className={stopActionClass} onMouseEnter={this.handleItemMouseEnterStop} onMouseLeave={this.handleItemMouseLeaveStop}>
|
||||
<div className="action-icon" onClick={this.handleStop}><RetinaImage src="button-stop.png" /></div>
|
||||
<span className="btn-label stop">Stop</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="details-subheader">
|
||||
<div className="details-header-actions">
|
||||
|
@ -179,6 +244,7 @@ var ContainerDetailsSubheader = React.createClass({
|
|||
<div className="action-icon" onClick={this.handleRestart}><RetinaImage src="button-restart.png"/></div>
|
||||
<span className="btn-label restart">Restart</span>
|
||||
</div>
|
||||
{{startStopToggle}}
|
||||
<div className={terminalActionClass} onMouseEnter={this.handleItemMouseEnterTerminal} onMouseLeave={this.handleItemMouseLeaveTerminal}>
|
||||
<div className="action-icon" onClick={this.handleTerminal}><RetinaImage src="button-terminal.png"/></div>
|
||||
<span className="btn-label terminal">Terminal</span>
|
|
@ -1,14 +1,19 @@
|
|||
var _ = require('underscore');
|
||||
var $ = require('jquery');
|
||||
var React = require('react/addons');
|
||||
var ContainerStore = require('./ContainerStore');
|
||||
var ContainerStore = require('../stores/ContainerStore');
|
||||
var Radial = require('./Radial.react');
|
||||
var ContainerHomePreview = require('./ContainerHomePreview.react');
|
||||
var ContainerHomeLogs = require('./ContainerHomeLogs.react');
|
||||
var ContainerHomeFolders = require('./ContainerHomeFolders.react');
|
||||
<<<<<<< HEAD:src/ContainerHome.react.js
|
||||
var ContainerUtil = require('./ContainerUtil');
|
||||
var util = require('./Util');
|
||||
var shell = require('shell');
|
||||
=======
|
||||
var ContainerUtil = require('../utils/ContainerUtil');
|
||||
var util = require('../utils/Util');
|
||||
>>>>>>> master:src/components/ContainerHome.react.js
|
||||
|
||||
var resizeWindow = function () {
|
||||
$('.left .wrapper').height(window.innerHeight - 240);
|
||||
|
@ -83,13 +88,23 @@ var ContainerHome = React.createClass({
|
|||
</div>
|
||||
);
|
||||
} else if (this.props.container && this.props.container.State.Downloading) {
|
||||
if (this.state.progress) {
|
||||
body = (
|
||||
<div className="details-progress">
|
||||
<h2>Downloading Image</h2>
|
||||
<Radial progress={Math.min(Math.round(this.state.progress * 100), 99)} thick={true} gray={true}/>
|
||||
</div>
|
||||
);
|
||||
if (this.state.progress !== undefined) {
|
||||
if (this.state.progress > 0) {
|
||||
body = (
|
||||
<div className="details-progress">
|
||||
<h2>Downloading Image</h2>
|
||||
<Radial progress={Math.min(Math.round(this.state.progress * 100), 99)} thick={true} gray={true}/>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
body = (
|
||||
<div className="details-progress">
|
||||
<h2>Downloading Image</h2>
|
||||
<Radial spin="true" progress="90" thick={true} transparent={true}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
} else if (this.state.blocked) {
|
||||
body = (
|
||||
<div className="details-progress">
|
|
@ -3,9 +3,10 @@ var React = require('react/addons');
|
|||
var RetinaImage = require('react-retina-image');
|
||||
var path = require('path');
|
||||
var shell = require('shell');
|
||||
var util = require('./Util');
|
||||
var metrics = require('./Metrics');
|
||||
var ContainerStore = require('./ContainerStore');
|
||||
var util = require('../utils/Util');
|
||||
var metrics = require('../utils/MetricsUtil');
|
||||
var ContainerStore = require('../stores/ContainerStore');
|
||||
var dialog = require('remote').require('dialog');
|
||||
|
||||
var ContainerHomeFolder = React.createClass({
|
||||
contextTypes: {
|
||||
|
@ -17,28 +18,35 @@ var ContainerHomeFolder = React.createClass({
|
|||
});
|
||||
|
||||
if (hostVolume.indexOf(process.env.HOME) === -1) {
|
||||
var volumes = _.clone(this.props.container.Volumes);
|
||||
var newHostVolume = path.join(util.home(), 'Kitematic', this.props.container.Name, containerVolume);
|
||||
volumes[containerVolume] = newHostVolume;
|
||||
var binds = _.pairs(volumes).map(function (pair) {
|
||||
if(util.isWindows()) {
|
||||
var home = util.home();
|
||||
home = home.charAt(0).toLowerCase() + home.slice(1);
|
||||
home = '/' + home.replace(':', '').replace(/\\/g, '/');
|
||||
var fullPath = path.join(home, 'Kitematic', pair[1], pair[0]);
|
||||
fullPath = fullPath.replace(/\\/g, '/');
|
||||
return fullPath + ':' + pair[0];
|
||||
dialog.showMessageBox({
|
||||
message: 'Enable all volumes to edit files via Finder? This may not work with all database containers.',
|
||||
buttons: ['Enable Volumes', 'Cancel']
|
||||
}, (index) => {
|
||||
if (index === 0) {
|
||||
var volumes = _.clone(this.props.container.Volumes);
|
||||
var newHostVolume = path.join(util.home(), 'Kitematic', this.props.container.Name, containerVolume);
|
||||
volumes[containerVolume] = newHostVolume;
|
||||
var binds = _.pairs(volumes).map(function (pair) {
|
||||
if(util.isWindows()) {
|
||||
var home = util.home();
|
||||
home = home.charAt(0).toLowerCase() + home.slice(1);
|
||||
home = '/' + home.replace(':', '').replace(/\\/g, '/');
|
||||
var fullPath = path.join(home, 'Kitematic', pair[1], pair[0]);
|
||||
fullPath = fullPath.replace(/\\/g, '/');
|
||||
return fullPath + ':' + pair[0];
|
||||
}
|
||||
return pair[1] + ':' + pair[0];
|
||||
});
|
||||
ContainerStore.updateContainer(this.props.container.Name, {
|
||||
Binds: binds
|
||||
}, (err) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return;
|
||||
}
|
||||
shell.showItemInFolder(newHostVolume);
|
||||
});
|
||||
}
|
||||
return pair[1] + ':' + pair[0];
|
||||
});
|
||||
ContainerStore.updateContainer(this.props.container.Name, {
|
||||
Binds: binds
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return;
|
||||
}
|
||||
shell.showItemInFolder(newHostVolume);
|
||||
});
|
||||
} else {
|
||||
shell.showItemInFolder(hostVolume);
|
|
@ -1,8 +1,8 @@
|
|||
var $ = require('jquery');
|
||||
var React = require('react/addons');
|
||||
var LogStore = require('./LogStore');
|
||||
var LogStore = require('../stores/LogStore');
|
||||
var Router = require('react-router');
|
||||
var metrics = require('./Metrics');
|
||||
var metrics = require('../utils/MetricsUtil');
|
||||
|
||||
var _prevBottom = 0;
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
var _ = require('underscore');
|
||||
var React = require('react/addons');
|
||||
var exec = require('exec');
|
||||
var ContainerStore = require('./ContainerStore');
|
||||
var ContainerUtil = require('./ContainerUtil');
|
||||
var ContainerStore = require('../stores/ContainerStore');
|
||||
var ContainerUtil = require('../utils/ContainerUtil');
|
||||
var request = require('request');
|
||||
var metrics = require('./Metrics');
|
||||
var webPorts = require('./Util').webPorts;
|
||||
var util = require('./Util');
|
||||
var util = require('../utils/Util');
|
||||
var metrics = require('../utils/MetricsUtil');
|
||||
var webPorts = require('../utils/Util').webPorts;
|
||||
|
||||
var ContainerHomePreview = React.createClass({
|
||||
contextTypes: {
|
|
@ -19,15 +19,9 @@ var ContainerList = React.createClass({
|
|||
<ContainerListItem key={containerId} container={container} start={self._start} />
|
||||
);
|
||||
});
|
||||
var newItem;
|
||||
if (!this.props.downloading) {
|
||||
newItem = <ContainerListNewItem key={'newcontainer'} containers={this.props.containers} />;
|
||||
} else {
|
||||
newItem = '';
|
||||
}
|
||||
return (
|
||||
<ul>
|
||||
{newItem}
|
||||
<ContainerListNewItem key={'newcontainer'} containers={this.props.containers}/>
|
||||
{containers}
|
||||
</ul>
|
||||
);
|
|
@ -3,8 +3,8 @@ 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 metrics = require('../utils/MetricsUtil');
|
||||
var ContainerStore = require('../stores/ContainerStore');
|
||||
var OverlayTrigger = require('react-bootstrap').OverlayTrigger;
|
||||
var Tooltip = require('react-bootstrap').Tooltip;
|
||||
|
||||
|
@ -31,7 +31,7 @@ var ContainerListItem = React.createClass({
|
|||
});
|
||||
ContainerStore.remove(this.props.container.Name, () => {
|
||||
var containers = ContainerStore.sorted();
|
||||
if (containers.length === 1) {
|
||||
if (containers.length === 0) {
|
||||
$(document.body).find('.new-container-item').parent().fadeIn();
|
||||
}
|
||||
});
|
|
@ -1,10 +1,13 @@
|
|||
var $ = require('jquery');
|
||||
var React = require('react/addons');
|
||||
var Router = require('react-router');
|
||||
var ContainerStore = require('./ContainerStore');
|
||||
var metrics = require('./Metrics');
|
||||
var ContainerStore = require('../stores/ContainerStore');
|
||||
var metrics = require('../utils/MetricsUtil');
|
||||
|
||||
var ContainerListNewItem = React.createClass({
|
||||
contextTypes: {
|
||||
router: React.PropTypes.func
|
||||
},
|
||||
handleItemMouseEnter: function () {
|
||||
var $action = $(this.getDOMNode()).find('.action');
|
||||
$action.show();
|
||||
|
@ -20,10 +23,10 @@ var ContainerListNewItem = React.createClass({
|
|||
type: 'new'
|
||||
});
|
||||
var containers = ContainerStore.sorted();
|
||||
$(self.getDOMNode()).fadeOut(300, function () {
|
||||
$(self.getDOMNode()).fadeOut(300, () => {
|
||||
if (containers.length > 0) {
|
||||
var name = containers[0].Name;
|
||||
self.transitionTo('containerHome', {name: name});
|
||||
this.context.router.transitionTo('containerHome', {name: name});
|
||||
}
|
||||
});
|
||||
},
|
|
@ -1,6 +1,6 @@
|
|||
var $ = require('jquery');
|
||||
var React = require('react/addons');
|
||||
var LogStore = require('./LogStore');
|
||||
var LogStore = require('../stores/LogStore');
|
||||
|
||||
var _prevBottom = 0;
|
||||
|
|
@ -1,15 +1,14 @@
|
|||
var _ = require('underscore');
|
||||
var $ = require('jquery');
|
||||
var React = require('react/addons');
|
||||
var Router = require('react-router');
|
||||
var path = require('path');
|
||||
var remote = require('remote');
|
||||
var rimraf = require('rimraf');
|
||||
var fs = require('fs');
|
||||
var metrics = require('./Metrics');
|
||||
var metrics = require('../utils/MetricsUtil');
|
||||
var dialog = remote.require('dialog');
|
||||
var ContainerStore = require('./ContainerStore');
|
||||
var ContainerUtil = require('./ContainerUtil');
|
||||
var ContainerStore = require('../stores/ContainerStore');
|
||||
var ContainerUtil = require('../utils/ContainerUtil');
|
||||
|
||||
var containerNameSlugify = function (text) {
|
||||
text = text.replace(/^\s+|\s+$/g, ''); // Trim
|
|
@ -1,10 +1,10 @@
|
|||
var _ = require('underscore');
|
||||
var React = require('react/addons');
|
||||
var ContainerStore = require('./ContainerStore');
|
||||
var ContainerUtil = require('./ContainerUtil');
|
||||
var metrics = require('./Metrics');
|
||||
var webPorts = require('./Util').webPorts;
|
||||
var shell = require('shell');
|
||||
var ContainerStore = require('../stores/ContainerStore');
|
||||
var ContainerUtil = require('../utils/ContainerUtil');
|
||||
var metrics = require('../utils/MetricsUtil');
|
||||
var webPorts = require('../utils/Util').webPorts;
|
||||
|
||||
var ContainerSettingsPorts = React.createClass({
|
||||
contextTypes: {
|
|
@ -2,9 +2,9 @@ var _ = require('underscore');
|
|||
var React = require('react/addons');
|
||||
var remote = require('remote');
|
||||
var dialog = remote.require('dialog');
|
||||
var metrics = require('./Metrics');
|
||||
var ContainerStore = require('./ContainerStore');
|
||||
var util = require('./Util');
|
||||
var metrics = require('../utils/MetricsUtil');
|
||||
var ContainerStore = require('../stores/ContainerStore');
|
||||
var util = require('../utils/Util');
|
||||
|
||||
var ContainerSettingsVolumes = React.createClass({
|
||||
handleChooseVolumeClick: function (dockerVol) {
|
|
@ -1,18 +1,16 @@
|
|||
var $ = require('jquery');
|
||||
var React = require('react/addons');
|
||||
var Router = require('react-router');
|
||||
var ContainerStore = require('./ContainerStore');
|
||||
var ContainerStore = require('../stores/ContainerStore');
|
||||
var ContainerList = require('./ContainerList.react');
|
||||
var Header = require('./Header.react');
|
||||
var ipc = require('ipc');
|
||||
var remote = require('remote');
|
||||
var metrics = require('./Metrics');
|
||||
var metrics = require('../utils/MetricsUtil');
|
||||
var autoUpdater = remote.require('auto-updater');
|
||||
var RetinaImage = require('react-retina-image');
|
||||
var machine = require('./DockerMachine');
|
||||
var OverlayTrigger = require('react-bootstrap').OverlayTrigger;
|
||||
var Tooltip = require('react-bootstrap').Tooltip;
|
||||
var shell = require('shell');
|
||||
var machine = require('../utils/DockerMachineUtil');
|
||||
|
||||
var Containers = React.createClass({
|
||||
contextTypes: {
|
||||
|
@ -35,10 +33,6 @@ var Containers = React.createClass({
|
|||
ContainerStore.on(ContainerStore.SERVER_CONTAINER_EVENT, this.update);
|
||||
ContainerStore.on(ContainerStore.CLIENT_CONTAINER_EVENT, this.updateFromClient);
|
||||
|
||||
if (this.state.sorted.length) {
|
||||
this.context.router.transitionTo('containerHome', {name: this.state.sorted[0].Name});
|
||||
}
|
||||
|
||||
ipc.on('application:update-available', () => {
|
||||
this.setState({
|
||||
updateAvailable: true
|
||||
|
@ -50,36 +44,33 @@ var Containers = React.createClass({
|
|||
ContainerStore.removeListener(ContainerStore.SERVER_CONTAINER_EVENT, this.update);
|
||||
ContainerStore.removeListener(ContainerStore.CLIENT_CONTAINER_EVENT, this.updateFromClient);
|
||||
},
|
||||
onDestroy: function () {
|
||||
if (this.state.sorted.length) {
|
||||
this.context.router.transitionTo('containerHome', {name: this.state.sorted[0].Name});
|
||||
} else {
|
||||
this.context.router.transitionTo('containers');
|
||||
}
|
||||
},
|
||||
updateError: function (err) {
|
||||
this.setState({
|
||||
error: err
|
||||
});
|
||||
},
|
||||
update: function (name, status) {
|
||||
var sorted = ContainerStore.sorted();
|
||||
this.setState({
|
||||
containers: ContainerStore.containers(),
|
||||
sorted: ContainerStore.sorted(),
|
||||
sorted: sorted,
|
||||
pending: ContainerStore.pending(),
|
||||
downloading: ContainerStore.downloading()
|
||||
});
|
||||
if (status === 'destroy') {
|
||||
this.onDestroy();
|
||||
if (sorted.length) {
|
||||
this.context.router.transitionTo('containerHome', {name: sorted[0].Name});
|
||||
} else {
|
||||
this.context.router.transitionTo('containers');
|
||||
}
|
||||
}
|
||||
},
|
||||
updateFromClient: function (name, status) {
|
||||
this.setState({
|
||||
containers: ContainerStore.containers(),
|
||||
sorted: ContainerStore.sorted(),
|
||||
downloading: ContainerStore.downloading()
|
||||
});
|
||||
this.update(name, status);
|
||||
if (status === 'create') {
|
||||
this.context.router.transitionTo('containerHome', {name: name});
|
||||
} else if (status === 'pending' && ContainerStore.pending()) {
|
||||
this.context.router.transitionTo('pull');
|
||||
} else if (status === 'destroy') {
|
||||
this.onDestroy();
|
||||
}
|
||||
|
@ -164,17 +155,6 @@ var Containers = React.createClass({
|
|||
);
|
||||
}
|
||||
|
||||
var button;
|
||||
if (this.state.downloading) {
|
||||
button = (
|
||||
<OverlayTrigger placement="bottom" overlay={<Tooltip>Only one Docker image can be downloaded at a time.</Tooltip>}>
|
||||
<a disabled={true} className="btn-new icon icon-add-3"></a>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
} else {
|
||||
button = <a className="btn-new icon icon-add-3" onClick={this.handleNewContainer}></a>;
|
||||
}
|
||||
|
||||
var container = this.context.router.getCurrentParams().name ? this.state.containers[this.context.router.getCurrentParams().name] : {};
|
||||
return (
|
||||
<div className="containers">
|
||||
|
@ -184,7 +164,7 @@ var Containers = React.createClass({
|
|||
<section className={sidebarHeaderClass}>
|
||||
<h4>Containers</h4>
|
||||
<div className="create">
|
||||
{button}
|
||||
<a className="btn-new icon icon-add-3" onClick={this.handleNewContainer}></a>
|
||||
</div>
|
||||
</section>
|
||||
<section className="sidebar-containers" onScroll={this.handleScroll}>
|
||||
|
@ -199,7 +179,7 @@ var Containers = React.createClass({
|
|||
<div className="sidebar-buttons-padding"></div>
|
||||
</section>
|
||||
</div>
|
||||
<Router.RouteHandler container={container} error={this.state.error}/>
|
||||
<Router.RouteHandler pending={this.state.pending} container={container} error={this.state.error}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
|
@ -1,11 +1,11 @@
|
|||
var $ = require('jquery');
|
||||
var React = require('react/addons');
|
||||
var RetinaImage = require('react-retina-image');
|
||||
var ContainerStore = require('./ContainerStore');
|
||||
var metrics = require('./Metrics');
|
||||
var ContainerStore = require('../stores/ContainerStore');
|
||||
var metrics = require('../utils/MetricsUtil');
|
||||
var OverlayTrigger = require('react-bootstrap').OverlayTrigger;
|
||||
var Tooltip = require('react-bootstrap').Tooltip;
|
||||
var util = require('./Util');
|
||||
var util = require('../utils/Util');
|
||||
|
||||
var ImageCard = React.createClass({
|
||||
getInitialState: function () {
|
||||
|
@ -23,7 +23,9 @@ var ImageCard = React.createClass({
|
|||
metrics.track('Selected Image Tag');
|
||||
},
|
||||
handleClick: function (name) {
|
||||
metrics.track('Created Container');
|
||||
metrics.track('Created Container', {
|
||||
from: 'search'
|
||||
});
|
||||
ContainerStore.create(name, this.state.chosenTag, function () {
|
||||
$(document.body).find('.new-container-item').parent().fadeOut();
|
||||
}.bind(this));
|
||||
|
@ -75,7 +77,7 @@ var ImageCard = React.createClass({
|
|||
name = (
|
||||
<div>
|
||||
<div className="namespace official">{namespace}</div>
|
||||
<OverlayTrigger placement="bottom" overlay={<Tooltip>View on DockerHub</Tooltip>}>
|
||||
<OverlayTrigger placement="bottom" overlay={<Tooltip>View on Docker Hub</Tooltip>}>
|
||||
<span className="repo" onClick={this.handleRepoClick}>{repo}</span>
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
|
@ -84,7 +86,7 @@ var ImageCard = React.createClass({
|
|||
name = (
|
||||
<div>
|
||||
<div className="namespace">{namespace}</div>
|
||||
<OverlayTrigger placement="bottom" overlay={<Tooltip>View on DockerHub</Tooltip>}>
|
||||
<OverlayTrigger placement="bottom" overlay={<Tooltip>View on Docker Hub</Tooltip>}>
|
||||
<span className="repo" onClick={this.handleRepoClick}>{repo}</span>
|
||||
</OverlayTrigger>
|
||||
</div>
|
|
@ -5,7 +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 metrics = require('../utils/MetricsUtil');
|
||||
var classNames = require('classnames');
|
||||
|
||||
var _recommended = [];
|
|
@ -0,0 +1,47 @@
|
|||
var React = require('react/addons');
|
||||
var Router = require('react-router');
|
||||
var shell = require('shell');
|
||||
var ContainerStore = require('../stores/ContainerStore');
|
||||
var metrics = require('../utils/MetricsUtil');
|
||||
|
||||
module.exports = React.createClass({
|
||||
mixins: [Router.Navigation],
|
||||
handleOpenClick: function () {
|
||||
var repo = this.props.pending.repository;
|
||||
if (repo.indexOf('/') === -1) {
|
||||
shell.openExternal(`https://registry.hub.docker.com/_/${this.props.pending.repository}`);
|
||||
} else {
|
||||
shell.openExternal(`https://registry.hub.docker.com/u/${this.props.pending.repository}`);
|
||||
}
|
||||
},
|
||||
handleCancelClick: function () {
|
||||
metrics.track('Canceled Click-To-Pull');
|
||||
ContainerStore.clearPending();
|
||||
this.context.router.transitionTo('new');
|
||||
},
|
||||
handleConfirmClick: function () {
|
||||
metrics.track('Created Container', {
|
||||
from: 'click-to-pull'
|
||||
});
|
||||
ContainerStore.clearPending();
|
||||
ContainerStore.create(this.props.pending.repository, this.props.pending.tag, function () {});
|
||||
},
|
||||
render: function () {
|
||||
if (!this.props.pending) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
<div className="details">
|
||||
<div className="new-container-pull">
|
||||
<div className="content">
|
||||
<h1>You're about to download and run <a onClick={this.handleOpenClick}>{this.props.pending.repository}:{this.props.pending.tag}</a>.</h1>
|
||||
<h1>Please confirm to create the container.</h1>
|
||||
<div className="buttons">
|
||||
<a className="btn btn-action" onClick={this.handleCancelClick}>Cancel</a> <a onClick={this.handleConfirmClick} className="btn btn-action">Confirm</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,168 @@
|
|||
var _ = require('underscore');
|
||||
var $ = require('jquery');
|
||||
var React = require('react/addons');
|
||||
var RetinaImage = require('react-retina-image');
|
||||
var Radial = require('./Radial.react');
|
||||
var ImageCard = require('./ImageCard.react');
|
||||
var Promise = require('bluebird');
|
||||
var metrics = require('../utils/MetricsUtil');
|
||||
|
||||
var _recommended = [];
|
||||
var _searchPromise = null;
|
||||
|
||||
module.exports = React.createClass({
|
||||
getInitialState: function () {
|
||||
return {
|
||||
query: '',
|
||||
loading: false,
|
||||
results: _recommended
|
||||
};
|
||||
},
|
||||
componentDidMount: function () {
|
||||
this.refs.searchInput.getDOMNode().focus();
|
||||
this.recommended();
|
||||
},
|
||||
componentWillUnmount: function () {
|
||||
if (_searchPromise) {
|
||||
_searchPromise.cancel();
|
||||
}
|
||||
},
|
||||
search: function (query) {
|
||||
if (_searchPromise) {
|
||||
_searchPromise.cancel();
|
||||
_searchPromise = null;
|
||||
}
|
||||
|
||||
if (!query.length) {
|
||||
this.setState({
|
||||
query: query,
|
||||
results: _recommended,
|
||||
loading: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
query: query,
|
||||
loading: true
|
||||
});
|
||||
|
||||
_searchPromise = Promise.delay(200).cancellable().then(() => Promise.resolve($.get('https://registry.hub.docker.com/v1/search?q=' + query))).then(data => {
|
||||
metrics.track('Searched for Images');
|
||||
this.setState({
|
||||
results: data.results,
|
||||
query: query,
|
||||
loading: false
|
||||
});
|
||||
_searchPromise = null;
|
||||
}).catch(Promise.CancellationError, () => {
|
||||
});
|
||||
},
|
||||
recommended: function () {
|
||||
if (_recommended.length) {
|
||||
return;
|
||||
}
|
||||
Promise.resolve($.ajax({
|
||||
url: 'https://kitematic.com/recommended.json',
|
||||
cache: false,
|
||||
dataType: 'json',
|
||||
})).then(res => res.repos).map(repo => {
|
||||
var query = repo.repo;
|
||||
var vals = query.split('/');
|
||||
if (vals.length === 1) {
|
||||
query = 'library/' + vals[0];
|
||||
}
|
||||
return $.get('https://registry.hub.docker.com/v1/repositories_info/' + query).then(data => {
|
||||
var res = _.extend(data, repo);
|
||||
res.description = data.short_description;
|
||||
res.is_official = data.namespace === 'library';
|
||||
res.name = data.repo;
|
||||
res.star_count = data.stars;
|
||||
return res;
|
||||
});
|
||||
}).then(results => {
|
||||
_recommended = results.filter(r => !!r);
|
||||
if (!this.state.query.length && this.isMounted()) {
|
||||
this.setState({
|
||||
results: _recommended
|
||||
});
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
},
|
||||
handleChange: function (e) {
|
||||
var query = e.target.value;
|
||||
if (query === this.state.query) {
|
||||
return;
|
||||
}
|
||||
this.search(query);
|
||||
},
|
||||
render: function () {
|
||||
var title = this.state.query ? 'Results' : 'Recommended';
|
||||
var data = this.state.results;
|
||||
var results;
|
||||
if (data.length) {
|
||||
var items = data.map(function (image) {
|
||||
return (
|
||||
<ImageCard key={image.name} image={image} />
|
||||
);
|
||||
});
|
||||
|
||||
results = (
|
||||
<div className="result-grid">
|
||||
{items}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
if (this.state.results.length === 0 && this.state.query === '') {
|
||||
results = (
|
||||
<div className="no-results">
|
||||
<div className="loader">
|
||||
<h2>Loading Images</h2>
|
||||
<Radial spin="true" progress={90} thick={true} transparent={true} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
results = (
|
||||
<div className="no-results">
|
||||
<h1>Cannot find a matching image.</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
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
|
||||
});
|
||||
return (
|
||||
<div className="details">
|
||||
<div className="new-container">
|
||||
<div className="new-container-header">
|
||||
<div className="text">
|
||||
Select a Docker image to create a new container.
|
||||
</div>
|
||||
<div className="search">
|
||||
<div className="search-bar">
|
||||
<input type="search" ref="searchInput" className="form-control" placeholder="Search Docker Hub for an image" onChange={this.handleChange}/>
|
||||
<div className={magnifierClasses}></div>
|
||||
<RetinaImage className={loadingClasses} src="loading.png"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="results">
|
||||
<h4>{title}</h4>
|
||||
{results}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -1,5 +1,5 @@
|
|||
var React = require('react/addons');
|
||||
var metrics = require('./Metrics');
|
||||
var metrics = require('../utils/MetricsUtil');
|
||||
var Router = require('react-router');
|
||||
|
||||
var Preferences = React.createClass({
|
||||
|
@ -30,7 +30,7 @@ var Preferences = React.createClass({
|
|||
metricsEnabled: checked
|
||||
});
|
||||
metrics.setEnabled(checked);
|
||||
metrics.track('Toggled Metrics', {
|
||||
metrics.track('Toggled util/MetricsUtil', {
|
||||
enabled: checked
|
||||
});
|
||||
},
|
|
@ -1,11 +1,11 @@
|
|||
var React = require('react/addons');
|
||||
var Router = require('react-router');
|
||||
var Radial = require('./Radial.react.js');
|
||||
var SetupStore = require('./SetupStore');
|
||||
var SetupStore = require('../stores/SetupStore');
|
||||
var RetinaImage = require('react-retina-image');
|
||||
var Header = require('./Header.react');
|
||||
var Util = require('./Util');
|
||||
var metrics = require('./Metrics');
|
||||
var Util = require('../utils/Util');
|
||||
var metrics = require('../utils/MetricsUtil');
|
||||
|
||||
var Setup = React.createClass({
|
||||
mixins: [ Router.Navigation ],
|
||||
|
@ -126,7 +126,10 @@ var Setup = React.createClass({
|
|||
<h1>We're Sorry!</h1>
|
||||
<p>There seems to have been an unexpected error with Kitematic:</p>
|
||||
<p className="error">{this.state.error.message || this.state.error}</p>
|
||||
<p><button className="btn btn-action" onClick={this.handleErrorRetry}>Retry Setup</button> <button className="btn btn-action" onClick={this.handleErrorRemoveRetry}>Delete VM and Retry Setup</button></p>
|
||||
<p className="setup-actions">
|
||||
<button className="btn btn-action" onClick={this.handleErrorRetry}>Retry Setup</button>
|
||||
<button className="btn btn-action" onClick={this.handleErrorRemoveRetry}>Delete VM and Retry Setup</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,11 +1,11 @@
|
|||
var remote = require('remote');
|
||||
var app = remote.require('app');
|
||||
var router = require('./Router');
|
||||
var util = require('./Util');
|
||||
var metrics = require('./Metrics');
|
||||
var machine = require('./DockerMachine');
|
||||
var docker = require('./Docker');
|
||||
var shell = require('shell');
|
||||
var router = require('./router');
|
||||
var util = require('./utils/Util');
|
||||
var metrics = require('./utils/MetricsUtil');
|
||||
var machine = require('./utils/DockerMachineUtil');
|
||||
var docker = require('./utils/DockerUtil');
|
||||
|
||||
// main.js
|
||||
var MenuTemplate = function () {
|
|
@ -1,5 +1,5 @@
|
|||
var Router = require('react-router');
|
||||
var routes = require('./Routes');
|
||||
var routes = require('./routes');
|
||||
|
||||
var router = Router.create({
|
||||
routes: routes
|
|
@ -0,0 +1,52 @@
|
|||
var React = require('react/addons');
|
||||
var Setup = require('./components/Setup.react');
|
||||
var Containers = require('./components/Containers.react');
|
||||
var ContainerDetails = require('./components/ContainerDetails.react');
|
||||
var ContainerHome = require('./components/ContainerHome.react');
|
||||
var ContainerLogs = require('./components/ContainerLogs.react');
|
||||
var ContainerSettings = require('./components/ContainerSettings.react');
|
||||
var ContainerSettingsGeneral = require('./components/ContainerSettingsGeneral.react');
|
||||
var ContainerSettingsPorts = require('./components/ContainerSettingsPorts.react');
|
||||
var ContainerSettingsVolumes = require('./components/ContainerSettingsVolumes.react');
|
||||
var Preferences = require('./components/Preferences.react');
|
||||
var NewContainerSearch = require('./components/NewContainerSearch.react');
|
||||
var NewContainerPull = require('./components/NewContainerPull.react');
|
||||
var Router = require('react-router');
|
||||
|
||||
var Route = Router.Route;
|
||||
var DefaultRoute = Router.DefaultRoute;
|
||||
var RouteHandler = Router.RouteHandler;
|
||||
var Redirect = Router.Redirect;
|
||||
|
||||
var App = React.createClass({
|
||||
render: function () {
|
||||
return (
|
||||
<RouteHandler/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var routes = (
|
||||
<Route name="app" path="/" handler={App}>
|
||||
<Route name="containers" handler={Containers}>
|
||||
<Route name="containerDetails" path="containers/details/:name" handler={ContainerDetails}>
|
||||
<Route name="containerHome" path="containers/details/:name/home" handler={ContainerHome} />
|
||||
<Route name="containerLogs" path="containers/details/:name/logs" handler={ContainerLogs}/>
|
||||
<Route name="containerSettings" path="containers/details/:name/settings" handler={ContainerSettings}>
|
||||
<Route name="containerSettingsGeneral" path="containers/details/:name/settings/general" handler={ContainerSettingsGeneral}/>
|
||||
<Route name="containerSettingsPorts" path="containers/details/:name/settings/ports" handler={ContainerSettingsPorts}/>
|
||||
<Route name="containerSettingsVolumes" path="containers/details/:name/settings/volumes" handler={ContainerSettingsVolumes}/>
|
||||
</Route>
|
||||
</Route>
|
||||
<Route name="new" path="containers/new">
|
||||
<DefaultRoute name="search" handler={NewContainerSearch}/>
|
||||
<Route name="pull" path="containers/new/pull" handler={NewContainerPull}></Route>
|
||||
</Route>
|
||||
<Route name="preferences" path="/preferences" handler={Preferences}/>
|
||||
<Redirect to="new"/>
|
||||
</Route>
|
||||
<DefaultRoute name="setup" handler={Setup}/>
|
||||
</Route>
|
||||
);
|
||||
|
||||
module.exports = routes;
|
|
@ -2,10 +2,10 @@ var _ = require('underscore');
|
|||
var EventEmitter = require('events').EventEmitter;
|
||||
var async = require('async');
|
||||
var assign = require('object-assign');
|
||||
var docker = require('./Docker');
|
||||
var metrics = require('./Metrics');
|
||||
var registry = require('./Registry');
|
||||
var logstore = require('./LogStore');
|
||||
var docker = require('../utils/DockerUtil');
|
||||
var metrics = require('../utils/MetricsUtil');
|
||||
var registry = require('../utils/RegistryUtil');
|
||||
var logstore = require('../stores/LogStore');
|
||||
var bugsnag = require('bugsnag-js');
|
||||
|
||||
var _placeholders = {};
|
||||
|
@ -14,6 +14,7 @@ var _progress = {};
|
|||
var _muted = {};
|
||||
var _blocked = {};
|
||||
var _error = null;
|
||||
var _pending = null;
|
||||
|
||||
var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
||||
CLIENT_CONTAINER_EVENT: 'client_container_event',
|
||||
|
@ -32,7 +33,7 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
|||
return image.Id.slice(0, 12);
|
||||
}));
|
||||
var layersToDownload = layerSizes.filter(function (layerSize) {
|
||||
return !existingIds.has(layerSize.Id);
|
||||
return !existingIds.has(layerSize.Id) && !isNaN(layerSize.size);
|
||||
});
|
||||
|
||||
var totalBytes = layersToDownload.map(function (s) { return s.size; }).reduce(function (pv, sv) { return pv + sv; }, 0);
|
||||
|
@ -54,7 +55,6 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
|||
|
||||
stream.on('data', str => {
|
||||
var data = JSON.parse(str);
|
||||
console.log(data);
|
||||
|
||||
if (data.error) {
|
||||
_error = data.error;
|
||||
|
@ -72,20 +72,25 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
|||
} else if (data.status === 'Downloading') {
|
||||
var current = data.progressDetail.current;
|
||||
var total = data.progressDetail.total;
|
||||
var layerFraction = current / total;
|
||||
layerProgress[data.id] = layerFraction;
|
||||
if (total <= 0) {
|
||||
progressCallback(0);
|
||||
return;
|
||||
} else {
|
||||
layerProgress[data.id] = current / total;
|
||||
}
|
||||
|
||||
var chunks = layersToDownload.map(function (s) {
|
||||
var progress = layerProgress[s.Id] || 0;
|
||||
return progress * s.size;
|
||||
});
|
||||
|
||||
var totalReceived = chunks.reduce(function (pv, sv) {
|
||||
return pv + sv;
|
||||
}, 0);
|
||||
|
||||
var totalProgress = totalReceived / totalBytes;
|
||||
progressCallback(totalProgress);
|
||||
}
|
||||
|
||||
var chunks = layersToDownload.map(function (s) {
|
||||
return layerProgress[s.Id] * s.size;
|
||||
});
|
||||
|
||||
var totalReceived = chunks.reduce(function (pv, sv) {
|
||||
return pv + sv;
|
||||
}, 0);
|
||||
|
||||
var totalProgress = totalReceived / totalBytes;
|
||||
progressCallback(totalProgress);
|
||||
});
|
||||
stream.on('end', function () {
|
||||
callback(_error);
|
||||
|
@ -172,6 +177,7 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
|||
stream.on('data', function () {});
|
||||
stream.on('end', function () {
|
||||
delete _placeholders[container.Name];
|
||||
delete _progress[container.Name];
|
||||
localStorage.setItem('store.placeholders', JSON.stringify(_placeholders));
|
||||
self._createContainer(container.Name, {Image: container.Config.Image}, err => {
|
||||
if (err) {
|
||||
|
@ -201,6 +207,10 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
|||
var data = JSON.parse(json);
|
||||
console.log(data);
|
||||
|
||||
if (data.status === 'pull' || data.status === 'untag' || data.status === 'delete') {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the event is delete, remove the container
|
||||
if (data.status === 'destroy') {
|
||||
var container = _.findWhere(_.values(_containers), {Id: data.id});
|
||||
|
@ -226,7 +236,6 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
|||
}
|
||||
},
|
||||
init: function (callback) {
|
||||
// TODO: Load cached data from db on loading
|
||||
this.fetchAllContainers(err => {
|
||||
if (err) {
|
||||
_error = err;
|
||||
|
@ -298,6 +307,7 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
|||
var containerName = this._generateName(repository);
|
||||
|
||||
_placeholders[containerName] = {
|
||||
Id: require('crypto').randomBytes(32).toString('hex'),
|
||||
Name: containerName,
|
||||
Image: imageName,
|
||||
Config: {
|
||||
|
@ -311,7 +321,6 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
|||
this.emit(this.CLIENT_CONTAINER_EVENT, containerName, 'create');
|
||||
|
||||
_muted[containerName] = true;
|
||||
_progress[containerName] = 0;
|
||||
this._pullImage(repository, tag, err => {
|
||||
if (err) {
|
||||
_error = err;
|
||||
|
@ -394,6 +403,29 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
|||
}
|
||||
});
|
||||
},
|
||||
stop: function (name, callback) {
|
||||
var container = docker.client().getContainer(name);
|
||||
_muted[name] = true;
|
||||
container.stop(err => {
|
||||
if (err && err.statusCode !== 304) {
|
||||
_muted[name] = false;
|
||||
callback(err);
|
||||
} else {
|
||||
_muted[name] = false;
|
||||
this.fetchContainer(name, callback);
|
||||
}
|
||||
});
|
||||
},
|
||||
start: function (name, callback) {
|
||||
var container = docker.client().getContainer(name);
|
||||
container.start(err => {
|
||||
if (err && err.statusCode !== 304) {
|
||||
callback(err);
|
||||
} else {
|
||||
this.fetchContainer(name, callback);
|
||||
}
|
||||
});
|
||||
},
|
||||
remove: function (name, callback) {
|
||||
if (_placeholders[name]) {
|
||||
delete _placeholders[name];
|
||||
|
@ -471,6 +503,20 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
|||
},
|
||||
downloading: function () {
|
||||
return !!_.keys(_placeholders).length;
|
||||
},
|
||||
pending: function () {
|
||||
return _pending;
|
||||
},
|
||||
setPending: function (repository, tag) {
|
||||
_pending = {
|
||||
repository: repository,
|
||||
tag: tag
|
||||
};
|
||||
this.emit(this.CLIENT_CONTAINER_EVENT, null, 'pending');
|
||||
},
|
||||
clearPending: function () {
|
||||
_pending = null;
|
||||
this.emit(this.CLIENT_CONTAINER_EVENT, null, 'pending');
|
||||
}
|
||||
});
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
var EventEmitter = require('events').EventEmitter;
|
||||
var assign = require('object-assign');
|
||||
var Convert = require('ansi-to-html');
|
||||
var docker = require('./Docker');
|
||||
var docker = require('../utils/DockerUtil');
|
||||
var stream = require('stream');
|
||||
|
||||
var _convert = new Convert();
|
|
@ -1,9 +1,9 @@
|
|||
jest.dontMock('./SetupStore');
|
||||
var setupStore = require('./SetupStore');
|
||||
var virtualBox = require('./VirtualBox');
|
||||
var util = require('./Util');
|
||||
var machine = require('./DockerMachine');
|
||||
var setupUtil = require('./SetupUtil');
|
||||
var virtualBox = require('../utils/VirtualBoxUtil');
|
||||
var util = require('../utils/Util');
|
||||
var machine = require('../utils/DockerMachineUtil');
|
||||
var setupUtil = require('../utils/SetupUtil');
|
||||
|
||||
describe('SetupStore', function () {
|
||||
describe('download step', function () {
|
||||
|
@ -21,7 +21,7 @@ describe('SetupStore', function () {
|
|||
pit('downloads virtualbox if it is installed but has an outdated version', function () {
|
||||
virtualBox.installed.mockReturnValue(true);
|
||||
virtualBox.version.mockReturnValue(Promise.resolve('4.3.16'));
|
||||
setupUtil.compareVersions.mockReturnValue(-1);
|
||||
util.compareVersions.mockReturnValue(-1);
|
||||
setupUtil.download.mockReturnValue(Promise.resolve());
|
||||
setupUtil.virtualBoxFileName.mockReturnValue('');
|
||||
util.supportDir.mockReturnValue('');
|
||||
|
@ -53,7 +53,7 @@ describe('SetupStore', function () {
|
|||
|
||||
pit('only installs binaries if virtualbox is installed', function () {
|
||||
virtualBox.installed.mockReturnValue(true);
|
||||
setupUtil.compareVersions.mockReturnValue(0);
|
||||
util.compareVersions.mockReturnValue(0);
|
||||
setupUtil.needsBinaryFix.mockReturnValue(true);
|
||||
return setupStore.steps().install.run().then(() => {
|
||||
expect(setupUtil.copyBinariesCmd).toBeCalled();
|
||||
|
@ -65,15 +65,6 @@ describe('SetupStore', function () {
|
|||
|
||||
describe('init step', function () {
|
||||
virtualBox.vmdestroy.mockReturnValue(Promise.resolve());
|
||||
pit('inintializes the machine vm if it does not exist', function () {
|
||||
util.home.mockReturnValue('home');
|
||||
machine.name.mockReturnValue('name');
|
||||
machine.exists.mockReturnValue(Promise.resolve(false));
|
||||
machine.create.mockReturnValue(Promise.resolve());
|
||||
return setupStore.steps().init.run().then(() => {
|
||||
expect(machine.create).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
pit('upgrades the vm if it exists and is out of date', function () {
|
||||
machine.exists.mockReturnValue(Promise.resolve(true));
|
||||
|
@ -82,7 +73,7 @@ describe('SetupStore', function () {
|
|||
machine.stop.mockReturnValue(Promise.resolve());
|
||||
machine.start.mockReturnValue(Promise.resolve());
|
||||
machine.upgrade.mockReturnValue(Promise.resolve());
|
||||
setupUtil.compareVersions.mockReturnValue(-1);
|
||||
util.compareVersions.mockReturnValue(-1);
|
||||
machine.create.mockClear();
|
||||
machine.upgrade.mockClear();
|
||||
machine.start.mockClear();
|
|
@ -3,15 +3,14 @@ var _ = require('underscore');
|
|||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var Promise = require('bluebird');
|
||||
var machine = require('./DockerMachine');
|
||||
var virtualBox = require('./VirtualBox');
|
||||
var setupUtil = require('./SetupUtil');
|
||||
var util = require('./Util');
|
||||
var machine = require('../utils/DockerMachineUtil');
|
||||
var virtualBox = require('../utils/VirtualBoxUtil');
|
||||
var setupUtil = require('../utils/SetupUtil');
|
||||
var util = require('../utils/Util');
|
||||
var assign = require('object-assign');
|
||||
var metrics = require('./Metrics');
|
||||
var metrics = require('../utils/MetricsUtil');
|
||||
var bugsnag = require('bugsnag-js');
|
||||
var rimraf = require('rimraf');
|
||||
var docker = require('./Docker');
|
||||
var docker = require('../utils/DockerUtil');
|
||||
|
||||
var _currentStep = null;
|
||||
var _error = null;
|
||||
|
@ -62,46 +61,43 @@ var _steps = [{
|
|||
seconds: 58,
|
||||
run: Promise.coroutine(function* (progressCallback) {
|
||||
setupUtil.simulateProgress(this.seconds, progressCallback);
|
||||
yield virtualBox.vmdestroy('kitematic-vm');
|
||||
var exists = yield machine.exists();
|
||||
if (!exists || (yield machine.state()) === 'Error') {
|
||||
try {
|
||||
yield machine.rm();
|
||||
yield machine.create();
|
||||
if(util.isWindows()) {
|
||||
var home = util.home();
|
||||
var driveLetter = home.charAt(0);
|
||||
var parts = home.split('\\').slice(0, -1);
|
||||
var usersDirName = parts[parts.length-1];
|
||||
var usersDirPath = parts.join('\\');
|
||||
var shareName = driveLetter + "/" + usersDirName;
|
||||
if (!exists) {
|
||||
yield machine.create();
|
||||
if(util.isWindows()) {
|
||||
let home = util.home();
|
||||
let driveLetter = home.charAt(0);
|
||||
let parts = home.split('\\').slice(0, -1);
|
||||
let usersDirName = parts[parts.length-1];
|
||||
let usersDirPath = parts.join('\\');
|
||||
let shareName = driveLetter + "/" + usersDirName;
|
||||
|
||||
yield machine.stop();
|
||||
yield virtualBox.mountSharedDir(machine.name(), shareName, usersDirPath);
|
||||
yield machine.start();
|
||||
}
|
||||
} catch (err) {
|
||||
rimraf.sync(path.join(util.home(), '.docker', 'machine', 'machines', machine.name()));
|
||||
yield machine.create();
|
||||
if(util.isWindows()) {
|
||||
var home = util.home();
|
||||
var driveLetter = home.charAt(0);
|
||||
var parts = home.split('\\').slice(0, -1);
|
||||
var usersDirName = parts[parts.length-1];
|
||||
var usersDirPath = parts.join('\\');
|
||||
var shareName = driveLetter + "/" + usersDirName;
|
||||
yield machine.stop();
|
||||
yield virtualBox.mountSharedDir(machine.name(), shareName, usersDirPath);
|
||||
yield machine.start();
|
||||
}
|
||||
return;
|
||||
} else if ((yield machine.state()) === 'Error') {
|
||||
yield machine.rm();
|
||||
yield machine.create();
|
||||
if(util.isWindows()) {
|
||||
let home = util.home();
|
||||
let driveLetter = home.charAt(0);
|
||||
let parts = home.split('\\').slice(0, -1);
|
||||
let usersDirName = parts[parts.length-1];
|
||||
let usersDirPath = parts.join('\\');
|
||||
let shareName = driveLetter + "/" + usersDirName;
|
||||
|
||||
yield machine.stop();
|
||||
yield virtualBox.mountSharedDir(machine.name(), shareName, usersDirPath);
|
||||
yield machine.start();
|
||||
}
|
||||
yield machine.stop();
|
||||
yield virtualBox.mountSharedDir(machine.name(), shareName, usersDirPath);
|
||||
yield machine.start();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var isoversion = machine.isoversion();
|
||||
var packagejson = util.packagejson();
|
||||
if (!isoversion || setupUtil.compareVersions(isoversion, packagejson['docker-version']) < 0) {
|
||||
if (!isoversion || util.compareVersions(isoversion, packagejson['docker-version']) < 0) {
|
||||
yield machine.start();
|
||||
yield machine.upgrade();
|
||||
}
|
||||
|
@ -178,10 +174,10 @@ var SetupStore = assign(Object.create(EventEmitter.prototype), {
|
|||
|
||||
required.download = vboxNeedsInstall && (!fs.existsSync(vboxfile) || setupUtil.checksum(vboxfile) !== setupUtil.virtualBoxChecksum());
|
||||
required.install = vboxNeedsInstall || setupUtil.needsBinaryFix();
|
||||
required.init = required.install || !(yield machine.exists()) || (yield machine.state()) !== 'Running' || !isoversion || setupUtil.compareVersions(isoversion, packagejson['docker-version']) < 0;
|
||||
required.init = required.install || !(yield machine.exists()) || (yield machine.state()) !== 'Running' || !isoversion || util.compareVersions(isoversion, packagejson['docker-version']) < 0;
|
||||
|
||||
var exists = yield machine.exists();
|
||||
if (isoversion && setupUtil.compareVersions(isoversion, packagejson['docker-version']) < 0) {
|
||||
if (isoversion && util.compareVersions(isoversion, packagejson['docker-version']) < 0) {
|
||||
this.steps().init.seconds = 33;
|
||||
} else if (exists && (yield machine.state()) === 'Saved') {
|
||||
this.steps().init.seconds = 8;
|
||||
|
@ -210,7 +206,6 @@ var SetupStore = assign(Object.create(EventEmitter.prototype), {
|
|||
yield this.updateBinaries();
|
||||
var steps = yield this.requiredSteps();
|
||||
for (let step of steps) {
|
||||
console.log(step.name);
|
||||
_currentStep = step;
|
||||
step.percent = 0;
|
||||
while (true) {
|
||||
|
@ -258,15 +253,15 @@ var SetupStore = assign(Object.create(EventEmitter.prototype), {
|
|||
metrics.track('Setup Finished');
|
||||
break;
|
||||
} catch (err) {
|
||||
err.message = util.removeSensitiveData(err.message);
|
||||
metrics.track('Setup Failed', {
|
||||
step: _currentStep,
|
||||
message: err.message
|
||||
});
|
||||
console.log(err);
|
||||
console.log(err.stack);
|
||||
bugsnag.notify('SetupError', err.message, {
|
||||
error: err,
|
||||
step: _currentStep
|
||||
output: err.message
|
||||
}, 'info');
|
||||
_error = err;
|
||||
this.emit(this.ERROR_EVENT);
|
|
@ -1,5 +1,5 @@
|
|||
var _ = require('underscore');
|
||||
var docker = require('./Docker');
|
||||
var docker = require('../utils/DockerUtil');
|
||||
|
||||
var ContainerUtil = {
|
||||
env: function (container) {
|
||||
|
@ -12,6 +12,7 @@ var ContainerUtil = {
|
|||
return splits;
|
||||
}));
|
||||
},
|
||||
// TODO (jeffdm): inject host here instead of requiring Docker
|
||||
ports: function (container) {
|
||||
if (!container.NetworkSettings) {
|
||||
return {};
|
|
@ -3,19 +3,18 @@ var path = require('path');
|
|||
var Promise = require('bluebird');
|
||||
var fs = require('fs');
|
||||
var util = require('./Util');
|
||||
var exec = require('child_process').exec;
|
||||
var resources = require('./Resources');
|
||||
var resources = require('./ResourcesUtil');
|
||||
|
||||
var NAME = 'dev';
|
||||
|
||||
var DockerMachine = {
|
||||
command() {
|
||||
command: function () {
|
||||
return resources.docker_machine();
|
||||
},
|
||||
name() {
|
||||
name: function () {
|
||||
return NAME;
|
||||
},
|
||||
isoversion() {
|
||||
isoversion: function () {
|
||||
try {
|
||||
var data = fs.readFileSync(path.join(util.home(), '.docker', 'machine', 'machines', NAME, 'boot2docker.iso'), 'utf8');
|
||||
var match = data.match(/Boot2Docker-v(\d+\.\d+\.\d+)/);
|
||||
|
@ -28,7 +27,7 @@ var DockerMachine = {
|
|||
return null;
|
||||
}
|
||||
},
|
||||
info() {
|
||||
info: function () {
|
||||
return util.exec([this.command(), 'ls']).then(stdout => {
|
||||
var lines = stdout.trim().split('\n').filter(line => line.indexOf('time=') === -1);
|
||||
var machines = {};
|
||||
|
@ -49,43 +48,43 @@ var DockerMachine = {
|
|||
}
|
||||
});
|
||||
},
|
||||
exists() {
|
||||
exists: function () {
|
||||
return this.info().then(() => {
|
||||
return true;
|
||||
}).catch(() => {
|
||||
return false;
|
||||
});
|
||||
},
|
||||
|
||||
create() {
|
||||
return util.exec([this.command(), 'create', '-d', 'virtualbox', '--virtualbox-memory', '2048', NAME]);
|
||||
create: function () {
|
||||
var dockerversion = util.packagejson()['docker-version'];
|
||||
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() {
|
||||
return util.exec([this.command(), 'start', NAME]);
|
||||
start: function () {
|
||||
return util.exec([this.command(), '-D', 'start', NAME]);
|
||||
},
|
||||
stop() {
|
||||
stop: function () {
|
||||
return util.exec([this.command(), 'stop', NAME]);
|
||||
},
|
||||
upgrade() {
|
||||
upgrade: function () {
|
||||
return util.exec([this.command(), 'upgrade', NAME]);
|
||||
},
|
||||
rm() {
|
||||
rm: function () {
|
||||
return util.exec([this.command(), 'rm', '-f', NAME]);
|
||||
},
|
||||
ip() {
|
||||
return util.exec([this.command(), 'ip', NAME]).then(stdout => {
|
||||
ip: function () {
|
||||
return util.exec([this.command(), '-D', 'ip', NAME]).then(stdout => {
|
||||
return Promise.resolve(stdout.trim().replace('\n', ''));
|
||||
});
|
||||
},
|
||||
regenerateCerts() {
|
||||
regenerateCerts: function () {
|
||||
return util.exec([this.command(), 'tls-regenerate-certs', '-f', NAME]);
|
||||
},
|
||||
state() {
|
||||
state: function () {
|
||||
return this.info().then(info => {
|
||||
return info ? info.state : null;
|
||||
});
|
||||
},
|
||||
disk() {
|
||||
disk: function () {
|
||||
return util.exec([this.command(), 'ssh', NAME, 'df']).then(stdout => {
|
||||
try {
|
||||
var lines = stdout.split('\n');
|
||||
|
@ -109,7 +108,7 @@ var DockerMachine = {
|
|||
}
|
||||
});
|
||||
},
|
||||
memory() {
|
||||
memory: function () {
|
||||
return util.exec([this.command(), 'ssh', NAME, 'free -m']).then(stdout => {
|
||||
try {
|
||||
var lines = stdout.split('\n');
|
||||
|
@ -135,7 +134,7 @@ var DockerMachine = {
|
|||
}
|
||||
});
|
||||
},
|
||||
stats() {
|
||||
stats: function () {
|
||||
this.state().then(state => {
|
||||
if (state === 'Stopped') {
|
||||
return Promise.resolve({state: state});
|
||||
|
@ -150,7 +149,7 @@ var DockerMachine = {
|
|||
});
|
||||
});
|
||||
},
|
||||
dockerTerminal() {
|
||||
dockerTerminal: function () {
|
||||
if(util.isWindows()) {
|
||||
this.info().then(machine => {
|
||||
util.execProper(`start cmd.exe /k "SET DOCKER_HOST=${machine.url}&& SET DOCKER_CERT_PATH=${path.join(util.home(), '.docker/machine/machines/' + machine.name)}&& SET DOCKER_TLS_VERIFY=1`);
|
|
@ -7,7 +7,7 @@ var util = require('./Util');
|
|||
var settings;
|
||||
|
||||
try {
|
||||
settings = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'settings.json'), 'utf8'));
|
||||
settings = JSON.parse(fs.readFileSync(path.join(__dirname, '../..', 'settings.json'), 'utf8'));
|
||||
} catch (err) {
|
||||
settings = {};
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
var util = require('./Util');
|
||||
var path = require('path');
|
||||
|
||||
module.exports = {
|
||||
resourceDir: function () {
|
||||
return process.env.RESOURCES_PATH;
|
||||
},
|
||||
macsudo: function () {
|
||||
return path.join(this.resourceDir(), 'macsudo');
|
||||
},
|
||||
terminal: function () {
|
||||
return path.join(this.resourceDir(), 'terminal');
|
||||
},
|
||||
docker: function () {
|
||||
return path.join(this.resourceDir(), 'docker-' + util.packagejson()['docker-version'] + util.binsEnding());
|
||||
},
|
||||
dockerMachine: function () {
|
||||
return path.join(this.resourceDir(), 'docker-machine-' + util.packagejson()['docker-machine-version'] + util.binsEnding());
|
||||
}
|
||||
};
|
|
@ -6,27 +6,33 @@ var request = require('request');
|
|||
var progress = require('request-progress');
|
||||
var Promise = require('bluebird');
|
||||
var util = require('./Util');
|
||||
var resources = require('./Resources');
|
||||
var resources = require('./ResourcesUtil');
|
||||
|
||||
var SetupUtil = {
|
||||
needsBinaryFix() {
|
||||
return !!(util.pathDoesNotExistOrDenied(util.binsPath()) || util.pathDoesNotExistOrDenied(util.dockerBinPath()) || util.pathDoesNotExistOrDenied(util.dockerMachineBinPath()));
|
||||
},
|
||||
pathDoesNotExistOrDenied: function (path) {
|
||||
if(util.isWindows()) {
|
||||
return (!fs.existsSync(path));
|
||||
} else {
|
||||
return (!fs.existsSync(path) || fs.statSync(path).gid !== 80 || fs.statSync(path).uid !== process.getuid());
|
||||
}
|
||||
},
|
||||
escapePath(str) {
|
||||
return str.replace(/ /g, '\\ ').replace(/\(/g, '\\(').replace(/\)/g, '\\)');
|
||||
},
|
||||
shouldUpdateBinaries() {
|
||||
return !fs.existsSync(util.dockerBinPath()) ||
|
||||
!fs.existsSync(util.dockerMachineBinPath()) ||
|
||||
this.checksum(util.dockerMachineBinPath()) !== this.checksum(resources.docker_machine()) ||
|
||||
this.checksum(util.dockerMachineBinPath()) !== this.checksum(resources.dockerMachine()) ||
|
||||
this.checksum(util.dockerBinPath()) !== this.checksum(resources.docker());
|
||||
|
||||
},
|
||||
copyBinariesCmd: Promise.coroutine(function* () {
|
||||
yield fs.mkdirs(util.binsPath());
|
||||
yield fs.copy(resources.docker_machine(), util.dockerMachineBinPath());
|
||||
yield fs.copy(resources.dockerMachine(), util.dockerMachineBinPath());
|
||||
yield fs.copy(resources.docker(), util.dockerBinPath());
|
||||
return Promise.resolve();
|
||||
}),
|
||||
fixBinariesCmd: Promise.coroutine(function* () {
|
||||
if(util.isWindows()) {
|
||||
|
@ -103,55 +109,6 @@ var SetupUtil = {
|
|||
},
|
||||
virtualBoxChecksum() {
|
||||
return util.isWindows() ? util.packagejson()['virtualbox-checksum-win'] : util.packagejson()['virtualbox-checksum'];
|
||||
},
|
||||
compareVersions(v1, v2, options) {
|
||||
var lexicographical = options && options.lexicographical,
|
||||
zeroExtend = options && options.zeroExtend,
|
||||
v1parts = v1.split('.'),
|
||||
v2parts = v2.split('.');
|
||||
|
||||
function isValidPart(x) {
|
||||
return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x);
|
||||
}
|
||||
|
||||
if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
if (zeroExtend) {
|
||||
while (v1parts.length < v2parts.length) {
|
||||
v1parts.push('0');
|
||||
}
|
||||
while (v2parts.length < v1parts.length) {
|
||||
v2parts.push('0');
|
||||
}
|
||||
}
|
||||
|
||||
if (!lexicographical) {
|
||||
v1parts = v1parts.map(Number);
|
||||
v2parts = v2parts.map(Number);
|
||||
}
|
||||
|
||||
for (var i = 0; i < v1parts.length; ++i) {
|
||||
if (v2parts.length === i) {
|
||||
return 1;
|
||||
}
|
||||
if (v1parts[i] === v2parts[i]) {
|
||||
continue;
|
||||
}
|
||||
else if (v1parts[i] > v2parts[i]) {
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (v1parts.length !== v2parts.length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
jest.dontMock('./URLUtil');
|
||||
jest.dontMock('parseUri');
|
||||
var urlUtil = require('./URLUtil');
|
||||
var util = require('./Util');
|
||||
|
||||
describe('URLUtil', function () {
|
||||
beforeEach(() => {
|
||||
util.compareVersions.mockClear();
|
||||
util.isOfficialRepo.mockClear();
|
||||
});
|
||||
|
||||
it('does nothing if the url is undefined', () => {
|
||||
util.compareVersions.mockReturnValue(1);
|
||||
util.isOfficialRepo.mockReturnValue(true);
|
||||
expect(urlUtil.openUrl()).toBe(false);
|
||||
});
|
||||
|
||||
it('does nothing if the flags object is undefined', () => {
|
||||
util.compareVersions.mockReturnValue(1);
|
||||
util.isOfficialRepo.mockReturnValue(true);
|
||||
expect(urlUtil.openUrl('docker://repository/run/redis')).toBe(false);
|
||||
});
|
||||
|
||||
it('does nothing if the url enabled flag is falsy', () => {
|
||||
util.compareVersions.mockReturnValue(1);
|
||||
util.isOfficialRepo.mockReturnValue(true);
|
||||
expect(urlUtil.openUrl('docker://repository/run/redis', {dockerURLEnabledVersion: undefined})).toBe(false);
|
||||
});
|
||||
|
||||
it('does nothing if the url enabled flag version is higher than the app version', () => {
|
||||
util.compareVersions.mockReturnValue(-1);
|
||||
util.isOfficialRepo.mockReturnValue(true);
|
||||
expect(urlUtil.openUrl('docker://repository/run/redis', {dockerURLEnabledVersion: '0.5.19'}, '0.5.18')).toBe(false);
|
||||
});
|
||||
|
||||
it('does nothing if the type is not in the whitelist', () => {
|
||||
util.compareVersions.mockReturnValue(1);
|
||||
util.isOfficialRepo.mockReturnValue(true);
|
||||
expect(urlUtil.openUrl('docker://badtype/run/redis', {dockerURLEnabledVersion: '0.5.19'}, '0.5.18')).toBe(false);
|
||||
});
|
||||
|
||||
it('does nothing if the method is not in the whitelist', () => {
|
||||
util.compareVersions.mockReturnValue(1);
|
||||
util.isOfficialRepo.mockReturnValue(true);
|
||||
expect(urlUtil.openUrl('docker://repository/badmethod/redis', {dockerURLEnabledVersion: '0.5.19'}, '0.5.18')).toBe(false);
|
||||
});
|
||||
|
||||
it('does nothing if protocol is not docker:', () => {
|
||||
util.compareVersions.mockReturnValue(1);
|
||||
util.isOfficialRepo.mockReturnValue(true);
|
||||
expect(urlUtil.openUrl('facetime://')).toBe(false);
|
||||
});
|
||||
|
||||
it('does nothing if repo is not official', () => {
|
||||
util.compareVersions.mockReturnValue(1);
|
||||
util.isOfficialRepo.mockReturnValue(false);
|
||||
expect(urlUtil.openUrl('docker://repository/run/not/official', {dockerURLEnabledVersion: '0.5.19'}, '0.5.20')).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true if type and method are correct', () => {
|
||||
util.compareVersions.mockReturnValue(1);
|
||||
util.isOfficialRepo.mockReturnValue(true);
|
||||
expect(urlUtil.openUrl('docker://repository/run/redis', {dockerURLEnabledVersion: '0.5.19'}, '0.5.20')).toBe(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,60 @@
|
|||
var util = require('./Util');
|
||||
var parseUri = require('parseUri');
|
||||
var containerStore = require('../stores/ContainerStore');
|
||||
|
||||
module.exports = {
|
||||
TYPE_WHITELIST: ['repository'],
|
||||
METHOD_WHITELIST: ['run'],
|
||||
openUrl: function (url, flags, appVersion) {
|
||||
if (!url || !flags || !flags.dockerURLEnabledVersion || !appVersion) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure this feature is enabled via the feature flag
|
||||
if (util.compareVersions(appVersion, flags.dockerURLEnabledVersion) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var parser = parseUri(url);
|
||||
|
||||
if (parser.protocol !== 'docker') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the type of object we're operating on, e.g. 'repository'
|
||||
var type = parser.host;
|
||||
|
||||
if (this.TYPE_WHITELIST.indexOf(type) === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Separate the path into [run', 'redis']
|
||||
var tokens = parser.path.replace('/', '').split('/');
|
||||
|
||||
// Get the method trying to be executed, e.g. 'run'
|
||||
var method = tokens[0];
|
||||
|
||||
if (this.METHOD_WHITELIST.indexOf(method) === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the repository namespace and repo name, e.g. 'redis' or 'myusername/myrepo'
|
||||
var repo = tokens.slice(1).join('/');
|
||||
|
||||
// Only accept official repos for now (one component)
|
||||
if (tokens > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only accept official repos for now
|
||||
if (!util.isOfficialRepo(repo)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (type === 'repository' && method === 'run') {
|
||||
containerStore.setPending(repo, 'latest');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,61 @@
|
|||
jest.dontMock('./Util');
|
||||
var util = require('./Util');
|
||||
|
||||
describe('Util', function () {
|
||||
describe('when removing sensitive data', function () {
|
||||
it('filters ssh certificate data', function () {
|
||||
var testdata = String.raw`time="2015-04-17T21:43:47-04:00" level="debug" msg="executing: ssh -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectionAttempts=30 -o LogLevel=quiet -p 50483 -i /Users/johnappleseed/.docker/machine/machines/dev2/id_rsa docker@localhost sudo mkdir -p /var/lib/boot2docker" time="2015-04-17T21:43:47-04:00" level="debug" msg="executing: ssh -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectionAttempts=30 -o LogLevel=quiet -p 50483 -i /Users/johnappleseed/.docker/machine/machines/dev2/id_rsa docker@localhost echo \"-----BEGIN CERTIFICATE-----\nMIIC+DCCAeKgAwIBAgIRANfIbsa2M94gDY+fBiBiQBkwCwYJKoZIhvcNAQELMBIx\nEDAOBgNVBAoTB2ptb3JnYW4wHhcNMTUwNDE4MDEzODAwWhcNMTgwNDAyMDEzODAw\nWjAPMQ0wCwYDVQQKEwRkZXYyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\nAQEA1yamWT0bk0pRU7eiStjiXe2jkzdeI0SdJZo+bjczkl6kzNW/FmR/OkcP8gHX\nCO3fUCWkR/+rBgz3nuM1Sy0BIUo0EMQGfx17OqIJPXO+BrpCHsXlphHmbQl5bE2Y\nF+bAsGc6WCippw/caNnIHRsb6zAZVYX2AHLYY0fwIDAQABo1AwTjAOBgNVHQ8BAf8EBAMCAKAwHQYD\nVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDwYDVR0R\nBAgwBocEwKhjZTALBgkqhkiG9w0BAQsDggEBAKBdD86+kl4X1VMjgGlNYnc42tWa\nbo1iDl/frxiLkfPSc2McAOm3AqX1ao+ynjqq1XTlBLPTQByu/oNZgA724LRJDfdG\nCKGUV8latW7rB1yhf/SZSmyhNjufuWlgCtbkw7Q/oPddzYuSOdDW8tVok9gMC0vL\naqKCWfVKkCmvGH+8/wPrkYmro/f0uwJ8ee+yrbBPlBE/qE+Lqcfr0YcXEDaS8CmL\nDjWg7KNFpA6M+/tFNQhplbjwRsCt7C4bzQu0aBIG5XH1Jr2HrKlLjWdmluPHWUL6\nX5Vh1bslYJzsSdBNZFWSKShZ+gtRpjtV7NynANDJPQNIRhDxAf4uDY9hA2c=\n-----END CERTIFICATE-----\n\" | sudo tee /var/lib/boot2docker/server.pem"
|
||||
time="2015-04-17T21:43:47-04:00" level="debug" msg="executing: /usr/bin/VBoxManage showvminfo dev2 --machinereadable"`;
|
||||
expect(util.removeSensitiveData(testdata).indexOf('CERTIFICATE')).toEqual(-1);
|
||||
expect(util.removeSensitiveData(testdata).indexOf('nX5Vh1bslYJzsSdBNZFWSKShZ+gtRpjtV7NynANDJPQNIRhDxAf4uDY9hA2c')).toEqual(-1);
|
||||
expect(util.removeSensitiveData(testdata).indexOf('<redacted>')).toNotEqual(-1);
|
||||
});
|
||||
|
||||
it('filters ssh private key data', function () {
|
||||
var testdata = String.raw`hZbuxglOtQv2AQqOp/luhZ3Y8kDs4cqRzoA1o+k+LAyjEb+Nk\nGA8=\n-----END CERTIFICATE-----\n\" | sudo tee /var/lib/boot2docker/ca.pem"
|
||||
time="2015-04-17T21:43:47-04:00" level="debug" msg="executing: ssh -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectionAttempts=30 -o LogLevel=quiet -p 50483 -i /Users/johnappleseed/.docker/machine/machines/dev2/id_rsa docker@localhost echo \"-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA1yamWT0bk0pRU7eiStjiXe2jkzdeI0SdJZo+bjczkl6kzNW/\nFmR/OkcP8gHXCO3fUCWkR/+rBgz3nuM1Sy0BIUo0EMQGfx17OqIJPXO+BrpCHsXl\nphHmbQl5bE2YF+bAsGc6WCippczQIu5bPweeAkR1WdlkhD08tHD4o1ESe09fXx5G\nXcZFfd2xQWdvAJX3fTuGBk3IMEF2fye5b69zUyVDGbTylyjKDOi9Xxdlc4y9cOPw\nzcwQFCOJiCBYlxDO0fbinA+KigCs29Dd5U3oXbloLr3JQTE/SkxFh9W5rkX8ysY4\n2h3EnR7YIBWt/caNnIHRsb6zAZVYX2AHLYY0fwIDAQABAoIBAQDKF3TTh/G59WnU\n4D2iXnyqy8gFRVG4gP+3TV3s+w8HIr1b5j6akwVqwUs5//5zVbSYPPNF6eJESbPi\nW/s4ROq10VR8lxSfHBsfJQrW3TwWZ6gp7atbxZ6Stv6F+5CsisReLmiAXJmVsn+j\nAA9Xchk6egFcxzWCfV7jAuaZyVI53cclepm/xkGjPwrfXr+nA+UMvO6DllC6IcBF\no4+O0jVtzdMecZnQk6nWxNJjurodTTQakrNAqSMgBshn48wf3N35b+p8RtTzLJ8L\nYuHkv6OKMITIazcHadjsN8icGgIGf2BJ1CRje7j0Yzow8jwY+Pet3yxKSfXED89B\nD34AEXl5AoGBANi17og+yPFOWURUrksO/QyzlOtXcQdQu8SmkUj4ACoqF0gegQIb\nC/DNMcYxJAsPPgw/t5Ws/af8DuatYguGukmekYREVjc7DS/hPWDZzeavPd95cOw0\nuMPgJE76HJ3BSYcp1f8WKcN+xDket9CF6Qz+VX5aQSUEc333V5h7D/nzAoGBAP4o\nVCvQu5eKYmDhMFSOA0+Qm3EECRqMLoH6kpEcbMjM8+kOeI0fUuE3CX8nzs7P4py/\n0IFj2Yxl578NHJOjCpbB1UKtxLkmDH42wXXzrWJXRaWXC93dh1sl0aB6qE25FtSD\nzjYh4y1DA/t6y95YRrIqC2WhIU7eigIoujmtOFJFAoGABSKiiWX7ewRhRyY+jxbG\n1lM3FzCWRBccq/dKgBEoZ9dhf9sBMZyUdttV751gfkaZMM8duZVE2YM2ky7OoPlL\nVs1EI38/D8X9dQIAY1gl8e57J92H2IETU8ju81Qn83EOHf7WzFmpGbHaUoQw1Ocn\nc6BfREQ9QPRPDFAdKkbYRRMCgYEAl44k4xvNQUhb8blWwJUOlFt+1Z26cAI3mXp5\n+94fYH4W1Fq0uDJ9kZ7oItLyF5EPaLlY9E8+YuJBl0OSTtdicROUv/Yu4Nk3ievM\n4TE1qvavqVaw1NRM6qVao3+A7Rf57S/Lv6vldBAKR+OpviSVw5gew7OZ0RYS5caz\nhcEtXKECgYAJb7t67nococm0PsRe8Xv1SQOQjetrhzwzD1PLOSC9TrzwA22/ZktZ\neu/qfvYgOPT4LkDGVCzn8J+TAcUVnIvAnJRQTsBu55uiL8YC5jZQ8E1hBf7kskMq\nh16WD19Djv3WhfBNXBxvnagDDWw5DxmiiKzSf0k3QDDoX7wjDAV1dQ==\n-----END RSA PRIVATE KEY-----\n\" | sudo tee /var/lib/boot2docker/server-key.pem"
|
||||
time="2015-04-17T21:43:47-04:00" level="debug" msg="executing: ssh -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectionAttempts=30 -o LogLevel=quiet -p 50483 -i /Users/johnappleseed/.docker/machine/machines/dev2/id_rsa docker@localhost echo \"-----BEGIN CERTIFICATE-----\nMIIC+DCCAeKgAwIBAgIRANfIbsa2M94gDY+fBiBiQBkwCwYJKoZIhvcNAQELMBIx\nEDAOBg`;
|
||||
expect(util.removeSensitiveData(testdata).indexOf('PRIVATE')).toEqual(-1);
|
||||
expect(util.removeSensitiveData(testdata).indexOf('94fYH4W1Fq0uDJ9kZ7oItLyF5EPaLlY9E8+YuJBl0OSTtdicROUv')).toEqual(-1);
|
||||
expect(util.removeSensitiveData(testdata).indexOf('<redacted>')).toNotEqual(-1);
|
||||
});
|
||||
|
||||
it('filters username data', function () {
|
||||
var testdata = String.raw`/Users/johnappleseed/.docker/machine/machines/dev2/id_rsa docker@localhost echo`;
|
||||
expect(util.removeSensitiveData(testdata).indexOf('/Users/johnappleseed/')).toEqual(-1);
|
||||
expect(util.removeSensitiveData(testdata).indexOf('/Users/<redacted>/')).toNotEqual(-1);
|
||||
|
||||
testdata = String.raw`/Users/some.wei-rdUsername/.docker/machine/machines/dev2/id_rsa docker@localhost echo`;
|
||||
expect(util.removeSensitiveData(testdata).indexOf('/Users/some.wei-rdUsername/')).toEqual(-1);
|
||||
expect(util.removeSensitiveData(testdata).indexOf('/Users/<redacted>/')).toNotEqual(-1);
|
||||
});
|
||||
|
||||
it ('returns input if empty or not a string', function () {
|
||||
expect(util.removeSensitiveData('')).toBe('');
|
||||
expect(util.removeSensitiveData(1)).toBe(1);
|
||||
expect(util.removeSensitiveData(undefined)).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when verifying that a repo is official', function () {
|
||||
it('accepts official repo', () => {
|
||||
expect(util.isOfficialRepo('redis')).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects falsy value as official repo', () => {
|
||||
expect(util.isOfficialRepo(undefined)).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects empty repo name', () => {
|
||||
expect(util.isOfficialRepo('')).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects repo with non official namespace', () => {
|
||||
expect(util.isOfficialRepo('kitematic/html')).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects repo with a different registry address', () => {
|
||||
expect(util.isOfficialRepo('www.myregistry.com/kitematic/html')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,127 @@
|
|||
var exec = require('exec');
|
||||
var Promise = require('bluebird');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
module.exports = {
|
||||
exec: function (args, options) {
|
||||
options = options || {};
|
||||
return new Promise((resolve, reject) => {
|
||||
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));
|
||||
} else {
|
||||
resolve(stdout);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
isWindows: function () {
|
||||
return process.platform === 'win32';
|
||||
},
|
||||
home: function () {
|
||||
return process.env[this.isWindows() ? 'USERPROFILE' : 'HOME'];
|
||||
},
|
||||
supportDir: function () {
|
||||
var acc = path.join(this.home(), 'Library', 'Application\ Support', 'Kitematic');
|
||||
fs.mkdirsSync(acc);
|
||||
return acc;
|
||||
},
|
||||
CommandOrCtrl: function () {
|
||||
return this.isWindows() ? 'Ctrl' : 'Command';
|
||||
},
|
||||
|
||||
binsPath: function () {
|
||||
return this.isWindows() ? path.join(this.home(), 'Kitematic-bins') : path.join('/usr/local/bin');
|
||||
},
|
||||
binsEnding: function () {
|
||||
return this.isWindows() ? '.exe' : '';
|
||||
},
|
||||
dockerBinPath: function () {
|
||||
return path.join(this.binsPath(), 'docker' + this.binsEnding());
|
||||
},
|
||||
dockerMachineBinPath: function () {
|
||||
return path.join(this.binsPath(), 'docker-machine' + this.binsEnding());
|
||||
},
|
||||
removeSensitiveData: function (str) {
|
||||
if (!str || str.length === 0 || typeof str !== 'string' ) {
|
||||
return str;
|
||||
}
|
||||
return str.replace(/-----BEGIN CERTIFICATE-----.*-----END CERTIFICATE-----/mg, '<redacted>')
|
||||
.replace(/-----BEGIN RSA PRIVATE KEY-----.*-----END RSA PRIVATE KEY-----/mg, '<redacted>')
|
||||
.replace(/\/Users\/.*\//mg, '/Users/<redacted>/');
|
||||
},
|
||||
packagejson: function () {
|
||||
return JSON.parse(fs.readFileSync(path.join(__dirname, '../..', 'package.json'), 'utf8'));
|
||||
},
|
||||
settingsjson: function () {
|
||||
var settingsjson = {};
|
||||
try {
|
||||
settingsjson = JSON.parse(fs.readFileSync(path.join(__dirname, '../..', 'settings.json'), 'utf8'));
|
||||
} catch (err) {}
|
||||
return settingsjson;
|
||||
},
|
||||
isOfficialRepo: function (name) {
|
||||
if (!name || !name.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// An official repo is alphanumeric characters separated by dashes or
|
||||
// underscores.
|
||||
// Examples: myrepo, my-docker-repo, my_docker_repo
|
||||
// Non-exapmles: mynamespace/myrepo, my%!repo
|
||||
var repoRegexp = /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/;
|
||||
return repoRegexp.test(name);
|
||||
},
|
||||
compareVersions: function (v1, v2, options) {
|
||||
var lexicographical = options && options.lexicographical,
|
||||
zeroExtend = options && options.zeroExtend,
|
||||
v1parts = v1.split('.'),
|
||||
v2parts = v2.split('.');
|
||||
|
||||
function isValidPart(x) {
|
||||
return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x);
|
||||
}
|
||||
|
||||
if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
if (zeroExtend) {
|
||||
while (v1parts.length < v2parts.length) {
|
||||
v1parts.push('0');
|
||||
}
|
||||
while (v2parts.length < v1parts.length) {
|
||||
v2parts.push('0');
|
||||
}
|
||||
}
|
||||
|
||||
if (!lexicographical) {
|
||||
v1parts = v1parts.map(Number);
|
||||
v2parts = v2parts.map(Number);
|
||||
}
|
||||
|
||||
for (var i = 0; i < v1parts.length; ++i) {
|
||||
if (v2parts.length === i) {
|
||||
return 1;
|
||||
}
|
||||
if (v1parts[i] === v2parts[i]) {
|
||||
continue;
|
||||
}
|
||||
else if (v1parts[i] > v2parts[i]) {
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (v1parts.length !== v2parts.length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
},
|
||||
webPorts: ['80', '8000', '8080', '3000', '5000', '2368', '9200', '8983']
|
||||
};
|
|
@ -1,5 +1,5 @@
|
|||
jest.dontMock('./VirtualBox');
|
||||
var virtualBox = require('./VirtualBox');
|
||||
jest.dontMock('./VirtualBoxUtil');
|
||||
var virtualBox = require('./VirtualBoxUtil');
|
||||
var util = require('./Util');
|
||||
|
||||
describe('VirtualBox', function () {
|
|
@ -3,6 +3,7 @@ var fs = require('fs');
|
|||
var util = require('./Util');
|
||||
var path = require('path');
|
||||
var bugsnag = require('bugsnag-js');
|
||||
var metrics = require('./MetricsUtil');
|
||||
|
||||
var WebUtil = {
|
||||
addWindowSizeSaving: function () {
|
||||
|
@ -24,6 +25,7 @@ var WebUtil = {
|
|||
},
|
||||
addBugReporting: function () {
|
||||
var settingsjson = util.settingsjson();
|
||||
|
||||
if (settingsjson.bugsnag) {
|
||||
bugsnag.apiKey = settingsjson.bugsnag;
|
||||
bugsnag.autoNotify = true;
|
||||
|
@ -35,11 +37,21 @@ var WebUtil = {
|
|||
};
|
||||
|
||||
bugsnag.beforeNotify = function(payload) {
|
||||
var re = new RegExp(util.home().replace(/\s+/g, '\\s+'), 'g');
|
||||
payload.stacktrace = payload.stacktrace.replace(/%20/g, ' ').replace(re, '<redacted homedir>');
|
||||
payload.context = payload.context.replace(/%20/g, ' ').replace(re, '<redacted homedir>');
|
||||
payload.file = payload.file.replace(/%20/g, ' ').replace(re, '<redacted homedir>');
|
||||
payload.url = '<redacted url>';
|
||||
if (!metrics.enabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
payload.stacktrace = util.removeSensitiveData(payload.stacktrace);
|
||||
payload.context = util.removeSensitiveData(payload.context);
|
||||
payload.file = util.removeSensitiveData(payload.file);
|
||||
payload.message = util.removeSensitiveData(payload.message);
|
||||
payload.url = util.removeSensitiveData(payload.url);
|
||||
payload.name = util.removeSensitiveData(payload.name);
|
||||
payload.file = util.removeSensitiveData(payload.file);
|
||||
|
||||
for(var key in payload.metaData) {
|
||||
payload.metaData[key] = util.removeSensitiveData(payload.metaData[key]);
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
|
@ -180,7 +180,7 @@
|
|||
width: 110px;
|
||||
padding: 5px;
|
||||
&:hover {
|
||||
background-color: @color-background;
|
||||
background-color: darken(@color-background, 2%);
|
||||
border-radius: 10px;
|
||||
}
|
||||
img {
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
.widget-style() {
|
||||
border-radius: 4px;
|
||||
border: 1px solid @gray-lightest;
|
||||
border: 1px solid @gray-lighter;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,32 @@
|
|||
.new-container-pull {
|
||||
display: flex;
|
||||
flex: 1 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.content {
|
||||
text-align: center;
|
||||
|
||||
.buttons {
|
||||
margin-top: 30px;
|
||||
.btn {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
padding: 8px 18px;
|
||||
font-size: 14px;
|
||||
background: white;
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
}
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
color: @gray-normal;
|
||||
font-weight: 400;
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.new-container {
|
||||
display: flex;
|
||||
flex: 1 auto;
|
||||
|
@ -69,14 +98,14 @@
|
|||
padding: 4px 8px 4px 35px;
|
||||
color: @gray-darkest;
|
||||
margin-bottom: 3px;
|
||||
border-color: @gray-lightest;
|
||||
border-color: @gray-lighter;
|
||||
box-shadow: none;
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
border-color: @gray-lighter;
|
||||
border-color: @brand-primary;
|
||||
}
|
||||
&::-webkit-input-placeholder {
|
||||
color: #DDD;
|
||||
color: @gray-lightest;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
@ -93,10 +122,10 @@
|
|||
overflow: auto;
|
||||
.image-item {
|
||||
display: flex;
|
||||
position: relative;
|
||||
width: 320px;
|
||||
height: 166px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid @gray-lightest;
|
||||
background-color: white;
|
||||
margin-right: 20px;
|
||||
margin-bottom: 20px;
|
||||
|
@ -150,10 +179,11 @@
|
|||
flex: 1 auto;
|
||||
min-width: 90px;
|
||||
background-color: @brand-action;
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-top-left-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
box-shadow: inset 0px 0px 0px 1px rgba(0,0,0,0.2);
|
||||
img {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
@ -161,6 +191,10 @@
|
|||
.card {
|
||||
padding: 10px 20px 10px 20px;
|
||||
position: relative;
|
||||
border: 1px solid @gray-lighter;
|
||||
border-left: 0;
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
.badges {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
|
@ -186,7 +220,6 @@
|
|||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
.description {
|
||||
|
|
|
@ -100,7 +100,7 @@
|
|||
}
|
||||
|
||||
&.radial-gray {
|
||||
background: #EEE;
|
||||
background: @gray-lightest;
|
||||
}
|
||||
|
||||
&.radial-transparent {
|
||||
|
|
|
@ -25,6 +25,10 @@
|
|||
.action {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
img {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
}
|
||||
&.disabled {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
@ -40,15 +44,18 @@
|
|||
top: 45px;
|
||||
&.view {
|
||||
left: 7px;
|
||||
//left: 0px;
|
||||
}
|
||||
&.restart {
|
||||
left: 2px;
|
||||
//left: -18px;
|
||||
}
|
||||
&.stop {
|
||||
left: 7px;
|
||||
}
|
||||
&.start {
|
||||
left: 7px;
|
||||
}
|
||||
&.terminal {
|
||||
left: -1px;
|
||||
//left: -30px;
|
||||
}
|
||||
visibility: hidden;
|
||||
}
|
||||
|
@ -66,7 +73,7 @@
|
|||
display: inline-block;
|
||||
&:hover {
|
||||
border-radius: 40px;
|
||||
background-color: @color-background;
|
||||
background-color: darken(@color-background, 2%);
|
||||
}
|
||||
&.active {
|
||||
border-radius: 40px;
|
||||
|
|
|
@ -59,7 +59,13 @@
|
|||
background-color: lighten(@brand-negative, 32%);
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
-webkit-user-select: text;
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
.setup-actions {
|
||||
button {
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -134,7 +134,6 @@ input[type="text"] {
|
|||
font-weight: 400;
|
||||
text-shadow: none;
|
||||
padding: 5px 14px 5px 14px;
|
||||
height: 30px;
|
||||
cursor: default;
|
||||
|
||||
&.small {
|
||||
|
|
|
@ -12,14 +12,14 @@
|
|||
@traffic-light-gray: #E5E5E5;
|
||||
@traffic-light-gray-border: #D3D3D3;
|
||||
|
||||
@gray-darkest: #253237;
|
||||
@gray-darker: #394C51;
|
||||
@gray-normal: #546C70;
|
||||
@gray-lighter: #7A9999;
|
||||
@gray-lightest: #C7D7D7;
|
||||
@gray-darkest: #233137;
|
||||
@gray-darker: #556473;
|
||||
@gray-normal: #7A8491;
|
||||
@gray-lighter: #C4CDDA;
|
||||
@gray-lightest: #E1E8EF;
|
||||
|
||||
@color-divider: #DCE2E2;
|
||||
@color-background: #F9F9F9;
|
||||
@color-divider: @gray-lightest;
|
||||
@color-background: #FCFCFC;
|
||||
|
||||
@font-regular: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
|
||||
@font-code: Menlo;
|
||||
|
|
|
@ -26,5 +26,16 @@
|
|||
<string>AtomApplication</string>
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||
<true/>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>docker</string>
|
||||
</array>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>Docker App Protocol</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
22
util/deps
|
@ -3,19 +3,20 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|||
BASE=$DIR/..
|
||||
DOCKER_MACHINE_CLI_VERSION=$(node -pe "JSON.parse(process.argv[1])['docker-machine-version']" "$(cat $BASE/package.json)")
|
||||
DOCKER_MACHINE_CLI_FILE=docker-machine-$DOCKER_MACHINE_CLI_VERSION
|
||||
DOCKER_CLI_VERSION=$(node -pe "JSON.parse(process.argv[1])['docker-version']" "$(cat $BASE/package.json)")
|
||||
DOCKER_CLI_FILE=docker-$DOCKER_CLI_VERSION
|
||||
DOCKER_VERSION=$(node -pe "JSON.parse(process.argv[1])['docker-version']" "$(cat $BASE/package.json)")
|
||||
DOCKER_CLI_FILE=docker-$DOCKER_VERSION
|
||||
BOOT2DOCKER_FILE=boot2docker-$DOCKER_VERSION.iso
|
||||
|
||||
pushd $BASE/resources > /dev/null
|
||||
|
||||
if [ ! -f $DOCKER_CLI_FILE ]; then
|
||||
echo "-----> Downloading Docker CLI..."
|
||||
rm -rf docker-*
|
||||
curl -L -o docker-$DOCKER_CLI_VERSION.tgz https://get.docker.com/builds/Darwin/x86_64/docker-$DOCKER_CLI_VERSION.tgz
|
||||
tar xvzf docker-$DOCKER_CLI_VERSION.tgz --strip=3
|
||||
rm docker-$DOCKER_CLI_VERSION.tgz
|
||||
mv docker docker-$DOCKER_CLI_VERSION
|
||||
chmod +x $DOCKER_CLI_FILE
|
||||
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
|
||||
mv docker docker-$DOCKER_VERSION
|
||||
chmod +x $DOCKER_VERSION
|
||||
fi
|
||||
|
||||
if [ ! -f $DOCKER_MACHINE_CLI_FILE ]; then
|
||||
|
@ -25,4 +26,11 @@ if [ ! -f $DOCKER_MACHINE_CLI_FILE ]; then
|
|||
chmod +x $DOCKER_MACHINE_CLI_FILE
|
||||
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
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,14 +1,14 @@
|
|||
var ReactTools = require('react-tools');
|
||||
var babel = require('babel');
|
||||
var fs = require('fs');
|
||||
var crypto = require('crypto');
|
||||
|
||||
module.exports = {
|
||||
process: function(src, filename) {
|
||||
if (filename.indexOf('node_modules') === -1) {
|
||||
var res = ReactTools.transform(require('babel').transform(src).code);
|
||||
if (filename.indexOf('-test') !== -1) {
|
||||
res = 'require(\'babel/polyfill\');' + res;
|
||||
}
|
||||
return res;
|
||||
if (filename.indexOf('node_modules') !== -1) {
|
||||
return src;
|
||||
}
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
|