This commit is contained in:
Alexandre Vazquez 2016-03-26 20:37:39 +01:00
commit cac4c6fbae
32 changed files with 265 additions and 147 deletions

View File

@ -91,7 +91,7 @@ module.exports = function (grunt) {
'FileDescription': WINDOWS_APPNAME, 'FileDescription': WINDOWS_APPNAME,
'InternalName': BASENAME + '.exe', 'InternalName': BASENAME + '.exe',
'OriginalFilename': BASENAME + '.exe', 'OriginalFilename': BASENAME + '.exe',
'LegalCopyright': 'Copyright 2015 Docker Inc. All rights reserved.' 'LegalCopyright': 'Copyright 2015-2016 Docker Inc. All rights reserved.'
} }
} }
} }

View File

@ -187,7 +187,7 @@
same "printed page" as the copyright notice for easier same "printed page" as the copyright notice for easier
identification within third-party archives. identification within third-party archives.
Copyright 2014-2015 Docker, Inc. Copyright 2014-2016 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -5,6 +5,11 @@ run:
npm install npm install
npm run npm run
# Get the IP ADDRESS
DOCKER_IP=$(shell python -c "import urlparse ; print urlparse.urlparse('$(DOCKER_HOST)').hostname or ''")
HUGO_BASE_URL=$(shell test -z "$(DOCKER_IP)" && echo localhost || echo "$(DOCKER_IP)")
HUGO_BIND_IP=0.0.0.0
# import the existing docs build cmds from docker/docker # import the existing docs build cmds from docker/docker
DOCS_MOUNT := $(if $(DOCSDIR),-v $(CURDIR)/$(DOCSDIR):/$(DOCSDIR)) DOCS_MOUNT := $(if $(DOCSDIR),-v $(CURDIR)/$(DOCSDIR):/$(DOCSDIR))
DOCSPORT := 8000 DOCSPORT := 8000
@ -13,7 +18,9 @@ DOCKER_DOCS_IMAGE := kitematic-docs$(if $(GIT_BRANCH),:$(GIT_BRANCH))
DOCKER_RUN_DOCS := docker run --rm -it $(DOCS_MOUNT) DOCKER_RUN_DOCS := docker run --rm -it $(DOCS_MOUNT)
docs: docs-build docs: docs-build
$(DOCKER_RUN_DOCS) -p $(if $(DOCSPORT),$(DOCSPORT):)8000 "$(DOCKER_DOCS_IMAGE)" mkdocs serve $(DOCKER_RUN_DOCS) -p $(if $(DOCSPORT),$(DOCSPORT):)8000 "$(DOCKER_DOCS_IMAGE)" \
hugo server \
--port=$(DOCSPORT) --baseUrl=$(HUGO_BASE_URL) --bind=$(HUGO_BIND_IP)
docs-shell: docs-build docs-shell: docs-build
$(DOCKER_RUN_DOCS) -p $(if $(DOCSPORT),$(DOCSPORT):)8000 "$(DOCKER_DOCS_IMAGE)" bash $(DOCKER_RUN_DOCS) -p $(if $(DOCSPORT),$(DOCSPORT):)8000 "$(DOCKER_DOCS_IMAGE)" bash

View File

@ -11,5 +11,5 @@ deployment:
tag: /v.*/ tag: /v.*/
owner: docker owner: docker
commands: commands:
- 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-Mac.zip --name Kitematic-$(echo $CIRCLE_TAG | cut -c2-)-Mac.zip
- github-release upload --user docker --repo kitematic --tag $CIRCLE_TAG --file release/Kitematic-Windows.zip --name Kitematic-Windows.zip - github-release upload --user docker --repo kitematic --tag $CIRCLE_TAG --file release/Kitematic-Windows.zip --name Kitematic-$(echo $CIRCLE_TAG | cut -c2-)-Windows.zip

View File

@ -5,9 +5,10 @@ RUN svn checkout https://github.com/docker/compose/trunk/docs /docs/content/comp
RUN svn checkout https://github.com/docker/docker/trunk/docs /docs/content/docker RUN svn checkout https://github.com/docker/docker/trunk/docs /docs/content/docker
RUN svn checkout https://github.com/docker/swarm/trunk/docs /docs/content/swarm RUN svn checkout https://github.com/docker/swarm/trunk/docs /docs/content/swarm
RUN svn checkout https://github.com/docker/distribution/trunk/docs /docs/content/registry RUN svn checkout https://github.com/docker/distribution/trunk/docs /docs/content/registry
RUN svn checkout https://github.com/docker/tutorials/trunk/docs /docs/content RUN svn checkout https://github.com/docker/notary/trunk/docs /docs/content/notary
RUN svn checkout https://github.com/docker/opensource/trunk/docs /docs/content/opensource 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 RUN svn checkout https://github.com/docker/machine/trunk/docs /docs/content/machine
RUN svn checkout https://github.com/docker/toolbox/trunk/docs /docs/content/toolbox
ENV PROJECT=kitematic ENV PROJECT=kitematic
# To get the git info for this repo # To get the git info for this repo

View File

