mirror of https://github.com/docker/docs.git
Merge remote-tracking branch 'kitematic/master'
This commit is contained in:
commit
19090252b9
|
|
@ -2,6 +2,7 @@
|
||||||
.swp
|
.swp
|
||||||
build
|
build
|
||||||
dist
|
dist
|
||||||
|
release
|
||||||
installer
|
installer
|
||||||
node_modules
|
node_modules
|
||||||
coverage
|
coverage
|
||||||
|
|
|
||||||
31
.jshintrc
31
.jshintrc
|
|
@ -1,31 +0,0 @@
|
||||||
{
|
|
||||||
"curly": true,
|
|
||||||
"noempty": true,
|
|
||||||
"newcap": true,
|
|
||||||
"eqeqeq": true,
|
|
||||||
"eqnull": true,
|
|
||||||
"esnext": true,
|
|
||||||
"undef": true,
|
|
||||||
"unused": true,
|
|
||||||
"devel": true,
|
|
||||||
"node": true,
|
|
||||||
"browser": true,
|
|
||||||
"evil": false,
|
|
||||||
"latedef": true,
|
|
||||||
"nonew": true,
|
|
||||||
"trailing": true,
|
|
||||||
"immed": true,
|
|
||||||
"smarttabs": true,
|
|
||||||
"strict": false,
|
|
||||||
"quotmark": false,
|
|
||||||
"nonbsp": true,
|
|
||||||
"noempty": true,
|
|
||||||
"camelcase": false,
|
|
||||||
"jasmine": true,
|
|
||||||
"globals": {
|
|
||||||
"define": true,
|
|
||||||
"jest": true,
|
|
||||||
"pit": true
|
|
||||||
},
|
|
||||||
"predef": [ "-Promise" ]
|
|
||||||
}
|
|
||||||
18
.travis.yml
18
.travis.yml
|
|
@ -1,18 +1,14 @@
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- "4.1"
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
- resources
|
- node_modules
|
||||||
- node_modules
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- brew unlink node
|
|
||||||
- brew update
|
|
||||||
- brew install homebrew/versions/node4-lts
|
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- npm install
|
- npm install
|
||||||
- npm test
|
- npm test
|
||||||
- '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && npm run integration || false'
|
|
||||||
- '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && ./ci/release || false'
|
|
||||||
|
|
||||||
os:
|
|
||||||
- osx
|
|
||||||
|
|
|
||||||
18
Gruntfile.js
18
Gruntfile.js
|
|
@ -22,6 +22,7 @@ module.exports = function (grunt) {
|
||||||
var BASENAME = 'Kitematic';
|
var BASENAME = 'Kitematic';
|
||||||
var OSX_APPNAME = BASENAME + ' (Beta)';
|
var OSX_APPNAME = BASENAME + ' (Beta)';
|
||||||
var WINDOWS_APPNAME = BASENAME + ' (Alpha)';
|
var WINDOWS_APPNAME = BASENAME + ' (Alpha)';
|
||||||
|
var LINUX_APPNAME = BASENAME + ' (Alpha)';
|
||||||
var OSX_OUT = './dist';
|
var OSX_OUT = './dist';
|
||||||
var OSX_OUT_X64 = OSX_OUT + '/' + OSX_APPNAME + '-darwin-x64';
|
var OSX_OUT_X64 = OSX_OUT + '/' + OSX_APPNAME + '-darwin-x64';
|
||||||
var OSX_FILENAME = OSX_OUT_X64 + '/' + OSX_APPNAME + '.app';
|
var OSX_FILENAME = OSX_OUT_X64 + '/' + OSX_APPNAME + '.app';
|
||||||
|
|
@ -54,6 +55,18 @@ module.exports = function (grunt) {
|
||||||
platform: 'darwin',
|
platform: 'darwin',
|
||||||
arch: 'x64',
|
arch: 'x64',
|
||||||
asar: true,
|
asar: true,
|
||||||
|
'app-version': packagejson.version
|
||||||
|
}
|
||||||
|
},
|
||||||
|
linux: {
|
||||||
|
options: {
|
||||||
|
name: LINUX_APPNAME,
|
||||||
|
dir: 'build/',
|
||||||
|
out: 'dist',
|
||||||
|
version: packagejson['electron-version'],
|
||||||
|
platform: 'linux',
|
||||||
|
arch: 'x64',
|
||||||
|
asar: true,
|
||||||
'app-bundle-id': 'com.kitematic.kitematic',
|
'app-bundle-id': 'com.kitematic.kitematic',
|
||||||
'app-version': packagejson.version
|
'app-version': packagejson.version
|
||||||
}
|
}
|
||||||
|
|
@ -195,7 +208,7 @@ module.exports = function (grunt) {
|
||||||
].join(' && '),
|
].join(' && '),
|
||||||
},
|
},
|
||||||
zip: {
|
zip: {
|
||||||
command: 'ditto -c -k --sequesterRsrc --keepParent <%= OSX_FILENAME_ESCAPED %> dist/' + BASENAME + '-' + packagejson.version + '-Mac.zip',
|
command: 'ditto -c -k --sequesterRsrc --keepParent <%= OSX_FILENAME_ESCAPED %> release/' + BASENAME + '-Mac.zip',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -206,7 +219,7 @@ module.exports = function (grunt) {
|
||||||
compress: {
|
compress: {
|
||||||
windows: {
|
windows: {
|
||||||
options: {
|
options: {
|
||||||
archive: './dist/' + BASENAME + '-' + packagejson.version + '-Windows-Alpha.zip',
|
archive: './release/' + BASENAME + '-Windows.zip',
|
||||||
mode: 'zip'
|
mode: 'zip'
|
||||||
},
|
},
|
||||||
files: [{
|
files: [{
|
||||||
|
|
@ -244,7 +257,6 @@ module.exports = function (grunt) {
|
||||||
|
|
||||||
grunt.registerTask('default', ['newer:babel', 'less', 'newer:copy:dev', 'shell:electron', 'watchChokidar']);
|
grunt.registerTask('default', ['newer:babel', 'less', 'newer:copy:dev', 'shell:electron', 'watchChokidar']);
|
||||||
grunt.registerTask('release', ['clean:release', 'babel', 'less', 'copy:dev', 'electron', 'copy:osx', 'shell:sign', 'shell:zip', 'copy:windows', 'rcedit:exes', 'compress']);
|
grunt.registerTask('release', ['clean:release', 'babel', 'less', 'copy:dev', 'electron', 'copy:osx', 'shell:sign', 'shell:zip', 'copy:windows', 'rcedit:exes', 'compress']);
|
||||||
grunt.registerTask('release-mac', ['clean:release', 'babel', 'less', 'copy:dev', 'electron:osx', 'copy:osx', 'shell:sign', 'shell:zip']);
|
|
||||||
|
|
||||||
process.on('SIGINT', function () {
|
process.on('SIGINT', function () {
|
||||||
grunt.task.run(['shell:electron:kill']);
|
grunt.task.run(['shell:electron:kill']);
|
||||||
|
|
|
||||||
50
MAINTAINERS
50
MAINTAINERS
|
|
@ -1,4 +1,46 @@
|
||||||
Jeff Morgan <jmorgan@docker.com> (@jeffdm)
|
# Kitematic maintainers file
|
||||||
Sean Li <mail@shang.li> (@elesant)
|
#
|
||||||
Michael Chiang <mchiang@docker.com> (@mchiang0610)
|
# This file describes who runs the docker/kitematic project and how.
|
||||||
Ben French <me@frenchben.com> (@FrenchBen)
|
# This is a living document - if you see something out of date or missing, speak up!
|
||||||
|
#
|
||||||
|
# It is structured to be consumable by both humans and programs.
|
||||||
|
# To extract its contents programmatically, use any TOML-compliant parser.
|
||||||
|
#
|
||||||
|
# This file is compiled into the MAINTAINERS file in docker/opensource.
|
||||||
|
#
|
||||||
|
[Org]
|
||||||
|
[Org."Core maintainers"]
|
||||||
|
people = [
|
||||||
|
"elesant",
|
||||||
|
"FrenchBen",
|
||||||
|
"jeffdm",
|
||||||
|
"mchiang0610",
|
||||||
|
]
|
||||||
|
|
||||||
|
[people]
|
||||||
|
|
||||||
|
# A reference list of all people associated with the project.
|
||||||
|
# All other sections should refer to people by their canonical key
|
||||||
|
# in the people section.
|
||||||
|
|
||||||
|
# ADD YOURSELF HERE IN ALPHABETICAL ORDER
|
||||||
|
|
||||||
|
[people.elesant]
|
||||||
|
Name = "Sean Li"
|
||||||
|
Email = "mail@shang.li"
|
||||||
|
GitHub = "elesant"
|
||||||
|
|
||||||
|
[people.FrenchBen]
|
||||||
|
Name = "Ben French"
|
||||||
|
Email = "me@frenchben.com"
|
||||||
|
GitHub = "FrenchBen"
|
||||||
|
|
||||||
|
[people.jeffdm]
|
||||||
|
Name = "Jeff Morgan"
|
||||||
|
Email = "jmorgan@docker.com"
|
||||||
|
GitHub = "jeffdm"
|
||||||
|
|
||||||
|
[people.mchiang0610]
|
||||||
|
Name = "Michael Chiang"
|
||||||
|
Email = "mchiang@docker.com"
|
||||||
|
GitHub = "mchiang0610"
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
[](https://travis-ci.org/kitematic/kitematic)
|
[](https://travis-ci.org/docker/kitematic)
|
||||||
|
|
||||||
|
|
||||||
[](https://kitematic.com)
|
[](https://kitematic.com)
|
||||||
|
|
||||||
Kitematic is a simple application for managing Docker containers on Mac and Windows.
|
Kitematic is a simple application for managing Docker containers on Mac, Linux and Windows.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
echo $MAC_KEY_CONTENT > mac_key_content.hex && xxd -p -r mac_key_content.hex ~/Library/Keychains/keychain.keychain && rm mac_key_content.hex
|
|
||||||
security unlock-keychain -p "$MAC_KEY_SECRET" ~/Library/Keychains/keychain.keychain
|
|
||||||
security default-keychain -s keychain.keychain
|
|
||||||
security list-keychains
|
|
||||||
npm run release
|
|
||||||
16
circle.yml
16
circle.yml
|
|
@ -1,3 +1,15 @@
|
||||||
machine:
|
machine:
|
||||||
node:
|
xcode:
|
||||||
version: 4.1.2
|
version: "7.0"
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
cache_directories:
|
||||||
|
- "node_modules"
|
||||||
|
|
||||||
|
deployment:
|
||||||
|
release:
|
||||||
|
tag: /v.*/
|
||||||
|
owner: docker
|
||||||
|
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-Windows.zip --name Kitematic-Windows.zip
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
FROM docs/base:hugo-github-linking
|
FROM docs/base:latest
|
||||||
MAINTAINER Mary Anthony <mary@docker.com> (@moxiegirl)
|
MAINTAINER Mary Anthony <mary@docker.com> (@moxiegirl)
|
||||||
|
|
||||||
RUN svn checkout https://github.com/docker/compose/trunk/docs /docs/content/compose
|
RUN svn checkout https://github.com/docker/compose/trunk/docs /docs/content/compose
|
||||||
|
|
@ -9,7 +9,8 @@ RUN svn checkout https://github.com/docker/tutorials/trunk/docs /docs/content
|
||||||
RUN svn checkout https://github.com/docker/opensource/trunk/docs /docs/content/opensource
|
RUN svn checkout https://github.com/docker/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
|
||||||
|
|
||||||
|
ENV PROJECT=kitematic
|
||||||
# To get the git info for this repo
|
# To get the git info for this repo
|
||||||
COPY . /src
|
COPY . /src
|
||||||
|
|
||||||
COPY . /docs/content/kitematic/
|
COPY . /docs/content/$PROJECT/
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ using Kitematic and Docker.
|
||||||
### Create Minecraft Server Container
|
### Create Minecraft Server Container
|
||||||
|
|
||||||
First, if you haven't yet done so, [download and start
|
First, if you haven't yet done so, [download and start
|
||||||
Kitematic](/). Once installed and running, the app should look like this:
|
Kitematic](index.md). Once installed and running, the app should look like this:
|
||||||
|
|
||||||
Create a container from the recommended Minecraft image by clicking the "Create"
|
Create a container from the recommended Minecraft image by clicking the "Create"
|
||||||
button.
|
button.
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ Let's get to it!
|
||||||
#### Running the Nginx Web Server Container
|
#### Running the Nginx Web Server Container
|
||||||
|
|
||||||
First, if you haven't yet done so, [download and start
|
First, if you haven't yet done so, [download and start
|
||||||
Kitematic](/). Once installed and running, the app should look like this:
|
Kitematic](index.md). Once installed and running, the app should look like this:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ In this tutorial, you will:
|
||||||
### Setting up RethinkDB in Kitematic
|
### Setting up RethinkDB in Kitematic
|
||||||
|
|
||||||
First, if you haven't yet done so, [download and start
|
First, if you haven't yet done so, [download and start
|
||||||
Kitematic](/). Once open, the app should look like
|
Kitematic](index.md). Once open, the app should look like
|
||||||
this:
|
this:
|
||||||
|
|
||||||

|

|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ stream logs, and single click terminal into your Docker container all from the
|
||||||
GUI.
|
GUI.
|
||||||
|
|
||||||
First, if you haven't yet done so, [download and start
|
First, if you haven't yet done so, [download and start
|
||||||
Kitematic](/).
|
Kitematic](index.md).
|
||||||
|
|
||||||
## Container list
|
## Container list
|
||||||
|
|
||||||
|
|
|
||||||
11
package.json
11
package.json
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "Kitematic",
|
"name": "Kitematic",
|
||||||
"version": "0.9.3",
|
"version": "0.9.4",
|
||||||
"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/",
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
"bugs": "https://github.com/kitematic/kitematic/issues",
|
"bugs": "https://github.com/kitematic/kitematic/issues",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "grunt",
|
"start": "grunt",
|
||||||
|
"start-dev": "NODE_ENV=development grunt",
|
||||||
"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",
|
||||||
|
|
@ -19,8 +20,9 @@
|
||||||
"lint": "jsxhint src"
|
"lint": "jsxhint src"
|
||||||
},
|
},
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"electron-version": "0.33.6",
|
"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",
|
||||||
|
|
@ -30,8 +32,7 @@
|
||||||
"classnames": "^2.1.5",
|
"classnames": "^2.1.5",
|
||||||
"coveralls": "^2.11.2",
|
"coveralls": "^2.11.2",
|
||||||
"deep-extend": "^0.4.0",
|
"deep-extend": "^0.4.0",
|
||||||
"dockerode": "^2.2.3",
|
"dockerode": "^2.2.7",
|
||||||
"exec": "0.2.1",
|
|
||||||
"install": "^0.1.8",
|
"install": "^0.1.8",
|
||||||
"jquery": "^2.1.3",
|
"jquery": "^2.1.3",
|
||||||
"mixpanel": "kitematic/mixpanel-node",
|
"mixpanel": "kitematic/mixpanel-node",
|
||||||
|
|
@ -54,7 +55,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel": "^5.8.23",
|
"babel": "^5.8.23",
|
||||||
"babel-jest": "^5.2.0",
|
"babel-jest": "^5.2.0",
|
||||||
"electron-prebuilt": "^0.33.6",
|
"electron-prebuilt": "^0.35.4",
|
||||||
"eslint": "^1.3.1",
|
"eslint": "^1.3.1",
|
||||||
"eslint-plugin-react": "^3.3.0",
|
"eslint-plugin-react": "^3.3.0",
|
||||||
"grunt": "^0.4.5",
|
"grunt": "^0.4.5",
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,10 @@ class ContainerActions {
|
||||||
run (name, repo, tag) {
|
run (name, repo, tag) {
|
||||||
dockerUtil.run(name, repo, tag);
|
dockerUtil.run(name, repo, tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
active (name) {
|
||||||
|
dockerUtil.active(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default alt.createActions(ContainerActions);
|
export default alt.createActions(ContainerActions);
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,9 @@ class ContainerServerActions {
|
||||||
'updated',
|
'updated',
|
||||||
'waiting',
|
'waiting',
|
||||||
'kill',
|
'kill',
|
||||||
'stopped'
|
'stopped',
|
||||||
|
'log',
|
||||||
|
'logs'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import Router from 'react-router';
|
||||||
import routes from './routes';
|
import routes from './routes';
|
||||||
import routerContainer from './router';
|
import routerContainer from './router';
|
||||||
import repositoryActions from './actions/RepositoryActions';
|
import repositoryActions from './actions/RepositoryActions';
|
||||||
|
import util from './utils/Util';
|
||||||
var app = remote.require('app');
|
var app = remote.require('app');
|
||||||
|
|
||||||
hubUtil.init();
|
hubUtil.init();
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,10 @@ app.on('ready', function () {
|
||||||
show: false
|
show: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
mainWindow.openDevTools({detach: true});
|
||||||
|
}
|
||||||
|
|
||||||
mainWindow.loadUrl(path.normalize('file://' + path.join(__dirname, 'index.html')));
|
mainWindow.loadUrl(path.normalize('file://' + path.join(__dirname, 'index.html')));
|
||||||
|
|
||||||
app.on('activate-with-no-open-windows', function () {
|
app.on('activate-with-no-open-windows', function () {
|
||||||
|
|
|
||||||
|
|
@ -28,17 +28,10 @@ var ContainerHomeFolder = React.createClass({
|
||||||
var mounts = _.clone(this.props.container.Mounts);
|
var mounts = _.clone(this.props.container.Mounts);
|
||||||
var newSource = path.join(util.home(), util.documents(), 'Kitematic', this.props.container.Name, destination);
|
var newSource = path.join(util.home(), util.documents(), 'Kitematic', this.props.container.Name, destination);
|
||||||
|
|
||||||
var binds = mounts.map(function (m) {
|
mounts.forEach(m => {
|
||||||
let source = m.Source;
|
|
||||||
if (m.Destination === destination) {
|
if (m.Destination === destination) {
|
||||||
source = newSource;
|
m.Source = util.windowsToLinuxPath(newSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(util.isWindows()) {
|
|
||||||
return util.windowsToLinuxPath(source) + ':' + m.Destination;
|
|
||||||
}
|
|
||||||
|
|
||||||
return source + ':' + m.Destination;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
mkdirp(newSource, function (err) {
|
mkdirp(newSource, function (err) {
|
||||||
|
|
@ -48,7 +41,7 @@ var ContainerHomeFolder = React.createClass({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
containerActions.update(this.props.container.Name, {Binds: binds});
|
containerActions.update(this.props.container.Name, {Mounts: mounts});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,74 +1,44 @@
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import React from 'react/addons';
|
import React from 'react/addons';
|
||||||
import LogStore from '../stores/LogStore';
|
|
||||||
import Router from 'react-router';
|
import Router from 'react-router';
|
||||||
import metrics from '../utils/MetricsUtil';
|
import containerActions from '../actions/ContainerActions';
|
||||||
|
import Convert from 'ansi-to-html';
|
||||||
|
|
||||||
var _prevBottom = 0;
|
let escape = function (html) {
|
||||||
|
var text = document.createTextNode(html);
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.appendChild(text);
|
||||||
|
return div.innerHTML;
|
||||||
|
};
|
||||||
|
|
||||||
|
let convert = new Convert();
|
||||||
|
let prevBottom = 0;
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
mixins: [Router.Navigation],
|
|
||||||
getInitialState: function () {
|
componentDidUpdate: function () {
|
||||||
return {
|
var node = $('.logs').get()[0];
|
||||||
logs: []
|
node.scrollTop = node.scrollHeight;
|
||||||
};
|
|
||||||
},
|
|
||||||
componentDidMount: function() {
|
|
||||||
if (!this.props.container) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.update();
|
|
||||||
this.scrollToBottom();
|
|
||||||
LogStore.on(LogStore.SERVER_LOGS_EVENT, this.update);
|
|
||||||
LogStore.fetch(this.props.container.Name);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillReceiveProps: function (nextProps) {
|
componentWillReceiveProps: function (nextProps) {
|
||||||
if (this.props.container && nextProps.container && this.props.container.Name !== nextProps.container.Name) {
|
if (this.props.container && nextProps.container && this.props.container.Name !== nextProps.container.Name) {
|
||||||
LogStore.detach(this.props.container.Name);
|
containerActions.active(nextProps.container.Name);
|
||||||
LogStore.fetch(nextProps.container.Name);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentDidMount: function () {
|
||||||
if (!this.props.container) {
|
containerActions.active(this.props.container.Name);
|
||||||
return;
|
},
|
||||||
}
|
|
||||||
|
|
||||||
LogStore.detach(this.props.container.Name);
|
componentWillUnmount: function () {
|
||||||
LogStore.removeListener(LogStore.SERVER_LOGS_EVENT, this.update);
|
containerActions.active(null);
|
||||||
},
|
|
||||||
componentDidUpdate: function () {
|
|
||||||
this.scrollToBottom();
|
|
||||||
},
|
|
||||||
scrollToBottom: function () {
|
|
||||||
var parent = $('.logs');
|
|
||||||
if (parent[0].scrollHeight - parent.height() >= _prevBottom - 50) {
|
|
||||||
parent.scrollTop(parent[0].scrollHeight - parent.height());
|
|
||||||
}
|
|
||||||
_prevBottom = parent[0].scrollHeight - parent.height();
|
|
||||||
},
|
|
||||||
handleClickLogs: function () {
|
|
||||||
metrics.track('Viewed Logs', {
|
|
||||||
from: 'preview'
|
|
||||||
});
|
|
||||||
this.context.router.transitionTo('containerLogs', {name: this.props.container.Name});
|
|
||||||
},
|
|
||||||
update: function () {
|
|
||||||
if (!this.props.container) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
logs: LogStore.logs(this.props.container.Name)
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function () {
|
render: function () {
|
||||||
var logs = this.state.logs.map(function (l, i) {
|
let logs = this.props.container.Logs ?
|
||||||
return <span key={i} dangerouslySetInnerHTML={{__html: l}}></span>;
|
this.props.container.Logs.map((l) => <div key={l.substr(0,l.indexOf(' '))} dangerouslySetInnerHTML={{__html: convert.toHtml(escape(l.substr(l.indexOf(' ')+1)).replace(/ /g, ' <wbr>'))}}></div>) :
|
||||||
});
|
['0 No logs for this container.'];
|
||||||
if (logs.length === 0) {
|
|
||||||
logs = "No logs for this container.";
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div className="mini-logs wrapper">
|
<div className="mini-logs wrapper">
|
||||||
<div className="widget">
|
<div className="widget">
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,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}>
|
<li onMouseEnter={self.handleItemMouseEnter} onMouseLeave={self.handleItemMouseLeave} onClick={self.handleClick}>
|
||||||
{state}
|
{state}
|
||||||
<div className="info">
|
<div className="info">
|
||||||
<div className="name">
|
<div className="name">
|
||||||
|
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
import $ from 'jquery';
|
|
||||||
import React from 'react/addons';
|
|
||||||
import LogStore from '../stores/LogStore';
|
|
||||||
|
|
||||||
var _prevBottom = 0;
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
getInitialState: function () {
|
|
||||||
return {
|
|
||||||
logs: []
|
|
||||||
};
|
|
||||||
},
|
|
||||||
componentDidMount: function() {
|
|
||||||
if (!this.props.container) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.update();
|
|
||||||
this.scrollToBottom();
|
|
||||||
LogStore.on(LogStore.SERVER_LOGS_EVENT, this.update);
|
|
||||||
LogStore.fetch(this.props.container.Name);
|
|
||||||
},
|
|
||||||
componentWillUnmount: function() {
|
|
||||||
if (!this.props.container) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LogStore.detach(this.props.container.Name);
|
|
||||||
LogStore.removeListener(LogStore.SERVER_LOGS_EVENT, this.update);
|
|
||||||
},
|
|
||||||
componentDidUpdate: function () {
|
|
||||||
this.scrollToBottom();
|
|
||||||
},
|
|
||||||
scrollToBottom: function () {
|
|
||||||
var parent = $('.details-logs');
|
|
||||||
if (parent.scrollTop() >= _prevBottom - 50) {
|
|
||||||
parent.scrollTop(parent[0].scrollHeight - parent.height());
|
|
||||||
}
|
|
||||||
_prevBottom = parent[0].scrollHeight - parent.height();
|
|
||||||
},
|
|
||||||
update: function () {
|
|
||||||
if (!this.props.container) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
logs: LogStore.logs(this.props.container.Name)
|
|
||||||
});
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
var logs = this.state.logs.map(function (l, i) {
|
|
||||||
return <span key={i} dangerouslySetInnerHTML={{__html: l}}></span>;
|
|
||||||
});
|
|
||||||
if (logs.length === 0) {
|
|
||||||
logs = "No logs for this container.";
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className="details-panel details-logs logs">
|
|
||||||
{logs}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
@ -27,14 +27,10 @@ var ContainerSettingsVolumes = React.createClass({
|
||||||
|
|
||||||
metrics.track('Choose Directory for Volume');
|
metrics.track('Choose Directory for Volume');
|
||||||
|
|
||||||
if(util.isWindows()) {
|
|
||||||
directory = util.windowsToLinuxPath(directory);
|
|
||||||
}
|
|
||||||
|
|
||||||
var mounts = _.clone(this.props.container.Mounts);
|
var mounts = _.clone(this.props.container.Mounts);
|
||||||
_.each(mounts, m => {
|
_.each(mounts, m => {
|
||||||
if (m.Destination === dockerVol) {
|
if (m.Destination === dockerVol) {
|
||||||
m.Source = directory;
|
m.Source = util.windowsToLinuxPath(directory);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -50,19 +46,14 @@ var ContainerSettingsVolumes = React.createClass({
|
||||||
from: 'settings'
|
from: 'settings'
|
||||||
});
|
});
|
||||||
|
|
||||||
var hostConfig = _.clone(this.props.container.HostConfig);
|
|
||||||
var binds = hostConfig.Binds;
|
|
||||||
var mounts = _.clone(this.props.container.Mounts);
|
var 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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
var index = _.findIndex(binds, bind => bind.indexOf(`:${dockerVol}`) !== -1);
|
|
||||||
if (index >= 0) {
|
containerActions.update(this.props.container.Name, {Mounts: mounts});
|
||||||
binds.splice(index, 1);
|
|
||||||
}
|
|
||||||
containerActions.update(this.props.container.Name, {HostConfig: hostConfig, Binds: binds, Mounts: mounts});
|
|
||||||
},
|
},
|
||||||
handleOpenVolumeClick: function (path) {
|
handleOpenVolumeClick: function (path) {
|
||||||
metrics.track('Opened Volume Directory', {
|
metrics.track('Opened Volume Directory', {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import React from 'react/addons';
|
||||||
import remote from 'remote';
|
import remote from 'remote';
|
||||||
import RetinaImage from 'react-retina-image';
|
import RetinaImage from 'react-retina-image';
|
||||||
import ipc from 'ipc';
|
import ipc from 'ipc';
|
||||||
var autoUpdater = remote.require('auto-updater');
|
|
||||||
import util from '../utils/Util';
|
import util from '../utils/Util';
|
||||||
import metrics from '../utils/MetricsUtil';
|
import metrics from '../utils/MetricsUtil';
|
||||||
var Menu = remote.require('menu');
|
var Menu = remote.require('menu');
|
||||||
|
|
@ -32,7 +31,6 @@ var Header = React.createClass({
|
||||||
updateAvailable: true
|
updateAvailable: true
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
autoUpdater.checkForUpdates();
|
|
||||||
},
|
},
|
||||||
componentWillUnmount: function () {
|
componentWillUnmount: function () {
|
||||||
document.removeEventListener('keyup', this.handleDocumentKeyUp, false);
|
document.removeEventListener('keyup', this.handleDocumentKeyUp, false);
|
||||||
|
|
|
||||||
|
|
@ -35,10 +35,11 @@ var Preferences = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
render: function () {
|
render: function () {
|
||||||
return (
|
var vmSettings;
|
||||||
<div className="preferences">
|
|
||||||
<div className="preferences-content">
|
if (process.platform !== 'linux') {
|
||||||
<a onClick={this.handleGoBackClick}>Go Back</a>
|
vmSettings = (
|
||||||
|
<div>
|
||||||
<div className="title">VM Settings</div>
|
<div className="title">VM Settings</div>
|
||||||
<div className="option">
|
<div className="option">
|
||||||
<div className="option-name">
|
<div className="option-name">
|
||||||
|
|
@ -48,6 +49,15 @@ var Preferences = React.createClass({
|
||||||
<input type="checkbox" checked={this.state.closeVMOnQuit} onChange={this.handleChangeCloseVMOnQuit}/>
|
<input type="checkbox" checked={this.state.closeVMOnQuit} onChange={this.handleChangeCloseVMOnQuit}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="preferences">
|
||||||
|
<div className="preferences-content">
|
||||||
|
<a onClick={this.handleGoBackClick}>Go Back</a>
|
||||||
|
{vmSettings}
|
||||||
<div className="title">App Settings</div>
|
<div className="title">App Settings</div>
|
||||||
<div className="option">
|
<div className="option">
|
||||||
<div className="option-name">
|
<div className="option-name">
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ 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 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';
|
||||||
|
|
@ -43,6 +44,13 @@ var Setup = React.createClass({
|
||||||
shell.openExternal('https://www.docker.com/docker-toolbox');
|
shell.openExternal('https://www.docker.com/docker-toolbox');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleLinuxDockerInstall: function () {
|
||||||
|
metrics.track('Opening Linux Docker installation instructions', {
|
||||||
|
from: 'setup'
|
||||||
|
});
|
||||||
|
shell.openExternal('http://docs.docker.com/linux/started/');
|
||||||
|
},
|
||||||
|
|
||||||
renderContents: function () {
|
renderContents: function () {
|
||||||
return (
|
return (
|
||||||
<div className="contents">
|
<div className="contents">
|
||||||
|
|
@ -74,6 +82,25 @@ var Setup = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
renderError: function () {
|
renderError: function () {
|
||||||
|
let deleteVmAndRetry;
|
||||||
|
|
||||||
|
if (Util.isLinux()) {
|
||||||
|
if (!this.state.started) {
|
||||||
|
deleteVmAndRetry = (
|
||||||
|
<button className="btn btn-action" onClick={this.handleLinuxDockerInstall}>Install Docker</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.state.started) {
|
||||||
|
deleteVmAndRetry = (
|
||||||
|
<button className="btn btn-action" onClick={this.handleErrorRemoveRetry}>Delete VM & Retry Setup</button>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
deleteVmAndRetry = (
|
||||||
|
<button className="btn btn-action" onClick={this.handleToolBox}>Get Toolbox</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="setup">
|
<div className="setup">
|
||||||
<Header hideLogin={true}/>
|
<Header hideLogin={true}/>
|
||||||
|
|
@ -93,7 +120,7 @@ var Setup = React.createClass({
|
||||||
<p className="error">{this.state.error.message || this.state.error}</p>
|
<p className="error">{this.state.error.message || this.state.error}</p>
|
||||||
<p className="setup-actions">
|
<p className="setup-actions">
|
||||||
<button className="btn btn-action" onClick={this.handleErrorRetry}>Retry Setup</button>
|
<button className="btn btn-action" onClick={this.handleErrorRetry}>Retry Setup</button>
|
||||||
{this.state.started ? <button className="btn btn-action" onClick={this.handleErrorRemoveRetry}>Delete VM & Retry Setup</button> : <button className="btn btn-action" onClick={this.handleToolBox}>Get Toolbox</button>}
|
{{deleteVmAndRetry}}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,17 @@ var MenuTemplate = function () {
|
||||||
{
|
{
|
||||||
label: 'Bring All to Front',
|
label: 'Bring All to Front',
|
||||||
selector: 'arrangeInFront:'
|
selector: 'arrangeInFront:'
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Kitematic',
|
||||||
|
accelerator: 'Cmd+0',
|
||||||
|
click: function () {
|
||||||
|
remote.getCurrentWindow().show();
|
||||||
|
}
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import AccountLogin from './components/AccountLogin.react';
|
||||||
import Containers from './components/Containers.react';
|
import Containers from './components/Containers.react';
|
||||||
import ContainerDetails from './components/ContainerDetails.react';
|
import ContainerDetails from './components/ContainerDetails.react';
|
||||||
import ContainerHome from './components/ContainerHome.react';
|
import ContainerHome from './components/ContainerHome.react';
|
||||||
import ContainerLogs from './components/ContainerLogs.react';
|
|
||||||
import ContainerSettings from './components/ContainerSettings.react';
|
import ContainerSettings from './components/ContainerSettings.react';
|
||||||
import ContainerSettingsGeneral from './components/ContainerSettingsGeneral.react';
|
import ContainerSettingsGeneral from './components/ContainerSettingsGeneral.react';
|
||||||
import ContainerSettingsPorts from './components/ContainerSettingsPorts.react';
|
import ContainerSettingsPorts from './components/ContainerSettingsPorts.react';
|
||||||
|
|
@ -39,7 +38,6 @@ var routes = (
|
||||||
<Route name="containers" path="containers" handler={Containers}>
|
<Route name="containers" path="containers" handler={Containers}>
|
||||||
<Route name="container" path="details/:name" handler={ContainerDetails}>
|
<Route name="container" path="details/:name" handler={ContainerDetails}>
|
||||||
<DefaultRoute name="containerHome" handler={ContainerHome} />
|
<DefaultRoute name="containerHome" handler={ContainerHome} />
|
||||||
<Route name="containerLogs" path="logs" handler={ContainerLogs}/>
|
|
||||||
<Route name="containerSettings" path="settings" handler={ContainerSettings}>
|
<Route name="containerSettings" path="settings" handler={ContainerSettings}>
|
||||||
<Route name="containerSettingsGeneral" path="general" handler={ContainerSettingsGeneral}/>
|
<Route name="containerSettingsGeneral" path="general" handler={ContainerSettingsGeneral}/>
|
||||||
<Route name="containerSettingsPorts" path="ports" handler={ContainerSettingsPorts}/>
|
<Route name="containerSettingsPorts" path="ports" handler={ContainerSettingsPorts}/>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ import alt from '../alt';
|
||||||
import containerServerActions from '../actions/ContainerServerActions';
|
import containerServerActions from '../actions/ContainerServerActions';
|
||||||
import containerActions from '../actions/ContainerActions';
|
import containerActions from '../actions/ContainerActions';
|
||||||
|
|
||||||
|
let MAX_LOG_SIZE = 3000;
|
||||||
|
|
||||||
class ContainerStore {
|
class ContainerStore {
|
||||||
constructor () {
|
constructor () {
|
||||||
this.bindActions(containerActions);
|
this.bindActions(containerActions);
|
||||||
|
|
@ -98,16 +100,20 @@ class ContainerStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
updated ({container}) {
|
updated ({container}) {
|
||||||
|
if (!container || !container.Name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let containers = this.containers;
|
let containers = this.containers;
|
||||||
if (containers[container.Name] && containers[container.Name].State.Updating) {
|
if (containers[container.Name] && containers[container.Name].State.Updating) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Trigger log update
|
|
||||||
// TODO: fix this loading multiple times
|
if (containers[container.Name] && containers[container.Name].Logs) {
|
||||||
// LogStore.fetch(container.Name);
|
container.Logs = containers[container.Name].Logs;
|
||||||
|
}
|
||||||
|
|
||||||
containers[container.Name] = container;
|
containers[container.Name] = container;
|
||||||
|
|
||||||
this.setState({containers});
|
this.setState({containers});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,7 +147,7 @@ class ContainerStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
waiting({name, waiting}) {
|
waiting ({name, waiting}) {
|
||||||
let containers = this.containers;
|
let containers = this.containers;
|
||||||
if (containers[name]) {
|
if (containers[name]) {
|
||||||
containers[name].State.Waiting = waiting;
|
containers[name].State.Waiting = waiting;
|
||||||
|
|
@ -158,6 +164,33 @@ class ContainerStore {
|
||||||
this.setState({pending: null});
|
this.setState({pending: null});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log ({name, entry}) {
|
||||||
|
let container = this.containers[name];
|
||||||
|
if (!container) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!container.Logs) {
|
||||||
|
container.Logs = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
container.Logs.push.apply(container.Logs, entry.split('\n').filter(e => e.length));
|
||||||
|
container.Logs = container.Logs.slice(container.Logs.length - MAX_LOG_SIZE, MAX_LOG_SIZE);
|
||||||
|
this.emitChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
logs ({name, logs}) {
|
||||||
|
let container = this.containers[name];
|
||||||
|
|
||||||
|
if (!container) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.Logs = logs.split('\n');
|
||||||
|
container.Logs = container.Logs.slice(container.Logs.length - MAX_LOG_SIZE, MAX_LOG_SIZE);
|
||||||
|
this.emitChange();
|
||||||
|
}
|
||||||
|
|
||||||
static generateName (repo) {
|
static generateName (repo) {
|
||||||
const base = _.last(repo.split('/'));
|
const base = _.last(repo.split('/'));
|
||||||
const names = _.keys(this.getState().containers);
|
const names = _.keys(this.getState().containers);
|
||||||
|
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
import {EventEmitter} from 'events';
|
|
||||||
import assign from 'object-assign';
|
|
||||||
import Convert from 'ansi-to-html';
|
|
||||||
import docker from '../utils/DockerUtil';
|
|
||||||
import stream from 'stream';
|
|
||||||
|
|
||||||
var _convert = new Convert();
|
|
||||||
var _logs = {};
|
|
||||||
var _streams = {};
|
|
||||||
|
|
||||||
var MAX_LOG_SIZE = 3000;
|
|
||||||
|
|
||||||
module.exports = assign(Object.create(EventEmitter.prototype), {
|
|
||||||
SERVER_LOGS_EVENT: 'server_logs_event',
|
|
||||||
_escape: function (html) {
|
|
||||||
var text = document.createTextNode(html);
|
|
||||||
var div = document.createElement('div');
|
|
||||||
div.appendChild(text);
|
|
||||||
return div.innerHTML;
|
|
||||||
},
|
|
||||||
fetch: function (name) {
|
|
||||||
if (!name || !docker.client) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
docker.client.getContainer(name).logs({
|
|
||||||
stdout: true,
|
|
||||||
stderr: true,
|
|
||||||
timestamps: false,
|
|
||||||
tail: MAX_LOG_SIZE,
|
|
||||||
follow: false
|
|
||||||
}, (err, logStream) => {
|
|
||||||
if (err) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var logs = [];
|
|
||||||
var outstream = new stream.PassThrough();
|
|
||||||
docker.client.modem.demuxStream(logStream, outstream, outstream);
|
|
||||||
outstream.on('data', (chunk) => {
|
|
||||||
logs.push(_convert.toHtml(this._escape(chunk)));
|
|
||||||
});
|
|
||||||
logStream.on('end', () => {
|
|
||||||
_logs[name] = logs;
|
|
||||||
this.emit(this.SERVER_LOGS_EVENT);
|
|
||||||
this.attach(name);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
attach: function (name) {
|
|
||||||
if (!name || !docker.client || _streams[name]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
docker.client.getContainer(name).attach({
|
|
||||||
stdout: true,
|
|
||||||
stderr: true,
|
|
||||||
logs: false,
|
|
||||||
stream: true
|
|
||||||
}, (err, logStream) => {
|
|
||||||
if (err) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_streams[name] = logStream;
|
|
||||||
var outstream = new stream.PassThrough();
|
|
||||||
docker.client.modem.demuxStream(logStream, outstream, outstream);
|
|
||||||
outstream.on('data', (chunk) => {
|
|
||||||
_logs[name].push(_convert.toHtml(this._escape(chunk)));
|
|
||||||
if (_logs[name].length > MAX_LOG_SIZE) {
|
|
||||||
_logs[name] = _logs[name].slice(_logs[name].length - MAX_LOG_SIZE, MAX_LOG_SIZE);
|
|
||||||
}
|
|
||||||
this.emit(this.SERVER_LOGS_EVENT);
|
|
||||||
});
|
|
||||||
logStream.on('end', () => {
|
|
||||||
this.detach(name);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
detach: function (name) {
|
|
||||||
if (_streams[name]) {
|
|
||||||
_streams[name].destroy();
|
|
||||||
delete _streams[name];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
logs: function (name) {
|
|
||||||
return _logs[name] || [];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
@ -3,6 +3,7 @@ import path from 'path';
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import util from './Util';
|
import util from './Util';
|
||||||
|
import child_process from 'child_process';
|
||||||
|
|
||||||
var DockerMachine = {
|
var DockerMachine = {
|
||||||
command: function () {
|
command: function () {
|
||||||
|
|
@ -22,7 +23,7 @@ var DockerMachine = {
|
||||||
return fs.existsSync(this.command());
|
return fs.existsSync(this.command());
|
||||||
},
|
},
|
||||||
version: function () {
|
version: function () {
|
||||||
return util.exec([this.command(), '-v']).then(stdout => {
|
return util.execFile([this.command(), '-v']).then(stdout => {
|
||||||
try {
|
try {
|
||||||
var matchlist = stdout.match(/(\d+\.\d+\.\d+).*/);
|
var matchlist = stdout.match(/(\d+\.\d+\.\d+).*/);
|
||||||
if (!matchlist || matchlist.length < 2) {
|
if (!matchlist || matchlist.length < 2) {
|
||||||
|
|
@ -57,40 +58,46 @@ var DockerMachine = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
create: function (machineName = this.name()) {
|
create: function (machineName = this.name()) {
|
||||||
return util.exec([this.command(), '-D', 'create', '-d', 'virtualbox', '--virtualbox-memory', '2048', machineName]);
|
return util.execFile([this.command(), '-D', 'create', '-d', 'virtualbox', '--virtualbox-memory', '2048', machineName]);
|
||||||
},
|
},
|
||||||
start: function (machineName = this.name()) {
|
start: function (machineName = this.name()) {
|
||||||
return util.exec([this.command(), '-D', 'start', machineName]);
|
return util.execFile([this.command(), '-D', 'start', machineName]);
|
||||||
},
|
},
|
||||||
stop: function (machineName = this.name()) {
|
stop: function (machineName = this.name()) {
|
||||||
return util.exec([this.command(), 'stop', machineName]);
|
return util.execFile([this.command(), 'stop', machineName]);
|
||||||
},
|
},
|
||||||
upgrade: function (machineName = this.name()) {
|
upgrade: function (machineName = this.name()) {
|
||||||
return util.exec([this.command(), 'upgrade', machineName]);
|
return util.execFile([this.command(), 'upgrade', machineName]);
|
||||||
},
|
},
|
||||||
rm: function (machineName = this.name()) {
|
rm: function (machineName = this.name()) {
|
||||||
return util.exec([this.command(), 'rm', '-f', machineName]);
|
return util.execFile([this.command(), 'rm', '-f', machineName]);
|
||||||
},
|
},
|
||||||
ip: function (machineName = this.name()) {
|
ip: function (machineName = this.name()) {
|
||||||
return util.exec([this.command(), 'ip', machineName]).then(stdout => {
|
return util.execFile([this.command(), 'ip', machineName]).then(stdout => {
|
||||||
return Promise.resolve(stdout.trim().replace('\n', ''));
|
return Promise.resolve(stdout.trim().replace('\n', ''));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
url: function (machineName = this.name()) {
|
url: function (machineName = this.name()) {
|
||||||
return util.exec([this.command(), 'url', machineName]).then(stdout => {
|
return util.execFile([this.command(), 'url', machineName]).then(stdout => {
|
||||||
return Promise.resolve(stdout.trim().replace('\n', ''));
|
return Promise.resolve(stdout.trim().replace('\n', ''));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
regenerateCerts: function (machineName = this.name()) {
|
regenerateCerts: function (machineName = this.name()) {
|
||||||
return util.exec([this.command(), 'tls-regenerate-certs', '-f', machineName]);
|
return util.execFile([this.command(), 'tls-regenerate-certs', '-f', machineName]);
|
||||||
},
|
},
|
||||||
status: function (machineName = this.name()) {
|
status: function (machineName = this.name()) {
|
||||||
return util.exec([this.command(), 'status', machineName]).then(stdout => {
|
return new Promise((resolve, reject) => {
|
||||||
return Promise.resolve(stdout.trim().replace('\n', ''));
|
child_process.execFile(this.command(), ['status', machineName], (error, stdout, stderr) => {
|
||||||
|
if (error) {
|
||||||
|
reject(new Error('Encountered an error: ' + error));
|
||||||
|
} else {
|
||||||
|
resolve(stdout.trim() + stderr.trim());
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
disk: function (machineName = this.name()) {
|
disk: function (machineName = this.name()) {
|
||||||
return util.exec([this.command(), 'ssh', machineName, 'df']).then(stdout => {
|
return util.execFile([this.command(), 'ssh', machineName, 'df']).then(stdout => {
|
||||||
try {
|
try {
|
||||||
var lines = stdout.split('\n');
|
var lines = stdout.split('\n');
|
||||||
var dataline = _.find(lines, function (line) {
|
var dataline = _.find(lines, function (line) {
|
||||||
|
|
@ -114,7 +121,7 @@ var DockerMachine = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
memory: function (machineName = this.name()) {
|
memory: function (machineName = this.name()) {
|
||||||
return util.exec([this.command(), 'ssh', machineName, 'free -m']).then(stdout => {
|
return util.execFile([this.command(), 'ssh', machineName, 'free -m']).then(stdout => {
|
||||||
try {
|
try {
|
||||||
var lines = stdout.split('\n');
|
var lines = stdout.split('\n');
|
||||||
var dataline = _.find(lines, function (line) {
|
var dataline = _.find(lines, function (line) {
|
||||||
|
|
@ -151,10 +158,15 @@ var DockerMachine = {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
} else if (util.isLinux()) {
|
||||||
|
cmd = cmd || process.env.SHELL;
|
||||||
|
var terminal = util.linuxTerminal();
|
||||||
|
if (terminal)
|
||||||
|
util.execFile(terminal.concat([cmd])).then(() => {});
|
||||||
} else {
|
} else {
|
||||||
cmd = cmd || process.env.SHELL;
|
cmd = cmd || process.env.SHELL;
|
||||||
this.url(machineName).then(machineUrl => {
|
this.url(machineName).then(machineUrl => {
|
||||||
util.exec([path.join(process.env.RESOURCES_PATH, 'terminal'), `DOCKER_HOST=${machineUrl} DOCKER_CERT_PATH=${path.join(util.home(), '.docker/machine/machines/' + machineName)} DOCKER_TLS_VERIFY=1 ${cmd}`]).then(() => {});
|
util.execFile([path.join(process.env.RESOURCES_PATH, 'terminal'), `DOCKER_HOST=${machineUrl} DOCKER_CERT_PATH=${path.join(util.home(), '.docker/machine/machines/' + machineName)} DOCKER_TLS_VERIFY=1 ${cmd}`]).then(() => {});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -3,37 +3,46 @@ import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import dockerode from 'dockerode';
|
import dockerode from 'dockerode';
|
||||||
import _ from 'underscore';
|
import _ from 'underscore';
|
||||||
|
import child_process from 'child_process';
|
||||||
import util from './Util';
|
import util from './Util';
|
||||||
import hubUtil from './HubUtil';
|
import hubUtil from './HubUtil';
|
||||||
import metrics from '../utils/MetricsUtil';
|
import metrics from '../utils/MetricsUtil';
|
||||||
import containerServerActions from '../actions/ContainerServerActions';
|
import containerServerActions from '../actions/ContainerServerActions';
|
||||||
import Promise from 'bluebird';
|
|
||||||
import rimraf from 'rimraf';
|
import rimraf from 'rimraf';
|
||||||
|
import stream from 'stream';
|
||||||
|
import JSONStream from 'JSONStream';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
host: null,
|
host: null,
|
||||||
client: null,
|
client: null,
|
||||||
placeholders: {},
|
placeholders: {},
|
||||||
|
streams: {},
|
||||||
|
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');
|
||||||
}
|
}
|
||||||
|
|
||||||
let certDir = path.join(util.home(), '.docker/machine/machines/', name);
|
if (util.isLinux()) {
|
||||||
if (!fs.existsSync(certDir)) {
|
this.host = 'localhost';
|
||||||
throw new Error('Certificate directory does not exist');
|
this.client = new dockerode({socketPath: '/var/run/docker.sock'});
|
||||||
}
|
} else {
|
||||||
|
let certDir = path.join(util.home(), '.docker/machine/machines/', name);
|
||||||
|
if (!fs.existsSync(certDir)) {
|
||||||
|
throw new Error('Certificate directory does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
this.host = ip;
|
this.host = ip;
|
||||||
this.client = new dockerode({
|
this.client = new dockerode({
|
||||||
protocol: 'https',
|
protocol: 'https',
|
||||||
host: ip,
|
host: ip,
|
||||||
port: 2376,
|
port: 2376,
|
||||||
ca: fs.readFileSync(path.join(certDir, 'ca.pem')),
|
ca: fs.readFileSync(path.join(certDir, 'ca.pem')),
|
||||||
cert: fs.readFileSync(path.join(certDir, 'cert.pem')),
|
cert: fs.readFileSync(path.join(certDir, 'cert.pem')),
|
||||||
key: fs.readFileSync(path.join(certDir, 'key.pem'))
|
key: fs.readFileSync(path.join(certDir, 'key.pem'))
|
||||||
});
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
init () {
|
init () {
|
||||||
|
|
@ -66,6 +75,14 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isDockerRunning () {
|
||||||
|
try {
|
||||||
|
child_process.execSync('ps ax | grep "docker daemon" | grep -v grep');
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('Cannot connect to the Docker daemon. The daemon is not running.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
startContainer (name, containerData) {
|
startContainer (name, containerData) {
|
||||||
let startopts = {
|
let startopts = {
|
||||||
Binds: containerData.Binds || []
|
Binds: containerData.Binds || []
|
||||||
|
|
@ -109,7 +126,7 @@ export default {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
containerData.Cmd = image.Config.Cmd || 'bash';
|
containerData.Cmd = image.Config.Cmd || image.Config.Entrypoint || 'bash';
|
||||||
let existing = this.client.getContainer(name);
|
let existing = this.client.getContainer(name);
|
||||||
existing.kill(() => {
|
existing.kill(() => {
|
||||||
existing.remove(() => {
|
existing.remove(() => {
|
||||||
|
|
@ -231,6 +248,11 @@ export default {
|
||||||
existingData.Tty = existingData.Config.Tty;
|
existingData.Tty = existingData.Config.Tty;
|
||||||
existingData.OpenStdin = existingData.Config.OpenStdin;
|
existingData.OpenStdin = existingData.Config.OpenStdin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data.Mounts = data.Mounts || existingData.Mounts;
|
||||||
|
data.Binds = data.Mounts.map(m => m.Source + ':' + m.Destination);
|
||||||
|
|
||||||
|
// Preserve Ports
|
||||||
let networking = _.extend(existingData.NetworkSettings, data.NetworkSettings);
|
let networking = _.extend(existingData.NetworkSettings, data.NetworkSettings);
|
||||||
if (networking && networking.Ports) {
|
if (networking && networking.Ports) {
|
||||||
let exposed = _.reduce(networking.Ports, (res, value, key) => {
|
let exposed = _.reduce(networking.Ports, (res, value, key) => {
|
||||||
|
|
@ -256,8 +278,8 @@ export default {
|
||||||
containerServerActions.error({name, error});
|
containerServerActions.error({name, error});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var oldPath = path.join(util.home(), 'Kitematic', name);
|
var oldPath = util.windowsToLinuxPath(path.join(util.home(), util.documents(), 'Kitematic', name));
|
||||||
var newPath = path.join(util.home(), 'Kitematic', newName);
|
var newPath = util.windowsToLinuxPath(path.join(util.home(), util.documents(), 'Kitematic', newName));
|
||||||
|
|
||||||
this.client.getContainer(newName).inspect((error, container) => {
|
this.client.getContainer(newName).inspect((error, container) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|
@ -268,13 +290,12 @@ export default {
|
||||||
if (fs.existsSync(oldPath)) {
|
if (fs.existsSync(oldPath)) {
|
||||||
fs.renameSync(oldPath, newPath);
|
fs.renameSync(oldPath, newPath);
|
||||||
}
|
}
|
||||||
var binds = _.pairs(container.Volumes).map(function (pair) {
|
|
||||||
return pair[1] + ':' + pair[0];
|
container.Mounts.forEach(m => {
|
||||||
|
m.Source = m.Source.replace(oldPath, newPath);
|
||||||
});
|
});
|
||||||
var newBinds = binds.map(b => {
|
|
||||||
return b.replace(path.join(util.home(), 'Kitematic', name), path.join(util.home(), 'Kitematic', newName));
|
this.updateContainer(newName, {Mounts: container.Mounts});
|
||||||
});
|
|
||||||
this.updateContainer(newName, {Binds: newBinds});
|
|
||||||
rimraf(oldPath, () => {});
|
rimraf(oldPath, () => {});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -343,6 +364,85 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
active (name) {
|
||||||
|
this.detach();
|
||||||
|
this.activeContainerName = name;
|
||||||
|
|
||||||
|
if (name) {
|
||||||
|
this.logs();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
logs () {
|
||||||
|
if (!this.activeContainerName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.client.getContainer(this.activeContainerName).logs({
|
||||||
|
stdout: true,
|
||||||
|
stderr: true,
|
||||||
|
tail: 1000,
|
||||||
|
follow: false,
|
||||||
|
timestamps: 1
|
||||||
|
}, (err, logStream) => {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let logs = '';
|
||||||
|
logStream.setEncoding('utf8');
|
||||||
|
logStream.on('data', chunk => logs += chunk);
|
||||||
|
logStream.on('end', () => {
|
||||||
|
containerServerActions.logs({name: this.activeContainerName, logs});
|
||||||
|
this.attach();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
attach () {
|
||||||
|
if (!this.activeContainerName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.client.getContainer(this.activeContainerName).logs({
|
||||||
|
stdout: true,
|
||||||
|
stderr: true,
|
||||||
|
tail: 0,
|
||||||
|
follow: true,
|
||||||
|
timestamps: 1
|
||||||
|
}, (err, logStream) => {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.stream) {
|
||||||
|
this.detach();
|
||||||
|
}
|
||||||
|
this.stream = logStream;
|
||||||
|
|
||||||
|
let timeout = null;
|
||||||
|
let batch = '';
|
||||||
|
logStream.setEncoding('utf8');
|
||||||
|
logStream.on('data', (chunk) => {
|
||||||
|
batch += chunk;
|
||||||
|
if (!timeout) {
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
containerServerActions.log({name: this.activeContainerName, entry: batch});
|
||||||
|
timeout = null;
|
||||||
|
batch = '';
|
||||||
|
}, 16);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
detach () {
|
||||||
|
if (this.stream) {
|
||||||
|
this.stream.destroy();
|
||||||
|
this.stream = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
listen () {
|
listen () {
|
||||||
this.client.getEvents((error, stream) => {
|
this.client.getEvents((error, stream) => {
|
||||||
if (error || !stream) {
|
if (error || !stream) {
|
||||||
|
|
@ -351,19 +451,26 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.setEncoding('utf8');
|
stream.setEncoding('utf8');
|
||||||
stream.on('data', json => {
|
stream.pipe(JSONStream.parse()).on('data', data => {
|
||||||
let data = JSON.parse(json);
|
if (data.status === 'pull' || data.status === 'untag' || data.status === 'delete' || data.status === 'attach') {
|
||||||
|
|
||||||
if (data.status === 'pull' || data.status === 'untag' || data.status === 'delete' || data.status === 'attach') {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.status === 'destroy') {
|
if (data.status === 'destroy') {
|
||||||
containerServerActions.destroyed({id: data.id});
|
containerServerActions.destroyed({id: data.id});
|
||||||
|
this.detach(data.id);
|
||||||
} else if (data.status === 'kill') {
|
} else if (data.status === 'kill') {
|
||||||
containerServerActions.kill({id: data.id});
|
containerServerActions.kill({id: data.id});
|
||||||
|
this.detach(data.id);
|
||||||
} else if (data.status === 'stop') {
|
} else if (data.status === 'stop') {
|
||||||
containerServerActions.stopped({id: data.id});
|
containerServerActions.stopped({id: data.id});
|
||||||
|
this.detach(data.id);
|
||||||
|
} else if (data.status === 'create') {
|
||||||
|
this.logs();
|
||||||
|
this.fetchContainer(data.id);
|
||||||
|
} else if (data.status === 'start') {
|
||||||
|
this.attach();
|
||||||
|
this.fetchContainer(data.id);
|
||||||
} else if (data.id) {
|
} else if (data.id) {
|
||||||
this.fetchContainer(data.id);
|
this.fetchContainer(data.id);
|
||||||
}
|
}
|
||||||
|
|
@ -405,9 +512,7 @@ export default {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
// data is associated with one layer only (can be identified with id)
|
// data is associated with one layer only (can be identified with id)
|
||||||
stream.on('data', str => {
|
stream.pipe(JSONStream.parse()).on('data', data => {
|
||||||
var data = JSON.parse(str);
|
|
||||||
|
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
error = data.error;
|
error = data.error;
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ import _ from 'underscore';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
import util from './Util';
|
|
||||||
import bugsnag from 'bugsnag-js';
|
import bugsnag from 'bugsnag-js';
|
||||||
|
import util from './Util';
|
||||||
import virtualBox from './VirtualBoxUtil';
|
import virtualBox from './VirtualBoxUtil';
|
||||||
import setupServerActions from '../actions/SetupServerActions';
|
import setupServerActions from '../actions/SetupServerActions';
|
||||||
import metrics from './MetricsUtil';
|
import metrics from './MetricsUtil';
|
||||||
|
|
@ -51,7 +51,35 @@ export default {
|
||||||
return _retryPromise.promise;
|
return _retryPromise.promise;
|
||||||
},
|
},
|
||||||
|
|
||||||
async setup () {
|
setup() {
|
||||||
|
return util.isLinux() ? this.nativeSetup() : this.nonNativeSetup();
|
||||||
|
},
|
||||||
|
|
||||||
|
async nativeSetup () {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
docker.setup('localhost', machine.name());
|
||||||
|
docker.isDockerRunning();
|
||||||
|
|
||||||
|
break;
|
||||||
|
} catch (error) {
|
||||||
|
router.get().transitionTo('setup');
|
||||||
|
metrics.track('Native Setup Failed');
|
||||||
|
setupServerActions.error({error});
|
||||||
|
|
||||||
|
let message = error.message.split('\n');
|
||||||
|
let lastLine = message.length > 1 ? message[message.length - 2] : 'Docker Machine encountered an error.';
|
||||||
|
bugsnag.notify('Native Setup Failed', lastLine, {
|
||||||
|
'Docker Machine Logs': error.message
|
||||||
|
}, 'info');
|
||||||
|
|
||||||
|
this.clearTimers();
|
||||||
|
await this.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async nonNativeSetup () {
|
||||||
let virtualBoxVersion = null;
|
let virtualBoxVersion = null;
|
||||||
let machineVersion = null;
|
let machineVersion = null;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
@ -93,6 +121,7 @@ export default {
|
||||||
await machine.create();
|
await machine.create();
|
||||||
} else {
|
} else {
|
||||||
let state = await machine.status();
|
let state = await machine.status();
|
||||||
|
console.log(state);
|
||||||
if (state !== 'Running') {
|
if (state !== 'Running') {
|
||||||
if (state === 'Saved') {
|
if (state === 'Saved') {
|
||||||
router.get().transitionTo('setup');
|
router.get().transitionTo('setup');
|
||||||
|
|
@ -109,9 +138,9 @@ export default {
|
||||||
let tries = 80, ip = null;
|
let tries = 80, ip = null;
|
||||||
while (!ip && tries > 0) {
|
while (!ip && tries > 0) {
|
||||||
try {
|
try {
|
||||||
|
tries -= 1;
|
||||||
console.log('Trying to fetch machine IP, tries left: ' + tries);
|
console.log('Trying to fetch machine IP, tries left: ' + tries);
|
||||||
ip = await machine.ip();
|
ip = await machine.ip();
|
||||||
tries -= 1;
|
|
||||||
await Promise.delay(1000);
|
await Promise.delay(1000);
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
}
|
}
|
||||||
|
|
@ -125,11 +154,12 @@ export default {
|
||||||
break;
|
break;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
router.get().transitionTo('setup');
|
router.get().transitionTo('setup');
|
||||||
metrics.track('Setup Failed', {
|
|
||||||
|
let novtx = error.message.indexOf('This computer doesn\'t have VT-X/AMD-v enabled') !== -1;
|
||||||
|
metrics.track(novtx ? 'Setup Halted' : 'Setup Failed', {
|
||||||
virtualBoxVersion,
|
virtualBoxVersion,
|
||||||
machineVersion
|
machineVersion
|
||||||
});
|
});
|
||||||
setupServerActions.error({error});
|
|
||||||
|
|
||||||
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.';
|
||||||
|
|
@ -142,6 +172,8 @@ export default {
|
||||||
groupingHash: machineVersion
|
groupingHash: machineVersion
|
||||||
}, 'info');
|
}, 'info');
|
||||||
|
|
||||||
|
setupServerActions.error({error: new Error(message)});
|
||||||
|
|
||||||
this.clearTimers();
|
this.clearTimers();
|
||||||
await this.pause();
|
await this.pause();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,29 @@
|
||||||
import exec from 'exec';
|
|
||||||
import child_process from 'child_process';
|
import child_process from 'child_process';
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import remote from 'remote';
|
import remote from 'remote';
|
||||||
|
var dialog = remote.require('dialog');
|
||||||
var app = remote.require('app');
|
var app = remote.require('app');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
exec: function (args, options) {
|
execFile: 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) => {
|
return new Promise((resolve, reject) => {
|
||||||
fn(args, options, (stderr, stdout, code) => {
|
child_process.execFile(args[0], args.slice(1), options, (error, stdout) => {
|
||||||
if (code) {
|
if (error) {
|
||||||
var cmd = Array.isArray(args) ? args.join(' ') : args;
|
reject(new Error('Encountered an error: ' + error));
|
||||||
reject(new Error(cmd + ' returned non zero exit code. Stderr: ' + stderr));
|
} else {
|
||||||
|
resolve(stdout);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
exec: function (args, options) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
child_process.exec(args, options, (error, stdout) => {
|
||||||
|
if (error) {
|
||||||
|
reject(new Error('Encountered an error: ' + error));
|
||||||
} else {
|
} else {
|
||||||
resolve(stdout);
|
resolve(stdout);
|
||||||
}
|
}
|
||||||
|
|
@ -34,6 +33,9 @@ module.exports = {
|
||||||
isWindows: function () {
|
isWindows: function () {
|
||||||
return process.platform === 'win32';
|
return process.platform === 'win32';
|
||||||
},
|
},
|
||||||
|
isLinux: function () {
|
||||||
|
return process.platform === 'linux';
|
||||||
|
},
|
||||||
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');
|
||||||
},
|
},
|
||||||
|
|
@ -156,5 +158,17 @@ module.exports = {
|
||||||
linuxToWindowsPath: function (linuxAbsPath) {
|
linuxToWindowsPath: function (linuxAbsPath) {
|
||||||
return linuxAbsPath.replace('/c', 'C:').split('/').join('\\');
|
return linuxAbsPath.replace('/c', 'C:').split('/').join('\\');
|
||||||
},
|
},
|
||||||
|
linuxTerminal: function () {
|
||||||
|
if (fs.existsSync('/usr/bin/x-terminal-emulator')) {
|
||||||
|
return ['/usr/bin/x-terminal-emulator', '-e'];
|
||||||
|
} else {
|
||||||
|
dialog.showMessageBox({
|
||||||
|
type: 'warning',
|
||||||
|
buttons: ['OK'],
|
||||||
|
message: 'The terminal emulator symbolic link doesn\'t exists. Please read the Wiki at https://github.com/kitematic/kitematic/wiki/Common-Issues-and-Fixes#early-linux-support-from-zedtux.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
webPorts: ['80', '8000', '8080', '8888', '3000', '5000', '2368', '9200', '8983']
|
webPorts: ['80', '8000', '8080', '8888', '3000', '5000', '2368', '9200', '8983']
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,16 @@ var VirtualBox = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
installed: function () {
|
installed: function () {
|
||||||
|
if (util.isWindows() && !process.env.VBOX_INSTALL_PATH && !process.env.VBOX_MSI_INSTALL_PATH) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return fs.existsSync(this.command());
|
return fs.existsSync(this.command());
|
||||||
},
|
},
|
||||||
active: function () {
|
active: function () {
|
||||||
return fs.existsSync('/dev/vboxnetctl');
|
return fs.existsSync('/dev/vboxnetctl');
|
||||||
},
|
},
|
||||||
version: function () {
|
version: function () {
|
||||||
return util.exec([this.command(), '-v']).then(stdout => {
|
return util.execFile([this.command(), '-v']).then(stdout => {
|
||||||
let matchlist = stdout.match(/(\d+\.\d+\.\d+).*/);
|
let matchlist = stdout.match(/(\d+\.\d+\.\d+).*/);
|
||||||
if (!matchlist || matchlist.length < 2) {
|
if (!matchlist || matchlist.length < 2) {
|
||||||
Promise.reject('VBoxManage -v output format not recognized.');
|
Promise.reject('VBoxManage -v output format not recognized.');
|
||||||
|
|
@ -32,29 +35,11 @@ var VirtualBox = {
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
poweroffall: function () {
|
|
||||||
return util.exec(this.command() + ' list runningvms | sed -E \'s/.*\\{(.*)\\}/\\1/\' | xargs -L1 -I {} ' + this.command() + ' controlvm {} poweroff');
|
|
||||||
},
|
|
||||||
mountSharedDir: function (vmName, pathName, hostPath) {
|
mountSharedDir: function (vmName, pathName, hostPath) {
|
||||||
return util.exec([this.command(), 'sharedfolder', 'add', vmName, '--name', pathName, '--hostpath', hostPath, '--automount']);
|
return util.execFile([this.command(), 'sharedfolder', 'add', vmName, '--name', pathName, '--hostpath', hostPath, '--automount']);
|
||||||
},
|
|
||||||
killall: function () {
|
|
||||||
if (util.isWindows()) {
|
|
||||||
return this.poweroffall().then(() => {
|
|
||||||
return util.exec(['powershell.exe', '\"get-process VBox* | stop-process\"']);
|
|
||||||
}).catch(() => {});
|
|
||||||
} else {
|
|
||||||
return this.poweroffall().then(() => {
|
|
||||||
return util.exec(['pkill', 'VirtualBox']);
|
|
||||||
}).then(() => {
|
|
||||||
return util.exec(['pkill', 'VBox']);
|
|
||||||
}).catch(() => {
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
vmExists: function (name) {
|
vmExists: function (name) {
|
||||||
return util.exec([this.command(), 'showvminfo', name]).then(() => {
|
return util.execFile([this.command(), 'showvminfo', name]).then(() => {
|
||||||
return true;
|
return true;
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,15 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
.left {
|
.left {
|
||||||
width: 100%;
|
display: flex;
|
||||||
|
flex: 0.9 1 0;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
}
|
}
|
||||||
.right {
|
.right {
|
||||||
|
display: flex;
|
||||||
|
flex: 0.1 0 300px;
|
||||||
width: 40%;
|
width: 40%;
|
||||||
min-width: 200px;
|
|
||||||
max-width: 600px;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
.full {
|
.full {
|
||||||
|
|
@ -103,7 +104,6 @@
|
||||||
color: @gray-lightest;
|
color: @gray-lightest;
|
||||||
font-family: @font-code;
|
font-family: @font-code;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
white-space: pre-wrap;
|
|
||||||
-webkit-user-select: text;
|
-webkit-user-select: text;
|
||||||
padding: 1.2rem 1.2rem 5rem 1.2rem;
|
padding: 1.2rem 1.2rem 5rem 1.2rem;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@
|
||||||
@color-box-button: lighten(@gray-lightest, 5%);
|
@color-box-button: lighten(@gray-lightest, 5%);
|
||||||
@color-background: lighten(@gray-lightest, 4.5%);
|
@color-background: lighten(@gray-lightest, 4.5%);
|
||||||
|
|
||||||
@font-regular: "Helvetica Neue", Segoe UI, Arial, "Lucida Grande", sans-serif;
|
@font-regular: "Helvetica Neue", Segoe UI, "Ubuntu", Arial, "Lucida Grande", sans-serif;
|
||||||
@font-code: Menlo, Consolas;
|
@font-code: Menlo, Consolas, "DejaVu Sans Mono";
|
||||||
|
|
||||||
@border-radius: 0.2rem;
|
@border-radius: 0.2rem;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue