diff --git a/.gitignore b/.gitignore index e51ebc5468..8fe91862fb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .swp build dist +installer node_modules coverage npm-debug.log diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000000..c415bae736 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,308 @@ +var path = require('path'); +var execFile = require('child_process').execFile; +var packagejson = require('./package.json'); +var electron = require('electron-prebuilt'); + +var WINDOWS_DOCKER_URL = 'https://get.docker.com/builds/Windows/x86_64/docker-1.6.2.exe'; +var DARWIN_DOCKER_URL = 'https://get.docker.com/builds/Darwin/x86_64/docker-' + packagejson['docker-version']; +var WINDOWS_DOCKER_MACHINE_URL = 'https://github.com/docker/machine/releases/download/v' + packagejson['docker-machine-version'] + '/docker-machine_windows-amd64.exe'; +var DARWIN_DOCKER_MACHINE_URL = 'https://github.com/docker/machine/releases/download/v' + packagejson['docker-machine-version'] + '/docker-machine_darwin-amd64'; +var DARWIN_COMPOSE_URL = 'https://github.com/docker/compose/releases/download/' + packagejson['docker-compose-version'] + '/docker-compose-Darwin-x86_64'; +var BOOT2DOCKER_ISO_URL = 'https://github.com/boot2docker/boot2docker/releases/download/v' + packagejson['docker-version'] + '/boot2docker.iso'; + +module.exports = function (grunt) { + require('load-grunt-tasks')(grunt); + var target = grunt.option('target') || 'development'; + var beta = grunt.option('beta') || false; + var env = process.env; + env.NODE_PATH = '..:' + env.NODE_PATH; + env.NODE_ENV = target; + + var version = function (str) { + var match = str.match(/(\d+\.\d+\.\d+)/); + return match ? match[1] : null; + }; + + grunt.registerMultiTask('download-binary', 'Downloads binary unless version up to date', function () { + var target = grunt.task.current.target; + var done = this.async(); + var config = grunt.config('download-binary')[target]; + execFile(config.binary, ['--version'], function (err, stdout) { + var currentVersion = version(stdout); + if (!currentVersion || currentVersion !== version(config.version)) { + grunt.task.run('curl:' + target); + grunt.task.run('chmod'); + } + done(); + }); + }); + + var APPNAME = beta ? 'Kitematic (Beta)' : 'Kitematic'; + var OSX_OUT = './dist/osx'; + var OSX_FILENAME = OSX_OUT + '/' + APPNAME + '.app'; + + grunt.initConfig({ + IDENTITY: 'Developer ID Application: Docker Inc', + APPNAME: APPNAME, + OSX_OUT: OSX_OUT, + OSX_FILENAME: OSX_FILENAME, + OSX_FILENAME_ESCAPED: OSX_FILENAME.replace(' ', '\\ ').replace('(','\\(').replace(')','\\)'), + + // electron + electron: { + windows: { + options: { + name: '<%= APPNAME %>', + dir: 'build/', + out: 'dist/', + version: packagejson['electron-version'], + platform: 'win32', + arch: 'x64', + asar: true, + icon: 'util/kitematic.ico' + } + }, + osx: { + options: { + name: '<%= APPNAME %>', + dir: 'build/', + out: '<%= OSX_OUT %>', + version: packagejson['electron-version'], + platform: 'darwin', + arch: 'x64', + asar: true, + 'app-bundle-id': 'com.kitematic.kitematic' + } + } + }, + + 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', + description: 'Kitematic', + title: 'Kitematic', + version: packagejson.version + }, + + // docker binaries + 'download-binary': { + docker: { + version: packagejson['docker-version'], + binary: path.join('resources', 'docker'), + download: 'curl:docker' + }, + 'docker-machine': { + version: packagejson['docker-machine-version'], + binary: path.join('resources', 'docker-machine'), + download: 'curl:docker-machine' + } + }, + + // images + copy: { + dev: { + files: [{ + expand: true, + cwd: '.', + src: ['package.json', 'settings.json', 'index.html'], + dest: 'build/' + }, { + expand: true, + cwd: 'images/', + src: ['**/*'], + dest: 'build/' + }, { + expand: true, + cwd: 'fonts/', + src: ['**/*'], + dest: 'build/' + }, { + cwd: 'node_modules/', + src: Object.keys(packagejson.dependencies).map(function (dep) { return dep + '/**/*';}), + dest: 'build/node_modules/', + expand: true + }] + }, + windows: { + files: [{ + expand: true, + cwd: 'resources', + src: ['docker*', 'boot2docker.iso', 'ssh.exe', 'OPENSSH_LICENSE', 'msys-*'], + dest: 'dist/Kitematic-win32/resources/resources/' + }], + options: { + mode: true + } + }, + osx: { + files: [{ + expand: true, + cwd: 'resources', + src: ['docker*', 'boot2docker.iso', 'macsudo', 'terminal'], + dest: '<%= OSX_FILENAME %>/Contents/Resources/resources/' + }, { + src: 'util/kitematic.icns', + dest: '<%= OSX_FILENAME %>/Contents/Resources/atom.icns' + }], + options: { + mode: true + } + } + }, + + rename: { + installer: { + src: 'installer/Setup.exe', + dest: 'installer/KitematicSetup-' + packagejson.version + '.exe' + } + }, + + // download binaries + curl: { + docker: { + src: process.platform === 'win32' ? WINDOWS_DOCKER_URL : DARWIN_DOCKER_URL, + dest: process.platform === 'win32' ? path.join('resources', 'docker.exe') : path.join('resources', 'docker') + }, + 'docker-machine': { + src: process.platform === 'win32' ? WINDOWS_DOCKER_MACHINE_URL : DARWIN_DOCKER_MACHINE_URL, + dest: process.platform === 'win32' ? path.join('resources', 'docker-machine.exe') : path.join('resources', 'docker-machine') + }, + 'docker-compose': { + src: DARWIN_COMPOSE_URL, + dest: 'resources/docker-compose' + }, + 'boot2docker-iso': { + src: BOOT2DOCKER_ISO_URL, + dest: path.join('resources', 'boot2docker-' + packagejson['docker-version']) + } + }, + + chmod: { + binaries: { + options: { + mode: '755' + }, + src: ['resources/docker*'] + } + }, + + // styles + less: { + options: { + sourceMapFileInline: true + }, + dist: { + files: { + 'build/main.css': 'styles/main.less' + } + } + }, + + // javascript + babel: { + options: { + sourceMap: 'inline', + blacklist: 'regenerator' + }, + dist: { + files: [{ + expand: true, + cwd: 'src/', + src: ['**/*.js'], + dest: 'build/', + }] + } + }, + + shell: { + electron: { + command: electron + ' ' + 'build', + options: { + async: true, + execOptions: { + env: env + } + } + }, + sign: { + options: { + failOnError: false, + }, + command: [ + 'codesign --deep -v -f -s "<%= IDENTITY %>" <%= OSX_FILENAME_ESCAPED %>/Contents/Frameworks/*', + 'codesign -v -f -s "<%= IDENTITY %>" <%= OSX_FILENAME_ESCAPED %>', + 'codesign -vvv --display <%= OSX_FILENAME_ESCAPED %>', + 'codesign -v --verify <%= OSX_FILENAME_ESCAPED %>', + ].join(' && '), + }, + zip: { + command: 'ditto -c -k --sequesterRsrc --keepParent <%= OSX_FILENAME_ESCAPED %> <%= OSX_OUT %>/Kitematic-' + packagejson.version + '.zip', + } + }, + + clean: { + release: ['build/', 'dist/', 'installer/'], + }, + + // livereload + watchChokidar: { + options: { + spawn: true + }, + livereload: { + options: {livereload: true}, + files: ['build/**/*'] + }, + js: { + files: ['src/**/*.js'], + tasks: ['newer:babel'] + }, + less: { + files: ['styles/**/*.less'], + tasks: ['less'] + }, + copy: { + files: ['images/*', 'index.html', 'fonts/*'], + tasks: ['newer:copy:dev'] + } + } + }); + 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', 'rcedit:exes', 'create-windows-installer', 'rename:installer']); + } else { + grunt.registerTask('release', ['clean', 'download-binary', 'babel', 'less', 'copy:dev', 'electron:osx', 'copy:osx', 'shell:sign', 'shell:zip']); + } + + process.on('SIGINT', function () { + grunt.task.run(['shell:electron:kill']); + process.exit(1); + }); +}; diff --git a/package.json b/package.json index a2ee190495..46b64fb55d 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,22 @@ { "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/", - "main": "build/browser.js", + "main": "browser.js", "repository": { "type": "git", "url": "git@github.com:kitematic/kitematic.git" }, "bugs": "https://github.com/kitematic/kitematic/issues", "scripts": { - "start": "gulp", + "start": "grunt", "test": "jest -c jest-unit.json", "integration": "jest -c jest-integration.json", - "release": "gulp release", - "release:beta": "gulp release --beta", - "lint": "jsxhint src", - "reset": "gulp reset" + "release": "grunt release", + "release:beta": "grunt release --beta=true", + "lint": "jsxhint src" }, "licenses": [ { @@ -67,25 +66,31 @@ "devDependencies": { "babel": "^5.1.10", "babel-jest": "^5.2.0", - "gulp": "^3.8.11", - "gulp-babel": "^5.1.0", - "gulp-changed": "^1.2.1", - "gulp-concat": "^2.5.2", - "gulp-cssmin": "^0.1.6", - "gulp-download-electron": "^0.0.5", - "gulp-if": "^1.2.5", - "gulp-insert": "^0.4.0", - "gulp-less": "^3.0.2", - "gulp-livereload": "^3.8.0", - "gulp-plumber": "^1.0.0", - "gulp-shell": "^0.4.1", - "gulp-sourcemaps": "^1.5.2", - "gulp-util": "^3.0.4", + "electron-prebuilt": "^0.27.3", + "grunt": "^0.4.5", + "grunt-babel": "^5.0.1", + "grunt-chmod": "^1.0.3", + "grunt-cli": "^0.1.13", + "grunt-contrib-clean": "^0.6.0", + "grunt-contrib-copy": "^0.8.0", + "grunt-contrib-less": "^1.0.1", + "grunt-contrib-watch-chokidar": "^1.0.0", + "grunt-curl": "^2.2.0", + "grunt-download-electron": "^2.1.1", + "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", "jest-cli": "^0.4.5", "jsxhint": "^0.14.0", + "load-grunt-tasks": "^3.2.0", "minimist": "^1.1.1", "react-tools": "^0.13.1", "run-sequence": "^1.0.2", + "shell-escape": "^0.2.0", "source-map-support": "^0.2.10" } } 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 a9f087e7c8..208597fda6 100644 --- a/src/browser.js +++ b/src/browser.js @@ -2,12 +2,13 @@ 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.NODE_PATH = path.join(__dirname, 'node_modules'); process.env.RESOURCES_PATH = path.join(__dirname, '/../resources'); -process.chdir(path.join(__dirname, '..')); process.env.PATH = '/usr/local/bin:' + process.env.PATH; var size = {}, settingsjson = {}; @@ -15,9 +16,32 @@ try { size = JSON.parse(fs.readFileSync(path.join(process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'], 'Library', 'Application\ Support', 'Kitematic', 'size'))); } catch (err) {} try { - settingsjson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'settings.json'), 'utf8')); + settingsjson = JSON.parse(fs.readFileSync(path.join(__dirname, 'settings.json'), 'utf8')); } catch (err) {} +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': + updateCmd(['--createShortcut', target], app.quit); + break; + case '--squirrel-uninstall': + updateCmd(['--removeShortcut', target], app.quit); + break; + case '--squirrel-obsolete': + app.quit(); + break; + } +} + var openURL = null; app.on('open-url', function (event, url) { event.preventDefault(); @@ -33,10 +57,10 @@ app.on('ready', function () { 'standard-window': false, resizable: true, frame: false, - show: false, + show: false }); - mainWindow.loadUrl(path.normalize('file://' + path.join(__dirname, '..', 'build/index.html'))); + mainWindow.loadUrl(path.normalize('file://' + path.join(__dirname, 'index.html'))); app.on('activate-with-no-open-windows', function () { if (mainWindow) { @@ -51,11 +75,21 @@ app.on('ready', function () { autoUpdater.quitAndInstall(); }); - app.on('before-quit', function () { - if (!updating) { + 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(); @@ -85,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/ContainerDetailsSubheader.react.js b/src/components/ContainerDetailsSubheader.react.js index f0cedecc95..e4793d6d5b 100644 --- a/src/components/ContainerDetailsSubheader.react.js +++ b/src/components/ContainerDetailsSubheader.react.js @@ -1,13 +1,18 @@ +var $ = require('jquery'); var _ = require('underscore'); var React = require('react'); var exec = require('exec'); var shell = require('shell'); var metrics = require('../utils/MetricsUtil'); var ContainerUtil = require('../utils/ContainerUtil'); +var util = require('../utils/Util'); var machine = require('../utils/DockerMachineUtil'); +var RetinaImage = require('react-retina-image'); var classNames = require('classnames'); var resources = require('../utils/ResourcesUtil'); +var dockerUtil = require('../utils/DockerUtil'); var containerActions = require('../actions/ContainerActions'); +var dockerMachineUtil = require('../utils/DockerMachineUtil'); var ContainerDetailsSubheader = React.createClass({ contextTypes: { @@ -101,15 +106,7 @@ var ContainerDetailsSubheader = React.createClass({ if(!shell) { shell = 'sh'; } - machine.ip().then(ip => { - var cmd = [resources.terminal(), 'ssh', '-p', '22', '-o', 'UserKnownHostsFile=/dev/null', '-o', 'LogLevel=quiet', '-o', 'StrictHostKeyChecking=no', '-i', '~/.docker/machine/machines/' + machine.name() + '/id_rsa', 'docker@' + ip, '-t', 'docker', - 'exec', '-i', '-t', container.Name, shell]; - exec(cmd, function (stderr, stdout, code) { - if (code) { - console.log(stderr); - } - }); - }); + dockerMachineUtil.dockerTerminal(`docker exec -it ${this.props.container.Name} ${shell}`); } }, render: function () { diff --git a/src/components/ContainerHomeFolders.react.js b/src/components/ContainerHomeFolders.react.js index e2fbf89e78..53ba985fe5 100644 --- a/src/components/ContainerHomeFolders.react.js +++ b/src/components/ContainerHomeFolders.react.js @@ -18,23 +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()) { - var home = util.home(); - home = home.charAt(0).toLowerCase() + home.slice(1); - home = '/' + home.replace(':', '').replace(/\\/g, '/'); - var fullPath = path.join(home, 'Kitematic', pair[1], pair[0]); - fullPath = fullPath.replace(/\\/g, '/'); - return fullPath + ':' + pair[0]; + return util.windowsToLinuxPath(pair[1]) + ':' + pair[0]; } return pair[1] + ':' + pair[0]; }); @@ -49,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..c4d7818d4f 100644 --- a/src/components/ContainerHomePreview.react.js +++ b/src/components/ContainerHomePreview.react.js @@ -62,7 +62,7 @@ var ContainerHomePreview = React.createClass({ {frame} -
+
); @@ -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 b1b828b686..154bc87666 100644 --- a/src/components/ContainerSettingsVolumes.react.js +++ b/src/components/ContainerSettingsVolumes.react.js @@ -3,58 +3,81 @@ var React = require('react/addons'); var remote = require('remote'); var dialog = remote.require('dialog'); var shell = require('shell'); +var util = require('../utils/Util'); var metrics = require('../utils/MetricsUtil'); 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('Chose Directory for Volume'); - 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); + if (util.isWindows()) { + shell.showItemInFolder(util.linuxToWindowsPath(path)); + } else { + shell.showItemInFolder(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 ( @@ -76,7 +99,7 @@ var ContainerSettingsVolumes = React.createClass({ DOCKER FOLDER - MAC FOLDER + LOCAL FOLDER diff --git a/src/components/Containers.react.js b/src/components/Containers.react.js index 449afb4835..521001629a 100644 --- a/src/components/Containers.react.js +++ b/src/components/Containers.react.js @@ -6,6 +6,7 @@ var containerStore = require('../stores/ContainerStore'); var ContainerList = require('./ContainerList.react'); var Header = require('./Header.react'); var metrics = require('../utils/MetricsUtil'); +var RetinaImage = require('react-retina-image'); var shell = require('shell'); var machine = require('../utils/DockerMachineUtil'); 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 588d2c07e8..7d8061bb55 100644 --- a/src/stores/SetupStore.js +++ b/src/stores/SetupStore.js @@ -42,14 +42,14 @@ var _steps = [{ progressCallback(50); // TODO: detect when the installation has started so we can simulate progress try { if (util.isWindows()) { - yield util.exec([path.join(util.supportDir(), virtualBox.filename())]); + yield util.exec([path.join(util.supportDir(), virtualBox.filename()), '-msiparams', 'REBOOT=ReallySuppress', 'LIMITUI=INSTALLUILEVEL_PROGRESSONLY']); } else { yield util.exec(setupUtil.macSudoCmd(setupUtil.installVirtualBoxCmd())); } } catch (err) { throw null; } - } else if (!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: 72, + seconds: 110, run: Promise.coroutine(function* (progressCallback) { setupUtil.simulateProgress(this.seconds, progressCallback); var exists = yield machine.exists(); @@ -68,16 +68,6 @@ var _steps = [{ yield machine.rm(); } yield machine.create(); - if(util.isWindows()) { - let home = util.home(); - let driveLetter = home.charAt(0); - let parts = home.split('\\').slice(0, -1); - let usersDirName = parts[parts.length-1]; - let usersDirPath = parts.join('\\'); - let shareName = driveLetter + '/' + usersDirName; - yield virtualBox.mountSharedDir(machine.name(), shareName, usersDirPath); - yield machine.start(); - } return; } @@ -159,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 || !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 0875882d5c..8006e49bf7 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 () { @@ -153,18 +153,25 @@ var DockerMachine = { }); }); }, - dockerTerminal: function () { + dockerTerminal: function (cmd) { if(util.isWindows()) { + cmd = cmd || ''; this.info().then(machine => { - util.execProper(`start cmd.exe /k "SET DOCKER_HOST=${machine.url}&& SET DOCKER_CERT_PATH=${path.join(util.home(), '.docker/machine/machines/' + machine.name)}&& SET DOCKER_TLS_VERIFY=1`); + util.exec('start powershell.exe ' + cmd, + {env: { + 'DOCKER_HOST' : machine.url, + 'DOCKER_CERT_PATH' : path.join(util.home(), '.docker/machine/machines/' + machine.name), + 'DOCKER_TLS_VERIFY': 1 + } + }); }); } else { + cmd = cmd || process.env.SHELL; this.info().then(machine => { - var cmd = [resources.terminal(), `DOCKER_HOST=${machine.url} DOCKER_CERT_PATH=${path.join(util.home(), '.docker/machine/machines/' + machine.name)} DOCKER_TLS_VERIFY=1 $SHELL`]; - util.exec(cmd).then(() => {}); + util.exec([resources.terminal(), `DOCKER_HOST=${machine.url} DOCKER_CERT_PATH=${path.join(util.home(), '.docker/machine/machines/' + machine.name)} DOCKER_TLS_VERIFY=1 ${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/MetricsUtil.js b/src/utils/MetricsUtil.js index e7083989fd..8b19edfbd0 100644 --- a/src/utils/MetricsUtil.js +++ b/src/utils/MetricsUtil.js @@ -7,7 +7,7 @@ var util = require('./Util'); var settings; try { - settings = JSON.parse(fs.readFileSync(path.join(__dirname, '../..', 'settings.json'), 'utf8')); + settings = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'settings.json'), 'utf8')); } catch (err) { settings = {}; } diff --git a/src/utils/RegHubUtil.js b/src/utils/RegHubUtil.js index bb7b1f5734..cf00640249 100644 --- a/src/utils/RegHubUtil.js +++ b/src/utils/RegHubUtil.js @@ -5,6 +5,7 @@ var util = require('../utils/Util'); var hubUtil = require('../utils/HubUtil'); var repositoryServerActions = require('../actions/RepositoryServerActions'); var tagServerActions = require('../actions/TagServerActions'); +var Promise = require('bluebird'); let REGHUB2_ENDPOINT = process.env.REGHUB2_ENDPOINT || 'https://registry.hub.docker.com/v2'; let searchReq = null; @@ -61,7 +62,7 @@ module.exports = { let data = JSON.parse(body); let repos = data.repos; async.map(repos, (repo, cb) => { - var name = repo.repo; + let name = repo.repo; if (util.isOfficialRepo(name)) { name = 'library/' + name; } diff --git a/src/utils/SetupUtil.js b/src/utils/SetupUtil.js index 9c3b61cfa3..f22e7a4c33 100644 --- a/src/utils/SetupUtil.js +++ b/src/utils/SetupUtil.js @@ -35,7 +35,7 @@ var SetupUtil = { }, copycmd: function (src, dest) { return ['rm', '-f', dest, '&&', 'cp', src, dest]; - }, + }, copyBinariesCmd: function () { var cmd = ['mkdir', '-p', '/usr/local/bin']; cmd.push('&&'); diff --git a/src/utils/Util.js b/src/utils/Util.js index 86c5f965df..1dc83a6178 100644 --- a/src/utils/Util.js +++ b/src/utils/Util.js @@ -1,4 +1,5 @@ var exec = require('exec'); +var child_process = require('child_process'); var Promise = require('bluebird'); var fs = require('fs'); var path = require('path'); @@ -9,11 +10,21 @@ 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) => { - exec(args, options, (stderr, stdout, code) => { + 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.\n===== Stderr =====\n ' + stderr + '\n===== Stdout =====\n' + stdout)); + reject(new Error(cmd + ' returned non zero exit code. Stderr: ' + stderr)); } else { resolve(stdout); } @@ -45,7 +56,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'); @@ -62,12 +74,12 @@ module.exports = { .replace(/\/Users\/[^\/]*\//mg, '/Users//'); }, packagejson: function () { - return JSON.parse(fs.readFileSync(path.join(__dirname, '../..', 'package.json'), 'utf8')); + return JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')); }, settingsjson: function () { var settingsjson = {}; try { - settingsjson = JSON.parse(fs.readFileSync(path.join(__dirname, '../..', 'settings.json'), 'utf8')); + settingsjson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'settings.json'), 'utf8')); } catch (err) {} return settingsjson; }, @@ -135,5 +147,15 @@ module.exports = { randomId: function () { return crypto.randomBytes(32).toString('hex'); }, + windowsToLinuxPath: function(windowsAbsPath) { + var fullPath = windowsAbsPath.replace(':', '').split(path.sep).join('/'); + if(fullPath.charAt(0) !== '/'){ + fullPath = '/' + fullPath.charAt(0).toLowerCase() + fullPath.substring(1); + } + 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; } diff --git a/util/kitematic.ico b/util/kitematic.ico new file mode 100644 index 0000000000..ec7dab48ab Binary files /dev/null and b/util/kitematic.ico differ diff --git a/util/loading.gif b/util/loading.gif new file mode 100644 index 0000000000..3f6d3b671a Binary files /dev/null and b/util/loading.gif differ