@ -4,7 +4,7 @@ title = "Frequently Asked Questions"
description = "Documentation covering common questions users have about Kitematic" description = "Documentation covering common questions users have about Kitematic"
keywords = ["docker, documentation, about, technology, kitematic, gui"] keywords = ["docker, documentation, about, technology, kitematic, gui"]
[menu.main] [menu.main]
parent="smn_workw_kitematic" parent="toolbox_kitematic"
weight=5 weight=5
+++ +++
<![end-metadata]--> <![end-metadata]-->

View File

@ -4,7 +4,8 @@ title = "Kitematic"
description = "Documentation that provides an overview of Kitematic and installation instructions" description = "Documentation that provides an overview of Kitematic and installation instructions"
keywords = ["docker, documentation, about, technology, kitematic, gui"] keywords = ["docker, documentation, about, technology, kitematic, gui"]
[menu.main] [menu.main]
parent="mn_install" identifier="toolbox_kitematic"
parent="workw_toolbox"
weight=2 weight=2
+++ +++
<![end-metadata]--> <![end-metadata]-->

View File

@ -4,7 +4,7 @@ title = "Known Issues"
description = "Information about known issues in Kitematic" description = "Information about known issues in Kitematic"
keywords = ["docker, documentation, about, technology, kitematic, gui"] keywords = ["docker, documentation, about, technology, kitematic, gui"]
[menu.main] [menu.main]
parent="smn_workw_kitematic" parent="toolbox_kitematic"
weight=5 weight=5
+++ +++
<![end-metadata]--> <![end-metadata]-->

View File

@ -4,7 +4,7 @@ title = "Set up a Minecraft Server"
description = "Tutorial demonstrating the setup of a Minecraft server using Docker and Kitematic" description = "Tutorial demonstrating the setup of a Minecraft server using Docker and Kitematic"
keywords = ["docker, documentation, about, technology, kitematic, gui, minecraft, tutorial"] keywords = ["docker, documentation, about, technology, kitematic, gui, minecraft, tutorial"]
[menu.main] [menu.main]
parent="smn_workw_kitematic" parent="toolbox_kitematic"
weight=2 weight=2
+++ +++
<![end-metadata]--> <![end-metadata]-->

View File

@ -4,7 +4,7 @@ title = "Set up an Nginx web server"
description = "Tutorial demonstrating the setup of an Nginx web server using Docker and Kitematic" description = "Tutorial demonstrating the setup of an Nginx web server using Docker and Kitematic"
keywords = ["docker, documentation, about, technology, kitematic, gui, nginx, tutorial"] keywords = ["docker, documentation, about, technology, kitematic, gui, nginx, tutorial"]
[menu.main] [menu.main]
parent="smn_workw_kitematic" parent="toolbox_kitematic"
weight=1 weight=1
+++ +++
<![end-metadata]--> <![end-metadata]-->

View File

@ -4,7 +4,7 @@ title = "Creating a Local RethinkDB Database for Development"
description = "Tutorial demonstrating the setup of an RethinkDB database for development" description = "Tutorial demonstrating the setup of an RethinkDB database for development"
keywords = ["docker, documentation, about, technology, kitematic, gui, rethink, tutorial"] keywords = ["docker, documentation, about, technology, kitematic, gui, rethink, tutorial"]
[menu.main] [menu.main]
parent="smn_workw_kitematic" parent="toolbox_kitematic"
weight=3 weight=3
+++ +++
<![end-metadata]--> <![end-metadata]-->

View File

@ -4,7 +4,7 @@ title = "Kitematic User Guide: Intro & Overview"
description = "Documentation that provides an overview of Kitematic and installation instructions" description = "Documentation that provides an overview of Kitematic and installation instructions"
keywords = ["docker, documentation, about, technology, kitematic, gui"] keywords = ["docker, documentation, about, technology, kitematic, gui"]
[menu.main] [menu.main]
parent="smn_workw_kitematic" parent="toolbox_kitematic"
+++ +++
<![end-metadata]--> <![end-metadata]-->

View File

