diff --git a/images/button-stop.png b/images/button-stop.png new file mode 100644 index 0000000000..14f88011b8 Binary files /dev/null and b/images/button-stop.png differ diff --git a/images/button-stop@2x.png b/images/button-stop@2x.png new file mode 100644 index 0000000000..3d20f5315a Binary files /dev/null and b/images/button-stop@2x.png differ diff --git a/package.json b/package.json index 3d364642a7..dbf5d3f6c9 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "jest": { "scriptPreprocessor": "/util/preprocessor.js", "setupEnvScriptFile": "/util/testenv.js", + "setupTestFrameworkScriptFile": "/util/prepare.js", "collectCoverage": true, "testDirectoryName": "src", "testPathIgnorePatterns": [ @@ -42,7 +43,8 @@ "/node_modules/.*JSONStream", "/node_modules/object-assign", "/node_modules/underscore", - "/node_modules/bluebird" + "/node_modules/bluebird", + "/node_modules/source-map-support" ] }, "docker-version": "1.6.0", @@ -93,6 +95,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" } } diff --git a/src/components/ContainerDetailsHeader.react.js b/src/components/ContainerDetailsHeader.react.js index ded2816d5e..016bb01287 100644 --- a/src/components/ContainerDetailsHeader.react.js +++ b/src/components/ContainerDetailsHeader.react.js @@ -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 = RUNNING; } else if (this.props.container.State.Restarting) { state = RESTARTING; diff --git a/src/components/ContainerDetailsSubheader.react.js b/src/components/ContainerDetailsSubheader.react.js index 36e5fa25d3..11f92f35b3 100644 --- a/src/components/ContainerDetailsSubheader.react.js +++ b/src/components/ContainerDetailsSubheader.react.js @@ -54,6 +54,12 @@ var ContainerDetailsSubheader = React.createClass({ } return (this.props.container.State.Downloading || this.props.container.State.Restarting); }, + stopDisabled: function () { + if (!this.props.container) { + return false; + } + return (this.props.container.State.Downloading || this.props.container.State.ExitCode); + }, disableTerminal: function () { if (!this.props.container) { return false; @@ -103,6 +109,13 @@ var ContainerDetailsSubheader = React.createClass({ }); } }, + handleStop: function () { + if (!this.stopDisabled()) { + metrics.track('Stopped Container'); + ContainerStore.stop(this.props.container.Name, function () { + }); + } + }, handleTerminal: function () { if (!this.disableTerminal()) { metrics.track('Terminaled Into Container'); @@ -134,6 +147,14 @@ 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"); + }, handleItemMouseEnterTerminal: function () { var $action = $(this.getDOMNode()).find('.action .terminal'); $action.css("visibility", "visible"); @@ -151,6 +172,10 @@ var ContainerDetailsSubheader = React.createClass({ action: true, disabled: this.disableRestart() }); + var stopActionClass = classNames({ + action: true, + disabled: this.stopDisabled() + }); var terminalActionClass = classNames({ action: true, disabled: this.disableTerminal() @@ -181,6 +206,10 @@ var ContainerDetailsSubheader = React.createClass({
Restart +
+
+ Stop +
Terminal diff --git a/src/stores/ContainerStore.js b/src/stores/ContainerStore.js index 1f662a304a..0d24c12530 100644 --- a/src/stores/ContainerStore.js +++ b/src/stores/ContainerStore.js @@ -402,6 +402,19 @@ 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); + } + }); + }, remove: function (name, callback) { if (_placeholders[name]) { delete _placeholders[name]; diff --git a/styles/right-panel.less b/styles/right-panel.less index 384b3e8934..e36251045f 100644 --- a/styles/right-panel.less +++ b/styles/right-panel.less @@ -46,6 +46,10 @@ left: 2px; //left: -18px; } + &.stop { + left: 7px; + //left: -18px; + } &.terminal { left: -1px; //left: -30px; diff --git a/util/prepare.js b/util/prepare.js new file mode 100644 index 0000000000..2aa87fd0a3 --- /dev/null +++ b/util/prepare.js @@ -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; + } + } + } +}); diff --git a/util/preprocessor.js b/util/preprocessor.js index eb39f8b3f1..a284c1eacc 100644 --- a/util/preprocessor.js +++ b/util/preprocessor.js @@ -1,12 +1,14 @@ +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 = 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; } };