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({

Allocate a TTY for this container

Keep STDIN open even if not attached

+

Privileged mode

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} - + TCP UDP + {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) => {