From 7ec68bfe7df68ecc57c0250d49badcf853789c5e Mon Sep 17 00:00:00 2001 From: Mika Andrianarijaona Date: Thu, 19 Nov 2015 11:44:51 +0300 Subject: [PATCH 01/49] Cmd + 0 to reopen closed window --- src/menutemplate.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/menutemplate.js b/src/menutemplate.js index 5bc23a30e4..fe1f5abc3c 100644 --- a/src/menutemplate.js +++ b/src/menutemplate.js @@ -158,7 +158,17 @@ var MenuTemplate = function () { { label: 'Bring All to Front', selector: 'arrangeInFront:' - } + }, + { + type: 'separator' + }, + { + label: 'Kitematic', + accelerator: util.CommandOrCtrl() + '+0', + click: function () { + remote.getCurrentWindow().show(); + } + }, ] }, { From de0b2efd2afa6594baa0e536ea601bad6fb6c5a5 Mon Sep 17 00:00:00 2001 From: Guillaume Hain Date: Sat, 20 Jun 2015 12:28:00 +0200 Subject: [PATCH 02/49] Implement Linux support --- Gruntfile.js | 20 ++++++++++++++- README.md | 2 +- src/app.js | 4 ++- src/components/Preferences.react.js | 18 ++++++++++--- src/components/Setup.react.js | 29 ++++++++++++++++++++- src/utils/DockerMachineUtil.js | 5 ++++ src/utils/DockerUtil.js | 40 +++++++++++++++++++---------- src/utils/SetupUtil.js | 28 ++++++++++++++++++-- src/utils/Util.js | 16 ++++++++++++ styles/variables.less | 4 +-- 10 files changed, 141 insertions(+), 25 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index ee2afeadc6..5b93ba7e1a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -22,6 +22,7 @@ module.exports = function (grunt) { var BASENAME = 'Kitematic'; var OSX_APPNAME = BASENAME + ' (Beta)'; var WINDOWS_APPNAME = BASENAME + ' (Alpha)'; + var LINUX_APPNAME = BASENAME + ' (Alpha)'; var OSX_OUT = './dist'; var OSX_OUT_X64 = OSX_OUT + '/' + OSX_APPNAME + '-darwin-x64'; var OSX_FILENAME = OSX_OUT_X64 + '/' + OSX_APPNAME + '.app'; @@ -57,6 +58,19 @@ module.exports = function (grunt) { 'app-bundle-id': 'com.kitematic.kitematic', 'app-version': packagejson.version } + }, + linux: { + options: { + name: LINUX_APPNAME, + dir: 'build/', + out: 'dist/linux/', + version: packagejson['electron-version'], + platform: 'linux', + arch: 'x64', + asar: true, + 'app-bundle-id': 'com.kitematic.kitematic', + 'app-version': packagejson.version + } } }, @@ -243,7 +257,11 @@ module.exports = function (grunt) { }); grunt.registerTask('default', ['newer:babel', 'less', 'newer:copy:dev', 'shell:electron', 'watchChokidar']); - grunt.registerTask('release', ['clean:release', 'babel', 'less', 'copy:dev', 'electron', 'copy:osx', 'shell:sign', 'shell:zip', 'copy:windows', 'rcedit:exes', 'compress']); + if(process.platform === 'linux') { + grunt.registerTask('release', ['clean:release', 'babel', 'less', 'copy:dev', 'electron:linux']); + } else { + grunt.registerTask('release', ['clean:release', 'babel', 'less', 'copy:dev', 'electron', 'copy:osx', 'shell:sign', 'shell:zip', 'copy:windows', 'rcedit:exes', 'compress']); + } process.on('SIGINT', function () { grunt.task.run(['shell:electron:kill']); diff --git a/README.md b/README.md index 6939a181d6..52c9f1ec20 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Kitematic Logo](https://cloud.githubusercontent.com/assets/251292/5269258/1b229c3c-7a2f-11e4-96f1-e7baf3c86d73.png)](https://kitematic.com) -Kitematic is a simple application for managing Docker containers on Mac and Windows. +Kitematic is a simple application for managing Docker containers on Mac, Linux and Windows. ![Kitematic Screenshot](https://cloud.githubusercontent.com/assets/251292/8246120/d3ab271a-15ed-11e5-8736-9a730a27c79a.png) diff --git a/src/app.js b/src/app.js index 45f46a4d54..360556f8e5 100644 --- a/src/app.js +++ b/src/app.js @@ -17,6 +17,7 @@ import Router from 'react-router'; import routes from './routes'; import routerContainer from './router'; import repositoryActions from './actions/RepositoryActions'; +import util from './utils/Util'; var app = remote.require('app'); hubUtil.init(); @@ -46,7 +47,8 @@ var router = Router.create({ router.run(Handler => React.render(, document.body)); routerContainer.set(router); -setupUtil.setup().then(() => { +let setup = util.isLinux() ? setupUtil.nativeSetup : setupUtil.nonNativeSetup; +setup().then(() => { Menu.setApplicationMenu(Menu.buildFromTemplate(template())); docker.init(); if (!hub.prompted() && !hub.loggedin()) { diff --git a/src/components/Preferences.react.js b/src/components/Preferences.react.js index 2326d11d7b..7884b72714 100644 --- a/src/components/Preferences.react.js +++ b/src/components/Preferences.react.js @@ -35,10 +35,11 @@ var Preferences = React.createClass({ }); }, render: function () { - return ( -
-
- Go Back + var vmSettings; + + if (process.platform !== 'linux') { + vmSettings = ( +
VM Settings
@@ -48,6 +49,15 @@ var Preferences = React.createClass({
+
+ ); + } + + return ( +
+
+ Go Back + {vmSettings}
App Settings
diff --git a/src/components/Setup.react.js b/src/components/Setup.react.js index eb44736a5b..741322e174 100644 --- a/src/components/Setup.react.js +++ b/src/components/Setup.react.js @@ -3,6 +3,7 @@ import Router from 'react-router'; import Radial from './Radial.react.js'; import RetinaImage from 'react-retina-image'; import Header from './Header.react'; +import Util from '../utils/Util'; import metrics from '../utils/MetricsUtil'; import setupStore from '../stores/SetupStore'; import setupActions from '../actions/SetupActions'; @@ -43,6 +44,13 @@ var Setup = React.createClass({ shell.openExternal('https://www.docker.com/docker-toolbox'); }, + handleLinuxDockerInstall: function () { + metrics.track('Opening Linux Docker installation instructions', { + from: 'setup' + }); + shell.openExternal('http://docs.docker.com/linux/started/'); + }, + renderContents: function () { return (
@@ -74,6 +82,25 @@ var Setup = React.createClass({ }, renderError: function () { + let deleteVmAndRetry; + + if (Util.isLinux()) { + if (!this.state.started) { + deleteVmAndRetry = ( + + ); + } + } else { + if (this.state.started) { + deleteVmAndRetry = ( + + ); + } else { + deleteVmAndRetry = ( + + ); + } + } return (
@@ -93,7 +120,7 @@ var Setup = React.createClass({

{this.state.error.message || this.state.error}

- {this.state.started ? : } + {{deleteVmAndRetry}}

diff --git a/src/utils/DockerMachineUtil.js b/src/utils/DockerMachineUtil.js index 283b4cee85..fa22e37eab 100644 --- a/src/utils/DockerMachineUtil.js +++ b/src/utils/DockerMachineUtil.js @@ -151,6 +151,11 @@ var DockerMachine = { } }); }); + } else if (util.isLinux()) { + cmd = cmd || process.env.SHELL; + var terminal = util.linuxTerminal(); + if (terminal) + util.exec(terminal.concat([cmd])).then(() => {}); } else { cmd = cmd || process.env.SHELL; this.url(machineName).then(machineUrl => { diff --git a/src/utils/DockerUtil.js b/src/utils/DockerUtil.js index 7091482feb..1eede0525e 100644 --- a/src/utils/DockerUtil.js +++ b/src/utils/DockerUtil.js @@ -3,6 +3,7 @@ import fs from 'fs'; import path from 'path'; import dockerode from 'dockerode'; import _ from 'underscore'; +import child_process from 'child_process'; import util from './Util'; import hubUtil from './HubUtil'; import metrics from '../utils/MetricsUtil'; @@ -20,20 +21,25 @@ export default { throw new Error('Falsy ip or name passed to docker client setup'); } - let certDir = path.join(util.home(), '.docker/machine/machines/', name); - if (!fs.existsSync(certDir)) { - throw new Error('Certificate directory does not exist'); - } + if (util.isLinux()) { + this.host = 'localhost'; + this.client = new dockerode({socketPath: '/var/run/docker.sock'}); + } else { + let certDir = path.join(util.home(), '.docker/machine/machines/', name); + if (!fs.existsSync(certDir)) { + throw new Error('Certificate directory does not exist'); + } - this.host = ip; - this.client = new dockerode({ - protocol: 'https', - host: ip, - port: 2376, - ca: fs.readFileSync(path.join(certDir, 'ca.pem')), - cert: fs.readFileSync(path.join(certDir, 'cert.pem')), - key: fs.readFileSync(path.join(certDir, 'key.pem')) - }); + this.host = ip; + this.client = new dockerode({ + protocol: 'https', + host: ip, + port: 2376, + ca: fs.readFileSync(path.join(certDir, 'ca.pem')), + cert: fs.readFileSync(path.join(certDir, 'cert.pem')), + key: fs.readFileSync(path.join(certDir, 'key.pem')) + }); + } }, init () { @@ -66,6 +72,14 @@ export default { }); }, + isDockerRunning () { + try { + child_process.execSync('ps ax | grep "docker daemon" | grep -v grep'); + } catch (error) { + throw new Error('Cannot connect to the Docker daemon. The daemon is not running.'); + } + }, + startContainer (name, containerData) { let startopts = { Binds: containerData.Binds || [] diff --git a/src/utils/SetupUtil.js b/src/utils/SetupUtil.js index b0cf1ba0d6..cf9b6ac43f 100644 --- a/src/utils/SetupUtil.js +++ b/src/utils/SetupUtil.js @@ -2,8 +2,8 @@ import _ from 'underscore'; import fs from 'fs'; import path from 'path'; import Promise from 'bluebird'; -import util from './Util'; import bugsnag from 'bugsnag-js'; +import util from './Util'; import virtualBox from './VirtualBoxUtil'; import setupServerActions from '../actions/SetupServerActions'; import metrics from './MetricsUtil'; @@ -51,7 +51,31 @@ export default { return _retryPromise.promise; }, - async setup () { + async nativeSetup () { + while (true) { + try { + docker.setup('localhost', machine.name()); + docker.isDockerRunning(); + + break; + } catch (error) { + router.get().transitionTo('setup'); + metrics.track('Native Setup Failed'); + setupServerActions.error({error}); + + let message = error.message.split('\n'); + let lastLine = message.length > 1 ? message[message.length - 2] : 'Docker Machine encountered an error.'; + bugsnag.notify('Native Setup Failed', lastLine, { + 'Docker Machine Logs': error.message + }, 'info'); + + this.clearTimers(); + await this.pause(); + } + } + }, + + async nonNativeSetup () { let virtualBoxVersion = null; let machineVersion = null; while (true) { diff --git a/src/utils/Util.js b/src/utils/Util.js index 9950ca6ec1..a0671a57ad 100644 --- a/src/utils/Util.js +++ b/src/utils/Util.js @@ -5,6 +5,7 @@ import fs from 'fs'; import path from 'path'; import crypto from 'crypto'; import remote from 'remote'; +var dialog = remote.require('dialog'); var app = remote.require('app'); module.exports = { @@ -34,6 +35,9 @@ module.exports = { isWindows: function () { return process.platform === 'win32'; }, + isLinux: function () { + return process.platform === 'linux'; + }, binsPath: function () { return this.isWindows() ? path.join(this.home(), 'Kitematic-bins') : path.join('/usr/local/bin'); }, @@ -156,5 +160,17 @@ module.exports = { linuxToWindowsPath: function (linuxAbsPath) { return linuxAbsPath.replace('/c', 'C:').split('/').join('\\'); }, + linuxTerminal: function () { + if (fs.existsSync('/usr/bin/x-terminal-emulator')) { + return ['/usr/bin/x-terminal-emulator', '-e']; + } else { + dialog.showMessageBox({ + type: 'warning', + buttons: ['OK'], + message: 'The terminal emulator symbolic link doesn\'t exists. Please read the Wiki at https://github.com/kitematic/kitematic/wiki/Common-Issues-and-Fixes#early-linux-support-from-zedtux.' + }); + return; + } + }, webPorts: ['80', '8000', '8080', '8888', '3000', '5000', '2368', '9200', '8983'] }; diff --git a/styles/variables.less b/styles/variables.less index a98c2fe98d..9e873d583d 100644 --- a/styles/variables.less +++ b/styles/variables.less @@ -23,8 +23,8 @@ @color-box-button: lighten(@gray-lightest, 5%); @color-background: lighten(@gray-lightest, 4.5%); -@font-regular: "Helvetica Neue", Segoe UI, Arial, "Lucida Grande", sans-serif; -@font-code: Menlo, Consolas; +@font-regular: "Helvetica Neue", Segoe UI, "Ubuntu", Arial, "Lucida Grande", sans-serif; +@font-code: Menlo, Consolas, "DejaVu Sans Mono"; @border-radius: 0.2rem; From 80f01815a8668076392da894fa5279a029eee80b Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Tue, 24 Nov 2015 15:28:59 -0500 Subject: [PATCH 03/49] Travis: run release-mac Signed-off-by: Jeffrey Morgan --- ci/release | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/release b/ci/release index 98c03bd338..4ab8348ace 100755 --- a/ci/release +++ b/ci/release @@ -2,4 +2,4 @@ echo $MAC_KEY_CONTENT > mac_key_content.hex && xxd -p -r mac_key_content.hex ~/L security unlock-keychain -p "$MAC_KEY_SECRET" ~/Library/Keychains/keychain.keychain security default-keychain -s keychain.keychain security list-keychains -npm run release +npm run release-mac From d804a20527fc156018213f81f61d1e7d199c7c76 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Tue, 24 Nov 2015 15:41:32 -0500 Subject: [PATCH 04/49] Travis: add linux build Signed-off-by: Jeffrey Morgan --- .travis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 12878978ce..ffc629d48a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,8 +11,10 @@ before_install: script: - npm install - npm test - - '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && npm run integration || false' - - '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && ./ci/release || false' + - '[ "${TRAVIS_OS_NAME}" = "linux" ] && npm test || false' + - '[ "${TRAVIS_OS_NAME}" = "linux" && "${TRAVIS_PULL_REQUEST}" = "false" ] && npm run integration || false' + - '[ "${TRAVIS_OS_NAME}" = "osx" && "${TRAVIS_PULL_REQUEST}" = "false" ] && ./ci/release || false' os: - osx + - linux From f03f307b5787fa9352e8b2f1733da7c77dfa1533 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Tue, 24 Nov 2015 15:45:08 -0500 Subject: [PATCH 05/49] Travis: use build matrix for dual os --- .travis.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index ffc629d48a..434c6e7bd3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ cache: directories: - - resources - node_modules before_install: @@ -10,11 +9,15 @@ before_install: script: - npm install - - npm test - '[ "${TRAVIS_OS_NAME}" = "linux" ] && npm test || false' - '[ "${TRAVIS_OS_NAME}" = "linux" && "${TRAVIS_PULL_REQUEST}" = "false" ] && npm run integration || false' - '[ "${TRAVIS_OS_NAME}" = "osx" && "${TRAVIS_PULL_REQUEST}" = "false" ] && ./ci/release || false' -os: - - osx - - linux +matrix: + include: + - os: linux + services: + - docker + - os: osx + language: generic + From 179f437c35f61fcc1e0fc19b65bc2db1c8b08b09 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Tue, 24 Nov 2015 15:55:19 -0500 Subject: [PATCH 06/49] Travis: fix node version Signed-off-by: Jeffrey Morgan --- .travis.yml | 14 +++++++------- ci/install | 7 +++++++ 2 files changed, 14 insertions(+), 7 deletions(-) create mode 100755 ci/install diff --git a/.travis.yml b/.travis.yml index 434c6e7bd3..077a5968b8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,22 +2,22 @@ cache: directories: - node_modules -before_install: - - brew unlink node - - brew update - - brew install homebrew/versions/node4-lts +install: ./ci/install script: - npm install - '[ "${TRAVIS_OS_NAME}" = "linux" ] && npm test || false' - - '[ "${TRAVIS_OS_NAME}" = "linux" && "${TRAVIS_PULL_REQUEST}" = "false" ] && npm run integration || false' - - '[ "${TRAVIS_OS_NAME}" = "osx" && "${TRAVIS_PULL_REQUEST}" = "false" ] && ./ci/release || false' + - '[ "${TRAVIS_OS_NAME}" = "linux" ] && [ "${TRAVIS_PULL_REQUEST}" = "false" ] && npm run integration || false' + - '[ "${TRAVIS_OS_NAME}" = "osx" ] && [ "${TRAVIS_PULL_REQUEST}" = "false" ] && ./ci/release-mac || false' matrix: include: - os: linux services: - - docker + - docker + language: node_js + node_js: + - "4.1" - os: osx language: generic diff --git a/ci/install b/ci/install new file mode 100755 index 0000000000..0851c751f6 --- /dev/null +++ b/ci/install @@ -0,0 +1,7 @@ +#!/bin/bash + +if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then + brew unlink node + brew update + brew install homebrew/versions/node4-lts +fi From 30118cfd7fe59f06af003b8d733412ab74ab5583 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Tue, 24 Nov 2015 16:19:58 -0500 Subject: [PATCH 07/49] Travis: release script Signed-off-by: Jeffrey Morgan --- .travis.yml | 18 +++++++++--------- ci/release | 17 ++++++++++++----- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index 077a5968b8..084e7799b1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,18 +6,18 @@ install: ./ci/install script: - npm install - - '[ "${TRAVIS_OS_NAME}" = "linux" ] && npm test || false' - - '[ "${TRAVIS_OS_NAME}" = "linux" ] && [ "${TRAVIS_PULL_REQUEST}" = "false" ] && npm run integration || false' - - '[ "${TRAVIS_OS_NAME}" = "osx" ] && [ "${TRAVIS_PULL_REQUEST}" = "false" ] && ./ci/release-mac || false' + - npm test + - '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && npm run integration || false' + - '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && ./ci/release || false' matrix: include: - - os: linux - services: - - docker - language: node_js - node_js: - - "4.1" + - os: linux + services: + - docker + language: node_js + node_js: + - "4.1" - os: osx language: generic diff --git a/ci/release b/ci/release index 4ab8348ace..71d763e80f 100755 --- a/ci/release +++ b/ci/release @@ -1,5 +1,12 @@ -echo $MAC_KEY_CONTENT > mac_key_content.hex && xxd -p -r mac_key_content.hex ~/Library/Keychains/keychain.keychain && rm mac_key_content.hex -security unlock-keychain -p "$MAC_KEY_SECRET" ~/Library/Keychains/keychain.keychain -security default-keychain -s keychain.keychain -security list-keychains -npm run release-mac +#!/bin/bash + +if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then + echo $MAC_KEY_CONTENT > mac_key_content.hex && xxd -p -r mac_key_content.hex ~/Library/Keychains/keychain.keychain && rm mac_key_content.hex + security unlock-keychain -p "$MAC_KEY_SECRET" ~/Library/Keychains/keychain.keychain + security default-keychain -s keychain.keychain + security list-keychains + npm run release-mac +else + echo "Skipped for now" +fi + From 637306b03c466d75a087ee5aba8b40b3574cecaf Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Tue, 24 Nov 2015 16:26:08 -0500 Subject: [PATCH 08/49] Remove circle.yml Signed-off-by: Jeffrey Morgan --- circle.yml | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 circle.yml diff --git a/circle.yml b/circle.yml deleted file mode 100644 index a6cf065bc2..0000000000 --- a/circle.yml +++ /dev/null @@ -1,3 +0,0 @@ -machine: - node: - version: 4.1.2 From dcb4fbf5a9503e9c43af368cb395e40035b9241b Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Tue, 24 Nov 2015 16:27:55 -0500 Subject: [PATCH 09/49] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6939a181d6..5c58342740 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://travis-ci.org/kitematic/kitematic.svg?branch=master)](https://travis-ci.org/kitematic/kitematic) +[![Build Status](https://travis-ci.org/docker/kitematic.svg?branch=master)](https://travis-ci.org/kitematic/kitematic) [![Kitematic Logo](https://cloud.githubusercontent.com/assets/251292/5269258/1b229c3c-7a2f-11e4-96f1-e7baf3c86d73.png)](https://kitematic.com) From dbdbee24cc84ef7b6577791abe90cab7ca7a97e3 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Tue, 24 Nov 2015 16:29:29 -0500 Subject: [PATCH 10/49] Travis: install on new line Signed-off-by: Jeffrey Morgan --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 084e7799b1..05b31a7af1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,8 @@ cache: directories: - node_modules -install: ./ci/install +install: + - ./ci/install script: - npm install From 94a55bc87ea827120b79141ac7461eb8dd7d920d Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Tue, 24 Nov 2015 16:33:53 -0500 Subject: [PATCH 11/49] Travis: lint .travis.yml Signed-off-by: Jeffrey Morgan --- .travis.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 05b31a7af1..240b51fd95 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,12 +13,12 @@ script: matrix: include: - - os: linux - services: - - docker - language: node_js - node_js: - - "4.1" + - os: linux + services: + - docker + language: node_js + node_js: + - "4.1" - os: osx language: generic From f50a846d2692e96807ff907e332c1392af2b566f Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Tue, 24 Nov 2015 16:36:50 -0500 Subject: [PATCH 12/49] Travis: install wine on linux Signed-off-by: Jeffrey Morgan --- ci/install | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ci/install b/ci/install index 0851c751f6..a289c67cfc 100755 --- a/ci/install +++ b/ci/install @@ -1,7 +1,9 @@ #!/bin/bash if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - brew unlink node - brew update - brew install homebrew/versions/node4-lts + brew unlink node + brew update + brew install homebrew/versions/node4-lts +else + apt-get install wine fi From 53ef34a13dd03aa7b81e1b5b8e6cc6d4cccbea69 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Tue, 24 Nov 2015 16:39:02 -0500 Subject: [PATCH 13/49] Travis: add sudo to apt-get Signed-off-by: Jeffrey Morgan --- ci/install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/install b/ci/install index a289c67cfc..b1598e70d7 100755 --- a/ci/install +++ b/ci/install @@ -5,5 +5,5 @@ if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update brew install homebrew/versions/node4-lts else - apt-get install wine + sudo apt-get install wine fi From c9d94f99014e7488b49c1e1ba999abcb2eb81d63 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Tue, 24 Nov 2015 16:41:55 -0500 Subject: [PATCH 14/49] Travis: apt-get update Signed-off-by: Jeffrey Morgan --- ci/install | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/install b/ci/install index b1598e70d7..fa3b6f9551 100755 --- a/ci/install +++ b/ci/install @@ -5,5 +5,6 @@ if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update brew install homebrew/versions/node4-lts else + sudo apt-get update sudo apt-get install wine fi From 99c43f8dfb6dcd6a7956fb956f0d8ee6a728b3c4 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Tue, 24 Nov 2015 16:45:38 -0500 Subject: [PATCH 15/49] Travis: build only on mac Signed-off-by: Jeffrey Morgan --- .travis.yml | 23 +++++++---------------- ci/release | 17 ++++++++--------- 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/.travis.yml b/.travis.yml index 240b51fd95..a68ee30de5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,24 +1,15 @@ -cache: - directories: - - node_modules - install: - - ./ci/install + - brew unlink node + - brew update + - brew install homebrew/versions/node4-lts script: - npm install - npm test - '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && npm run integration || false' + +after_script: - '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && ./ci/release || false' -matrix: - include: - - os: linux - services: - - docker - language: node_js - node_js: - - "4.1" - - os: osx - language: generic - +os: + - osx diff --git a/ci/release b/ci/release index 71d763e80f..f14ca6fb3a 100755 --- a/ci/release +++ b/ci/release @@ -1,12 +1,11 @@ #!/bin/bash -if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - echo $MAC_KEY_CONTENT > mac_key_content.hex && xxd -p -r mac_key_content.hex ~/Library/Keychains/keychain.keychain && rm mac_key_content.hex - security unlock-keychain -p "$MAC_KEY_SECRET" ~/Library/Keychains/keychain.keychain - security default-keychain -s keychain.keychain - security list-keychains - npm run release-mac -else - echo "Skipped for now" -fi +echo $MAC_KEY_CONTENT > mac_key_content.hex && xxd -p -r mac_key_content.hex ~/Library/Keychains/keychain.keychain && rm mac_key_content.hex +security unlock-keychain -p "$MAC_KEY_SECRET" ~/Library/Keychains/keychain.keychain +security default-keychain -s keychain.keychain +security list-keychains + +# TODO: speed this up, it takes ~30 minutes +# brew install wine +npm run release-mac From ecd6b4bf17f998b304871455ee0a9059d6ddb6c7 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Tue, 24 Nov 2015 20:16:52 -0500 Subject: [PATCH 16/49] Delete .jshintrc --- .jshintrc | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 .jshintrc diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 9240a3ae9f..0000000000 --- a/.jshintrc +++ /dev/null @@ -1,31 +0,0 @@ -{ - "curly": true, - "noempty": true, - "newcap": true, - "eqeqeq": true, - "eqnull": true, - "esnext": true, - "undef": true, - "unused": true, - "devel": true, - "node": true, - "browser": true, - "evil": false, - "latedef": true, - "nonew": true, - "trailing": true, - "immed": true, - "smarttabs": true, - "strict": false, - "quotmark": false, - "nonbsp": true, - "noempty": true, - "camelcase": false, - "jasmine": true, - "globals": { - "define": true, - "jest": true, - "pit": true - }, - "predef": [ "-Promise" ] -} From 0fed4fd0e59e854a299b6e2f53bd5383a36ce32a Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Wed, 25 Nov 2015 11:14:08 +1000 Subject: [PATCH 17/49] Docs validation checker fixes Signed-off-by: Sven Dowideit --- docs/minecraft-server.md | 2 +- docs/nginx-web-server.md | 2 +- docs/rethinkdb-dev-database.md | 2 +- docs/userguide.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/minecraft-server.md b/docs/minecraft-server.md index 4456e0993f..2c4d089bae 100755 --- a/docs/minecraft-server.md +++ b/docs/minecraft-server.md @@ -17,7 +17,7 @@ using Kitematic and Docker. ### Create Minecraft Server Container First, if you haven't yet done so, [download and start -Kitematic](/). Once installed and running, the app should look like this: +Kitematic](index.md). Once installed and running, the app should look like this: Create a container from the recommended Minecraft image by clicking the "Create" button. diff --git a/docs/nginx-web-server.md b/docs/nginx-web-server.md index dd9e6599a9..7121c4fd6f 100755 --- a/docs/nginx-web-server.md +++ b/docs/nginx-web-server.md @@ -25,7 +25,7 @@ Let's get to it! #### Running the Nginx Web Server Container First, if you haven't yet done so, [download and start -Kitematic](/). Once installed and running, the app should look like this: +Kitematic](index.md). Once installed and running, the app should look like this: ![Nginx create](images/nginx-create.png) diff --git a/docs/rethinkdb-dev-database.md b/docs/rethinkdb-dev-database.md index c5546274cb..620cb8f189 100755 --- a/docs/rethinkdb-dev-database.md +++ b/docs/rethinkdb-dev-database.md @@ -19,7 +19,7 @@ In this tutorial, you will: ### Setting up RethinkDB in Kitematic First, if you haven't yet done so, [download and start -Kitematic](/). Once open, the app should look like +Kitematic](index.md). Once open, the app should look like this: ![Rethink create button](images/rethink-create.png) diff --git a/docs/userguide.md b/docs/userguide.md index 698b9f8bf3..4943e45562 100755 --- a/docs/userguide.md +++ b/docs/userguide.md @@ -30,7 +30,7 @@ stream logs, and single click terminal into your Docker container all from the GUI. First, if you haven't yet done so, [download and start -Kitematic](/). +Kitematic](index.md). ## Container list From b1fcc307cc08d24681d913c84a67f33fbafd53e0 Mon Sep 17 00:00:00 2001 From: Mika Andrianarijaona Date: Wed, 25 Nov 2015 09:29:36 +0300 Subject: [PATCH 18/49] Changed accelerator --- src/menutemplate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/menutemplate.js b/src/menutemplate.js index fe1f5abc3c..043d89239f 100644 --- a/src/menutemplate.js +++ b/src/menutemplate.js @@ -164,7 +164,7 @@ var MenuTemplate = function () { }, { label: 'Kitematic', - accelerator: util.CommandOrCtrl() + '+0', + accelerator: 'Cmd+0', click: function () { remote.getCurrentWindow().show(); } From 604ed015dc08e007eab14fbfa3cfb20f0688c8b3 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Wed, 25 Nov 2015 17:06:44 -0500 Subject: [PATCH 19/49] CI: Remove unused install Signed-off-by: Jeffrey Morgan --- ci/install | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100755 ci/install diff --git a/ci/install b/ci/install deleted file mode 100755 index fa3b6f9551..0000000000 --- a/ci/install +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - brew unlink node - brew update - brew install homebrew/versions/node4-lts -else - sudo apt-get update - sudo apt-get install wine -fi From 8975f8e61be56b92155791d4619fb807edcc283b Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Wed, 25 Nov 2015 17:08:08 -0500 Subject: [PATCH 20/49] CI: only use travis for tests Signed-off-by: Jeffrey Morgan --- .travis.yml | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index a68ee30de5..8228cfe757 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,8 @@ -install: - - brew unlink node - - brew update - - brew install homebrew/versions/node4-lts +language: node_js +node_js: + - "4.1" script: - npm install - npm test - - '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && npm run integration || false' -after_script: - - '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && ./ci/release || false' - -os: - - osx From b43fc78a6399c587ee7ae62f26b793daeb024945 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Wed, 25 Nov 2015 17:26:03 -0500 Subject: [PATCH 21/49] CI: Fix circleci Signed-off-by: Jeffrey Morgan --- circle.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 circle.yml diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000000..ceaa3ca553 --- /dev/null +++ b/circle.yml @@ -0,0 +1,6 @@ +machine: + xcode: + version: "7.0" + +cache_directories: + - node_modules From 4b888a2d87cc04718ad72a47cb8c4138734a0e4e Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Wed, 25 Nov 2015 17:28:42 -0500 Subject: [PATCH 22/49] CI: Travis sudo: false Signed-off-by: Jeffrey Morgan --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 8228cfe757..a416947675 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,5 @@ +sudo: false + language: node_js node_js: - "4.1" From 332cf7374717948ad4435d35c1707d93f51d332e Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Wed, 25 Nov 2015 17:43:50 -0500 Subject: [PATCH 23/49] Travis: cache Signed-off-by: Jeffrey Morgan --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index a416947675..49d2dc07ee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,10 @@ language: node_js node_js: - "4.1" +cache: + directories: + - node_modules + script: - npm install - npm test From 15b2891a036fe8f0e0b5f2f313bfb48fbf6a9230 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Wed, 25 Nov 2015 18:07:48 -0500 Subject: [PATCH 24/49] CI: Remove unused sign script Signed-off-by: Jeffrey Morgan --- ci/release | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100755 ci/release diff --git a/ci/release b/ci/release deleted file mode 100755 index f14ca6fb3a..0000000000 --- a/ci/release +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -echo $MAC_KEY_CONTENT > mac_key_content.hex && xxd -p -r mac_key_content.hex ~/Library/Keychains/keychain.keychain && rm mac_key_content.hex -security unlock-keychain -p "$MAC_KEY_SECRET" ~/Library/Keychains/keychain.keychain -security default-keychain -s keychain.keychain -security list-keychains - -# TODO: speed this up, it takes ~30 minutes -# brew install wine -npm run release-mac - From c5c9fc34bc7f839bc8e333e92728a8c8bd37bcbe Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Wed, 25 Nov 2015 18:13:01 -0500 Subject: [PATCH 25/49] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5c58342740..a8bdad8554 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://travis-ci.org/docker/kitematic.svg?branch=master)](https://travis-ci.org/kitematic/kitematic) +[![Build Status](https://travis-ci.org/docker/kitematic.svg?branch=master)](https://travis-ci.org/docker/kitematic) [![Kitematic Logo](https://cloud.githubusercontent.com/assets/251292/5269258/1b229c3c-7a2f-11e4-96f1-e7baf3c86d73.png)](https://kitematic.com) From 65cea9e82a0238cd57021ba82a9583d16f0ed8be Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Wed, 25 Nov 2015 18:23:19 -0500 Subject: [PATCH 26/49] CI: proper circle cache syntax Signed-off-by: Jeffrey Morgan --- circle.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/circle.yml b/circle.yml index ceaa3ca553..ea6f753291 100644 --- a/circle.yml +++ b/circle.yml @@ -2,5 +2,6 @@ machine: xcode: version: "7.0" -cache_directories: - - node_modules +dependencies: + cache_directories: + - node_modules From 241b354c662722fbe3e828e9a81bb0732dc908ec Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Wed, 25 Nov 2015 18:38:57 -0500 Subject: [PATCH 27/49] CI: cache cellar Signed-off-by: Jeffrey Morgan --- circle.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/circle.yml b/circle.yml index ea6f753291..1977fcf45c 100644 --- a/circle.yml +++ b/circle.yml @@ -5,3 +5,4 @@ machine: dependencies: cache_directories: - node_modules + - /usr/local/Cellar From 252049115e6ad64936d2dd867bdc93e01707680f Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Wed, 25 Nov 2015 18:45:24 -0500 Subject: [PATCH 28/49] Put zip files in release dir Signed-off-by: Jeffrey Morgan --- .gitignore | 1 + Gruntfile.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 7de51eecbc..b2eb68a055 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .swp build dist +release installer node_modules coverage diff --git a/Gruntfile.js b/Gruntfile.js index d4f3e0afa4..dd2362df6a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -195,7 +195,7 @@ module.exports = function (grunt) { ].join(' && '), }, zip: { - command: 'ditto -c -k --sequesterRsrc --keepParent <%= OSX_FILENAME_ESCAPED %> dist/' + BASENAME + '-' + packagejson.version + '-Mac.zip', + command: 'ditto -c -k --sequesterRsrc --keepParent <%= OSX_FILENAME_ESCAPED %> release/' + BASENAME + '-' + packagejson.version + '-Mac.zip', } }, @@ -206,7 +206,7 @@ module.exports = function (grunt) { compress: { windows: { options: { - archive: './dist/' + BASENAME + '-' + packagejson.version + '-Windows-Alpha.zip', + archive: './release/' + BASENAME + '-' + packagejson.version + '-Windows-Alpha.zip', mode: 'zip' }, files: [{ From d8de60d612337cf58899d35e8b596a996b314f17 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Wed, 25 Nov 2015 18:52:18 -0500 Subject: [PATCH 29/49] Bump electron to 0.33.9 Signed-off-by: Jeffrey Morgan --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 7d057aad29..6bebc3836b 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "lint": "jsxhint src" }, "license": "Apache-2.0", - "electron-version": "0.33.6", + "electron-version": "0.33.9", "dependencies": { "alt": "^0.16.2", "ansi-to-html": "0.3.0", @@ -54,7 +54,7 @@ "devDependencies": { "babel": "^5.8.23", "babel-jest": "^5.2.0", - "electron-prebuilt": "^0.33.6", + "electron-prebuilt": "^0.33.9", "eslint": "^1.3.1", "eslint-plugin-react": "^3.3.0", "grunt": "^0.4.5", From ec92514207ef81340504ee457658dbee1aa4db2a Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Wed, 25 Nov 2015 19:26:59 -0500 Subject: [PATCH 30/49] CI: add quotes to circle yml Signed-off-by: Jeffrey Morgan --- circle.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/circle.yml b/circle.yml index 1977fcf45c..2343e14ab4 100644 --- a/circle.yml +++ b/circle.yml @@ -4,5 +4,5 @@ machine: dependencies: cache_directories: - - node_modules - - /usr/local/Cellar + - "node_modules" + - "/usr/local/Cellar" From adc1f170f5992e758ad3cb52c6e52d048b431232 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Wed, 25 Nov 2015 19:38:57 -0500 Subject: [PATCH 31/49] CI: do not cache homebrew Signed-off-by: Jeffrey Morgan --- circle.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/circle.yml b/circle.yml index 2343e14ab4..90f69e603f 100644 --- a/circle.yml +++ b/circle.yml @@ -5,4 +5,3 @@ machine: dependencies: cache_directories: - "node_modules" - - "/usr/local/Cellar" From 835dc9c0470c5c525e2f439906ae56b29f9d15ed Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Thu, 26 Nov 2015 20:17:43 +1000 Subject: [PATCH 32/49] Use FROM docs/base:latest again Signed-off-by: Sven Dowideit --- docs/Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/Dockerfile b/docs/Dockerfile index 72cc010377..b42ddbd82d 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -1,4 +1,4 @@ -FROM docs/base:hugo-github-linking +FROM docs/base:latest MAINTAINER Mary Anthony (@moxiegirl) RUN svn checkout https://github.com/docker/compose/trunk/docs /docs/content/compose @@ -9,7 +9,8 @@ RUN svn checkout https://github.com/docker/tutorials/trunk/docs /docs/content RUN svn checkout https://github.com/docker/opensource/trunk/docs /docs/content/opensource RUN svn checkout https://github.com/docker/machine/trunk/docs /docs/content/machine +ENV PROJECT=kitematic # To get the git info for this repo COPY . /src -COPY . /docs/content/kitematic/ +COPY . /docs/content/$PROJECT/ From d39b8235b96161d6532c2d4226eec9b1b0ca1164 Mon Sep 17 00:00:00 2001 From: French Ben Date: Mon, 23 Nov 2015 15:50:43 -0800 Subject: [PATCH 33/49] Add package option for devTools window on start Signed-off-by: French Ben --- package.json | 1 + src/browser.js | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/package.json b/package.json index 6bebc3836b..206b329fa2 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "bugs": "https://github.com/kitematic/kitematic/issues", "scripts": { "start": "grunt", + "start-dev": "NODE_ENV=development grunt", "test": "jest -c jest-unit.json", "integration": "jest -c jest-integration.json", "release": "grunt release", diff --git a/src/browser.js b/src/browser.js index 2ad5a38954..cdd788e1e5 100644 --- a/src/browser.js +++ b/src/browser.js @@ -53,6 +53,10 @@ app.on('ready', function () { show: false }); + if (process.env.NODE_ENV === 'development') { + mainWindow.openDevTools({detach: true}); + } + mainWindow.loadUrl(path.normalize('file://' + path.join(__dirname, 'index.html'))); app.on('activate-with-no-open-windows', function () { From 83b2bff85024f28a6ea35ec8a3863cde5e807052 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Mon, 9 Nov 2015 23:16:44 -0800 Subject: [PATCH 34/49] Improved log performance and reliability Signed-off-by: Jeffrey Morgan --- src/actions/ContainerActions.js | 4 + src/actions/ContainerServerActions.js | 4 +- src/components/ContainerHomeLogs.react.js | 80 ++++++------------- src/components/ContainerListItem.react.js | 2 +- src/components/ContainerLogs.react.js | 61 --------------- src/stores/ContainerStore.js | 35 ++++++++- src/stores/LogStore.js | 85 -------------------- src/utils/DockerUtil.js | 94 ++++++++++++++++++++++- styles/container-home.less | 8 +- 9 files changed, 160 insertions(+), 213 deletions(-) delete mode 100644 src/components/ContainerLogs.react.js delete mode 100644 src/stores/LogStore.js diff --git a/src/actions/ContainerActions.js b/src/actions/ContainerActions.js index 2bcbdf8856..a9c2f4a61c 100644 --- a/src/actions/ContainerActions.js +++ b/src/actions/ContainerActions.js @@ -38,6 +38,10 @@ class ContainerActions { run (name, repo, tag) { dockerUtil.run(name, repo, tag); } + + active (name) { + dockerUtil.active(name); + } } export default alt.createActions(ContainerActions); diff --git a/src/actions/ContainerServerActions.js b/src/actions/ContainerServerActions.js index 7308349f11..59acb37c83 100644 --- a/src/actions/ContainerServerActions.js +++ b/src/actions/ContainerServerActions.js @@ -15,7 +15,9 @@ class ContainerServerActions { 'updated', 'waiting', 'kill', - 'stopped' + 'stopped', + 'log', + 'logs' ); } } diff --git a/src/components/ContainerHomeLogs.react.js b/src/components/ContainerHomeLogs.react.js index 0853f98692..bffe4157dc 100644 --- a/src/components/ContainerHomeLogs.react.js +++ b/src/components/ContainerHomeLogs.react.js @@ -1,74 +1,44 @@ import $ from 'jquery'; import React from 'react/addons'; -import LogStore from '../stores/LogStore'; import Router from 'react-router'; -import metrics from '../utils/MetricsUtil'; +import containerActions from '../actions/ContainerActions'; +import Convert from 'ansi-to-html'; -var _prevBottom = 0; +let escape = function (html) { + var text = document.createTextNode(html); + var div = document.createElement('div'); + div.appendChild(text); + return div.innerHTML; +}; + +let convert = new Convert(); +let prevBottom = 0; module.exports = React.createClass({ - mixins: [Router.Navigation], - getInitialState: function () { - return { - logs: [] - }; - }, - componentDidMount: function() { - if (!this.props.container) { - return; - } - this.update(); - this.scrollToBottom(); - LogStore.on(LogStore.SERVER_LOGS_EVENT, this.update); - LogStore.fetch(this.props.container.Name); + + componentDidUpdate: function () { + var node = $('.logs').get()[0]; + node.scrollTop = node.scrollHeight; }, componentWillReceiveProps: function (nextProps) { if (this.props.container && nextProps.container && this.props.container.Name !== nextProps.container.Name) { - LogStore.detach(this.props.container.Name); - LogStore.fetch(nextProps.container.Name); + containerActions.active(nextProps.container.Name); } }, - componentWillUnmount: function() { - if (!this.props.container) { - return; - } + componentDidMount: function () { + containerActions.active(this.props.container.Name); + }, - LogStore.detach(this.props.container.Name); - LogStore.removeListener(LogStore.SERVER_LOGS_EVENT, this.update); - }, - componentDidUpdate: function () { - this.scrollToBottom(); - }, - scrollToBottom: function () { - var parent = $('.logs'); - if (parent[0].scrollHeight - parent.height() >= _prevBottom - 50) { - parent.scrollTop(parent[0].scrollHeight - parent.height()); - } - _prevBottom = parent[0].scrollHeight - parent.height(); - }, - handleClickLogs: function () { - metrics.track('Viewed Logs', { - from: 'preview' - }); - this.context.router.transitionTo('containerLogs', {name: this.props.container.Name}); - }, - update: function () { - if (!this.props.container) { - return; - } - this.setState({ - logs: LogStore.logs(this.props.container.Name) - }); + componentWillUnmount: function () { + containerActions.active(null); }, + render: function () { - var logs = this.state.logs.map(function (l, i) { - return ; - }); - if (logs.length === 0) { - logs = "No logs for this container."; - } + let logs = this.props.container.Logs ? + this.props.container.Logs.map((l) =>
'))}}>
) : + ['0 No logs for this container.']; return (
diff --git a/src/components/ContainerListItem.react.js b/src/components/ContainerListItem.react.js index 589ff6ef3c..76c9d886a0 100644 --- a/src/components/ContainerListItem.react.js +++ b/src/components/ContainerListItem.react.js @@ -96,7 +96,7 @@ var ContainerListItem = React.createClass({ return ( -
  • +
  • {state}
    diff --git a/src/components/ContainerLogs.react.js b/src/components/ContainerLogs.react.js deleted file mode 100644 index 7da5e84129..0000000000 --- a/src/components/ContainerLogs.react.js +++ /dev/null @@ -1,61 +0,0 @@ -import $ from 'jquery'; -import React from 'react/addons'; -import LogStore from '../stores/LogStore'; - -var _prevBottom = 0; - -module.exports = React.createClass({ - getInitialState: function () { - return { - logs: [] - }; - }, - componentDidMount: function() { - if (!this.props.container) { - return; - } - this.update(); - this.scrollToBottom(); - LogStore.on(LogStore.SERVER_LOGS_EVENT, this.update); - LogStore.fetch(this.props.container.Name); - }, - componentWillUnmount: function() { - if (!this.props.container) { - return; - } - - LogStore.detach(this.props.container.Name); - LogStore.removeListener(LogStore.SERVER_LOGS_EVENT, this.update); - }, - componentDidUpdate: function () { - this.scrollToBottom(); - }, - scrollToBottom: function () { - var parent = $('.details-logs'); - if (parent.scrollTop() >= _prevBottom - 50) { - parent.scrollTop(parent[0].scrollHeight - parent.height()); - } - _prevBottom = parent[0].scrollHeight - parent.height(); - }, - update: function () { - if (!this.props.container) { - return; - } - this.setState({ - logs: LogStore.logs(this.props.container.Name) - }); - }, - render: function () { - var logs = this.state.logs.map(function (l, i) { - return ; - }); - if (logs.length === 0) { - logs = "No logs for this container."; - } - return ( -
    - {logs} -
    - ); - } -}); diff --git a/src/stores/ContainerStore.js b/src/stores/ContainerStore.js index 28a6e306dd..46ddfce8a1 100644 --- a/src/stores/ContainerStore.js +++ b/src/stores/ContainerStore.js @@ -3,6 +3,8 @@ import alt from '../alt'; import containerServerActions from '../actions/ContainerServerActions'; import containerActions from '../actions/ContainerActions'; +let MAX_LOG_SIZE = 3000; + class ContainerStore { constructor () { this.bindActions(containerActions); @@ -102,10 +104,8 @@ class ContainerStore { if (containers[container.Name] && containers[container.Name].State.Updating) { return; } - // Trigger log update - // TODO: fix this loading multiple times - // LogStore.fetch(container.Name); + container.Logs = containers[container.Name].Logs; containers[container.Name] = container; this.setState({containers}); @@ -141,7 +141,7 @@ class ContainerStore { } } - waiting({name, waiting}) { + waiting ({name, waiting}) { let containers = this.containers; if (containers[name]) { containers[name].State.Waiting = waiting; @@ -158,6 +158,33 @@ class ContainerStore { this.setState({pending: null}); } + log ({name, entry}) { + let container = this.containers[name]; + if (!container) { + return; + } + + if (!container.Logs) { + container.Logs = []; + } + + container.Logs.push.apply(container.Logs, entry.split('\n').filter(e => e.length)); + container.Logs = container.Logs.slice(container.Logs.length - MAX_LOG_SIZE, MAX_LOG_SIZE); + this.emitChange(); + } + + logs ({name, logs}) { + let container = this.containers[name]; + + if (!container) { + return; + } + + container.Logs = logs.split('\n'); + container.Logs = container.Logs.slice(container.Logs.length - MAX_LOG_SIZE, MAX_LOG_SIZE); + this.emitChange(); + } + static generateName (repo) { const base = _.last(repo.split('/')); const names = _.keys(this.getState().containers); diff --git a/src/stores/LogStore.js b/src/stores/LogStore.js deleted file mode 100644 index 89ed09cebd..0000000000 --- a/src/stores/LogStore.js +++ /dev/null @@ -1,85 +0,0 @@ -import {EventEmitter} from 'events'; -import assign from 'object-assign'; -import Convert from 'ansi-to-html'; -import docker from '../utils/DockerUtil'; -import stream from 'stream'; - -var _convert = new Convert(); -var _logs = {}; -var _streams = {}; - -var MAX_LOG_SIZE = 3000; - -module.exports = assign(Object.create(EventEmitter.prototype), { - SERVER_LOGS_EVENT: 'server_logs_event', - _escape: function (html) { - var text = document.createTextNode(html); - var div = document.createElement('div'); - div.appendChild(text); - return div.innerHTML; - }, - fetch: function (name) { - if (!name || !docker.client) { - return; - } - docker.client.getContainer(name).logs({ - stdout: true, - stderr: true, - timestamps: false, - tail: MAX_LOG_SIZE, - follow: false - }, (err, logStream) => { - if (err) { - return; - } - var logs = []; - var outstream = new stream.PassThrough(); - docker.client.modem.demuxStream(logStream, outstream, outstream); - outstream.on('data', (chunk) => { - logs.push(_convert.toHtml(this._escape(chunk))); - }); - logStream.on('end', () => { - _logs[name] = logs; - this.emit(this.SERVER_LOGS_EVENT); - this.attach(name); - }); - }); - }, - attach: function (name) { - if (!name || !docker.client || _streams[name]) { - return; - } - docker.client.getContainer(name).attach({ - stdout: true, - stderr: true, - logs: false, - stream: true - }, (err, logStream) => { - if (err) { - return; - } - _streams[name] = logStream; - var outstream = new stream.PassThrough(); - docker.client.modem.demuxStream(logStream, outstream, outstream); - outstream.on('data', (chunk) => { - _logs[name].push(_convert.toHtml(this._escape(chunk))); - if (_logs[name].length > MAX_LOG_SIZE) { - _logs[name] = _logs[name].slice(_logs[name].length - MAX_LOG_SIZE, MAX_LOG_SIZE); - } - this.emit(this.SERVER_LOGS_EVENT); - }); - logStream.on('end', () => { - this.detach(name); - }); - }); - }, - detach: function (name) { - if (_streams[name]) { - _streams[name].destroy(); - delete _streams[name]; - } - }, - logs: function (name) { - return _logs[name] || []; - } -}); diff --git a/src/utils/DockerUtil.js b/src/utils/DockerUtil.js index 7091482feb..c3f1c5f76d 100644 --- a/src/utils/DockerUtil.js +++ b/src/utils/DockerUtil.js @@ -7,13 +7,15 @@ import util from './Util'; import hubUtil from './HubUtil'; import metrics from '../utils/MetricsUtil'; import containerServerActions from '../actions/ContainerServerActions'; -import Promise from 'bluebird'; import rimraf from 'rimraf'; +import stream from 'stream'; export default { host: null, client: null, placeholders: {}, + streams: {}, + activeContainerName: null, setup (ip, name) { if (!ip || !name) { @@ -343,6 +345,85 @@ export default { }); }, + active (name) { + this.detach(); + this.activeContainerName = name; + + if (name) { + this.logs(); + } + }, + + logs () { + if (!this.activeContainerName) { + return; + } + + this.client.getContainer(this.activeContainerName).logs({ + stdout: true, + stderr: true, + tail: 1000, + follow: false, + timestamps: 1 + }, (err, logStream) => { + if (err) { + return; + } + + let logs = ''; + logStream.setEncoding('utf8'); + logStream.on('data', chunk => logs += chunk); + logStream.on('end', () => { + containerServerActions.logs({name: this.activeContainerName, logs}); + this.attach(); + }); + }); + }, + + attach () { + if (!this.activeContainerName) { + return; + } + + this.client.getContainer(this.activeContainerName).logs({ + stdout: true, + stderr: true, + tail: 0, + follow: true, + timestamps: 1 + }, (err, logStream) => { + if (err) { + return; + } + + if (this.stream) { + this.detach(); + } + this.stream = logStream; + + let timeout = null; + let batch = ''; + logStream.setEncoding('utf8'); + logStream.on('data', (chunk) => { + batch += chunk; + if (!timeout) { + timeout = setTimeout(() => { + containerServerActions.log({name: this.activeContainerName, entry: batch}); + timeout = null; + batch = ''; + }, 16); + } + }); + }); + }, + + detach () { + if (this.stream) { + this.stream.destroy(); + this.stream = null; + } + }, + listen () { this.client.getEvents((error, stream) => { if (error || !stream) { @@ -354,16 +435,25 @@ export default { stream.on('data', json => { let data = JSON.parse(json); - if (data.status === 'pull' || data.status === 'untag' || data.status === 'delete' || data.status === 'attach') { + if (data.status === 'pull' || data.status === 'untag' || data.status === 'delete' || data.status === 'attach') { return; } if (data.status === 'destroy') { containerServerActions.destroyed({id: data.id}); + this.detach(data.id); } else if (data.status === 'kill') { containerServerActions.kill({id: data.id}); + this.detach(data.id); } else if (data.status === 'stop') { containerServerActions.stopped({id: data.id}); + this.detach(data.id); + } else if (data.status === 'create') { + this.logs(); + this.fetchContainer(data.id); + } else if (data.status === 'start') { + this.attach(); + this.fetchContainer(data.id); } else if (data.id) { this.fetchContainer(data.id); } diff --git a/styles/container-home.less b/styles/container-home.less index b8dbc3f20b..91433584f8 100644 --- a/styles/container-home.less +++ b/styles/container-home.less @@ -7,14 +7,15 @@ flex-direction: row; padding: 1rem; .left { - width: 100%; + display: flex; + flex: 0.9 1 0; flex-direction: column; margin-right: 1rem; } .right { + display: flex; + flex: 0.1 0 300px; width: 40%; - min-width: 200px; - max-width: 600px; flex-direction: column; } .full { @@ -103,7 +104,6 @@ color: @gray-lightest; font-family: @font-code; font-size: 10px; - white-space: pre-wrap; -webkit-user-select: text; padding: 1.2rem 1.2rem 5rem 1.2rem; overflow: auto; From 0dd51c775ef798251f4fced569fe9dba903e13f9 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Mon, 30 Nov 2015 14:29:43 -0800 Subject: [PATCH 35/49] CI: Release on tags Signed-off-by: Jeffrey Morgan --- circle.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/circle.yml b/circle.yml index 90f69e603f..d06a79e23c 100644 --- a/circle.yml +++ b/circle.yml @@ -5,3 +5,10 @@ machine: dependencies: cache_directories: - "node_modules" + +deployment: + release: + tag: /v.*/ + owner: docker + commands: + - echo $CIRCLE_BRANCH From e78f7453293af84febb468d31134260fa3229ee2 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Tue, 1 Dec 2015 10:16:10 -0800 Subject: [PATCH 36/49] Build Linux to dist Signed-off-by: Jeffrey Morgan --- Gruntfile.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 23e3f8053b..c76072ba44 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -55,7 +55,6 @@ module.exports = function (grunt) { platform: 'darwin', arch: 'x64', asar: true, - 'app-bundle-id': 'com.kitematic.kitematic', 'app-version': packagejson.version } }, @@ -63,7 +62,7 @@ module.exports = function (grunt) { options: { name: LINUX_APPNAME, dir: 'build/', - out: 'dist/linux/', + out: 'dist', version: packagejson['electron-version'], platform: 'linux', arch: 'x64', From f93c58da72b9b36d8ac51b440c4bf1afc333308f Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Tue, 1 Dec 2015 11:56:13 -0800 Subject: [PATCH 37/49] Remove reference to container logs Signed-off-by: Jeffrey Morgan --- src/routes.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/routes.js b/src/routes.js index 824b5d7069..eac26dc3ea 100644 --- a/src/routes.js +++ b/src/routes.js @@ -6,7 +6,6 @@ import AccountLogin from './components/AccountLogin.react'; import Containers from './components/Containers.react'; import ContainerDetails from './components/ContainerDetails.react'; import ContainerHome from './components/ContainerHome.react'; -import ContainerLogs from './components/ContainerLogs.react'; import ContainerSettings from './components/ContainerSettings.react'; import ContainerSettingsGeneral from './components/ContainerSettingsGeneral.react'; import ContainerSettingsPorts from './components/ContainerSettingsPorts.react'; @@ -39,7 +38,6 @@ var routes = ( - From 77c6ce65f86f25ea6eb872a100313e6d461c371d Mon Sep 17 00:00:00 2001 From: French Ben Date: Tue, 1 Dec 2015 12:03:55 -0800 Subject: [PATCH 38/49] Updated setup check Signed-off-by: French Ben --- src/app.js | 3 +-- src/utils/SetupUtil.js | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app.js b/src/app.js index 360556f8e5..c706a18be4 100644 --- a/src/app.js +++ b/src/app.js @@ -47,8 +47,7 @@ var router = Router.create({ router.run(Handler => React.render(, document.body)); routerContainer.set(router); -let setup = util.isLinux() ? setupUtil.nativeSetup : setupUtil.nonNativeSetup; -setup().then(() => { +setupUtil.setup().then(() => { Menu.setApplicationMenu(Menu.buildFromTemplate(template())); docker.init(); if (!hub.prompted() && !hub.loggedin()) { diff --git a/src/utils/SetupUtil.js b/src/utils/SetupUtil.js index cf9b6ac43f..e2b794851a 100644 --- a/src/utils/SetupUtil.js +++ b/src/utils/SetupUtil.js @@ -51,6 +51,10 @@ export default { return _retryPromise.promise; }, + setup() { + return util.isLinux() ? this.nativeSetup() : this.nonNativeSetup(); + }, + async nativeSetup () { while (true) { try { From 4e14413786210d8e42f12f87be93739878a92029 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Tue, 1 Dec 2015 12:08:11 -0800 Subject: [PATCH 39/49] Use generic .zip names for releases, upload to GitHub Signed-off-by: Jeffrey Morgan --- Gruntfile.js | 5 ++--- circle.yml | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index c76072ba44..735dc33004 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -208,7 +208,7 @@ module.exports = function (grunt) { ].join(' && '), }, zip: { - command: 'ditto -c -k --sequesterRsrc --keepParent <%= OSX_FILENAME_ESCAPED %> release/' + BASENAME + '-' + packagejson.version + '-Mac.zip', + command: 'ditto -c -k --sequesterRsrc --keepParent <%= OSX_FILENAME_ESCAPED %> release/' + BASENAME + '-Mac.zip', } }, @@ -219,7 +219,7 @@ module.exports = function (grunt) { compress: { windows: { options: { - archive: './release/' + BASENAME + '-' + packagejson.version + '-Windows-Alpha.zip', + archive: './release/' + BASENAME + '-Windows.zip', mode: 'zip' }, files: [{ @@ -257,7 +257,6 @@ module.exports = function (grunt) { grunt.registerTask('default', ['newer:babel', 'less', 'newer:copy:dev', 'shell:electron', 'watchChokidar']); grunt.registerTask('release', ['clean:release', 'babel', 'less', 'copy:dev', 'electron', 'copy:osx', 'shell:sign', 'shell:zip', 'copy:windows', 'rcedit:exes', 'compress']); - grunt.registerTask('release-mac', ['clean:release', 'babel', 'less', 'copy:dev', 'electron:osx', 'copy:osx', 'shell:sign', 'shell:zip']); process.on('SIGINT', function () { grunt.task.run(['shell:electron:kill']); diff --git a/circle.yml b/circle.yml index d06a79e23c..24d27b74da 100644 --- a/circle.yml +++ b/circle.yml @@ -11,4 +11,5 @@ deployment: tag: /v.*/ owner: docker commands: - - echo $CIRCLE_BRANCH + - github-release upload --user docker --repo kitematic --tag $CIRCLE_TAG --file release/Kitematic-Mac.zip --name Kitematic-Mac.zip + - github-release upload --user docker --repo kitematic --tag $CIRCLE_TAG --file release/Kitematic-Windows.zip --name Kitematic-Windows.zip From 3d83d34e22d0e8ef7ce8a1f6467d70acd490866b Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Fri, 4 Dec 2015 17:59:15 +0100 Subject: [PATCH 40/49] update maintainers file for parsing this updates the MAINTAINERS file to the new format, so that it can be parsed and collected in the docker/opensource repository. Signed-off-by: Sebastiaan van Stijn --- MAINTAINERS | 50 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index f863c81756..25cdb0fcc0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1,4 +1,46 @@ -Jeff Morgan (@jeffdm) -Sean Li (@elesant) -Michael Chiang (@mchiang0610) -Ben French (@FrenchBen) +# Kitematic maintainers file +# +# This file describes who runs the docker/kitematic project and how. +# This is a living document - if you see something out of date or missing, speak up! +# +# It is structured to be consumable by both humans and programs. +# To extract its contents programmatically, use any TOML-compliant parser. +# +# This file is compiled into the MAINTAINERS file in docker/opensource. +# +[Org] + [Org."Core maintainers"] + people = [ + "elesant", + "FrenchBen", + "jeffdm", + "mchiang0610", + ] + +[people] + +# A reference list of all people associated with the project. +# All other sections should refer to people by their canonical key +# in the people section. + + # ADD YOURSELF HERE IN ALPHABETICAL ORDER + + [people.elesant] + Name = "Sean Li" + Email = "mail@shang.li" + GitHub = "elesant" + + [people.FrenchBen] + Name = "Ben French" + Email = "me@frenchben.com" + GitHub = "FrenchBen" + + [people.jeffdm] + Name = "Jeff Morgan" + Email = "jmorgan@docker.com" + GitHub = "jeffdm" + + [people.mchiang0610] + Name = "Michael Chiang" + Email = "mchiang@docker.com" + GitHub = "mchiang0610" From 3fc43ec8294463f09b66be7884680ed67f6ba980 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Fri, 4 Dec 2015 13:26:56 -0800 Subject: [PATCH 41/49] Fix error when fetching containers upon daemon events Signed-off-by: Jeffrey Morgan --- src/stores/ContainerStore.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/stores/ContainerStore.js b/src/stores/ContainerStore.js index 46ddfce8a1..6e6eeee093 100644 --- a/src/stores/ContainerStore.js +++ b/src/stores/ContainerStore.js @@ -100,14 +100,20 @@ class ContainerStore { } updated ({container}) { + if (!container || !container.Name) { + return; + } + let containers = this.containers; if (containers[container.Name] && containers[container.Name].State.Updating) { return; } - container.Logs = containers[container.Name].Logs; - containers[container.Name] = container; + if (containers[container.Name] && containers[container.Name].Logs) { + container.Logs = containers[container.Name].Logs; + } + containers[container.Name] = container; this.setState({containers}); } From 262a9cda378ad807ebcf9a1ae3631003b9f1ec34 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Fri, 4 Dec 2015 13:59:30 -0800 Subject: [PATCH 42/49] Fix errors due to json streams over sockets Signed-off-by: Jeffrey Morgan --- package.json | 1 + src/utils/DockerUtil.js | 9 +++------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 206b329fa2..89e4b83718 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "license": "Apache-2.0", "electron-version": "0.33.9", "dependencies": { + "JSONStream": "^1.0.7", "alt": "^0.16.2", "ansi-to-html": "0.3.0", "any-promise": "^0.1.0", diff --git a/src/utils/DockerUtil.js b/src/utils/DockerUtil.js index db15b5407b..3baa3140a2 100644 --- a/src/utils/DockerUtil.js +++ b/src/utils/DockerUtil.js @@ -10,6 +10,7 @@ import metrics from '../utils/MetricsUtil'; import containerServerActions from '../actions/ContainerServerActions'; import rimraf from 'rimraf'; import stream from 'stream'; +import JSONStream from 'JSONStream'; export default { host: null, @@ -446,9 +447,7 @@ export default { } stream.setEncoding('utf8'); - stream.on('data', json => { - let data = JSON.parse(json); - + stream.pipe(JSONStream.parse()).on('data', data => { if (data.status === 'pull' || data.status === 'untag' || data.status === 'delete' || data.status === 'attach') { return; } @@ -509,9 +508,7 @@ export default { let error = null; // data is associated with one layer only (can be identified with id) - stream.on('data', str => { - var data = JSON.parse(str); - + stream.pipe(JSONStream.parse()).on('data', data => { if (data.error) { error = data.error; return; From 31928e6914a817f6a7a9862d90935f92f16b3781 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Mon, 7 Dec 2015 14:54:38 -0800 Subject: [PATCH 43/49] Bump version, fix exec related error in new version Signed-off-by: Jeffrey Morgan --- package.json | 5 ++--- src/utils/Util.js | 10 ++++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 89e4b83718..69a42f947a 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "lint": "jsxhint src" }, "license": "Apache-2.0", - "electron-version": "0.33.9", + "electron-version": "0.35.4", "dependencies": { "JSONStream": "^1.0.7", "alt": "^0.16.2", @@ -33,7 +33,6 @@ "coveralls": "^2.11.2", "deep-extend": "^0.4.0", "dockerode": "^2.2.3", - "exec": "0.2.1", "install": "^0.1.8", "jquery": "^2.1.3", "mixpanel": "kitematic/mixpanel-node", @@ -56,7 +55,7 @@ "devDependencies": { "babel": "^5.8.23", "babel-jest": "^5.2.0", - "electron-prebuilt": "^0.33.9", + "electron-prebuilt": "^0.35.4", "eslint": "^1.3.1", "eslint-plugin-react": "^3.3.0", "grunt": "^0.4.5", diff --git a/src/utils/Util.js b/src/utils/Util.js index a0671a57ad..4add73aa43 100644 --- a/src/utils/Util.js +++ b/src/utils/Util.js @@ -1,4 +1,3 @@ -import exec from 'exec'; import child_process from 'child_process'; import Promise from 'bluebird'; import fs from 'fs'; @@ -20,12 +19,11 @@ module.exports = { } } - let fn = Array.isArray(args) ? exec : child_process.exec; return new Promise((resolve, reject) => { - fn(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)); + var cmd = Array.isArray(args) ? args.join(' ') : args; + child_process.exec(cmd, options, (error, stdout, stderr) => { + if (error) { + reject(new Error('Encountered an error: ' + error)); } else { resolve(stdout); } From 94f7ce8b7129c899ecaf4767cc5fa7a40103a0c2 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Sat, 12 Dec 2015 17:32:18 -0800 Subject: [PATCH 44/49] Fix bug where renaming or editing container settings would cause volume settings to be lost Signed-off-by: Jeffrey Morgan --- package.json | 2 +- src/components/ContainerHomeFolders.react.js | 13 +++--------- .../ContainerSettingsVolumes.react.js | 15 +++----------- src/utils/DockerUtil.js | 20 +++++++++++-------- 4 files changed, 19 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index 69a42f947a..58c8faf087 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "classnames": "^2.1.5", "coveralls": "^2.11.2", "deep-extend": "^0.4.0", - "dockerode": "^2.2.3", + "dockerode": "^2.2.7", "install": "^0.1.8", "jquery": "^2.1.3", "mixpanel": "kitematic/mixpanel-node", diff --git a/src/components/ContainerHomeFolders.react.js b/src/components/ContainerHomeFolders.react.js index c0a6366d6a..ad52f39d71 100644 --- a/src/components/ContainerHomeFolders.react.js +++ b/src/components/ContainerHomeFolders.react.js @@ -28,17 +28,10 @@ var ContainerHomeFolder = React.createClass({ var mounts = _.clone(this.props.container.Mounts); var newSource = path.join(util.home(), util.documents(), 'Kitematic', this.props.container.Name, destination); - var binds = mounts.map(function (m) { - let source = m.Source; + mounts.forEach(m => { if (m.Destination === destination) { - source = newSource; + m.Source = util.windowsToLinuxPath(newSource); } - - if(util.isWindows()) { - return util.windowsToLinuxPath(source) + ':' + m.Destination; - } - - return source + ':' + m.Destination; }); mkdirp(newSource, function (err) { @@ -48,7 +41,7 @@ var ContainerHomeFolder = React.createClass({ } }); - containerActions.update(this.props.container.Name, {Binds: binds}); + containerActions.update(this.props.container.Name, {Mounts: mounts}); } }); } else { diff --git a/src/components/ContainerSettingsVolumes.react.js b/src/components/ContainerSettingsVolumes.react.js index f195badb7c..8c1e615813 100644 --- a/src/components/ContainerSettingsVolumes.react.js +++ b/src/components/ContainerSettingsVolumes.react.js @@ -27,14 +27,10 @@ var ContainerSettingsVolumes = React.createClass({ metrics.track('Choose Directory for Volume'); - if(util.isWindows()) { - directory = util.windowsToLinuxPath(directory); - } - var mounts = _.clone(this.props.container.Mounts); _.each(mounts, m => { if (m.Destination === dockerVol) { - m.Source = directory; + m.Source = util.windowsToLinuxPath(directory); } }); @@ -50,19 +46,14 @@ var ContainerSettingsVolumes = React.createClass({ from: 'settings' }); - var hostConfig = _.clone(this.props.container.HostConfig); - var binds = hostConfig.Binds; var mounts = _.clone(this.props.container.Mounts); _.each(mounts, m => { if (m.Destination === dockerVol) { m.Source = null; } }); - var index = _.findIndex(binds, bind => bind.indexOf(`:${dockerVol}`) !== -1); - if (index >= 0) { - binds.splice(index, 1); - } - containerActions.update(this.props.container.Name, {HostConfig: hostConfig, Binds: binds, Mounts: mounts}); + + containerActions.update(this.props.container.Name, {Mounts: mounts}); }, handleOpenVolumeClick: function (path) { metrics.track('Opened Volume Directory', { diff --git a/src/utils/DockerUtil.js b/src/utils/DockerUtil.js index 3baa3140a2..f4227470c0 100644 --- a/src/utils/DockerUtil.js +++ b/src/utils/DockerUtil.js @@ -248,6 +248,11 @@ export default { existingData.Tty = existingData.Config.Tty; existingData.OpenStdin = existingData.Config.OpenStdin; } + + data.Mounts = data.Mounts || existingData.Mounts; + data.Binds = data.Mounts.map(m => m.Source + ':' + m.Destination); + + // Preserve Ports let networking = _.extend(existingData.NetworkSettings, data.NetworkSettings); if (networking && networking.Ports) { let exposed = _.reduce(networking.Ports, (res, value, key) => { @@ -273,8 +278,8 @@ export default { containerServerActions.error({name, error}); return; } - var oldPath = path.join(util.home(), 'Kitematic', name); - var newPath = path.join(util.home(), 'Kitematic', newName); + var oldPath = util.windowsToLinuxPath(path.join(util.home(), util.documents(), 'Kitematic', name)); + var newPath = util.windowsToLinuxPath(path.join(util.home(), util.documents(), 'Kitematic', newName)); this.client.getContainer(newName).inspect((error, container) => { if (error) { @@ -285,13 +290,12 @@ export default { if (fs.existsSync(oldPath)) { fs.renameSync(oldPath, newPath); } - var binds = _.pairs(container.Volumes).map(function (pair) { - return pair[1] + ':' + pair[0]; + + container.Mounts.forEach(m => { + m.Source = m.Source.replace(oldPath, newPath); }); - var newBinds = binds.map(b => { - return b.replace(path.join(util.home(), 'Kitematic', name), path.join(util.home(), 'Kitematic', newName)); - }); - this.updateContainer(newName, {Binds: newBinds}); + + this.updateContainer(newName, {Mounts: container.Mounts}); rimraf(oldPath, () => {}); }); }); From 13537af0bc1dfcf0b9fafc61c24c722402931fab Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Sat, 12 Dec 2015 21:29:22 -0800 Subject: [PATCH 45/49] Fix bugs related to exec and dont track VTX as an error Signed-off-by: Jeffrey Morgan --- src/components/Header.react.js | 2 -- src/utils/DockerMachineUtil.js | 28 ++++++++++++++-------------- src/utils/SetupUtil.js | 9 ++++++--- src/utils/Util.js | 26 +++++++++++++------------- src/utils/VirtualBoxUtil.js | 27 ++++++--------------------- 5 files changed, 39 insertions(+), 53 deletions(-) diff --git a/src/components/Header.react.js b/src/components/Header.react.js index e69c14ea80..d56ec9a7b8 100644 --- a/src/components/Header.react.js +++ b/src/components/Header.react.js @@ -2,7 +2,6 @@ import React from 'react/addons'; import remote from 'remote'; import RetinaImage from 'react-retina-image'; import ipc from 'ipc'; -var autoUpdater = remote.require('auto-updater'); import util from '../utils/Util'; import metrics from '../utils/MetricsUtil'; var Menu = remote.require('menu'); @@ -32,7 +31,6 @@ var Header = React.createClass({ updateAvailable: true }); }); - autoUpdater.checkForUpdates(); }, componentWillUnmount: function () { document.removeEventListener('keyup', this.handleDocumentKeyUp, false); diff --git a/src/utils/DockerMachineUtil.js b/src/utils/DockerMachineUtil.js index fa22e37eab..d3d462ecbc 100644 --- a/src/utils/DockerMachineUtil.js +++ b/src/utils/DockerMachineUtil.js @@ -22,7 +22,7 @@ var DockerMachine = { return fs.existsSync(this.command()); }, version: function () { - return util.exec([this.command(), '-v']).then(stdout => { + return util.execFile([this.command(), '-v']).then(stdout => { try { var matchlist = stdout.match(/(\d+\.\d+\.\d+).*/); if (!matchlist || matchlist.length < 2) { @@ -57,40 +57,40 @@ var DockerMachine = { }); }, create: function (machineName = this.name()) { - return util.exec([this.command(), '-D', 'create', '-d', 'virtualbox', '--virtualbox-memory', '2048', machineName]); + return util.execFile([this.command(), '-D', 'create', '-d', 'virtualbox', '--virtualbox-memory', '2048', machineName]); }, start: function (machineName = this.name()) { - return util.exec([this.command(), '-D', 'start', machineName]); + return util.execFile([this.command(), '-D', 'start', machineName]); }, stop: function (machineName = this.name()) { - return util.exec([this.command(), 'stop', machineName]); + return util.execFile([this.command(), 'stop', machineName]); }, upgrade: function (machineName = this.name()) { - return util.exec([this.command(), 'upgrade', machineName]); + return util.execFile([this.command(), 'upgrade', machineName]); }, rm: function (machineName = this.name()) { - return util.exec([this.command(), 'rm', '-f', machineName]); + return util.execFile([this.command(), 'rm', '-f', machineName]); }, ip: function (machineName = this.name()) { - return util.exec([this.command(), 'ip', machineName]).then(stdout => { + return util.execFile([this.command(), 'ip', machineName]).then(stdout => { return Promise.resolve(stdout.trim().replace('\n', '')); }); }, url: function (machineName = this.name()) { - return util.exec([this.command(), 'url', machineName]).then(stdout => { + return util.execFile([this.command(), 'url', machineName]).then(stdout => { return Promise.resolve(stdout.trim().replace('\n', '')); }); }, regenerateCerts: function (machineName = this.name()) { - return util.exec([this.command(), 'tls-regenerate-certs', '-f', machineName]); + return util.execFile([this.command(), 'tls-regenerate-certs', '-f', machineName]); }, status: function (machineName = this.name()) { - return util.exec([this.command(), 'status', machineName]).then(stdout => { + return util.execFile([this.command(), 'status', machineName]).then(stdout => { return Promise.resolve(stdout.trim().replace('\n', '')); }); }, disk: function (machineName = this.name()) { - return util.exec([this.command(), 'ssh', machineName, 'df']).then(stdout => { + return util.execFile([this.command(), 'ssh', machineName, 'df']).then(stdout => { try { var lines = stdout.split('\n'); var dataline = _.find(lines, function (line) { @@ -114,7 +114,7 @@ var DockerMachine = { }); }, memory: function (machineName = this.name()) { - return util.exec([this.command(), 'ssh', machineName, 'free -m']).then(stdout => { + return util.execFile([this.command(), 'ssh', machineName, 'free -m']).then(stdout => { try { var lines = stdout.split('\n'); var dataline = _.find(lines, function (line) { @@ -155,11 +155,11 @@ var DockerMachine = { cmd = cmd || process.env.SHELL; var terminal = util.linuxTerminal(); if (terminal) - util.exec(terminal.concat([cmd])).then(() => {}); + util.execFile(terminal.concat([cmd])).then(() => {}); } else { cmd = cmd || process.env.SHELL; this.url(machineName).then(machineUrl => { - util.exec([path.join(process.env.RESOURCES_PATH, 'terminal'), `DOCKER_HOST=${machineUrl} DOCKER_CERT_PATH=${path.join(util.home(), '.docker/machine/machines/' + machineName)} DOCKER_TLS_VERIFY=1 ${cmd}`]).then(() => {}); + util.execFile([path.join(process.env.RESOURCES_PATH, 'terminal'), `DOCKER_HOST=${machineUrl} DOCKER_CERT_PATH=${path.join(util.home(), '.docker/machine/machines/' + machineName)} DOCKER_TLS_VERIFY=1 ${cmd}`]).then(() => {}); }); } }, diff --git a/src/utils/SetupUtil.js b/src/utils/SetupUtil.js index e2b794851a..c1d09ad5e2 100644 --- a/src/utils/SetupUtil.js +++ b/src/utils/SetupUtil.js @@ -137,9 +137,9 @@ export default { let tries = 80, ip = null; while (!ip && tries > 0) { try { + tries -= 1; console.log('Trying to fetch machine IP, tries left: ' + tries); ip = await machine.ip(); - tries -= 1; await Promise.delay(1000); } catch (err) {} } @@ -153,11 +153,12 @@ export default { break; } catch (error) { router.get().transitionTo('setup'); - metrics.track('Setup Failed', { + + let novtx = error.message.indexOf('This computer doesn\'t have VT-X/AMD-v enabled') !== -1; + metrics.track(novtx ? 'Setup Halted' : 'Setup Failed', { virtualBoxVersion, machineVersion }); - setupServerActions.error({error}); let message = error.message.split('\n'); let lastLine = message.length > 1 ? message[message.length - 2] : 'Docker Machine encountered an error.'; @@ -170,6 +171,8 @@ export default { groupingHash: machineVersion }, 'info'); + setupServerActions.error({error: new Error(lastLine)}); + this.clearTimers(); await this.pause(); } diff --git a/src/utils/Util.js b/src/utils/Util.js index 4add73aa43..995e547f33 100644 --- a/src/utils/Util.js +++ b/src/utils/Util.js @@ -8,20 +8,20 @@ var dialog = remote.require('dialog'); var app = remote.require('app'); module.exports = { - exec: function (args, options) { - options = options || {}; - - // Add resources dir to exec path for Windows - if (this.isWindows()) { - options.env = options.env || {}; - if (!options.env.PATH) { - options.env.PATH = process.env.RESOURCES_PATH + ';' + process.env.PATH; - } - } - + execFile: function (args, options) { return new Promise((resolve, reject) => { - var cmd = Array.isArray(args) ? args.join(' ') : args; - child_process.exec(cmd, options, (error, stdout, stderr) => { + child_process.execFile(args[0], args.slice(1), options, (error, stdout, stderr) => { + if (error) { + reject(new Error('Encountered an error: ' + error)); + } else { + resolve(stdout); + } + }); + }); + }, + exec: function (args, options) { + return new Promise((resolve, reject) => { + child_process.exec(args, options, (error, stdout, stderr) => { if (error) { reject(new Error('Encountered an error: ' + error)); } else { diff --git a/src/utils/VirtualBoxUtil.js b/src/utils/VirtualBoxUtil.js index 2768eecc11..1d9bae104f 100644 --- a/src/utils/VirtualBoxUtil.js +++ b/src/utils/VirtualBoxUtil.js @@ -16,13 +16,16 @@ var VirtualBox = { } }, installed: function () { + if (util.isWindows() && !process.env.VBOX_INSTALL_PATH && !process.env.VBOX_MSI_INSTALL_PATH) { + return false; + } return fs.existsSync(this.command()); }, active: function () { return fs.existsSync('/dev/vboxnetctl'); }, version: function () { - return util.exec([this.command(), '-v']).then(stdout => { + return util.execFile([this.command(), '-v']).then(stdout => { let matchlist = stdout.match(/(\d+\.\d+\.\d+).*/); if (!matchlist || matchlist.length < 2) { Promise.reject('VBoxManage -v output format not recognized.'); @@ -32,29 +35,11 @@ var VirtualBox = { return Promise.resolve(null); }); }, - poweroffall: function () { - return util.exec(this.command() + ' list runningvms | sed -E \'s/.*\\{(.*)\\}/\\1/\' | xargs -L1 -I {} ' + this.command() + ' controlvm {} poweroff'); - }, mountSharedDir: function (vmName, pathName, hostPath) { - return util.exec([this.command(), 'sharedfolder', 'add', vmName, '--name', pathName, '--hostpath', hostPath, '--automount']); - }, - killall: function () { - if (util.isWindows()) { - return this.poweroffall().then(() => { - return util.exec(['powershell.exe', '\"get-process VBox* | stop-process\"']); - }).catch(() => {}); - } else { - return this.poweroffall().then(() => { - return util.exec(['pkill', 'VirtualBox']); - }).then(() => { - return util.exec(['pkill', 'VBox']); - }).catch(() => { - - }); - } + return util.execFile([this.command(), 'sharedfolder', 'add', vmName, '--name', pathName, '--hostpath', hostPath, '--automount']); }, vmExists: function (name) { - return util.exec([this.command(), 'showvminfo', name]).then(() => { + return util.execFile([this.command(), 'showvminfo', name]).then(() => { return true; }).catch((err) => { return false; From ad0d7e003ff4a886c9e8c0abcf274bc837bdc27c Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Sun, 13 Dec 2015 18:37:05 -0800 Subject: [PATCH 46/49] Fix bug where container would be run with bash even with a valid entrypoint Signed-off-by: Jeffrey Morgan --- src/utils/DockerUtil.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/DockerUtil.js b/src/utils/DockerUtil.js index f4227470c0..e30bc052d7 100644 --- a/src/utils/DockerUtil.js +++ b/src/utils/DockerUtil.js @@ -126,7 +126,7 @@ export default { return; } - containerData.Cmd = image.Config.Cmd || 'bash'; + containerData.Cmd = image.Config.Cmd || image.Config.Entrypoint || 'bash'; let existing = this.client.getContainer(name); existing.kill(() => { existing.remove(() => { From 979fa6fd2ac549c82a4fc5ae4e0d3a048dc8d5ea Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Mon, 14 Dec 2015 08:57:15 -0800 Subject: [PATCH 47/49] Bump version to 0.9.4 Signed-off-by: Jeffrey Morgan --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 58c8faf087..67807b9f27 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Kitematic", - "version": "0.9.3", + "version": "0.9.4", "author": "Kitematic", "description": "Simple Docker Container management for Mac OS X.", "homepage": "https://kitematic.com/", From f3f35ba0da7d483346feb4036189645961f1dc84 Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Mon, 14 Dec 2015 16:35:50 -0800 Subject: [PATCH 48/49] Fix docker-machine shell out for status command Signed-off-by: Jeffrey Morgan --- src/utils/DockerMachineUtil.js | 11 +++++++++-- src/utils/SetupUtil.js | 3 ++- src/utils/Util.js | 4 ++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/utils/DockerMachineUtil.js b/src/utils/DockerMachineUtil.js index d3d462ecbc..5cc371ee24 100644 --- a/src/utils/DockerMachineUtil.js +++ b/src/utils/DockerMachineUtil.js @@ -3,6 +3,7 @@ import path from 'path'; import Promise from 'bluebird'; import fs from 'fs'; import util from './Util'; +import child_process from 'child_process'; var DockerMachine = { command: function () { @@ -85,8 +86,14 @@ var DockerMachine = { return util.execFile([this.command(), 'tls-regenerate-certs', '-f', machineName]); }, status: function (machineName = this.name()) { - return util.execFile([this.command(), 'status', machineName]).then(stdout => { - return Promise.resolve(stdout.trim().replace('\n', '')); + return new Promise((resolve, reject) => { + child_process.execFile(this.command(), ['status', machineName], (error, stdout, stderr) => { + if (error) { + reject(new Error('Encountered an error: ' + error)); + } else { + resolve(stderr.trim()); + } + }); }); }, disk: function (machineName = this.name()) { diff --git a/src/utils/SetupUtil.js b/src/utils/SetupUtil.js index c1d09ad5e2..b6d8787654 100644 --- a/src/utils/SetupUtil.js +++ b/src/utils/SetupUtil.js @@ -121,6 +121,7 @@ export default { await machine.create(); } else { let state = await machine.status(); + console.log(state); if (state !== 'Running') { if (state === 'Saved') { router.get().transitionTo('setup'); @@ -171,7 +172,7 @@ export default { groupingHash: machineVersion }, 'info'); - setupServerActions.error({error: new Error(lastLine)}); + setupServerActions.error({error: new Error(message)}); this.clearTimers(); await this.pause(); diff --git a/src/utils/Util.js b/src/utils/Util.js index 995e547f33..e3418c7c3a 100644 --- a/src/utils/Util.js +++ b/src/utils/Util.js @@ -10,7 +10,7 @@ var app = remote.require('app'); module.exports = { execFile: function (args, options) { return new Promise((resolve, reject) => { - child_process.execFile(args[0], args.slice(1), options, (error, stdout, stderr) => { + child_process.execFile(args[0], args.slice(1), options, (error, stdout) => { if (error) { reject(new Error('Encountered an error: ' + error)); } else { @@ -21,7 +21,7 @@ module.exports = { }, exec: function (args, options) { return new Promise((resolve, reject) => { - child_process.exec(args, options, (error, stdout, stderr) => { + child_process.exec(args, options, (error, stdout) => { if (error) { reject(new Error('Encountered an error: ' + error)); } else { From 1b44c2007ca41cec775a54ad8163453621f7e03a Mon Sep 17 00:00:00 2001 From: Jeffrey Morgan Date: Tue, 15 Dec 2015 13:39:09 -0800 Subject: [PATCH 49/49] Look at stderr and stdout for docker-machine status Signed-off-by: Jeffrey Morgan --- src/utils/DockerMachineUtil.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/DockerMachineUtil.js b/src/utils/DockerMachineUtil.js index 5cc371ee24..49886372c4 100644 --- a/src/utils/DockerMachineUtil.js +++ b/src/utils/DockerMachineUtil.js @@ -91,7 +91,7 @@ var DockerMachine = { if (error) { reject(new Error('Encountered an error: ' + error)); } else { - resolve(stderr.trim()); + resolve(stdout.trim() + stderr.trim()); } }); });