Merge branch 'master' into hub-button
|
@ -3,6 +3,7 @@
|
||||||
build
|
build
|
||||||
dist
|
dist
|
||||||
node_modules
|
node_modules
|
||||||
|
coverage
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
|
|
||||||
# Signing Identity
|
# Signing Identity
|
||||||
|
|
|
@ -27,5 +27,5 @@
|
||||||
"jest": true,
|
"jest": true,
|
||||||
"pit": true
|
"pit": true
|
||||||
},
|
},
|
||||||
"predef": [ "-Promise" ]
|
"predef": [ "Promise" ]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- "0.10"
|
||||||
|
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
cache:
|
||||||
|
- resources
|
||||||
|
- node_modules
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
which ./node_modules/coveralls/bin/coveralls.js && cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
|
|
@ -6,12 +6,30 @@ Before you fil an issue or a pull request, quickly read of the following tips on
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Development](#development)
|
||||||
- [GitHub Issues](#github-issues)
|
- [GitHub Issues](#github-issues)
|
||||||
- [Pull Requests](#submitting-pull-requests)
|
- [Pull Requests](#pull-requests)
|
||||||
- [Code Guidelines](#code-guidelines)
|
- [Code Guidelines](#code-guidelines)
|
||||||
- [Testing](#testing)
|
- [Testing](#testing)
|
||||||
- [License](#license)
|
- [License](#license)
|
||||||
|
|
||||||
|
### Development
|
||||||
|
|
||||||
|
- `npm install`
|
||||||
|
|
||||||
|
To run the app in development:
|
||||||
|
|
||||||
|
- `npm start`
|
||||||
|
|
||||||
|
### Building & Release
|
||||||
|
|
||||||
|
- `npm run release`
|
||||||
|
|
||||||
|
### Unit Tests
|
||||||
|
|
||||||
|
- `npm test`
|
||||||
|
|
||||||
|
|
||||||
## GitHub Issues
|
## GitHub Issues
|
||||||
|
|
||||||
Please try and label any issue as:
|
Please try and label any issue as:
|
||||||
|
@ -35,6 +53,10 @@ We're thrilled to receive pull requests of any kind. Anything from bug fix, test
|
||||||
|
|
||||||
That said, please let us know what you're planning to do! For large changes always create a proposal. Maintainers will love to give you advice on building it and it keeps the app's design coherent.
|
That said, please let us know what you're planning to do! For large changes always create a proposal. Maintainers will love to give you advice on building it and it keeps the app's design coherent.
|
||||||
|
|
||||||
|
### Pull Request Requirements:
|
||||||
|
- Tests
|
||||||
|
- [Signed Off](https://github.com/docker/docker/blob/master/CONTRIBUTING.md#sign-your-work)
|
||||||
|
|
||||||
## Code Guidelines
|
## Code Guidelines
|
||||||
|
|
||||||
### Javascript
|
### Javascript
|
||||||
|
|
41
README.md
|
@ -1,9 +1,11 @@
|
||||||
[](https://circleci.com/gh/kitematic/kitematic/tree/master)
|
[](https://circleci.com/gh/kitematic/kitematic/tree/master)
|
||||||
|
[](https://coveralls.io/r/kitematic/kitematic?branch=master)
|
||||||
[](http://app.bithound.io/kitematic/kitematic)
|
[](http://app.bithound.io/kitematic/kitematic)
|
||||||
|
[](https://gitter.im/kitematic/kitematic?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Kitematic is a simple application for managing Docker containers on Mac OS X.
|
Kitematic is a simple application for managing Docker containers on Mac OS X and Windows (coming soon!).
|
||||||
|
|
||||||
## Installing Kitematic
|
## Installing Kitematic
|
||||||
|
|
||||||
|
@ -13,21 +15,23 @@ Kitematic is a simple application for managing Docker containers on Mac OS X.
|
||||||
|
|
||||||
Kitematic's documentation and other information can be found at [http://kitematic.com/docs](http://kitematic.com/docs).
|
Kitematic's documentation and other information can be found at [http://kitematic.com/docs](http://kitematic.com/docs).
|
||||||
|
|
||||||
### Development
|
## Bugs and Feature Requests
|
||||||
|
|
||||||
- `npm install`
|
Have a bug or a feature request? Please first read the [Issue Guidelines](https://github.com/kitematic/kitematic/blob/master/CONTRIBUTING.md#using-the-issue-tracker) and search for existing and closed issues. If your problem or idea is not addressed yet, [please open a new issue](https://github.com/kitematic/kitematic/issues/new).
|
||||||
|
|
||||||
To run the app in development:
|
## Roadmap & Contributing
|
||||||
|
|
||||||
- `npm start`
|
We welcome all pull requests and contributions that anyone would like to make. The Kitematic team is super happy to support anyone who wants to get involved. Please checkout our [roadmap](ROADMAP.md) that we keep up to date for ideas to help you with contributing. We would love to talk to you about contributing. Please ask us questions and discuss contributions on our [dedicated contributors forum](https://dev.dockerproject.com/c/kitematic).
|
||||||
|
|
||||||
### Building the Mac OS X Package
|
Please read through our [Contributing Guidelines](https://github.com/kitematic/kitematic/blob/master/CONTRIBUTING.md). Included are directions for opening issues, coding standards, and notes on development.
|
||||||
|
|
||||||
- `npm run release`
|
## Community
|
||||||
|
|
||||||
### Unit Tests
|
- For questions on how to use Kitematic, see our [user forum](https://forums.docker.com/c/kitematic).
|
||||||
|
- **#kitematic** on IRC. [Join the channel](http://webchat.freenode.net/?channels=%23kitematic&uio=d4).
|
||||||
- `npm test`
|
- Join the Kitematic [Gitter Channel](https://gitter.im/kitematic/kitematic)
|
||||||
|
- Follow [@kitematic on Twitter](https://twitter.com/kitematic).
|
||||||
|
- Read and subscribe to [the Kitematic Blog](http://blog.kitematic.com).
|
||||||
|
|
||||||
## Uninstalling
|
## Uninstalling
|
||||||
|
|
||||||
|
@ -38,23 +42,6 @@ To run the app in development:
|
||||||
rm -rf ~/Library/Application\ Support/Kitematic
|
rm -rf ~/Library/Application\ Support/Kitematic
|
||||||
```
|
```
|
||||||
|
|
||||||
## Bugs and Feature Requests
|
|
||||||
|
|
||||||
Have a bug or a feature request? Please first read the [Issue Guidelines](https://github.com/kitematic/kitematic/blob/master/CONTRIBUTING.md#using-the-issue-tracker) and search for existing and closed issues. If your problem or idea is not addressed yet, [please open a new issue](https://github.com/kitematic/kitematic/issues/new).
|
|
||||||
|
|
||||||
## Roadmap & Contributing
|
|
||||||
|
|
||||||
We will be accepting pull requests that help with our [roadmap](ROADMAP.md).
|
|
||||||
|
|
||||||
Please read through our [Contributing Guidelines](https://github.com/kitematic/kitematic/blob/master/CONTRIBUTING.md). Included are directions for opening issues, coding standards, and notes on development.
|
|
||||||
|
|
||||||
## Community
|
|
||||||
|
|
||||||
Keep track of development and community news.
|
|
||||||
|
|
||||||
- Follow [@kitematic on Twitter](https://twitter.com/kitematic).
|
|
||||||
- Read and subscribe to [The Kitematic Blog](http://blog.kitematic.com).
|
|
||||||
|
|
||||||
## Copyright and License
|
## Copyright and License
|
||||||
|
|
||||||
Code released under the [Apache license](LICENSE).
|
Code released under the [Apache license](LICENSE).
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
## Kitematic Roadmap
|
## Kitematic Roadmap
|
||||||
|
|
||||||
We will be accepting pull requests that help with our roadmap.
|
|
||||||
|
|
||||||
**January 2015**
|
**January 2015**
|
||||||
|
|
||||||
* Automatic updates
|
* Automatic updates
|
||||||
|
|
|
@ -5,3 +5,6 @@ dependencies:
|
||||||
cache_directories:
|
cache_directories:
|
||||||
- "resources"
|
- "resources"
|
||||||
- "node_modules"
|
- "node_modules"
|
||||||
|
notify:
|
||||||
|
webhooks:
|
||||||
|
- url: https://coveralls.io/webhook
|
||||||
|
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.5 KiB |
|
@ -6,6 +6,6 @@
|
||||||
<title>Kitematic</title>
|
<title>Kitematic</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script src="Main.js"></script>
|
<script src="Startup.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
29
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "Kitematic",
|
"name": "Kitematic",
|
||||||
"version": "0.5.11",
|
"version": "0.5.13",
|
||||||
"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,7 +12,7 @@
|
||||||
"bugs": "https://github.com/kitematic/kitematic/issues",
|
"bugs": "https://github.com/kitematic/kitematic/issues",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "gulp",
|
"start": "gulp",
|
||||||
"test": "jest",
|
"test": "jest --coverage",
|
||||||
"release": "gulp release",
|
"release": "gulp release",
|
||||||
"release:beta": "gulp release --beta",
|
"release:beta": "gulp release --beta",
|
||||||
"preinstall": "./util/deps",
|
"preinstall": "./util/deps",
|
||||||
|
@ -28,30 +28,36 @@
|
||||||
"jest": {
|
"jest": {
|
||||||
"scriptPreprocessor": "<rootDir>/util/preprocessor.js",
|
"scriptPreprocessor": "<rootDir>/util/preprocessor.js",
|
||||||
"setupEnvScriptFile": "<rootDir>/util/testenv.js",
|
"setupEnvScriptFile": "<rootDir>/util/testenv.js",
|
||||||
|
"collectCoverage": true,
|
||||||
|
"testDirectoryName": "src",
|
||||||
|
"testPathIgnorePatterns": [
|
||||||
|
"/node_modules/",
|
||||||
|
"^((?!-test).)*$"
|
||||||
|
],
|
||||||
"unmockedModulePathPatterns": [
|
"unmockedModulePathPatterns": [
|
||||||
|
"stream",
|
||||||
"tty",
|
"tty",
|
||||||
"net",
|
"net",
|
||||||
"crypto",
|
"crypto",
|
||||||
"stream",
|
"<rootDir>/node_modules/.*JSONStream",
|
||||||
"<rootDir>/node_modules/object-assign",
|
"<rootDir>/node_modules/object-assign",
|
||||||
"<rootDir>/node_modules/underscore",
|
"<rootDir>/node_modules/underscore",
|
||||||
"<rootDir>/node_modules/react",
|
"<rootDir>/node_modules/bluebird"
|
||||||
"<rootDir>/node_modules/bluebird",
|
|
||||||
"<rootDir>/node_modules/babel"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"docker-version": "1.5.0",
|
"docker-version": "1.5.0",
|
||||||
"docker-machine-version": "0.1.0-kitematic-0.5.10",
|
"docker-machine-version": "0.1.0-kitematic-0.5.10",
|
||||||
"atom-shell-version": "0.21.3",
|
"atom-shell-version": "0.21.3",
|
||||||
"virtualbox-version": "4.3.24",
|
"virtualbox-version": "4.3.26",
|
||||||
"virtualbox-filename": "VirtualBox-4.3.24.pkg",
|
"virtualbox-filename": "VirtualBox-4.3.26.pkg",
|
||||||
"virtualbox-checksum": "100eee21df3808fc1b1e461e86f10b6d2a748935c5f31bc665f4ff0777e44a0b",
|
"virtualbox-checksum": "668f61c95efe37f8fc65cafe95b866fba64e37f2492dfc1e2b44a7ac3dcafa3b",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-to-html": "0.3.0",
|
"ansi-to-html": "0.3.0",
|
||||||
"async": "^0.9.0",
|
"async": "^0.9.0",
|
||||||
"bluebird": "^2.9.12",
|
"bluebird": "^2.9.12",
|
||||||
"bugsnag-js": "^2.4.7",
|
"bugsnag-js": "^2.4.7",
|
||||||
"dockerode": "^2.0.7",
|
"coveralls": "^2.11.2",
|
||||||
|
"dockerode": "^2.1.1",
|
||||||
"exec": "0.2.0",
|
"exec": "0.2.0",
|
||||||
"jquery": "^2.1.3",
|
"jquery": "^2.1.3",
|
||||||
"minimist": "^1.1.0",
|
"minimist": "^1.1.0",
|
||||||
|
@ -76,6 +82,7 @@
|
||||||
"gulp-cssmin": "^0.1.6",
|
"gulp-cssmin": "^0.1.6",
|
||||||
"gulp-download-atom-shell": "0.0.4",
|
"gulp-download-atom-shell": "0.0.4",
|
||||||
"gulp-if": "^1.2.5",
|
"gulp-if": "^1.2.5",
|
||||||
|
"gulp-insert": "^0.4.0",
|
||||||
"gulp-less": "^3.0.1",
|
"gulp-less": "^3.0.1",
|
||||||
"gulp-livereload": "^3.8.0",
|
"gulp-livereload": "^3.8.0",
|
||||||
"gulp-plumber": "^0.6.6",
|
"gulp-plumber": "^0.6.6",
|
||||||
|
@ -83,7 +90,7 @@
|
||||||
"gulp-shell": "^0.3.0",
|
"gulp-shell": "^0.3.0",
|
||||||
"gulp-sourcemaps": "^1.5.0",
|
"gulp-sourcemaps": "^1.5.0",
|
||||||
"gulp-util": "^3.0.4",
|
"gulp-util": "^3.0.4",
|
||||||
"jest-cli": "^0.4.0",
|
"jest-cli": "kitematic/jest",
|
||||||
"jsxhint": "^0.12.1",
|
"jsxhint": "^0.12.1",
|
||||||
"react-tools": "^0.12.2",
|
"react-tools": "^0.12.2",
|
||||||
"run-sequence": "^1.0.2"
|
"run-sequence": "^1.0.2"
|
||||||
|
|
|
@ -107,7 +107,7 @@ var ContainerDetailsSubheader = React.createClass({
|
||||||
var container = this.props.container;
|
var container = this.props.container;
|
||||||
var terminal = path.join(process.cwd(), 'resources', 'terminal');
|
var terminal = path.join(process.cwd(), 'resources', 'terminal');
|
||||||
machine.ip().then(ip => {
|
machine.ip().then(ip => {
|
||||||
var cmd = [terminal, 'ssh', '-o', 'UserKnownHostsFile=/dev/null', '-o', 'LogLevel=quiet', '-o', 'StrictHostKeyChecking=no', '-i', '~/.docker/machine/machines/' + machine.name() + '/id_rsa', 'docker@' + ip, '-t', 'docker', 'exec', '-i', '-t', container.Name, 'sh'];
|
var cmd = [terminal, 'ssh', '-p', '22', '-o', 'UserKnownHostsFile=/dev/null', '-o', 'LogLevel=quiet', '-o', 'StrictHostKeyChecking=no', '-i', '~/.docker/machine/machines/' + machine.name() + '/id_rsa', 'docker@' + ip, '-t', 'docker', 'exec', '-i', '-t', container.Name, 'sh'];
|
||||||
exec(cmd, function (stderr, stdout, code) {
|
exec(cmd, function (stderr, stdout, code) {
|
||||||
if (code) {
|
if (code) {
|
||||||
console.log(stderr);
|
console.log(stderr);
|
||||||
|
|
|
@ -74,7 +74,9 @@ var ContainerHome = React.createClass({
|
||||||
if (this.props.error) {
|
if (this.props.error) {
|
||||||
body = (
|
body = (
|
||||||
<div className="details-progress">
|
<div className="details-progress">
|
||||||
<h3>There was a problem connecting to the Docker Engine in the VirtualBox VM.<br/>This could be caused because this Mac is currently connected to a VPN, blocking access to the VM. If the issue persists, please <a onClick={this.handleErrorClick}>file a ticket on our GitHub repo.</a></h3>
|
<h3>An error occurred:</h3>
|
||||||
|
<h2>{this.props.error.statusCode} {this.props.error.reason} - {this.props.error.json}</h2>
|
||||||
|
<h3>If you feel that this error is invalid, please <a onClick={this.handleErrorClick}>file a ticket on our GitHub repo.</a></h3>
|
||||||
<Radial progress={100} error={true} thick={true} transparent={true}/>
|
<Radial progress={100} error={true} thick={true} transparent={true}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,19 +2,38 @@ var _ = require('underscore');
|
||||||
var React = require('react/addons');
|
var React = require('react/addons');
|
||||||
var RetinaImage = require('react-retina-image');
|
var RetinaImage = require('react-retina-image');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var exec = require('exec');
|
var shell = require('shell');
|
||||||
|
var util = require('./Util');
|
||||||
var metrics = require('./Metrics');
|
var metrics = require('./Metrics');
|
||||||
var Router = require('react-router');
|
var Router = require('react-router');
|
||||||
|
var ContainerStore = require('./ContainerStore');
|
||||||
|
|
||||||
var ContainerHomeFolder = React.createClass({
|
var ContainerHomeFolder = React.createClass({
|
||||||
mixins: [Router.State, Router.Navigation],
|
mixins: [Router.State, Router.Navigation],
|
||||||
handleClickFolder: function (path) {
|
handleClickFolder: function (hostVolume, containerVolume) {
|
||||||
metrics.track('Opened Volume Directory', {
|
metrics.track('Opened Volume Directory', {
|
||||||
from: 'home'
|
from: 'home'
|
||||||
});
|
});
|
||||||
exec(['open', path], function (err) {
|
|
||||||
if (err) { throw err; }
|
if (hostVolume.indexOf(process.env.HOME) === -1) {
|
||||||
});
|
var volumes = _.clone(this.props.container.Volumes);
|
||||||
|
var newHostVolume = path.join(util.home(), 'Kitematic', this.props.container.Name, containerVolume);
|
||||||
|
volumes[containerVolume] = newHostVolume;
|
||||||
|
var binds = _.pairs(volumes).map(function (pair) {
|
||||||
|
return pair[1] + ':' + pair[0];
|
||||||
|
});
|
||||||
|
ContainerStore.updateContainer(this.props.container.Name, {
|
||||||
|
Binds: binds
|
||||||
|
}, function (err) {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
shell.showItemInFolder(newHostVolume);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
shell.showItemInFolder(hostVolume);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
handleClickChangeFolders: function () {
|
handleClickChangeFolders: function () {
|
||||||
metrics.track('Viewed Volume Settings', {
|
metrics.track('Viewed Volume Settings', {
|
||||||
|
@ -23,24 +42,21 @@ var ContainerHomeFolder = React.createClass({
|
||||||
this.transitionTo('containerSettingsVolumes', {name: this.getParams().name});
|
this.transitionTo('containerSettingsVolumes', {name: this.getParams().name});
|
||||||
},
|
},
|
||||||
render: function () {
|
render: function () {
|
||||||
var folders;
|
if (!this.props.container) {
|
||||||
if (this.props.container) {
|
return false;
|
||||||
var self = this;
|
|
||||||
folders = _.map(self.props.container.Volumes, function (val, key) {
|
|
||||||
var firstFolder = key.split(path.sep)[1];
|
|
||||||
if (!val || val.indexOf(process.env.HOME) === -1) {
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<div key={key} className="folder" onClick={self.handleClickFolder.bind(self, val)}>
|
|
||||||
<RetinaImage src="folder.png" />
|
|
||||||
<div className="text">{firstFolder}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (this.props.container && this.props.container.Volumes && _.keys(this.props.container.Volumes).length > 0 && this.props.container.State.Running) {
|
|
||||||
|
var folders = _.map(this.props.container.Volumes, (val, key) => {
|
||||||
|
var firstFolder = key.split(path.sep)[1];
|
||||||
|
return (
|
||||||
|
<div key={key} className="folder" onClick={this.handleClickFolder.bind(this, val, key)}>
|
||||||
|
<RetinaImage src="folder.png" />
|
||||||
|
<div className="text">{firstFolder}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.props.container.Volumes && _.keys(this.props.container.Volumes).length > 0 && this.props.container.State.Running) {
|
||||||
return (
|
return (
|
||||||
<div className="folders wrapper">
|
<div className="folders wrapper">
|
||||||
<h4>Edit Files</h4>
|
<h4>Edit Files</h4>
|
||||||
|
@ -51,9 +67,7 @@ var ContainerHomeFolder = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return false;
|
||||||
<div></div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -52,6 +52,9 @@ var ContainerHomeLogs = React.createClass({
|
||||||
var logs = this.state.logs.map(function (l, i) {
|
var logs = this.state.logs.map(function (l, i) {
|
||||||
return <p key={i} dangerouslySetInnerHTML={{__html: l}}></p>;
|
return <p key={i} dangerouslySetInnerHTML={{__html: l}}></p>;
|
||||||
});
|
});
|
||||||
|
if (logs.length === 0) {
|
||||||
|
logs = "No logs for this container.";
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="mini-logs wrapper">
|
<div className="mini-logs wrapper">
|
||||||
<h4>Logs</h4>
|
<h4>Logs</h4>
|
||||||
|
|
|
@ -37,9 +37,6 @@ var ContainerHomePreview = React.createClass({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
componentDidUpdate: function () {
|
|
||||||
this.reload();
|
|
||||||
},
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
clearInterval(this.timer);
|
clearInterval(this.timer);
|
||||||
},
|
},
|
||||||
|
|
|
@ -45,6 +45,9 @@ var ContainerLogs = React.createClass({
|
||||||
var logs = this.state.logs.map(function (l, i) {
|
var logs = this.state.logs.map(function (l, i) {
|
||||||
return <p key={i} dangerouslySetInnerHTML={{__html: l}}></p>;
|
return <p key={i} dangerouslySetInnerHTML={{__html: l}}></p>;
|
||||||
});
|
});
|
||||||
|
if (logs.length === 0) {
|
||||||
|
logs = "No logs for this container.";
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="details-panel details-logs logs">
|
<div className="details-panel details-logs logs">
|
||||||
{logs}
|
{logs}
|
||||||
|
|
|
@ -31,6 +31,21 @@ var ContainerSettingsVolumes = React.createClass({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
handleRemoveVolumeClick: function (dockerVol) {
|
||||||
|
metrics.track('Removed Volume Directory', {
|
||||||
|
from: 'settings'
|
||||||
|
});
|
||||||
|
var volumes = _.clone(this.props.container.Volumes);
|
||||||
|
delete volumes[dockerVol];
|
||||||
|
var binds = _.pairs(volumes).map(function (pair) {
|
||||||
|
return pair[1] + ':' + pair[0];
|
||||||
|
});
|
||||||
|
ContainerStore.updateContainer(this.props.container.Name, {
|
||||||
|
Binds: binds
|
||||||
|
}, function (err) {
|
||||||
|
if (err) { console.log(err); }
|
||||||
|
});
|
||||||
|
},
|
||||||
handleOpenVolumeClick: function (path) {
|
handleOpenVolumeClick: function (path) {
|
||||||
metrics.track('Opened Volume Directory', {
|
metrics.track('Opened Volume Directory', {
|
||||||
from: 'settings'
|
from: 'settings'
|
||||||
|
@ -50,6 +65,7 @@ var ContainerSettingsVolumes = React.createClass({
|
||||||
<span>
|
<span>
|
||||||
<a className="value-right">No Folder</a>
|
<a className="value-right">No Folder</a>
|
||||||
<a className="btn btn-action small" onClick={self.handleChooseVolumeClick.bind(self, key)}>Change</a>
|
<a className="btn btn-action small" onClick={self.handleChooseVolumeClick.bind(self, key)}>Change</a>
|
||||||
|
<a className="btn btn-action small" onClick={self.handleRemoveVolumeClick.bind(self, key)}>Remove</a>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -57,6 +73,7 @@ var ContainerSettingsVolumes = React.createClass({
|
||||||
<span>
|
<span>
|
||||||
<a className="value-right" onClick={self.handleOpenVolumeClick.bind(self, val)}>{val.replace(process.env.HOME, '~')}</a>
|
<a className="value-right" onClick={self.handleOpenVolumeClick.bind(self, val)}>{val.replace(process.env.HOME, '~')}</a>
|
||||||
<a className="btn btn-action small" onClick={self.handleChooseVolumeClick.bind(self, key)}>Change</a>
|
<a className="btn btn-action small" onClick={self.handleChooseVolumeClick.bind(self, key)}>Change</a>
|
||||||
|
<a className="btn btn-action small" onClick={self.handleRemoveVolumeClick.bind(self, key)}>Remove</a>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
var _ = require('underscore');
|
var _ = require('underscore');
|
||||||
var EventEmitter = require('events').EventEmitter;
|
var EventEmitter = require('events').EventEmitter;
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var path = require('path');
|
|
||||||
var assign = require('object-assign');
|
var assign = require('object-assign');
|
||||||
var docker = require('./Docker');
|
var docker = require('./Docker');
|
||||||
var metrics = require('./Metrics');
|
var metrics = require('./Metrics');
|
||||||
var registry = require('./Registry');
|
var registry = require('./Registry');
|
||||||
var logstore = require('./LogStore');
|
var logstore = require('./LogStore');
|
||||||
var bugsnag = require('bugsnag-js');
|
var bugsnag = require('bugsnag-js');
|
||||||
var util = require('./Util');
|
|
||||||
|
|
||||||
var _placeholders = {};
|
var _placeholders = {};
|
||||||
var _containers = {};
|
var _containers = {};
|
||||||
|
@ -59,6 +57,12 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
||||||
var data = JSON.parse(str);
|
var data = JSON.parse(str);
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
|
||||||
|
if (data.error) {
|
||||||
|
_error = data.error;
|
||||||
|
callback(data.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (data.status && (data.status === 'Pulling dependent layers' || data.status.indexOf('already being pulled by another client') !== -1)) {
|
if (data.status && (data.status === 'Pulling dependent layers' || data.status.indexOf('already being pulled by another client') !== -1)) {
|
||||||
blockedCallback();
|
blockedCallback();
|
||||||
return;
|
return;
|
||||||
|
@ -85,7 +89,8 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
||||||
progressCallback(totalProgress);
|
progressCallback(totalProgress);
|
||||||
});
|
});
|
||||||
stream.on('end', function () {
|
stream.on('end', function () {
|
||||||
callback();
|
callback(_error);
|
||||||
|
_error = null;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -93,38 +98,22 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
||||||
},
|
},
|
||||||
_startContainer: function (name, containerData, callback) {
|
_startContainer: function (name, containerData, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
docker.client().getImage(containerData.Image).inspect(function (err, data) {
|
var binds = containerData.Binds || [];
|
||||||
|
var startopts = {
|
||||||
|
Binds: binds
|
||||||
|
};
|
||||||
|
if (containerData.NetworkSettings && containerData.NetworkSettings.Ports) {
|
||||||
|
startopts.PortBindings = containerData.NetworkSettings.Ports;
|
||||||
|
} else{
|
||||||
|
startopts.PublishAllPorts = true;
|
||||||
|
}
|
||||||
|
var container = docker.client().getContainer(name);
|
||||||
|
container.start(startopts, function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var binds = containerData.Binds || [];
|
self.fetchContainer(name, callback);
|
||||||
if (data.Config.Volumes) {
|
|
||||||
_.each(data.Config.Volumes, function (value, key) {
|
|
||||||
var existingBind = _.find(binds, b => {
|
|
||||||
return b.indexOf(':' + key) !== -1;
|
|
||||||
});
|
|
||||||
if (!existingBind) {
|
|
||||||
binds.push(path.join(util.home(), 'Kitematic', name, key)+ ':' + key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
var startopts = {
|
|
||||||
Binds: binds
|
|
||||||
};
|
|
||||||
if (containerData.NetworkSettings && containerData.NetworkSettings.Ports) {
|
|
||||||
startopts.PortBindings = containerData.NetworkSettings.Ports;
|
|
||||||
} else{
|
|
||||||
startopts.PublishAllPorts = true;
|
|
||||||
}
|
|
||||||
var container = docker.client().getContainer(name);
|
|
||||||
container.start(startopts, function (err) {
|
|
||||||
if (err) {
|
|
||||||
callback(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.fetchContainer(name, callback);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
_createContainer: function (name, containerData, callback) {
|
_createContainer: function (name, containerData, callback) {
|
||||||
|
@ -138,6 +127,9 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
||||||
if (containerData.Config && containerData.Config.Image) {
|
if (containerData.Config && containerData.Config.Image) {
|
||||||
containerData.Image = containerData.Config.Image;
|
containerData.Image = containerData.Config.Image;
|
||||||
}
|
}
|
||||||
|
if (!containerData.Env && containerData.Config && containerData.Config.Env) {
|
||||||
|
containerData.Env = containerData.Config.Env;
|
||||||
|
}
|
||||||
existing.kill(function () {
|
existing.kill(function () {
|
||||||
existing.remove(function () {
|
existing.remove(function () {
|
||||||
docker.client().createContainer(containerData, function (err) {
|
docker.client().createContainer(containerData, function (err) {
|
||||||
|
@ -145,11 +137,7 @@ var ContainerStore = assign(Object.create(EventEmitter.prototype), {
|
||||||
callback(err, null);
|
callback(err, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (containerData.State && !containerData.State.Running) {
|
self._startContainer(name, containerData, callback);
|
||||||
self.fetchContainer(containerData.name, callback);
|
|
||||||
} else {
|
|
||||||
self._startContainer(name, containerData, callback);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,6 +5,7 @@ var ContainerStore = require('./ContainerStore');
|
||||||
var metrics = require('./Metrics');
|
var metrics = require('./Metrics');
|
||||||
var OverlayTrigger = require('react-bootstrap').OverlayTrigger;
|
var OverlayTrigger = require('react-bootstrap').OverlayTrigger;
|
||||||
var Tooltip = require('react-bootstrap').Tooltip;
|
var Tooltip = require('react-bootstrap').Tooltip;
|
||||||
|
var util = require('./Util');
|
||||||
|
|
||||||
var ImageCard = React.createClass({
|
var ImageCard = React.createClass({
|
||||||
getInitialState: function () {
|
getInitialState: function () {
|
||||||
|
@ -30,16 +31,33 @@ var ImageCard = React.createClass({
|
||||||
handleTagOverlayClick: function (name) {
|
handleTagOverlayClick: function (name) {
|
||||||
var $tagOverlay = $(this.getDOMNode()).find('.tag-overlay');
|
var $tagOverlay = $(this.getDOMNode()).find('.tag-overlay');
|
||||||
$tagOverlay.fadeIn(300);
|
$tagOverlay.fadeIn(300);
|
||||||
$.get('https://registry.hub.docker.com/v1/repositories/' + name + '/tags', function (result) {
|
$.get('https://registry.hub.docker.com/v1/repositories/' + name + '/tags', result => {
|
||||||
this.setState({
|
this.setState({
|
||||||
tags: result
|
tags: result
|
||||||
});
|
});
|
||||||
}.bind(this));
|
});
|
||||||
},
|
},
|
||||||
handleCloseTagOverlay: function () {
|
handleCloseTagOverlay: function () {
|
||||||
var $tagOverlay = $(this.getDOMNode()).find('.tag-overlay');
|
var $tagOverlay = $(this.getDOMNode()).find('.tag-overlay');
|
||||||
$tagOverlay.fadeOut(300);
|
$tagOverlay.fadeOut(300);
|
||||||
},
|
},
|
||||||
|
handleRepoClick: function () {
|
||||||
|
var $repoUri = 'https://registry.hub.docker.com/';
|
||||||
|
if (this.props.image.is_official) {
|
||||||
|
$repoUri = $repoUri + "_/";
|
||||||
|
} else {
|
||||||
|
$repoUri = $repoUri + "u/";
|
||||||
|
}
|
||||||
|
util.exec(['open', $repoUri + this.props.image.name]);
|
||||||
|
},
|
||||||
|
componentDidMount: function() {
|
||||||
|
$.get('https://registry.hub.docker.com/v1/repositories/' + this.props.image.name + '/tags', result => {
|
||||||
|
this.setState({
|
||||||
|
tags: result,
|
||||||
|
chosenTag: result[0].name
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
render: function () {
|
render: function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
var name;
|
var name;
|
||||||
|
@ -57,8 +75,8 @@ var ImageCard = React.createClass({
|
||||||
name = (
|
name = (
|
||||||
<div>
|
<div>
|
||||||
<div className="namespace official">{namespace}</div>
|
<div className="namespace official">{namespace}</div>
|
||||||
<OverlayTrigger placement="bottom" overlay={<Tooltip>{this.props.image.name}</Tooltip>}>
|
<OverlayTrigger placement="bottom" overlay={<Tooltip>View on DockerHub</Tooltip>}>
|
||||||
<span className="repo">{repo}</span>
|
<span className="repo" onClick={this.handleRepoClick}>{repo}</span>
|
||||||
</OverlayTrigger>
|
</OverlayTrigger>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -66,8 +84,8 @@ var ImageCard = React.createClass({
|
||||||
name = (
|
name = (
|
||||||
<div>
|
<div>
|
||||||
<div className="namespace">{namespace}</div>
|
<div className="namespace">{namespace}</div>
|
||||||
<OverlayTrigger placement="bottom" overlay={<Tooltip>{this.props.image.name}</Tooltip>}>
|
<OverlayTrigger placement="bottom" overlay={<Tooltip>View on DockerHub</Tooltip>}>
|
||||||
<span className="repo">{repo}</span>
|
<span className="repo" onClick={this.handleRepoClick}>{repo}</span>
|
||||||
</OverlayTrigger>
|
</OverlayTrigger>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
jest.dontMock('../src/SetupStore');
|
jest.dontMock('./SetupStore');
|
||||||
var setupStore = require('../src/SetupStore');
|
var setupStore = require('./SetupStore');
|
||||||
var virtualBox = require('../src/VirtualBox');
|
var virtualBox = require('./VirtualBox');
|
||||||
var util = require('../src/Util');
|
var util = require('./Util');
|
||||||
var machine = require('../src/DockerMachine');
|
var machine = require('./DockerMachine');
|
||||||
var setupUtil = require('../src/SetupUtil');
|
var setupUtil = require('./SetupUtil');
|
||||||
var Promise = require('bluebird');
|
|
||||||
|
|
||||||
describe('SetupStore', function () {
|
describe('SetupStore', function () {
|
||||||
describe('download step', function () {
|
describe('download step', function () {
|
|
@ -0,0 +1 @@
|
||||||
|
require('./Main');
|
|
@ -1,7 +1,6 @@
|
||||||
jest.dontMock('../src/VirtualBox');
|
jest.dontMock('./VirtualBox');
|
||||||
var virtualBox = require('../src/VirtualBox');
|
var virtualBox = require('./VirtualBox');
|
||||||
var util = require('../src/Util');
|
var util = require('./Util');
|
||||||
var Promise = require('bluebird');
|
|
||||||
|
|
||||||
describe('VirtualBox', function () {
|
describe('VirtualBox', function () {
|
||||||
it('returns the right command', function () {
|
it('returns the right command', function () {
|
|
@ -201,7 +201,7 @@
|
||||||
left: -20px;
|
left: -20px;
|
||||||
.at2x('runningwave.png', 20px, 20px);
|
.at2x('runningwave.png', 20px, 20px);
|
||||||
-webkit-animation-name: translatewave;
|
-webkit-animation-name: translatewave;
|
||||||
-webkit-animation-duration: 8.0s;
|
-webkit-animation-duration: 7.0s;
|
||||||
-webkit-animation-iteration-count: infinite;
|
-webkit-animation-iteration-count: infinite;
|
||||||
-webkit-animation-timing-function: linear;
|
-webkit-animation-timing-function: linear;
|
||||||
}
|
}
|
||||||
|
|
|
@ -186,6 +186,7 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.description {
|
.description {
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
.action {
|
.action {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 10px;
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
}
|
}
|
||||||
|
@ -36,11 +35,11 @@
|
||||||
.btn-label {
|
.btn-label {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
color: @brand-action;
|
color: @brand-action;
|
||||||
font-size: 10px;
|
font-size: 9px;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
top: 30px;
|
top: 45px;
|
||||||
&.view {
|
&.view {
|
||||||
left: 6px;
|
left: 7px;
|
||||||
//left: 0px;
|
//left: 0px;
|
||||||
}
|
}
|
||||||
&.restart {
|
&.restart {
|
||||||
|
|