diff --git a/src/components/Header.react.js b/src/components/Header.react.js index e69c14ea..4a0cec40 100644 --- a/src/components/Header.react.js +++ b/src/components/Header.react.js @@ -26,7 +26,8 @@ var Header = React.createClass({ document.addEventListener('keyup', this.handleDocumentKeyUp, false); accountStore.listen(this.update); - + // remove listener if exists + ipc.removeAllListeners('application:update-available'); ipc.on('application:update-available', () => { this.setState({ updateAvailable: true diff --git a/src/components/Preferences.react.js b/src/components/Preferences.react.js index 2326d11d..69898324 100644 --- a/src/components/Preferences.react.js +++ b/src/components/Preferences.react.js @@ -1,6 +1,8 @@ import React from 'react/addons'; import metrics from '../utils/MetricsUtil'; import Router from 'react-router'; +import setupUtil from '../utils/SetupUtil'; +import docker from '../utils/DockerUtil'; var Preferences = React.createClass({ mixins: [Router.Navigation], @@ -14,6 +16,21 @@ var Preferences = React.createClass({ this.goBack(); metrics.track('Went Back From Preferences'); }, + handleClearVMClick: function () { + localStorage.removeItem('settings.vm'); + localStorage.removeItem('placeholders'); + setupUtil.resetProgress(); + setupUtil.setup().then(() => { + docker.init(); + this.transitionTo('search'); + }).catch(err => { + metrics.track('Setup Failed', { + step: 'catch', + message: err.message + }); + throw err; + }); + }, handleChangeCloseVMOnQuit: function (e) { var checked = e.target.checked; this.setState({ @@ -48,6 +65,14 @@ var Preferences = React.createClass({ +
+
+ Reset VM preferences +
+
+ +
+
App Settings
diff --git a/src/components/SelectMachine.react.js b/src/components/SelectMachine.react.js new file mode 100644 index 00000000..08aee981 --- /dev/null +++ b/src/components/SelectMachine.react.js @@ -0,0 +1,85 @@ +import _ from 'underscore'; +import utils from '../utils/Util'; +import React from 'react/addons'; +import Router from 'react-router'; +import RetinaImage from 'react-retina-image'; +import metrics from '../utils/MetricsUtil'; +import {DropdownButton, MenuItem} from 'react-bootstrap'; +import machine from '../utils/DockerMachineUtil'; +import setupActions from '../actions/SetupActions'; + +var Setup = React.createClass({ + mixins: [Router.Navigation], + + getInitialState: function () { + return { + currentEngine: localStorage.getItem('settings.vm.name') || 'VM Available', + machines: {} + }; + }, + + componentDidMount: function () { + machine.list().then( machines => { + if (typeof machines.default === 'undefined') { + machines.default = {'driver': 'virtualbox', 'name': 'default', 'create': true}; + } + this.setState({ + machines: machines + }); + }); + }, + + handleChangeDockerEngine: function (machineIndex) { + localStorage.setItem('settings.vm', JSON.stringify(this.state.machines[machineIndex])); + if (this.state.currentEngine !== machineIndex) { + this.setState({ + currentEngine: machineIndex + }); + } + setupActions.retry(false); + }, + render: function () { + let currentDriver = ''; + let machineDropdown = (
); + if (!_.isEmpty(this.state.machines)) { + machineDropdown = React.addons.createFragment(_.mapObject(this.state.machines, (machineItem, index) => { + let menu = []; + let machineDriver = utils.camelCase(machineItem.driver); + let machineName = utils.camelCase(machineItem.name); + if (machineItem.create) { + machineName = 'Create ' + machineName; + } + if (currentDriver !== machineItem.driver) { + menu.push({machineDriver}); + currentDriver = machine.driver; + } + menu.push({machineName}); + return menu; + })); + } + return ( +
+
+
+
+ +
+
+
+
+
+
+

Select Docker VM

+

To run Docker containers on your computer, Kitematic needs Linux virtual machine.

+ + {machineDropdown} + +
+
+
+
+ ); + } +}); + +module.exports = Setup; diff --git a/src/routes.js b/src/routes.js index 824b5d70..a1001f10 100644 --- a/src/routes.js +++ b/src/routes.js @@ -1,5 +1,6 @@ import React from 'react/addons'; import Setup from './components/Setup.react'; +import SelectMachine from './components/SelectMachine.react'; import Account from './components/Account.react'; import AccountSignup from './components/AccountSignup.react'; import AccountLogin from './components/AccountLogin.react'; @@ -53,6 +54,7 @@ var routes = ( + ); diff --git a/src/utils/DockerMachineUtil.js b/src/utils/DockerMachineUtil.js index e342e7fc..bc173697 100644 --- a/src/utils/DockerMachineUtil.js +++ b/src/utils/DockerMachineUtil.js @@ -13,7 +13,10 @@ var DockerMachine = { } }, name: function () { - return 'default'; + return util.vmsettings().name || 'default'; + }, + driver: function () { + return util.vmsettings().driver || 'virtualbox'; }, installed: function () { if (util.isWindows() && !process.env.DOCKER_TOOLBOX_INSTALL_PATH) { @@ -34,6 +37,24 @@ var DockerMachine = { return null; } }, + list: function () { + return util.exec([this.command(), 'ls']).then(stdout => { + var lines = stdout.trim().split('\n').filter(line => line.indexOf('time=') === -1); + var machines = {}; + lines.slice(1, lines.length).forEach(line => { + var tokens = line.trim().split(/[\s]+/).filter(token => token !== '*'); + var machine = { + name: tokens[0], + active: tokens[1], + driver: tokens[2], + state: tokens[3], + url: tokens[4] || '' + }; + machines[machine.name] = machine; + }); + return Promise.resolve(machines); + }); + }, exists: function (machineName = this.name()) { return this.status(machineName).then(() => { return true; diff --git a/src/utils/SetupUtil.js b/src/utils/SetupUtil.js index 25cdd8aa..a50366d0 100644 --- a/src/utils/SetupUtil.js +++ b/src/utils/SetupUtil.js @@ -26,6 +26,10 @@ export default { }); }, + resetProgress () { + setupServerActions.progress({progress: null}); + }, + clearTimers () { _timers.forEach(t => clearTimeout(t)); _timers = []; @@ -62,8 +66,20 @@ export default { throw new Error('Docker Machine is not installed. Please install it via the Docker Toolbox.'); } + // Allow machine selection on initial launch + if (!localStorage.getItem('settings.vm')) { + router.get().transitionTo('selectmachine'); + await this.pause(); + } + setupServerActions.started({started: true}); - let exists = await virtualBox.vmExists(machine.name()) && fs.existsSync(path.join(util.home(), '.docker', 'machine', 'machines', machine.name())); + + let exists = true; + // Check if we're dealing with a virtualbox machine + if (machine.driver() === 'virtualbox') { + exists = await virtualBox.vmExists(machine.name()) && fs.existsSync(path.join(util.home(), '.docker', 'machine', 'machines', machine.name())); + } + if (!exists) { router.get().transitionTo('setup'); setupServerActions.started({started: true}); @@ -90,7 +106,6 @@ export default { let tries = 80, ip = null; while (!ip && tries > 0) { try { - console.log('Trying to fetch machine IP, tries left: ' + tries); ip = await machine.ip(); tries -= 1; await Promise.delay(1000); diff --git a/src/utils/Util.js b/src/utils/Util.js index 9950ca6e..0d86cedf 100644 --- a/src/utils/Util.js +++ b/src/utils/Util.js @@ -84,6 +84,9 @@ module.exports = { } catch (err) {} return settingsjson; }, + vmsettings: function () { + return JSON.parse(localStorage.getItem('settings.vm')) || null; + }, isOfficialRepo: function (name) { if (!name || !name.length) { return false; @@ -143,6 +146,11 @@ module.exports = { return 0; }, + camelCase: function (text) { + return text.toLowerCase().replace( /\b\w/g, function (m) { + return m.toUpperCase(); + }); + }, randomId: function () { return crypto.randomBytes(32).toString('hex'); },