diff --git a/Gruntfile.js b/Gruntfile.js index 062ff65461..5958dd4503 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -76,11 +76,38 @@ module.exports = function (grunt) { } }, + rcedit: { + exes: { + files: [{ + expand: true, + cwd: 'dist/Kitematic-win32', + src: ['Kitematic.exe'] + }], + options: { + icon: 'util/kitematic.ico', + 'file-version': packagejson.version, + 'product-version': packagejson.version, + 'version-string': { + 'CompanyName': 'Docker, Inc', + 'ProductVersion': packagejson.version, + 'ProductName': 'Kitematic', + 'FileDescription': 'Kitematic', + 'InternalName': 'Kitematic.exe', + 'OriginalFilename': 'Kitematic.exe', + 'LegalCopyright': 'Copyright 2015 Docker Inc. All rights reserved.' + } + } + } + }, + 'create-windows-installer': { appDirectory: 'dist/Kitematic-win32/', authors: 'Docker Inc.', loadingGif: 'util/loading.gif', - setupIcon: 'util/kitematic.ico' + setupIcon: 'util/kitematic.ico', + description: 'Kitematic', + title: 'Kitematic', + version: packagejson.version }, // docker binaries @@ -126,7 +153,7 @@ module.exports = function (grunt) { files: [{ expand: true, cwd: 'resources', - src: ['docker*'], + src: ['docker*', 'boot2docker.iso', 'ssh.exe', 'OPENSSH_LICENSE', 'msys-*'], dest: 'dist/Kitematic-win32/resources/resources/' }], options: { @@ -137,7 +164,7 @@ module.exports = function (grunt) { files: [{ expand: true, cwd: 'resources', - src: ['**/*'], + src: ['docker*', 'boot2docker.iso', 'macsudo', 'terminal'], dest: '<%= OSX_FILENAME %>/Contents/Resources/resources/' }, { src: 'util/kitematic.icns', @@ -152,7 +179,7 @@ module.exports = function (grunt) { rename: { installer: { src: 'installer/Setup.exe', - dest: 'installer/KitematicSetup.exe' + dest: 'installer/KitematicSetup-' + packagejson.version + '.exe' } }, @@ -258,7 +285,7 @@ module.exports = function (grunt) { }, less: { files: ['styles/**/*.less'], - tasks: ['newer:less'] + tasks: ['less'] }, copy: { files: ['images/*', 'index.html', 'fonts/*'], @@ -266,10 +293,10 @@ module.exports = function (grunt) { } } }); - grunt.registerTask('default', ['download-binary', 'newer:babel', 'newer:less', 'newer:copy:dev', 'shell:electron', 'watchChokidar']); + grunt.registerTask('default', ['download-binary', 'newer:babel', 'less', 'newer:copy:dev', 'shell:electron', 'watchChokidar']); if (process.platform === 'win32') { - grunt.registerTask('release', ['clean', 'download-binary', 'babel', 'less', 'copy:dev', 'electron:windows', 'copy:windows', 'create-windows-installer', 'rename:installer']); + grunt.registerTask('release', ['clean', 'download-binary', 'babel', 'less', 'copy:dev', 'electron:windows', 'copy:windows', 'rcedit:exes', 'create-windows-installer', 'rename:installer']); } else { grunt.registerTask('release', ['clean:dist', 'clean:build', 'download-binary', 'babel', 'less', 'copy:dev', 'electron:osx', 'copy:osx', 'shell:sign', 'shell:zip']); } diff --git a/package.json b/package.json index 88fb701a22..46b64fb55d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Kitematic", - "version": "0.6.6", + "version": "0.6.7", "author": "Kitematic", "description": "Simple Docker Container management for Mac OS X.", "homepage": "https://kitematic.com/", @@ -80,6 +80,7 @@ "grunt-electron": "^1.0.0", "grunt-electron-installer": "^0.33.0", "grunt-newer": "^1.1.1", + "grunt-rcedit": "^0.3.1", "grunt-rename": "^0.1.4", "grunt-shell": "^1.1.2", "grunt-shell-spawn": "^0.3.8", diff --git a/resources/MSYS_LICENSE b/resources/MSYS_LICENSE new file mode 100644 index 0000000000..b87e6b1bc7 --- /dev/null +++ b/resources/MSYS_LICENSE @@ -0,0 +1,20 @@ +Kitematic includes (but does not link to) various DLLs included with the msysgit Git-1.9.5-preview20150319 distribution. Included is the MSYS runtime license. +Source is available online at https://github.com/msysgit/git/tree/v1.9.5.msysgit.1 + +File: MSYS_LICENSE +Copyright (C): 2001, Earnie Boyd +File $Revision$ +File Revision $Date$ +MSYS Release: 1.0.2 +MSYS Release Date: November 30th, 2001 + +The software, both source and binary forms, are covered via differing licenses. +Each license has it's own set of rules so please make sure you read them +carefully to see how it applies to you, particularly if you're going to +distribute the software. + +The MSYS runtime software source can found in the winsup/cygwin directory. The +existing code portions of this source is covered by the CYGWIN_LICENSE which can +be found in this directory in a file by the name of CYGWIN_LICENSE. MSYS +specific software code added regardless of existing license is covered by the +ESPL which can be found in a file by the same name. diff --git a/resources/OPENSSH_LICENSE b/resources/OPENSSH_LICENSE new file mode 100644 index 0000000000..2c300483f4 --- /dev/null +++ b/resources/OPENSSH_LICENSE @@ -0,0 +1,205 @@ +Kitematic includes OpenSSH ssh.exe from the msysgit distribution version Git-1.9.5-preview20150319, available online at https://github.com/msysgit/git/tree/v1.9.5.msysgit.1 + +This file is part of the OpenSSH software. + +The licences which components of this software fall under are as +follows. First, we will summarize and say that all components +are under a BSD licence, or a licence more free than that. + +OpenSSH contains no GPL code. + +1) + * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland + * All rights reserved + * + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * software must be clearly marked as such, and if the derived work is + * incompatible with the protocol description in the RFC file, it must be + * called by a name other than "ssh" or "Secure Shell". + + [Tatu continues] + * However, I am not implying to give any licenses to any patents or + * copyrights held by third parties, and the software includes parts that + * are not under my direct control. As far as I know, all included + * source code is used in accordance with the relevant license agreements + * and can be used freely for any purpose (the GNU license being the most + * restrictive); see below for details. + + [However, none of that term is relevant at this point in time. All of + these restrictively licenced software components which he talks about + have been removed from OpenSSH, i.e., + + - RSA is no longer included, found in the OpenSSL library + - IDEA is no longer included, its use is deprecated + - DES is now external, in the OpenSSL library + - GMP is no longer used, and instead we call BN code from OpenSSL + - Zlib is now external, in a library + - The make-ssh-known-hosts script is no longer included + - TSS has been removed + - MD5 is now external, in the OpenSSL library + - RC4 support has been replaced with ARC4 support from OpenSSL + - Blowfish is now external, in the OpenSSL library + + [The licence continues] + + Note that any information and cryptographic algorithms used in this + software are publicly available on the Internet and at any major + bookstore, scientific library, and patent office worldwide. More + information can be found e.g. at "http://www.cs.hut.fi/crypto". + + The legal status of this program is some combination of all these + permissions and restrictions. Use only at your own responsibility. + You will be responsible for any legal consequences yourself; I am not + making any claims whether possessing or using this is legal or not in + your country, and I am not taking any responsibility on your behalf. + + + NO WARRANTY + + BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY + FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN + OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES + PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED + OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS + TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE + PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, + REPAIR OR CORRECTION. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING + WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR + REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, + INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING + OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED + TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY + YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER + PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE + POSSIBILITY OF SUCH DAMAGES. + +2) + The 32-bit CRC compensation attack detector in deattack.c was + contributed by CORE SDI S.A. under a BSD-style license. + + * Cryptographic attack detector for ssh - source code + * + * Copyright (c) 1998 CORE SDI S.A., Buenos Aires, Argentina. + * + * All rights reserved. Redistribution and use in source and binary + * forms, with or without modification, are permitted provided that + * this copyright notice is retained. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES ARE DISCLAIMED. IN NO EVENT SHALL CORE SDI S.A. BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR + * CONSEQUENTIAL DAMAGES RESULTING FROM THE USE OR MISUSE OF THIS + * SOFTWARE. + * + * Ariel Futoransky + * + +3) + ssh-keyscan was contributed by David Mazieres under a BSD-style + license. + + * Copyright 1995, 1996 by David Mazieres . + * + * Modification and redistribution in source and binary forms is + * permitted provided that due credit is given to the author and the + * OpenBSD project by leaving this copyright notice intact. + +4) + The Rijndael implementation by Vincent Rijmen, Antoon Bosselaers + and Paulo Barreto is in the public domain and distributed + with the following license: + + * @version 3.0 (December 2000) + * + * Optimised ANSI C code for the Rijndael cipher (now AES) + * + * @author Vincent Rijmen + * @author Antoon Bosselaers + * @author Paulo Barreto + * + * This code is hereby placed in the public domain. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +5) + One component of the ssh source code is under a 3-clause BSD license, + held by the University of California, since we pulled these parts from + original Berkeley code. + + * Copyright (c) 1983, 1990, 1992, 1993, 1995 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + +6) + Remaining components of the software are provided under a standard + 2-term BSD licence with the following names as copyright holders: + + Markus Friedl + Theo de Raadt + Niels Provos + Dug Song + Aaron Campbell + Damien Miller + Kevin Steves + Daniel Kouril + Wesley Griffin + Per Allansson + Nils Nordman + Simon Wilkinson + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/resources/msys-1.0.dll b/resources/msys-1.0.dll new file mode 100644 index 0000000000..0c6d58a0ba Binary files /dev/null and b/resources/msys-1.0.dll differ diff --git a/resources/msys-crypto-1.0.0.dll b/resources/msys-crypto-1.0.0.dll new file mode 100644 index 0000000000..3ee99ab4a7 Binary files /dev/null and b/resources/msys-crypto-1.0.0.dll differ diff --git a/resources/msys-minires.dll b/resources/msys-minires.dll new file mode 100644 index 0000000000..ca06cef6f8 Binary files /dev/null and b/resources/msys-minires.dll differ diff --git a/resources/msys-z.dll b/resources/msys-z.dll new file mode 100644 index 0000000000..a29dba8dcb Binary files /dev/null and b/resources/msys-z.dll differ diff --git a/resources/ssh.exe b/resources/ssh.exe new file mode 100644 index 0000000000..13c2d066e5 Binary files /dev/null and b/resources/ssh.exe differ diff --git a/src/actions/ContainerActions.js b/src/actions/ContainerActions.js index d3e5996d4e..5146b4222c 100644 --- a/src/actions/ContainerActions.js +++ b/src/actions/ContainerActions.js @@ -28,9 +28,9 @@ class ContainerActions { dockerUtil.restart(name); } - update (name, containerOpts) { - this.dispatch({name, containerOpts}); - dockerUtil.updateContainer(name, containerOpts); + update (name, container) { + this.dispatch({name, container}); + dockerUtil.updateContainer(name, container); } clearPending () { diff --git a/src/browser.js b/src/browser.js index bc0979d16b..208597fda6 100644 --- a/src/browser.js +++ b/src/browser.js @@ -2,8 +2,10 @@ var app = require('app'); var autoUpdater = require('auto-updater'); var BrowserWindow = require('browser-window'); var fs = require('fs'); +var os = require('os'); var ipc = require('ipc'); var path = require('path'); +var child_process = require('child_process'); process.env.NODE_PATH = path.join(__dirname, 'node_modules'); process.env.RESOURCES_PATH = path.join(__dirname, '/../resources'); @@ -17,45 +19,28 @@ try { settingsjson = JSON.parse(fs.readFileSync(path.join(__dirname, 'settings.json'), 'utf8')); } catch (err) {} -var handleStartupEvent = function() { - if (process.platform !== 'win32') { - return false; - } +let updateCmd = (args, cb) => { + let updateExe = path.resolve(path.dirname(process.execPath), '..', 'Update.exe'); + let child = child_process.spawn(updateExe, args, {detached: true}); + child.on('close', cb); +}; +if (process.platform === 'win32') { var squirrelCommand = process.argv[1]; + let target = path.basename(process.execPath); switch (squirrelCommand) { case '--squirrel-install': case '--squirrel-updated': - - // Optionally do things such as: - // - // - Install desktop and start menu shortcuts - // - Add your .exe to the PATH - // - Write to the registry for things like file associations and - // explorer context menus - - // Always quit when done - app.quit(); - - return true; + updateCmd(['--createShortcut', target], app.quit); + break; case '--squirrel-uninstall': - // Undo anything you did in the --squirrel-install and - // --squirrel-updated handlers - - // Always quit when done - app.quit(); - - return true; + updateCmd(['--removeShortcut', target], app.quit); + break; case '--squirrel-obsolete': - // This is called on the outgoing version of your app before - // we update to the new version - it's the opposite of - // --squirrel-updated app.quit(); - return true; + break; } -}; - -handleStartupEvent(); +} var openURL = null; app.on('open-url', function (event, url) { @@ -72,7 +57,7 @@ app.on('ready', function () { 'standard-window': false, resizable: true, frame: false, - show: false, + show: false }); mainWindow.loadUrl(path.normalize('file://' + path.join(__dirname, 'index.html'))); @@ -90,12 +75,21 @@ app.on('ready', function () { autoUpdater.quitAndInstall(); }); - app.on('before-quit', function () { - // TODO: make this work for right click + close - if (!updating && mainWindow.webContents) { + if (os.platform() === 'win32') { + mainWindow.on('close', function () { mainWindow.webContents.send('application:quitting'); - } - }); + return true; + }); + app.on('window-all-closed', function() { + app.quit(); + }); + } else if (os.platform() === 'darwin') { + app.on('before-quit', function () { + if (!updating) { + mainWindow.webContents.send('application:quitting'); + } + }); + } mainWindow.webContents.on('new-window', function (e) { e.preventDefault(); @@ -125,7 +119,7 @@ app.on('ready', function () { }); if (process.env.NODE_ENV !== 'development') { - autoUpdater.setFeedUrl('https://updates.kitematic.com/releases/latest?version=' + app.getVersion() + '&beta=' + !!settingsjson.beta); + autoUpdater.setFeedUrl('https://updates.kitematic.com/releases/latest?version=' + app.getVersion() + '&beta=' + !!settingsjson.beta + '&platform=' + os.platform()); } }); diff --git a/src/components/ContainerHomeFolders.react.js b/src/components/ContainerHomeFolders.react.js index f2b9912684..53ba985fe5 100644 --- a/src/components/ContainerHomeFolders.react.js +++ b/src/components/ContainerHomeFolders.react.js @@ -18,18 +18,18 @@ var ContainerHomeFolder = React.createClass({ from: 'home' }); - if (hostVolume.indexOf(process.env.HOME) === -1) { + if (hostVolume.indexOf(util.windowsToLinuxPath(util.home())) === -1) { dialog.showMessageBox({ message: 'Enable all volumes to edit files via Finder? This may not work with all database containers.', buttons: ['Enable Volumes', 'Cancel'] }, (index) => { if (index === 0) { var volumes = _.clone(this.props.container.Volumes); - var newHostVolume = path.join(util.home(), util.documents(), 'Kitematic', this.props.container.Name, containerVolume); + var newHostVolume = util.escapePath(path.join(util.home(), util.documents(), 'Kitematic', this.props.container.Name, containerVolume)); volumes[containerVolume] = newHostVolume; var binds = _.pairs(volumes).map(function (pair) { if(util.isWindows()) { - return util.windowsToLinuxPath(pair[1]) + ':' + pair[0]; + return util.windowsToLinuxPath(pair[1]) + ':' + pair[0]; } return pair[1] + ':' + pair[0]; }); @@ -44,7 +44,8 @@ var ContainerHomeFolder = React.createClass({ } }); } else { - shell.showItemInFolder(hostVolume); + let path = util.isWindows() ? util.linuxToWindowsPath(hostVolume) : hostVolume; + shell.showItemInFolder(path); } }, handleClickChangeFolders: function () { diff --git a/src/components/ContainerHomePreview.react.js b/src/components/ContainerHomePreview.react.js index 29ff41c9de..ddb4670449 100644 --- a/src/components/ContainerHomePreview.react.js +++ b/src/components/ContainerHomePreview.react.js @@ -92,7 +92,7 @@ var ContainerHomePreview = React.createClass({ DOCKER PORT - MAC PORT + ACCESS URL diff --git a/src/components/ContainerSettingsPorts.react.js b/src/components/ContainerSettingsPorts.react.js index e81be6ebd6..1738db5a5b 100644 --- a/src/components/ContainerSettingsPorts.react.js +++ b/src/components/ContainerSettingsPorts.react.js @@ -46,7 +46,7 @@ var ContainerSettingsPorts = React.createClass({ }, render: function () { if (!this.props.container) { - return (
); + return false; } var ports = _.map(_.pairs(this.state.ports), pair => { var key = pair[0]; @@ -66,7 +66,7 @@ var ContainerSettingsPorts = React.createClass({ DOCKER PORT - MAC PORT + ACCESS URL diff --git a/src/components/ContainerSettingsVolumes.react.js b/src/components/ContainerSettingsVolumes.react.js index fe2572eae7..0ce4136456 100644 --- a/src/components/ContainerSettingsVolumes.react.js +++ b/src/components/ContainerSettingsVolumes.react.js @@ -9,57 +9,71 @@ var containerActions = require('../actions/ContainerActions'); var ContainerSettingsVolumes = React.createClass({ handleChooseVolumeClick: function (dockerVol) { - var self = this; dialog.showOpenDialog({properties: ['openDirectory', 'createDirectory']}, (filenames) => { if (!filenames) { return; } - var directory = filenames[0]; - if (directory) { - metrics.track('Choose Directory for Volume'); - if(util.isWindows()) { - directory = util.windowsToLinuxPath(directory); - } - var volumes = _.clone(self.props.container.Volumes); - volumes[dockerVol] = directory; - var binds = _.pairs(volumes).map(function (pair) { - return pair[1] + ':' + pair[0]; - }); - containerActions.update(this.props.container.Name, {Binds: binds, Volumes: volumes}); + var directory = filenames[0]; + + if (!directory || directory.indexOf(util.home()) === -1) { + dialog.showMessageBox({ + type: 'warning', + buttons: ['OK'], + message: 'Invalid directory. Volume directories must be under your Users directory' + }); + return; } + + metrics.track('Choose Directory for Volume'); + if(util.isWindows()) { + directory = util.escapePath(util.windowsToLinuxPath(directory)); + } + var volumes = _.clone(this.props.container.Volumes); + volumes[dockerVol] = directory; + var binds = _.pairs(volumes).map(function (pair) { + return pair[1] + ':' + pair[0]; + }); + + containerActions.update(this.props.container.Name, {Binds: binds, Volumes: volumes}); }); }, handleRemoveVolumeClick: function (dockerVol) { metrics.track('Removed Volume Directory', { from: 'settings' }); + + var hostConfig = _.clone(this.props.container.HostConfig); + var binds = hostConfig.Binds; var volumes = _.clone(this.props.container.Volumes); - delete volumes[dockerVol]; - var binds = _.pairs(volumes).map(function (pair) { - return pair[1] + ':' + pair[0]; - }); - containerActions.update(this.props.container.Name, {Binds: binds, Volumes: volumes}); + volumes[dockerVol] = 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, Volumes: volumes}); }, handleOpenVolumeClick: function (path) { metrics.track('Opened Volume Directory', { from: 'settings' }); - shell.showItemInFolder(path); + shell.showItemInFolder(util.linuxToWindowsPath(path)); }, render: function () { if (!this.props.container) { return false; } + var homeDir = util.isWindows() ? util.windowsToLinuxPath(util.home()) : util.home(); var volumes = _.map(this.props.container.Volumes, (val, key) => { - if (!val || val.indexOf(process.env.HOME) === -1) { + if (!val || val.indexOf(homeDir) === -1) { val = ( No Folder ); } else { + let local = util.isWindows() ? util.linuxToWindowsPath(val) : val; val = ( - {val.replace(process.env.HOME, '~')} + {local.replace(process.env.HOME, '~')} ); } return ( @@ -81,7 +95,7 @@ var ContainerSettingsVolumes = React.createClass({ DOCKER FOLDER - MAC FOLDER + LOCAL FOLDER diff --git a/src/components/Header.react.js b/src/components/Header.react.js index a6c4ed7f0b..abdbe0e02c 100644 --- a/src/components/Header.react.js +++ b/src/components/Header.react.js @@ -4,6 +4,7 @@ var RetinaImage = require('react-retina-image'); var remote = require('remote'); var ipc = require('ipc'); var autoUpdater = remote.require('auto-updater'); +var util = require('../utils/Util'); var metrics = require('../utils/MetricsUtil'); var Menu = remote.require('menu'); var MenuItem = remote.require('menu-item'); @@ -52,7 +53,11 @@ var Header = React.createClass({ } }, handleClose: function () { - remote.getCurrentWindow().hide(); + if (util.isWindows()) { + remote.getCurrentWindow().close(); + } else { + remote.getCurrentWindow().hide(); + } }, handleMinimize: function () { remote.getCurrentWindow().minimize(); @@ -94,22 +99,29 @@ var Header = React.createClass({ }); accountActions.verify(); }, + renderLogo: function () { + return ( +
+ +
+ ); + }, renderWindowButtons: function () { let buttons; - if (this.state.fullscreen) { + if (util.isWindows()) { buttons = ( -
-
-
-
+
+
+
+
); } else { buttons = (
-
-
-
+
+
+
); } @@ -150,16 +162,14 @@ var Header = React.createClass({ return (
- {this.renderWindowButtons()} + {util.isWindows () ? this.renderLogo() : this.renderWindowButtons()} {username}
{updateWidget}
-
- -
+ {util.isWindows () ? this.renderWindowButtons() : this.renderLogo()}
); @@ -173,9 +183,10 @@ var Header = React.createClass({ return (
- {this.renderWindowButtons()} + {util.isWindows () ? null : this.renderWindowButtons()}
+ {util.isWindows () ? this.renderWindowButtons() : null}
); diff --git a/src/stores/ContainerStore.js b/src/stores/ContainerStore.js index 3899a878e7..da39037352 100644 --- a/src/stores/ContainerStore.js +++ b/src/stores/ContainerStore.js @@ -74,7 +74,7 @@ class ContainerStore { return; } - deepExtend(containers[name], container); + _.extend(containers[name], container); if (containers[name].State) { containers[name].State.Updating = true; @@ -89,7 +89,8 @@ class ContainerStore { return; } // Trigger log update - LogStore.fetch(container.Name); + // TODO: fix this loading multiple times + // LogStore.fetch(container.Name); containers[container.Name] = container; diff --git a/src/stores/SetupStore.js b/src/stores/SetupStore.js index 7c99a0ff64..7d8061bb55 100644 --- a/src/stores/SetupStore.js +++ b/src/stores/SetupStore.js @@ -49,7 +49,7 @@ var _steps = [{ } catch (err) { throw null; } - } else if (util.isWindows() && !virtualBox.active()) { + } else if (!util.isWindows() && !virtualBox.active()) { yield util.exec(setupUtil.macSudoCmd(util.escapePath('/Library/Application Support/VirtualBox/LaunchDaemons/VirtualBoxStartup.sh') + ' restart')); } }) @@ -59,7 +59,7 @@ var _steps = [{ message: 'To run Docker containers on your computer, Kitematic is starting a Linux virtual machine. This may take a minute...', totalPercent: 60, percent: 0, - seconds: 80, + seconds: 110, run: Promise.coroutine(function* (progressCallback) { setupUtil.simulateProgress(this.seconds, progressCallback); var exists = yield machine.exists(); @@ -149,7 +149,7 @@ var SetupStore = assign(Object.create(EventEmitter.prototype), { var vboxNeedsInstall = !virtualBox.installed(); required.download = vboxNeedsInstall && (!fs.existsSync(vboxfile) || setupUtil.checksum(vboxfile) !== virtualBox.checksum()); - required.install = vboxNeedsInstall || (util.isWindows() && !virtualBox.active()); + required.install = vboxNeedsInstall || (!util.isWindows() && !virtualBox.active()); required.init = required.install || !(yield machine.exists()) || (yield machine.state()) !== 'Running' || !isoversion || util.compareVersions(isoversion, packagejson['docker-version']) < 0; var exists = yield machine.exists(); diff --git a/src/utils/DockerMachineUtil.js b/src/utils/DockerMachineUtil.js index 37532dc690..576148a8f1 100644 --- a/src/utils/DockerMachineUtil.js +++ b/src/utils/DockerMachineUtil.js @@ -5,7 +5,7 @@ var fs = require('fs'); var util = require('./Util'); var resources = require('./ResourcesUtil'); -var NAME = 'dev'; +var NAME = util.isWindows () ? 'kitematic' : 'dev'; var DockerMachine = { command: function () { @@ -60,7 +60,7 @@ var DockerMachine = { if (util.isWindows()) { return util.exec([this.command(), '-D', 'create', '-d', 'virtualbox', '--virtualbox-memory', '2048', NAME]); } else { - return util.exec([this.command(), '-D', 'create', '-d', 'virtualbox', '--virtualbox-boot2docker-url', path.join(process.cwd(), 'resources', 'boot2docker-' + dockerversion + '.iso'), '--virtualbox-memory', '2048', NAME]); + return util.exec([this.command(), '-D', 'create', '-d', 'virtualbox' ,'--virtualbox-boot2docker-url', path.join(process.cwd(), 'resources', 'boot2docker-' + dockerversion + '.iso'), '--virtualbox-memory', '2048', NAME]); } }, start: function () { @@ -161,8 +161,7 @@ var DockerMachine = { {env: { 'DOCKER_HOST' : machine.url, 'DOCKER_CERT_PATH' : path.join(util.home(), '.docker/machine/machines/' + machine.name), - 'DOCKER_TLS_VERIFY': 1, - 'PATH': resources.resourceDir() + 'DOCKER_TLS_VERIFY': 1 } }); }); @@ -173,7 +172,7 @@ var DockerMachine = { util.exec(cmd).then(() => {}); }); } - } + }, }; module.exports = DockerMachine; diff --git a/src/utils/DockerUtil.js b/src/utils/DockerUtil.js index 46dceeb5a1..9570d62305 100644 --- a/src/utils/DockerUtil.js +++ b/src/utils/DockerUtil.js @@ -25,8 +25,6 @@ export default { throw new Error('Certificate directory does not exist'); } - console.log(ip); - this.host = ip; this.client = new dockerode({ protocol: 'https', @@ -369,13 +367,14 @@ export default { let columns = {}; columns.amount = 4; // arbitrary columns.toFill = 0; // the current column index, waiting for layer IDs to be displayed + let error = null; // data is associated with one layer only (can be identified with id) stream.on('data', str => { var data = JSON.parse(str); if (data.error) { - callback(data.error); + error = data.error; return; } @@ -449,7 +448,7 @@ export default { } }); stream.on('end', function () { - callback(); + callback(error); }); }); }, diff --git a/src/utils/HubUtil.js b/src/utils/HubUtil.js index e14ca89ef3..ef186ed952 100644 --- a/src/utils/HubUtil.js +++ b/src/utils/HubUtil.js @@ -74,11 +74,10 @@ module.exports = { let data = JSON.parse(body); if (response.statusCode === 200 && data && data.token) { localStorage.setItem('auth.jwt', data.token); + this.request(req, callback); } else { this.logout(); } - - this.request(req, callback); }); } else { callback(error, response, body); diff --git a/src/utils/Util.js b/src/utils/Util.js index 4566025bf5..adc40cf580 100644 --- a/src/utils/Util.js +++ b/src/utils/Util.js @@ -10,6 +10,15 @@ 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; + } + } + let fn = Array.isArray(args) ? exec : child_process.exec; return new Promise((resolve, reject) => { fn(args, options, (stderr, stdout, code) => { @@ -44,7 +53,8 @@ module.exports = { return app.getPath('home'); }, documents: function () { - return this.isWindows() ? 'My\ Documents' : 'Documents'; + // TODO: fix me for windows 7 + return 'Documents'; }, supportDir: function () { return app.getPath('userData'); @@ -141,5 +151,8 @@ module.exports = { } return fullPath; }, + linuxToWindowsPath: function (linuxAbsPath) { + return linuxAbsPath.replace('/c', 'C:').split('/').join('\\'); + }, webPorts: ['80', '8000', '8080', '3000', '5000', '2368', '9200', '8983'] }; diff --git a/styles/header.less b/styles/header.less index 329756ca32..49040cf224 100644 --- a/styles/header.less +++ b/styles/header.less @@ -12,12 +12,12 @@ .no-drag { -webkit-app-region: no-drag; } - + .left-header { display: flex; min-width: @sidebar-width + 1px; } - + .right-header { display: flex; justify-content: flex-end; @@ -33,9 +33,9 @@ } .logo { - padding: 0.9rem 1rem 0 0; + padding: 0.9rem 1rem 0 1rem; } - + .login-wrapper { flex: 1 auto; display: flex; @@ -65,6 +65,81 @@ } } + .windows-buttons { + display: flex; + align-items: flex-start; + justify-content: flex-end; + flex: 0 1 auto; + margin-right: 7px; + -webkit-app-region: no-drag; + + .windows-button { + height: 25px; + margin-left: 1px; + -webkit-transition: -webkit-filter 0.3s; + + &:hover { + -webkit-transition: -webkit-filter 0s; + } + + &.button-minimize, &.button-fullscreen, &.button-fullscreenclose { + min-width: 34px; + background-color: white; + + .icon { + background-repeat: no-repeat; + -webkit-filter: brightness(0.3); + height: 25px; + } + + &:hover { + -webkit-filter: brightness(0.9); + } + + &:active { + -webkit-filter: brightness(0.8); + } + } + + &.button-minimize { + .icon { + background-position: 50% 18px; + .at2x('windows-minimize.png', 14px, 2px); + } + } + + &.button-fullscreen { + .icon { + background-position: center; + .at2x('windows-fullscreen.png', 14px, 2px); + } + } + + &.button-fullscreenclose { + .icon { + background-position: center; + .at2x('windows-fullscreenclose.png', 14px, 2px); + } + } + + &.button-close { + min-width: 58px; + background: #C75050; + .at2x('windows-close.png', 12px, 9px); + background-repeat: no-repeat; + background-position: center; + box-shadow: inset 0 0 0 -1px rgba(255, 255, 255, 0.4); + &:hover { + -webkit-filter: saturate(120%); + } + + &:active { + -webkit-filter: brightness(0.8); + } + } + } + } + .buttons { display: flex; margin: 0 1.5rem; diff --git a/styles/setup.less b/styles/setup.less index 20184deeec..6d29178240 100644 --- a/styles/setup.less +++ b/styles/setup.less @@ -10,9 +10,9 @@ flex-direction: row; flex: 1 auto; - + padding: 2rem; - + .image { display: flex; flex: 1 auto; @@ -163,7 +163,7 @@ .btn-close { -webkit-app-region: no-drag; position: absolute; - top: 16px; + bottom: 16px; right: 16px; font-size: 14px; }