diff --git a/Gruntfile.js b/Gruntfile.js
index 5905f45f1..f3a81cd42 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -29,6 +29,7 @@ module.exports = function (grunt) {
var OSX_FILENAME = OSX_OUT_X64 + '/' + OSX_APPNAME + '.app';
var LINUX_FILENAME = OSX_OUT + '/' + BASENAME + '_' + packagejson.version + '_amd64.deb';
+
var IS_WINDOWS = process.platform === 'win32';
var IS_LINUX = process.platform === 'linux';
@@ -144,7 +145,9 @@ module.exports = function (grunt) {
dest: 'build/'
}, {
cwd: 'node_modules/',
- src: Object.keys(packagejson.dependencies).map(function (dep) { return dep + '/**/*';}),
+ src: Object.keys(packagejson.dependencies).map(function (dep) {
+ return dep + '/**/*';
+ }),
dest: 'build/node_modules/',
expand: true
}]
@@ -384,7 +387,7 @@ module.exports = function (grunt) {
grunt.registerTask('default', ['newer:babel', 'less', 'newer:copy:dev', 'shell:electron', 'watchChokidar']);
if (!IS_WINDOWS && !IS_LINUX) {
- grunt.registerTask('release', ['clean:release', 'babel', 'less', 'copy:dev', 'electron', 'copy:osx', 'shell:sign', 'shell:zip', 'copy:windows', 'rcedit:exes', 'shell:linux_npm', 'electron-packager:osxlnx', 'electron-installer-debian:linux64', 'shell:linux_zip']);
+ grunt.registerTask('release', ['clean:release', 'babel', 'less', 'copy:dev', 'electron', 'copy:osx', 'shell:sign', 'shell:zip', 'copy:windows', 'rcedit:exes', 'compress', 'shell:linux_npm', 'electron-packager:osxlnx', 'electron-installer-debian:linux64', 'shell:linux_zip']);
}else if (IS_LINUX) {
if (linuxpackage) {
grunt.registerTask('release', ['clean:release', 'babel', 'less', 'copy:dev', 'shell:linux_npm', 'electron-packager:build', linuxpackage]);
diff --git a/MAINTAINERS b/MAINTAINERS
index 25cdb0fcc..fdb4bd546 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -32,7 +32,7 @@
[people.FrenchBen]
Name = "Ben French"
- Email = "me@frenchben.com"
+ Email = "frenchben@docker.com"
GitHub = "FrenchBen"
[people.jeffdm]
diff --git a/package.json b/package.json
index 2328e9336..94ec6f1f5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "Kitematic",
- "version": "0.10.2",
+ "version": "0.10.4",
"author": "Kitematic",
"description": "Simple Docker Container management for Mac OS X.",
"homepage": "https://kitematic.com/",
diff --git a/src/browser.js b/src/browser.js
index 2069c8022..7b81f3d54 100644
--- a/src/browser.js
+++ b/src/browser.js
@@ -41,7 +41,7 @@ app.on('ready', function () {
mainWindow.loadURL(path.normalize('file://' + path.join(__dirname, 'index.html')));
- app.on('activate-with-no-open-windows', function () {
+ app.on('activate', function () {
if (mainWindow) {
mainWindow.show();
}
diff --git a/src/components/ContainerHome.react.js b/src/components/ContainerHome.react.js
index 4da81d2d9..a92048137 100644
--- a/src/components/ContainerHome.react.js
+++ b/src/components/ContainerHome.react.js
@@ -52,12 +52,15 @@ var ContainerHome = React.createClass({
let body;
if (this.props.container.Error) {
let error = this.props.container.Error.message;
- console.log('Err: %o - %o', typeof error, error);
- if (error.indexOf('ETIMEDOUT') !== -1) {
- error = 'Timeout error - Try and restart your VM by running: \n"docker-machine restart default" in a terminal';
- }
- if (error.indexOf('ECONNREFUSED') !== -1) {
- error = 'Is your VM up and running? Check that "docker ps" works in a terminal.';
+ if (!error) {
+ error = this.props.container.Error;
+ } else {
+ if (error.indexOf('ETIMEDOUT') !== -1) {
+ error = 'Timeout error - Try and restart your VM by running: \n"docker-machine restart default" in a terminal';
+ }
+ if (error.indexOf('ECONNREFUSED') !== -1) {
+ error = 'Is your VM up and running? Check that "docker ps" works in a terminal.';
+ }
}
body = (
diff --git a/src/components/ContainerSettingsAdvanced.react.js b/src/components/ContainerSettingsAdvanced.react.js
index 6120498c3..08b8e351b 100644
--- a/src/components/ContainerSettingsAdvanced.react.js
+++ b/src/components/ContainerSettingsAdvanced.react.js
@@ -1,3 +1,4 @@
+import _ from 'underscore';
import React from 'react/addons';
import metrics from '../utils/MetricsUtil';
import ContainerUtil from '../utils/ContainerUtil';
@@ -11,10 +12,11 @@ var ContainerSettingsAdvanced = React.createClass({
},
getInitialState: function () {
- let [tty, openStdin] = ContainerUtil.mode(this.props.container) || [true, true];
+ let [tty, openStdin, privileged] = ContainerUtil.mode(this.props.container) || [true, true, false];
return {
tty: tty,
- openStdin: openStdin
+ openStdin: openStdin,
+ privileged: privileged
};
},
@@ -22,7 +24,9 @@ var ContainerSettingsAdvanced = React.createClass({
metrics.track('Saved Advanced Options');
let tty = this.state.tty;
let openStdin = this.state.openStdin;
- containerActions.update(this.props.container.Name, {Tty: tty, OpenStdin: openStdin});
+ let privileged = this.state.privileged;
+ let hostConfig = _.extend(this.props.container.HostConfig, {Privileged: privileged});
+ containerActions.update(this.props.container.Name, {Tty: tty, OpenStdin: openStdin, HostConfig: hostConfig});
},
handleChangeTty: function () {
@@ -37,6 +41,12 @@ var ContainerSettingsAdvanced = React.createClass({
});
},
+ handleChangePrivileged: function () {
+ this.setState({
+ privileged: !this.state.privileged
+ });
+ },
+
render: function () {
if (!this.props.container) {
return false;
@@ -49,6 +59,7 @@ var ContainerSettingsAdvanced = React.createClass({
Save
diff --git a/src/components/ContainerSettingsPorts.react.js b/src/components/ContainerSettingsPorts.react.js
index 83fbd198e..4a9359b45 100644
--- a/src/components/ContainerSettingsPorts.react.js
+++ b/src/components/ContainerSettingsPorts.react.js
@@ -5,6 +5,7 @@ import ContainerUtil from '../utils/ContainerUtil';
import containerActions from '../actions/ContainerActions';
import containerStore from '../stores/ContainerStore';
import metrics from '../utils/MetricsUtil';
+import docker from '../utils/DockerUtil';
import {webPorts} from '../utils/Util';
import {DropdownButton, MenuItem} from 'react-bootstrap';
@@ -13,42 +14,89 @@ var ContainerSettingsPorts = React.createClass({
router: React.PropTypes.func
},
getInitialState: function () {
+ var ports = ContainerUtil.ports(this.props.container);
+ var initialPorts = this.props.container.InitialPorts;
+ ports[''] = {
+ ip: docker.host,
+ url: '',
+ port: '',
+ portType: 'tcp',
+ error: null
+ };
return {
- ports: ContainerUtil.ports(this.props.container)
+ ports: ports,
+ initialPorts: initialPorts
};
},
handleViewLink: function (url) {
metrics.track('Opened In Browser', {
from: 'settings'
});
- shell.openExternal(url);
+ shell.openExternal('http://' + url);
},
- handleChangePort: function(key, e) {
- let ports = this.state.ports;
- let port = e.target.value;
-
- // save updated port
- ports[key] = _.extend(ports[key], {
- url: 'http://' + ports[key]['ip'] + ':' + port,
- port: port,
- error: null
- });
+ createEmptyPort: function (ports) {
+ ports[''] = {
+ ip: docker.host,
+ url: '',
+ port: '',
+ portType: 'tcp'
+ };
+ document.getElementById('portKey').value = '';
+ document.getElementById('portValue').value = '';
+ },
+ addPort: function () {
+ if (document.getElementById('portKey') != null){
+ var portKey = document.getElementById('portKey').value;
+ var portValue = document.getElementById('portValue').value;
+ var portTypeValue = document.getElementById('portType').textContent;
+ var ports = this.state.ports;
+ if (portKey !== '') {
+ ports[portKey] = {
+ ip: docker.host,
+ url: docker.host + ':' + portValue,
+ port: portValue,
+ portType: portTypeValue.trim(),
+ error: null
+ };
+ this.checkPort(ports, portKey, portKey);
+ if (ports[portKey].error === null) {
+ this.createEmptyPort(ports);
+ }
+ }
+ }
+ return ports;
+ },
+ handleAddPort: function (e) {
+ var ports = this.addPort();
+ this.setState({ports: ports});
+ metrics.track('Added Pending Port');
+ },
+ checkPort: function (ports, port, key) {
// basic validation, if number is integer, if its in range, if there
// is no collision with ports of other containers and also if there is no
// collision with ports for current container
const otherContainers = _.filter(_.values(containerStore.getState().containers), c => c.Name !== this.props.container.Name);
const otherPorts = _.flatten(otherContainers.map(container => {
- return _.values(container.NetworkSettings.Ports).map(hosts => hosts.map(host => {
- return {port: host.HostPort, name: container.Name}
- }));
+ try {
+ return _.values(container.NetworkSettings.Ports).map(hosts => hosts.map(host => {
+ return {port: host.HostPort, name: container.Name};
+ })
+ );
+ }catch (err) {
+
+ }
})).reduce((prev, pair) => {
- prev[pair.port] = pair.name;
+ try {
+ prev[pair.port] = pair.name;
+ }catch (err) {
+
+ }
return prev;
}, {});
const duplicates = _.filter(ports, (v, i) => {
- return (i != key && _.isEqual(v.port, port));
+ return (i !== key && _.isEqual(v.port, port));
});
if (!port.match(/^[0-9]+$/g)) {
@@ -56,12 +104,41 @@ var ContainerSettingsPorts = React.createClass({
} else if (port <= 0 || port > 65535) {
ports[key].error = 'Needs to be in range <1,65535>.';
} else if (otherPorts[port]) {
- ports[key].error = 'Collision with container "'+ otherPorts[port] +'"';
+ ports[key].error = 'Collision with container "' + otherPorts[port] + '"';
} else if (duplicates.length > 0) {
ports[key].error = 'Collision with another port in this container.';
- } else if (port == 22 || port == 2376) {
+ } else if (port === 22 || port === 2376) {
ports[key].error = 'Ports 22 and 2376 are reserved ports for Kitematic/Docker.';
}
+ },
+ handleChangePort: function (key, e) {
+ let ports = this.state.ports;
+ let port = e.target.value;
+ // save updated port
+ ports[key] = _.extend(ports[key], {
+ url: 'http://' + ports[key].ip + ':' + port,
+ port: port,
+ error: null
+ });
+ this.checkPort(ports, port, key);
+
+ this.setState({ports: ports});
+ },
+ handleChangePortKey: function (key, e) {
+ let ports = this.state.ports;
+ let portKey = e.target.value;
+
+ // save updated port
+ var currentPort = ports[key];
+
+ delete ports[key];
+ ports[portKey] = currentPort;
+
+ this.setState({ports: ports});
+ },
+ handleRemovePort: function (key, e) {
+ let ports = this.state.ports;
+ delete ports[key];
this.setState({ports: ports});
},
handleChangePortType: function (key, portType) {
@@ -77,19 +154,37 @@ var ContainerSettingsPorts = React.createClass({
});
this.setState({ports: ports});
},
+ isInitialPort: function (key, ports) {
+ for (var idx in ports) {
+ if (ports.hasOwnProperty(idx)) {
+ var p = idx.split('/');
+ if (p.length > 0) {
+ if (p[0] === key) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ },
handleSave: function () {
+ let ports = this.state.ports;
+ ports = this.addPort();
+ this.setState({ports: ports});
let exposedPorts = {};
- let portBindings = _.reduce(this.state.ports, (res, value, key) => {
- res[key + '/' + value.portType] = [{
- HostPort: value.port
- }];
- exposedPorts[key] = {};
+ let portBindings = _.reduce(ports, (res, value, key) => {
+ if (key !== '') {
+ res[key + '/' + value.portType] = [{
+ HostPort: value.port
+ }];
+ exposedPorts[key + '/' + value.portType] = {};
+ }
return res;
}, {});
let hostConfig = _.extend(this.props.container.HostConfig, {PortBindings: portBindings});
-
containerActions.update(this.props.container.Name, {ExposedPorts: exposedPorts, HostConfig: hostConfig});
+
},
render: function () {
if (!this.props.container) {
@@ -102,24 +197,37 @@ var ContainerSettingsPorts = React.createClass({
var key = pair[0];
var {ip, port, url, portType, error} = pair[1];
isValid = (error) ? false : isValid;
- let ipLink = (this.props.container.State.Running && !this.props.container.State.Paused && !this.props.container.State.ExitCode && !this.props.container.State.Restarting) ? ({ip}):({ip});
+ let ipLink = (this.props.container.State.Running && !this.props.container.State.Paused && !this.props.container.State.ExitCode && !this.props.container.State.Restarting) ? ({ip}) : ({ip});
+ var icon = '';
+ var portKey = '';
+ var portValue = '';
+ if (key === '') {
+ icon = | ;
+ portKey = ;
+ portValue = ;
+ }else {
+ if (this.isInitialPort(key, this.state.initialPorts)) {
+ icon = | ;
+ }else {
+ icon = | ;
+ }
+ portKey = ;
+ portValue = ;
+ }
return (
- {key} |
+ {portKey} |
{ipLink}:
-
+ {portValue}
|
-
+
|
+ {icon}
{error} |
);
diff --git a/src/utils/ContainerUtil.js b/src/utils/ContainerUtil.js
index a19576970..5d1d2014c 100644
--- a/src/utils/ContainerUtil.js
+++ b/src/utils/ContainerUtil.js
@@ -15,10 +15,11 @@ var ContainerUtil = {
// Provide Foreground options
mode: function (container) {
- if (!container || !container.Config) {
- return [true, true];
- }
- return [container.Config.Tty, container.Config.OpenStdin];
+ return [
+ (container && container.Config) ? container.Config.Tty : true,
+ (container && container.Config) ? container.Config.OpenStdin : true,
+ (container && container.HostConfig) ? container.HostConfig.Privileged : false
+ ];
},
// TODO: inject host here instead of requiring Docker
diff --git a/src/utils/DockerUtil.js b/src/utils/DockerUtil.js
index 7c6bd2fd5..40befff57 100644
--- a/src/utils/DockerUtil.js
+++ b/src/utils/DockerUtil.js
@@ -125,6 +125,7 @@ var DockerUtil = {
startContainer (name) {
let container = this.client.getContainer(name);
+
container.start((error) => {
if (error) {
containerServerActions.error({name, error});
@@ -190,6 +191,14 @@ var DockerUtil = {
containerServerActions.error({name: id, error});
} else {
container.Name = container.Name.replace('/', '');
+ this.client.getImage(container.Image).inspect((error, image) => {
+ if (error) {
+ containerServerActions.error({name, error});
+ return;
+ }
+ container.InitialPorts = image.Config.ExposedPorts;
+ });
+
containerServerActions.updated({container});
}
});
@@ -213,6 +222,13 @@ var DockerUtil = {
this.imagesUsed.push(imgSha);
}
container.Name = container.Name.replace('/', '');
+ this.client.getImage(container.Image).inspect((error, image) => {
+ if (error) {
+ containerServerActions.error({name, error});
+ return;
+ }
+ container.InitialPorts = image.Config.ExposedPorts;
+ });
callback(null, container);
});
}, (err, containers) => {