@ -13,6 +13,7 @@
"object-assign", "object-assign",
"underscore", "underscore",
"source-map-support", "source-map-support",
"<rootDir>/node_modules/.*JSONStream" "<rootDir>/node_modules/.*JSONStream",
"<rootDir>/node_modules/core-js"
] ]
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "Kitematic", "name": "Kitematic",
"version": "0.9.5", "version": "0.10.0",
"author": "Kitematic", "author": "Kitematic",
"description": "Simple Docker Container management for Mac OS X.", "description": "Simple Docker Container management for Mac OS X.",
"homepage": "https://kitematic.com/", "homepage": "https://kitematic.com/",
@ -16,13 +16,11 @@
"test": "jest -c jest-unit.json", "test": "jest -c jest-unit.json",
"integration": "jest -c jest-integration.json", "integration": "jest -c jest-integration.json",
"release": "grunt release", "release": "grunt release",
"release-mac": "grunt release-mac",
"lint": "jsxhint src" "lint": "jsxhint src"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"electron-version": "0.35.4", "electron-version": "0.35.4",
"dependencies": { "dependencies": {
"JSONStream": "^1.0.7",
"alt": "^0.16.2", "alt": "^0.16.2",
"ansi-to-html": "0.3.0", "ansi-to-html": "0.3.0",
"any-promise": "^0.1.0", "any-promise": "^0.1.0",
@ -35,6 +33,7 @@
"dockerode": "^2.2.7", "dockerode": "^2.2.7",
"install": "^0.1.8", "install": "^0.1.8",
"jquery": "^2.1.3", "jquery": "^2.1.3",
"JSONStream": "^1.0.7",
"mixpanel": "kitematic/mixpanel-node", "mixpanel": "kitematic/mixpanel-node",
"mkdirp": "^0.5.0", "mkdirp": "^0.5.0",
"node-uuid": "^1.4.3", "node-uuid": "^1.4.3",

View File

@ -15,22 +15,49 @@ EOF`
function open_iterm () { function open_iterm () {
osascript > /dev/null <<EOF osascript > /dev/null <<EOF
tell application "iTerm" tell application "iTerm"
activate if version < 2.9 then
try activate
tell the first terminal try
launch session "Default Session" tell current window
tell the last session create tab with default profile
write text "bash -c \"$CMD\"" tell the current session of current window
end tell write text "bash -c \"$CMD\""
end tell end tell
on error end tell
tell (make new terminal) on error
launch session "Default Session" try
tell the last session tell the first terminal
write text "bash -c \"$CMD\"" launch session "Default Session"
end tell tell the last session
end tell write text "bash -c \"$CMD\""
end try end tell
end tell
on error
tell (make new terminal)
launch session "Default Session"
tell the last session
write text "bash -c \"$CMD\""
end tell
end tell
end try
end try
end if
end tell
EOF
}
function open_iterm2point9 () {
osascript > /dev/null <<EOF
tell application "iTerm"
if version ≥ 2.9 then
activate
tell current window
create tab with default profile
tell first session of current tab
write text "bash -c \"$CMD\""
end tell
end tell
end if
end tell end tell
EOF EOF
} }
@ -47,7 +74,7 @@ EOF
} }
if [ "$ITERM_EXISTS" == "true" ]; then if [ "$ITERM_EXISTS" == "true" ]; then
open_iterm "$@" || open_terminal "$@" open_iterm2point9 "$@" || open_iterm "$@" || open_terminal "$@"
else else
open_terminal "$@" open_terminal "$@"
fi fi

View File

@ -6,6 +6,11 @@ class SetupActions {
this.dispatch({removeVM}); this.dispatch({removeVM});
setupUtil.retry(removeVM); setupUtil.retry(removeVM);
} }
useVbox () {
this.dispatch({});
setupUtil.useVbox();
}
} }
export default alt.createActions(SetupActions); export default alt.createActions(SetupActions);

View File

@ -1,4 +1,5 @@
require.main.paths.splice(0, 0, process.env.NODE_PATH); require.main.paths.splice(0, 0, process.env.NODE_PATH);
import electron from 'electron'; import electron from 'electron';
const remote = electron.remote; const remote = electron.remote;
const Menu = remote.Menu; const Menu = remote.Menu;

View File

@ -4,18 +4,20 @@ const BrowserWindow = electron.BrowserWindow;
import fs from 'fs'; import fs from 'fs';
import os from 'os'; import os from 'os';
import path from 'path'; import path from 'path';
import child_process from 'child_process'; import child_process from '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.env.RESOURCES_PATH = path.join(__dirname, '/../resources');
process.env.PATH = '/usr/local/bin:' + process.env.PATH; if (process.platform !== 'win32') {
process.env.PATH = '/usr/local/bin:' + process.env.PATH;
}
var size = {}, settingsjson = {}; var size = {}, settingsjson = {};
try { try {
size = JSON.parse(fs.readFileSync(path.join(process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'], 'Library', 'Application\ Support', 'Kitematic', 'size'))); size = JSON.parse(fs.readFileSync(path.join(app.getPath('userData'), 'size')));
} catch (err) {} } catch (err) {}
try { 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) {} } catch (err) {}

View File

@ -52,7 +52,7 @@ var ContainerHome = React.createClass({
if (this.props.container.Error) { if (this.props.container.Error) {
body = ( body = (
<div className="details-progress error"> <div className="details-progress error">
<h2>We&#39;re sorry. There seem to be an error:</h2> <h2>We&#39;re sorry. There seems to be an error:</h2>
<p className="error-message">{this.props.container.Error}</p> <p className="error-message">{this.props.container.Error}</p>
<p>If this error is invalid, please file a ticket on our Github repo.</p> <p>If this error is invalid, please file a ticket on our Github repo.</p>
<a className="btn btn-action" onClick={this.handleErrorClick}>File Ticket</a> <a className="btn btn-action" onClick={this.handleErrorClick}>File Ticket</a>

View File

@ -32,6 +32,7 @@ var ContainerHomeFolder = React.createClass({
mounts.forEach(m => { mounts.forEach(m => {
if (m.Destination === destination) { if (m.Destination === destination) {
m.Source = util.windowsToLinuxPath(newSource); m.Source = util.windowsToLinuxPath(newSource);
m.Driver = null;
} }
}); });

View File

@ -97,7 +97,7 @@ var ContainerListItem = React.createClass({
return ( return (
<Router.Link to="container" params={{name: container.Name}}> <Router.Link to="container" params={{name: container.Name}}>
<li onMouseEnter={self.handleItemMouseEnter} onMouseLeave={self.handleItemMouseLeave} onClick={self.handleClick}> <li onMouseEnter={self.handleItemMouseEnter} onMouseLeave={self.handleItemMouseLeave} onClick={self.handleClick} id={this.props.key}>
{state} {state}
<div className="info"> <div className="info">
<div className="name"> <div className="name">

View File

@ -28,7 +28,7 @@ var ContainerSettingsVolumes = React.createClass({
metrics.track('Choose Directory for Volume'); metrics.track('Choose Directory for Volume');
var mounts = _.clone(this.props.container.Mounts); let mounts = _.clone(this.props.container.Mounts);
_.each(mounts, m => { _.each(mounts, m => {
if (m.Destination === dockerVol) { if (m.Destination === dockerVol) {
m.Source = util.windowsToLinuxPath(directory); m.Source = util.windowsToLinuxPath(directory);
@ -36,7 +36,7 @@ var ContainerSettingsVolumes = React.createClass({
} }
}); });
var binds = mounts.map(m => { let binds = mounts.map(m => {
return m.Source + ':' + m.Destination; return m.Source + ':' + m.Destination;
}); });
@ -50,7 +50,7 @@ var ContainerSettingsVolumes = React.createClass({
from: 'settings' from: 'settings'
}); });
var mounts = _.clone(this.props.container.Mounts); let mounts = _.clone(this.props.container.Mounts);
_.each(mounts, m => { _.each(mounts, m => {
if (m.Destination === dockerVol) { if (m.Destination === dockerVol) {
m.Source = null; m.Source = null;

View File

@ -26,7 +26,7 @@ var Containers = React.createClass({
containerStore.listen(this.update); containerStore.listen(this.update);
}, },
componentDidUnmount: function () { componentWillUnmount: function () {
containerStore.unlisten(this.update); containerStore.unlisten(this.update);
}, },
@ -106,43 +106,7 @@ var Containers = React.createClass({
metrics.track('Opened Issue Reporter', { metrics.track('Opened Issue Reporter', {
from: 'app' from: 'app'
}); });
shell.openExternal('https://github.com/kitematic/kitematic/issues/new'); shell.openExternal('https://github.com/docker/kitematic/issues/new');
},
handleMouseEnterDockerTerminal: function () {
this.setState({
currentButtonLabel: 'Open terminal to use Docker command line.'
});
},
handleMouseLeaveDockerTerminal: function () {
this.setState({
currentButtonLabel: ''
});
},
handleMouseEnterReportIssue: function () {
this.setState({
currentButtonLabel: 'Report an issue or suggest feedback.'
});
},
handleMouseLeaveReportIssue: function () {
this.setState({
currentButtonLabel: ''
});
},
handleMouseEnterPreferences: function () {
this.setState({
currentButtonLabel: 'Change app preferences.'
});
},
handleMouseLeavePreferences: function () {
this.setState({
currentButtonLabel: ''
});
}, },
render: function () { render: function () {
@ -169,9 +133,9 @@ var Containers = React.createClass({
<ContainerList containers={this.state.sorted} newContainer={this.state.newContainer} /> <ContainerList containers={this.state.sorted} newContainer={this.state.newContainer} />
</section> </section>
<section className="sidebar-buttons"> <section className="sidebar-buttons">
<span className="btn-sidebar btn-terminal" onClick={this.handleClickDockerTerminal} onMouseEnter={this.handleMouseEnterDockerTerminal} onMouseLeave={this.handleMouseLeaveDockerTerminal}><span className="icon icon-docker-cli"></span><span className="text">DOCKER CLI</span></span> <span className="btn-sidebar btn-terminal" onClick={this.handleClickDockerTerminal} ><span className="icon icon-docker-cli"></span><span className="text">DOCKER CLI</span></span>
<span className="btn-sidebar btn-feedback" onClick={this.handleClickReportIssue} onMouseEnter={this.handleMouseEnterDockerTerminal} onMouseLeave={this.handleMouseLeaveDockerTerminal}><span className="icon icon-feedback"></span></span> <span className="btn-sidebar btn-feedback" onClick={this.handleClickReportIssue} ><span className="icon icon-feedback"></span></span>
<span className="btn-sidebar btn-preferences" onClick={this.handleClickPreferences} onMouseEnter={this.handleMouseEnterDockerTerminal} onMouseLeave={this.handleMouseLeaveDockerTerminal}><span className="icon icon-preferences"></span></span> <span className="btn-sidebar btn-preferences" onClick={this.handleClickPreferences} ><span className="icon icon-preferences"></span></span>
</section> </section>
</div> </div>
<Router.RouteHandler pending={this.state.pending} containers={this.state.containers} container={container}/> <Router.RouteHandler pending={this.state.pending} containers={this.state.containers} container={container}/>

View File

@ -76,11 +76,11 @@ var ImageCard = React.createClass({
$tagOverlay.fadeOut(300); $tagOverlay.fadeOut(300);
}, },
handleRepoClick: function () { handleRepoClick: function () {
var repoUri = 'https://registry.hub.docker.com/'; var repoUri = 'https://hub.docker.com/';
if (this.props.image.namespace === 'library') { if (this.props.image.namespace === 'library') {
repoUri = repoUri + '_/' + this.props.image.name; repoUri = repoUri + '_/' + this.props.image.name;
} else { } else {
repoUri = repoUri + 'u/' + this.props.image.namespace + '/' + this.props.image.name; repoUri = repoUri + 'r/' + this.props.image.namespace + '/' + this.props.image.name;
} }
shell.openExternal(repoUri); shell.openExternal(repoUri);
}, },
@ -105,6 +105,8 @@ var ImageCard = React.createClass({
var description; var description;
if (this.props.image.description) { if (this.props.image.description) {
description = this.props.image.description; description = this.props.image.description;
} else if(this.props.image.short_description){
description = this.props.image.short_description;
} else { } else {
description = "No description."; description = "No description.";
} }

View File

@ -73,7 +73,6 @@ module.exports = React.createClass({
nextPage = (page + 1 > this.state.totalPage) ? this.state.totalPage : page + 1; nextPage = (page + 1 > this.state.totalPage) ? this.state.totalPage : page + 1;
totalPage = this.state.totalPage; totalPage = this.state.totalPage;
} }
this.setState({ this.setState({
query: query, query: query,
loading: true, loading: true,

View File

@ -3,12 +3,13 @@ import Router from 'react-router';
import Radial from './Radial.react.js'; import Radial from './Radial.react.js';
import RetinaImage from 'react-retina-image'; import RetinaImage from 'react-retina-image';
import Header from './Header.react'; import Header from './Header.react';
import Util from '../utils/Util'; import util from '../utils/Util';
import metrics from '../utils/MetricsUtil'; import metrics from '../utils/MetricsUtil';
import setupStore from '../stores/SetupStore'; import setupStore from '../stores/SetupStore';
import setupActions from '../actions/SetupActions'; import setupActions from '../actions/SetupActions';
import shell from 'shell'; import shell from 'shell';
var Setup = React.createClass({ var Setup = React.createClass({
mixins: [Router.Navigation], mixins: [Router.Navigation],
@ -20,7 +21,7 @@ var Setup = React.createClass({
setupStore.listen(this.update); setupStore.listen(this.update);
}, },
componentDidUnmount: function () { componentWillUnmount: function () {
setupStore.unlisten(this.update); setupStore.unlisten(this.update);
}, },
@ -32,6 +33,10 @@ var Setup = React.createClass({
setupActions.retry(false); setupActions.retry(false);
}, },
handleUseVbox: function () {
setupActions.useVbox();
},
handleErrorRemoveRetry: function () { handleErrorRemoveRetry: function () {
console.log('Deleting VM and trying again.' ); console.log('Deleting VM and trying again.' );
setupActions.retry(true); setupActions.retry(true);
@ -63,6 +68,12 @@ var Setup = React.createClass({
}, },
renderProgress: function () { renderProgress: function () {
let title = 'Starting Docker VM';
let descr = 'To run Docker containers on your computer, Kitematic is starting a Linux virtual machine. This may take a minute...';
if (util.isNative()) {
title = 'Checking Docker';
descr = 'To run Docker containers on your computer, Kitematic is checking the Docker connection.';
}
return ( return (
<div className="setup"> <div className="setup">
<Header hideLogin={true}/> <Header hideLogin={true}/>
@ -72,8 +83,8 @@ var Setup = React.createClass({
</div> </div>
<div className="desc"> <div className="desc">
<div className="content"> <div className="content">
<h1>Starting Docker VM</h1> <h1>{title}</h1>
<p>To run Docker containers on your computer, Kitematic is starting a Linux virtual machine. This may take a minute...</p> <p>{descr}</p>
</div> </div>
</div> </div>
</div> </div>
@ -84,22 +95,24 @@ var Setup = React.createClass({
renderError: function () { renderError: function () {
let deleteVmAndRetry; let deleteVmAndRetry;
if (Util.isLinux()) { if (util.isLinux()) {
if (!this.state.started) { if (!this.state.started) {
deleteVmAndRetry = ( deleteVmAndRetry = (
<button className="btn btn-action" onClick={this.handleLinuxDockerInstall}>Install Docker</button> <button className="btn btn-action" onClick={this.handleLinuxDockerInstall}>Install Docker</button>
); );
} }
} else if (util.isNative()) {
deleteVmAndRetry = (
<button className="btn btn-action" onClick={this.handleUseVbox}>Use VirtualBox</button>
);
} else if (this.state.started) {
deleteVmAndRetry = (
<button className="btn btn-action" onClick={this.handleErrorRemoveRetry}>Delete VM &amp; Retry Setup</button>
);
} else { } else {
if (this.state.started) { deleteVmAndRetry = (
deleteVmAndRetry = ( <button className="btn btn-action" onClick={this.handleToolBox}>Get Toolbox</button>
<button className="btn btn-action" onClick={this.handleErrorRemoveRetry}>Delete VM &amp; Retry Setup</button> );
);
} else {
deleteVmAndRetry = (
<button className="btn btn-action" onClick={this.handleToolBox}>Get Toolbox</button>
);
}
} }
return ( return (
<div className="setup"> <div className="setup">

View File

@ -158,11 +158,12 @@ var DockerMachine = {
} }
}); });
}); });
} else if (util.isLinux()) { } else if (util.isNative()) {
cmd = cmd || process.env.SHELL; cmd = cmd || process.env.SHELL;
var terminal = util.linuxTerminal(); var terminal = util.isLinux() ? util.linuxTerminal() : path.join(process.env.RESOURCES_PATH, 'terminal');
if (terminal) if (terminal) {
util.execFile(terminal.concat([cmd])).then(() => {}); util.execFile([terminal, cmd]).then(() => {});
}
} else { } else {
cmd = cmd || process.env.SHELL; cmd = cmd || process.env.SHELL;
this.url(machineName).then(machineUrl => { this.url(machineName).then(machineUrl => {

View File

@ -11,6 +11,9 @@ import containerServerActions from '../actions/ContainerServerActions';
import rimraf from 'rimraf'; import rimraf from 'rimraf';
import stream from 'stream'; import stream from 'stream';
import JSONStream from 'JSONStream'; import JSONStream from 'JSONStream';
import Promise from 'bluebird';
export default { export default {
host: null, host: null,
@ -20,20 +23,23 @@ export default {
activeContainerName: null, activeContainerName: null,
setup (ip, name) { setup (ip, name) {
if (!ip || !name) { if (!ip && !name) {
throw new Error('Falsy ip or name passed to docker client setup'); throw new Error('Falsy ip or name passed to docker client setup');
} }
this.host = ip;
if (util.isLinux()) { if (ip.indexOf('local') !== -1) {
this.host = 'localhost'; try {
this.client = new dockerode({socketPath: '/var/run/docker.sock'}); this.client = new dockerode({socketPath: '/var/run/docker.sock'});
} catch (error) {
throw new Error('Cannot connect to the Docker daemon. Is the daemon running?');
}
} else { } else {
let certDir = path.join(util.home(), '.docker/machine/machines/', name); let certDir = path.join(util.home(), '.docker/machine/machines/', name);
if (!fs.existsSync(certDir)) { if (!fs.existsSync(certDir)) {
throw new Error('Certificate directory does not exist'); throw new Error('Certificate directory does not exist');
} }
this.host = ip;
this.client = new dockerode({ this.client = new dockerode({
protocol: 'https', protocol: 'https',
host: ip, host: ip,
@ -45,6 +51,28 @@ export default {
} }
}, },
async version () {
let version = null;
let maxRetries = 10;
let retries = 0;
let error_message = "";
while (version == null && retries < maxRetries) {
this.client.version((error,data) => {
if (!error) {
version = data.Version;
} else {
error_message = error;
}
retries++;
});
await Promise.delay(1000);
}
if (version == null) {
throw new Error(error_message);
}
return version;
},
init () { init () {
this.placeholders = JSON.parse(localStorage.getItem('placeholders')) || {}; this.placeholders = JSON.parse(localStorage.getItem('placeholders')) || {};
this.fetchAllContainers(); this.fetchAllContainers();
@ -88,6 +116,7 @@ export default {
container.start((error) => { container.start((error) => {
if (error) { if (error) {
containerServerActions.error({name, error}); containerServerActions.error({name, error});
console.log('error starting: %o - %o', name, error);
return; return;
} }
containerServerActions.started({name, error}); containerServerActions.started({name, error});
@ -118,7 +147,12 @@ export default {
containerData.PublishAllPorts = true; containerData.PublishAllPorts = true;
} }
containerData.Cmd = image.Config.Cmd || image.Config.Entrypoint || 'bash'; if (image.Config.Cmd) {
containerData.Cmd = image.Config.Cmd;
} else if (!image.Config.Entrypoint) {
containerData.Cmd = 'bash';
}
let existing = this.client.getContainer(name); let existing = this.client.getContainer(name);
existing.kill(() => { existing.kill(() => {
existing.remove(() => { existing.remove(() => {
@ -151,6 +185,7 @@ export default {
fetchAllContainers () { fetchAllContainers () {
this.client.listContainers({all: true}, (err, containers) => { this.client.listContainers({all: true}, (err, containers) => {
if (err) { if (err) {
console.error(err);
return; return;
} }
async.map(containers, (container, callback) => { async.map(containers, (container, callback) => {
@ -166,6 +201,7 @@ export default {
containers = containers.filter(c => c !== null); containers = containers.filter(c => c !== null);
if (err) { if (err) {
// TODO: add a global error handler for this // TODO: add a global error handler for this
console.error(err);
return; return;
} }
containerServerActions.allUpdated({containers: _.indexBy(containers.concat(_.values(this.placeholders)), 'Name')}); containerServerActions.allUpdated({containers: _.indexBy(containers.concat(_.values(this.placeholders)), 'Name')});
@ -362,6 +398,8 @@ export default {
timestamps: 1 timestamps: 1
}, (err, logStream) => { }, (err, logStream) => {
if (err) { if (err) {
// socket hang up can be captured
console.error(err);
return; return;
} }
@ -388,6 +426,8 @@ export default {
timestamps: 1 timestamps: 1
}, (err, logStream) => { }, (err, logStream) => {
if (err) { if (err) {
// Socket hang up also can be found here
console.error(err);
return; return;
} }

View File

@ -11,8 +11,14 @@ import machine from './DockerMachineUtil';
import docker from './DockerUtil'; import docker from './DockerUtil';
import router from '../router'; import router from '../router';
// Docker Machine exits with 3 to differentiate pre-create check failures (e.g.
// virtualization isn't enabled) from normal errors during create (exit code
// 1).
const precreateCheckExitCode = 3;
let _retryPromise = null; let _retryPromise = null;
let _timers = []; let _timers = [];
let useNative = util.isNative() ? util.isNative() : true;
export default { export default {
simulateProgress (estimateSeconds) { simulateProgress (estimateSeconds) {
@ -31,12 +37,21 @@ export default {
_timers = []; _timers = [];
}, },
async useVbox () {
metrics.track('Retried Setup with VBox');
localStorage.setItem('settings.useNative', false);
router.get().transitionTo('loading');
setupServerActions.error({ error: { message: null }});
_retryPromise.resolve();
},
retry (removeVM) { retry (removeVM) {
metrics.track('Retried Setup', { metrics.track('Retried Setup', {
removeVM removeVM
}); });
router.get().transitionTo('loading'); router.get().transitionTo('loading');
setupServerActions.error({ error: { message: null }});
if (removeVM) { if (removeVM) {
machine.rm().finally(() => { machine.rm().finally(() => {
_retryPromise.resolve(); _retryPromise.resolve();
@ -51,30 +66,44 @@ export default {
return _retryPromise.promise; return _retryPromise.promise;
}, },
setup() { async setup () {
return util.isLinux() ? this.nativeSetup() : this.nonNativeSetup(); while (true) {
try {
if (util.isNative()) {
localStorage.setItem('setting.useNative', true);
let stats = fs.statSync('/var/run/docker.sock');
if (stats.isSocket()) {
await this.nativeSetup();
} else {
throw new Error('File found is not a socket');
}
} else {
await this.nonNativeSetup();
}
return;
} catch (error) {
metrics.track('Native Setup Failed');
setupServerActions.error({error});
bugsnag.notify('Native Setup Failed', error.message, {
'Docker Error': error.message
}, 'info');
this.clearTimers();
await this.pause();
}
}
}, },
async nativeSetup () { async nativeSetup () {
while (true) { while (true) {
try { try {
docker.setup('localhost', machine.name());
docker.isDockerRunning();
break;
} catch (error) {
router.get().transitionTo('setup'); router.get().transitionTo('setup');
metrics.track('Native Setup Failed'); docker.setup(util.isLinux() ? 'localhost':'docker.local');
setupServerActions.error({error}); setupServerActions.started({started: true});
this.simulateProgress(20);
let message = error.message.split('\n'); return docker.version();
let lastLine = message.length > 1 ? message[message.length - 2] : 'Docker Machine encountered an error.'; } catch (error) {
bugsnag.notify('Native Setup Failed', lastLine, { throw new Error(error);
'Docker Machine Logs': error.message
}, 'info');
this.clearTimers();
await this.pause();
} }
} }
}, },
@ -86,7 +115,7 @@ export default {
try { try {
setupServerActions.started({started: false}); setupServerActions.started({started: false});
// Make sure virtulBox and docker-machine are installed // Make sure virtualBox and docker-machine are installed
let virtualBoxInstalled = virtualBox.installed(); let virtualBoxInstalled = virtualBox.installed();
let machineInstalled = machine.installed(); let machineInstalled = machine.installed();
if (!virtualBoxInstalled || !machineInstalled) { if (!virtualBoxInstalled || !machineInstalled) {
@ -122,13 +151,16 @@ export default {
} else { } else {
let state = await machine.status(); let state = await machine.status();
if (state !== 'Running') { if (state !== 'Running') {
router.get().transitionTo('setup');
setupServerActions.started({started: true});
if (state === 'Saved') { if (state === 'Saved') {
router.get().transitionTo('setup');
this.simulateProgress(10); this.simulateProgress(10);
} else if (state === 'Stopped') { } else if (state === 'Stopped') {
router.get().transitionTo('setup');
this.simulateProgress(25); this.simulateProgress(25);
} else {
this.simulateProgress(40);
} }
await machine.start(); await machine.start();
} }
} }
@ -146,6 +178,7 @@ export default {
if (ip) { if (ip) {
docker.setup(ip, machine.name()); docker.setup(ip, machine.name());
await docker.version();
} else { } else {
throw new Error('Could not determine IP from docker-machine.'); throw new Error('Could not determine IP from docker-machine.');
} }
@ -154,11 +187,17 @@ export default {
} catch (error) { } catch (error) {
router.get().transitionTo('setup'); router.get().transitionTo('setup');
let novtx = error.message.indexOf('This computer doesn\'t have VT-X/AMD-v enabled') !== -1; if (error.code === precreateCheckExitCode) {
metrics.track(novtx ? 'Setup Halted' : 'Setup Failed', { metrics.track('Setup Halted', {
virtualBoxVersion, virtualBoxVersion,
machineVersion machineVersion
}); });
} else {
metrics.track('Setup Failed', {
virtualBoxVersion,
machineVersion
});
}
let message = error.message.split('\n'); let message = error.message.split('\n');
let lastLine = message.length > 1 ? message[message.length - 2] : 'Docker Machine encountered an error.'; let lastLine = message.length > 1 ? message[message.length - 2] : 'Docker Machine encountered an error.';

View File

@ -13,7 +13,7 @@ module.exports = {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
child_process.execFile(args[0], args.slice(1), options, (error, stdout) => { child_process.execFile(args[0], args.slice(1), options, (error, stdout) => {
if (error) { if (error) {
reject(new Error('Encountered an error: ' + error)); reject(error);
} else { } else {
resolve(stdout); resolve(stdout);
} }
@ -37,6 +37,24 @@ module.exports = {
isLinux: function () { isLinux: function () {
return process.platform === 'linux'; return process.platform === 'linux';
}, },
isNative: function () {
// let native = JSON.parse(localStorage.getItem('settings.useNative'));
let native = null;
if (native === null) {
try {
// Check if file exists
fs.statSync('/var/run/docker.sock');
native = true;
} catch (e) {
if (this.isLinux()) {
native = true;
} else {
native = false;
}
}
}
return native;
},
binsPath: function () { binsPath: function () {
return this.isWindows() ? path.join(this.home(), 'Kitematic-bins') : path.join('/usr/local/bin'); return this.isWindows() ? path.join(this.home(), 'Kitematic-bins') : path.join('/usr/local/bin');
}, },
@ -62,9 +80,6 @@ module.exports = {
// TODO: fix me for windows 7 // TODO: fix me for windows 7
return 'Documents'; return 'Documents';
}, },
supportDir: function () {
return app.getPath('userData');
},
CommandOrCtrl: function () { CommandOrCtrl: function () {
return this.isWindows() ? 'Ctrl' : 'Command'; return this.isWindows() ? 'Ctrl' : 'Command';
}, },
@ -95,7 +110,7 @@ module.exports = {
// An official repo is alphanumeric characters separated by dashes or // An official repo is alphanumeric characters separated by dashes or
// underscores. // underscores.
// Examples: myrepo, my-docker-repo, my_docker_repo // Examples: myrepo, my-docker-repo, my_docker_repo
// Non-exapmles: mynamespace/myrepo, my%!repo // Non-examples: mynamespace/myrepo, my%!repo
var repoRegexp = /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/; var repoRegexp = /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/;
return repoRegexp.test(name); return repoRegexp.test(name);
}, },
@ -166,9 +181,9 @@ module.exports = {
dialog.showMessageBox({ dialog.showMessageBox({
type: 'warning', type: 'warning',
buttons: ['OK'], 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.' message: 'The terminal emulator symbolic link doesn\'t exists. Please read the Wiki at https://github.com/docker/kitematic/wiki/Early-Linux-Support.'
}); });
return; return false;
} }
}, },
webPorts: ['80', '8000', '8080', '8888', '3000', '5000', '2368', '9200', '8983'] webPorts: ['80', '8000', '8080', '8888', '3000', '5000', '2368', '9200', '8983']

View File

@ -39,9 +39,9 @@ var VirtualBox = {
return util.execFile([this.command(), 'sharedfolder', 'add', vmName, '--name', pathName, '--hostpath', hostPath, '--automount']); return util.execFile([this.command(), 'sharedfolder', 'add', vmName, '--name', pathName, '--hostpath', hostPath, '--automount']);
}, },
vmExists: function (name) { vmExists: function (name) {
return util.execFile([this.command(), 'showvminfo', name]).then(() => { return util.execFile([this.command(), 'list', 'vms']).then(out => {
return true; return out.indexOf('"' + name + '"') !== -1;
}).catch((err) => { }).catch(() => {
return false; return false;
}); });
} }

View File

@ -10,7 +10,7 @@ import metrics from './MetricsUtil';
var WebUtil = { var WebUtil = {
addWindowSizeSaving: function () { addWindowSizeSaving: function () {
window.addEventListener('resize', function () { window.addEventListener('resize', function () {
fs.writeFileSync(path.join(util.supportDir(), 'size'), JSON.stringify({ fs.writeFileSync(path.join(app.getPath('userData'), 'size'), JSON.stringify({
width: window.outerWidth, width: window.outerWidth,
height: window.outerHeight height: window.outerHeight
})); }));